@voyage_ai/v402-web-ts 0.1.0 → 0.1.1

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.
@@ -16,7 +16,7 @@ import {
16
16
  } from "x402/types";
17
17
 
18
18
  // src/types/common.ts
19
- var PROD_BACK_URL = "https://v402.onvoyage.ai/api";
19
+ var PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
20
20
 
21
21
  // src/types/svm.ts
22
22
  import { z } from "zod";
@@ -205,6 +205,40 @@ function onAccountsChanged(callback) {
205
205
  ethereum.removeListener?.("accountsChanged", handler);
206
206
  };
207
207
  }
208
+ function onChainChanged(callback) {
209
+ if (typeof window === "undefined" || !window.ethereum) {
210
+ return () => {
211
+ };
212
+ }
213
+ const ethereum = window.ethereum;
214
+ const handler = (chainId) => {
215
+ console.log("\u{1F504} Chain changed to:", chainId);
216
+ callback(chainId);
217
+ };
218
+ ethereum.on("chainChanged", handler);
219
+ return () => {
220
+ ethereum.removeListener?.("chainChanged", handler);
221
+ };
222
+ }
223
+ function onWalletDisconnect(callback) {
224
+ if (typeof window === "undefined") {
225
+ return () => {
226
+ };
227
+ }
228
+ const solana = window.solana;
229
+ if (!solana) {
230
+ return () => {
231
+ };
232
+ }
233
+ const handler = () => {
234
+ console.log("\u{1F50C} Solana wallet disconnected");
235
+ callback();
236
+ };
237
+ solana.on("disconnect", handler);
238
+ return () => {
239
+ solana.removeListener?.("disconnect", handler);
240
+ };
241
+ }
208
242
 
209
243
  // src/services/svm/payment-header.ts
