@voyage_ai/v402-web-ts 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,64 @@
1
- // src/react/hooks/useWalletStore.ts
2
- import { useSyncExternalStore } from "react";
1
+ var __getOwnPropNames = Object.getOwnPropertyNames;
2
+ var __esm = (fn, res) => function __init() {
3
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
4
+ };
5
+
6
+ // src/types/common.ts
7
+ var PROD_BACK_URL, DEV_BACK_URL;
8
+ var init_common = __esm({
9
+ "src/types/common.ts"() {
10
+ "use strict";
11
+ PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
12
+ DEV_BACK_URL = "http://localhost:3000/api/pay";
13
+ }
14
+ });
15
+
16
+ // src/types/svm.ts
17
+ import { z } from "zod";
18
+ import { ExactSvmPayloadSchema } from "x402/types";
19
+ var SolanaNetworkSchema, SolanaPaymentPayloadSchema;
20
+ var init_svm = __esm({
21
+ "src/types/svm.ts"() {
22
+ "use strict";
23
+ SolanaNetworkSchema = z.enum([
24
+ "solana-devnet",
25
+ "solana",
26
+ "solana-mainnet"
27
+ // Alias for mainnet
28
+ ]);
29
+ SolanaPaymentPayloadSchema = z.object({
30
+ x402Version: z.literal(1),
31
+ scheme: z.literal("exact"),
32
+ network: SolanaNetworkSchema,
33
+ payload: ExactSvmPayloadSchema
34
+ });
35
+ }
36
+ });
37
+
38
+ // src/types/evm.ts
39
+ import { z as z2 } from "zod";
40
+ import { ExactEvmPayloadSchema } from "x402/types";
41
+ var EvmNetworkSchema, EvmPaymentPayloadSchema;
42
+ var init_evm = __esm({
43
+ "src/types/evm.ts"() {
44
+ "use strict";
45
+ EvmNetworkSchema = z2.enum([
46
+ "ethereum",
47
+ "sepolia",
48
+ "base",
49
+ "base-sepolia",
50
+ "polygon",
51
+ "arbitrum",
52
+ "optimism"
53
+ ]);
54
+ EvmPaymentPayloadSchema = z2.object({
55
+ x402Version: z2.literal(1),
56
+ scheme: z2.literal("exact"),
57
+ network: EvmNetworkSchema,
58
+ payload: ExactEvmPayloadSchema
59
+ });
60
+ }
61
+ });
3
62
 
4
63
  // src/types/index.ts
