@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.
@@ -45,7 +45,7 @@ var import_react = require("react");
45
45
  var import_types3 = require("x402/types");
46
46
 
47
47
  // src/types/common.ts
48
- var PROD_BACK_URL = "https://v402.onvoyage.ai/api";
48
+ var PROD_BACK_URL = "https://v402.onvoyage.ai/api/pay";
49
49
 
50
50
  // src/types/svm.ts
51
51
  var import_zod = require("zod");
@@ -234,6 +234,40 @@ function onAccountsChanged(callback) {
234
234
  ethereum.removeListener?.("accountsChanged", handler);
235
235
  };
236
236
  }
237
+ function onChainChanged(callback) {
238
+ if (typeof window === "undefined" || !window.ethereum) {
239
+ return () => {
240
+ };
241
+ }
242
+ const ethereum = window.ethereum;
243
+ const handler = (chainId) => {
244
+ console.log("\u{1F504} Chain changed to:", chainId);
245
+ callback(chainId);
246
+ };
247
+ ethereum.on("chainChanged", handler);
248
+ return () => {
249
+ ethereum.removeListener?.("chainChanged", handler);
250
+ };
251
+ }
252
+ function onWalletDisconnect(callback) {
253
+ if (typeof window === "undefined") {
254
+ return () => {
255
+ };
256
+ }
257
+ const solana = window.solana;
258
+ if (!solana) {
259
+ return () => {
260
+ };
261
+ }
262
+ const handler = () => {
263
+ console.log("\u{1F50C} Solana wallet disconnected");
264
+ callback();
265
+ };
266
+ solana.on("disconnect", handler);
267
+ return () => {
268
+ solana.removeListener?.("disconnect", handler);
269
+ };
270
+ }
237
271
 
238
272
  // src/services/svm/payment-header.ts
239
273
  var import_web3 = require("@solana/web3.js");
@@ -322,7 +356,14 @@ async function createSvmPaymentHeader(params) {
322
356
  if (typeof wallet?.signTransaction !== "function") {
323
357
  throw new Error("Connected wallet does not support signTransaction");
324
358
  }
325
- const userSignedTx = await wallet.signTransaction(transaction);
359
+ let userSignedTx;
360
+ try {
361
+ userSignedTx = await wallet.signTransaction(transaction);
362
+ console.log("\u2705 Transaction signed successfully");
363
+ } catch (error) {
364
+ console.error("\u274C Failed to sign transaction:", error);
365
+ throw wrapPaymentError(error);
366
+ }
326
367
  const serializedTransaction = Buffer.from(userSignedTx.serialize()).toString("base64");
327
368
  const paymentPayload = {
328
369
  x402Version,
@@ -338,7 +379,7 @@ async function createSvmPaymentHeader(params) {
338
379
  function getDefaultSolanaRpcUrl(network) {
339
380
  const normalized = network.toLowerCase();
340
381
  if (normalized === "solana" || normalized === "solana-mainnet") {
341
- return "https://api.mainnet-beta.solana.com";
382
+ return "https://cathee-fu8ezd-fast-mainnet.helius-rpc.com";
342
383
  } else if (normalized === "solana-devnet") {
343
384
  return "https://api.devnet.solana.com";
344
385
  }
@@ -356,6 +397,26 @@ async function handleSvmPayment(endpoint, config, requestInit) {
356
397
  return initialResponse;
357
398
  }
358
399
  const rawResponse = await initialResponse.json();
400
+ const IGNORED_ERRORS = [
401
+ "X-PAYMENT header is required",
402
+ "missing X-PAYMENT header",
403
+ "payment_required"
404
+ ];
405
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
406
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
407
+ const ERROR_MESSAGES = {
408
+ "insufficient_funds": "Insufficient balance to complete this payment",
409
+ "invalid_signature": "Invalid payment signature",
410
+ "expired": "Payment authorization has expired",
411
+ "already_used": "This payment has already been used",
412
+ "network_mismatch": "Payment network does not match",
413
+ "invalid_payment": "Invalid payment data",
414
+ "verification_failed": "Payment verification failed"
415
+ };
416
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
417
+ const error = new Error(errorMessage);
418
+ throw wrapPaymentError(error);
419
+ }
359
420
  const x402Version = rawResponse.x402Version;
360
421
  const parsedPaymentRequirements = rawResponse.accepts || [];
361
422
  const selectedRequirements = parsedPaymentRequirements.find(
@@ -375,13 +436,22 @@ async function handleSvmPayment(endpoint, config, requestInit) {
375
436
  );
376
437
  }
377
438
  }
378
- const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(network);
379
- const paymentHeader = await createSvmPaymentHeader({
380
- wallet,
381
- paymentRequirements: selectedRequirements,
382
- x402Version,
383
- rpcUrl: effectiveRpcUrl
384
- });
439
+ const effectiveRpcUrl = rpcUrl || getDefaultSolanaRpcUrl(selectedRequirements.network);
440
+ console.log(`\u{1F4CD} Using Solana RPC: ${effectiveRpcUrl.substring(0, 40)}...`);
441
+ console.log(`\u{1F4CD} Network from backend: ${selectedRequirements.network}`);
442
+ let paymentHeader;
443
+ try {
444
+ paymentHeader = await createSvmPaymentHeader({
445
+ wallet,
446
+ paymentRequirements: selectedRequirements,
447
+ x402Version,
448
+ rpcUrl: effectiveRpcUrl
449
+ });
450
+ console.log("\u2705 Payment header created successfully");
451
+ } catch (error) {
452
+ console.error("\u274C Failed to create payment header:", error);
453
+ throw wrapPaymentError(error);
454
+ }
385
455
  const newInit = {
386
456
  ...requestInit,
387
457
  method: requestInit?.method || "POST",
@@ -391,7 +461,38 @@ async function handleSvmPayment(endpoint, config, requestInit) {
391
461
  "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
392
462
  }
393
463
  };
394
- return await fetch(endpoint, newInit);
464
+ const retryResponse = await fetch(endpoint, newInit);
465
+ if (retryResponse.status === 402) {
466
+ try {
467
+ const retryData = await retryResponse.json();
468
+ const IGNORED_ERRORS2 = [
469
+ "X-PAYMENT header is required",
470
+ "missing X-PAYMENT header",
471
+ "payment_required"
472
+ ];
473
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
474
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
475
+ const ERROR_MESSAGES = {
476
+ "insufficient_funds": "Insufficient balance to complete this payment",
477
+ "invalid_signature": "Invalid payment signature",
478
+ "expired": "Payment authorization has expired",
479
+ "already_used": "This payment has already been used",
480
+ "network_mismatch": "Payment network does not match",
481
+ "invalid_payment": "Invalid payment data",
482
+ "verification_failed": "Payment verification failed"
483
+ };
484
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
485
+ const error = new Error(errorMessage);
486
+ throw wrapPaymentError(error);
487
+ }
488
+ } catch (error) {
489
+ if (error instanceof PaymentOperationError) {
490
+ throw error;
491
+ }
492
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
493
+ }
494
+ }
495
+ return retryResponse;
395
496
  }