210
244
  import {
@@ -305,7 +339,14 @@ async function createSvmPaymentHeader(params) {
305
339
  if (typeof wallet?.signTransaction !== "function") {
306
340
  throw new Error("Connected wallet does not support signTransaction");
307
341
  }
308
- const userSignedTx = await wallet.signTransaction(transaction);
342
+ let userSignedTx;
343
+ try {
344
+ userSignedTx = await wallet.signTransaction(transaction);
345
+ console.log("\u2705 Transaction signed successfully");
346
+ } catch (error) {
347
+ console.error("\u274C Failed to sign transaction:", error);
348
+ throw wrapPaymentError(error);
349
+ }
309
350
  const serializedTransaction = Buffer.from(userSignedTx.serialize()).toString("base64");
310
351
  const paymentPayload = {
311
352
  x402Version,
@@ -321,7 +362,7 @@ async function createSvmPaymentHeader(params) {
321
362
  function getDefaultSolanaRpcUrl(network) {
322
363
  const normalized = network.toLowerCase();
323
364
  if (normalized === "solana" || normalized === "solana-mainnet") {
324
- return "https://api.mainnet-beta.solana.com";
365
+ return "https://cathee-fu8ezd-fast-mainnet.helius-rpc.com";
325
366
  } else if (normalized === "solana-devnet") {
326
367
  return "https://api.devnet.solana.com";
327
368
  }
@@ -339,6 +380,26 @@ async function handleSvmPayment(endpoint, config, requestInit) {
339
380
  return initialResponse;
340
381
  }
341
382
  const rawResponse = await initialResponse.json();
383
+ const IGNORED_ERRORS = [
384
+ "X-PAYMENT header is required",
385
+ "missing X-PAYMENT header",
386
+ "payment_required"
387
+ ];
388
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
389
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
390
+ const ERROR_MESSAGES = {
391
+ "insufficient_funds": "Insufficient balance to complete this payment",
392
+ "invalid_signature": "Invalid payment signature",
393
+ "expired": "Payment authorization has expired",
394
+ "already_used": "This payment has already been used",
395
+ "network_mismatch": "Payment network does not match",
396
+ "invalid_payment": "Invalid payment data",
397
+ "verification_failed": "Payment verification failed"
398
+ };
399
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
400
+ const error = new Error(errorMessage);
401
+ throw wrapPaymentError(error);
402
+ }
342
403
  const x402Version = rawResponse.x402Version;
343
404
  const parsedPaymentRequirements = rawResponse.accepts || [];
344
405
  const selectedRequirements = parsedPaymentRequirements.find(
@@ -358,13 +419,22 @@ async function handleSvmPayment(endpoint, config, requestInit) {
358
419
  );
359
420
  }
360
421
  }
361
- const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(network);
362
- const paymentHeader = await createSvmPaymentHeader({
363
- wallet,
364
- paymentRequirements: selectedRequirements,
365
- x402Version,
366
- rpcUrl: effectiveRpcUrl
367
- });
422
+ const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(selectedRequirements.network);
423
+ console.log(`\u{1F4CD} Using Solana RPC: ${effectiveRpcUrl.substring(0, 40)}...`);
424
+ console.log(`\u{1F4CD} Network from backend: ${selectedRequirements.network}`);
425
+ let paymentHeader;
426
+ try {
427
+ paymentHeader = await createSvmPaymentHeader({
428
+ wallet,
429
+ paymentRequirements: selectedRequirements,
430
+ x402Version,
431
+ rpcUrl: effectiveRpcUrl
432
+ });
433
+ console.log("\u2705 Payment header created successfully");
434
+ } catch (error) {
435
+ console.error("\u274C Failed to create payment header:", error);
436
+ throw wrapPaymentError(error);
437
+ }
368
438
  const newInit = {
369
439
  ...requestInit,
370
440
  method: requestInit?.method || "POST",
@@ -374,7 +444,38 @@ async function handleSvmPayment(endpoint, config, requestInit) {
374
444
  "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
375
445
  }
376
446
  };
377
- return await fetch(endpoint, newInit);
447
+ const retryResponse = await fetch(endpoint, newInit);
448
+ if (retryResponse.status === 402) {
449
+ try {
450
+ const retryData = await retryResponse.json();
451
+ const IGNORED_ERRORS2 = [
452
+ "X-PAYMENT header is required",
453
+ "missing X-PAYMENT header",
454
+ "payment_required"
455
+ ];
456
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
457
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
458
+ const ERROR_MESSAGES = {
459
+ "insufficient_funds": "Insufficient balance to complete this payment",
460
+ "invalid_signature": "Invalid payment signature",
461
+ "expired": "Payment authorization has expired",
462
+ "already_used": "This payment has already been used",
463
+ "network_mismatch": "Payment network does not match",
464
+ "invalid_payment": "Invalid payment data",
465
+ "verification_failed": "Payment verification failed"
466
+ };
467
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
468
+ const error = new Error(errorMessage);
469
+ throw wrapPaymentError(error);
470
+ }
471
+ } catch (error) {
472
+ if (error instanceof PaymentOperationError) {
473
+ throw error;
474
+ }
475
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
476
+ }
477
+ }
478
+ return retryResponse;
378
479
  }
379
480
 
380
481
  // src/services/evm/payment-header.ts
@@ -387,6 +488,34 @@ async function createEvmPaymentHeader(params) {
387
488
  if (!paymentRequirements?.asset) {
388
489
  throw new Error("Missing asset (token contract) in payment requirements");
389
490
  }
491
+ if (wallet.getChainId) {
492
+ try {
493
+ const currentChainIdHex = await wallet.getChainId();
494
+ const currentChainId = parseInt(currentChainIdHex, 16);
495
+ if (currentChainId !== chainId) {
496
+ const networkNames = {
497
+ 1: "Ethereum Mainnet",
498
+ 11155111: "Sepolia Testnet",
499
+ 8453: "Base Mainnet",
500
+ 84532: "Base Sepolia Testnet",
501
+ 137: "Polygon Mainnet",
502
+ 42161: "Arbitrum One",
503
+ 10: "Optimism Mainnet"
504
+ };
505
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
506
+ const targetNetworkName = networkNames[chainId] || `Chain ${chainId}`;
507
+ throw new Error(
508
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch your wallet to the correct network.`
509
+ );
510
+ }
511
+ console.log(`\u2705 Chain ID verified: ${chainId}`);
512
+ } catch (error) {
513
+ if (error.message.includes("Network mismatch")) {
514
+ throw wrapPaymentError(error);
515
+ }
516
+ console.warn("Could not verify chainId:", error);
517
+ }
518
+ }
390
519
  const now = Math.floor(Date.now() / 1e3);
391
520
  const nonceBytes = ethers.randomBytes(32);
392
521
  const nonceBytes32 = ethers.hexlify(nonceBytes);
@@ -415,7 +544,14 @@ async function createEvmPaymentHeader(params) {
415
544
  validBefore: String(now + (paymentRequirements.maxTimeoutSeconds || 3600)),
416
545
  nonce: nonceBytes32
417
546
  };
418
- const signature = await wallet.signTypedData(domain, types, authorization);
547
+ let signature;
548
+ try {
549
+ signature = await wallet.signTypedData(domain, types, authorization);
550
+ console.log("\u2705 Signature created successfully");
551
+ } catch (error) {
552
+ console.error("\u274C Failed to create signature:", error);
553
+ throw wrapPaymentError(error);
554
+ }
419
555
  const headerPayload = {
420
556
  x402_version: x402Version,
421
557
  x402Version,
@@ -466,6 +602,26 @@ async function handleEvmPayment(endpoint, config, requestInit) {
466
602
  return initialResponse;
467
603
  }
468
604
  const rawResponse = await initialResponse.json();
605
+ const IGNORED_ERRORS = [
606
+ "X-PAYMENT header is required",
607
+ "missing X-PAYMENT header",
608
+ "payment_required"
609
+ ];
610
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
611
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
612
+ const ERROR_MESSAGES = {
613
+ "insufficient_funds": "Insufficient balance to complete this payment",
614
+ "invalid_signature": "Invalid payment signature",
615
+ "expired": "Payment authorization has expired",
616
+ "already_used": "This payment has already been used",
617
+ "network_mismatch": "Payment network does not match",
618
+ "invalid_payment": "Invalid payment data",
619
+ "verification_failed": "Payment verification failed"
620
+ };
621
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
622
+ const error = new Error(errorMessage);
623
+ throw wrapPaymentError(error);
624
+ }
469
625
  const x402Version = rawResponse.x402Version;
470
626
  const parsedPaymentRequirements = rawResponse.accepts || [];
471
627
  const selectedRequirements = parsedPaymentRequirements.find(
@@ -486,19 +642,81 @@ async function handleEvmPayment(endpoint, config, requestInit) {
486
642
  }
487
643
  }
488
644
  const targetChainId = getChainIdFromNetwork(selectedRequirements.network);
489
- if (wallet.switchChain) {
645
+ let currentChainId;
646
+ if (wallet.getChainId) {
647
+ try {
648
+ const chainIdHex = await wallet.getChainId();
649
+ currentChainId = parseInt(chainIdHex, 16);
650
+ console.log(`\u{1F4CD} Current wallet chain: ${currentChainId}`);
651
+ } catch (error) {
652
+ console.warn("\u26A0\uFE0F Failed to get current chainId:", error);
653
+ }
654
+ }
655
+ const networkNames = {
656
+ 1: "Ethereum Mainnet",
657
+ 11155111: "Sepolia Testnet",
658
+ 8453: "Base Mainnet",
659
+ 84532: "Base Sepolia Testnet",
660
+ 137: "Polygon Mainnet",
661
+ 42161: "Arbitrum One",
662
+ 10: "Optimism Mainnet"
663
+ };
664
+ if (currentChainId && currentChainId !== targetChainId) {
665
+ if (!wallet.switchChain) {
666
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
667
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
668
+ const error = new Error(
669
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch to ${targetNetworkName} manually in your wallet.`
670
+ );
671
+ throw wrapPaymentError(error);
672
+ }
673
+ try {
674
+ console.log(`\u{1F504} Switching to chain ${targetChainId}...`);
675
+ await wallet.switchChain(`0x${targetChainId.toString(16)}`);
676
+ console.log(`\u2705 Successfully switched to chain ${targetChainId}`);
677
+ } catch (error) {
678
+ console.error("\u274C Failed to switch chain:", error);
679
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
680
+ const wrappedError = wrapPaymentError(error);
681
+ let finalError;
682
+ if (wrappedError.code === "USER_REJECTED" /* USER_REJECTED */) {
683
+ finalError = new PaymentOperationError({
684
+ code: wrappedError.code,
685
+ message: wrappedError.message,
686
+ userMessage: `You rejected the network switch request. Please switch to ${targetNetworkName} manually.`,
687
+ originalError: wrappedError.originalError
688
+ });
689
+ } else {
690
+ finalError = new PaymentOperationError({
691
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
692
+ message: wrappedError.message,
693
+ userMessage: `Failed to switch to ${targetNetworkName}. Please switch manually in your wallet.`,
694
+ originalError: wrappedError.originalError
695
+ });
696
+ }
697
+ throw finalError;
698
+ }
699
+ } else if (wallet.switchChain && !currentChainId) {
490
700
  try {
701
+ console.log(`\u{1F504} Attempting to switch to chain ${targetChainId}...`);
491
702
  await wallet.switchChain(`0x${targetChainId.toString(16)}`);
703
+ console.log(`\u2705 Switch attempted successfully`);
492
704
  } catch (error) {
493
- console.warn("Failed to switch chain:", error);
705
+ console.warn("\u26A0\uFE0F Failed to switch chain (best effort):", error);
494
706
  }
495
707
  }
496
- const paymentHeader = await createEvmPaymentHeader({
497
- wallet,
498
- paymentRequirements: selectedRequirements,
499
- x402Version,
500
- chainId: targetChainId
501
- });
708
+ let paymentHeader;
709
+ try {
710
+ paymentHeader = await createEvmPaymentHeader({
711
+ wallet,
712
+ paymentRequirements: selectedRequirements,
713
+ x402Version,
714
+ chainId: targetChainId
715
+ });
716
+ } catch (error) {
717
+ console.error("\u274C Failed to create payment header:", error);
718
+ throw wrapPaymentError(error);
719
+ }
502
720
  const newInit = {
503
721
  ...requestInit,
504
722
  method: requestInit?.method || "POST",
@@ -508,7 +726,38 @@ async function handleEvmPayment(endpoint, config, requestInit) {
508
726
  "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
509
727
  }
510
728
  };
511
- return await fetch(endpoint, newInit);
729
+ const retryResponse = await fetch(endpoint, newInit);
730
+ if (retryResponse.status === 402) {
731
+ try {
732
+ const retryData = await retryResponse.json();
733
+ const IGNORED_ERRORS2 = [
734
+ "X-PAYMENT header is required",
735
+ "missing X-PAYMENT header",
736
+ "payment_required"
737
+ ];
738
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
739
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
740
+ const ERROR_MESSAGES = {
741
+ "insufficient_funds": "Insufficient balance to complete this payment",
742
+ "invalid_signature": "Invalid payment signature",
743
+ "expired": "Payment authorization has expired",
744
+ "already_used": "This payment has already been used",
745
+ "network_mismatch": "Payment network does not match",
746
+ "invalid_payment": "Invalid payment data",
747
+ "verification_failed": "Payment verification failed"
748
+ };
749
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
750
+ const error = new Error(errorMessage);
751
+ throw wrapPaymentError(error);
752
+ }
753
+ } catch (error) {
754
+ if (error instanceof PaymentOperationError) {
755
+ throw error;
756
+ }
757
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
758
+ }
759
+ }
760
+ return retryResponse;
512
761
  }
513
762
 
514
763
  // src/utils/payment-helpers.ts
@@ -549,7 +798,8 @@ async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL) {
549
798
  }
