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
package/dist/cli/index.js CHANGED
@@ -60,12 +60,15 @@ var CHAINS = {
60
60
  USDC: {
61
61
  address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
62
62
  decimals: 6,
63
- symbol: "USDC"
63
+ symbol: "USDC",
64
+ eip712Name: "USD Coin"
65
+ // EIP-712 domain name
64
66
  },
65
67
  USDT: {
66
68
  address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
67
69
  decimals: 6,
68
- symbol: "USDT"
70
+ symbol: "USDT",
71
+ eip712Name: "Tether USD"
69
72
  }
70
73
  },
71
74
  usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -82,12 +85,15 @@ var CHAINS = {
82
85
  USDC: {
83
86
  address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
84
87
  decimals: 6,
85
- symbol: "USDC"
88
+ symbol: "USDC",
89
+ eip712Name: "USD Coin"
86
90
  },
87
91
  USDT: {
88
92
  address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
89
93
  decimals: 6,
90
- symbol: "USDT"
94
+ symbol: "USDT",
95
+ eip712Name: "(PoS) Tether USD"
96
+ // Polygon uses this name
91
97
  }
92
98
  },
93
99
  usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
@@ -95,27 +101,6 @@ var CHAINS = {
95
101
  explorerTx: "https://polygonscan.com/tx/",
96
102
  avgBlockTime: 2
97
103
  },
98
- ethereum: {
99
- name: "Ethereum",
100
- chainId: 1,
101
- rpc: "https://eth.llamarpc.com",
102
- tokens: {
103
- USDC: {
104
- address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
105
- decimals: 6,
106
- symbol: "USDC"
107
- },
108
- USDT: {
109
- address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
110
- decimals: 6,
111
- symbol: "USDT"
112
- }
113
- },
114
- usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
115
- explorer: "https://etherscan.io/address/",
116
- explorerTx: "https://etherscan.io/tx/",
117
- avgBlockTime: 12
118
- },
119
104
  // ============ Testnet ============
120
105
  base_sepolia: {
121
106
  name: "Base Sepolia",
@@ -125,41 +110,23 @@ var CHAINS = {
125
110
  USDC: {
126
111
  address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
127
112
  decimals: 6,
128
- symbol: "USDC"
113
+ symbol: "USDC",
114
+ eip712Name: "USDC"
115
+ // Testnet USDC uses 'USDC' not 'USD Coin'
129
116
  },
130
117
  USDT: {
131
118
  address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
132
119
  // Same as USDC on testnet (no official USDT)
133
120
  decimals: 6,
134
- symbol: "USDT"
121
+ symbol: "USDT",
122
+ eip712Name: "USDC"
123
+ // Uses same contract as USDC
135
124
  }
136
125
  },
137
126
  usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
138
127
  explorer: "https://sepolia.basescan.org/address/",
139
128
  explorerTx: "https://sepolia.basescan.org/tx/",
140
129
  avgBlockTime: 2
141
- },
142
- sepolia: {
143
- name: "Sepolia",
144
- chainId: 11155111,
145
- rpc: "https://rpc.sepolia.org",
146
- tokens: {
147
- USDC: {
148
- address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
149
- decimals: 6,
150
- symbol: "USDC"
151
- },
152
- USDT: {
153
- address: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
154
- // Same as USDC on testnet
155
- decimals: 6,
156
- symbol: "USDT"
157
- }
158
- },
159
- usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
160
- explorer: "https://sepolia.etherscan.io/address/",
161
- explorerTx: "https://sepolia.etherscan.io/tx/",
162
- avgBlockTime: 12
163
130
  }
164
131
  };