396
497
 
397
498
  // src/services/evm/payment-header.ts
@@ -404,6 +505,34 @@ async function createEvmPaymentHeader(params) {
404
505
  if (!paymentRequirements?.asset) {
405
506
  throw new Error("Missing asset (token contract) in payment requirements");
406
507
  }
508
+ if (wallet.getChainId) {
509
+ try {
510
+ const currentChainIdHex = await wallet.getChainId();
511
+ const currentChainId = parseInt(currentChainIdHex, 16);
512
+ if (currentChainId !== chainId) {
513
+ const networkNames = {
514
+ 1: "Ethereum Mainnet",
515
+ 11155111: "Sepolia Testnet",
516
+ 8453: "Base Mainnet",
517
+ 84532: "Base Sepolia Testnet",
518
+ 137: "Polygon Mainnet",
519
+ 42161: "Arbitrum One",
520
+ 10: "Optimism Mainnet"
521
+ };
522
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
523
+ const targetNetworkName = networkNames[chainId] || `Chain ${chainId}`;
524
+ throw new Error(
525
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch your wallet to the correct network.`
526
+ );
527
+ }
528
+ console.log(`\u2705 Chain ID verified: ${chainId}`);
529
+ } catch (error) {
530
+ if (error.message.includes("Network mismatch")) {
531
+ throw wrapPaymentError(error);
532
+ }
533
+ console.warn("Could not verify chainId:", error);
534
+ }
535
+ }
407
536
  const now = Math.floor(Date.now() / 1e3);
408
537
  const nonceBytes = import_ethers.ethers.randomBytes(32);
409
538
  const nonceBytes32 = import_ethers.ethers.hexlify(nonceBytes);
@@ -432,7 +561,14 @@ async function createEvmPaymentHeader(params) {
432
561
  validBefore: String(now + (paymentRequirements.maxTimeoutSeconds || 3600)),
433
562
  nonce: nonceBytes32
434
563
  };
435
- const signature = await wallet.signTypedData(domain, types, authorization);
564
+ let signature;
565
+ try {
566
+ signature = await wallet.signTypedData(domain, types, authorization);
567
+ console.log("\u2705 Signature created successfully");
568
+ } catch (error) {
569
+ console.error("\u274C Failed to create signature:", error);
570
+ throw wrapPaymentError(error);
571
+ }
436
572
  const headerPayload = {
437
573
  x402_version: x402Version,
438
574
  x402Version,
@@ -483,6 +619,26 @@ async function handleEvmPayment(endpoint, config, requestInit) {
483
619
  return initialResponse;
484
620
  }
485
621
  const rawResponse = await initialResponse.json();
622
+ const IGNORED_ERRORS = [
623
+ "X-PAYMENT header is required",
624
+ "missing X-PAYMENT header",
625
+ "payment_required"
626
+ ];
627
+ if (rawResponse.error && !IGNORED_ERRORS.includes(rawResponse.error)) {
628
+ console.error(`\u274C Payment verification failed: ${rawResponse.error}`);
629
+ const ERROR_MESSAGES = {
630
+ "insufficient_funds": "Insufficient balance to complete this payment",
631
+ "invalid_signature": "Invalid payment signature",
632
+ "expired": "Payment authorization has expired",
633
+ "already_used": "This payment has already been used",
634
+ "network_mismatch": "Payment network does not match",
635
+ "invalid_payment": "Invalid payment data",
636
+ "verification_failed": "Payment verification failed"
637
+ };
638
+ const errorMessage = ERROR_MESSAGES[rawResponse.error] || `Payment failed: ${rawResponse.error}`;
639
+ const error = new Error(errorMessage);
640
+ throw wrapPaymentError(error);
641
+ }
486
642
  const x402Version = rawResponse.x402Version;
487
643
  const parsedPaymentRequirements = rawResponse.accepts || [];
488
644
  const selectedRequirements = parsedPaymentRequirements.find(
@@ -503,19 +659,81 @@ async function handleEvmPayment(endpoint, config, requestInit) {
503
659
  }
504
660
  }
505
661
  const targetChainId = getChainIdFromNetwork(selectedRequirements.network);
506
- if (wallet.switchChain) {
662
+ let currentChainId;
663
+ if (wallet.getChainId) {
664
+ try {
665
+ const chainIdHex = await wallet.getChainId();
666
+ currentChainId = parseInt(chainIdHex, 16);
667
+ console.log(`\u{1F4CD} Current wallet chain: ${currentChainId}`);
668
+ } catch (error) {
669
+ console.warn("\u26A0\uFE0F Failed to get current chainId:", error);
670
+ }
671
+ }
672
+ const networkNames = {
673
+ 1: "Ethereum Mainnet",
674
+ 11155111: "Sepolia Testnet",
675
+ 8453: "Base Mainnet",
676
+ 84532: "Base Sepolia Testnet",
677
+ 137: "Polygon Mainnet",
678
+ 42161: "Arbitrum One",
679
+ 10: "Optimism Mainnet"
680
+ };
681
+ if (currentChainId && currentChainId !== targetChainId) {
682
+ if (!wallet.switchChain) {
683
+ const currentNetworkName = networkNames[currentChainId] || `Chain ${currentChainId}`;
684
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
685
+ const error = new Error(
686
+ `Network mismatch: Your wallet is connected to ${currentNetworkName}, but payment requires ${targetNetworkName}. Please switch to ${targetNetworkName} manually in your wallet.`
687
+ );
688
+ throw wrapPaymentError(error);
689
+ }
690
+ try {
691
+ console.log(`\u{1F504} Switching to chain ${targetChainId}...`);
692
+ await wallet.switchChain(`0x${targetChainId.toString(16)}`);
693
+ console.log(`\u2705 Successfully switched to chain ${targetChainId}`);
694
+ } catch (error) {
695
+ console.error("\u274C Failed to switch chain:", error);
696
+ const targetNetworkName = networkNames[targetChainId] || selectedRequirements.network;
697
+ const wrappedError = wrapPaymentError(error);
698
+ let finalError;
699
+ if (wrappedError.code === "USER_REJECTED" /* USER_REJECTED */) {
700
+ finalError = new PaymentOperationError({
701
+ code: wrappedError.code,
702
+ message: wrappedError.message,
703
+ userMessage: `You rejected the network switch request. Please switch to ${targetNetworkName} manually.`,
704
+ originalError: wrappedError.originalError
705
+ });
706
+ } else {
707
+ finalError = new PaymentOperationError({
708
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
709
+ message: wrappedError.message,
710
+ userMessage: `Failed to switch to ${targetNetworkName}. Please switch manually in your wallet.`,
711
+ originalError: wrappedError.originalError
712
+ });
713
+ }
714
+ throw finalError;
715
+ }
716
+ } else if (wallet.switchChain && !currentChainId) {
507
717
  try {
718
+ console.log(`\u{1F504} Attempting to switch to chain ${targetChainId}...`);
508
719
  await wallet.switchChain(`0x${targetChainId.toString(16)}`);
720
+ console.log(`\u2705 Switch attempted successfully`);
509
721
  } catch (error) {
510
- console.warn("Failed to switch chain:", error);
722
+ console.warn("\u26A0\uFE0F Failed to switch chain (best effort):", error);
511
723
  }
512
724
  }
513
- const paymentHeader = await createEvmPaymentHeader({
514
- wallet,
515
- paymentRequirements: selectedRequirements,
516
- x402Version,
517
- chainId: targetChainId
518
- });
725
+ let paymentHeader;
726
+ try {
727
+ paymentHeader = await createEvmPaymentHeader({
728
+ wallet,
729
+ paymentRequirements: selectedRequirements,
730
+ x402Version,
731
+ chainId: targetChainId
732
+ });
733
+ } catch (error) {
734
+ console.error("\u274C Failed to create payment header:", error);
735
+ throw wrapPaymentError(error);
736
+ }
519
737
  const newInit = {
520
738
  ...requestInit,
521
739
  method: requestInit?.method || "POST",
@@ -525,7 +743,38 @@ async function handleEvmPayment(endpoint, config, requestInit) {
525
743
  "Access-Control-Expose-Headers": "X-PAYMENT-RESPONSE"
526
744
  }
527
745
  };
528
- return await fetch(endpoint, newInit);
746
+ const retryResponse = await fetch(endpoint, newInit);
747
+ if (retryResponse.status === 402) {
748
+ try {
749
+ const retryData = await retryResponse.json();
750
+ const IGNORED_ERRORS2 = [
751
+ "X-PAYMENT header is required",
752
+ "missing X-PAYMENT header",
753
+ "payment_required"
754
+ ];
755
+ if (retryData.error && !IGNORED_ERRORS2.includes(retryData.error)) {
756
+ console.error(`\u274C Payment verification failed: ${retryData.error}`);
757
+ const ERROR_MESSAGES = {
758
+ "insufficient_funds": "Insufficient balance to complete this payment",
759
+ "invalid_signature": "Invalid payment signature",
760
+ "expired": "Payment authorization has expired",
761
+ "already_used": "This payment has already been used",
762
+ "network_mismatch": "Payment network does not match",
763
+ "invalid_payment": "Invalid payment data",
764
+ "verification_failed": "Payment verification failed"
765
+ };
766
+ const errorMessage = ERROR_MESSAGES[retryData.error] || `Payment failed: ${retryData.error}`;
767
+ const error = new Error(errorMessage);
768
+ throw wrapPaymentError(error);
769
+ }
770
+ } catch (error) {
771
+ if (error instanceof PaymentOperationError) {
772
+ throw error;
773
+ }
774
+ console.warn("\u26A0\uFE0F Could not parse retry 402 response:", error);
775
+ }
776
+ }
777
+ return retryResponse;
529
778
  }
530
779
 
531
780
  // src/utils/payment-helpers.ts
@@ -566,7 +815,8 @@ async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL) {
566
815
  }
567
816
  response = await handleSvmPayment(endpoint, {
568
817
  wallet: solana,
569
- network: "solana-devnet"
818
+ network: "solana"
819
+ // Will use backend's network configuration
570
820
  });
571
821
  } else if (networkType === "evm" /* EVM */) {
572
822
  if (!window.ethereum) {
@@ -578,12 +828,24 @@ async function makePayment(networkType, merchantId, endpoint = PROD_BACK_URL) {
578
828
  address: await signer.getAddress(),
579
829
  signTypedData: async (domain, types, message) => {
580
830
  return await signer.signTypedData(domain, types, message);
831
+ },
832
+ // Get current chain ID from wallet
833
+ getChainId: async () => {
834
+ const network = await provider.getNetwork();
835
+ return `0x${network.chainId.toString(16)}`;
836
+ },
837
+ // Switch to a different chain
838
+ switchChain: async (chainId) => {
839
+ await window.ethereum.request({
840
+ method: "wallet_switchEthereumChain",
841
+ params: [{ chainId }]
842
+ });
581
843
  }
582
844
  };
583
- const network = endpoint.includes("sepolia") ? "base-sepolia" : "base";
584
845
  response = await handleEvmPayment(endpoint, {
585
846
  wallet,
586
- network
847
+ network: "base"
848
+ // Will use backend's network configuration
587
849
  });
588
850
  } else {
589
851
  throw new Error(`\u4E0D\u652F\u6301\u7684\u7F51\u7EDC\u7C7B\u578B: ${networkType}`);
@@ -646,6 +908,138 @@ function getNetworkDisplayName(network) {
646
908
  return displayNames[network.toLowerCase()] || network;
647
909
  }
648
910
 
911
+ // src/utils/payment-error-handler.ts
912
+ function parsePaymentError(error) {
913
+ if (!error) {
914
+ return {
915
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
916
+ message: "Unknown error occurred",
917
+ userMessage: "An unknown error occurred. Please try again.",
918
+ originalError: error
919
+ };
920
+ }
921
+ const errorMessage = error.message || error.toString();
922
+ const errorCode = error.code;
923
+ 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")) {
924
+ return {
925
+ code: "USER_REJECTED" /* USER_REJECTED */,
926
+ message: "User rejected the transaction",
927
+ userMessage: "You rejected the signature request. Please try again if you want to proceed.",
928
+ originalError: error
929
+ };
930
+ }
931
+ if (errorMessage.includes("chainId") && (errorMessage.includes("must match") || errorMessage.includes("does not match"))) {
932
+ const match = errorMessage.match(/chainId.*?"(\d+)".*?active.*?"(\d+)"/i) || errorMessage.match(/chain (\d+).*?chain (\d+)/i);
933
+ if (match) {
934
+ const [, requestedChain, activeChain] = match;
935
+ return {
936
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
937
+ message: `Network mismatch (wallet is on different chain): Requested ${requestedChain}, but wallet is on ${activeChain}`,
938
+ userMessage: `Your wallet is on the wrong network. Please switch to the correct network and try again.`,
939
+ originalError: error
940
+ };
941
+ }
942
+ return {
943
+ code: "CHAIN_ID_MISMATCH" /* CHAIN_ID_MISMATCH */,
944
+ message: "Network mismatch (wallet selected network does not match)",
945
+ userMessage: "Your wallet is on the wrong network. Please switch to the correct network.",
946
+ originalError: error
947
+ };
948
+ }
949
+ if (errorMessage.includes("Network mismatch") || errorMessage.includes("Wrong network") || errorMessage.includes("Incorrect network")) {
950
+ return {
951
+ code: "NETWORK_MISMATCH" /* NETWORK_MISMATCH */,
952
+ message: errorMessage,
953
+ userMessage: "Please switch your wallet to the correct network.",
954
+ originalError: error
955
+ };
956
+ }
957
+ if (errorMessage.includes("locked") || errorMessage.includes("Wallet is locked")) {
958
+ return {
959
+ code: "WALLET_LOCKED" /* WALLET_LOCKED */,
960
+ message: "Wallet is locked",
961
+ userMessage: "Please unlock your wallet and try again.",
962
+ originalError: error
963
+ };
964
+ }
965
+ if (errorMessage.includes("insufficient") && (errorMessage.includes("balance") || errorMessage.includes("funds"))) {
966
+ return {
967
+ code: "INSUFFICIENT_BALANCE" /* INSUFFICIENT_BALANCE */,
968
+ message: "Insufficient balance",
969
+ userMessage: "You don't have enough balance to complete this payment.",
970
+ originalError: error
971
+ };
972
+ }
973
+ if (errorMessage.includes("Failed to switch") || errorMessage.includes("switch chain")) {
974
+ return {
975
+ code: "NETWORK_SWITCH_FAILED" /* NETWORK_SWITCH_FAILED */,
976
+ message: errorMessage,
977
+ userMessage: "Failed to switch network. Please switch manually in your wallet.",
978
+ originalError: error
979
+ };
980
+ }
981
+ if (errorMessage.includes("not connected") || errorMessage.includes("No wallet") || errorMessage.includes("Connect wallet")) {
982
+ return {
983
+ code: "WALLET_NOT_CONNECTED" /* WALLET_NOT_CONNECTED */,
984
+ message: "Wallet not connected",
985
+ userMessage: "Please connect your wallet first.",
986
+ originalError: error
987
+ };
988
+ }
989
+ if (errorMessage.includes("No suitable") || errorMessage.includes("payment requirements") || errorMessage.includes("Missing payTo") || errorMessage.includes("Missing asset")) {
990
+ return {
991
+ code: "INVALID_PAYMENT_REQUIREMENTS" /* INVALID_PAYMENT_REQUIREMENTS */,
992
+ message: errorMessage,
993
+ userMessage: "Invalid payment configuration. Please contact support.",
994
+ originalError: error
995
+ };
996
+ }
997
+ if (errorMessage.includes("exceeds maximum")) {
998
+ return {
999
+ code: "AMOUNT_EXCEEDED" /* AMOUNT_EXCEEDED */,
1000
+ message: errorMessage,
1001
+ userMessage: "Payment amount exceeds the maximum allowed.",
1002
+ originalError: error
1003
+ };
1004
+ }
1005
+ if (errorMessage.includes("signature") || errorMessage.includes("sign") || errorCode === "UNKNOWN_ERROR") {
1006
+ return {
1007
+ code: "SIGNATURE_FAILED" /* SIGNATURE_FAILED */,
1008
+ message: errorMessage,
1009
+ userMessage: "Failed to sign the transaction. Please try again.",
1010
+ originalError: error
1011
+ };
1012
+ }
1013
+ return {
1014
+ code: "UNKNOWN_ERROR" /* UNKNOWN_ERROR */,
1015
+ message: errorMessage,
1016
+ userMessage: "An unexpected error occurred. Please try again or contact support.",
1017
+ originalError: error
1018
+ };
1019
+ }
1020
+ var PaymentOperationError = class _PaymentOperationError extends Error {
1021
+ constructor(paymentError) {
1022
+ super(paymentError.message);
1023
+ this.name = "PaymentOperationError";
1024
+ this.code = paymentError.code;
1025
+ this.userMessage = paymentError.userMessage;
1026
+ this.originalError = paymentError.originalError;
1027
+ if (Error.captureStackTrace) {
1028
+ Error.captureStackTrace(this, _PaymentOperationError);
1029
+ }
1030
+ }
1031
+ /**
1032
+ * Get a formatted error message for logging
1033
+ */
1034
+ toLogString() {
1035
+ return `[${this.code}] ${this.message} | User Message: ${this.userMessage}`;
1036
+ }
1037
+ };
1038
+ function wrapPaymentError(error) {
1039
+ const parsedError = parsePaymentError(error);
1040
+ return new PaymentOperationError(parsedError);
1041
+ }
1042
+
649
1043
  // src/react/store/walletStore.ts
650
1044
  var WalletStore = class {
651
1045
  constructor() {
@@ -677,6 +1071,29 @@ var WalletStore = class {
677
1071
  }
678
1072
  }
679
1073
  });
1074
+ onChainChanged(() => {
1075
+ const connectedType = getConnectedNetworkType();
1076
+ if (connectedType === "evm" /* EVM */) {
1077
+ console.log("\u26A0\uFE0F Network changed detected - disconnecting wallet");
1078
+ disconnectWallet();
1079
+ this.setState({
1080
+ address: null,
1081
+ networkType: null,
1082
+ error: "Network changed. Please reconnect your wallet."
1083
+ });
1084
+ }
1085
+ });
1086
+ onWalletDisconnect(() => {
1087
+ const connectedType = getConnectedNetworkType();
1088
+ if (connectedType === "solana" /* SOLANA */ || connectedType === "svm" /* SVM */) {
1089
+ console.log("\u26A0\uFE0F Solana wallet disconnected");
1090
+ disconnectWallet();
1091
+ this.setState({
1092
+ address: null,
1093
+ networkType: null
1094
+ });
1095
+ }
1096
+ });
680
1097
  }
681
1098
  async autoReconnect() {
682
1099
  if (!isWalletManuallyDisconnected()) {
@@ -844,6 +1261,189 @@ function usePaymentInfo(merchantId, endpoint = PROD_BACK_URL) {
844
1261
 
845
1262
  // src/react/components/WalletConnect.tsx
846
1263
  var import_react4 = __toESM(require("react"));
1264
+
1265
+ // src/react/styles/inline-styles.ts
1266
+ var isDarkMode = () => {
1267
+ if (typeof window === "undefined") return false;
1268
+ return window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
1269
+ };
1270
+ var colors = {
1271
+ // Light mode
1272
+ light: {
1273
+ background: "#fafafa",
1274
+ cardBg: "#ffffff",
1275
+ text: "#0a0a0a",
1276
+ textSecondary: "#737373",
1277
+ primary: "#000000",
1278
+ primaryHover: "#262626",
1279
+ danger: "#ef4444",
1280
+ dangerHover: "#dc2626",
1281
+ success: "#10b981",
1282
+ successHover: "#059669",
1283
+ disabled: "#e5e5e5",
1284
+ disabledText: "#a3a3a3",
1285
+ errorBg: "#fef2f2",
1286
+ errorText: "#dc2626"
1287
+ },
1288
+ // Dark mode
1289
+ dark: {
1290
+ background: "#0a0a0a",
1291
+ cardBg: "#171717",
1292
+ text: "#fafafa",
1293
+ textSecondary: "#a3a3a3",
1294
+ primary: "#ffffff",
1295
+ primaryHover: "#e5e5e5",
1296
+ danger: "#f87171",
1297
+ dangerHover: "#ef4444",
1298
+ success: "#34d399",
1299
+ successHover: "#10b981",
1300
+ disabled: "#262626",
1301
+ disabledText: "#525252",
1302
+ errorBg: "#1c1917",
1303
+ errorText: "#f87171"
1304
+ }
1305
+ };
1306
+ var getColors = () => {
1307
+ return isDarkMode() ? colors.dark : colors.light;
1308
+ };
1309
+ var containerStyle = {
1310
+ width: "100%",
1311
+ maxWidth: "420px",
1312
+ margin: "0 auto"
1313
+ };
1314
+ var getSectionStyle = () => {
1315
+ const c = getColors();
1316
+ return {
1317
+ padding: "1.5rem",
1318
+ background: c.cardBg,
1319
+ borderRadius: "12px"
1320
+ };
1321
+ };
1322
+ var getTitleStyle = () => {
1323
+ const c = getColors();
1324
+ return {
1325
+ margin: "0 0 1.25rem 0",
1326
+ fontSize: "1.125rem",
1327
+ fontWeight: 600,
1328
+ color: c.text,
1329
+ letterSpacing: "-0.01em"
1330
+ };
1331
+ };
1332
+ var buttonsContainerStyle = {
1333
+ display: "flex",
1334
+ flexDirection: "column",
1335
+ gap: "0.75rem"
1336
+ };
1337
+ var walletOptionStyle = {
1338
+ display: "flex",
1339
+ flexDirection: "column",
1340
+ gap: "0.5rem"
1341
+ };
1342
+ var baseButtonStyle = {
1343
+ padding: "0.875rem 1.25rem",
1344
+ fontSize: "0.9375rem",
1345
+ fontWeight: 500,
1346
+ border: "none",
1347
+ borderRadius: "8px",
1348
+ cursor: "pointer",
1349
+ transition: "background-color 0.15s ease, opacity 0.15s ease",
1350
+ outline: "none",
1351
+ letterSpacing: "-0.01em"
1352
+ };
1353
+ var getConnectButtonStyle = (isDisabled, isHovered) => {
1354
+ const c = getColors();
1355
+ return {
1356
+ ...baseButtonStyle,
1357
+ background: isDisabled ? c.disabled : isHovered ? c.primaryHover : c.primary,
1358
+ color: isDarkMode() ? "#000000" : "#ffffff",
1359
+ cursor: isDisabled ? "not-allowed" : "pointer",
1360
+ opacity: isDisabled ? 0.5 : 1
1361
+ };
1362
+ };
1363
+ var getDisconnectButtonStyle = (isHovered) => {
1364
+ const c = getColors();
1365
+ return {
1366
+ ...baseButtonStyle,
1367
+ background: isHovered ? c.dangerHover : c.danger,
1368
+ color: "#ffffff"
1369
+ };
1370
+ };
1371
+ var getPayButtonStyle = (isDisabled, isHovered) => {
1372
+ const c = getColors();
1373
+ return {
1374
+ ...baseButtonStyle,
1375
+ background: isDisabled ? c.disabled : isHovered ? c.successHover : c.success,
1376
+ color: "#ffffff",
1377
+ width: "100%",
1378
+ cursor: isDisabled ? "not-allowed" : "pointer",
1379
+ opacity: isDisabled ? 0.5 : 1
1380
+ };
1381
+ };
1382
+ var getInstallLinkStyle = (isHovered) => {
1383
+ const c = getColors();
1384
+ return {
1385
+ display: "inline-block",
1386
+ padding: "0.5rem",
1387
+ fontSize: "0.8125rem",
1388
+ color: c.textSecondary,
1389
+ textDecoration: isHovered ? "underline" : "none",
1390
+ textAlign: "center",
1391
+ fontWeight: 500
1392
+ };
1393
+ };
1394
+ var walletAddressStyle = {
1395
+ display: "flex",
1396
+ flexDirection: "column",
1397
+ gap: "0.5rem",
1398
+ marginBottom: "1rem"
1399
+ };
1400
+ var getLabelStyle = () => {
1401
+ const c = getColors();
1402
+ return {
1403
+ fontSize: "0.8125rem",
1404
+ color: c.textSecondary,
1405
+ fontWeight: 500,
1406
+ textTransform: "uppercase",
1407
+ letterSpacing: "0.05em"
1408
+ };
1409
+ };
1410
+ var getAddressStyle = () => {
1411
+ const c = getColors();
1412
+ return {
1413
+ fontFamily: "ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace",
1414
+ fontSize: "0.9375rem",
1415
+ fontWeight: 500,
1416
+ color: c.text,
1417
+ letterSpacing: "-0.01em"
1418
+ };
1419
+ };
1420
+ var walletActionsStyle = {
1421
+ margin: "1rem 0"
1422
+ };
1423
+ var getHintStyle = () => {
1424
+ const c = getColors();
1425
+ return {
1426
+ marginTop: "1rem",
1427
+ fontSize: "0.8125rem",
1428
+ color: c.textSecondary,
1429
+ textAlign: "center",
1430
+ lineHeight: "1.5"
1431
+ };
1432
+ };
1433
+ var getErrorStyle = () => {
1434
+ const c = getColors();
1435
+ return {
1436
+ marginTop: "1rem",
1437
+ padding: "0.75rem 1rem",
1438
+ background: c.errorBg,
1439
+ color: c.errorText,
1440
+ borderRadius: "8px",
1441
+ fontSize: "0.8125rem",
1442
+ fontWeight: 500
1443
+ };
1444
+ };
1445
+
1446
+ // src/react/components/WalletConnect.tsx
847
1447
  function WalletConnect({
848
1448
  supportedNetworks = ["solana" /* SOLANA */, "evm" /* EVM */],
849
1449
  className = "",
@@ -851,6 +1451,8 @@ function WalletConnect({
851
1451
  onDisconnect
852
1452
  }) {
853
1453
  const { address, networkType, isConnecting, error, connect, disconnect } = useWallet();
1454
+ const [hoveredButton, setHoveredButton] = (0, import_react4.useState)(null);
1455
+ const [hoveredLink, setHoveredLink] = (0, import_react4.useState)(null);
854
1456
  const handleConnect = async (network) => {
855
1457
  try {
856
1458
  await connect(network);
@@ -861,14 +1463,16 @@ function WalletConnect({
861
1463
  disconnect();
862
1464
  onDisconnect?.();
863
1465
  };
864
- return /* @__PURE__ */ import_react4.default.createElement("div", { className: `x402-wallet-connect ${className}` }, !address ? /* @__PURE__ */ import_react4.default.createElement("div", { className: "x402-wallet-section" }, /* @__PURE__ */ import_react4.default.createElement("h3", { className: "x402-section-title" }, "Choose Your Wallet"), supportedNetworks.length === 0 ? /* @__PURE__ */ import_react4.default.createElement("p", { className: "x402-hint" }, "No payment required") : /* @__PURE__ */ import_react4.default.createElement("div", { className: "x402-wallet-buttons" }, supportedNetworks.map((network) => {
1466
+ return /* @__PURE__ */ import_react4.default.createElement("div", { style: { ...containerStyle, ...className ? {} : {} }, className }, !address ? /* @__PURE__ */ import_react4.default.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ import_react4.default.createElement("h3", { style: getTitleStyle() }, "Connect Wallet"), supportedNetworks.length === 0 ? /* @__PURE__ */ import_react4.default.createElement("p", { style: getHintStyle() }, "No payment required") : /* @__PURE__ */ import_react4.default.createElement("div", { style: buttonsContainerStyle }, supportedNetworks.map((network) => {
865
1467
  const installed = isWalletInstalled(network);
866
- return /* @__PURE__ */ import_react4.default.createElement("div", { key: network, className: "x402-wallet-option" }, /* @__PURE__ */ import_react4.default.createElement(
1468
+ return /* @__PURE__ */ import_react4.default.createElement("div", { key: network, style: walletOptionStyle }, /* @__PURE__ */ import_react4.default.createElement(
867
1469
  "button",
868
1470
  {
869
- className: "x402-connect-button",
1471
+ style: getConnectButtonStyle(isConnecting || !installed, hoveredButton === network),
870
1472
  onClick: () => handleConnect(network),
871
- disabled: isConnecting || !installed
1473
+ disabled: isConnecting || !installed,
1474
+ onMouseEnter: () => setHoveredButton(network),
1475
+ onMouseLeave: () => setHoveredButton(null)
872
1476
  },
873
1477
  isConnecting ? "Connecting..." : getNetworkDisplayName(network)
874
1478
  ), !installed && /* @__PURE__ */ import_react4.default.createElement(
@@ -877,11 +1481,22 @@ function WalletConnect({
877
1481
  href: getWalletInstallUrl(network),
878
1482
  target: "_blank",
879
1483
  rel: "noopener noreferrer",
880
- className: "x402-install-link"
1484
+ style: getInstallLinkStyle(hoveredLink === network),
1485
+ onMouseEnter: () => setHoveredLink(network),
1486
+ onMouseLeave: () => setHoveredLink(null)
881
1487
  },
882
1488
  "Install Wallet"
883
1489
  ));
884
- })), error && /* @__PURE__ */ import_react4.default.createElement("p", { className: "x402-error" }, error), /* @__PURE__ */ import_react4.default.createElement("p", { className: "x402-hint" }, "To switch accounts, please change it in your wallet extension")) : /* @__PURE__ */ import_react4.default.createElement("div", { className: "x402-wallet-info" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "x402-wallet-address" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "x402-wallet-label" }, "Connected ", networkType && `(${getNetworkDisplayName(networkType)})`, ":"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "x402-address" }, formatAddress(address))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "x402-wallet-actions" }, /* @__PURE__ */ import_react4.default.createElement("button", { className: "x402-disconnect-button", onClick: handleDisconnect }, "Disconnect")), /* @__PURE__ */ import_react4.default.createElement("p", { className: "x402-hint" }, "Switch account in your wallet to change address")));
1490
+ })), error && /* @__PURE__ */ import_react4.default.createElement("p", { style: getErrorStyle() }, error), /* @__PURE__ */ import_react4.default.createElement("p", { style: getHintStyle() }, "To switch accounts, please change it in your wallet extension")) : /* @__PURE__ */ import_react4.default.createElement("div", { style: getSectionStyle() }, /* @__PURE__ */ import_react4.default.createElement("div", { style: walletAddressStyle }, /* @__PURE__ */ import_react4.default.createElement("span", { style: getLabelStyle() }, "Connected ", networkType && `(${getNetworkDisplayName(networkType)})`), /* @__PURE__ */ import_react4.default.createElement("span", { style: getAddressStyle() }, formatAddress(address))), /* @__PURE__ */ import_react4.default.createElement("div", { style: walletActionsStyle }, /* @__PURE__ */ import_react4.default.createElement(
1491
+ "button",
1492
+ {
1493
+ style: getDisconnectButtonStyle(hoveredButton === "disconnect"),
1494
+ onClick: handleDisconnect,
1495
+ onMouseEnter: () => setHoveredButton("disconnect"),
1496
+ onMouseLeave: () => setHoveredButton(null)
1497
+ },
1498
+ "Disconnect"
1499
+ )), /* @__PURE__ */ import_react4.default.createElement("p", { style: getHintStyle() }, "Switch account in your wallet to change address")));
885
1500
  }
886
1501
 
887
1502
  // src/react/components/PaymentButton.tsx
@@ -898,6 +1513,7 @@ function PaymentButton({
898
1513
  }) {
899
1514
  const { networkType } = useWallet();
900
1515
  const { isProcessing, setIsProcessing, setResult, setError, error } = usePayment();
1516
+ const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
901
1517
  const handleClick = async () => {
902
1518
  if (!networkType) {
903
1519
  const errorMsg = "Please connect wallet first";
@@ -921,15 +1537,19 @@ function PaymentButton({
921
1537
  onFinish?.();
922
1538
  }
923
1539
  };
1540
+ const isDisabled = disabled || isProcessing || !networkType;
924
1541
  return /* @__PURE__ */ import_react5.default.createElement(import_react5.default.Fragment, null, /* @__PURE__ */ import_react5.default.createElement(
925
1542
  "button",
926
1543
  {
927
- className: `x402-pay-button ${className}`,
1544
+ style: getPayButtonStyle(isDisabled, isHovered),
1545
+ className,
928
1546
  onClick: handleClick,
929
- disabled: disabled || isProcessing || !networkType
1547
+ disabled: isDisabled,
1548
+ onMouseEnter: () => setIsHovered(true),
1549
+ onMouseLeave: () => setIsHovered(false)
930
1550
  },
931
1551
  isProcessing ? "Processing..." : children
932
- ), error && /* @__PURE__ */ import_react5.default.createElement("p", { className: "x402-error" }, error));
1552
+ ), error && /* @__PURE__ */ import_react5.default.createElement("p", { style: getErrorStyle() }, error));
933
1553
  }
934
1554
  // Annotate the CommonJS export names for ESM import in node:
935
1555
  0 && (module.exports = {