moltspay 1.2.0 → 1.3.0
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.
- package/README.md +161 -17
- package/dist/cdp/index.d.mts +1 -1
- package/dist/cdp/index.d.ts +1 -1
- package/dist/cdp/index.js +60 -30408
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs +44 -30400
- package/dist/cdp/index.mjs.map +1 -1
- package/dist/cdp-DeohBe1o.d.ts +66 -0
- package/dist/cdp-p_eHuQpb.d.mts +66 -0
- package/dist/chains/index.d.mts +1 -1
- package/dist/chains/index.d.ts +1 -1
- package/dist/chains/index.js +36 -40
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +36 -40
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +997 -174
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1001 -174
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +27 -4
- package/dist/client/index.d.ts +27 -4
- package/dist/client/index.js +217 -60
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +207 -60
- package/dist/client/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +15 -47
- package/dist/facilitators/index.d.ts +15 -47
- package/dist/facilitators/index.js +273 -34
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +272 -34
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/{index-B3v8IWjM.d.mts → index-On9ZaGDW.d.mts} +2 -1
- package/dist/{index-B3v8IWjM.d.ts → index-On9ZaGDW.d.ts} +2 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +792 -30657
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +782 -30657
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +17 -0
- package/dist/server/index.d.ts +17 -0
- package/dist/server/index.js +513 -48
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +513 -48
- package/dist/server/index.mjs.map +1 -1
- package/dist/verify/index.d.mts +1 -1
- package/dist/verify/index.d.ts +1 -1
- package/dist/verify/index.js +36 -40
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs +36 -40
- package/dist/verify/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +1 -1
- package/dist/wallet/index.d.ts +1 -1
- package/dist/wallet/index.js +36 -40
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +36 -40
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +5 -2
package/dist/cli/index.js
CHANGED
|
@@ -35,6 +35,7 @@ var init_cjs_shims = __esm({
|
|
|
35
35
|
|
|
36
36
|
// src/cli/index.ts
|
|
37
37
|
init_cjs_shims();
|
|
38
|
+
var import_crypto = require("crypto");
|
|
38
39
|
var import_commander = require("commander");
|
|
39
40
|
var import_os2 = require("os");
|
|
40
41
|
var import_path2 = require("path");
|
|
@@ -60,12 +61,15 @@ var CHAINS = {
|
|
|
60
61
|
USDC: {
|
|
61
62
|
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
62
63
|
decimals: 6,
|
|
63
|
-
symbol: "USDC"
|
|
64
|
+
symbol: "USDC",
|
|
65
|
+
eip712Name: "USD Coin"
|
|
66
|
+
// EIP-712 domain name
|
|
64
67
|
},
|
|
65
68
|
USDT: {
|
|
66
69
|
address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
|
|
67
70
|
decimals: 6,
|
|
68
|
-
symbol: "USDT"
|
|
71
|
+
symbol: "USDT",
|
|
72
|
+
eip712Name: "Tether USD"
|
|
69
73
|
}
|
|
70
74
|
},
|
|
71
75
|
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -82,12 +86,15 @@ var CHAINS = {
|
|
|
82
86
|
USDC: {
|
|
83
87
|
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
84
88
|
decimals: 6,
|
|
85
|
-
symbol: "USDC"
|
|
89
|
+
symbol: "USDC",
|
|
90
|
+
eip712Name: "USD Coin"
|
|
86
91
|
},
|
|
87
92
|
USDT: {
|
|
88
93
|
address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
89
94
|
decimals: 6,
|
|
90
|
-
symbol: "USDT"
|
|
95
|
+
symbol: "USDT",
|
|
96
|
+
eip712Name: "(PoS) Tether USD"
|
|
97
|
+
// Polygon uses this name
|
|
91
98
|
}
|
|
92
99
|
},
|
|
93
100
|
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
@@ -95,27 +102,6 @@ var CHAINS = {
|
|
|
95
102
|
explorerTx: "https://polygonscan.com/tx/",
|
|
96
103
|
avgBlockTime: 2
|
|
97
104
|
},
|
|
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
105
|
// ============ Testnet ============
|
|
120
106
|
base_sepolia: {
|
|
121
107
|
name: "Base Sepolia",
|
|
@@ -125,13 +111,17 @@ var CHAINS = {
|
|
|
125
111
|
USDC: {
|
|
126
112
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
127
113
|
decimals: 6,
|
|
128
|
-
symbol: "USDC"
|
|
114
|
+
symbol: "USDC",
|
|
115
|
+
eip712Name: "USDC"
|
|
116
|
+
// Testnet USDC uses 'USDC' not 'USD Coin'
|
|
129
117
|
},
|
|
130
118
|
USDT: {
|
|
131
119
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
132
120
|
// Same as USDC on testnet (no official USDT)
|
|
133
121
|
decimals: 6,
|
|
134
|
-
symbol: "USDT"
|
|
122
|
+
symbol: "USDT",
|
|
123
|
+
eip712Name: "USDC"
|
|
124
|
+
// Uses same contract as USDC
|
|
135
125
|
}
|
|
136
126
|
},
|
|
137
127
|
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
@@ -139,27 +129,34 @@ var CHAINS = {
|
|
|
139
129
|
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
140
130
|
avgBlockTime: 2
|
|
141
131
|
},
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
132
|
+
// ============ Tempo Testnet (Moderato) ============
|
|
133
|
+
tempo_moderato: {
|
|
134
|
+
name: "Tempo Moderato",
|
|
135
|
+
chainId: 42431,
|
|
136
|
+
rpc: "https://rpc.moderato.tempo.xyz",
|
|
146
137
|
tokens: {
|
|
138
|
+
// TIP-20 stablecoins on Tempo testnet (from mppx SDK)
|
|
139
|
+
// Note: Tempo uses USD as native gas token, not ETH
|
|
147
140
|
USDC: {
|
|
148
|
-
address: "
|
|
141
|
+
address: "0x20c0000000000000000000000000000000000000",
|
|
142
|
+
// pathUSD - primary testnet stablecoin
|
|
149
143
|
decimals: 6,
|
|
150
|
-
symbol: "USDC"
|
|
144
|
+
symbol: "USDC",
|
|
145
|
+
eip712Name: "pathUSD"
|
|
151
146
|
},
|
|
152
147
|
USDT: {
|
|
153
|
-
address: "
|
|
154
|
-
//
|
|
148
|
+
address: "0x20c0000000000000000000000000000000000001",
|
|
149
|
+
// alphaUSD
|
|
155
150
|
decimals: 6,
|
|
156
|
-
symbol: "USDT"
|
|
151
|
+
symbol: "USDT",
|
|
152
|
+
eip712Name: "alphaUSD"
|
|
157
153
|
}
|
|
158
154
|
},
|
|
159
|
-
usdc: "
|
|
160
|
-
explorer: "https://
|
|
161
|
-
explorerTx: "https://
|
|
162
|
-
avgBlockTime:
|
|
155
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
156
|
+
explorer: "https://explore.testnet.tempo.xyz/address/",
|
|
157
|
+
explorerTx: "https://explore.testnet.tempo.xyz/tx/",
|
|
158
|
+
avgBlockTime: 0.5
|
|
159
|
+
// ~500ms finality
|
|
163
160
|
}
|
|
164
161
|
};
|
|
165
162
|
function getChain(name) {
|
|
@@ -325,7 +322,7 @@ Server accepts: ${serverChains.join(", ")}`
|
|
|
325
322
|
} else {
|
|
326
323
|
throw new Error(
|
|
327
324
|
`Server accepts: ${serverChains.join(", ")}
|
|
328
|
-
Please specify: --chain base
|
|
325
|
+
Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
329
326
|
);
|
|
330
327
|
}
|
|
331
328
|
}
|
|
@@ -367,13 +364,19 @@ Please specify: --chain base or --chain polygon`
|
|
|
367
364
|
if (!payTo) {
|
|
368
365
|
throw new Error("Missing payTo address in payment requirements");
|
|
369
366
|
}
|
|
370
|
-
const
|
|
367
|
+
const domainOverride = req.extra && typeof req.extra === "object" && req.extra.name ? { name: req.extra.name, version: req.extra.version || "2" } : void 0;
|
|
368
|
+
const authorization = await this.signEIP3009(payTo, amount, chain, token, domainOverride);
|
|
371
369
|
const tokenConfig = chain.tokens[token];
|
|
372
|
-
const
|
|
370
|
+
const extra = req.extra && typeof req.extra === "object" ? req.extra : {
|
|
371
|
+
name: tokenConfig.eip712Name || "USD Coin",
|
|
372
|
+
version: "2"
|
|
373
|
+
};
|
|
373
374
|
const payload = {
|
|
374
375
|
x402Version: X402_VERSION,
|
|
376
|
+
scheme: "exact",
|
|
377
|
+
network,
|
|
375
378
|
payload: authorization,
|
|
376
|
-
//
|
|
379
|
+
// { authorization: {...}, signature: "0x..." }
|
|
377
380
|
accepted: {
|
|
378
381
|
scheme: "exact",
|
|
379
382
|
network,
|
|
@@ -381,7 +384,7 @@ Please specify: --chain base or --chain polygon`
|
|
|
381
384
|
amount: amountRaw,
|
|
382
385
|
payTo,
|
|
383
386
|
maxTimeoutSeconds: req.maxTimeoutSeconds || 300,
|
|
384
|
-
extra
|
|
387
|
+
extra
|
|
385
388
|
}
|
|
386
389
|
};
|
|
387
390
|
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
@@ -411,7 +414,7 @@ Please specify: --chain base or --chain polygon`
|
|
|
411
414
|
* This only signs - no on-chain transaction, no gas needed.
|
|
412
415
|
* Supports both USDC and USDT.
|
|
413
416
|
*/
|
|
414
|
-
async signEIP3009(to, amount, chain, token = "USDC") {
|
|
417
|
+
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
415
418
|
const validAfter = 0;
|
|
416
419
|
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
417
420
|
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
@@ -425,10 +428,11 @@ Please specify: --chain base or --chain polygon`
|
|
|
425
428
|
validBefore: validBefore.toString(),
|
|
426
429
|
nonce
|
|
427
430
|
};
|
|
428
|
-
const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
|
|
431
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
432
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
429
433
|
const domain = {
|
|
430
434
|
name: tokenName,
|
|
431
|
-
version:
|
|
435
|
+
version: tokenVersion,
|
|
432
436
|
chainId: chain.chainId,
|
|
433
437
|
verifyingContract: tokenConfig.address
|
|
434
438
|
};
|
|
@@ -591,30 +595,59 @@ Please specify: --chain base or --chain polygon`
|
|
|
591
595
|
};
|
|
592
596
|
}
|
|
593
597
|
/**
|
|
594
|
-
* Get wallet balances on all supported chains (Base + Polygon)
|
|
598
|
+
* Get wallet balances on all supported chains (Base + Polygon + Tempo)
|
|
595
599
|
*/
|
|
596
600
|
async getAllBalances() {
|
|
597
601
|
if (!this.wallet) {
|
|
598
602
|
throw new Error("Client not initialized");
|
|
599
603
|
}
|
|
600
|
-
const supportedChains = ["base", "polygon"];
|
|
604
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
601
605
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
602
606
|
const results = {};
|
|
607
|
+
const tempoTokens = {
|
|
608
|
+
pathUSD: "0x20c0000000000000000000000000000000000000",
|
|
609
|
+
alphaUSD: "0x20c0000000000000000000000000000000000001",
|
|
610
|
+
betaUSD: "0x20c0000000000000000000000000000000000002",
|
|
611
|
+
thetaUSD: "0x20c0000000000000000000000000000000000003"
|
|
612
|
+
};
|
|
603
613
|
await Promise.all(
|
|
604
614
|
supportedChains.map(async (chainName) => {
|
|
605
615
|
try {
|
|
606
616
|
const chain = getChain(chainName);
|
|
607
617
|
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
+
if (chainName === "tempo_moderato") {
|
|
619
|
+
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
620
|
+
provider.getBalance(this.wallet.address),
|
|
621
|
+
new import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
622
|
+
new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
623
|
+
new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
624
|
+
new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
625
|
+
]);
|
|
626
|
+
results[chainName] = {
|
|
627
|
+
usdc: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
|
|
628
|
+
// pathUSD as default USDC
|
|
629
|
+
usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
|
|
630
|
+
// alphaUSD as default USDT
|
|
631
|
+
native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
|
|
632
|
+
tempo: {
|
|
633
|
+
pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
|
|
634
|
+
alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
|
|
635
|
+
betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
|
|
636
|
+
thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
} else {
|
|
640
|
+
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
641
|
+
provider.getBalance(this.wallet.address),
|
|
642
|
+
new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
643
|
+
new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
644
|
+
]);
|
|
645
|
+
results[chainName] = {
|
|
646
|
+
usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
647
|
+
usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
648
|
+
native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
|
|
649
|
+
};
|
|
650
|
+
}
|
|
618
651
|
} catch (err) {
|
|
619
652
|
results[chainName] = { usdc: 0, usdt: 0, native: 0 };
|
|
620
653
|
}
|
|
@@ -622,6 +655,121 @@ Please specify: --chain base or --chain polygon`
|
|
|
622
655
|
);
|
|
623
656
|
return results;
|
|
624
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Pay for a service using MPP (Machine Payments Protocol)
|
|
660
|
+
*
|
|
661
|
+
* This implements the MPP flow manually for EOA wallets:
|
|
662
|
+
* 1. Request service → get 402 with WWW-Authenticate
|
|
663
|
+
* 2. Parse payment requirements
|
|
664
|
+
* 3. Execute transfer on Tempo chain
|
|
665
|
+
* 4. Retry with transaction hash as credential
|
|
666
|
+
*
|
|
667
|
+
* @param url - Full URL of the MPP-enabled endpoint
|
|
668
|
+
* @param options - Request options (body, headers)
|
|
669
|
+
* @returns Response from the service
|
|
670
|
+
*/
|
|
671
|
+
async payWithMPP(url, options = {}) {
|
|
672
|
+
if (!this.wallet || !this.walletData) {
|
|
673
|
+
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
674
|
+
}
|
|
675
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
676
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
677
|
+
const { tempoModerato } = await import("viem/chains");
|
|
678
|
+
const { Actions } = await import("viem/tempo");
|
|
679
|
+
const privateKey = this.walletData.privateKey;
|
|
680
|
+
const account = privateKeyToAccount(privateKey);
|
|
681
|
+
console.log(`[MoltsPay] Making MPP request to: ${url}`);
|
|
682
|
+
console.log(`[MoltsPay] Using account: ${account.address}`);
|
|
683
|
+
const initResponse = await fetch(url, {
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: {
|
|
686
|
+
"Content-Type": "application/json",
|
|
687
|
+
...options.headers
|
|
688
|
+
},
|
|
689
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
690
|
+
});
|
|
691
|
+
if (initResponse.status !== 402) {
|
|
692
|
+
if (initResponse.ok) {
|
|
693
|
+
return initResponse.json();
|
|
694
|
+
}
|
|
695
|
+
const errorText = await initResponse.text();
|
|
696
|
+
throw new Error(`Request failed (${initResponse.status}): ${errorText}`);
|
|
697
|
+
}
|
|
698
|
+
const wwwAuth = initResponse.headers.get("www-authenticate");
|
|
699
|
+
if (!wwwAuth || !wwwAuth.toLowerCase().includes("payment")) {
|
|
700
|
+
throw new Error("No WWW-Authenticate Payment challenge in 402 response");
|
|
701
|
+
}
|
|
702
|
+
console.log(`[MoltsPay] Got 402, parsing payment challenge...`);
|
|
703
|
+
const parseAuthParam = (header, key) => {
|
|
704
|
+
const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
|
|
705
|
+
return match ? match[1] : null;
|
|
706
|
+
};
|
|
707
|
+
const challengeId = parseAuthParam(wwwAuth, "id");
|
|
708
|
+
const method = parseAuthParam(wwwAuth, "method");
|
|
709
|
+
const realm = parseAuthParam(wwwAuth, "realm");
|
|
710
|
+
const requestB64 = parseAuthParam(wwwAuth, "request");
|
|
711
|
+
if (method !== "tempo") {
|
|
712
|
+
throw new Error(`Unsupported payment method: ${method}`);
|
|
713
|
+
}
|
|
714
|
+
if (!requestB64) {
|
|
715
|
+
throw new Error("Missing request in WWW-Authenticate");
|
|
716
|
+
}
|
|
717
|
+
const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
|
|
718
|
+
const paymentRequest = JSON.parse(requestJson);
|
|
719
|
+
console.log(`[MoltsPay] Payment request:`, paymentRequest);
|
|
720
|
+
const { amount, currency, recipient, methodDetails } = paymentRequest;
|
|
721
|
+
const chainId = methodDetails?.chainId || 42431;
|
|
722
|
+
console.log(`[MoltsPay] Executing transfer on Tempo (chainId: ${chainId})...`);
|
|
723
|
+
console.log(`[MoltsPay] Amount: ${amount}, To: ${recipient}`);
|
|
724
|
+
const tempoChain = { ...tempoModerato, feeToken: currency };
|
|
725
|
+
const publicClient = createPublicClient({
|
|
726
|
+
chain: tempoChain,
|
|
727
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
728
|
+
});
|
|
729
|
+
const walletClient = createWalletClient({
|
|
730
|
+
account,
|
|
731
|
+
chain: tempoChain,
|
|
732
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
733
|
+
});
|
|
734
|
+
const txHash = await Actions.token.transfer(walletClient, {
|
|
735
|
+
to: recipient,
|
|
736
|
+
amount: BigInt(amount),
|
|
737
|
+
token: currency
|
|
738
|
+
});
|
|
739
|
+
console.log(`[MoltsPay] Transaction sent: ${txHash}`);
|
|
740
|
+
console.log(`[MoltsPay] Waiting for confirmation...`);
|
|
741
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
742
|
+
console.log(`[MoltsPay] Transaction confirmed!`);
|
|
743
|
+
const challenge = {
|
|
744
|
+
id: challengeId,
|
|
745
|
+
realm,
|
|
746
|
+
method: "tempo",
|
|
747
|
+
intent: "charge",
|
|
748
|
+
request: paymentRequest
|
|
749
|
+
};
|
|
750
|
+
const credential = {
|
|
751
|
+
challenge,
|
|
752
|
+
payload: { hash: txHash, type: "hash" },
|
|
753
|
+
source: `did:pkh:eip155:${chainId}:${account.address}`
|
|
754
|
+
};
|
|
755
|
+
const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
756
|
+
console.log(`[MoltsPay] Retrying with payment credential...`);
|
|
757
|
+
const paidResponse = await fetch(url, {
|
|
758
|
+
method: "POST",
|
|
759
|
+
headers: {
|
|
760
|
+
"Content-Type": "application/json",
|
|
761
|
+
"Authorization": `Payment ${credentialB64}`,
|
|
762
|
+
...options.headers
|
|
763
|
+
},
|
|
764
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
765
|
+
});
|
|
766
|
+
if (!paidResponse.ok) {
|
|
767
|
+
const errorText = await paidResponse.text();
|
|
768
|
+
throw new Error(`Payment verification failed (${paidResponse.status}): ${errorText}`);
|
|
769
|
+
}
|
|
770
|
+
console.log(`[MoltsPay] Payment verified! Service completed.`);
|
|
771
|
+
return paidResponse.json();
|
|
772
|
+
}
|
|
625
773
|
};
|
|
626
774
|
|
|
627
775
|
// src/server/index.ts
|
|
@@ -646,8 +794,8 @@ init_cjs_shims();
|
|
|
646
794
|
var import_fs2 = require("fs");
|
|
647
795
|
var path = __toESM(require("path"));
|
|
648
796
|
var X402_VERSION2 = 2;
|
|
649
|
-
var
|
|
650
|
-
var
|
|
797
|
+
var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
798
|
+
var TESTNET_CHAIN_IDS = [84532];
|
|
651
799
|
function loadEnvFile() {
|
|
652
800
|
const envPaths = [
|
|
653
801
|
path.join(process.cwd(), ".env"),
|
|
@@ -682,31 +830,33 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
682
830
|
displayName = "Coinbase CDP";
|
|
683
831
|
supportedNetworks;
|
|
684
832
|
endpoint;
|
|
685
|
-
useMainnet;
|
|
686
833
|
apiKeyId;
|
|
687
834
|
apiKeySecret;
|
|
688
835
|
constructor(config = {}) {
|
|
689
836
|
super();
|
|
690
837
|
loadEnvFile();
|
|
691
|
-
this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
692
838
|
this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
|
|
693
839
|
this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
|
|
694
|
-
this.endpoint =
|
|
695
|
-
this.supportedNetworks =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
840
|
+
this.endpoint = CDP_URL;
|
|
841
|
+
this.supportedNetworks = [
|
|
842
|
+
"eip155:8453",
|
|
843
|
+
// Base mainnet
|
|
844
|
+
"eip155:137",
|
|
845
|
+
// Polygon mainnet
|
|
846
|
+
"eip155:84532"
|
|
847
|
+
// Base Sepolia (testnet)
|
|
848
|
+
];
|
|
849
|
+
if (!this.apiKeyId || !this.apiKeySecret) {
|
|
850
|
+
console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
|
|
851
|
+
console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
|
|
699
852
|
}
|
|
700
853
|
}
|
|
701
854
|
/**
|
|
702
855
|
* Get auth headers for CDP API requests
|
|
703
856
|
*/
|
|
704
857
|
async getAuthHeaders(method, urlPath, body) {
|
|
705
|
-
if (!this.useMainnet) {
|
|
706
|
-
return {};
|
|
707
|
-
}
|
|
708
858
|
if (!this.apiKeyId || !this.apiKeySecret) {
|
|
709
|
-
throw new Error("CDP credentials required
|
|
859
|
+
throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
|
|
710
860
|
}
|
|
711
861
|
try {
|
|
712
862
|
const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
|
|
@@ -758,23 +908,23 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
758
908
|
paymentPayload,
|
|
759
909
|
paymentRequirements: requirements
|
|
760
910
|
};
|
|
911
|
+
console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
|
|
912
|
+
const authHeaders = await this.getAuthHeaders(
|
|
913
|
+
"POST",
|
|
914
|
+
"/platform/v2/x402/verify",
|
|
915
|
+
requestBody
|
|
916
|
+
);
|
|
761
917
|
const headers = {
|
|
762
|
-
"Content-Type": "application/json"
|
|
918
|
+
"Content-Type": "application/json",
|
|
919
|
+
...authHeaders
|
|
763
920
|
};
|
|
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
921
|
const response = await fetch(`${this.endpoint}/verify`, {
|
|
773
922
|
method: "POST",
|
|
774
923
|
headers,
|
|
775
924
|
body: JSON.stringify(requestBody)
|
|
776
925
|
});
|
|
777
926
|
const result = await response.json();
|
|
927
|
+
console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
|
|
778
928
|
if (!response.ok || !result.isValid) {
|
|
779
929
|
return {
|
|
780
930
|
valid: false,
|
|
@@ -800,17 +950,15 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
800
950
|
paymentPayload,
|
|
801
951
|
paymentRequirements: requirements
|
|
802
952
|
};
|
|
953
|
+
const authHeaders = await this.getAuthHeaders(
|
|
954
|
+
"POST",
|
|
955
|
+
"/platform/v2/x402/settle",
|
|
956
|
+
requestBody
|
|
957
|
+
);
|
|
803
958
|
const headers = {
|
|
804
|
-
"Content-Type": "application/json"
|
|
959
|
+
"Content-Type": "application/json",
|
|
960
|
+
...authHeaders
|
|
805
961
|
};
|
|
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
962
|
const response = await fetch(`${this.endpoint}/settle`, {
|
|
815
963
|
method: "POST",
|
|
816
964
|
headers,
|
|
@@ -845,13 +993,140 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
845
993
|
freeQuota: 1e3
|
|
846
994
|
};
|
|
847
995
|
}
|
|
996
|
+
/**
|
|
997
|
+
* Check if a chain ID is testnet
|
|
998
|
+
*/
|
|
999
|
+
static isTestnet(chainId) {
|
|
1000
|
+
return TESTNET_CHAIN_IDS.includes(chainId);
|
|
1001
|
+
}
|
|
848
1002
|
/**
|
|
849
1003
|
* Get configuration summary (for logging)
|
|
850
1004
|
*/
|
|
851
1005
|
getConfigSummary() {
|
|
852
|
-
const mode = this.useMainnet ? "mainnet" : "testnet";
|
|
853
1006
|
const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
|
|
854
|
-
|
|
1007
|
+
const networks = this.supportedNetworks.join(", ");
|
|
1008
|
+
return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
|
|
1012
|
+
// src/facilitators/tempo.ts
|
|
1013
|
+
init_cjs_shims();
|
|
1014
|
+
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1015
|
+
var TempoFacilitator = class extends BaseFacilitator {
|
|
1016
|
+
name = "tempo";
|
|
1017
|
+
displayName = "Tempo Testnet";
|
|
1018
|
+
supportedNetworks = ["eip155:42431"];
|
|
1019
|
+
// Tempo Moderato
|
|
1020
|
+
rpcUrl;
|
|
1021
|
+
constructor() {
|
|
1022
|
+
super();
|
|
1023
|
+
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
1024
|
+
}
|
|
1025
|
+
async healthCheck() {
|
|
1026
|
+
const start = Date.now();
|
|
1027
|
+
try {
|
|
1028
|
+
const response = await fetch(this.rpcUrl, {
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
headers: { "Content-Type": "application/json" },
|
|
1031
|
+
body: JSON.stringify({
|
|
1032
|
+
jsonrpc: "2.0",
|
|
1033
|
+
method: "eth_chainId",
|
|
1034
|
+
params: [],
|
|
1035
|
+
id: 1
|
|
1036
|
+
})
|
|
1037
|
+
});
|
|
1038
|
+
const data = await response.json();
|
|
1039
|
+
const chainId = parseInt(data.result, 16);
|
|
1040
|
+
if (chainId !== 42431) {
|
|
1041
|
+
return { healthy: false, error: `Wrong chainId: ${chainId}` };
|
|
1042
|
+
}
|
|
1043
|
+
return { healthy: true, latencyMs: Date.now() - start };
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
return { healthy: false, error: String(error) };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async verify(paymentPayload, requirements) {
|
|
1049
|
+
try {
|
|
1050
|
+
const tempoPayload = paymentPayload.payload;
|
|
1051
|
+
if (!tempoPayload?.txHash) {
|
|
1052
|
+
return { valid: false, error: "Missing txHash in payment payload" };
|
|
1053
|
+
}
|
|
1054
|
+
const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
|
|
1055
|
+
if (!receipt) {
|
|
1056
|
+
return { valid: false, error: "Transaction not found" };
|
|
1057
|
+
}
|
|
1058
|
+
if (receipt.status !== "0x1") {
|
|
1059
|
+
return { valid: false, error: "Transaction failed" };
|
|
1060
|
+
}
|
|
1061
|
+
const transferLog = receipt.logs.find(
|
|
1062
|
+
(log) => log.topics[0] === TRANSFER_EVENT_TOPIC
|
|
1063
|
+
);
|
|
1064
|
+
if (!transferLog) {
|
|
1065
|
+
return { valid: false, error: "No Transfer event found" };
|
|
1066
|
+
}
|
|
1067
|
+
const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
|
|
1068
|
+
const expectedTo = requirements.payTo.toLowerCase();
|
|
1069
|
+
if (toAddress !== expectedTo) {
|
|
1070
|
+
return {
|
|
1071
|
+
valid: false,
|
|
1072
|
+
error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
const amount = BigInt(transferLog.data);
|
|
1076
|
+
const expectedAmount = BigInt(requirements.amount);
|
|
1077
|
+
if (amount < expectedAmount) {
|
|
1078
|
+
return {
|
|
1079
|
+
valid: false,
|
|
1080
|
+
error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
const tokenAddress = transferLog.address.toLowerCase();
|
|
1084
|
+
const expectedToken = requirements.asset.toLowerCase();
|
|
1085
|
+
if (tokenAddress !== expectedToken) {
|
|
1086
|
+
return {
|
|
1087
|
+
valid: false,
|
|
1088
|
+
error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
valid: true,
|
|
1093
|
+
details: {
|
|
1094
|
+
txHash: tempoPayload.txHash,
|
|
1095
|
+
from: "0x" + transferLog.topics[1].slice(26),
|
|
1096
|
+
to: toAddress,
|
|
1097
|
+
amount: amount.toString(),
|
|
1098
|
+
token: tokenAddress
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
return { valid: false, error: `Verification failed: ${error}` };
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
async settle(paymentPayload, requirements) {
|
|
1106
|
+
const verifyResult = await this.verify(paymentPayload, requirements);
|
|
1107
|
+
if (!verifyResult.valid) {
|
|
1108
|
+
return { success: false, error: verifyResult.error };
|
|
1109
|
+
}
|
|
1110
|
+
const tempoPayload = paymentPayload.payload;
|
|
1111
|
+
return {
|
|
1112
|
+
success: true,
|
|
1113
|
+
transaction: tempoPayload.txHash,
|
|
1114
|
+
status: "settled"
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
async getTransactionReceipt(txHash) {
|
|
1118
|
+
const response = await fetch(this.rpcUrl, {
|
|
1119
|
+
method: "POST",
|
|
1120
|
+
headers: { "Content-Type": "application/json" },
|
|
1121
|
+
body: JSON.stringify({
|
|
1122
|
+
jsonrpc: "2.0",
|
|
1123
|
+
method: "eth_getTransactionReceipt",
|
|
1124
|
+
params: [txHash],
|
|
1125
|
+
id: 1
|
|
1126
|
+
})
|
|
1127
|
+
});
|
|
1128
|
+
const data = await response.json();
|
|
1129
|
+
return data.result;
|
|
855
1130
|
}
|
|
856
1131
|
};
|
|
857
1132
|
|
|
@@ -864,7 +1139,8 @@ var FacilitatorRegistry = class {
|
|
|
864
1139
|
roundRobinIndex = 0;
|
|
865
1140
|
constructor(selection) {
|
|
866
1141
|
this.registerFactory("cdp", (config) => new CDPFacilitator(config));
|
|
867
|
-
this.
|
|
1142
|
+
this.registerFactory("tempo", () => new TempoFacilitator());
|
|
1143
|
+
this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
|
|
868
1144
|
}
|
|
869
1145
|
/**
|
|
870
1146
|
* Register a new facilitator factory
|
|
@@ -1088,6 +1364,9 @@ var X402_VERSION3 = 2;
|
|
|
1088
1364
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
1089
1365
|
var PAYMENT_HEADER2 = "x-payment";
|
|
1090
1366
|
var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
1367
|
+
var MPP_AUTH_HEADER = "authorization";
|
|
1368
|
+
var MPP_WWW_AUTH_HEADER = "www-authenticate";
|
|
1369
|
+
var MPP_RECEIPT_HEADER = "payment-receipt";
|
|
1091
1370
|
var TOKEN_ADDRESSES = {
|
|
1092
1371
|
"eip155:8453": {
|
|
1093
1372
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -1101,17 +1380,48 @@ var TOKEN_ADDRESSES = {
|
|
|
1101
1380
|
"eip155:137": {
|
|
1102
1381
|
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
1103
1382
|
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
|
|
1383
|
+
},
|
|
1384
|
+
"eip155:42431": {
|
|
1385
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1386
|
+
USDC: "0x20c0000000000000000000000000000000000000",
|
|
1387
|
+
// pathUSD
|
|
1388
|
+
USDT: "0x20c0000000000000000000000000000000000001"
|
|
1389
|
+
// alphaUSD
|
|
1104
1390
|
}
|
|
1105
1391
|
};
|
|
1106
1392
|
var CHAIN_TO_NETWORK = {
|
|
1107
1393
|
"base": "eip155:8453",
|
|
1108
1394
|
"base_sepolia": "eip155:84532",
|
|
1109
|
-
"polygon": "eip155:137"
|
|
1395
|
+
"polygon": "eip155:137",
|
|
1396
|
+
"tempo_moderato": "eip155:42431"
|
|
1110
1397
|
};
|
|
1111
1398
|
var TOKEN_DOMAINS = {
|
|
1112
|
-
|
|
1113
|
-
|
|
1399
|
+
// Base mainnet
|
|
1400
|
+
"eip155:8453": {
|
|
1401
|
+
USDC: { name: "USD Coin", version: "2" },
|
|
1402
|
+
USDT: { name: "Tether USD", version: "2" }
|
|
1403
|
+
},
|
|
1404
|
+
// Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
|
|
1405
|
+
"eip155:84532": {
|
|
1406
|
+
USDC: { name: "USDC", version: "2" },
|
|
1407
|
+
USDT: { name: "USDC", version: "2" }
|
|
1408
|
+
// Same contract as USDC on testnet
|
|
1409
|
+
},
|
|
1410
|
+
// Polygon mainnet
|
|
1411
|
+
"eip155:137": {
|
|
1412
|
+
USDC: { name: "USD Coin", version: "2" },
|
|
1413
|
+
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1414
|
+
},
|
|
1415
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1416
|
+
"eip155:42431": {
|
|
1417
|
+
USDC: { name: "pathUSD", version: "1" },
|
|
1418
|
+
USDT: { name: "alphaUSD", version: "1" }
|
|
1419
|
+
}
|
|
1114
1420
|
};
|
|
1421
|
+
function getTokenDomain(network, token) {
|
|
1422
|
+
const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
|
|
1423
|
+
return networkDomains[token] || { name: "USD Coin", version: "2" };
|
|
1424
|
+
}
|
|
1115
1425
|
function getAcceptedCurrencies(config) {
|
|
1116
1426
|
return config.acceptedCurrencies ?? [config.currency];
|
|
1117
1427
|
}
|
|
@@ -1163,9 +1473,11 @@ var MoltsPayServer = class {
|
|
|
1163
1473
|
};
|
|
1164
1474
|
this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
1165
1475
|
this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
|
|
1476
|
+
const defaultFallback = ["tempo"];
|
|
1477
|
+
const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
|
|
1166
1478
|
const facilitatorConfig = options.facilitators || {
|
|
1167
1479
|
primary: process.env.FACILITATOR_PRIMARY || "cdp",
|
|
1168
|
-
fallback:
|
|
1480
|
+
fallback: envFallback || defaultFallback,
|
|
1169
1481
|
strategy: process.env.FACILITATOR_STRATEGY || "failover",
|
|
1170
1482
|
config: {
|
|
1171
1483
|
cdp: { useMainnet: this.useMainnet }
|
|
@@ -1205,11 +1517,14 @@ var MoltsPayServer = class {
|
|
|
1205
1517
|
getProviderChains() {
|
|
1206
1518
|
const provider = this.manifest.provider;
|
|
1207
1519
|
if (provider.chains && provider.chains.length > 0) {
|
|
1208
|
-
return provider.chains.map((c) =>
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1520
|
+
return provider.chains.map((c) => {
|
|
1521
|
+
const chainName = typeof c === "string" ? c : c.chain;
|
|
1522
|
+
return {
|
|
1523
|
+
network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
|
|
1524
|
+
wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
|
|
1525
|
+
tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
|
|
1526
|
+
};
|
|
1527
|
+
});
|
|
1213
1528
|
}
|
|
1214
1529
|
const chain = provider.chain || "base";
|
|
1215
1530
|
const network = CHAIN_TO_NETWORK[chain] || this.networkId;
|
|
@@ -1256,8 +1571,8 @@ var MoltsPayServer = class {
|
|
|
1256
1571
|
async handleRequest(req, res) {
|
|
1257
1572
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1258
1573
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1259
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
1260
|
-
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
1574
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1575
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
|
|
1261
1576
|
if (req.method === "OPTIONS") {
|
|
1262
1577
|
res.writeHead(204);
|
|
1263
1578
|
res.end();
|
|
@@ -1288,6 +1603,14 @@ var MoltsPayServer = class {
|
|
|
1288
1603
|
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1289
1604
|
return await this.handleProxy(body, paymentHeader, res);
|
|
1290
1605
|
}
|
|
1606
|
+
const servicePath = url.pathname.replace(/^\//, "");
|
|
1607
|
+
const skill = this.skills.get(servicePath);
|
|
1608
|
+
if (skill && (req.method === "POST" || req.method === "GET")) {
|
|
1609
|
+
const body = req.method === "POST" ? await this.readBody(req) : {};
|
|
1610
|
+
const authHeader = req.headers[MPP_AUTH_HEADER];
|
|
1611
|
+
const x402Header = req.headers[PAYMENT_HEADER2];
|
|
1612
|
+
return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
|
|
1613
|
+
}
|
|
1291
1614
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1292
1615
|
} catch (err) {
|
|
1293
1616
|
console.error("[MoltsPay] Error:", err);
|
|
@@ -1419,7 +1742,7 @@ var MoltsPayServer = class {
|
|
|
1419
1742
|
const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
|
|
1420
1743
|
const paymentWallet = this.getWalletForNetwork(paymentNetwork);
|
|
1421
1744
|
const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
|
|
1422
|
-
console.log(`[MoltsPay] Verifying payment...`);
|
|
1745
|
+
console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
|
|
1423
1746
|
const verifyResult = await this.registry.verify(payment, requirements);
|
|
1424
1747
|
if (!verifyResult.valid) {
|
|
1425
1748
|
return this.sendJson(res, 402, {
|
|
@@ -1471,6 +1794,187 @@ var MoltsPayServer = class {
|
|
|
1471
1794
|
payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
|
|
1472
1795
|
}, responseHeaders);
|
|
1473
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Handle MPP (Machine Payments Protocol) request
|
|
1799
|
+
* Supports both x402 and MPP protocols on service endpoints
|
|
1800
|
+
*/
|
|
1801
|
+
async handleMPPRequest(skill, body, authHeader, x402Header, res) {
|
|
1802
|
+
const config = skill.config;
|
|
1803
|
+
const params = body || {};
|
|
1804
|
+
if (x402Header) {
|
|
1805
|
+
return await this.handleExecute({ service: config.id, params }, x402Header, res);
|
|
1806
|
+
}
|
|
1807
|
+
if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
|
|
1808
|
+
return await this.handleMPPPayment(skill, params, authHeader, res);
|
|
1809
|
+
}
|
|
1810
|
+
return this.sendMPPPaymentRequired(config, res);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Handle MPP payment verification and service execution
|
|
1814
|
+
*/
|
|
1815
|
+
async handleMPPPayment(skill, params, authHeader, res) {
|
|
1816
|
+
const config = skill.config;
|
|
1817
|
+
const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
|
|
1818
|
+
if (!credentialMatch) {
|
|
1819
|
+
return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
|
|
1820
|
+
}
|
|
1821
|
+
let mppCredential;
|
|
1822
|
+
try {
|
|
1823
|
+
const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1824
|
+
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
|
1825
|
+
mppCredential = JSON.parse(decoded);
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
console.error("[MoltsPay] Failed to parse MPP credential:", err);
|
|
1828
|
+
return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
|
|
1829
|
+
}
|
|
1830
|
+
let txHash;
|
|
1831
|
+
if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
|
|
1832
|
+
txHash = mppCredential.payload.hash;
|
|
1833
|
+
} else if (mppCredential.payload?.type === "transaction") {
|
|
1834
|
+
return this.sendJson(res, 400, {
|
|
1835
|
+
error: "Transaction type not supported. Please use push mode (hash type)."
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
if (!txHash) {
|
|
1839
|
+
return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
|
|
1840
|
+
}
|
|
1841
|
+
let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
|
|
1842
|
+
if (!chainId && mppCredential.source) {
|
|
1843
|
+
const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
|
|
1844
|
+
if (chainMatch) chainId = parseInt(chainMatch[1], 10);
|
|
1845
|
+
}
|
|
1846
|
+
chainId = chainId || 42431;
|
|
1847
|
+
const network = `eip155:${chainId}`;
|
|
1848
|
+
if (!this.isNetworkAccepted(network)) {
|
|
1849
|
+
return this.sendJson(res, 402, {
|
|
1850
|
+
error: `Network not accepted: ${network}`
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
const requirements = this.buildPaymentRequirements(
|
|
1854
|
+
config,
|
|
1855
|
+
network,
|
|
1856
|
+
this.getWalletForNetwork(network),
|
|
1857
|
+
"USDC"
|
|
1858
|
+
);
|
|
1859
|
+
const paymentPayload = {
|
|
1860
|
+
x402Version: X402_VERSION3,
|
|
1861
|
+
scheme: "exact",
|
|
1862
|
+
network,
|
|
1863
|
+
payload: {
|
|
1864
|
+
txHash,
|
|
1865
|
+
chainId
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
|
|
1869
|
+
const verification = await this.registry.verify(paymentPayload, requirements);
|
|
1870
|
+
if (!verification.valid) {
|
|
1871
|
+
return this.sendJson(res, 402, {
|
|
1872
|
+
error: `Payment verification failed: ${verification.error}`
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
|
|
1876
|
+
let result;
|
|
1877
|
+
try {
|
|
1878
|
+
result = await skill.handler(params);
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
console.error(`[MoltsPay] Skill execution error:`, err);
|
|
1881
|
+
return this.sendJson(res, 500, {
|
|
1882
|
+
error: `Service execution failed: ${err.message}`
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
const receipt = {
|
|
1886
|
+
success: true,
|
|
1887
|
+
txHash,
|
|
1888
|
+
network,
|
|
1889
|
+
facilitator: verification.facilitator
|
|
1890
|
+
};
|
|
1891
|
+
const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
|
|
1892
|
+
res.writeHead(200, {
|
|
1893
|
+
"Content-Type": "application/json",
|
|
1894
|
+
[MPP_RECEIPT_HEADER]: receiptEncoded
|
|
1895
|
+
});
|
|
1896
|
+
res.end(JSON.stringify({
|
|
1897
|
+
success: true,
|
|
1898
|
+
result,
|
|
1899
|
+
payment: {
|
|
1900
|
+
txHash,
|
|
1901
|
+
status: "verified",
|
|
1902
|
+
facilitator: verification.facilitator
|
|
1903
|
+
}
|
|
1904
|
+
}, null, 2));
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Return 402 with both x402 and MPP payment requirements
|
|
1908
|
+
*/
|
|
1909
|
+
sendMPPPaymentRequired(config, res) {
|
|
1910
|
+
const acceptedTokens = getAcceptedCurrencies(config);
|
|
1911
|
+
const providerChains = this.getProviderChains();
|
|
1912
|
+
const accepts = [];
|
|
1913
|
+
for (const chainConfig of providerChains) {
|
|
1914
|
+
for (const token of acceptedTokens) {
|
|
1915
|
+
if (chainConfig.tokens.includes(token)) {
|
|
1916
|
+
accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const x402PaymentRequired = {
|
|
1921
|
+
x402Version: X402_VERSION3,
|
|
1922
|
+
accepts,
|
|
1923
|
+
acceptedCurrencies: acceptedTokens,
|
|
1924
|
+
resource: {
|
|
1925
|
+
url: `/${config.id}`,
|
|
1926
|
+
description: `${config.name} - $${config.price} ${config.currency}`
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
|
|
1930
|
+
const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
|
|
1931
|
+
let mppWwwAuth = "";
|
|
1932
|
+
if (tempoChain) {
|
|
1933
|
+
const challengeId = this.generateChallengeId();
|
|
1934
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
1935
|
+
const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
|
|
1936
|
+
const mppRequest = {
|
|
1937
|
+
amount: amountInUnits,
|
|
1938
|
+
currency: tokenAddress,
|
|
1939
|
+
methodDetails: {
|
|
1940
|
+
chainId: 42431,
|
|
1941
|
+
feePayer: true
|
|
1942
|
+
},
|
|
1943
|
+
recipient: tempoChain.wallet
|
|
1944
|
+
};
|
|
1945
|
+
const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
|
|
1946
|
+
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
1947
|
+
mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
|
|
1948
|
+
}
|
|
1949
|
+
const headers = {
|
|
1950
|
+
"Content-Type": "application/problem+json",
|
|
1951
|
+
[PAYMENT_REQUIRED_HEADER2]: x402Encoded
|
|
1952
|
+
};
|
|
1953
|
+
if (mppWwwAuth) {
|
|
1954
|
+
headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
|
|
1955
|
+
}
|
|
1956
|
+
res.writeHead(402, headers);
|
|
1957
|
+
res.end(JSON.stringify({
|
|
1958
|
+
type: "https://paymentauth.org/problems/payment-required",
|
|
1959
|
+
title: "Payment Required",
|
|
1960
|
+
status: 402,
|
|
1961
|
+
detail: `Payment is required (${config.name}).`,
|
|
1962
|
+
service: config.id,
|
|
1963
|
+
price: config.price,
|
|
1964
|
+
currency: config.currency,
|
|
1965
|
+
acceptedCurrencies: acceptedTokens
|
|
1966
|
+
}, null, 2));
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Generate a unique challenge ID for MPP
|
|
1970
|
+
*/
|
|
1971
|
+
generateChallengeId() {
|
|
1972
|
+
const bytes = new Uint8Array(24);
|
|
1973
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1974
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1975
|
+
}
|
|
1976
|
+
return Buffer.from(bytes).toString("base64url");
|
|
1977
|
+
}
|
|
1474
1978
|
/**
|
|
1475
1979
|
* Return 402 with x402 payment requirements (v2 format)
|
|
1476
1980
|
* Includes requirements for all chains and all accepted currencies
|
|
@@ -1545,7 +2049,7 @@ var MoltsPayServer = class {
|
|
|
1545
2049
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
1546
2050
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
1547
2051
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
1548
|
-
const tokenDomain =
|
|
2052
|
+
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
1549
2053
|
return {
|
|
1550
2054
|
scheme: "exact",
|
|
1551
2055
|
network: selectedNetwork,
|
|
@@ -1790,7 +2294,7 @@ var MoltsPayServer = class {
|
|
|
1790
2294
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
1791
2295
|
const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
|
|
1792
2296
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
1793
|
-
const tokenDomain =
|
|
2297
|
+
const tokenDomain = getTokenDomain(networkId, selectedToken);
|
|
1794
2298
|
return {
|
|
1795
2299
|
scheme: "exact",
|
|
1796
2300
|
network: networkId,
|
|
@@ -1845,6 +2349,26 @@ async function printQRCode(url) {
|
|
|
1845
2349
|
|
|
1846
2350
|
// src/cli/index.ts
|
|
1847
2351
|
var readline = __toESM(require("readline"));
|
|
2352
|
+
if (!globalThis.crypto) {
|
|
2353
|
+
globalThis.crypto = import_crypto.webcrypto;
|
|
2354
|
+
}
|
|
2355
|
+
function getVersion() {
|
|
2356
|
+
const locations = [
|
|
2357
|
+
(0, import_path2.join)(__dirname, "../../package.json"),
|
|
2358
|
+
(0, import_path2.join)(__dirname, "../package.json"),
|
|
2359
|
+
(0, import_path2.join)(process.cwd(), "node_modules/moltspay/package.json")
|
|
2360
|
+
];
|
|
2361
|
+
for (const loc of locations) {
|
|
2362
|
+
try {
|
|
2363
|
+
if ((0, import_fs4.existsSync)(loc)) {
|
|
2364
|
+
const pkg = JSON.parse((0, import_fs4.readFileSync)(loc, "utf-8"));
|
|
2365
|
+
if (pkg.name === "moltspay") return pkg.version;
|
|
2366
|
+
}
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return "0.0.0";
|
|
2371
|
+
}
|
|
1848
2372
|
var program = new import_commander.Command();
|
|
1849
2373
|
var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
|
|
1850
2374
|
var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
|
|
@@ -1863,7 +2387,7 @@ function prompt(question) {
|
|
|
1863
2387
|
});
|
|
1864
2388
|
});
|
|
1865
2389
|
}
|
|
1866
|
-
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(
|
|
2390
|
+
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
|
|
1867
2391
|
program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
1868
2392
|
console.log("\n\u{1F510} MoltsPay Client Setup\n");
|
|
1869
2393
|
if ((0, import_fs4.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
|
|
@@ -1872,6 +2396,11 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
|
|
|
1872
2396
|
return;
|
|
1873
2397
|
}
|
|
1874
2398
|
let chain = options.chain;
|
|
2399
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
2400
|
+
if (!supportedChains.includes(chain)) {
|
|
2401
|
+
console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
|
|
2402
|
+
process.exit(1);
|
|
2403
|
+
}
|
|
1875
2404
|
let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
|
|
1876
2405
|
let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
|
|
1877
2406
|
if (!maxPerTx) {
|
|
@@ -1934,7 +2463,7 @@ program.command("config").description("Update MoltsPay settings").option("--max-
|
|
|
1934
2463
|
}
|
|
1935
2464
|
}
|
|
1936
2465
|
});
|
|
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
|
|
2466
|
+
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
2467
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
1939
2468
|
if (!client.isInitialized) {
|
|
1940
2469
|
console.log("\u274C Not initialized. Run: npx moltspay init");
|
|
@@ -1946,8 +2475,18 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1946
2475
|
return;
|
|
1947
2476
|
}
|
|
1948
2477
|
const chain = options.chain?.toLowerCase() || "base";
|
|
1949
|
-
if (!["base", "polygon"].includes(chain)) {
|
|
1950
|
-
console.log("\u274C Invalid chain. Use: base or
|
|
2478
|
+
if (!["base", "polygon", "base_sepolia"].includes(chain)) {
|
|
2479
|
+
console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
|
|
2480
|
+
return;
|
|
2481
|
+
}
|
|
2482
|
+
if (chain === "base_sepolia") {
|
|
2483
|
+
console.log("\n\u{1F9EA} Testnet Funding\n");
|
|
2484
|
+
console.log(` Wallet: ${client.address}`);
|
|
2485
|
+
console.log(` Chain: Base Sepolia (testnet)
|
|
2486
|
+
`);
|
|
2487
|
+
console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
|
|
2488
|
+
console.log(" npx moltspay faucet\n");
|
|
2489
|
+
console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
|
|
1951
2490
|
return;
|
|
1952
2491
|
}
|
|
1953
2492
|
console.log("\n\u{1F4B3} Fund your agent wallet\n");
|
|
@@ -1979,6 +2518,90 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1979
2518
|
console.log(`\u274C ${error.message}`);
|
|
1980
2519
|
}
|
|
1981
2520
|
});
|
|
2521
|
+
program.command("faucet").description("Request testnet tokens from faucet (Base Sepolia or Tempo Moderato)").option("--chain <chain>", "Chain to get tokens on (base_sepolia or tempo_moderato)", "base_sepolia").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
2522
|
+
let address = options.address;
|
|
2523
|
+
const chain = options.chain?.toLowerCase() || "base_sepolia";
|
|
2524
|
+
if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
2525
|
+
console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2528
|
+
if (!address) {
|
|
2529
|
+
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2530
|
+
if (client.isInitialized) {
|
|
2531
|
+
address = client.address;
|
|
2532
|
+
} else {
|
|
2533
|
+
console.log('\u274C No wallet found. Either run "npx moltspay init" or provide --address');
|
|
2534
|
+
return;
|
|
2535
|
+
}
|
|
2536
|
+
}
|
|
2537
|
+
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
2538
|
+
console.log("\u274C Invalid Ethereum address");
|
|
2539
|
+
return;
|
|
2540
|
+
}
|
|
2541
|
+
console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
|
|
2542
|
+
if (chain === "tempo_moderato") {
|
|
2543
|
+
console.log(` Requesting testnet tokens on Tempo Moderato...`);
|
|
2544
|
+
console.log(` Address: ${address}
|
|
2545
|
+
`);
|
|
2546
|
+
try {
|
|
2547
|
+
const TEMPO_FAUCET_API = "https://docs.tempo.xyz/api/faucet";
|
|
2548
|
+
const response = await fetch(TEMPO_FAUCET_API, {
|
|
2549
|
+
method: "POST",
|
|
2550
|
+
headers: { "Content-Type": "application/json" },
|
|
2551
|
+
body: JSON.stringify({ address })
|
|
2552
|
+
});
|
|
2553
|
+
const result = await response.json();
|
|
2554
|
+
if (response.ok && result.data && result.data.length > 0) {
|
|
2555
|
+
console.log(`\u2705 Received testnet tokens!
|
|
2556
|
+
`);
|
|
2557
|
+
console.log(` Tokens: pathUSD, AlphaUSD, BetaUSD, ThetaUSD (1M each)`);
|
|
2558
|
+
console.log(` Transactions:`);
|
|
2559
|
+
for (const tx of result.data) {
|
|
2560
|
+
console.log(` https://explore.testnet.tempo.xyz/tx/${tx.hash}`);
|
|
2561
|
+
}
|
|
2562
|
+
console.log("\n\u{1F4A1} Use these tokens to test MPP payments:");
|
|
2563
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain tempo_moderato
|
|
2564
|
+
`);
|
|
2565
|
+
} else {
|
|
2566
|
+
console.log(`\u274C ${result.error || "Faucet request failed"}`);
|
|
2567
|
+
console.log("\n Try again later or use Tempo Wallet: https://wallet.tempo.xyz\n");
|
|
2568
|
+
}
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
console.log(`\u274C ${error.message}`);
|
|
2571
|
+
console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
|
|
2572
|
+
}
|
|
2573
|
+
} else {
|
|
2574
|
+
console.log(` Requesting 1 USDC on Base Sepolia...`);
|
|
2575
|
+
console.log(` Address: ${address}
|
|
2576
|
+
`);
|
|
2577
|
+
try {
|
|
2578
|
+
const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
|
|
2579
|
+
const response = await fetch(FAUCET_API, {
|
|
2580
|
+
method: "POST",
|
|
2581
|
+
headers: { "Content-Type": "application/json" },
|
|
2582
|
+
body: JSON.stringify({ address })
|
|
2583
|
+
});
|
|
2584
|
+
const result = await response.json();
|
|
2585
|
+
if (!response.ok) {
|
|
2586
|
+
console.log(`\u274C ${result.error || "Request failed"}`);
|
|
2587
|
+
if (result.hint) console.log(` ${result.hint}`);
|
|
2588
|
+
if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
console.log(`\u2705 Received ${result.amount} USDC!
|
|
2592
|
+
`);
|
|
2593
|
+
console.log(` Transaction: ${result.transaction}`);
|
|
2594
|
+
console.log(` Explorer: ${result.explorer}`);
|
|
2595
|
+
console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
|
|
2596
|
+
`);
|
|
2597
|
+
console.log("\u{1F4A1} Use this USDC to test x402 payments:");
|
|
2598
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
|
|
2599
|
+
`);
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
console.log(`\u274C ${error.message}`);
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
1982
2605
|
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
2606
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
1984
2607
|
if (!client.isInitialized) {
|
|
@@ -2008,8 +2631,26 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
2008
2631
|
console.log("");
|
|
2009
2632
|
console.log(" Balances:");
|
|
2010
2633
|
for (const [chainName, balance] of Object.entries(allBalances)) {
|
|
2011
|
-
|
|
2012
|
-
|
|
2634
|
+
let chainLabel;
|
|
2635
|
+
if (chainName === "base_sepolia") {
|
|
2636
|
+
chainLabel = "Base Sepolia";
|
|
2637
|
+
} else if (chainName === "tempo_moderato") {
|
|
2638
|
+
chainLabel = "Tempo Moderato";
|
|
2639
|
+
} else {
|
|
2640
|
+
chainLabel = chainName.charAt(0).toUpperCase() + chainName.slice(1);
|
|
2641
|
+
}
|
|
2642
|
+
if (chainName === "tempo_moderato" && balance.tempo) {
|
|
2643
|
+
const tempo = balance.tempo;
|
|
2644
|
+
const nativeStr = balance.native > 1e12 ? balance.native.toExponential(2) : balance.native.toFixed(2);
|
|
2645
|
+
console.log(` ${chainLabel}:`);
|
|
2646
|
+
console.log(` Native: ${nativeStr} TEMPO (for gas)`);
|
|
2647
|
+
console.log(` pathUSD: ${tempo.pathUSD.toFixed(2)}`);
|
|
2648
|
+
console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
|
|
2649
|
+
console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
|
|
2650
|
+
console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
|
|
2651
|
+
} else {
|
|
2652
|
+
console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
|
|
2653
|
+
}
|
|
2013
2654
|
}
|
|
2014
2655
|
console.log("");
|
|
2015
2656
|
console.log(" Spending Limits:");
|
|
@@ -2018,7 +2659,7 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
2018
2659
|
console.log("");
|
|
2019
2660
|
}
|
|
2020
2661
|
});
|
|
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) => {
|
|
2662
|
+
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
2663
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2023
2664
|
if (!client.isInitialized) {
|
|
2024
2665
|
console.log("\u274C Not initialized. Run: npx moltspay init");
|
|
@@ -2027,8 +2668,8 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2027
2668
|
const days = parseInt(options.days) || 7;
|
|
2028
2669
|
const limit = parseInt(options.limit) || 20;
|
|
2029
2670
|
const chain = options.chain?.toLowerCase() || "all";
|
|
2030
|
-
if (!["base", "polygon", "all"].includes(chain)) {
|
|
2031
|
-
console.log("\u274C Invalid chain. Use: base, polygon, or all");
|
|
2671
|
+
if (!["base", "polygon", "base_sepolia", "tempo_moderato", "all"].includes(chain)) {
|
|
2672
|
+
console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, tempo_moderato, or all");
|
|
2032
2673
|
return;
|
|
2033
2674
|
}
|
|
2034
2675
|
const wallet = client.address;
|
|
@@ -2043,9 +2684,21 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2043
2684
|
api: "https://polygon.blockscout.com/api/v2",
|
|
2044
2685
|
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
2045
2686
|
name: "Polygon"
|
|
2687
|
+
},
|
|
2688
|
+
base_sepolia: {
|
|
2689
|
+
api: "https://base-sepolia.blockscout.com/api/v2",
|
|
2690
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
2691
|
+
name: "Base Sepolia"
|
|
2692
|
+
},
|
|
2693
|
+
// Tempo explorer doesn't have public API yet
|
|
2694
|
+
tempo_moderato: {
|
|
2695
|
+
api: "",
|
|
2696
|
+
// No API available
|
|
2697
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
2698
|
+
name: "Tempo Moderato"
|
|
2046
2699
|
}
|
|
2047
2700
|
};
|
|
2048
|
-
const chainsToQuery = chain === "all" ? ["base", "polygon"] : [chain];
|
|
2701
|
+
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia", "tempo_moderato"] : [chain];
|
|
2049
2702
|
console.log(`
|
|
2050
2703
|
\u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
|
|
2051
2704
|
`);
|
|
@@ -2053,27 +2706,136 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2053
2706
|
for (const c of chainsToQuery) {
|
|
2054
2707
|
const explorer = explorers[c];
|
|
2055
2708
|
try {
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2709
|
+
if (c === "tempo_moderato") {
|
|
2710
|
+
const tempoTokens = [
|
|
2711
|
+
{ address: "0x20c0000000000000000000000000000000000000", name: "pathUSD" },
|
|
2712
|
+
{ address: "0x20c0000000000000000000000000000000000001", name: "alphaUSD" },
|
|
2713
|
+
{ address: "0x20c0000000000000000000000000000000000002", name: "betaUSD" },
|
|
2714
|
+
{ address: "0x20c0000000000000000000000000000000000003", name: "thetaUSD" }
|
|
2715
|
+
];
|
|
2716
|
+
const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
2717
|
+
const walletTopic = "0x000000000000000000000000" + wallet.toLowerCase().slice(2);
|
|
2718
|
+
let latestBlock = 0;
|
|
2719
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2720
|
+
try {
|
|
2721
|
+
const blockRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2722
|
+
method: "POST",
|
|
2723
|
+
headers: { "Content-Type": "application/json" },
|
|
2724
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 })
|
|
2725
|
+
});
|
|
2726
|
+
const blockData = await blockRes.json();
|
|
2727
|
+
if (blockData.result) {
|
|
2728
|
+
latestBlock = parseInt(blockData.result, 16);
|
|
2729
|
+
break;
|
|
2730
|
+
}
|
|
2731
|
+
} catch (e) {
|
|
2732
|
+
if (attempt === 2) throw e;
|
|
2733
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (latestBlock === 0) {
|
|
2737
|
+
console.log(" \u26A0\uFE0F Tempo Moderato: Could not get latest block");
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
const maxBlocks = 1e5;
|
|
2741
|
+
const blocksPerDay = 172800;
|
|
2742
|
+
const requestedBlocks = blocksPerDay * days;
|
|
2743
|
+
const actualBlocks = Math.min(requestedBlocks, maxBlocks);
|
|
2744
|
+
const fromBlock = "0x" + Math.max(0, latestBlock - actualBlocks).toString(16);
|
|
2745
|
+
const toBlock = "0x" + latestBlock.toString(16);
|
|
2746
|
+
if (requestedBlocks > maxBlocks) {
|
|
2747
|
+
console.log(` \u2139\uFE0F Tempo: querying last ~14 hours (RPC limit: 100k blocks)`);
|
|
2748
|
+
}
|
|
2749
|
+
for (const token of tempoTokens) {
|
|
2750
|
+
try {
|
|
2751
|
+
const inRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2752
|
+
method: "POST",
|
|
2753
|
+
headers: { "Content-Type": "application/json" },
|
|
2754
|
+
body: JSON.stringify({
|
|
2755
|
+
jsonrpc: "2.0",
|
|
2756
|
+
method: "eth_getLogs",
|
|
2757
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, null, walletTopic] }],
|
|
2758
|
+
id: 1
|
|
2759
|
+
})
|
|
2760
|
+
});
|
|
2761
|
+
const inData = await inRes.json();
|
|
2762
|
+
if (inData.error) {
|
|
2763
|
+
console.log(` \u26A0\uFE0F ${token.name}: ${inData.error.message}`);
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
if (inData.result && Array.isArray(inData.result)) {
|
|
2767
|
+
for (const log of inData.result) {
|
|
2768
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2769
|
+
if (timestamp < cutoffTime) continue;
|
|
2770
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2771
|
+
const from = "0x" + log.topics[1].slice(26);
|
|
2772
|
+
allTxns.push({
|
|
2773
|
+
chain: c,
|
|
2774
|
+
timestamp,
|
|
2775
|
+
type: "IN",
|
|
2776
|
+
amount,
|
|
2777
|
+
other: from,
|
|
2778
|
+
hash: log.transactionHash,
|
|
2779
|
+
token: token.name
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
const outRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2784
|
+
method: "POST",
|
|
2785
|
+
headers: { "Content-Type": "application/json" },
|
|
2786
|
+
body: JSON.stringify({
|
|
2787
|
+
jsonrpc: "2.0",
|
|
2788
|
+
method: "eth_getLogs",
|
|
2789
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, walletTopic, null] }],
|
|
2790
|
+
id: 1
|
|
2791
|
+
})
|
|
2792
|
+
});
|
|
2793
|
+
const outData = await outRes.json();
|
|
2794
|
+
if (outData.result && Array.isArray(outData.result)) {
|
|
2795
|
+
for (const log of outData.result) {
|
|
2796
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2797
|
+
if (timestamp < cutoffTime) continue;
|
|
2798
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2799
|
+
const to = "0x" + log.topics[2].slice(26);
|
|
2800
|
+
allTxns.push({
|
|
2801
|
+
chain: c,
|
|
2802
|
+
timestamp,
|
|
2803
|
+
type: "OUT",
|
|
2804
|
+
amount,
|
|
2805
|
+
other: to,
|
|
2806
|
+
hash: log.transactionHash,
|
|
2807
|
+
token: token.name
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
} catch (tokenError) {
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
} else {
|
|
2816
|
+
const url = `${explorer.api}/addresses/${wallet}/token-transfers?type=ERC-20&token=${explorer.usdc}`;
|
|
2817
|
+
const response = await fetch(url);
|
|
2818
|
+
const data = await response.json();
|
|
2819
|
+
if (data.items && Array.isArray(data.items)) {
|
|
2820
|
+
for (const tx of data.items) {
|
|
2821
|
+
const timestamp = new Date(tx.timestamp).getTime();
|
|
2822
|
+
if (timestamp < cutoffTime) continue;
|
|
2823
|
+
const isIncoming = tx.to.hash.toLowerCase() === wallet.toLowerCase();
|
|
2824
|
+
const decimals = parseInt(tx.total.decimals) || 6;
|
|
2825
|
+
allTxns.push({
|
|
2826
|
+
chain: c,
|
|
2827
|
+
timestamp,
|
|
2828
|
+
type: isIncoming ? "IN" : "OUT",
|
|
2829
|
+
amount: parseInt(tx.total.value) / Math.pow(10, decimals),
|
|
2830
|
+
other: isIncoming ? tx.from.hash : tx.to.hash,
|
|
2831
|
+
hash: tx.transaction_hash
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2073
2834
|
}
|
|
2074
2835
|
}
|
|
2075
2836
|
} catch (error) {
|
|
2076
|
-
|
|
2837
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2838
|
+
console.log(` \u26A0\uFE0F ${explorer.name}: ${errMsg}`);
|
|
2077
2839
|
}
|
|
2078
2840
|
}
|
|
2079
2841
|
allTxns.sort((a, b) => b.timestamp - a.timestamp);
|
|
@@ -2086,8 +2848,12 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2086
2848
|
const color = tx.type === "IN" ? "\x1B[32m" : "\x1B[31m";
|
|
2087
2849
|
const reset = "\x1B[0m";
|
|
2088
2850
|
const date = new Date(tx.timestamp).toISOString().slice(5, 16).replace("T", " ");
|
|
2089
|
-
|
|
2090
|
-
|
|
2851
|
+
let chainLabel = tx.chain.toUpperCase();
|
|
2852
|
+
if (tx.chain === "tempo_moderato") chainLabel = "TEMPO";
|
|
2853
|
+
else if (tx.chain === "base_sepolia") chainLabel = "BASE_SEPOLIA";
|
|
2854
|
+
const chainTag = chain === "all" ? `[${chainLabel}] ` : "";
|
|
2855
|
+
const tokenName = tx.token || "USDC";
|
|
2856
|
+
console.log(` ${color}${sign}${tx.amount.toFixed(2)} ${tokenName}${reset} | ${chainTag}${tx.type === "IN" ? "from" : "to"} ${tx.other.slice(0, 10)}...${tx.other.slice(-4)} | ${date}`);
|
|
2091
2857
|
}
|
|
2092
2858
|
const inTotal = allTxns.filter((t) => t.type === "IN").reduce((s, t) => s + t.amount, 0);
|
|
2093
2859
|
const outTotal = allTxns.filter((t) => t.type === "OUT").reduce((s, t) => s + t.amount, 0);
|
|
@@ -2096,39 +2862,88 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2096
2862
|
`);
|
|
2097
2863
|
}
|
|
2098
2864
|
});
|
|
2099
|
-
program.command("services
|
|
2865
|
+
program.command("services [url]").description("List services from registry or a specific provider").option("-q, --query <keyword>", "Search by keyword (name, description, tags)").option("--max-price <price>", "Maximum price in USD").option("--type <type>", "Filter by type: api_service | file_download").option("--tag <tag>", "Filter by tag").option("--json", "Output as JSON").action(async (url, options) => {
|
|
2866
|
+
const MOLTSPAY_REGISTRY = "https://moltspay.com";
|
|
2100
2867
|
try {
|
|
2101
|
-
|
|
2102
|
-
|
|
2868
|
+
let services;
|
|
2869
|
+
let isRegistry = false;
|
|
2870
|
+
if (url) {
|
|
2871
|
+
const client = new MoltsPayClient();
|
|
2872
|
+
services = await client.getServices(url);
|
|
2873
|
+
} else {
|
|
2874
|
+
isRegistry = true;
|
|
2875
|
+
const params = new URLSearchParams();
|
|
2876
|
+
if (options.query) params.set("q", options.query);
|
|
2877
|
+
if (options.maxPrice) params.set("maxPrice", options.maxPrice);
|
|
2878
|
+
if (options.type) params.set("type", options.type);
|
|
2879
|
+
if (options.tag) params.set("tag", options.tag);
|
|
2880
|
+
const queryString = params.toString();
|
|
2881
|
+
const registryUrl = `${MOLTSPAY_REGISTRY}/registry/services${queryString ? "?" + queryString : ""}`;
|
|
2882
|
+
const res = await fetch(registryUrl);
|
|
2883
|
+
if (!res.ok) {
|
|
2884
|
+
throw new Error(`Registry request failed: ${res.status}`);
|
|
2885
|
+
}
|
|
2886
|
+
services = await res.json();
|
|
2887
|
+
}
|
|
2103
2888
|
if (options.json) {
|
|
2104
2889
|
console.log(JSON.stringify(services, null, 2));
|
|
2105
2890
|
} else {
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2891
|
+
const serviceList = services.services || [];
|
|
2892
|
+
if (isRegistry) {
|
|
2893
|
+
if (options.query) {
|
|
2894
|
+
console.log(`
|
|
2895
|
+
\u{1F50D} Search: "${options.query}" (${serviceList.length} results)
|
|
2896
|
+
`);
|
|
2897
|
+
} else {
|
|
2898
|
+
const filters = [];
|
|
2899
|
+
if (options.maxPrice) filters.push(`max $${options.maxPrice}`);
|
|
2900
|
+
if (options.type) filters.push(options.type);
|
|
2901
|
+
if (options.tag) filters.push(`#${options.tag}`);
|
|
2902
|
+
const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
|
|
2903
|
+
console.log(`
|
|
2904
|
+
\u{1F50D} MoltsPay Registry${filterStr} - ${serviceList.length} services
|
|
2109
2905
|
`);
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2906
|
+
}
|
|
2907
|
+
for (const svc of serviceList) {
|
|
2908
|
+
const name = (svc.name || svc.id).slice(0, 30).padEnd(30);
|
|
2909
|
+
const price = `$${svc.price}`.padEnd(8);
|
|
2910
|
+
const type = (svc.type || "unknown").padEnd(14);
|
|
2911
|
+
const provider = `@${svc.provider?.username || "unknown"}`;
|
|
2912
|
+
console.log(` ${name} ${price} ${type} ${provider}`);
|
|
2913
|
+
}
|
|
2914
|
+
if (serviceList.length > 0) {
|
|
2915
|
+
console.log(`
|
|
2916
|
+
\u{1F4A1} Use: moltspay pay <provider-url> <service-id>
|
|
2917
|
+
`);
|
|
2918
|
+
}
|
|
2114
2919
|
} else {
|
|
2115
|
-
|
|
2116
|
-
|
|
2920
|
+
if (services.provider) {
|
|
2921
|
+
console.log(`
|
|
2922
|
+
\u{1F3EA} ${services.provider.name}
|
|
2923
|
+
`);
|
|
2924
|
+
console.log(` ${services.provider.description || ""}`);
|
|
2925
|
+
console.log(` Wallet: ${services.provider.wallet}`);
|
|
2926
|
+
const chains = services.provider.chains ? Array.isArray(services.provider.chains) ? services.provider.chains.map((c) => typeof c === "string" ? c : c.chain).join(", ") : services.provider.chains : services.provider.chain || "base";
|
|
2927
|
+
console.log(` Chains: ${chains}`);
|
|
2928
|
+
} else {
|
|
2929
|
+
console.log(`
|
|
2930
|
+
\u{1F3EA} Provider Services
|
|
2117
2931
|
`);
|
|
2118
|
-
|
|
2119
|
-
}
|
|
2120
|
-
console.log("\n\u{1F4E6} Services:\n");
|
|
2121
|
-
for (const svc of services.services) {
|
|
2122
|
-
const status = svc.available !== false ? "\u2705" : "\u274C";
|
|
2123
|
-
console.log(` ${status} ${svc.id || svc.name}`);
|
|
2124
|
-
console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
|
|
2125
|
-
if (svc.description) {
|
|
2126
|
-
console.log(` ${svc.description}`);
|
|
2932
|
+
console.log(` ${serviceList.length} services available`);
|
|
2127
2933
|
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2934
|
+
console.log("\n\u{1F4E6} Services:\n");
|
|
2935
|
+
for (const svc of serviceList) {
|
|
2936
|
+
const status = svc.available !== false ? "\u2705" : "\u274C";
|
|
2937
|
+
console.log(` ${status} ${svc.id || svc.name}`);
|
|
2938
|
+
console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
|
|
2939
|
+
if (svc.description) {
|
|
2940
|
+
console.log(` ${svc.description}`);
|
|
2941
|
+
}
|
|
2942
|
+
if (svc.provider && !services.provider) {
|
|
2943
|
+
console.log(` Provider: ${svc.provider.name || svc.provider.username}`);
|
|
2944
|
+
}
|
|
2945
|
+
console.log("");
|
|
2130
2946
|
}
|
|
2131
|
-
console.log("");
|
|
2132
2947
|
}
|
|
2133
2948
|
}
|
|
2134
2949
|
} catch (err) {
|
|
@@ -2342,8 +3157,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
|
|
|
2342
3157
|
process.exit(1);
|
|
2343
3158
|
}
|
|
2344
3159
|
});
|
|
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
|
|
2346
|
-
const client = new MoltsPayClient();
|
|
3160
|
+
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, base_sepolia, or tempo_moderato).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
|
|
3161
|
+
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2347
3162
|
if (!client.isInitialized) {
|
|
2348
3163
|
console.error("\u274C Wallet not initialized. Run: npx moltspay init");
|
|
2349
3164
|
process.exit(1);
|
|
@@ -2372,13 +3187,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2372
3187
|
params.image_base64 = imageData.toString("base64");
|
|
2373
3188
|
}
|
|
2374
3189
|
}
|
|
2375
|
-
if (!params.prompt) {
|
|
2376
|
-
console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
|
|
2377
|
-
process.exit(1);
|
|
2378
|
-
}
|
|
2379
3190
|
const chain = options.chain?.toLowerCase();
|
|
2380
|
-
if (chain && !["base", "polygon"].includes(chain)) {
|
|
2381
|
-
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon`);
|
|
3191
|
+
if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
3192
|
+
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
|
|
2382
3193
|
process.exit(1);
|
|
2383
3194
|
}
|
|
2384
3195
|
const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
|
|
@@ -2409,10 +3220,22 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2409
3220
|
console.log("");
|
|
2410
3221
|
}
|
|
2411
3222
|
try {
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
3223
|
+
let result;
|
|
3224
|
+
if (chain === "tempo_moderato") {
|
|
3225
|
+
if (!options.json) {
|
|
3226
|
+
console.log(" Protocol: MPP (Machine Payments Protocol)");
|
|
3227
|
+
console.log("");
|
|
3228
|
+
}
|
|
3229
|
+
const mppUrl = server.includes(service) ? server : `${server}/${service}`;
|
|
3230
|
+
result = await client.payWithMPP(mppUrl, {
|
|
3231
|
+
body: params
|
|
3232
|
+
});
|
|
3233
|
+
} else {
|
|
3234
|
+
result = await client.pay(server, service, params, {
|
|
3235
|
+
token,
|
|
3236
|
+
chain
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
2416
3239
|
if (options.json) {
|
|
2417
3240
|
console.log(JSON.stringify(result));
|
|
2418
3241
|
} else {
|