moltspay 1.2.0 → 1.2.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.
Files changed (54) hide show
  1. package/README.md +78 -9
  2. package/dist/cdp/index.d.mts +1 -1
  3. package/dist/cdp/index.d.ts +1 -1
  4. package/dist/cdp/index.js +16 -49
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +16 -49
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/chains/index.d.mts +1 -1
  9. package/dist/chains/index.d.ts +1 -1
  10. package/dist/chains/index.js +16 -49
  11. package/dist/chains/index.js.map +1 -1
  12. package/dist/chains/index.mjs +16 -49
  13. package/dist/chains/index.mjs.map +1 -1
  14. package/dist/cli/index.js +180 -111
  15. package/dist/cli/index.js.map +1 -1
  16. package/dist/cli/index.mjs +180 -111
  17. package/dist/cli/index.mjs.map +1 -1
  18. package/dist/client/index.d.mts +3 -3
  19. package/dist/client/index.d.ts +3 -3
  20. package/dist/client/index.js +32 -58
  21. package/dist/client/index.js.map +1 -1
  22. package/dist/client/index.mjs +32 -58
  23. package/dist/client/index.mjs.map +1 -1
  24. package/dist/facilitators/index.d.mts +12 -6
  25. package/dist/facilitators/index.d.ts +12 -6
  26. package/dist/facilitators/index.js +39 -33
  27. package/dist/facilitators/index.js.map +1 -1
  28. package/dist/facilitators/index.mjs +39 -33
  29. package/dist/facilitators/index.mjs.map +1 -1
  30. package/dist/{index-B3v8IWjM.d.mts → index-DgJPZMBG.d.mts} +2 -1
  31. package/dist/{index-B3v8IWjM.d.ts → index-DgJPZMBG.d.ts} +2 -1
  32. package/dist/index.d.mts +1 -1
  33. package/dist/index.d.ts +1 -1
  34. package/dist/index.js +102 -101
  35. package/dist/index.js.map +1 -1
  36. package/dist/index.mjs +102 -101
  37. package/dist/index.mjs.map +1 -1
  38. package/dist/server/index.js +70 -43
  39. package/dist/server/index.js.map +1 -1
  40. package/dist/server/index.mjs +70 -43
  41. package/dist/server/index.mjs.map +1 -1
  42. package/dist/verify/index.d.mts +1 -1
  43. package/dist/verify/index.d.ts +1 -1
  44. package/dist/verify/index.js +16 -49
  45. package/dist/verify/index.js.map +1 -1
  46. package/dist/verify/index.mjs +16 -49
  47. package/dist/verify/index.mjs.map +1 -1
  48. package/dist/wallet/index.d.mts +1 -1
  49. package/dist/wallet/index.d.ts +1 -1
  50. package/dist/wallet/index.js +16 -49
  51. package/dist/wallet/index.js.map +1 -1
  52. package/dist/wallet/index.mjs +16 -49
  53. package/dist/wallet/index.mjs.map +1 -1
  54. package/package.json +1 -1
@@ -40,12 +40,15 @@ var CHAINS = {
40
40
  USDC: {
41
41
  address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
42
42
  decimals: 6,
43
- symbol: "USDC"
43
+ symbol: "USDC",
44
+ eip712Name: "USD Coin"
45
+ // EIP-712 domain name
44
46
  },
45
47
  USDT: {
46
48
  address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
47
49
  decimals: 6,
48
- symbol: "USDT"
50
+ symbol: "USDT",
51
+ eip712Name: "Tether USD"
49
52
  }
50
53
  },
51
54
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -62,12 +65,15 @@ var CHAINS = {
62
65
  USDC: {
63
66
  address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
64
67
  decimals: 6,
65
- symbol: "USDC"
68
+ symbol: "USDC",
69
+ eip712Name: "USD Coin"
66
70
  },
67
71
  USDT: {
68
72
  address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
69
73
  decimals: 6,
70
- symbol: "USDT"
74
+ symbol: "USDT",
75
+ eip712Name: "(PoS) Tether USD"
76
+ // Polygon uses this name
71
77
  }
72
78
  },
73
79
  usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