5
64
  import {
@@ -9,53 +68,18 @@ import {
9
68
  SettleResponseSchema,
10
69
  SupportedPaymentKindSchema,
11
70
  SupportedPaymentKindsResponseSchema,
12
- ExactSvmPayloadSchema as ExactSvmPayloadSchema2,
13
- ExactEvmPayloadSchema as ExactEvmPayloadSchema2,
14
71
  SupportedSVMNetworks,
15
72
  SvmNetworkToChainId
16
73
  } from "x402/types";
17
-
18
- // src/types/common.ts
19
- var PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
20
-
21
- // src/types/svm.ts
22
- import { z } from "zod";
23
- import { ExactSvmPayloadSchema } from "x402/types";
24
- var SolanaNetworkSchema = z.enum([
25
- "solana-devnet",
26
- "solana",
27
- "solana-mainnet"
28
- // Alias for mainnet
29
- ]);
30
- var SolanaPaymentPayloadSchema = z.object({
31
- x402Version: z.literal(1),
32
- scheme: z.literal("exact"),
33
- network: SolanaNetworkSchema,
34
- payload: ExactSvmPayloadSchema
35
- });
36
-
37
- // src/types/evm.ts
38
- import { z as z2 } from "zod";
39
- import { ExactEvmPayloadSchema } from "x402/types";
40
- var EvmNetworkSchema = z2.enum([
41
- "ethereum",
42
- "sepolia",
43
- "base",
44
- "base-sepolia",
45
- "polygon",
46
- "arbitrum",
47
- "optimism"
48
- ]);
49
- var EvmPaymentPayloadSchema = z2.object({
50
- x402Version: z2.literal(1),
51
- scheme: z2.literal("exact"),
52
- network: EvmNetworkSchema,
53
- payload: ExactEvmPayloadSchema
74
+ var init_types = __esm({
75
+ "src/types/index.ts"() {
76
+ "use strict";
77
+ init_svm();
78
+ init_evm();
79
+ }
54
80
  });
55
81
 
56
82
  // src/utils/wallet.ts
57
- var WALLET_DISCONNECTED_KEY = "wallet_manually_disconnected";
58
- var CONNECTED_NETWORK_TYPE_KEY = "connected_network_type";
59
83
  function isWalletInstalled(networkType) {
60
84
  if (typeof window === "undefined") {
61
85
  return false;
@@ -76,22 +100,50 @@ function formatAddress(address) {
76
100
  }
77
101
  return `${address.slice(0, 6)}...${address.slice(-4)}`;
78
102
  }
79
- function markWalletDisconnected() {
103
+ function getDisconnectedNetworks() {
104
+ if (typeof window === "undefined") {
105
+ return {};
106
+ }
107
+ try {
108
+ const cached = localStorage.getItem(WALLET_DISCONNECTED_NETWORKS_KEY);
109
+ return cached ? JSON.parse(cached) : {};
110
+ } catch (error) {
111
+ return {};
112
+ }
113
+ }
114
+ function markWalletDisconnected(networkType) {
80
115
  if (typeof window !== "undefined") {
81
- localStorage.setItem(WALLET_DISCONNECTED_KEY, "true");
82
- localStorage.removeItem(CONNECTED_NETWORK_TYPE_KEY);
116
+ if (networkType) {
117
+ const disconnected = getDisconnectedNetworks();
118
+ disconnected[networkType] = true;
119
+ localStorage.setItem(WALLET_DISCONNECTED_NETWORKS_KEY, JSON.stringify(disconnected));
120
+ } else {
121
+ localStorage.setItem(WALLET_DISCONNECTED_KEY, "true");
122
+ localStorage.removeItem(CONNECTED_NETWORK_TYPE_KEY);
123
+ }
83
124
  }
84
125
  }
85
- function clearWalletDisconnection() {
126
+ function clearWalletDisconnection(networkType) {
86
127
  if (typeof window !== "undefined") {
87
- localStorage.removeItem(WALLET_DISCONNECTED_KEY);
128
+ if (networkType) {
129
+ const disconnected = getDisconnectedNetworks();
130
+ delete disconnected[networkType];
131
+ localStorage.setItem(WALLET_DISCONNECTED_NETWORKS_KEY, JSON.stringify(disconnected));
132
+ } else {
133
+ localStorage.removeItem(WALLET_DISCONNECTED_KEY);
134
+ }
88
135
  }
89
136
  }
90
- function isWalletManuallyDisconnected() {
137
+ function isWalletManuallyDisconnected(networkType) {
91
138
  if (typeof window === "undefined") {
92
139
  return false;
93
140
  }
94
- return localStorage.getItem(WALLET_DISCONNECTED_KEY) === "true";
141
+ if (networkType) {
142
+ const disconnected = getDisconnectedNetworks();
143
+ return disconnected[networkType] === true;
144
+ } else {
145
+ return localStorage.getItem(WALLET_DISCONNECTED_KEY) === "true";
146
+ }
95
147
  }
96
148
  function saveConnectedNetworkType(networkType) {
97
149
  if (typeof window !== "undefined") {
@@ -116,8 +168,60 @@ function getWalletInstallUrl(networkType) {
116
168
  return "#";
117
169
  }
118
170
  }
171
+ function getAllWalletAddresses() {
172
+ if (typeof window === "undefined") {
173
+ return {};
174
+ }
175
+ try {
176
+ const cached = localStorage.getItem(WALLET_ADDRESSES_KEY);
177
+ return cached ? JSON.parse(cached) : {};
178
+ } catch (error) {
179
+ console.error("Failed to parse wallet addresses cache:", error);
180
+ return {};
181
+ }
182
+ }
183
+ function saveWalletAddress(networkType, address) {
184
+ if (typeof window === "undefined") {
185
+ return;
186
+ }
187
+ const addresses = getAllWalletAddresses();
188
+ addresses[networkType] = address;
189
+ localStorage.setItem(WALLET_ADDRESSES_KEY, JSON.stringify(addresses));
190
+ }
191
+ function getCachedWalletAddress(networkType) {
192
+ const addresses = getAllWalletAddresses();
193
+ return addresses[networkType] || null;
194
+ }
195
+ function removeWalletAddress(networkType) {
196
+ if (typeof window === "undefined") {
197
+ return;
198
+ }
199
+ const addresses = getAllWalletAddresses();
200
+ delete addresses[networkType];
201
+ localStorage.setItem(WALLET_ADDRESSES_KEY, JSON.stringify(addresses));
202
+ }
203
+ var WALLET_DISCONNECTED_KEY, WALLET_DISCONNECTED_NETWORKS_KEY, CONNECTED_NETWORK_TYPE_KEY, WALLET_ADDRESSES_KEY;
204
+ var init_wallet = __esm({
205
+ "src/utils/wallet.ts"() {
206
+ "use strict";
207
+ WALLET_DISCONNECTED_KEY = "wallet_manually_disconnected";
208
+ WALLET_DISCONNECTED_NETWORKS_KEY = "wallet_disconnected_networks";
209
+ CONNECTED_NETWORK_TYPE_KEY = "connected_network_type";
210
+ WALLET_ADDRESSES_KEY = "wallet_addresses_cache";
211
+ }
212
+ });
213
+
214
+ // src/react/index.ts
215
+ import "./styles.css";
216
+
217
+ // src/react/hooks/useWalletStore.ts
218
+ import { useSyncExternalStore } from "react";
219
+
220
+ // src/utils/index.ts
221
+ init_wallet();
119
222
 
120
223
  // src/utils/wallet-connect.ts
224
+ init_wallet();
121
225
  async function connectWallet(networkType) {
122
226
  if (typeof window === "undefined") {
123
227
  throw new Error("\u8BF7\u5728\u6D4F\u89C8\u5668\u73AF\u5883\u4E2D\u4F7F\u7528");
@@ -152,13 +256,11 @@ async function connectWallet(networkType) {
152
256
  default:
153
257
  throw new Error("\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B");
154
258
  }
155
- clearWalletDisconnection();
259
+ clearWalletDisconnection(networkType);
156
260
  saveConnectedNetworkType(networkType);
261
+ saveWalletAddress(networkType, address);
157
262
  return address;
158
263
  }
159
- function disconnectWallet() {
160
- markWalletDisconnected();
161
- }
162
264
  async function getCurrentWallet(networkType) {
163
265
  if (typeof window === "undefined") {
164
266
  return null;
@@ -167,28 +269,36 @@ async function getCurrentWallet(networkType) {
167
269
  if (!type) {
168
270
  return null;
169
271
  }
272
+ const cachedAddress = getCachedWalletAddress(type);
170
273
  try {
274
+ let currentAddress = null;
171
275
  switch (type) {
172
276
  case "evm" /* EVM */: {
173
- if (!window.ethereum) return null;
277
+ if (!window.ethereum) return cachedAddress;
174
278
  const accounts = await window.ethereum.request({
175
279
  method: "eth_accounts",
176
280
  params: []
177
281
  });
178
- return accounts && accounts.length > 0 ? accounts[0] : null;
282
+ currentAddress = accounts && accounts.length > 0 ? accounts[0] : null;
283
+ break;
179
284
  }
180
285
  case "solana" /* SOLANA */:
181
286
  case "svm" /* SVM */: {
182
287
  const solana = window.solana;
183
- if (!solana || !solana.isConnected) return null;
184
- return solana.publicKey?.toString() || null;
288
+ if (!solana || !solana.isConnected) return cachedAddress;
289
+ currentAddress = solana.publicKey?.toString() || null;
290
+ break;
185
291
  }
186
292
  default:
187
- return null;
293
+ return cachedAddress;
188
294
  }
295
+ if (currentAddress && currentAddress !== cachedAddress) {
296
+ saveWalletAddress(type, currentAddress);
297
+ }
298
+ return currentAddress || cachedAddress;
189
299
  } catch (error) {
190
300
  console.error("Failed to get current wallet:", error);
191
- return null;
301
+ return cachedAddress;
192
302
  }
193
303
  }
194
304
  function onAccountsChanged(callback) {
@@ -239,6 +349,18 @@ function onWalletDisconnect(callback) {
239
349
  solana.removeListener?.("disconnect", handler);
240
350
  };
241
351
  }
352
+ async function switchNetwork(networkType) {
353
+ const cachedAddress = getCachedWalletAddress(networkType);
354
+ if (cachedAddress) {
355
+ saveConnectedNetworkType(networkType);
356
+ clearWalletDisconnection(networkType);
357
+ const currentAddress = await getCurrentWallet(networkType);
358
+ if (currentAddress) {
359
+ return currentAddress;
360
+ }
361
+ }
362
+ return null;
363
+ }
242
364
 
243
365
  // src/services/svm/payment-header.ts
244
366
  import {
@@ -255,11 +377,517 @@ import {
255
377
  TOKEN_2022_PROGRAM_ID,
256
378
  TOKEN_PROGRAM_ID
257
379
  } from "@solana/spl-token";
380
+ async function createSvmPaymentHeader(params) {
381
+ const { wallet, paymentRequirements, x402Version, rpcUrl } = params;
382
+ const connection = new Connection(rpcUrl, "confirmed");
383
+ const feePayer = paymentRequirements?.extra?.feePayer;
384
+ if (typeof feePayer !== "string" || !feePayer) {
385
+ throw new Error("Missing facilitator feePayer in payment requirements (extra.feePayer).");
386
+ }
387
+ const feePayerPubkey = new PublicKey(feePayer);
388
+ const walletAddress = wallet?.publicKey?.toString() || wallet?.address;
389
+ if (!walletAddress) {
390
+ throw new Error("Missing connected Solana wallet address or publicKey");
391
+ }
392
+ const userPubkey = new PublicKey(walletAddress);
393
+ if (!paymentRequirements?.payTo) {
394
+ throw new Error("Missing payTo in payment requirements");
395
+ }
396
+ const destination = new PublicKey(paymentRequirements.payTo);
397
+ const instructions = [];
398
+ instructions.push(
399
+ ComputeBudgetProgram.setComputeUnitLimit({
400
+ units: 7e3
401
+ // Sufficient for SPL token transfer
402
+ })
403
+ );
404
+ instructions.push(
405
+ ComputeBudgetProgram.setComputeUnitPrice({
406
+ microLamports: 1
407
+ // Minimal price
408
+ })
409
+ );
410
+ if (!paymentRequirements.asset) {
411
+ throw new Error("Missing token mint for SPL transfer");
412
+ }
413
+ const mintPubkey = new PublicKey(paymentRequirements.asset);
414
+ const mintInfo = await connection.getAccountInfo(mintPubkey, "confirmed");
415
+ const programId = mintInfo?.owner?.toBase58() === TOKEN_2022_PROGRAM_ID.toBase58() ? TOKEN_2022_PROGRAM_ID : TOKEN_PROGRAM_ID;
416
+ const mint = await getMint(connection, mintPubkey, void 0, programId);
417
+ const sourceAta = await getAssociatedTokenAddress(
418
+ mintPubkey,
419
+ userPubkey,
420
+ false,
421
+ programId
422
+ );
423
+ const destinationAta = await getAssociatedTokenAddress(
424
+ mintPubkey,
425
+ destination,
426
+ false,
427
+ programId
428
+ );
429
+ const sourceAtaInfo = await connection.getAccountInfo(sourceAta, "confirmed");
430
+ if (!sourceAtaInfo) {
431
+ throw new Error(
432
+ `User does not have an Associated Token Account for ${paymentRequirements.asset}. Please create one first or ensure you have the required token.`
433
+ );
434
+ }
435
+ const destAtaInfo = await connection.getAccountInfo(destinationAta, "confirmed");
436
+ if (!destAtaInfo) {
437
+ throw new Error(
438
+ `Destination does not have an Associated Token Account for ${paymentRequirements.asset}. The receiver must create their token account before receiving payments.`
439
+ );
440
+ }
441
+ const amount = BigInt(paymentRequirements.maxAmountRequired);
442
+ instructions.push(
443
+ createTransferCheckedInstruction(
444
+ sourceAta,
445
+ mintPubkey,
446
+ destinationAta,
447
+ userPubkey,
448
+ amount,
449
+ mint.decimals,
450
+ [],
451
+ programId
452
+ )
453
+ );
454
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
455
+ const message2 = new TransactionMessage({
456
+ payerKey: feePayerPubkey,
457
+ recentBlockhash: blockhash,
458
+ instructions
459
+ }).compileToV0Message();
460
+ const transaction = new VersionedTransaction(message2);
461
+ if (typeof wallet?.signTransaction !== "function") {
462
+ throw new Error("Connected wallet does not support signTransaction");
463
+ }
464
+ let userSignedTx;
465
+ try {
466
+ userSignedTx = await wallet.signTransaction(transaction);
467
+ console.log("\u2705 Transaction signed successfully");
468
+ } catch (error) {
469
+ console.error("\u274C Failed to sign transaction:", error);
470
+ throw wrapPaymentError(error);
471
+ }
472
+ const serializedTransaction = Buffer.from(userSignedTx.serialize()).toString("base64");
473
+ const paymentPayload = {
474
+ x402Version,
475
+ scheme: paymentRequirements.scheme,
476
+ network: paymentRequirements.network,
477
+ payload: {
478
+ transaction: serializedTransaction
479
+ }
480
+ };
481
+ const paymentHeader = Buffer.from(JSON.stringify(paymentPayload)).toString("base64");
482
+ return paymentHeader;
483
+ }
484
+ function getDefaultSolanaRpcUrl(network) {
485
+ const normalized = network.toLowerCase();
486
+ if (normalized === "solana" || normalized === "solana-mainnet") {
487
+ return "https://cathee-fu8ezd-fast-mainnet.helius-rpc.com";
488
+ } else if (normalized === "solana-devnet") {
489
+ return "https://api.devnet.solana.com";
490
+ }
491
+ throw new Error(`Unsupported Solana network: ${network}`);
492
+ }
493
+
494
+ // src/services/svm/payment-handler.ts
495
+ init_types();
496
+ async function handleSvmPayment(endpoint, config, requestInit) {
497
+ const { wallet, network, rpcUrl, maxPaymentAmount } = config;
498
+ const initialResponse = await fetch(endpoint, {
499
+ ...requestInit,
500
+ method: requestInit?.method || "POST"
501
+ });
502
+ if (initialResponse.status !== 402) {
503
+ return initialResponse;
504
+ }
505
+ const rawResponse = await initialResponse.json();
506
+ const IGNORED_ERRORS = [
507
+ "X-PAYMENT header is required",
508
+ "missing X-PAYMENT header",
509
+ "payment_required"
510
+ ];
511
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
512
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
513
+ const ERROR_MESSAGES = {
514
+ "insufficient_funds": "Insufficient balance to complete this payment",
515
+ "invalid_signature": "Invalid payment signature",
516
+ "expired": "Payment authorization has expired",
517
+ "already_used": "This payment has already been used",
518
+ "network_mismatch": "Payment network does not match",
519
+ "invalid_payment": "Invalid payment data",
520
+ "verification_failed": "Payment verification failed",
521
+ "invalid_exact_svm_payload_transaction_simulation_failed": "Transaction simulation failed due to insufficient balance. Please check your wallet balance carefully and ensure you have enough funds to cover the payment and transaction fees."
522
+ };
523
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
524
+ const error = new Error(errorMessage);
525
+ throw wrapPaymentError(error);
526
+ }
527
+ const x402Version = rawResponse.x402Version;
528
+ const parsedPaymentRequirements = rawResponse.accepts || [];
529
+ const selectedRequirements = parsedPaymentRequirements.find(
530
+ (req) => req.scheme === "exact" && SolanaNetworkSchema.safeParse(req.network.toLowerCase()).success
531
+ );
532
+ if (!selectedRequirements) {
533
+ console.error(
534
+ "\u274C No suitable Solana payment requirements found. Available networks:",
535
+ parsedPaymentRequirements.map((req) => req.network)
536
+ );
537
+ throw new Error("No suitable Solana payment requirements found");
538
+ }
539
+ if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
540
+ if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
541
+ throw new Error(
542
+ `Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
543
+ );
544
+ }
545
+ }
546
+ const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(selectedRequirements.network);
547
+ console.log(`\u{1F4CD} Using Solana RPC: ${effectiveRpcUrl.substring(0, 40)}...`);
548
+ console.log(`\u{1F4CD} Network from backend: ${selectedRequirements.network}`);
549
+ let paymentHeader;
550
+ try {
551
+ paymentHeader = await createSvmPaymentHeader({
552
+ wallet,
553
+ paymentRequirements: selectedRequirements,
554
+ x402Version,
555
+ rpcUrl: effectiveRpcUrl
556
+ });
557
+ console.log("\u2705 Payment header created successfully");
558
+ } catch (error) {
559
+ console.error("\u274C Failed to create payment header:", error);
560
+ throw wrapPaymentError(error);
561
+ }
562
+ const newInit = {
563
+ ...requestInit,
564
+ method: requestInit?.method || "POST",
565
+ headers: {
566
+ ...requestInit?.headers || {},
567
+ "X-PAYMENT": paymentHeader,
568
+ "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
569
+ }
570
+ };
571
+ const retryResponse = await fetch(endpoint, newInit);
572
+ if (retryResponse.status === 402) {
573
+ try {
574
+ const retryData = await retryResponse.json();
575
+ const IGNORED_ERRORS2 = [
576
+ "X-PAYMENT header is required",
577
+ "missing X-PAYMENT header",
578
+ "payment_required"
579
+ ];
580
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
581
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
582
+ const ERROR_MESSAGES = {
583
+ "insufficient_funds": "Insufficient balance to complete this payment",
584
+ "invalid_signature": "Invalid payment signature",
585
+ "expired": "Payment authorization has expired",
586
+ "already_used": "This payment has already been used",
587
+ "network_mismatch": "Payment network does not match",
588
+ "invalid_payment": "Invalid payment data",
589
+ "verification_failed": "Payment verification failed",
590
+ "invalid_exact_svm_payload_transaction_simulation_failed": "Transaction simulation failed due to insufficient balance. Please check your wallet balance carefully and ensure you have enough funds to cover the payment and transaction fees."
591
+ };
592
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
593
+ const error = new Error(errorMessage);
594
+ throw wrapPaymentError(error);
595
+ }
596
+ } catch (error) {
597
+ if (error instanceof PaymentOperationError) {
598
+ throw error;
599
+ }
600
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
601
+ }
602
+ }
603
+ return retryResponse;
604
+ }
258
605
 
259
606
  // src/services/evm/payment-header.ts
260
607
  import { ethers } from "ethers";
608
+ async function createEvmPaymentHeader(params) {
609
+ const { wallet, paymentRequirements, x402Version, chainId } = params;
610
+ if (!paymentRequirements?.payTo) {
611
+ throw new Error("Missing payTo in payment requirements");
612
+ }
613
+ if (!paymentRequirements?.asset) {
614
+ throw new Error("Missing asset (token contract) in payment requirements");
615
+ }
616
+ if (wallet.getChainId) {
617
+ try {
618
+ const currentChainIdHex = await wallet.getChainId();
619
+ const currentChainId = parseInt(currentChainIdHex, 16);
620
+ if (currentChainId !== chainId) {
621
+ const networkNames = {
622
+ 1: "Ethereum Mainnet",
623
+ 11155111: "Sepolia Testnet",
624
+ 8453: "Base Mainnet",
625
+ 84532: "Base Sepolia Testnet",
626
+ 137: "Polygon Mainnet",
627
+ 42161: "Arbitrum One",
628
+ 10: "Optimism Mainnet"
629
+ };
630
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
631
+ const targetNetworkName = networkNames[chainId] || `Chain ${chainId}`;
632
+ throw new Error(
633
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch your wallet to the correct network.`
634
+ );
635
+ }
636
+ console.log(`\u2705 Chain ID verified: ${chainId}`);
637
+ } catch (error) {
638
+ if (error.message.includes("Network mismatch")) {
639
+ throw wrapPaymentError(error);
640
+ }
641
+ console.warn("Could not verify chainId:", error);
642
+ }
643
+ }
644
+ const now = Math.floor(Date.now() / 1e3);
645
+ const nonceBytes = ethers.randomBytes(32);
646
+ const nonceBytes32 = ethers.hexlify(nonceBytes);
647
+ const domain = {
648
+ name: paymentRequirements.extra?.name || "USDC",
649
+ version: paymentRequirements.extra?.version || "2",
650
+ chainId,
651
+ verifyingContract: paymentRequirements.asset
652
+ };
653
+ const types = {
654
+ TransferWithAuthorization: [
655
+ { name: "from", type: "address" },
656
+ { name: "to", type: "address" },
657
+ { name: "value", type: "uint256" },
658
+ { name: "validAfter", type: "uint256" },
659
+ { name: "validBefore", type: "uint256" },
660
+ { name: "nonce", type: "bytes32" }
661
+ ]
662
+ };
663
+ const authorization = {
664
+ from: wallet.address,
665
+ to: paymentRequirements.payTo,
666
+ value: paymentRequirements.maxAmountRequired,
667
+ validAfter: "0",
668
+ // Effective immediately
669
+ validBefore: String(now + (paymentRequirements.maxTimeoutSeconds || 3600)),
670
+ nonce: nonceBytes32
671
+ };
672
+ let signature;
673
+ try {
674
+ signature = await wallet.signTypedData(domain, types, authorization);
675
+ console.log("\u2705 Signature created successfully");
676
+ } catch (error) {
677
+ console.error("\u274C Failed to create signature:", error);
678
+ throw wrapPaymentError(error);
679
+ }
680
+ const headerPayload = {
681
+ x402_version: x402Version,
682
+ x402Version,
683
+ scheme: paymentRequirements.scheme,
684
+ network: paymentRequirements.network,
685
+ payload: {
686
+ signature,
687
+ authorization: {
688
+ from: authorization.from,
689
+ to: authorization.to,
690
+ value: String(authorization.value),
691
+ valid_after: authorization.validAfter,
692
+ validAfter: authorization.validAfter,
693
+ valid_before: authorization.validBefore,
694
+ validBefore: authorization.validBefore,
695
+ nonce: authorization.nonce
696
+ }
697
+ }
698
+ };
699
+ const paymentHeader = btoa(JSON.stringify(headerPayload));
700
+ return paymentHeader;
701
+ }
702
+ function getChainIdFromNetwork(network) {
703
+ const chainIdMap = {
704
+ "ethereum": 1,
705
+ "sepolia": 11155111,
706
+ "base": 8453,
707
+ "base-sepolia": 84532,
708
+ "polygon": 137,
709
+ "arbitrum": 42161,
710
+ "optimism": 10
711
+ };
712
+ const chainId = chainIdMap[network.toLowerCase()];
713
+ if (!chainId) {
714
+ throw new Error(`Unknown network: ${network}`);
715
+ }
716
+ return chainId;
717
+ }
718
+
719
+ // src/services/evm/payment-handler.ts
720
+ init_types();
721
+ async function handleEvmPayment(endpoint, config, requestInit) {
722
+ const { wallet, network, maxPaymentAmount } = config;
723
+ const initialResponse = await fetch(endpoint, {
724
+ ...requestInit,
725
+ method: requestInit?.method || "POST"
726
+ });
727
+ if (initialResponse.status !== 402) {
728
+ return initialResponse;
729
+ }
730
+ const rawResponse = await initialResponse.json();
731
+ const IGNORED_ERRORS = [
732
+ "X-PAYMENT header is required",
733
+ "missing X-PAYMENT header",
734
+ "payment_required"
735
+ ];
736
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
737
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
738
+ const ERROR_MESSAGES = {
739
+ "insufficient_funds": "Insufficient balance to complete this payment",
740
+ "invalid_signature": "Invalid payment signature",
741
+ "expired": "Payment authorization has expired",
742
+ "already_used": "This payment has already been used",
743
+ "network_mismatch": "Payment network does not match",
744
+ "invalid_payment": "Invalid payment data",
745
+ "verification_failed": "Payment verification failed"
746
+ };
747
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
748
+ const error = new Error(errorMessage);
749
+ throw wrapPaymentError(error);
750
+ }
751
+ const x402Version = rawResponse.x402Version;
752
+ const parsedPaymentRequirements = rawResponse.accepts || [];
753
+ const selectedRequirements = parsedPaymentRequirements.find(
754
+ (req) => req.scheme === "exact" && EvmNetworkSchema.safeParse(req.network.toLowerCase()).success
755
+ );
756
+ if (!selectedRequirements) {
757
+ console.error(
758
+ "\u274C No suitable EVM payment requirements found. Available networks:",
759
+ parsedPaymentRequirements.map((req) => req.network)
760
+ );
761
+ throw new Error("No suitable EVM payment requirements found");
762
+ }
763
+ if (maxPaymentAmount && maxPaymentAmount > BigInt(0)) {
764
+ if (BigInt(selectedRequirements.maxAmountRequired) > maxPaymentAmount) {
765
+ throw new Error(
766
+ `Payment amount ${selectedRequirements.maxAmountRequired} exceeds maximum allowed ${maxPaymentAmount}`
767
+ );
768
+ }
769
+ }
770
+ const targetChainId = getChainIdFromNetwork(selectedRequirements.network);
771
+ let currentChainId;
772
+ if (wallet.getChainId) {
773
+ try {
774
+ const chainIdHex = await wallet.getChainId();
775
+ currentChainId = parseInt(chainIdHex, 16);
776
+ console.log(`\u{1F4CD} Current wallet chain: ${currentChainId}`);
777
+ } catch (error) {
778
+ console.warn("\u26A0\uFE0F Failed to get current chainId:", error);
779
+ }
780
+ }
781
+ const networkNames = {
782
+ 1: "Ethereum Mainnet",
783
+ 11155111: "Sepolia Testnet",
784
+ 8453: "Base Mainnet",
785
+ 84532: "Base Sepolia Testnet",
786
+ 137: "Polygon Mainnet",
787
+ 42161: "Arbitrum One",
788
+ 10: "Optimism Mainnet"
789
+ };
790
+ if (currentChainId && currentChainId !== targetChainId) {
791
+ if (!wallet.switchChain) {
792
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
793
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
794
+ const error = new Error(
795
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch to ${targetNetworkName} manually in your wallet.`
796
+ );
797
+ throw wrapPaymentError(error);
798
+ }
799
+ try {
800
+ console.log(`\u{1F504} Switching to chain ${targetChainId}...`);
801
+ await wallet.switchChain(`0x${targetChainId.toString(16)}`);
802
+ console.log(`\u2705 Successfully switched to chain ${targetChainId}`);
803
+ } catch (error) {
804
+ console.error("\u274C Failed to switch chain:", error);
805
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
806
+ const wrappedError = wrapPaymentError(error);
807
+ let finalError;
808
+ if (wrappedError.code === "USER_REJECTED" /* USER_REJECTED */) {
809
+ finalError = new PaymentOperationError({
810
+ code: wrappedError.code,
811
+ message: wrappedError.message,
812
+ userMessage: `You rejected the network switch request. Please switch to ${targetNetworkName} manually.`,
813
+ originalError: wrappedError.originalError
814
+ });
815
+ } else {
816
+ finalError = new PaymentOperationError({
817
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
818
+ message: wrappedError.message,
819
+ userMessage: `Failed to switch to ${targetNetworkName}. Please switch manually in your wallet.`,
820
+ originalError: wrappedError.originalError
821
+ });
822
+ }
823
+ throw finalError;
824
+ }
825
+ } else if (wallet.switchChain && !currentChainId) {
826
+ try {
827
+ console.log(`\u{1F504} Attempting to switch to chain ${targetChainId}...`);
828
+ await wallet.switchChain(`0x${targetChainId.toString(16)}`);
829
+ console.log(`\u2705 Switch attempted successfully`);
830
+ } catch (error) {
831
+ console.warn("\u26A0\uFE0F Failed to switch chain (best effort):", error);
832
+ }
833
+ }
834
+ let paymentHeader;
835
+ try {
836
+ paymentHeader = await createEvmPaymentHeader({
837
+ wallet,
838
+ paymentRequirements: selectedRequirements,
839
+ x402Version,
840
+ chainId: targetChainId
841
+ });
842
+ } catch (error) {
843
+ console.error("\u274C Failed to create payment header:", error);
844
+ throw wrapPaymentError(error);
845
+ }
846
+ const newInit = {
847
+ ...requestInit,
848
+ method: requestInit?.method || "POST",
849
+ headers: {
850
+ ...requestInit?.headers || {},
851
+ "X-PAYMENT": paymentHeader,
852
+ "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
853
+ }
854
+ };
855
+ const retryResponse = await fetch(endpoint, newInit);
856
+ if (retryResponse.status === 402) {
857
+ try {
858
+ const retryData = await retryResponse.json();
859
+ const IGNORED_ERRORS2 = [
860
+ "X-PAYMENT header is required",
861
+ "missing X-PAYMENT header",
862
+ "payment_required"
863
+ ];
864
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
865
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
866
+ const ERROR_MESSAGES = {
867
+ "insufficient_funds": "Insufficient balance to complete this payment",
868
+ "invalid_signature": "Invalid payment signature",
869
+ "expired": "Payment authorization has expired",
870
+ "already_used": "This payment has already been used",
871
+ "network_mismatch": "Payment network does not match",
872
+ "invalid_payment": "Invalid payment data",
873
+ "verification_failed": "Payment verification failed"
874
+ };
875
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
876
+ const error = new Error(errorMessage);
877
+ throw wrapPaymentError(error);
878
+ }
879
+ } catch (error) {
880
+ if (error instanceof PaymentOperationError) {
881
+ throw error;
882
+ }
883
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
884
+ }
885
+ }
886
+ return retryResponse;
887
+ }
261
888
 
262
889
  // src/utils/payment-helpers.ts
890
+ init_common();
263
891
  import { ethers as ethers2 } from "ethers";
264
892
  function parsePaymentRequired(response) {
265
893
  if (response && typeof response === "object") {
@@ -284,6 +912,62 @@ function getSupportedNetworkTypes(paymentRequirements) {
284
912
  });
285
913
  return Array.from(networkTypes);
286
914
  }
915
+ async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL, additionalParams) {
916
+ const fullEndpoint = `${endpoint}/${merchantId}`;
917
+ let response;
918
+ const requestInit = additionalParams && Object.keys(additionalParams).length > 0 ? {
919
+ body: JSON.stringify(additionalParams),
920
+ headers: {
921
+ "Content-Type": "application/json"
922
+ }
923
+ } : {};
924
+ if (networkType === "solana" /* SOLANA */ || networkType === "svm" /* SVM */) {
925
+ const solana = window.solana;
926
+ if (!solana) {
927
+ throw new Error("\u8BF7\u5B89\u88C5 Phantom \u94B1\u5305");
928
+ }
929
+ if (!solana.isConnected) {
930
+ await solana.connect();
931
+ }
932
+ response = await handleSvmPayment(fullEndpoint, {
933
+ wallet: solana,
934
+ network: "solana"
935
+ // Will use backend's network configuration
936
+ }, requestInit);
937
+ } else if (networkType === "evm" /* EVM */) {
938
+ if (!window.ethereum) {
939
+ throw new Error("\u8BF7\u5B89\u88C5 MetaMask \u94B1\u5305");
940
+ }
941
+ const provider = new ethers2.BrowserProvider(window.ethereum);
942
+ const signer = await provider.getSigner();
943
+ const wallet = {
944
+ address: await signer.getAddress(),
945
+ signTypedData: async (domain, types, message2) => {
946
+ return await signer.signTypedData(domain, types, message2);
947
+ },
948
+ // Get current chain ID from wallet
949
+ getChainId: async () => {
950
+ const network = await provider.getNetwork();
951
+ return `0x${network.chainId.toString(16)}`;
952
+ },
953
+ // Switch to a different chain
954
+ switchChain: async (chainId) => {
955
+ await window.ethereum.request({
956
+ method: "wallet_switchEthereumChain",
957
+ params: [{ chainId }]
958
+ });
959
+ }
960
+ };
961
+ response = await handleEvmPayment(fullEndpoint, {
962
+ wallet,
963
+ network: "base"
964
+ // Will use backend's network configuration
965
+ }, requestInit);
966
+ } else {
967
+ throw new Error(`\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B: ${networkType}`);
968
+ }
969
+ return response;
970
+ }
287
971
 
288
972
  // src/utils/network.ts
289
973
  var NETWORK_TYPE_MAP = {
@@ -322,6 +1006,138 @@ function getNetworkDisplayName(network) {
322
1006
  return displayNames[network.toLowerCase()] || network;
323
1007
  }
324
1008
 
1009
+ // src/utils/payment-error-handler.ts
1010
+ function parsePaymentError(error) {
1011
+ if (!error) {
1012
+ return {
1013
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1014
+ message: "Unknown error occurred",
1015
+ userMessage: "An unknown error occurred. Please try again.",
1016
+ originalError: error
1017
+ };
1018
+ }
1019
+ const errorMessage = error.message || error.toString();
1020
+ const errorCode = error.code;
1021
+ if (errorCode === 4001 || errorCode === "ACTION_REJECTED" || errorMessage.includes("User rejected") || errorMessage.includes("user rejected") || errorMessage.includes("User denied") || errorMessage.includes("user denied") || errorMessage.includes("ethers-user-denied")) {
1022
+ return {
1023
+ code: "USER_REJECTED" /* USER_REJECTED */,
1024
+ message: "User rejected the transaction",
1025
+ userMessage: "You rejected the signature request. Please try again if you want to proceed.",
1026
+ originalError: error
1027
+ };
1028
+ }
1029
+ if (errorMessage.includes("chainId") && (errorMessage.includes("must match") || errorMessage.includes("does not match"))) {
1030
+ const match = errorMessage.match(/chainId.*?"(\d+)".*?active.*?"(\d+)"/i) || errorMessage.match(/chain (\d+).*?chain (\d+)/i);
1031
+ if (match) {
1032
+ const [, requestedChain, activeChain] = match;
1033
+ return {
1034
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
1035
+ message: `Network mismatch (wallet is on different chain): Requested ${requestedChain}, but wallet is on ${activeChain}`,
1036
+ userMessage: `Your wallet is on the wrong network. Please switch to the correct network and try again.`,
1037
+ originalError: error
1038
+ };
1039
+ }
1040
+ return {
1041
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
1042
+ message: "Network mismatch (wallet selected network does not match)",
1043
+ userMessage: "Your wallet is on the wrong network. Please switch to the correct network.",
1044
+ originalError: error
1045
+ };
1046
+ }
1047
+ if (errorMessage.includes("Network mismatch") || errorMessage.includes("Wrong network") || errorMessage.includes("Incorrect network")) {
1048
+ return {
1049
+ code: "NETWORK_MISMATCH" /* NETWORK_MISMATCH */,
1050
+ message: errorMessage,
1051
+ userMessage: "Please switch your wallet to the correct network.",
1052
+ originalError: error
1053
+ };
1054
+ }
1055
+ if (errorMessage.includes("locked") || errorMessage.includes("Wallet is locked")) {
1056
+ return {
1057
+ code: "WALLET_LOCKED" /* WALLET_LOCKED */,
1058
+ message: "Wallet is locked",
1059
+ userMessage: "Please unlock your wallet and try again.",
1060
+ originalError: error
1061
+ };
1062
+ }
1063
+ if (errorMessage.includes("insufficient") && (errorMessage.includes("balance") || errorMessage.includes("funds"))) {
1064
+ return {
1065
+ code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
1066
+ message: "Insufficient balance",
1067
+ userMessage: "You don't have enough balance to complete this payment.",
1068
+ originalError: error
1069
+ };
1070
+ }
1071
+ if (errorMessage.includes("Failed to switch") || errorMessage.includes("switch chain")) {
1072
+ return {
1073
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
1074
+ message: errorMessage,
1075
+ userMessage: "Failed to switch network. Please switch manually in your wallet.",
1076
+ originalError: error
1077
+ };
1078
+ }
1079
+ if (errorMessage.includes("not connected") || errorMessage.includes("No wallet") || errorMessage.includes("Connect wallet")) {
1080
+ return {
1081
+ code: "WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */,
1082
+ message: "Wallet not connected",
1083
+ userMessage: "Please connect your wallet first.",
1084
+ originalError: error
1085
+ };
1086
+ }
1087
+ if (errorMessage.includes("No suitable") || errorMessage.includes("payment requirements") || errorMessage.includes("Missing payTo") || errorMessage.includes("Missing asset")) {
1088
+ return {
1089
+ code: "INVALID_PAYMENT_REQUIREMENTS" /* INVALID_PAYMENT_REQUIREMENTS */,
1090
+ message: errorMessage,
1091
+ userMessage: "Invalid payment configuration. Please contact support.",
1092
+ originalError: error
1093
+ };
1094
+ }
1095
+ if (errorMessage.includes("exceeds maximum")) {
1096
+ return {
1097
+ code: "AMOUNT_EXCEEDED" /* AMOUNT_EXCEEDED */,
1098
+ message: errorMessage,
1099
+ userMessage: "Payment amount exceeds the maximum allowed.",
1100
+ originalError: error
1101
+ };
1102
+ }
1103
+ if (errorMessage.includes("signature") || errorMessage.includes("sign") || errorCode === "UNKNOWN_ERROR") {
1104
+ return {
1105
+ code: "SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
1106
+ message: errorMessage,
1107
+ userMessage: "Failed to sign the transaction. Please try again.",
1108
+ originalError: error
1109
+ };
1110
+ }
1111
+ return {
1112
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1113
+ message: errorMessage,
1114
+ userMessage: "An unexpected error occurred. Please try again or contact support.",
1115
+ originalError: error
1116
+ };
1117
+ }
1118
+ var PaymentOperationError = class _PaymentOperationError extends Error {
1119
+ constructor(paymentError) {
1120
+ super(paymentError.message);
1121
+ this.name = "PaymentOperationError";
1122
+ this.code = paymentError.code;
1123
+ this.userMessage = paymentError.userMessage;
1124
+ this.originalError = paymentError.originalError;
1125
+ if (Error.captureStackTrace) {
1126
+ Error.captureStackTrace(this, _PaymentOperationError);
1127
+ }
1128
+ }
1129
+ /**
1130
+ * Get a formatted error message for logging
1131
+ */
1132
+ toLogString() {
1133
+ return `[${this.code}] ${this.message} | User Message: ${this.userMessage}`;
1134
+ }
1135
+ };
1136
+ function wrapPaymentError(error) {
1137
+ const parsedError = parsePaymentError(error);
1138
+ return new PaymentOperationError(parsedError);
1139
+ }
1140
+
325
1141
  // src/react/store/walletStore.ts
326
1142
  var WalletStore = class {
327
1143
  constructor() {
@@ -338,60 +1154,28 @@ var WalletStore = class {
338
1154
  init() {
339
1155
  if (this.initialized) return;
340
1156
  this.initialized = true;
341
- this.autoReconnect();
342
1157
  onAccountsChanged((accounts) => {
343
- const connectedType = getConnectedNetworkType();
344
- if (connectedType === "evm" /* EVM */) {
1158
+ if (this.state.networkType === "evm" /* EVM */) {
345
1159
  if (accounts.length === 0) {
346
1160
  this.setState({ address: null });
347
- console.log("\u{1F50C} Wallet disconnected");
348
- } else {
349
- if (!isWalletManuallyDisconnected()) {
350
- this.setState({ address: accounts[0] });
351
- console.log("\u{1F504} Account changed:", accounts[0]);
352
- }
1161
+ } else if (!isWalletManuallyDisconnected("evm" /* EVM */)) {
1162
+ this.setState({ address: accounts[0] });
1163
+ saveWalletAddress("evm" /* EVM */, accounts[0]);
353
1164
  }
354
1165
  }
355
1166
  });
356
1167
  onChainChanged(() => {
357
- const connectedType = getConnectedNetworkType();
358
- if (connectedType === "evm" /* EVM */) {
359
- console.log("\u26A0\uFE0F Network changed detected - disconnecting wallet");
360
- disconnectWallet();
361
- this.setState({
362
- address: null,
363
- networkType: null,
364
- error: "Network changed. Please reconnect your wallet."
365
- });
1168
+ if (this.state.networkType === "evm" /* EVM */) {
1169
+ this.handleDisconnect("evm" /* EVM */, "Chain changed. Please reconnect your wallet.");
366
1170
  }
367
1171
  });
368
1172
  onWalletDisconnect(() => {
369
- const connectedType = getConnectedNetworkType();
370
- if (connectedType === "solana" /* SOLANA */ || connectedType === "svm" /* SVM */) {
371
- console.log("\u26A0\uFE0F Solana wallet disconnected");
372
- disconnectWallet();
373
- this.setState({
374
- address: null,
375
- networkType: null
376
- });
1173
+ const svmTypes = ["solana" /* SOLANA */, "svm" /* SVM */];
1174
+ if (this.state.networkType && svmTypes.includes(this.state.networkType)) {
1175
+ this.handleDisconnect(this.state.networkType);
377
1176
  }
378
1177
  });
379
1178
  }
380
- async autoReconnect() {
381
- if (!isWalletManuallyDisconnected()) {
382
- const connectedType = getConnectedNetworkType();
383
- if (connectedType) {
384
- const currentAddress = await getCurrentWallet(connectedType);
385
- if (currentAddress) {
386
- this.setState({
387
- address: currentAddress,
388
- networkType: connectedType
389
- });
390
- console.log("\u{1F504} Auto-reconnected wallet:", currentAddress);
391
- }
392
- }
393
- }
394
- }
395
1179
  // Get current state
396
1180
  getState() {
397
1181
  return this.state;
@@ -412,18 +1196,32 @@ var WalletStore = class {
412
1196
  notifyListeners() {
413
1197
  this.listeners.forEach((listener) => listener());
414
1198
  }
1199
+ // Handle wallet disconnect (internal helper)
1200
+ handleDisconnect(networkType, error) {
1201
+ removeWalletAddress(networkType);
1202
+ markWalletDisconnected(networkType);
1203
+ if (typeof window !== "undefined") {
1204
+ localStorage.removeItem("connected_network_type");
1205
+ }
1206
+ this.setState({
1207
+ address: null,
1208
+ networkType: null,
1209
+ error: error || null
1210
+ });
1211
+ }
415
1212
  // Connect wallet
416
1213
  async connect(type) {
1214
+ if (this.state.address && this.state.networkType && this.state.networkType !== type) {
1215
+ saveWalletAddress(this.state.networkType, this.state.address);
1216
+ }
417
1217
  this.setState({ isConnecting: true, error: null });
418
1218
  try {
419
1219
  const walletAddress = await connectWallet(type);
420
- console.log("\u2705 Wallet connected:", walletAddress, "Network:", type);
421
1220
  this.setState({
422
1221
  address: walletAddress,
423
1222
  networkType: type,
424
1223
  isConnecting: false
425
1224
  });
426
- console.log("\u{1F4DD} Store state updated");
427
1225
  } catch (err) {
428
1226
  this.setState({
429
1227
  error: err.message || "Failed to connect wallet",
@@ -432,20 +1230,67 @@ var WalletStore = class {
432
1230
  throw err;
433
1231
  }
434
1232
  }
1233
+ // Switch network (use cached wallet if available)
1234
+ async switchNetwork(type) {
1235
+ if (this.state.address && this.state.networkType) {
1236
+ saveWalletAddress(this.state.networkType, this.state.address);
1237
+ }
1238
+ this.setState({ isConnecting: true, error: null });
1239
+ try {
1240
+ const address = await switchNetwork(type);
1241
+ if (address) {
1242
+ this.setState({
1243
+ address,
1244
+ networkType: type,
1245
+ isConnecting: false
1246
+ });
1247
+ } else {
1248
+ this.setState({
1249
+ address: null,
1250
+ networkType: type,
1251
+ isConnecting: true
1252
+ });
1253
+ await this.connect(type);
1254
+ }
1255
+ } catch (err) {
1256
+ this.setState({
1257
+ error: err.message || "Failed to switch network",
1258
+ isConnecting: false
1259
+ });
1260
+ throw err;
1261
+ }
1262
+ }
435
1263
  // Disconnect wallet
436
1264
  disconnect() {
437
- disconnectWallet();
438
- this.setState({
439
- address: null,
440
- networkType: null,
441
- error: null
442
- });
443
- console.log("\u{1F50C} Wallet disconnected from store");
1265
+ const currentNetwork = this.state.networkType;
1266
+ if (currentNetwork) {
1267
+ this.handleDisconnect(currentNetwork);
1268
+ } else {
1269
+ this.setState({
1270
+ address: null,
1271
+ networkType: null,
1272
+ error: null
1273
+ });
1274
+ }
444
1275
  }
445
1276
  // Clear error
446
1277
  clearError() {
447
1278
  this.setState({ error: null });
448
1279
  }
1280
+ // Ensure network matches expected type (for page-specific network requirements)
1281
+ async ensureNetwork(expectedNetwork) {
1282
+ if (isWalletManuallyDisconnected(expectedNetwork)) {
1283
+ return;
1284
+ }
1285
+ if (this.state.networkType === expectedNetwork && this.state.address) {
1286
+ return;
1287
+ }
1288
+ if (this.state.networkType !== expectedNetwork) {
1289
+ await this.switchNetwork(expectedNetwork);
1290
+ } else if (!this.state.address) {
1291
+ await this.connect(expectedNetwork);
1292
+ }
1293
+ }
449
1294
  };
450
1295
  var walletStore = new WalletStore();
451
1296
  if (typeof window !== "undefined") {
@@ -463,11 +1308,30 @@ function useWallet() {
463
1308
  return {
464
1309
  ...state,
465
1310
  connect: (type) => walletStore.connect(type),
1311
+ switchNetwork: (type) => walletStore.switchNetwork(type),
1312
+ ensureNetwork: (type) => walletStore.ensureNetwork(type),
466
1313
  disconnect: () => walletStore.disconnect(),
467
1314
  clearError: () => walletStore.clearError()
468
1315
  };
469
1316
  }
470
1317
 
1318
+ // src/react/hooks/usePageNetwork.ts
1319
+ import { useEffect } from "react";
1320
+ function usePageNetwork(expectedNetwork, options = {}) {
1321
+ const {
1322
+ autoSwitch = true,
1323
+ switchOnMount = true
1324
+ } = options;
1325
+ const wallet = useWallet();
1326
+ useEffect(() => {
1327
+ if (!autoSwitch || !switchOnMount) return;
1328
+ wallet.ensureNetwork(expectedNetwork).catch((err) => {
1329
+ console.error("Failed to ensure network:", err);
1330
+ });
1331
+ }, [expectedNetwork]);
1332
+ return wallet;
1333
+ }
1334
+
471
1335
  // src/react/hooks/usePayment.ts
472
1336
  import { useCallback, useState } from "react";
473
1337
  function usePayment() {
@@ -499,7 +1363,8 @@ function usePayment() {
499
1363
  }
500
1364
 
501
1365
  // src/react/hooks/usePaymentInfo.ts
502
- import { useEffect, useState as useState2 } from "react";
1366
+ import { useEffect as useEffect2, useState as useState2 } from "react";
1367
+ init_common();
503
1368
  function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL, additionalParams) {
504
1369
  const [paymentInfo, setPaymentInfo] = useState2(null);
505
1370
  const [supportedNetworks, setSupportedNetworks] = useState2([]);
@@ -538,7 +1403,7 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL, additionalParams)
538
1403
  setIsLoading(false);
539
1404
  }
540
1405
  };
541
- useEffect(() => {
1406
+ useEffect2(() => {
542
1407
  fetchPaymentInfo();
543
1408
  }, [endpoint, merchantId]);
544
1409
  return {
@@ -787,8 +1652,572 @@ function WalletConnect({
787
1652
  "Disconnect"
788
1653
  )), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "Switch account in your wallet to change address")));
789
1654
  }
1655
+
1656
+ // src/react/components/V402Checkout.tsx
1657
+ import React3, { useEffect as useEffect3, useState as useState4 } from "react";
1658
+ import { Button, Card, Divider, message, Spin, Tooltip, Typography } from "antd";
1659
+ import {
1660
+ DisconnectOutlined,
1661
+ InfoCircleOutlined,
1662
+ LinkOutlined,
1663
+ LoadingOutlined,
1664
+ LockOutlined,
1665
+ SafetyOutlined
1666
+ } from "@ant-design/icons";
1667
+ init_common();
1668
+
1669
+ // src/react/utils/CryptoIcons.tsx
1670
+ import React2 from "react";
1671
+ var SolanaIcon = ({ width = 16, height = 16, className, style }) => {
1672
+ return /* @__PURE__ */ React2.createElement(
1673
+ "svg",
1674
+ {
1675
+ xmlns: "http://www.w3.org/2000/svg",
1676
+ viewBox: "0 0 16 16",
1677
+ width,
1678
+ height,
1679
+ className,
1680
+ style
1681
+ },
1682
+ /* @__PURE__ */ React2.createElement("desc", null, "Solana SOL Fill Streamline Icon: https://streamlinehq.com"),
1683
+ /* @__PURE__ */ React2.createElement("g", { fill: "none", fillRule: "evenodd" }, /* @__PURE__ */ React2.createElement(
1684
+ "path",
1685
+ {
1686
+ d: "M16 0v16H0V0h16ZM8.395333333333333 15.505333333333333l-0.007333333333333332 0.0013333333333333333 -0.047333333333333324 0.023333333333333334 -0.013333333333333332 0.0026666666666666666 -0.009333333333333332 -0.0026666666666666666 -0.047333333333333324 -0.023333333333333334c-0.006666666666666666 -0.0026666666666666666 -0.012666666666666666 -0.0006666666666666666 -0.016 0.003333333333333333l-0.0026666666666666666 0.006666666666666666 -0.011333333333333334 0.2853333333333333 0.003333333333333333 0.013333333333333332 0.006666666666666666 0.008666666666666666 0.06933333333333333 0.049333333333333326 0.009999999999999998 0.0026666666666666666 0.008 -0.0026666666666666666 0.06933333333333333 -0.049333333333333326 0.008 -0.010666666666666666 0.0026666666666666666 -0.011333333333333334 -0.011333333333333334 -0.2846666666666666c-0.0013333333333333333 -0.006666666666666666 -0.005999999999999999 -0.011333333333333334 -0.011333333333333334 -0.011999999999999999Zm0.17666666666666667 -0.07533333333333334 -0.008666666666666666 0.0013333333333333333 -0.12333333333333332 0.062 -0.006666666666666666 0.006666666666666666 -0.002 0.007333333333333332 0.011999999999999999 0.2866666666666666 0.003333333333333333 0.008 0.005333333333333333 0.004666666666666666 0.134 0.062c0.008 0.0026666666666666666 0.015333333333333332 0 0.019333333333333334 -0.005333333333333333l0.0026666666666666666 -0.009333333333333332 -0.02266666666666667 -0.4093333333333333c-0.002 -0.008 -0.006666666666666666 -0.013333333333333332 -0.013333333333333332 -0.014666666666666665Zm-0.4766666666666666 0.0013333333333333333a0.015333333333333332 0.015333333333333332 0 0 0 -0.018 0.004l-0.004 0.009333333333333332 -0.02266666666666667 0.4093333333333333c0 0.008 0.004666666666666666 0.013333333333333332 0.011333333333333334 0.016l0.009999999999999998 -0.0013333333333333333 0.134 -0.062 0.006666666666666666 -0.005333333333333333 0.0026666666666666666 -0.007333333333333332 0.011333333333333334 -0.2866666666666666 -0.002 -0.008 -0.006666666666666666 -0.006666666666666666 -0.12266666666666666 -0.06133333333333333Z",
1687
+ strokeWidth: "0.6667"
1688
+ }
1689
+ ), /* @__PURE__ */ React2.createElement(
1690
+ "path",
1691
+ {
1692
+ fill: "#000000",
1693
+ d: "M4.862 2.862A0.6666666666666666 0.6666666666666666 0 0 1 5.333333333333333 2.6666666666666665h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.47133333333333327 1.138l-2 2A0.6666666666666666 0.6666666666666666 0 0 1 12 6H3.333333333333333a0.6666666666666666 0.6666666666666666 0 0 1 -0.47133333333333327 -1.138l2 -2Zm-2.1166666666666663 4.156666666666666A0.6666666666666666 0.6666666666666666 0 0 1 3.333333333333333 6.666666666666666h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.5546666666666666 0.29666666666666663l1.3333333333333333 2A0.6666666666666666 0.6666666666666666 0 0 1 13.333333333333332 10H4.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 -0.5546666666666666 -0.29666666666666663l-1.3333333333333333 -2a0.6666666666666666 0.6666666666666666 0 0 1 -0.03333333333333333 -0.6846666666666665Zm1.4499999999999997 3.843333333333333A0.6666666666666666 0.6666666666666666 0 0 1 4.666666666666666 10.666666666666666h8.666666666666666a0.6666666666666666 0.6666666666666666 0 0 1 0.47133333333333327 1.138l-2 2A0.6666666666666666 0.6666666666666666 0 0 1 11.333333333333332 14H2.6666666666666665a0.6666666666666666 0.6666666666666666 0 0 1 -0.47133333333333327 -1.138l2 -2Z",
1694
+ strokeWidth: "0.6667"
1695
+ }
1696
+ ))
1697
+ );
1698
+ };
1699
+ var BaseIcon = ({ width = 24, height = 24, className, style }) => {
1700
+ return /* @__PURE__ */ React2.createElement(
1701
+ "svg",
1702
+ {
1703
+ xmlns: "http://www.w3.org/2000/svg",
1704
+ viewBox: "0 0 24 24",
1705
+ fill: "none",
1706
+ stroke: "#000000",
1707
+ strokeLinecap: "round",
1708
+ strokeLinejoin: "round",
1709
+ width,
1710
+ height,
1711
+ className,
1712
+ style
1713
+ },
1714
+ /* @__PURE__ */ React2.createElement("desc", null, "Brand Coinbase Streamline Icon: https://streamlinehq.com"),
1715
+ /* @__PURE__ */ React2.createElement(
1716
+ "path",
1717
+ {
1718
+ d: "M12.95 22c-4.503 0 -8.445 -3.04 -9.61 -7.413 -1.165 -4.373 0.737 -8.988 4.638 -11.25a9.906 9.906 0 0 1 12.008 1.598l-3.335 3.367a5.185 5.185 0 0 0 -7.354 0.013 5.252 5.252 0 0 0 0 7.393 5.185 5.185 0 0 0 7.354 0.013L20 19.088A9.887 9.887 0 0 1 12.95 22z",
1719
+ strokeWidth: "2"
1720
+ }
1721
+ )
1722
+ );
1723
+ };
1724
+ var getNetworkIcon = (network) => {
1725
+ const networkLower = network.toLowerCase();
1726
+ if (networkLower.includes("solana")) {
1727
+ return SolanaIcon;
1728
+ }
1729
+ if (networkLower.includes("base")) {
1730
+ return BaseIcon;
1731
+ }
1732
+ return BaseIcon;
1733
+ };
1734
+
1735
+ // src/react/components/V402Checkout.tsx
1736
+ var { Title, Text } = Typography;
1737
+ var notify = {
1738
+ success: (title, msg) => {
1739
+ message.success(`${title}: ${msg}`);
1740
+ },
1741
+ error: (title, msg) => {
1742
+ message.error(`${title}: ${msg}`);
1743
+ },
1744
+ info: (title, msg) => {
1745
+ message.info(`${title}: ${msg}`);
1746
+ }
1747
+ };
1748
+ function V402Checkout({
1749
+ checkoutId,
1750
+ headerInfo = {},
1751
+ isModal = false,
1752
+ onPaymentComplete,
1753
+ additionalParams = {},
1754
+ expectedNetwork
1755
+ }) {
1756
+ const {
1757
+ title = "V402Pay - Make x402Pay Easier",
1758
+ subtitle = "onvoyage.ai",
1759
+ tooltipText = "V402Pay - Accept Crypto Payments Easier"
1760
+ } = headerInfo;
1761
+ let endpoint = DEV_BACK_URL;
1762
+ const {
1763
+ supportedNetworks,
1764
+ isLoading: fetchingPaymentInfo,
1765
+ paymentInfo
1766
+ } = usePaymentInfo(checkoutId, endpoint, additionalParams);
1767
+ const targetNetwork = expectedNetwork || supportedNetworks[0];
1768
+ const { address, networkType, disconnect, ensureNetwork } = usePageNetwork(
1769
+ targetNetwork,
1770
+ { autoSwitch: !!targetNetwork, switchOnMount: true }
1771
+ );
1772
+ const { isProcessing, setIsProcessing, result, setResult, error, setError } = usePayment();
1773
+ const [paymentDetails, setPaymentDetails] = useState4(null);
1774
+ const handleDisconnect = () => {
1775
+ disconnect();
1776
+ setResult(null);
1777
+ setError(null);
1778
+ notify.info("Wallet Disconnected", "Your wallet has been disconnected successfully.");
1779
+ };
1780
+ useEffect3(() => {
1781
+ if (paymentInfo && paymentInfo.length > 0) {
1782
+ const firstPayment = paymentInfo[0];
1783
+ const rawAmount = firstPayment.maxAmountRequired?.toString() || "0";
1784
+ const decimals = 6;
1785
+ const humanReadableAmount = (Number(rawAmount) / Math.pow(10, decimals)).toFixed(2);
1786
+ const network = firstPayment.network || "Unknown";
1787
+ const currency = "USDC";
1788
+ setPaymentDetails({
1789
+ amount: humanReadableAmount,
1790
+ currency,
1791
+ network
1792
+ });
1793
+ }
1794
+ }, [paymentInfo]);
1795
+ useEffect3(() => {
1796
+ if (targetNetwork && !fetchingPaymentInfo && ensureNetwork) {
1797
+ ensureNetwork(targetNetwork).catch((err) => {
1798
+ console.error("Failed to ensure network:", err);
1799
+ });
1800
+ }
1801
+ }, [targetNetwork, fetchingPaymentInfo]);
1802
+ const handlePayment = async () => {
1803
+ if (!networkType) {
1804
+ notify.error("Wallet Not Connected", "Please connect your wallet first.");
1805
+ return;
1806
+ }
1807
+ setResult(null);
1808
+ setError(null);
1809
+ setIsProcessing(true);
1810
+ try {
1811
+ const response = await makePayment(networkType, checkoutId, endpoint, additionalParams);
1812
+ const data = await response.json();
1813
+ setResult(data);
1814
+ notify.success("Payment Successful!", "Your payment has been processed successfully.");
1815
+ if (onPaymentComplete) {
1816
+ onPaymentComplete(data);
1817
+ }
1818
+ } catch (err) {
1819
+ const errorMessage = err.message || "Payment failed";
1820
+ setError(errorMessage);
1821
+ notify.error("Payment Failed", errorMessage);
1822
+ } finally {
1823
+ setIsProcessing(false);
1824
+ }
1825
+ };
1826
+ const getNetworkColor = (network) => {
1827
+ if (network.toLowerCase().includes("solana")) return "#14F195";
1828
+ if (network.toLowerCase().includes("evm") || network.toLowerCase().includes("base")) return "#0052FF";
1829
+ return "#8c8c8c";
1830
+ };
1831
+ const NetworkIcon = paymentDetails ? getNetworkIcon(paymentDetails.network) : null;
1832
+ const networkColor = paymentDetails ? getNetworkColor(paymentDetails.network) : "#8c8c8c";
1833
+ const loadingColor = "#8c8c8c";
1834
+ const hasInvalidCheckoutId = !fetchingPaymentInfo && (!paymentInfo || paymentInfo.length === 0);
1835
+ return /* @__PURE__ */ React3.createElement(
1836
+ "div",
1837
+ {
1838
+ className: isModal ? "bg-white" : "h-screen bg-white flex items-center justify-center p-4 overflow-hidden"
1839
+ },
1840
+ /* @__PURE__ */ React3.createElement(
1841
+ "div",
1842
+ {
1843
+ className: "flex gap-4 items-center justify-center",
1844
+ style: {
1845
+ maxWidth: isProcessing || result || error ? "1200px" : "480px",
1846
+ transition: "max-width 0.4s ease-in-out",
1847
+ width: "100%"
1848
+ }
1849
+ },
1850
+ /* @__PURE__ */ React3.createElement(
1851
+ Card,
1852
+ {
1853
+ className: "flex-shrink-0",
1854
+ style: {
1855
+ border: isModal ? "none" : "1px solid #e8e8e8",
1856
+ borderRadius: isModal ? "0" : "16px",
1857
+ boxShadow: isModal ? "none" : "0 4px 24px rgba(0, 0, 0, 0.06)",
1858
+ maxHeight: isModal ? "calc(100vh - 100px)" : "calc(100vh - 32px)",
1859
+ overflow: "auto",
1860
+ width: isModal ? "100%" : "480px",
1861
+ transition: "all 0.4s ease-in-out",
1862
+ transform: result || error ? "translateX(0)" : "translateX(0)"
1863
+ },
1864
+ styles: { body: { padding: isModal ? "0px" : "32px 24px" } }
1865
+ },
1866
+ /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-3 mb-4" }, /* @__PURE__ */ React3.createElement(
1867
+ "div",
1868
+ {
1869
+ className: "w-12 h-12 rounded-xl flex items-center justify-center",
1870
+ style: {
1871
+ background: hasInvalidCheckoutId ? "#ff4d4f" : paymentDetails ? networkColor : loadingColor,
1872
+ transition: "background 0.3s ease"
1873
+ }
1874
+ },
1875
+ hasInvalidCheckoutId ? /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px", color: "white", fontWeight: "bold" } }, "\u2717") : paymentDetails && NetworkIcon ? /* @__PURE__ */ React3.createElement(NetworkIcon, { width: 24, height: 24 }) : /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { fontSize: "20px", color: "white" }, spin: true })
1876
+ ), /* @__PURE__ */ React3.createElement("div", { className: "flex-1" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React3.createElement(Title, { level: 4, style: { margin: 0, fontSize: "18px", fontWeight: 600 } }, title || "Echo Payment OnVoyage"), !hasInvalidCheckoutId && /* @__PURE__ */ React3.createElement(
1877
+ Tooltip,
1878
+ {
1879
+ title: tooltipText,
1880
+ placement: "top"
1881
+ },
1882
+ /* @__PURE__ */ React3.createElement(
1883
+ InfoCircleOutlined,
1884
+ {
1885
+ style: { fontSize: "14px", color: "#8c8c8c", cursor: "help" }
1886
+ }
1887
+ )
1888
+ )), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, subtitle))),
1889
+ /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-5" }, /* @__PURE__ */ React3.createElement("div", { className: "inline-flex items-center justify-center w-12 h-12 rounded-full bg-gray-50 mb-3" }, /* @__PURE__ */ React3.createElement(LockOutlined, { style: { fontSize: "20px", color: "#595959" } })), /* @__PURE__ */ React3.createElement(Title, { level: 3, style: { margin: "0 0 6px 0", fontSize: "20px", fontWeight: 600 } }, "Payment Required"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Pay ", paymentDetails ? `$${paymentDetails.amount} ${paymentDetails.currency}` : "the required amount", " to access")),
1890
+ hasInvalidCheckoutId && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-6" }, /* @__PURE__ */ React3.createElement(
1891
+ "div",
1892
+ {
1893
+ className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
1894
+ style: {
1895
+ background: "linear-gradient(135deg, #ef4444 0%, #f87171 100%)",
1896
+ boxShadow: "0 4px 20px rgba(239, 68, 68, 0.3)"
1897
+ }
1898
+ },
1899
+ /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "!")
1900
+ ), /* @__PURE__ */ React3.createElement(
1901
+ Title,
1902
+ {
1903
+ level: 4,
1904
+ style: { margin: "0 0 12px 0", fontSize: "18px", fontWeight: 600, color: "#262626" }
1905
+ },
1906
+ "Invalid Checkout ID"
1907
+ ), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c", display: "block", marginBottom: "16px" } }, "The checkout ID you provided is invalid or has expired."), /* @__PURE__ */ React3.createElement(
1908
+ "div",
1909
+ {
1910
+ style: {
1911
+ background: "#fef2f2",
1912
+ padding: "16px",
1913
+ borderRadius: "12px",
1914
+ border: "1px solid #fee2e2",
1915
+ marginTop: "16px"
1916
+ }
1917
+ },
1918
+ /* @__PURE__ */ React3.createElement(Text, { style: {
1919
+ fontSize: "13px",
1920
+ color: "#dc2626",
1921
+ lineHeight: "1.6",
1922
+ fontWeight: 500
1923
+ } }, "Failed to load payment information. Please check your checkout ID.")
1924
+ )),
1925
+ !hasInvalidCheckoutId && fetchingPaymentInfo && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-6" }, /* @__PURE__ */ React3.createElement(Text, { style: { color: "#8c8c8c" } }, "Loading payment information...")),
1926
+ !hasInvalidCheckoutId && !fetchingPaymentInfo && !address && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(WalletConnect, { supportedNetworks })),
1927
+ !hasInvalidCheckoutId && address && /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(
1928
+ "div",
1929
+ {
1930
+ className: "bg-gray-50 rounded-lg p-3 mb-4",
1931
+ style: { border: "1px solid #f0f0f0" }
1932
+ },
1933
+ /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between" }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-3 flex-1" }, /* @__PURE__ */ React3.createElement(
1934
+ "div",
1935
+ {
1936
+ className: "w-10 h-10 rounded-full bg-black flex items-center justify-center text-white text-sm font-semibold"
1937
+ },
1938
+ address.slice(0, 2).toUpperCase()
1939
+ ), /* @__PURE__ */ React3.createElement("div", { className: "flex-1 min-w-0" }, /* @__PURE__ */ React3.createElement(Text, { style: {
1940
+ display: "block",
1941
+ fontSize: "12px",
1942
+ color: "#8c8c8c",
1943
+ marginBottom: "2px"
1944
+ } }, "Connected Wallet"), /* @__PURE__ */ React3.createElement(
1945
+ Text,
1946
+ {
1947
+ style: {
1948
+ fontSize: "13px",
1949
+ fontWeight: 600,
1950
+ fontFamily: "Monaco, monospace"
1951
+ }
1952
+ },
1953
+ formatAddress(address)
1954
+ ))), /* @__PURE__ */ React3.createElement(
1955
+ Button,
1956
+ {
1957
+ type: "text",
1958
+ size: "small",
1959
+ icon: /* @__PURE__ */ React3.createElement(DisconnectOutlined, null),
1960
+ onClick: handleDisconnect,
1961
+ style: { color: "#ff4d4f" }
1962
+ }
1963
+ ))
1964
+ ), paymentDetails && /* @__PURE__ */ React3.createElement("div", { className: "bg-gray-50 rounded-lg p-3 mb-4", style: { border: "1px solid #f0f0f0" } }, /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Payment Amount"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "18px", fontWeight: 600 } }, "$", paymentDetails.amount)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Currency"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", fontWeight: 500 } }, paymentDetails.currency)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-center mb-2" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Network"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", fontWeight: 500 } }, paymentDetails.network)), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "6px 0" } }), /* @__PURE__ */ React3.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Wallet Address"), /* @__PURE__ */ React3.createElement(Text, { style: {
1965
+ fontSize: "11px",
1966
+ fontWeight: 500,
1967
+ fontFamily: "Monaco, monospace",
1968
+ wordBreak: "break-all",
1969
+ textAlign: "right",
1970
+ maxWidth: "60%",
1971
+ lineHeight: 1.4
1972
+ } }, address))), /* @__PURE__ */ React3.createElement(
1973
+ "div",
1974
+ {
1975
+ className: "flex items-center justify-center gap-2 mb-3 p-2 rounded-lg",
1976
+ style: { background: "#f6ffed", border: "1px solid #d9f7be" }
1977
+ },
1978
+ /* @__PURE__ */ React3.createElement(SafetyOutlined, { style: { color: "#52c41a", fontSize: "13px" } }),
1979
+ /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#52c41a", fontWeight: 500 } }, "Secure payment powered by v402pay")
1980
+ ), /* @__PURE__ */ React3.createElement(
1981
+ Button,
1982
+ {
1983
+ type: "primary",
1984
+ size: "large",
1985
+ onClick: handlePayment,
1986
+ disabled: isProcessing || !paymentDetails,
1987
+ loading: isProcessing,
1988
+ block: true,
1989
+ style: {
1990
+ height: "44px",
1991
+ fontSize: "14px",
1992
+ fontWeight: 600,
1993
+ borderRadius: "8px",
1994
+ ...!isProcessing && paymentDetails && {
1995
+ background: "#1a1a1a",
1996
+ borderColor: "#1a1a1a"
1997
+ },
1998
+ marginBottom: "10px"
1999
+ }
2000
+ },
2001
+ isProcessing ? "Processing..." : !paymentDetails ? "Loading..." : `Pay $${paymentDetails.amount} ${paymentDetails.currency}`
2002
+ ), /* @__PURE__ */ React3.createElement("div", { className: "text-center" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#8c8c8c" } }, "Don't have USDC?", " "), /* @__PURE__ */ React3.createElement(
2003
+ "a",
2004
+ {
2005
+ href: "https://faucet.circle.com/",
2006
+ target: "_blank",
2007
+ rel: "noopener noreferrer",
2008
+ className: "text-blue-600 hover:text-blue-700 text-sm font-medium inline-flex items-center gap-1"
2009
+ },
2010
+ "Get it here ",
2011
+ /* @__PURE__ */ React3.createElement(LinkOutlined, { style: { fontSize: "12px" } })
2012
+ )), isModal && result && /* @__PURE__ */ React3.createElement(
2013
+ "div",
2014
+ {
2015
+ className: "mt-4 p-4 rounded-lg",
2016
+ style: { background: "#f6ffed", border: "1px solid #b7eb8f" }
2017
+ },
2018
+ /* @__PURE__ */ React3.createElement("div", { className: "text-center" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px" } }, "\u2713"), /* @__PURE__ */ React3.createElement(Text, { style: {
2019
+ fontSize: "14px",
2020
+ color: "#52c41a",
2021
+ fontWeight: 600,
2022
+ marginLeft: "8px"
2023
+ } }, "Payment Successful!"))
2024
+ ), isModal && error && /* @__PURE__ */ React3.createElement(
2025
+ "div",
2026
+ {
2027
+ className: "mt-4 p-4 rounded-lg",
2028
+ style: { background: "#fff2f0", border: "1px solid #ffccc7" }
2029
+ },
2030
+ /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-3" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "20px" } }, "\u2717"), /* @__PURE__ */ React3.createElement(Text, { style: {
2031
+ fontSize: "14px",
2032
+ color: "#ff4d4f",
2033
+ fontWeight: 600,
2034
+ marginLeft: "8px",
2035
+ display: "block",
2036
+ marginTop: "4px"
2037
+ } }, "Payment Failed")),
2038
+ /* @__PURE__ */ React3.createElement(Text, { style: {
2039
+ fontSize: "13px",
2040
+ color: "#ff4d4f",
2041
+ display: "block",
2042
+ textAlign: "center"
2043
+ } }, error)
2044
+ ))
2045
+ ),
2046
+ !isModal && (isProcessing || result || error) && /* @__PURE__ */ React3.createElement(
2047
+ Card,
2048
+ {
2049
+ title: /* @__PURE__ */ React3.createElement("div", { className: "flex items-center gap-2" }, isProcessing && !result && !error ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { color: "#14b8a6", fontSize: "16px" } }), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Processing Payment")) : result ? /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { color: "#52c41a", fontSize: "18px" } }, "\u2713"), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Payment Successful")) : /* @__PURE__ */ React3.createElement(React3.Fragment, null, /* @__PURE__ */ React3.createElement("span", { style: { color: "#ff4d4f", fontSize: "18px" } }, "\u2717"), /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "16px", color: "#262626" } }, "Payment Failed"))),
2050
+ extra: !isProcessing && /* @__PURE__ */ React3.createElement(
2051
+ Button,
2052
+ {
2053
+ type: "text",
2054
+ size: "small",
2055
+ onClick: () => {
2056
+ setResult(null);
2057
+ setError(null);
2058
+ }
2059
+ },
2060
+ "Close"
2061
+ ),
2062
+ style: {
2063
+ border: "1px solid #e8e8e8",
2064
+ borderRadius: "16px",
2065
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.06)",
2066
+ maxHeight: "calc(100vh - 32px)",
2067
+ width: "480px",
2068
+ animation: "slideInRight 0.4s ease-out"
2069
+ },
2070
+ styles: {
2071
+ body: {
2072
+ padding: "24px",
2073
+ maxHeight: "calc(100vh - 120px)",
2074
+ overflow: "auto"
2075
+ }
2076
+ }
2077
+ },
2078
+ isProcessing && !result && !error && /* @__PURE__ */ React3.createElement("div", { className: "text-center py-10" }, /* @__PURE__ */ React3.createElement("div", { className: "relative inline-block" }, /* @__PURE__ */ React3.createElement(
2079
+ "div",
2080
+ {
2081
+ className: "absolute inset-0 rounded-full blur-xl opacity-40",
2082
+ style: {
2083
+ background: "linear-gradient(135deg, #14b8a6 0%, #06b6d4 100%)",
2084
+ animation: "pulse 2s ease-in-out infinite"
2085
+ }
2086
+ }
2087
+ ), /* @__PURE__ */ React3.createElement(
2088
+ Spin,
2089
+ {
2090
+ indicator: /* @__PURE__ */ React3.createElement(LoadingOutlined, { style: { fontSize: 56, color: "#14b8a6" } })
2091
+ }
2092
+ )), /* @__PURE__ */ React3.createElement("div", { className: "mt-6" }, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: { fontSize: "18px", color: "#262626", letterSpacing: "-0.02em" } }, "Verifying Payment")), /* @__PURE__ */ React3.createElement("div", { className: "mt-2 mb-6" }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c", lineHeight: "1.6" } }, "Please wait while we confirm your transaction")), /* @__PURE__ */ React3.createElement(
2093
+ "div",
2094
+ {
2095
+ className: "mt-4 p-4 rounded-xl",
2096
+ style: {
2097
+ background: "linear-gradient(135deg, #f0fdfa 0%, #ecfeff 100%)",
2098
+ border: "1px solid #ccfbf1"
2099
+ }
2100
+ },
2101
+ /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-center gap-2" }, /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "16px" } }, "\u23F1\uFE0F"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "13px", color: "#0f766e", fontWeight: 500 } }, "This may take a few moments"))
2102
+ )),
2103
+ result && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-6" }, /* @__PURE__ */ React3.createElement(
2104
+ "div",
2105
+ {
2106
+ className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
2107
+ style: {
2108
+ background: "linear-gradient(135deg, #10b981 0%, #34d399 100%)",
2109
+ boxShadow: "0 4px 20px rgba(16, 185, 129, 0.3)"
2110
+ }
2111
+ },
2112
+ /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "\u2713")
2113
+ ), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: {
2114
+ fontSize: "20px",
2115
+ color: "#262626",
2116
+ display: "block",
2117
+ marginBottom: "8px"
2118
+ } }, "Payment Successful!"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c" } }, "Your transaction has been confirmed"))), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "20px 0", borderColor: "#f0f0f0" } }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#8c8c8c", fontWeight: 500 } }, "RESPONSE DATA")), /* @__PURE__ */ React3.createElement(
2119
+ "pre",
2120
+ {
2121
+ style: {
2122
+ background: "#fafafa",
2123
+ padding: "20px",
2124
+ borderRadius: "12px",
2125
+ fontSize: "12px",
2126
+ lineHeight: "1.8",
2127
+ overflow: "auto",
2128
+ margin: 0,
2129
+ fontFamily: "Monaco, Courier New, monospace",
2130
+ whiteSpace: "pre-wrap",
2131
+ wordBreak: "break-word",
2132
+ border: "1px solid #e8e8e8",
2133
+ color: "#262626"
2134
+ }
2135
+ },
2136
+ JSON.stringify(result, null, 2)
2137
+ )),
2138
+ error && /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement("div", { className: "text-center mb-6" }, /* @__PURE__ */ React3.createElement(
2139
+ "div",
2140
+ {
2141
+ className: "inline-flex items-center justify-center w-16 h-16 rounded-full mb-4",
2142
+ style: {
2143
+ background: "linear-gradient(135deg, #ef4444 0%, #f87171 100%)",
2144
+ boxShadow: "0 4px 20px rgba(239, 68, 68, 0.3)"
2145
+ }
2146
+ },
2147
+ /* @__PURE__ */ React3.createElement("span", { style: { fontSize: "32px", color: "white" } }, "\u2717")
2148
+ ), /* @__PURE__ */ React3.createElement("div", null, /* @__PURE__ */ React3.createElement(Text, { strong: true, style: {
2149
+ fontSize: "20px",
2150
+ color: "#262626",
2151
+ display: "block",
2152
+ marginBottom: "8px"
2153
+ } }, "Payment Failed"), /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "14px", color: "#8c8c8c" } }, "Something went wrong with your transaction"))), /* @__PURE__ */ React3.createElement(Divider, { style: { margin: "20px 0", borderColor: "#f0f0f0" } }, /* @__PURE__ */ React3.createElement(Text, { style: { fontSize: "12px", color: "#8c8c8c", fontWeight: 500 } }, "ERROR DETAILS")), /* @__PURE__ */ React3.createElement(
2154
+ "div",
2155
+ {
2156
+ style: {
2157
+ background: "#fef2f2",
2158
+ padding: "20px",
2159
+ borderRadius: "12px",
2160
+ border: "1px solid #fee2e2"
2161
+ }
2162
+ },
2163
+ /* @__PURE__ */ React3.createElement(Text, { style: {
2164
+ fontSize: "14px",
2165
+ color: "#dc2626",
2166
+ lineHeight: "1.6",
2167
+ fontWeight: 500
2168
+ } }, error)
2169
+ ), /* @__PURE__ */ React3.createElement("div", { className: "mt-4 text-center" }, /* @__PURE__ */ React3.createElement(
2170
+ Button,
2171
+ {
2172
+ size: "large",
2173
+ onClick: handlePayment,
2174
+ style: {
2175
+ height: "44px",
2176
+ fontSize: "14px",
2177
+ fontWeight: 600,
2178
+ borderRadius: "8px",
2179
+ background: "#1a1a1a",
2180
+ borderColor: "#1a1a1a",
2181
+ color: "white",
2182
+ paddingLeft: "32px",
2183
+ paddingRight: "32px"
2184
+ }
2185
+ },
2186
+ "Try Again"
2187
+ )))
2188
+ )
2189
+ ),
2190
+ /* @__PURE__ */ React3.createElement("style", { dangerouslySetInnerHTML: {
2191
+ __html: `
2192
+ @keyframes slideInRight {
2193
+ from {
2194
+ opacity: 0;
2195
+ transform: translateX(100px);
2196
+ }
2197
+ to {
2198
+ opacity: 1;
2199
+ transform: translateX(0);
2200
+ }
2201
+ }
2202
+
2203
+ @keyframes pulse {
2204
+ 0%, 100% {
2205
+ transform: scale(1);
2206
+ opacity: 0.4;
2207
+ }
2208
+ 50% {
2209
+ transform: scale(1.1);
2210
+ opacity: 0.6;
2211
+ }
2212
+ }
2213
+ `
2214
+ } })
2215
+ );
2216
+ }
790
2217
  export {
2218
+ V402Checkout,
791
2219
  WalletConnect,
2220
+ usePageNetwork,
792
2221
  usePayment,
793
2222
  usePaymentInfo,
794
2223
  useWallet