165
132
  function getChain(name) {
@@ -325,7 +292,7 @@ Server accepts: ${serverChains.join(", ")}`
325
292
  } else {
326
293
  throw new Error(
327
294
  `Server accepts: ${serverChains.join(", ")}
328
- Please specify: --chain base or --chain polygon`
295
+ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
329
296
  );
330
297
  }
331
298
  }
@@ -367,13 +334,19 @@ Please specify: --chain base or --chain polygon`
367
334
  if (!payTo) {
368
335
  throw new Error("Missing payTo address in payment requirements");
369
336
  }
370
- const authorization = await this.signEIP3009(payTo, amount, chain, token);
337
+ const domainOverride = req.extra && typeof req.extra === "object" && req.extra.name ? { name: req.extra.name, version: req.extra.version || "2" } : void 0;
338
+ const authorization = await this.signEIP3009(payTo, amount, chain, token, domainOverride);
371
339
  const tokenConfig = chain.tokens[token];
372
- const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
340
+ const extra = req.extra && typeof req.extra === "object" ? req.extra : {
341
+ name: tokenConfig.eip712Name || "USD Coin",
342
+ version: "2"
343
+ };
373
344
  const payload = {
374
345
  x402Version: X402_VERSION,
346
+ scheme: "exact",
347
+ network,
375
348
  payload: authorization,
376
- // v2 requires 'accepted' field with the requirements being fulfilled
349
+ // { authorization: {...}, signature: "0x..." }
377
350
  accepted: {
378
351
  scheme: "exact",
379
352
  network,
@@ -381,7 +354,7 @@ Please specify: --chain base or --chain polygon`
381
354
  amount: amountRaw,
382
355
  payTo,
383
356
  maxTimeoutSeconds: req.maxTimeoutSeconds || 300,
384
- extra: req.extra || { name: tokenName, version: "2" }
357
+ extra
385
358
  }
386
359
  };
387
360
  const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
@@ -411,7 +384,7 @@ Please specify: --chain base or --chain polygon`
411
384
  * This only signs - no on-chain transaction, no gas needed.
412
385
  * Supports both USDC and USDT.
413
386
  */
414
- async signEIP3009(to, amount, chain, token = "USDC") {
387
+ async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
415
388
  const validAfter = 0;
416
389
  const validBefore = Math.floor(Date.now() / 1e3) + 3600;
417
390
  const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
@@ -425,10 +398,11 @@ Please specify: --chain base or --chain polygon`
425
398
  validBefore: validBefore.toString(),
426
399
  nonce
427
400
  };
428
- const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
401
+ const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
402
+ const tokenVersion = domainOverride?.version || "2";
429
403
  const domain = {
430
404
  name: tokenName,
431
- version: "2",
405
+ version: tokenVersion,
432
406
  chainId: chain.chainId,
433
407
  verifyingContract: tokenConfig.address
434
408
  };
@@ -597,7 +571,7 @@ Please specify: --chain base or --chain polygon`
597
571
  if (!this.wallet) {
598
572
  throw new Error("Client not initialized");
599
573
  }
600
- const supportedChains = ["base", "polygon"];
574
+ const supportedChains = ["base", "polygon", "base_sepolia"];
601
575
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
602
576
  const results = {};
603
577
  await Promise.all(
@@ -646,8 +620,8 @@ init_cjs_shims();
646
620
  var import_fs2 = require("fs");
647
621
  var path = __toESM(require("path"));
648
622
  var X402_VERSION2 = 2;
649
- var CDP_MAINNET_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
650
- var CDP_TESTNET_URL = "https://www.x402.org/facilitator";
623
+ var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
624
+ var TESTNET_CHAIN_IDS = [84532];
651
625
  function loadEnvFile() {
652
626
  const envPaths = [
653
627
  path.join(process.cwd(), ".env"),
@@ -682,31 +656,33 @@ var CDPFacilitator = class extends BaseFacilitator {
682
656
  displayName = "Coinbase CDP";
683
657
  supportedNetworks;
684
658
  endpoint;
685
- useMainnet;
686
659
  apiKeyId;
687
660
  apiKeySecret;
688
661
  constructor(config = {}) {
689
662
  super();
690
663
  loadEnvFile();
691
- this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
692
664
  this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
693
665
  this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
694
- this.endpoint = this.useMainnet ? CDP_MAINNET_URL : CDP_TESTNET_URL;
695
- this.supportedNetworks = this.useMainnet ? ["eip155:8453", "eip155:137"] : ["eip155:8453", "eip155:84532", "eip155:137"];
696
- if (this.useMainnet && (!this.apiKeyId || !this.apiKeySecret)) {
697
- console.warn("[CDPFacilitator] WARNING: Mainnet mode but missing CDP credentials!");
698
- console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
666
+ this.endpoint = CDP_URL;
667
+ this.supportedNetworks = [
668
+ "eip155:8453",
669
+ // Base mainnet
670
+ "eip155:137",
671
+ // Polygon mainnet
672
+ "eip155:84532"
673
+ // Base Sepolia (testnet)
674
+ ];
675
+ if (!this.apiKeyId || !this.apiKeySecret) {
676
+ console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
677
+ console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
699
678
  }
700
679
  }
701
680
  /**
702
681
  * Get auth headers for CDP API requests
703
682
  */
704
683
  async getAuthHeaders(method, urlPath, body) {
705
- if (!this.useMainnet) {
706
- return {};
707
- }
708
684
  if (!this.apiKeyId || !this.apiKeySecret) {
709
- throw new Error("CDP credentials required for mainnet");
685
+ throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
710
686
  }
711
687
  try {
712
688
  const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
@@ -758,23 +734,23 @@ var CDPFacilitator = class extends BaseFacilitator {
758
734
  paymentPayload,
759
735
  paymentRequirements: requirements
760
736
  };
737
+ console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
738
+ const authHeaders = await this.getAuthHeaders(
739
+ "POST",
740
+ "/platform/v2/x402/verify",
741
+ requestBody
742
+ );
761
743
  const headers = {
762
- "Content-Type": "application/json"
744
+ "Content-Type": "application/json",
745
+ ...authHeaders
763
746
  };
764
- if (this.useMainnet) {
765
- const authHeaders = await this.getAuthHeaders(
766
- "POST",
767
- "/platform/v2/x402/verify",
768
- requestBody
769
- );
770
- Object.assign(headers, authHeaders);
771
- }
772
747
  const response = await fetch(`${this.endpoint}/verify`, {
773
748
  method: "POST",
774
749
  headers,
775
750
  body: JSON.stringify(requestBody)
776
751
  });
777
752
  const result = await response.json();
753
+ console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
778
754
  if (!response.ok || !result.isValid) {
779
755
  return {
780
756
  valid: false,
@@ -800,17 +776,15 @@ var CDPFacilitator = class extends BaseFacilitator {
800
776
  paymentPayload,
801
777
  paymentRequirements: requirements
802
778
  };
779
+ const authHeaders = await this.getAuthHeaders(
780
+ "POST",
781
+ "/platform/v2/x402/settle",
782
+ requestBody
783
+ );
803
784
  const headers = {
804
- "Content-Type": "application/json"
785
+ "Content-Type": "application/json",
786
+ ...authHeaders
805
787
  };
806
- if (this.useMainnet) {
807
- const authHeaders = await this.getAuthHeaders(
808
- "POST",
809
- "/platform/v2/x402/settle",
810
- requestBody
811
- );
812
- Object.assign(headers, authHeaders);
813
- }
814
788
  const response = await fetch(`${this.endpoint}/settle`, {
815
789
  method: "POST",
816
790
  headers,
@@ -845,13 +819,19 @@ var CDPFacilitator = class extends BaseFacilitator {
845
819
  freeQuota: 1e3
846
820
  };
847
821
  }
822
+ /**
823
+ * Check if a chain ID is testnet
824
+ */
825
+ static isTestnet(chainId) {
826
+ return TESTNET_CHAIN_IDS.includes(chainId);
827
+ }
848
828
  /**
849
829
  * Get configuration summary (for logging)
850
830
  */
851
831
  getConfigSummary() {
852
- const mode = this.useMainnet ? "mainnet" : "testnet";
853
832
  const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
854
- return `CDP Facilitator (${mode}, credentials: ${hasCredentials ? "yes" : "no"})`;
833
+ const networks = this.supportedNetworks.join(", ");
834
+ return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
855
835
  }
856
836
  };
857
837
 
@@ -1109,9 +1089,27 @@ var CHAIN_TO_NETWORK = {
1109
1089
  "polygon": "eip155:137"
1110
1090
  };
1111
1091
  var TOKEN_DOMAINS = {
1112
- USDC: { name: "USD Coin", version: "2" },
1113
- USDT: { name: "Tether USD", version: "2" }
1092
+ // Base mainnet
1093
+ "eip155:8453": {
1094
+ USDC: { name: "USD Coin", version: "2" },
1095
+ USDT: { name: "Tether USD", version: "2" }
1096
+ },
1097
+ // Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
1098
+ "eip155:84532": {
1099
+ USDC: { name: "USDC", version: "2" },
1100
+ USDT: { name: "USDC", version: "2" }
1101
+ // Same contract as USDC on testnet
1102
+ },
1103
+ // Polygon mainnet
1104
+ "eip155:137": {
1105
+ USDC: { name: "USD Coin", version: "2" },
1106
+ USDT: { name: "(PoS) Tether USD", version: "2" }
1107
+ }
1114
1108
  };
1109
+ function getTokenDomain(network, token) {
1110
+ const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
1111
+ return networkDomains[token] || { name: "USD Coin", version: "2" };
1112
+ }
1115
1113
  function getAcceptedCurrencies(config) {
1116
1114
  return config.acceptedCurrencies ?? [config.currency];
1117
1115
  }
@@ -1205,11 +1203,14 @@ var MoltsPayServer = class {
1205
1203
  getProviderChains() {
1206
1204
  const provider = this.manifest.provider;
1207
1205
  if (provider.chains && provider.chains.length > 0) {
1208
- return provider.chains.map((c) => ({
1209
- network: c.network || CHAIN_TO_NETWORK[c.chain] || "eip155:8453",
1210
- wallet: c.wallet || provider.wallet,
1211
- tokens: c.tokens || ["USDC"]
1212
- }));
1206
+ return provider.chains.map((c) => {
1207
+ const chainName = typeof c === "string" ? c : c.chain;
1208
+ return {
1209
+ network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
1210
+ wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
1211
+ tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
1212
+ };
1213
+ });
1213
1214
  }
1214
1215
  const chain = provider.chain || "base";
1215
1216
  const network = CHAIN_TO_NETWORK[chain] || this.networkId;
@@ -1419,7 +1420,7 @@ var MoltsPayServer = class {
1419
1420
  const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
1420
1421
  const paymentWallet = this.getWalletForNetwork(paymentNetwork);
1421
1422
  const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
1422
- console.log(`[MoltsPay] Verifying payment...`);
1423
+ console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
1423
1424
  const verifyResult = await this.registry.verify(payment, requirements);
1424
1425
  if (!verifyResult.valid) {
1425
1426
  return this.sendJson(res, 402, {
@@ -1545,7 +1546,7 @@ var MoltsPayServer = class {
1545
1546
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1546
1547
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
1547
1548
  const tokenAddress = tokenAddresses[selectedToken];
1548
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1549
+ const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
1549
1550
  return {
1550
1551
  scheme: "exact",
1551
1552
  network: selectedNetwork,
@@ -1790,7 +1791,7 @@ var MoltsPayServer = class {
1790
1791
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
1791
1792
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
1792
1793
  const tokenAddress = tokenAddresses[selectedToken];
1793
- const tokenDomain = TOKEN_DOMAINS[selectedToken] || TOKEN_DOMAINS.USDC;
1794
+ const tokenDomain = getTokenDomain(networkId, selectedToken);
1794
1795
  return {
1795
1796
  scheme: "exact",
1796
1797
  network: networkId,
@@ -1872,6 +1873,11 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
1872
1873
  return;
1873
1874
  }
1874
1875
  let chain = options.chain;
1876
+ const supportedChains = ["base", "polygon", "base_sepolia"];
1877
+ if (!supportedChains.includes(chain)) {
1878
+ console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
1879
+ process.exit(1);
1880
+ }
1875
1881
  let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
1876
1882
  let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
1877
1883
  if (!maxPerTx) {
@@ -1934,7 +1940,7 @@ program.command("config").description("Update MoltsPay settings").option("--max-
1934
1940
  }
1935
1941
  }
1936
1942
  });
1937
- 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) => {
1943
+ 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) => {
1938
1944
  const client = new MoltsPayClient({ configDir: options.configDir });
1939
1945
  if (!client.isInitialized) {
1940
1946
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -1946,8 +1952,20 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1946
1952
  return;
1947
1953
  }
1948
1954
  const chain = options.chain?.toLowerCase() || "base";
1949
- if (!["base", "polygon"].includes(chain)) {
1950
- console.log("\u274C Invalid chain. Use: base or polygon");
1955
+ if (!["base", "polygon", "base_sepolia"].includes(chain)) {
1956
+ console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
1957
+ return;
1958
+ }
1959
+ if (chain === "base_sepolia") {
1960
+ console.log("\n\u{1F9EA} Testnet Funding\n");
1961
+ console.log(` Wallet: ${client.address}`);
1962
+ console.log(` Chain: Base Sepolia (testnet)
1963
+ `);
1964
+ console.log("\u{1F4DD} Get testnet USDC from these faucets:");
1965
+ console.log(" \u2022 Circle Faucet: https://faucet.circle.com/");
1966
+ console.log(" \u2022 Base Sepolia: https://www.coinbase.com/faucets/base-ethereum-sepolia-faucet\n");
1967
+ console.log(`\u{1F4A1} Send USDC to: ${client.address}
1968
+ `);
1951
1969
  return;
1952
1970
  }
1953
1971
  console.log("\n\u{1F4B3} Fund your agent wallet\n");
@@ -1979,6 +1997,52 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
1979
1997
  console.log(`\u274C ${error.message}`);
1980
1998
  }
1981
1999
  });
2000
+ 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) => {
2001
+ let address = options.address;
2002
+ if (!address) {
2003
+ const client = new MoltsPayClient({ configDir: options.configDir });
2004
+ if (client.isInitialized) {
2005
+ address = client.address;
2006
+ } else {
2007
+ console.log('\u274C No wallet found. Either run "npx moltspay init" or provide --address');
2008
+ return;
2009
+ }
2010
+ }
2011
+ if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
2012
+ console.log("\u274C Invalid Ethereum address");
2013
+ return;
2014
+ }
2015
+ console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
2016
+ console.log(` Requesting 1 USDC on Base Sepolia...`);
2017
+ console.log(` Address: ${address}
2018
+ `);
2019
+ try {
2020
+ const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
2021
+ const response = await fetch(FAUCET_API, {
2022
+ method: "POST",
2023
+ headers: { "Content-Type": "application/json" },
2024
+ body: JSON.stringify({ address })
2025
+ });
2026
+ const result = await response.json();
2027
+ if (!response.ok) {
2028
+ console.log(`\u274C ${result.error || "Request failed"}`);
2029
+ if (result.hint) console.log(` ${result.hint}`);
2030
+ if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
2031
+ return;
2032
+ }
2033
+ console.log(`\u2705 Received ${result.amount} USDC!
2034
+ `);
2035
+ console.log(` Transaction: ${result.transaction}`);
2036
+ console.log(` Explorer: ${result.explorer}`);
2037
+ console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
2038
+ `);
2039
+ console.log("\u{1F4A1} Use this USDC to test x402 payments:");
2040
+ console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
2041
+ `);
2042
+ } catch (error) {
2043
+ console.log(`\u274C ${error.message}`);
2044
+ }
2045
+ });
1982
2046
  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) => {
1983
2047
  const client = new MoltsPayClient({ configDir: options.configDir });
1984
2048
  if (!client.isInitialized) {
@@ -2018,7 +2082,7 @@ program.command("status").description("Show wallet status and balance").option("
2018
2082
  console.log("");
2019
2083
  }
2020
2084
  });
2021
- 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) => {
2085
+ 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) => {
2022
2086
  const client = new MoltsPayClient({ configDir: options.configDir });
2023
2087
  if (!client.isInitialized) {
2024
2088
  console.log("\u274C Not initialized. Run: npx moltspay init");
@@ -2027,8 +2091,8 @@ program.command("list").description("List recent transactions").option("--days <
2027
2091
  const days = parseInt(options.days) || 7;
2028
2092
  const limit = parseInt(options.limit) || 20;
2029
2093
  const chain = options.chain?.toLowerCase() || "all";
2030
- if (!["base", "polygon", "all"].includes(chain)) {
2031
- console.log("\u274C Invalid chain. Use: base, polygon, or all");
2094
+ if (!["base", "polygon", "base_sepolia", "all"].includes(chain)) {
2095
+ console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, or all");
2032
2096
  return;
2033
2097
  }
2034
2098
  const wallet = client.address;
@@ -2043,9 +2107,14 @@ program.command("list").description("List recent transactions").option("--days <
2043
2107
  api: "https://polygon.blockscout.com/api/v2",
2044
2108
  usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
2045
2109
  name: "Polygon"
2110
+ },
2111
+ base_sepolia: {
2112
+ api: "https://base-sepolia.blockscout.com/api/v2",
2113
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
2114
+ name: "Base Sepolia"
2046
2115
  }
2047
2116
  };
2048
- const chainsToQuery = chain === "all" ? ["base", "polygon"] : [chain];
2117
+ const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia"] : [chain];
2049
2118
  console.log(`
2050
2119
  \u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
2051
2120
  `);
@@ -2342,7 +2411,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
2342
2411
  process.exit(1);
2343
2412
  }
2344
2413
  });
2345
- 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) => {
2414
+ 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) => {
2346
2415
  const client = new MoltsPayClient();
2347
2416
  if (!client.isInitialized) {
2348
2417
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
@@ -2377,8 +2446,8 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
2377
2446
  process.exit(1);
2378
2447
  }
2379
2448
  const chain = options.chain?.toLowerCase();
2380
- if (chain && !["base", "polygon"].includes(chain)) {
2381
- console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon`);
2449
+ if (chain && !["base", "polygon", "base_sepolia"].includes(chain)) {
2450
+ console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia`);
2382
2451
  process.exit(1);
2383
2452
  }
2384
2453
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);