@@ -75,27 +81,6 @@ var CHAINS = {
75
81
  explorerTx: "https://polygonscan.com/tx/",
76
82
  avgBlockTime: 2
77
83
  },
78
- ethereum: {
79
- name: "Ethereum",
80
- chainId: 1,
81
- rpc: "https://eth.llamarpc.com",
82
- tokens: {
83
- USDC: {
84
- address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
85
- decimals: 6,
86
- symbol: "USDC"
87
- },
88
- USDT: {
89
- address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
90
- decimals: 6,
91
- symbol: "USDT"
92
- }
93
- },
94
- usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
95
- explorer: "https://etherscan.io/address/",
96
- explorerTx: "https://etherscan.io/tx/",
97
- avgBlockTime: 12
98
- },
99
84
  // ============ Testnet ============
100
85
  base_sepolia: {
101
86
  name: "Base Sepolia",
@@ -105,41 +90,23 @@ var CHAINS = {
105
90
  USDC: {
106
91
  address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
107
92
  decimals: 6,
108
- symbol: "USDC"
93
+ symbol: "USDC",
94
+ eip712Name: "USDC"
95
+ // Testnet USDC uses 'USDC' not 'USD Coin'
109
96
  },
110
97
  USDT: {
111
98
  address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
112
99
  // Same as USDC on testnet (no official USDT)
113
100
  decimals: 6,
114
- symbol: "USDT"
101
+ symbol: "USDT",
102
+ eip712Name: "USDC"
103
+ // Uses same contract as USDC
115
104
  }
116
105
  },
117
106
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
118
107
  explorer: "https://sepolia.basescan.org/address/",
119
108
  explorerTx: "https://sepolia.basescan.org/tx/",
120
109
  avgBlockTime: 2
121
- },
122
- sepolia: {
123
- name: "Sepolia",
124
- chainId: 11155111,
125
- rpc: "https://rpc.sepolia.org",
126
- tokens: {
127
- USDC: {
128
- address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
129
- decimals: 6,
130
- symbol: "USDC"
131
- },
132
- USDT: {
133
- address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
134
- // Same as USDC on testnet
135
- decimals: 6,
136
- symbol: "USDT"
137
- }
138
- },
139
- usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
140
- explorer: "https://sepolia.etherscan.io/address/",
141
- explorerTx: "https://sepolia.etherscan.io/tx/",
142
- avgBlockTime: 12
143
110
  }
144
111
  };