550
799
  response = await handleSvmPayment(endpoint, {
551
800
  wallet: solana,
552
- network: "solana-devnet"
801
+ network: "solana"
802
+ // Will use backend's network configuration
553
803
  });
554
804
  } else if (networkType === "evm" /* EVM */) {
555
805
  if (!window.ethereum) {
@@ -561,12 +811,24 @@ async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL) {
561
811
  address: await signer.getAddress(),
562
812
  signTypedData: async (domain, types, message) => {
563
813
  return await signer.signTypedData(domain, types, message);
814
+ },
815
+ // Get current chain ID from wallet
816
+ getChainId: async () => {
817
+ const network = await provider.getNetwork();
818
+ return `0x${network.chainId.toString(16)}`;
819
+ },
820
+ // Switch to a different chain
821
+ switchChain: async (chainId) => {
822
+ await window.ethereum.request({
823
+ method: "wallet_switchEthereumChain",
824
+ params: [{ chainId }]
825
+ });
564
826
  }
565
827
  };
566
- const network = endpoint.includes("sepolia") ? "base-sepolia" : "base";
567
828
  response = await handleEvmPayment(endpoint, {
568
829
  wallet,
569
- network
830
+ network: "base"
831
+ // Will use backend's network configuration
570
832
  });
571
833
  } else {
572
834
  throw new Error(`\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B: ${networkType}`);
@@ -629,6 +891,138 @@ function getNetworkDisplayName(network) {
629
891
  return displayNames[network.toLowerCase()] || network;
630
892
  }
631
893
 
894
+ // src/utils/payment-error-handler.ts
895
+ function parsePaymentError(error) {
896
+ if (!error) {
897
+ return {
898
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
899
+ message: "Unknown error occurred",
900
+ userMessage: "An unknown error occurred. Please try again.",
901
+ originalError: error
902
+ };
903
+ }
904
+ const errorMessage = error.message || error.toString();
905
+ const errorCode = error.code;
906
+ 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")) {
907
+ return {
908
+ code: "USER_REJECTED" /* USER_REJECTED */,
909
+ message: "User rejected the transaction",
910
+ userMessage: "You rejected the signature request. Please try again if you want to proceed.",
911
+ originalError: error
912
+ };
913
+ }
914
+ if (errorMessage.includes("chainId") && (errorMessage.includes("must match") || errorMessage.includes("does not match"))) {
915
+ const match = errorMessage.match(/chainId.*?"(\d+)".*?active.*?"(\d+)"/i) || errorMessage.match(/chain (\d+).*?chain (\d+)/i);
916
+ if (match) {
917
+ const [, requestedChain, activeChain] = match;
918
+ return {
919
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
920
+ message: `Network mismatch (wallet is on different chain): Requested ${requestedChain}, but wallet is on ${activeChain}`,
921
+ userMessage: `Your wallet is on the wrong network. Please switch to the correct network and try again.`,
922
+ originalError: error
923
+ };
924
+ }
925
+ return {
926
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
927
+ message: "Network mismatch (wallet selected network does not match)",
928
+ userMessage: "Your wallet is on the wrong network. Please switch to the correct network.",
929
+ originalError: error
930
+ };
931
+ }
932
+ if (errorMessage.includes("Network mismatch") || errorMessage.includes("Wrong network") || errorMessage.includes("Incorrect network")) {
933
+ return {
934
+ code: "NETWORK_MISMATCH" /* NETWORK_MISMATCH */,
935
+ message: errorMessage,
936
+ userMessage: "Please switch your wallet to the correct network.",
937
+ originalError: error
938
+ };
939
+ }
940
+ if (errorMessage.includes("locked") || errorMessage.includes("Wallet is locked")) {
941
+ return {
942
+ code: "WALLET_LOCKED" /* WALLET_LOCKED */,
943
+ message: "Wallet is locked",
944
+ userMessage: "Please unlock your wallet and try again.",
945
+ originalError: error
946
+ };
947
+ }
948
+ if (errorMessage.includes("insufficient") && (errorMessage.includes("balance") || errorMessage.includes("funds"))) {
949
+ return {
950
+ code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
951
+ message: "Insufficient balance",
952
+ userMessage: "You don't have enough balance to complete this payment.",
953
+ originalError: error
954
+ };
955
+ }
956
+ if (errorMessage.includes("Failed to switch") || errorMessage.includes("switch chain")) {
957
+ return {
958
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
959
+ message: errorMessage,
960
+ userMessage: "Failed to switch network. Please switch manually in your wallet.",
961
+ originalError: error
962
+ };
963
+ }
964
+ if (errorMessage.includes("not connected") || errorMessage.includes("No wallet") || errorMessage.includes("Connect wallet")) {
965
+ return {
966
+ code: "WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */,
967
+ message: "Wallet not connected",
968
+ userMessage: "Please connect your wallet first.",
969
+ originalError: error
970
+ };
971
+ }
972
+ if (errorMessage.includes("No suitable") || errorMessage.includes("payment requirements") || errorMessage.includes("Missing payTo") || errorMessage.includes("Missing asset")) {
973
+ return {
974
+ code: "INVALID_PAYMENT_REQUIREMENTS" /* INVALID_PAYMENT_REQUIREMENTS */,
975
+ message: errorMessage,
976
+ userMessage: "Invalid payment configuration. Please contact support.",
977
+ originalError: error
978
+ };
979
+ }
980
+ if (errorMessage.includes("exceeds maximum")) {
981
+ return {
982
+ code: "AMOUNT_EXCEEDED" /* AMOUNT_EXCEEDED */,
983
+ message: errorMessage,
984
+ userMessage: "Payment amount exceeds the maximum allowed.",
985
+ originalError: error
986
+ };
987
+ }
988
+ if (errorMessage.includes("signature") || errorMessage.includes("sign") || errorCode === "UNKNOWN_ERROR") {
989
+ return {
990
+ code: "SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
991
+ message: errorMessage,
992
+ userMessage: "Failed to sign the transaction. Please try again.",
993
+ originalError: error
994
+ };
995
+ }
996
+ return {
997
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
998
+ message: errorMessage,
999
+ userMessage: "An unexpected error occurred. Please try again or contact support.",
1000
+ originalError: error
1001
+ };
1002
+ }
1003
+ var PaymentOperationError = class _PaymentOperationError extends Error {
1004
+ constructor(paymentError) {
1005
+ super(paymentError.message);
1006
+ this.name = "PaymentOperationError";
1007
+ this.code = paymentError.code;
1008
+ this.userMessage = paymentError.userMessage;
1009
+ this.originalError = paymentError.originalError;
1010
+ if (Error.captureStackTrace) {
1011
+ Error.captureStackTrace(this, _PaymentOperationError);
1012
+ }
1013
+ }
1014
+ /**
1015
+ * Get a formatted error message for logging
1016
+ */
1017
+ toLogString() {
1018
+ return `[${this.code}] ${this.message} | User Message: ${this.userMessage}`;
1019
+ }
1020
+ };
1021
+ function wrapPaymentError(error) {
1022
+ const parsedError = parsePaymentError(error);
1023
+ return new PaymentOperationError(parsedError);
1024
+ }
1025
+
632
1026
  // src/react/store/walletStore.ts