145
112
  function getChain(name) {
@@ -305,7 +272,7 @@ Server accepts: ${serverChains.join(", ")}`
305
272
  } else {
306
273
  throw new Error(
307
274
  `Server accepts: ${serverChains.join(", ")}
308
- Please specify: --chain base or --chain polygon`
275
+ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
309
276
  );
310
277
  }
311
278
  }
@@ -347,13 +314,19 @@ Please specify: --chain base or --chain polygon`
347
314
  if (!payTo) {
348
315
  throw new Error("Missing payTo address in payment requirements");
349
316
  }
350
- const authorization = await this.signEIP3009(payTo, amount, chain, token);
317
+ const domainOverride = req.extra && typeof req.extra === "object" && req.extra.name ? { name: req.extra.name, version: req.extra.version || "2" } : void 0;
318
+ const authorization = await this.signEIP3009(payTo, amount, chain, token, domainOverride);
351
319
  const tokenConfig = chain.tokens[token];
352
- const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
320
+ const extra = req.extra && typeof req.extra === "object" ? req.extra : {
321
+ name: tokenConfig.eip712Name || "USD Coin",
322
+ version: "2"
323
+ };
353
324
  const payload = {
354
325
  x402Version: X402_VERSION,
326
+ scheme: "exact",
327
+ network,
355
328
  payload: authorization,
356
- // v2 requires 'accepted' field with the requirements being fulfilled
329
+ // { authorization: {...}, signature: "0x..." }
357
330
  accepted: {
358
331
  scheme: "exact",
359
332
  network,
@@ -361,7 +334,7 @@ Please specify: --chain base or --chain polygon`
361
334
  amount: amountRaw,
362
335
  payTo,
363
336
  maxTimeoutSeconds: req.maxTimeoutSeconds || 300,
364
- extra: req.extra || { name: tokenName, version: "2" }
337
+ extra
365
338
  }
366
339
  };
367
340
  const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
@@ -391,7 +364,7 @@ Please specify: --chain base or --chain polygon`
391
364
  * This only signs - no on-chain transaction, no gas needed.
392
365
  * Supports both USDC and USDT.
393
366
  */
394
- async signEIP3009(to, amount, chain, token = "USDC") {
367
+ async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
395
368
  const validAfter = 0;
396
369
  const validBefore = Math.floor(Date.now() / 1e3) + 3600;
397
370
  const nonce = ethers.hexlify(ethers.randomBytes(32));
@@ -405,10 +378,11 @@ Please specify: --chain base or --chain polygon`
405
378
  validBefore: validBefore.toString(),
406
379
  nonce
407
380
  };
408
- const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
381
+ const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
382
+ const tokenVersion = domainOverride?.version || "2";
409
383
  const domain = {
410
384
  name: tokenName,
411
- version: "2",
385
+ version: tokenVersion,
412
386
  chainId: chain.chainId,
413
387
  verifyingContract: tokenConfig.address
414
388
  };
@@ -577,7 +551,7 @@ Please specify: --chain base or --chain polygon`
577
551
  if (!this.wallet) {
578
552
  throw new Error("Client not initialized");
579
553
  }
580
- const supportedChains = ["base", "polygon"];
554
+ const supportedChains = ["base", "polygon", "base_sepolia"];
581
555
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
582
556
  const results = {};
583
557
  await Promise.all(
@@ -626,8 +600,8 @@ init_esm_shims();
626
600
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
627
601
  import * as path2 from "path";
628
602
  var X402_VERSION2 = 2;
629
- var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
630
- var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
603
+ var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
604
+ var TESTNET_CHAIN_IDS = [84532];
631
605
  function loadEnvFile() {
632
606
  const envPaths = [
633
607
  path2.join(process.cwd(), ".env"),
@@ -662,31 +636,33 @@ var CDPFacilitator = class extends BaseFacilitator {
662
636
  displayName = "Coinbase CDP";
663
637
  supportedNetworks;
664
638
  endpoint;
665
- useMainnet;
666
639
  apiKeyId;
667
640
  apiKeySecret;
668
641
  constructor(config = {}) {
669
642
  super();
670
643
  loadEnvFile();
671
- this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
672
644
  this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
673
645
  this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
674
- this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
675
- this.supportedNetworks = this.useMainnet ? ["eip155:8453", "eip155:137"] : ["eip155:8453", "eip155:84532", "eip155:137"];
676
- if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
677
- console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
678
- console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
646
+ this.endpoint = CDP_URL;
647
+ this.supportedNetworks = [
648
+ "eip155:8453",
649
+ // Base mainnet
650
+ "eip155:137",
651
+ // Polygon mainnet
652
+ "eip155:84532"
653
+ // Base Sepolia (testnet)
654
+ ];
655
+ if (!this.apiKeyId || !this.apiKeySecret) {
656
+ console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
657
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
679
658
  }
680
659
  }
681
660
  /**
682
661
  * Get auth headers for CDP API requests
683
662
  */
684
663
  async getAuthHeaders(method, urlPath, body) {
685
- if (!this.useMainnet) {
686
- return {};
687
- }
688
664
  if (!this.apiKeyId || !this.apiKeySecret) {
689
- throw new Error("CDP credentials required for mainnet");
665
+ throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
690
666
  }
691
667
  try {
692
668
  const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
@@ -738,23 +714,23 @@ var CDPFacilitator = class extends BaseFacilitator {
738
714
  paymentPayload,
739
715
  paymentRequirements: requirements
740
716
  };
717
+ console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
718
+ const authHeaders = await this.getAuthHeaders(
719
+ "POST",
720
+ "/platform/v2/x402/verify",
721
+ requestBody
722
+ );
741
723
  const headers = {
742
- "Content-Type": "application/json"
724
+ "Content-Type": "application/json",
725
+ ...authHeaders
743
726
  };
744
- if (this.useMainnet) {
745
- const authHeaders = await this.getAuthHeaders(
746
- "POST",
747
- "/platform/v2/x402/verify",
748
- requestBody
749
- );
750
- Object.assign(headers, authHeaders);
751
- }
752
727
  const response = await fetch(`${this.endpoint}/verify`, {
753
728
  method: "POST",
754
729
  headers,
755
730
  body: JSON.stringify(requestBody)
756
731
  });
757
732
  const result = await response.json();
733
+ console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
758
734
  if (!response.ok || !result.isValid) {
759
735
  return {
760
736
  valid: false,
@@ -780,17 +756,15 @@ var CDPFacilitator = class extends BaseFacilitator {
780
756
  paymentPayload,
781
757
  paymentRequirements: requirements
782
758
  };
759
+ const authHeaders = await this.getAuthHeaders(
760
+ "POST",
761
+ "/platform/v2/x402/settle",
762
+ requestBody
763
+ );
783
764
  const headers = {
784
- "Content-Type": "application/json"
765
+ "Content-Type": "application/json",
766
+ ...authHeaders
785
767
  };
786
- if (this.useMainnet) {
787
- const authHeaders = await this.getAuthHeaders(
788
- "POST",
789
- "/platform/v2/x402/settle",
790
- requestBody
791
- );
792
- Object.assign(headers, authHeaders);
793
- }
794
768
  const response = await fetch(`${this.endpoint}/settle`, {
795
769
  method: "POST",
796
770
  headers,
@@ -825,13 +799,19 @@ var CDPFacilitator = class extends BaseFacilitator {
825
799
  freeQuota: 1e3
826
800
  };
827
801
  }
802
+ /**
803
+ * Check if a chain ID is testnet
804
+ */
805
+ static isTestnet(chainId) {
806
+ return TESTNET_CHAIN_IDS.includes(chainId);
807
+ }
828
808
  /**
829
809
  * Get configuration summary (for logging)
830
810
  */
831
811
  getConfigSummary() {
832
- const mode = this.useMainnet ? "mainnet" : "testnet";
833
812
  const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
834
- return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
813
+ const networks = this.supportedNetworks.join(", ");
814
+ return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
835
815
  }
836
816
  };
837
817
 
@@ -1089,9 +1069,27 @@ var CHAIN_TO_NETWORK = {
1089
1069
  "polygon": "eip155:137"
1090
1070
  };
1091
1071
  var TOKEN_DOMAINS = {
1092
- USDC: { name: "USD Coin", version: "2" },
1093
- USDT: { name: "Tether USD", version: "2" }
1072
+ // Base mainnet
1073
+ "eip155:8453": {
1074
+ USDC: { name: "USD Coin", version: "2" },
1075
+ USDT: { name: "Tether USD", version: "2" }
1076
+ },
1077
+ // Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
1078
+ "eip155:84532": {
1079
+ USDC: { name: "USDC", version: "2" },
1080
+ USDT: { name: "USDC", version: "2" }
1081
+ // Same contract as USDC on testnet
1082
+ },
1083
+ // Polygon mainnet
1084
+ "eip155:137": {
1085
+ USDC: { name: "USD Coin", version: "2" },
1086
+ USDT: { name: "(PoS) Tether USD", version: "2" }
1087
+ }
1094
1088
  };
1089
+ function getTokenDomain(network, token) {
1090
+ const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
1091
+ return networkDomains[token] || { name: "USD Coin", version: "2" };
1092
+ }
1095
1093
  function getAcceptedCurrencies(config) {
1096
1094
  return config.acceptedCurrencies ?? [config.currency];
1097
1095
  }
@@ -1185,11 +1183,14 @@ var MoltsPayServer = class {
1185
1183
  getProviderChains() {
1186
1184
  const provider = this.manifest.provider;
1187
1185
  if (provider.chains && provider.chains.length > 0) {
1188
- return provider.chains.map((c) => ({
1189
- network: c.network || CHAIN_TO_NETWORK[c.chain] || "eip155:8453",
1190
- wallet: c.wallet || provider.wallet,
1191
- tokens: c.tokens || ["USDC"]
1192
- }));
1186
+ return provider.chains.map((c) => {
1187
+ const chainName = typeof c === "string" ? c : c.chain;
1188
+ return {
1189
+ network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
1190
+ wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1191
+ tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
1192
+ };
1193
+ });
1193
1194
  }
1194
1195
  const chain = provider.chain || "base";
1195
1196
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
@@ -1399,7 +1400,7 @@ var MoltsPayServer = class {
1399
1400
  const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
1400
1401
  const paymentWallet = this.getWalletForNetwork(paymentNetwork);
1401
1402
  const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
1402
- console.log(`[MoltsPay] Verifying payment...`);
1403
+ console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
1403
1404
  const verifyResult = await this.registry.verify(payment, requirements);
1404
1405
  if (!verifyResult.valid) {
1405
1406
  return this.sendJson(res, 402, {
@@ -1525,7 +1526,7 @@ var MoltsPayServer = class {
1525
1526
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1526
1527
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
1527
1528
  const tokenAddress = tokenAddresses[selectedToken];
1528
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1529
+ const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
1529
1530
  return {
1530
1531
  scheme: "exact",
1531
1532
  network: selectedNetwork,
@@ -1770,7 +1771,7 @@ var MoltsPayServer = class {
1770
1771
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1771
1772
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1772
1773
  const tokenAddress = tokenAddresses[selectedToken];
1773
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1774
+ const tokenDomain = getTokenDomain(networkId, selectedToken);
1774
1775
  return {
1775
1776
  scheme: "exact",
1776
1777
  network: networkId,
@@ -1852,6 +1853,11 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
1852
1853
  return;
1853
1854
  }
1854
1855
  let chain = options.chain;
1856
+ const supportedChains = ["base", "polygon", "base_sepolia"];
1857
+ if (!supportedChains.includes(chain)) {
1858
+ console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
1859
+ process.exit(1);
1860
+ }
1855
1861
  let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
1856
1862
  let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
1857
1863
  if (!maxPerTx) {
@@ -1914,7 +1920,7 @@ program.command("config").description("Update MoltsPay settings").option("--max-
1914
1920
  }
1915
1921
  }
1916
1922
  });
1917
- program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base or polygon)", "base").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (amountStr, options) => {
1923
+ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base, polygon, or base_sepolia)", "base").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (amountStr, options) => {
1918
1924
  const client = new MoltsPayClient({ configDir: options.configDir });
1919
1925
  if (!client.isInitialized) {
1920
1926
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -1926,8 +1932,20 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1926
1932
  return;
1927
1933
  }
1928
1934
  const chain = options.chain?.toLowerCase() || "base";
1929
- if (!["base", "polygon"].includes(chain)) {
1930
- console.log("\u274C Invalid chain. Use: base or polygon");
1935
+ if (!["base", "polygon", "base_sepolia"].includes(chain)) {
1936
+ console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
1937
+ return;
1938
+ }
1939
+ if (chain === "base_sepolia") {
1940
+ console.log("\n\u{1F9EA} Testnet Funding\n");
1941
+ console.log(` Wallet: ${client.address}`);
1942
+ console.log(` Chain: Base Sepolia (testnet)
1943
+ `);
1944
+ console.log("\u{1F4DD} Get testnet USDC from these faucets:");
1945
+ console.log(" \u2022 Circle Faucet: https://faucet.circle.com/");
1946
+ console.log(" \u2022 Base Sepolia: https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet\n");
1947
+ console.log(`\u{1F4A1} Send USDC to: ${client.address}
1948
+ `);
1931
1949
  return;
1932
1950
  }
1933
1951
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
@@ -1959,6 +1977,52 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1959
1977
  console.log(`\u274C ${error.message}`);
1960
1978
  }
1961
1979
  });
1980
+ program.command("faucet").description("Request testnet USDC from MoltsPay faucet (Base Sepolia)").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
1981
+ let address = options.address;
1982
+ if (!address) {
1983
+ const client = new MoltsPayClient({ configDir: options.configDir });
1984
+ if (client.isInitialized) {
1985
+ address = client.address;
1986
+ } else {
1987
+ console.log('\u274C No wallet found. Either run "npx moltspay init" or provide --address');
1988
+ return;
1989
+ }
1990
+ }
1991
+ if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
1992
+ console.log("\u274C Invalid Ethereum address");
1993
+ return;
1994
+ }
1995
+ console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
1996
+ console.log(` Requesting 1 USDC on Base Sepolia...`);
1997
+ console.log(` Address: ${address}
1998
+ `);
1999
+ try {
2000
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
2001
+ const response = await fetch(FAUCET_API, {
2002
+ method: "POST",
2003
+ headers: { "Content-Type": "application/json" },
2004
+ body: JSON.stringify({ address })
2005
+ });
2006
+ const result = await response.json();
2007
+ if (!response.ok) {
2008
+ console.log(`\u274C ${result.error || "Request failed"}`);
2009
+ if (result.hint) console.log(` ${result.hint}`);
2010
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
2011
+ return;
2012
+ }
2013
+ console.log(`\u2705 Received ${result.amount} USDC!
2014
+ `);
2015
+ console.log(` Transaction: ${result.transaction}`);
2016
+ console.log(` Explorer: ${result.explorer}`);
2017
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
2018
+ `);
2019
+ console.log("\u{1F4A1} Use this USDC to test x402 payments:");
2020
+ console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
2021
+ `);
2022
+ } catch (error) {
2023
+ console.log(`\u274C ${error.message}`);
2024
+ }
2025
+ });
1962
2026
  program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
1963
2027
  const client = new MoltsPayClient({ configDir: options.configDir });
1964
2028
  if (!client.isInitialized) {
@@ -1998,7 +2062,7 @@ program.command("status").description("Show wallet status and balance").option("
1998
2062
  console.log("");
1999
2063
  }
2000
2064
  });
2001
- program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
2065
+ program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, base_sepolia, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
2002
2066
  const client = new MoltsPayClient({ configDir: options.configDir });
2003
2067
  if (!client.isInitialized) {
2004
2068
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2007,8 +2071,8 @@ program.command("list").description("List recent transactions").option("--days <
2007
2071
  const days = parseInt(options.days) || 7;
2008
2072
  const limit = parseInt(options.limit) || 20;
2009
2073
  const chain = options.chain?.toLowerCase() || "all";
2010
- if (!["base", "polygon", "all"].includes(chain)) {
2011
- console.log("\u274C Invalid chain. Use: base, polygon, or all");
2074
+ if (!["base", "polygon", "base_sepolia", "all"].includes(chain)) {
2075
+ console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, or all");
2012
2076
  return;
2013
2077
  }
2014
2078
  const wallet = client.address;
@@ -2023,9 +2087,14 @@ program.command("list").description("List recent transactions").option("--days <
2023
2087
  api: "https://polygon.blockscout.com/api/v2",
2024
2088
  usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
2025
2089
  name: "Polygon"
2090
+ },
2091
+ base_sepolia: {
2092
+ api: "https://base-sepolia.blockscout.com/api/v2",
2093
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
2094
+ name: "Base Sepolia"
2026
2095
  }
2027
2096
  };
2028
- const chainsToQuery = chain === "all" ? ["base", "polygon"] : [chain];
2097
+ const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia"] : [chain];
2029
2098
  console.log(`
2030
2099
  \u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
2031
2100
  `);
@@ -2322,7 +2391,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
2322
2391
  process.exit(1);
2323
2392
  }
2324
2393
  });
2325
- program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base or polygon). Required if server accepts multiple chains.").option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
2394
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, or base_sepolia). Required if server accepts multiple chains.").option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
2326
2395
  const client = new MoltsPayClient();
2327
2396
  if (!client.isInitialized) {
2328
2397
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
@@ -2357,8 +2426,8 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
2357
2426
  process.exit(1);
2358
2427
  }
2359
2428
  const chain = options.chain?.toLowerCase();
2360
- if (chain && !["base", "polygon"].includes(chain)) {
2361
- console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon`);
2429
+ if (chain && !["base", "polygon", "base_sepolia"].includes(chain)) {
2430
+ console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia`);
2362
2431
  process.exit(1);
2363
2432
  }
2364
2433
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);