633
1027
  var WalletStore = class {
634
1028
  constructor() {
@@ -660,6 +1054,29 @@ var WalletStore = class {
660
1054
  }
661
1055
  }
662
1056
  });
1057
+ onChainChanged(() => {
1058
+ const connectedType = getConnectedNetworkType();
1059
+ if (connectedType === "evm" /* EVM */) {
1060
+ console.log("\u26A0\uFE0F Network changed detected - disconnecting wallet");
1061
+ disconnectWallet();
1062
+ this.setState({
1063
+ address: null,
1064
+ networkType: null,
1065
+ error: "Network changed. Please reconnect your wallet."
1066
+ });
1067
+ }
1068
+ });
1069
+ onWalletDisconnect(() => {
1070
+ const connectedType = getConnectedNetworkType();
1071
+ if (connectedType === "solana" /* SOLANA */ || connectedType === "svm" /* SVM */) {
1072
+ console.log("\u26A0\uFE0F Solana wallet disconnected");
1073
+ disconnectWallet();
1074
+ this.setState({
1075
+ address: null,
1076
+ networkType: null
1077
+ });
1078
+ }
1079
+ });
663
1080
  }
664
1081
  async autoReconnect() {
665
1082
  if (!isWalletManuallyDisconnected()) {
@@ -826,7 +1243,190 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
826
1243
  }
827
1244
 
828
1245
  // src/react/components/WalletConnect.tsx
829
- import React from "react";
1246
+ import React, { useState as useState3 } from "react";
1247
+
1248
+ // src/react/styles/inline-styles.ts
1249
+ var isDarkMode = () => {
1250
+ if (typeof window === "undefined") return false;
1251
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
1252
+ };
1253
+ var colors = {
1254
+ // Light mode
1255
+ light: {
1256
+ background: "#fafafa",
1257
+ cardBg: "#ffffff",
1258
+ text: "#0a0a0a",
1259
+ textSecondary: "#737373",
1260
+ primary: "#000000",
1261
+ primaryHover: "#262626",
1262
+ danger: "#ef4444",
1263
+ dangerHover: "#dc2626",
1264
+ success: "#10b981",
1265
+ successHover: "#059669",
1266
+ disabled: "#e5e5e5",
1267
+ disabledText: "#a3a3a3",
1268
+ errorBg: "#fef2f2",
1269
+ errorText: "#dc2626"
1270
+ },
1271
+ // Dark mode
1272
+ dark: {
1273
+ background: "#0a0a0a",
1274
+ cardBg: "#171717",
1275
+ text: "#fafafa",
1276
+ textSecondary: "#a3a3a3",
1277
+ primary: "#ffffff",
1278
+ primaryHover: "#e5e5e5",
1279
+ danger: "#f87171",
1280
+ dangerHover: "#ef4444",
1281
+ success: "#34d399",
1282
+ successHover: "#10b981",
1283
+ disabled: "#262626",
1284
+ disabledText: "#525252",
1285
+ errorBg: "#1c1917",
1286
+ errorText: "#f87171"
1287
+ }
1288
+ };
1289
+ var getColors = () => {
1290
+ return isDarkMode() ? colors.dark : colors.light;
1291
+ };
1292
+ var containerStyle = {
1293
+ width: "100%",
1294
+ maxWidth: "420px",
1295
+ margin: "0 auto"
1296
+ };
1297
+ var getSectionStyle = () => {
1298
+ const c = getColors();
1299
+ return {
1300
+ padding: "1.5rem",
1301
+ background: c.cardBg,
1302
+ borderRadius: "12px"
1303
+ };
1304
+ };
1305
+ var getTitleStyle = () => {
1306
+ const c = getColors();
1307
+ return {
1308
+ margin: "0 0 1.25rem 0",
1309
+ fontSize: "1.125rem",
1310
+ fontWeight: 600,
1311
+ color: c.text,
1312
+ letterSpacing: "-0.01em"
1313
+ };
1314
+ };
1315
+ var buttonsContainerStyle = {
1316
+ display: "flex",
1317
+ flexDirection: "column",
1318
+ gap: "0.75rem"
1319
+ };
1320
+ var walletOptionStyle = {
1321
+ display: "flex",
1322
+ flexDirection: "column",
1323
+ gap: "0.5rem"
1324
+ };
1325
+ var baseButtonStyle = {
1326
+ padding: "0.875rem 1.25rem",
1327
+ fontSize: "0.9375rem",
1328
+ fontWeight: 500,
1329
+ border: "none",
1330
+ borderRadius: "8px",
1331
+ cursor: "pointer",
1332
+ transition: "background-color 0.15s ease, opacity 0.15s ease",
1333
+ outline: "none",
1334
+ letterSpacing: "-0.01em"
1335
+ };
1336
+ var getConnectButtonStyle = (isDisabled, isHovered) => {
1337
+ const c = getColors();
1338
+ return {
1339
+ ...baseButtonStyle,
1340
+ background: isDisabled ? c.disabled : isHovered ? c.primaryHover : c.primary,
1341
+ color: isDarkMode() ? "#000000" : "#ffffff",
1342
+ cursor: isDisabled ? "not-allowed" : "pointer",
1343
+ opacity: isDisabled ? 0.5 : 1
1344
+ };
1345
+ };
1346
+ var getDisconnectButtonStyle = (isHovered) => {
1347
+ const c = getColors();
1348
+ return {
1349
+ ...baseButtonStyle,
1350
+ background: isHovered ? c.dangerHover : c.danger,
1351
+ color: "#ffffff"
1352
+ };
1353
+ };
1354
+ var getPayButtonStyle = (isDisabled, isHovered) => {
1355
+ const c = getColors();
1356
+ return {
1357
+ ...baseButtonStyle,
1358
+ background: isDisabled ? c.disabled : isHovered ? c.successHover : c.success,
1359
+ color: "#ffffff",
1360
+ width: "100%",
1361
+ cursor: isDisabled ? "not-allowed" : "pointer",
1362
+ opacity: isDisabled ? 0.5 : 1
1363
+ };
1364
+ };
1365
+ var getInstallLinkStyle = (isHovered) => {
1366
+ const c = getColors();
1367
+ return {
1368
+ display: "inline-block",
1369
+ padding: "0.5rem",
1370
+ fontSize: "0.8125rem",
1371
+ color: c.textSecondary,
1372
+ textDecoration: isHovered ? "underline" : "none",
1373
+ textAlign: "center",
1374
+ fontWeight: 500
1375
+ };
1376
+ };
1377
+ var walletAddressStyle = {
1378
+ display: "flex",
1379
+ flexDirection: "column",
1380
+ gap: "0.5rem",
1381
+ marginBottom: "1rem"
1382
+ };
1383
+ var getLabelStyle = () => {
1384
+ const c = getColors();
1385
+ return {
1386
+ fontSize: "0.8125rem",
1387
+ color: c.textSecondary,
1388
+ fontWeight: 500,
1389
+ textTransform: "uppercase",
1390
+ letterSpacing: "0.05em"
1391
+ };
1392
+ };
1393
+ var getAddressStyle = () => {
1394
+ const c = getColors();
1395
+ return {
1396
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1397
+ fontSize: "0.9375rem",
1398
+ fontWeight: 500,
1399
+ color: c.text,
1400
+ letterSpacing: "-0.01em"
1401
+ };
1402
+ };
1403
+ var walletActionsStyle = {
1404
+ margin: "1rem 0"
1405
+ };
1406
+ var getHintStyle = () => {
1407
+ const c = getColors();
1408
+ return {
1409
+ marginTop: "1rem",
1410
+ fontSize: "0.8125rem",
1411
+ color: c.textSecondary,
1412
+ textAlign: "center",
1413
+ lineHeight: "1.5"
1414
+ };
1415
+ };
1416
+ var getErrorStyle = () => {
1417
+ const c = getColors();
1418
+ return {
1419
+ marginTop: "1rem",
1420
+ padding: "0.75rem 1rem",
1421
+ background: c.errorBg,
1422
+ color: c.errorText,
1423
+ borderRadius: "8px",
1424
+ fontSize: "0.8125rem",
1425
+ fontWeight: 500
1426
+ };
1427
+ };
1428
+
1429
+ // src/react/components/WalletConnect.tsx
830
1430
  function WalletConnect({
831
1431
  supportedNetworks = ["solana" /* SOLANA */, "evm" /* EVM */],
832
1432
  className = "",
@@ -834,6 +1434,8 @@ function WalletConnect({
834
1434
  onDisconnect
835
1435
  }) {
836
1436
  const { address, networkType, isConnecting, error, connect, disconnect } = useWallet();
1437
+ const [hoveredButton, setHoveredButton] = useState3(null);
1438
+ const [hoveredLink, setHoveredLink] = useState3(null);
837
1439
  const handleConnect = async (network) => {
838
1440
  try {
839
1441
  await connect(network);
@@ -844,14 +1446,16 @@ function WalletConnect({
844
1446
  disconnect();
845
1447
  onDisconnect?.();
846
1448
  };
847
- return /* @__PURE__ */ React.createElement("div", { className: `x402-wallet-connect ${className}` }, !address ? /* @__PURE__ */ React.createElement("div", { className: "x402-wallet-section" }, /* @__PURE__ */ React.createElement("h3", { className: "x402-section-title" }, "Choose Your Wallet"), supportedNetworks.length === 0 ? /* @__PURE__ */ React.createElement("p", { className: "x402-hint" }, "No payment required") : /* @__PURE__ */ React.createElement("div", { className: "x402-wallet-buttons" }, supportedNetworks.map((network) => {
1449
+ return /* @__PURE__ */ React.createElement("div", { style: { ...containerStyle, ...className ? {} : {} }, className }, !address ? /* @__PURE__ */ React.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ React.createElement("h3", { style: getTitleStyle() }, "Connect Wallet"), supportedNetworks.length === 0 ? /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "No payment required") : /* @__PURE__ */ React.createElement("div", { style: buttonsContainerStyle }, supportedNetworks.map((network) => {
848
1450
  const installed = isWalletInstalled(network);
849
- return /* @__PURE__ */ React.createElement("div", { key: network, className: "x402-wallet-option" }, /* @__PURE__ */ React.createElement(
1451
+ return /* @__PURE__ */ React.createElement("div", { key: network, style: walletOptionStyle }, /* @__PURE__ */ React.createElement(
850
1452
  "button",
851
1453
  {
852
- className: "x402-connect-button",
1454
+ style: getConnectButtonStyle(isConnecting || !installed, hoveredButton === network),
853
1455
  onClick: () => handleConnect(network),
854
- disabled: isConnecting || !installed
1456
+ disabled: isConnecting || !installed,
1457
+ onMouseEnter: () => setHoveredButton(network),
1458
+ onMouseLeave: () => setHoveredButton(null)
855
1459
  },
856
1460
  isConnecting ? "Connecting..." : getNetworkDisplayName(network)
857
1461
  ), !installed && /* @__PURE__ */ React.createElement(
@@ -860,15 +1464,26 @@ function WalletConnect({
860
1464
  href: getWalletInstallUrl(network),
861
1465
  target: "_blank",
862
1466
  rel: "noopener noreferrer",
863
- className: "x402-install-link"
1467
+ style: getInstallLinkStyle(hoveredLink === network),
1468
+ onMouseEnter: () => setHoveredLink(network),
1469
+ onMouseLeave: () => setHoveredLink(null)
864
1470
  },
865
1471
  "Install Wallet"
866
1472
  ));
867
- })), error && /* @__PURE__ */ React.createElement("p", { className: "x402-error" }, error), /* @__PURE__ */ React.createElement("p", { className: "x402-hint" }, "To switch accounts, please change it in your wallet extension")) : /* @__PURE__ */ React.createElement("div", { className: "x402-wallet-info" }, /* @__PURE__ */ React.createElement("div", { className: "x402-wallet-address" }, /* @__PURE__ */ React.createElement("span", { className: "x402-wallet-label" }, "Connected ", networkType && `(${getNetworkDisplayName(networkType)})`, ":"), /* @__PURE__ */ React.createElement("span", { className: "x402-address" }, formatAddress(address))), /* @__PURE__ */ React.createElement("div", { className: "x402-wallet-actions" }, /* @__PURE__ */ React.createElement("button", { className: "x402-disconnect-button", onClick: handleDisconnect }, "Disconnect")), /* @__PURE__ */ React.createElement("p", { className: "x402-hint" }, "Switch account in your wallet to change address")));
1473
+ })), error && /* @__PURE__ */ React.createElement("p", { style: getErrorStyle() }, error), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "To switch accounts, please change it in your wallet extension")) : /* @__PURE__ */ React.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ React.createElement("div", { style: walletAddressStyle }, /* @__PURE__ */ React.createElement("span", { style: getLabelStyle() }, "Connected ", networkType && `(${getNetworkDisplayName(networkType)})`), /* @__PURE__ */ React.createElement("span", { style: getAddressStyle() }, formatAddress(address))), /* @__PURE__ */ React.createElement("div", { style: walletActionsStyle }, /* @__PURE__ */ React.createElement(
1474
+ "button",
1475
+ {
1476
+ style: getDisconnectButtonStyle(hoveredButton === "disconnect"),
1477
+ onClick: handleDisconnect,
1478
+ onMouseEnter: () => setHoveredButton("disconnect"),
1479
+ onMouseLeave: () => setHoveredButton(null)
1480
+ },
1481
+ "Disconnect"
1482
+ )), /* @__PURE__ */ React.createElement("p", { style: getHintStyle() }, "Switch account in your wallet to change address")));
868
1483
  }
869
1484
 
870
1485
  // src/react/components/PaymentButton.tsx
871
- import React2 from "react";
1486
+ import React2, { useState as useState4 } from "react";
872
1487
  function PaymentButton({
873
1488
  endpoint,
874
1489
  className = "",
@@ -881,6 +1496,7 @@ function PaymentButton({
881
1496
  }) {
882
1497
  const { networkType } = useWallet();
883
1498
  const { isProcessing, setIsProcessing, setResult, setError, error } = usePayment();
1499
+ const [isHovered, setIsHovered] = useState4(false);
884
1500
  const handleClick = async () => {
885
1501
  if (!networkType) {
886
1502
  const errorMsg = "Please connect wallet first";
@@ -904,15 +1520,19 @@ function PaymentButton({
904
1520
  onFinish?.();
905
1521
  }
906
1522
  };
1523
+ const isDisabled = disabled || isProcessing || !networkType;
907
1524
  return /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(
908
1525
  "button",
909
1526
  {
910
- className: `x402-pay-button ${className}`,
1527
+ style: getPayButtonStyle(isDisabled, isHovered),
1528
+ className,
911
1529
  onClick: handleClick,
912
- disabled: disabled || isProcessing || !networkType
1530
+ disabled: isDisabled,
1531
+ onMouseEnter: () => setIsHovered(true),
1532
+ onMouseLeave: () => setIsHovered(false)
913
1533
  },
914
1534
  isProcessing ? "Processing..." : children
915
- ), error && /* @__PURE__ */ React2.createElement("p", { className: "x402-error" }, error));
1535
+ ), error && /* @__PURE__ */ React2.createElement("p", { style: getErrorStyle() }, error));
916
1536
  }
917
1537
  export {
918
1538
  PaymentButton,