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.mjs
CHANGED
|
@@ -7,14 +7,19 @@ var __esm = (fn, res) => function __init() {
|
|
|
7
7
|
// node_modules/tsup/assets/esm_shims.js
|
|
8
8
|
import path from "path";
|
|
9
9
|
import { fileURLToPath } from "url";
|
|
10
|
+
var getFilename, getDirname, __dirname;
|
|
10
11
|
var init_esm_shims = __esm({
|
|
11
12
|
"node_modules/tsup/assets/esm_shims.js"() {
|
|
12
13
|
"use strict";
|
|
14
|
+
getFilename = () => fileURLToPath(import.meta.url);
|
|
15
|
+
getDirname = () => path.dirname(getFilename());
|
|
16
|
+
__dirname = /* @__PURE__ */ getDirname();
|
|
13
17
|
}
|
|
14
18
|
});
|
|
15
19
|
|
|
16
20
|
// src/cli/index.ts
|
|
17
21
|
init_esm_shims();
|
|
22
|
+
import { webcrypto } from "crypto";
|
|
18
23
|
import { Command } from "commander";
|
|
19
24
|
import { homedir as homedir2 } from "os";
|
|
20
25
|
import { join as join4, dirname, resolve } from "path";
|
|
@@ -40,12 +45,15 @@ var CHAINS = {
|
|
|
40
45
|
USDC: {
|
|
41
46
|
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
42
47
|
decimals: 6,
|
|
43
|
-
symbol: "USDC"
|
|
48
|
+
symbol: "USDC",
|
|
49
|
+
eip712Name: "USD Coin"
|
|
50
|
+
// EIP-712 domain name
|
|
44
51
|
},
|
|
45
52
|
USDT: {
|
|
46
53
|
address: "0xfde4C96c8593536E31F229EA8f37b2ADa2699bb2",
|
|
47
54
|
decimals: 6,
|
|
48
|
-
symbol: "USDT"
|
|
55
|
+
symbol: "USDT",
|
|
56
|
+
eip712Name: "Tether USD"
|
|
49
57
|
}
|
|
50
58
|
},
|
|
51
59
|
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -62,12 +70,15 @@ var CHAINS = {
|
|
|
62
70
|
USDC: {
|
|
63
71
|
address: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
64
72
|
decimals: 6,
|
|
65
|
-
symbol: "USDC"
|
|
73
|
+
symbol: "USDC",
|
|
74
|
+
eip712Name: "USD Coin"
|
|
66
75
|
},
|
|
67
76
|
USDT: {
|
|
68
77
|
address: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F",
|
|
69
78
|
decimals: 6,
|
|
70
|
-
symbol: "USDT"
|
|
79
|
+
symbol: "USDT",
|
|
80
|
+
eip712Name: "(PoS) Tether USD"
|
|
81
|
+
// Polygon uses this name
|
|
71
82
|
}
|
|
72
83
|
},
|
|
73
84
|
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
@@ -75,27 +86,6 @@ var CHAINS = {
|
|
|
75
86
|
explorerTx: "https://polygonscan.com/tx/",
|
|
76
87
|
avgBlockTime: 2
|
|
77
88
|
},
|
|
78
|
-
ethereum: {
|
|
79
|
-
name: "Ethereum",
|
|
80
|
-
chainId: 1,
|
|
81
|
-
rpc: "https://eth.llamarpc.com",
|
|
82
|
-
tokens: {
|
|
83
|
-
USDC: {
|
|
84
|
-
address: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
85
|
-
decimals: 6,
|
|
86
|
-
symbol: "USDC"
|
|
87
|
-
},
|
|
88
|
-
USDT: {
|
|
89
|
-
address: "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
|
90
|
-
decimals: 6,
|
|
91
|
-
symbol: "USDT"
|
|
92
|
-
}
|
|
93
|
-
},
|
|
94
|
-
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
95
|
-
explorer: "https://etherscan.io/address/",
|
|
96
|
-
explorerTx: "https://etherscan.io/tx/",
|
|
97
|
-
avgBlockTime: 12
|
|
98
|
-
},
|
|
99
89
|
// ============ Testnet ============
|
|
100
90
|
base_sepolia: {
|
|
101
91
|
name: "Base Sepolia",
|
|
@@ -105,13 +95,17 @@ var CHAINS = {
|
|
|
105
95
|
USDC: {
|
|
106
96
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
107
97
|
decimals: 6,
|
|
108
|
-
symbol: "USDC"
|
|
98
|
+
symbol: "USDC",
|
|
99
|
+
eip712Name: "USDC"
|
|
100
|
+
// Testnet USDC uses 'USDC' not 'USD Coin'
|
|
109
101
|
},
|
|
110
102
|
USDT: {
|
|
111
103
|
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
112
104
|
// Same as USDC on testnet (no official USDT)
|
|
113
105
|
decimals: 6,
|
|
114
|
-
symbol: "USDT"
|
|
106
|
+
symbol: "USDT",
|
|
107
|
+
eip712Name: "USDC"
|
|
108
|
+
// Uses same contract as USDC
|
|
115
109
|
}
|
|
116
110
|
},
|
|
117
111
|
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
@@ -119,27 +113,34 @@ var CHAINS = {
|
|
|
119
113
|
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
120
114
|
avgBlockTime: 2
|
|
121
115
|
},
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
116
|
+
// ============ Tempo Testnet (Moderato) ============
|
|
117
|
+
tempo_moderato: {
|
|
118
|
+
name: "Tempo Moderato",
|
|
119
|
+
chainId: 42431,
|
|
120
|
+
rpc: "https://rpc.moderato.tempo.xyz",
|
|
126
121
|
tokens: {
|
|
122
|
+
// TIP-20 stablecoins on Tempo testnet (from mppx SDK)
|
|
123
|
+
// Note: Tempo uses USD as native gas token, not ETH
|
|
127
124
|
USDC: {
|
|
128
|
-
address: "
|
|
125
|
+
address: "0x20c0000000000000000000000000000000000000",
|
|
126
|
+
// pathUSD - primary testnet stablecoin
|
|
129
127
|
decimals: 6,
|
|
130
|
-
symbol: "USDC"
|
|
128
|
+
symbol: "USDC",
|
|
129
|
+
eip712Name: "pathUSD"
|
|
131
130
|
},
|
|
132
131
|
USDT: {
|
|
133
|
-
address: "
|
|
134
|
-
//
|
|
132
|
+
address: "0x20c0000000000000000000000000000000000001",
|
|
133
|
+
// alphaUSD
|
|
135
134
|
decimals: 6,
|
|
136
|
-
symbol: "USDT"
|
|
135
|
+
symbol: "USDT",
|
|
136
|
+
eip712Name: "alphaUSD"
|
|
137
137
|
}
|
|
138
138
|
},
|
|
139
|
-
usdc: "
|
|
140
|
-
explorer: "https://
|
|
141
|
-
explorerTx: "https://
|
|
142
|
-
avgBlockTime:
|
|
139
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
140
|
+
explorer: "https://explore.testnet.tempo.xyz/address/",
|
|
141
|
+
explorerTx: "https://explore.testnet.tempo.xyz/tx/",
|
|
142
|
+
avgBlockTime: 0.5
|
|
143
|
+
// ~500ms finality
|
|
143
144
|
}
|
|
144
145
|
};
|
|
145
146
|
function getChain(name) {
|
|
@@ -305,7 +306,7 @@ Server accepts: ${serverChains.join(", ")}`
|
|
|
305
306
|
} else {
|
|
306
307
|
throw new Error(
|
|
307
308
|
`Server accepts: ${serverChains.join(", ")}
|
|
308
|
-
Please specify: --chain base
|
|
309
|
+
Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
309
310
|
);
|
|
310
311
|
}
|
|
311
312
|
}
|
|
@@ -347,13 +348,19 @@ Please specify: --chain base or --chain polygon`
|
|
|
347
348
|
if (!payTo) {
|
|
348
349
|
throw new Error("Missing payTo address in payment requirements");
|
|
349
350
|
}
|
|
350
|
-
const
|
|
351
|
+
const domainOverride = req.extra && typeof req.extra === "object" && req.extra.name ? { name: req.extra.name, version: req.extra.version || "2" } : void 0;
|
|
352
|
+
const authorization = await this.signEIP3009(payTo, amount, chain, token, domainOverride);
|
|
351
353
|
const tokenConfig = chain.tokens[token];
|
|
352
|
-
const
|
|
354
|
+
const extra = req.extra && typeof req.extra === "object" ? req.extra : {
|
|
355
|
+
name: tokenConfig.eip712Name || "USD Coin",
|
|
356
|
+
version: "2"
|
|
357
|
+
};
|
|
353
358
|
const payload = {
|
|
354
359
|
x402Version: X402_VERSION,
|
|
360
|
+
scheme: "exact",
|
|
361
|
+
network,
|
|
355
362
|
payload: authorization,
|
|
356
|
-
//
|
|
363
|
+
// { authorization: {...}, signature: "0x..." }
|
|
357
364
|
accepted: {
|
|
358
365
|
scheme: "exact",
|
|
359
366
|
network,
|
|
@@ -361,7 +368,7 @@ Please specify: --chain base or --chain polygon`
|
|
|
361
368
|
amount: amountRaw,
|
|
362
369
|
payTo,
|
|
363
370
|
maxTimeoutSeconds: req.maxTimeoutSeconds || 300,
|
|
364
|
-
extra
|
|
371
|
+
extra
|
|
365
372
|
}
|
|
366
373
|
};
|
|
367
374
|
const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
|
|
@@ -391,7 +398,7 @@ Please specify: --chain base or --chain polygon`
|
|
|
391
398
|
* This only signs - no on-chain transaction, no gas needed.
|
|
392
399
|
* Supports both USDC and USDT.
|
|
393
400
|
*/
|
|
394
|
-
async signEIP3009(to, amount, chain, token = "USDC") {
|
|
401
|
+
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
395
402
|
const validAfter = 0;
|
|
396
403
|
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
397
404
|
const nonce = ethers.hexlify(ethers.randomBytes(32));
|
|
@@ -405,10 +412,11 @@ Please specify: --chain base or --chain polygon`
|
|
|
405
412
|
validBefore: validBefore.toString(),
|
|
406
413
|
nonce
|
|
407
414
|
};
|
|
408
|
-
const tokenName = token === "USDC" ? "USD Coin" : "Tether USD";
|
|
415
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
416
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
409
417
|
const domain = {
|
|
410
418
|
name: tokenName,
|
|
411
|
-
version:
|
|
419
|
+
version: tokenVersion,
|
|
412
420
|
chainId: chain.chainId,
|
|
413
421
|
verifyingContract: tokenConfig.address
|
|
414
422
|
};
|
|
@@ -571,30 +579,59 @@ Please specify: --chain base or --chain polygon`
|
|
|
571
579
|
};
|
|
572
580
|
}
|
|
573
581
|
/**
|
|
574
|
-
* Get wallet balances on all supported chains (Base + Polygon)
|
|
582
|
+
* Get wallet balances on all supported chains (Base + Polygon + Tempo)
|
|
575
583
|
*/
|
|
576
584
|
async getAllBalances() {
|
|
577
585
|
if (!this.wallet) {
|
|
578
586
|
throw new Error("Client not initialized");
|
|
579
587
|
}
|
|
580
|
-
const supportedChains = ["base", "polygon"];
|
|
588
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
581
589
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
582
590
|
const results = {};
|
|
591
|
+
const tempoTokens = {
|
|
592
|
+
pathUSD: "0x20c0000000000000000000000000000000000000",
|
|
593
|
+
alphaUSD: "0x20c0000000000000000000000000000000000001",
|
|
594
|
+
betaUSD: "0x20c0000000000000000000000000000000000002",
|
|
595
|
+
thetaUSD: "0x20c0000000000000000000000000000000000003"
|
|
596
|
+
};
|
|
583
597
|
await Promise.all(
|
|
584
598
|
supportedChains.map(async (chainName) => {
|
|
585
599
|
try {
|
|
586
600
|
const chain = getChain(chainName);
|
|
587
601
|
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
602
|
+
if (chainName === "tempo_moderato") {
|
|
603
|
+
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
604
|
+
provider.getBalance(this.wallet.address),
|
|
605
|
+
new ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
606
|
+
new ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
607
|
+
new ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
608
|
+
new ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
609
|
+
]);
|
|
610
|
+
results[chainName] = {
|
|
611
|
+
usdc: parseFloat(ethers.formatUnits(pathUSD, 6)),
|
|
612
|
+
// pathUSD as default USDC
|
|
613
|
+
usdt: parseFloat(ethers.formatUnits(alphaUSD, 6)),
|
|
614
|
+
// alphaUSD as default USDT
|
|
615
|
+
native: parseFloat(ethers.formatEther(nativeBalance)),
|
|
616
|
+
tempo: {
|
|
617
|
+
pathUSD: parseFloat(ethers.formatUnits(pathUSD, 6)),
|
|
618
|
+
alphaUSD: parseFloat(ethers.formatUnits(alphaUSD, 6)),
|
|
619
|
+
betaUSD: parseFloat(ethers.formatUnits(betaUSD, 6)),
|
|
620
|
+
thetaUSD: parseFloat(ethers.formatUnits(thetaUSD, 6))
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
} else {
|
|
624
|
+
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
625
|
+
provider.getBalance(this.wallet.address),
|
|
626
|
+
new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
627
|
+
new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
628
|
+
]);
|
|
629
|
+
results[chainName] = {
|
|
630
|
+
usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
631
|
+
usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
632
|
+
native: parseFloat(ethers.formatEther(nativeBalance))
|
|
633
|
+
};
|
|
634
|
+
}
|
|
598
635
|
} catch (err) {
|
|
599
636
|
results[chainName] = { usdc: 0, usdt: 0, native: 0 };
|
|
600
637
|
}
|
|
@@ -602,6 +639,121 @@ Please specify: --chain base or --chain polygon`
|
|
|
602
639
|
);
|
|
603
640
|
return results;
|
|
604
641
|
}
|
|
642
|
+
/**
|
|
643
|
+
* Pay for a service using MPP (Machine Payments Protocol)
|
|
644
|
+
*
|
|
645
|
+
* This implements the MPP flow manually for EOA wallets:
|
|
646
|
+
* 1. Request service → get 402 with WWW-Authenticate
|
|
647
|
+
* 2. Parse payment requirements
|
|
648
|
+
* 3. Execute transfer on Tempo chain
|
|
649
|
+
* 4. Retry with transaction hash as credential
|
|
650
|
+
*
|
|
651
|
+
* @param url - Full URL of the MPP-enabled endpoint
|
|
652
|
+
* @param options - Request options (body, headers)
|
|
653
|
+
* @returns Response from the service
|
|
654
|
+
*/
|
|
655
|
+
async payWithMPP(url, options = {}) {
|
|
656
|
+
if (!this.wallet || !this.walletData) {
|
|
657
|
+
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
658
|
+
}
|
|
659
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
660
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
661
|
+
const { tempoModerato } = await import("viem/chains");
|
|
662
|
+
const { Actions } = await import("viem/tempo");
|
|
663
|
+
const privateKey = this.walletData.privateKey;
|
|
664
|
+
const account = privateKeyToAccount(privateKey);
|
|
665
|
+
console.log(`[MoltsPay] Making MPP request to: ${url}`);
|
|
666
|
+
console.log(`[MoltsPay] Using account: ${account.address}`);
|
|
667
|
+
const initResponse = await fetch(url, {
|
|
668
|
+
method: "POST",
|
|
669
|
+
headers: {
|
|
670
|
+
"Content-Type": "application/json",
|
|
671
|
+
...options.headers
|
|
672
|
+
},
|
|
673
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
674
|
+
});
|
|
675
|
+
if (initResponse.status !== 402) {
|
|
676
|
+
if (initResponse.ok) {
|
|
677
|
+
return initResponse.json();
|
|
678
|
+
}
|
|
679
|
+
const errorText = await initResponse.text();
|
|
680
|
+
throw new Error(`Request failed (${initResponse.status}): ${errorText}`);
|
|
681
|
+
}
|
|
682
|
+
const wwwAuth = initResponse.headers.get("www-authenticate");
|
|
683
|
+
if (!wwwAuth || !wwwAuth.toLowerCase().includes("payment")) {
|
|
684
|
+
throw new Error("No WWW-Authenticate Payment challenge in 402 response");
|
|
685
|
+
}
|
|
686
|
+
console.log(`[MoltsPay] Got 402, parsing payment challenge...`);
|
|
687
|
+
const parseAuthParam = (header, key) => {
|
|
688
|
+
const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
|
|
689
|
+
return match ? match[1] : null;
|
|
690
|
+
};
|
|
691
|
+
const challengeId = parseAuthParam(wwwAuth, "id");
|
|
692
|
+
const method = parseAuthParam(wwwAuth, "method");
|
|
693
|
+
const realm = parseAuthParam(wwwAuth, "realm");
|
|
694
|
+
const requestB64 = parseAuthParam(wwwAuth, "request");
|
|
695
|
+
if (method !== "tempo") {
|
|
696
|
+
throw new Error(`Unsupported payment method: ${method}`);
|
|
697
|
+
}
|
|
698
|
+
if (!requestB64) {
|
|
699
|
+
throw new Error("Missing request in WWW-Authenticate");
|
|
700
|
+
}
|
|
701
|
+
const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
|
|
702
|
+
const paymentRequest = JSON.parse(requestJson);
|
|
703
|
+
console.log(`[MoltsPay] Payment request:`, paymentRequest);
|
|
704
|
+
const { amount, currency, recipient, methodDetails } = paymentRequest;
|
|
705
|
+
const chainId = methodDetails?.chainId || 42431;
|
|
706
|
+
console.log(`[MoltsPay] Executing transfer on Tempo (chainId: ${chainId})...`);
|
|
707
|
+
console.log(`[MoltsPay] Amount: ${amount}, To: ${recipient}`);
|
|
708
|
+
const tempoChain = { ...tempoModerato, feeToken: currency };
|
|
709
|
+
const publicClient = createPublicClient({
|
|
710
|
+
chain: tempoChain,
|
|
711
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
712
|
+
});
|
|
713
|
+
const walletClient = createWalletClient({
|
|
714
|
+
account,
|
|
715
|
+
chain: tempoChain,
|
|
716
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
717
|
+
});
|
|
718
|
+
const txHash = await Actions.token.transfer(walletClient, {
|
|
719
|
+
to: recipient,
|
|
720
|
+
amount: BigInt(amount),
|
|
721
|
+
token: currency
|
|
722
|
+
});
|
|
723
|
+
console.log(`[MoltsPay] Transaction sent: ${txHash}`);
|
|
724
|
+
console.log(`[MoltsPay] Waiting for confirmation...`);
|
|
725
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
726
|
+
console.log(`[MoltsPay] Transaction confirmed!`);
|
|
727
|
+
const challenge = {
|
|
728
|
+
id: challengeId,
|
|
729
|
+
realm,
|
|
730
|
+
method: "tempo",
|
|
731
|
+
intent: "charge",
|
|
732
|
+
request: paymentRequest
|
|
733
|
+
};
|
|
734
|
+
const credential = {
|
|
735
|
+
challenge,
|
|
736
|
+
payload: { hash: txHash, type: "hash" },
|
|
737
|
+
source: `did:pkh:eip155:${chainId}:${account.address}`
|
|
738
|
+
};
|
|
739
|
+
const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
740
|
+
console.log(`[MoltsPay] Retrying with payment credential...`);
|
|
741
|
+
const paidResponse = await fetch(url, {
|
|
742
|
+
method: "POST",
|
|
743
|
+
headers: {
|
|
744
|
+
"Content-Type": "application/json",
|
|
745
|
+
"Authorization": `Payment ${credentialB64}`,
|
|
746
|
+
...options.headers
|
|
747
|
+
},
|
|
748
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
749
|
+
});
|
|
750
|
+
if (!paidResponse.ok) {
|
|
751
|
+
const errorText = await paidResponse.text();
|
|
752
|
+
throw new Error(`Payment verification failed (${paidResponse.status}): ${errorText}`);
|
|
753
|
+
}
|
|
754
|
+
console.log(`[MoltsPay] Payment verified! Service completed.`);
|
|
755
|
+
return paidResponse.json();
|
|
756
|
+
}
|
|
605
757
|
};
|
|
606
758
|
|
|
607
759
|
// src/server/index.ts
|
|
@@ -626,8 +778,8 @@ init_esm_shims();
|
|
|
626
778
|
import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
627
779
|
import * as path2 from "path";
|
|
628
780
|
var X402_VERSION2 = 2;
|
|
629
|
-
var
|
|
630
|
-
var
|
|
781
|
+
var CDP_URL = "https://api.cdp.coinbase.com/platform/v2/x402";
|
|
782
|
+
var TESTNET_CHAIN_IDS = [84532];
|
|
631
783
|
function loadEnvFile() {
|
|
632
784
|
const envPaths = [
|
|
633
785
|
path2.join(process.cwd(), ".env"),
|
|
@@ -662,31 +814,33 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
662
814
|
displayName = "Coinbase CDP";
|
|
663
815
|
supportedNetworks;
|
|
664
816
|
endpoint;
|
|
665
|
-
useMainnet;
|
|
666
817
|
apiKeyId;
|
|
667
818
|
apiKeySecret;
|
|
668
819
|
constructor(config = {}) {
|
|
669
820
|
super();
|
|
670
821
|
loadEnvFile();
|
|
671
|
-
this.useMainnet = config.useMainnet ?? process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
672
822
|
this.apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
|
|
673
823
|
this.apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
|
|
674
|
-
this.endpoint =
|
|
675
|
-
this.supportedNetworks =
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
824
|
+
this.endpoint = CDP_URL;
|
|
825
|
+
this.supportedNetworks = [
|
|
826
|
+
"eip155:8453",
|
|
827
|
+
// Base mainnet
|
|
828
|
+
"eip155:137",
|
|
829
|
+
// Polygon mainnet
|
|
830
|
+
"eip155:84532"
|
|
831
|
+
// Base Sepolia (testnet)
|
|
832
|
+
];
|
|
833
|
+
if (!this.apiKeyId || !this.apiKeySecret) {
|
|
834
|
+
console.warn("[CDPFacilitator] WARNING: Missing CDP credentials!");
|
|
835
|
+
console.warn("[CDPFacilitator] Set CDP_API_KEY_ID and CDP_API_KEY_SECRET in ~/.moltspay/.env");
|
|
679
836
|
}
|
|
680
837
|
}
|
|
681
838
|
/**
|
|
682
839
|
* Get auth headers for CDP API requests
|
|
683
840
|
*/
|
|
684
841
|
async getAuthHeaders(method, urlPath, body) {
|
|
685
|
-
if (!this.useMainnet) {
|
|
686
|
-
return {};
|
|
687
|
-
}
|
|
688
842
|
if (!this.apiKeyId || !this.apiKeySecret) {
|
|
689
|
-
throw new Error("CDP credentials required
|
|
843
|
+
throw new Error("CDP credentials required. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET");
|
|
690
844
|
}
|
|
691
845
|
try {
|
|
692
846
|
const { getAuthHeaders } = await import("@coinbase/cdp-sdk/auth");
|
|
@@ -738,23 +892,23 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
738
892
|
paymentPayload,
|
|
739
893
|
paymentRequirements: requirements
|
|
740
894
|
};
|
|
895
|
+
console.log("[CDP Verify] Payload:", JSON.stringify(paymentPayload, null, 2));
|
|
896
|
+
const authHeaders = await this.getAuthHeaders(
|
|
897
|
+
"POST",
|
|
898
|
+
"/platform/v2/x402/verify",
|
|
899
|
+
requestBody
|
|
900
|
+
);
|
|
741
901
|
const headers = {
|
|
742
|
-
"Content-Type": "application/json"
|
|
902
|
+
"Content-Type": "application/json",
|
|
903
|
+
...authHeaders
|
|
743
904
|
};
|
|
744
|
-
if (this.useMainnet) {
|
|
745
|
-
const authHeaders = await this.getAuthHeaders(
|
|
746
|
-
"POST",
|
|
747
|
-
"/platform/v2/x402/verify",
|
|
748
|
-
requestBody
|
|
749
|
-
);
|
|
750
|
-
Object.assign(headers, authHeaders);
|
|
751
|
-
}
|
|
752
905
|
const response = await fetch(`${this.endpoint}/verify`, {
|
|
753
906
|
method: "POST",
|
|
754
907
|
headers,
|
|
755
908
|
body: JSON.stringify(requestBody)
|
|
756
909
|
});
|
|
757
910
|
const result = await response.json();
|
|
911
|
+
console.log("[CDP Verify] Response:", response.status, JSON.stringify(result));
|
|
758
912
|
if (!response.ok || !result.isValid) {
|
|
759
913
|
return {
|
|
760
914
|
valid: false,
|
|
@@ -780,17 +934,15 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
780
934
|
paymentPayload,
|
|
781
935
|
paymentRequirements: requirements
|
|
782
936
|
};
|
|
937
|
+
const authHeaders = await this.getAuthHeaders(
|
|
938
|
+
"POST",
|
|
939
|
+
"/platform/v2/x402/settle",
|
|
940
|
+
requestBody
|
|
941
|
+
);
|
|
783
942
|
const headers = {
|
|
784
|
-
"Content-Type": "application/json"
|
|
943
|
+
"Content-Type": "application/json",
|
|
944
|
+
...authHeaders
|
|
785
945
|
};
|
|
786
|
-
if (this.useMainnet) {
|
|
787
|
-
const authHeaders = await this.getAuthHeaders(
|
|
788
|
-
"POST",
|
|
789
|
-
"/platform/v2/x402/settle",
|
|
790
|
-
requestBody
|
|
791
|
-
);
|
|
792
|
-
Object.assign(headers, authHeaders);
|
|
793
|
-
}
|
|
794
946
|
const response = await fetch(`${this.endpoint}/settle`, {
|
|
795
947
|
method: "POST",
|
|
796
948
|
headers,
|
|
@@ -825,13 +977,140 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
825
977
|
freeQuota: 1e3
|
|
826
978
|
};
|
|
827
979
|
}
|
|
980
|
+
/**
|
|
981
|
+
* Check if a chain ID is testnet
|
|
982
|
+
*/
|
|
983
|
+
static isTestnet(chainId) {
|
|
984
|
+
return TESTNET_CHAIN_IDS.includes(chainId);
|
|
985
|
+
}
|
|
828
986
|
/**
|
|
829
987
|
* Get configuration summary (for logging)
|
|
830
988
|
*/
|
|
831
989
|
getConfigSummary() {
|
|
832
|
-
const mode = this.useMainnet ? "mainnet" : "testnet";
|
|
833
990
|
const hasCredentials = !!(this.apiKeyId && this.apiKeySecret);
|
|
834
|
-
|
|
991
|
+
const networks = this.supportedNetworks.join(", ");
|
|
992
|
+
return `CDP Facilitator (networks: ${networks}, credentials: ${hasCredentials ? "yes" : "no"})`;
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// src/facilitators/tempo.ts
|
|
997
|
+
init_esm_shims();
|
|
998
|
+
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
999
|
+
var TempoFacilitator = class extends BaseFacilitator {
|
|
1000
|
+
name = "tempo";
|
|
1001
|
+
displayName = "Tempo Testnet";
|
|
1002
|
+
supportedNetworks = ["eip155:42431"];
|
|
1003
|
+
// Tempo Moderato
|
|
1004
|
+
rpcUrl;
|
|
1005
|
+
constructor() {
|
|
1006
|
+
super();
|
|
1007
|
+
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
1008
|
+
}
|
|
1009
|
+
async healthCheck() {
|
|
1010
|
+
const start = Date.now();
|
|
1011
|
+
try {
|
|
1012
|
+
const response = await fetch(this.rpcUrl, {
|
|
1013
|
+
method: "POST",
|
|
1014
|
+
headers: { "Content-Type": "application/json" },
|
|
1015
|
+
body: JSON.stringify({
|
|
1016
|
+
jsonrpc: "2.0",
|
|
1017
|
+
method: "eth_chainId",
|
|
1018
|
+
params: [],
|
|
1019
|
+
id: 1
|
|
1020
|
+
})
|
|
1021
|
+
});
|
|
1022
|
+
const data = await response.json();
|
|
1023
|
+
const chainId = parseInt(data.result, 16);
|
|
1024
|
+
if (chainId !== 42431) {
|
|
1025
|
+
return { healthy: false, error: `Wrong chainId: ${chainId}` };
|
|
1026
|
+
}
|
|
1027
|
+
return { healthy: true, latencyMs: Date.now() - start };
|
|
1028
|
+
} catch (error) {
|
|
1029
|
+
return { healthy: false, error: String(error) };
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
async verify(paymentPayload, requirements) {
|
|
1033
|
+
try {
|
|
1034
|
+
const tempoPayload = paymentPayload.payload;
|
|
1035
|
+
if (!tempoPayload?.txHash) {
|
|
1036
|
+
return { valid: false, error: "Missing txHash in payment payload" };
|
|
1037
|
+
}
|
|
1038
|
+
const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
|
|
1039
|
+
if (!receipt) {
|
|
1040
|
+
return { valid: false, error: "Transaction not found" };
|
|
1041
|
+
}
|
|
1042
|
+
if (receipt.status !== "0x1") {
|
|
1043
|
+
return { valid: false, error: "Transaction failed" };
|
|
1044
|
+
}
|
|
1045
|
+
const transferLog = receipt.logs.find(
|
|
1046
|
+
(log) => log.topics[0] === TRANSFER_EVENT_TOPIC
|
|
1047
|
+
);
|
|
1048
|
+
if (!transferLog) {
|
|
1049
|
+
return { valid: false, error: "No Transfer event found" };
|
|
1050
|
+
}
|
|
1051
|
+
const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
|
|
1052
|
+
const expectedTo = requirements.payTo.toLowerCase();
|
|
1053
|
+
if (toAddress !== expectedTo) {
|
|
1054
|
+
return {
|
|
1055
|
+
valid: false,
|
|
1056
|
+
error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
1059
|
+
const amount = BigInt(transferLog.data);
|
|
1060
|
+
const expectedAmount = BigInt(requirements.amount);
|
|
1061
|
+
if (amount < expectedAmount) {
|
|
1062
|
+
return {
|
|
1063
|
+
valid: false,
|
|
1064
|
+
error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
const tokenAddress = transferLog.address.toLowerCase();
|
|
1068
|
+
const expectedToken = requirements.asset.toLowerCase();
|
|
1069
|
+
if (tokenAddress !== expectedToken) {
|
|
1070
|
+
return {
|
|
1071
|
+
valid: false,
|
|
1072
|
+
error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
return {
|
|
1076
|
+
valid: true,
|
|
1077
|
+
details: {
|
|
1078
|
+
txHash: tempoPayload.txHash,
|
|
1079
|
+
from: "0x" + transferLog.topics[1].slice(26),
|
|
1080
|
+
to: toAddress,
|
|
1081
|
+
amount: amount.toString(),
|
|
1082
|
+
token: tokenAddress
|
|
1083
|
+
}
|
|
1084
|
+
};
|
|
1085
|
+
} catch (error) {
|
|
1086
|
+
return { valid: false, error: `Verification failed: ${error}` };
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
async settle(paymentPayload, requirements) {
|
|
1090
|
+
const verifyResult = await this.verify(paymentPayload, requirements);
|
|
1091
|
+
if (!verifyResult.valid) {
|
|
1092
|
+
return { success: false, error: verifyResult.error };
|
|
1093
|
+
}
|
|
1094
|
+
const tempoPayload = paymentPayload.payload;
|
|
1095
|
+
return {
|
|
1096
|
+
success: true,
|
|
1097
|
+
transaction: tempoPayload.txHash,
|
|
1098
|
+
status: "settled"
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
async getTransactionReceipt(txHash) {
|
|
1102
|
+
const response = await fetch(this.rpcUrl, {
|
|
1103
|
+
method: "POST",
|
|
1104
|
+
headers: { "Content-Type": "application/json" },
|
|
1105
|
+
body: JSON.stringify({
|
|
1106
|
+
jsonrpc: "2.0",
|
|
1107
|
+
method: "eth_getTransactionReceipt",
|
|
1108
|
+
params: [txHash],
|
|
1109
|
+
id: 1
|
|
1110
|
+
})
|
|
1111
|
+
});
|
|
1112
|
+
const data = await response.json();
|
|
1113
|
+
return data.result;
|
|
835
1114
|
}
|
|
836
1115
|
};
|
|
837
1116
|
|
|
@@ -844,7 +1123,8 @@ var FacilitatorRegistry = class {
|
|
|
844
1123
|
roundRobinIndex = 0;
|
|
845
1124
|
constructor(selection) {
|
|
846
1125
|
this.registerFactory("cdp", (config) => new CDPFacilitator(config));
|
|
847
|
-
this.
|
|
1126
|
+
this.registerFactory("tempo", () => new TempoFacilitator());
|
|
1127
|
+
this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
|
|
848
1128
|
}
|
|
849
1129
|
/**
|
|
850
1130
|
* Register a new facilitator factory
|
|
@@ -1068,6 +1348,9 @@ var X402_VERSION3 = 2;
|
|
|
1068
1348
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
1069
1349
|
var PAYMENT_HEADER2 = "x-payment";
|
|
1070
1350
|
var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
1351
|
+
var MPP_AUTH_HEADER = "authorization";
|
|
1352
|
+
var MPP_WWW_AUTH_HEADER = "www-authenticate";
|
|
1353
|
+
var MPP_RECEIPT_HEADER = "payment-receipt";
|
|
1071
1354
|
var TOKEN_ADDRESSES = {
|
|
1072
1355
|
"eip155:8453": {
|
|
1073
1356
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -1081,17 +1364,48 @@ var TOKEN_ADDRESSES = {
|
|
|
1081
1364
|
"eip155:137": {
|
|
1082
1365
|
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
1083
1366
|
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
|
|
1367
|
+
},
|
|
1368
|
+
"eip155:42431": {
|
|
1369
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1370
|
+
USDC: "0x20c0000000000000000000000000000000000000",
|
|
1371
|
+
// pathUSD
|
|
1372
|
+
USDT: "0x20c0000000000000000000000000000000000001"
|
|
1373
|
+
// alphaUSD
|
|
1084
1374
|
}
|
|
1085
1375
|
};
|
|
1086
1376
|
var CHAIN_TO_NETWORK = {
|
|
1087
1377
|
"base": "eip155:8453",
|
|
1088
1378
|
"base_sepolia": "eip155:84532",
|
|
1089
|
-
"polygon": "eip155:137"
|
|
1379
|
+
"polygon": "eip155:137",
|
|
1380
|
+
"tempo_moderato": "eip155:42431"
|
|
1090
1381
|
};
|
|
1091
1382
|
var TOKEN_DOMAINS = {
|
|
1092
|
-
|
|
1093
|
-
|
|
1383
|
+
// Base mainnet
|
|
1384
|
+
"eip155:8453": {
|
|
1385
|
+
USDC: { name: "USD Coin", version: "2" },
|
|
1386
|
+
USDT: { name: "Tether USD", version: "2" }
|
|
1387
|
+
},
|
|
1388
|
+
// Base Sepolia testnet - USDC uses 'USDC' not 'USD Coin'
|
|
1389
|
+
"eip155:84532": {
|
|
1390
|
+
USDC: { name: "USDC", version: "2" },
|
|
1391
|
+
USDT: { name: "USDC", version: "2" }
|
|
1392
|
+
// Same contract as USDC on testnet
|
|
1393
|
+
},
|
|
1394
|
+
// Polygon mainnet
|
|
1395
|
+
"eip155:137": {
|
|
1396
|
+
USDC: { name: "USD Coin", version: "2" },
|
|
1397
|
+
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1398
|
+
},
|
|
1399
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1400
|
+
"eip155:42431": {
|
|
1401
|
+
USDC: { name: "pathUSD", version: "1" },
|
|
1402
|
+
USDT: { name: "alphaUSD", version: "1" }
|
|
1403
|
+
}
|
|
1094
1404
|
};
|
|
1405
|
+
function getTokenDomain(network, token) {
|
|
1406
|
+
const networkDomains = TOKEN_DOMAINS[network] || TOKEN_DOMAINS["eip155:8453"];
|
|
1407
|
+
return networkDomains[token] || { name: "USD Coin", version: "2" };
|
|
1408
|
+
}
|
|
1095
1409
|
function getAcceptedCurrencies(config) {
|
|
1096
1410
|
return config.acceptedCurrencies ?? [config.currency];
|
|
1097
1411
|
}
|
|
@@ -1143,9 +1457,11 @@ var MoltsPayServer = class {
|
|
|
1143
1457
|
};
|
|
1144
1458
|
this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
1145
1459
|
this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
|
|
1460
|
+
const defaultFallback = ["tempo"];
|
|
1461
|
+
const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
|
|
1146
1462
|
const facilitatorConfig = options.facilitators || {
|
|
1147
1463
|
primary: process.env.FACILITATOR_PRIMARY || "cdp",
|
|
1148
|
-
fallback:
|
|
1464
|
+
fallback: envFallback || defaultFallback,
|
|
1149
1465
|
strategy: process.env.FACILITATOR_STRATEGY || "failover",
|
|
1150
1466
|
config: {
|
|
1151
1467
|
cdp: { useMainnet: this.useMainnet }
|
|
@@ -1185,11 +1501,14 @@ var MoltsPayServer = class {
|
|
|
1185
1501
|
getProviderChains() {
|
|
1186
1502
|
const provider = this.manifest.provider;
|
|
1187
1503
|
if (provider.chains && provider.chains.length > 0) {
|
|
1188
|
-
return provider.chains.map((c) =>
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1504
|
+
return provider.chains.map((c) => {
|
|
1505
|
+
const chainName = typeof c === "string" ? c : c.chain;
|
|
1506
|
+
return {
|
|
1507
|
+
network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
|
|
1508
|
+
wallet: (typeof c === "object" ? c.wallet : null) || provider.wallet,
|
|
1509
|
+
tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
|
|
1510
|
+
};
|
|
1511
|
+
});
|
|
1193
1512
|
}
|
|
1194
1513
|
const chain = provider.chain || "base";
|
|
1195
1514
|
const network = CHAIN_TO_NETWORK[chain] || this.networkId;
|
|
@@ -1236,8 +1555,8 @@ var MoltsPayServer = class {
|
|
|
1236
1555
|
async handleRequest(req, res) {
|
|
1237
1556
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1238
1557
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1239
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
1240
|
-
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
1558
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1559
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
|
|
1241
1560
|
if (req.method === "OPTIONS") {
|
|
1242
1561
|
res.writeHead(204);
|
|
1243
1562
|
res.end();
|
|
@@ -1268,6 +1587,14 @@ var MoltsPayServer = class {
|
|
|
1268
1587
|
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1269
1588
|
return await this.handleProxy(body, paymentHeader, res);
|
|
1270
1589
|
}
|
|
1590
|
+
const servicePath = url.pathname.replace(/^\//, "");
|
|
1591
|
+
const skill = this.skills.get(servicePath);
|
|
1592
|
+
if (skill && (req.method === "POST" || req.method === "GET")) {
|
|
1593
|
+
const body = req.method === "POST" ? await this.readBody(req) : {};
|
|
1594
|
+
const authHeader = req.headers[MPP_AUTH_HEADER];
|
|
1595
|
+
const x402Header = req.headers[PAYMENT_HEADER2];
|
|
1596
|
+
return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
|
|
1597
|
+
}
|
|
1271
1598
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1272
1599
|
} catch (err) {
|
|
1273
1600
|
console.error("[MoltsPay] Error:", err);
|
|
@@ -1399,7 +1726,7 @@ var MoltsPayServer = class {
|
|
|
1399
1726
|
const paymentNetwork = payment.accepted?.network || payment.network || this.networkId;
|
|
1400
1727
|
const paymentWallet = this.getWalletForNetwork(paymentNetwork);
|
|
1401
1728
|
const requirements = this.buildPaymentRequirements(skill.config, paymentNetwork, paymentWallet, paymentToken);
|
|
1402
|
-
console.log(`[MoltsPay] Verifying payment...`);
|
|
1729
|
+
console.log(`[MoltsPay] Verifying payment on ${paymentNetwork}...`);
|
|
1403
1730
|
const verifyResult = await this.registry.verify(payment, requirements);
|
|
1404
1731
|
if (!verifyResult.valid) {
|
|
1405
1732
|
return this.sendJson(res, 402, {
|
|
@@ -1451,6 +1778,187 @@ var MoltsPayServer = class {
|
|
|
1451
1778
|
payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
|
|
1452
1779
|
}, responseHeaders);
|
|
1453
1780
|
}
|
|
1781
|
+
/**
|
|
1782
|
+
* Handle MPP (Machine Payments Protocol) request
|
|
1783
|
+
* Supports both x402 and MPP protocols on service endpoints
|
|
1784
|
+
*/
|
|
1785
|
+
async handleMPPRequest(skill, body, authHeader, x402Header, res) {
|
|
1786
|
+
const config = skill.config;
|
|
1787
|
+
const params = body || {};
|
|
1788
|
+
if (x402Header) {
|
|
1789
|
+
return await this.handleExecute({ service: config.id, params }, x402Header, res);
|
|
1790
|
+
}
|
|
1791
|
+
if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
|
|
1792
|
+
return await this.handleMPPPayment(skill, params, authHeader, res);
|
|
1793
|
+
}
|
|
1794
|
+
return this.sendMPPPaymentRequired(config, res);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Handle MPP payment verification and service execution
|
|
1798
|
+
*/
|
|
1799
|
+
async handleMPPPayment(skill, params, authHeader, res) {
|
|
1800
|
+
const config = skill.config;
|
|
1801
|
+
const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
|
|
1802
|
+
if (!credentialMatch) {
|
|
1803
|
+
return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
|
|
1804
|
+
}
|
|
1805
|
+
let mppCredential;
|
|
1806
|
+
try {
|
|
1807
|
+
const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1808
|
+
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
|
1809
|
+
mppCredential = JSON.parse(decoded);
|
|
1810
|
+
} catch (err) {
|
|
1811
|
+
console.error("[MoltsPay] Failed to parse MPP credential:", err);
|
|
1812
|
+
return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
|
|
1813
|
+
}
|
|
1814
|
+
let txHash;
|
|
1815
|
+
if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
|
|
1816
|
+
txHash = mppCredential.payload.hash;
|
|
1817
|
+
} else if (mppCredential.payload?.type === "transaction") {
|
|
1818
|
+
return this.sendJson(res, 400, {
|
|
1819
|
+
error: "Transaction type not supported. Please use push mode (hash type)."
|
|
1820
|
+
});
|
|
1821
|
+
}
|
|
1822
|
+
if (!txHash) {
|
|
1823
|
+
return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
|
|
1824
|
+
}
|
|
1825
|
+
let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
|
|
1826
|
+
if (!chainId && mppCredential.source) {
|
|
1827
|
+
const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
|
|
1828
|
+
if (chainMatch) chainId = parseInt(chainMatch[1], 10);
|
|
1829
|
+
}
|
|
1830
|
+
chainId = chainId || 42431;
|
|
1831
|
+
const network = `eip155:${chainId}`;
|
|
1832
|
+
if (!this.isNetworkAccepted(network)) {
|
|
1833
|
+
return this.sendJson(res, 402, {
|
|
1834
|
+
error: `Network not accepted: ${network}`
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
const requirements = this.buildPaymentRequirements(
|
|
1838
|
+
config,
|
|
1839
|
+
network,
|
|
1840
|
+
this.getWalletForNetwork(network),
|
|
1841
|
+
"USDC"
|
|
1842
|
+
);
|
|
1843
|
+
const paymentPayload = {
|
|
1844
|
+
x402Version: X402_VERSION3,
|
|
1845
|
+
scheme: "exact",
|
|
1846
|
+
network,
|
|
1847
|
+
payload: {
|
|
1848
|
+
txHash,
|
|
1849
|
+
chainId
|
|
1850
|
+
}
|
|
1851
|
+
};
|
|
1852
|
+
console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
|
|
1853
|
+
const verification = await this.registry.verify(paymentPayload, requirements);
|
|
1854
|
+
if (!verification.valid) {
|
|
1855
|
+
return this.sendJson(res, 402, {
|
|
1856
|
+
error: `Payment verification failed: ${verification.error}`
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
|
|
1860
|
+
let result;
|
|
1861
|
+
try {
|
|
1862
|
+
result = await skill.handler(params);
|
|
1863
|
+
} catch (err) {
|
|
1864
|
+
console.error(`[MoltsPay] Skill execution error:`, err);
|
|
1865
|
+
return this.sendJson(res, 500, {
|
|
1866
|
+
error: `Service execution failed: ${err.message}`
|
|
1867
|
+
});
|
|
1868
|
+
}
|
|
1869
|
+
const receipt = {
|
|
1870
|
+
success: true,
|
|
1871
|
+
txHash,
|
|
1872
|
+
network,
|
|
1873
|
+
facilitator: verification.facilitator
|
|
1874
|
+
};
|
|
1875
|
+
const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
|
|
1876
|
+
res.writeHead(200, {
|
|
1877
|
+
"Content-Type": "application/json",
|
|
1878
|
+
[MPP_RECEIPT_HEADER]: receiptEncoded
|
|
1879
|
+
});
|
|
1880
|
+
res.end(JSON.stringify({
|
|
1881
|
+
success: true,
|
|
1882
|
+
result,
|
|
1883
|
+
payment: {
|
|
1884
|
+
txHash,
|
|
1885
|
+
status: "verified",
|
|
1886
|
+
facilitator: verification.facilitator
|
|
1887
|
+
}
|
|
1888
|
+
}, null, 2));
|
|
1889
|
+
}
|
|
1890
|
+
/**
|
|
1891
|
+
* Return 402 with both x402 and MPP payment requirements
|
|
1892
|
+
*/
|
|
1893
|
+
sendMPPPaymentRequired(config, res) {
|
|
1894
|
+
const acceptedTokens = getAcceptedCurrencies(config);
|
|
1895
|
+
const providerChains = this.getProviderChains();
|
|
1896
|
+
const accepts = [];
|
|
1897
|
+
for (const chainConfig of providerChains) {
|
|
1898
|
+
for (const token of acceptedTokens) {
|
|
1899
|
+
if (chainConfig.tokens.includes(token)) {
|
|
1900
|
+
accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
const x402PaymentRequired = {
|
|
1905
|
+
x402Version: X402_VERSION3,
|
|
1906
|
+
accepts,
|
|
1907
|
+
acceptedCurrencies: acceptedTokens,
|
|
1908
|
+
resource: {
|
|
1909
|
+
url: `/${config.id}`,
|
|
1910
|
+
description: `${config.name} - $${config.price} ${config.currency}`
|
|
1911
|
+
}
|
|
1912
|
+
};
|
|
1913
|
+
const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
|
|
1914
|
+
const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
|
|
1915
|
+
let mppWwwAuth = "";
|
|
1916
|
+
if (tempoChain) {
|
|
1917
|
+
const challengeId = this.generateChallengeId();
|
|
1918
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
1919
|
+
const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
|
|
1920
|
+
const mppRequest = {
|
|
1921
|
+
amount: amountInUnits,
|
|
1922
|
+
currency: tokenAddress,
|
|
1923
|
+
methodDetails: {
|
|
1924
|
+
chainId: 42431,
|
|
1925
|
+
feePayer: true
|
|
1926
|
+
},
|
|
1927
|
+
recipient: tempoChain.wallet
|
|
1928
|
+
};
|
|
1929
|
+
const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
|
|
1930
|
+
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
1931
|
+
mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
|
|
1932
|
+
}
|
|
1933
|
+
const headers = {
|
|
1934
|
+
"Content-Type": "application/problem+json",
|
|
1935
|
+
[PAYMENT_REQUIRED_HEADER2]: x402Encoded
|
|
1936
|
+
};
|
|
1937
|
+
if (mppWwwAuth) {
|
|
1938
|
+
headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
|
|
1939
|
+
}
|
|
1940
|
+
res.writeHead(402, headers);
|
|
1941
|
+
res.end(JSON.stringify({
|
|
1942
|
+
type: "https://paymentauth.org/problems/payment-required",
|
|
1943
|
+
title: "Payment Required",
|
|
1944
|
+
status: 402,
|
|
1945
|
+
detail: `Payment is required (${config.name}).`,
|
|
1946
|
+
service: config.id,
|
|
1947
|
+
price: config.price,
|
|
1948
|
+
currency: config.currency,
|
|
1949
|
+
acceptedCurrencies: acceptedTokens
|
|
1950
|
+
}, null, 2));
|
|
1951
|
+
}
|
|
1952
|
+
/**
|
|
1953
|
+
* Generate a unique challenge ID for MPP
|
|
1954
|
+
*/
|
|
1955
|
+
generateChallengeId() {
|
|
1956
|
+
const bytes = new Uint8Array(24);
|
|
1957
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1958
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1959
|
+
}
|
|
1960
|
+
return Buffer.from(bytes).toString("base64url");
|
|
1961
|
+
}
|
|
1454
1962
|
/**
|
|
1455
1963
|
* Return 402 with x402 payment requirements (v2 format)
|
|
1456
1964
|
* Includes requirements for all chains and all accepted currencies
|
|
@@ -1525,7 +2033,7 @@ var MoltsPayServer = class {
|
|
|
1525
2033
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
1526
2034
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
1527
2035
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
1528
|
-
const tokenDomain =
|
|
2036
|
+
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
1529
2037
|
return {
|
|
1530
2038
|
scheme: "exact",
|
|
1531
2039
|
network: selectedNetwork,
|
|
@@ -1770,7 +2278,7 @@ var MoltsPayServer = class {
|
|
|
1770
2278
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
1771
2279
|
const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
|
|
1772
2280
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
1773
|
-
const tokenDomain =
|
|
2281
|
+
const tokenDomain = getTokenDomain(networkId, selectedToken);
|
|
1774
2282
|
return {
|
|
1775
2283
|
scheme: "exact",
|
|
1776
2284
|
network: networkId,
|
|
@@ -1825,6 +2333,26 @@ async function printQRCode(url) {
|
|
|
1825
2333
|
|
|
1826
2334
|
// src/cli/index.ts
|
|
1827
2335
|
import * as readline from "readline";
|
|
2336
|
+
if (!globalThis.crypto) {
|
|
2337
|
+
globalThis.crypto = webcrypto;
|
|
2338
|
+
}
|
|
2339
|
+
function getVersion() {
|
|
2340
|
+
const locations = [
|
|
2341
|
+
join4(__dirname, "../../package.json"),
|
|
2342
|
+
join4(__dirname, "../package.json"),
|
|
2343
|
+
join4(process.cwd(), "node_modules/moltspay/package.json")
|
|
2344
|
+
];
|
|
2345
|
+
for (const loc of locations) {
|
|
2346
|
+
try {
|
|
2347
|
+
if (existsSync4(loc)) {
|
|
2348
|
+
const pkg = JSON.parse(readFileSync4(loc, "utf-8"));
|
|
2349
|
+
if (pkg.name === "moltspay") return pkg.version;
|
|
2350
|
+
}
|
|
2351
|
+
} catch {
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
return "0.0.0";
|
|
2355
|
+
}
|
|
1828
2356
|
var program = new Command();
|
|
1829
2357
|
var DEFAULT_CONFIG_DIR = join4(homedir2(), ".moltspay");
|
|
1830
2358
|
var PID_FILE = join4(DEFAULT_CONFIG_DIR, "server.pid");
|
|
@@ -1843,7 +2371,7 @@ function prompt(question) {
|
|
|
1843
2371
|
});
|
|
1844
2372
|
});
|
|
1845
2373
|
}
|
|
1846
|
-
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(
|
|
2374
|
+
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
|
|
1847
2375
|
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) => {
|
|
1848
2376
|
console.log("\n\u{1F510} MoltsPay Client Setup\n");
|
|
1849
2377
|
if (existsSync4(join4(options.configDir, "wallet.json"))) {
|
|
@@ -1852,6 +2380,11 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
|
|
|
1852
2380
|
return;
|
|
1853
2381
|
}
|
|
1854
2382
|
let chain = options.chain;
|
|
2383
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
2384
|
+
if (!supportedChains.includes(chain)) {
|
|
2385
|
+
console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
|
|
2386
|
+
process.exit(1);
|
|
2387
|
+
}
|
|
1855
2388
|
let maxPerTx = options.maxPerTx ? parseFloat(options.maxPerTx) : null;
|
|
1856
2389
|
let maxPerDay = options.maxPerDay ? parseFloat(options.maxPerDay) : null;
|
|
1857
2390
|
if (!maxPerTx) {
|
|
@@ -1914,7 +2447,7 @@ program.command("config").description("Update MoltsPay settings").option("--max-
|
|
|
1914
2447
|
}
|
|
1915
2448
|
}
|
|
1916
2449
|
});
|
|
1917
|
-
program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base or
|
|
2450
|
+
program.command("fund <amount>").description("Fund wallet with USDC via Coinbase (US debit card / Apple Pay)").option("--chain <chain>", "Chain to fund (base, polygon, or base_sepolia)", "base").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (amountStr, options) => {
|
|
1918
2451
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
1919
2452
|
if (!client.isInitialized) {
|
|
1920
2453
|
console.log("\u274C Not initialized. Run: npx moltspay init");
|
|
@@ -1926,8 +2459,18 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1926
2459
|
return;
|
|
1927
2460
|
}
|
|
1928
2461
|
const chain = options.chain?.toLowerCase() || "base";
|
|
1929
|
-
if (!["base", "polygon"].includes(chain)) {
|
|
1930
|
-
console.log("\u274C Invalid chain. Use: base or
|
|
2462
|
+
if (!["base", "polygon", "base_sepolia"].includes(chain)) {
|
|
2463
|
+
console.log("\u274C Invalid chain. Use: base, polygon, or base_sepolia");
|
|
2464
|
+
return;
|
|
2465
|
+
}
|
|
2466
|
+
if (chain === "base_sepolia") {
|
|
2467
|
+
console.log("\n\u{1F9EA} Testnet Funding\n");
|
|
2468
|
+
console.log(` Wallet: ${client.address}`);
|
|
2469
|
+
console.log(` Chain: Base Sepolia (testnet)
|
|
2470
|
+
`);
|
|
2471
|
+
console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
|
|
2472
|
+
console.log(" npx moltspay faucet\n");
|
|
2473
|
+
console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
|
|
1931
2474
|
return;
|
|
1932
2475
|
}
|
|
1933
2476
|
console.log("\n\u{1F4B3} Fund your agent wallet\n");
|
|
@@ -1959,6 +2502,90 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1959
2502
|
console.log(`\u274C ${error.message}`);
|
|
1960
2503
|
}
|
|
1961
2504
|
});
|
|
2505
|
+
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) => {
|
|
2506
|
+
let address = options.address;
|
|
2507
|
+
const chain = options.chain?.toLowerCase() || "base_sepolia";
|
|
2508
|
+
if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
2509
|
+
console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
|
|
2510
|
+
return;
|
|
2511
|
+
}
|
|
2512
|
+
if (!address) {
|
|
2513
|
+
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2514
|
+
if (client.isInitialized) {
|
|
2515
|
+
address = client.address;
|
|
2516
|
+
} else {
|
|
2517
|
+
console.log('\u274C No wallet found. Either run "npx moltspay init" or provide --address');
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
}
|
|
2521
|
+
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
2522
|
+
console.log("\u274C Invalid Ethereum address");
|
|
2523
|
+
return;
|
|
2524
|
+
}
|
|
2525
|
+
console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
|
|
2526
|
+
if (chain === "tempo_moderato") {
|
|
2527
|
+
console.log(` Requesting testnet tokens on Tempo Moderato...`);
|
|
2528
|
+
console.log(` Address: ${address}
|
|
2529
|
+
`);
|
|
2530
|
+
try {
|
|
2531
|
+
const TEMPO_FAUCET_API = "https://docs.tempo.xyz/api/faucet";
|
|
2532
|
+
const response = await fetch(TEMPO_FAUCET_API, {
|
|
2533
|
+
method: "POST",
|
|
2534
|
+
headers: { "Content-Type": "application/json" },
|
|
2535
|
+
body: JSON.stringify({ address })
|
|
2536
|
+
});
|
|
2537
|
+
const result = await response.json();
|
|
2538
|
+
if (response.ok && result.data && result.data.length > 0) {
|
|
2539
|
+
console.log(`\u2705 Received testnet tokens!
|
|
2540
|
+
`);
|
|
2541
|
+
console.log(` Tokens: pathUSD, AlphaUSD, BetaUSD, ThetaUSD (1M each)`);
|
|
2542
|
+
console.log(` Transactions:`);
|
|
2543
|
+
for (const tx of result.data) {
|
|
2544
|
+
console.log(` https://explore.testnet.tempo.xyz/tx/${tx.hash}`);
|
|
2545
|
+
}
|
|
2546
|
+
console.log("\n\u{1F4A1} Use these tokens to test MPP payments:");
|
|
2547
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain tempo_moderato
|
|
2548
|
+
`);
|
|
2549
|
+
} else {
|
|
2550
|
+
console.log(`\u274C ${result.error || "Faucet request failed"}`);
|
|
2551
|
+
console.log("\n Try again later or use Tempo Wallet: https://wallet.tempo.xyz\n");
|
|
2552
|
+
}
|
|
2553
|
+
} catch (error) {
|
|
2554
|
+
console.log(`\u274C ${error.message}`);
|
|
2555
|
+
console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
|
|
2556
|
+
}
|
|
2557
|
+
} else {
|
|
2558
|
+
console.log(` Requesting 1 USDC on Base Sepolia...`);
|
|
2559
|
+
console.log(` Address: ${address}
|
|
2560
|
+
`);
|
|
2561
|
+
try {
|
|
2562
|
+
const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
|
|
2563
|
+
const response = await fetch(FAUCET_API, {
|
|
2564
|
+
method: "POST",
|
|
2565
|
+
headers: { "Content-Type": "application/json" },
|
|
2566
|
+
body: JSON.stringify({ address })
|
|
2567
|
+
});
|
|
2568
|
+
const result = await response.json();
|
|
2569
|
+
if (!response.ok) {
|
|
2570
|
+
console.log(`\u274C ${result.error || "Request failed"}`);
|
|
2571
|
+
if (result.hint) console.log(` ${result.hint}`);
|
|
2572
|
+
if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
|
|
2573
|
+
return;
|
|
2574
|
+
}
|
|
2575
|
+
console.log(`\u2705 Received ${result.amount} USDC!
|
|
2576
|
+
`);
|
|
2577
|
+
console.log(` Transaction: ${result.transaction}`);
|
|
2578
|
+
console.log(` Explorer: ${result.explorer}`);
|
|
2579
|
+
console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
|
|
2580
|
+
`);
|
|
2581
|
+
console.log("\u{1F4A1} Use this USDC to test x402 payments:");
|
|
2582
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
|
|
2583
|
+
`);
|
|
2584
|
+
} catch (error) {
|
|
2585
|
+
console.log(`\u274C ${error.message}`);
|
|
2586
|
+
}
|
|
2587
|
+
}
|
|
2588
|
+
});
|
|
1962
2589
|
program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
|
|
1963
2590
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
1964
2591
|
if (!client.isInitialized) {
|
|
@@ -1988,8 +2615,26 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
1988
2615
|
console.log("");
|
|
1989
2616
|
console.log(" Balances:");
|
|
1990
2617
|
for (const [chainName, balance] of Object.entries(allBalances)) {
|
|
1991
|
-
|
|
1992
|
-
|
|
2618
|
+
let chainLabel;
|
|
2619
|
+
if (chainName === "base_sepolia") {
|
|
2620
|
+
chainLabel = "Base Sepolia";
|
|
2621
|
+
} else if (chainName === "tempo_moderato") {
|
|
2622
|
+
chainLabel = "Tempo Moderato";
|
|
2623
|
+
} else {
|
|
2624
|
+
chainLabel = chainName.charAt(0).toUpperCase() + chainName.slice(1);
|
|
2625
|
+
}
|
|
2626
|
+
if (chainName === "tempo_moderato" && balance.tempo) {
|
|
2627
|
+
const tempo = balance.tempo;
|
|
2628
|
+
const nativeStr = balance.native > 1e12 ? balance.native.toExponential(2) : balance.native.toFixed(2);
|
|
2629
|
+
console.log(` ${chainLabel}:`);
|
|
2630
|
+
console.log(` Native: ${nativeStr} TEMPO (for gas)`);
|
|
2631
|
+
console.log(` pathUSD: ${tempo.pathUSD.toFixed(2)}`);
|
|
2632
|
+
console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
|
|
2633
|
+
console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
|
|
2634
|
+
console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
|
|
2635
|
+
} else {
|
|
2636
|
+
console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
|
|
2637
|
+
}
|
|
1993
2638
|
}
|
|
1994
2639
|
console.log("");
|
|
1995
2640
|
console.log(" Spending Limits:");
|
|
@@ -1998,7 +2643,7 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
1998
2643
|
console.log("");
|
|
1999
2644
|
}
|
|
2000
2645
|
});
|
|
2001
|
-
program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
2646
|
+
program.command("list").description("List recent transactions").option("--days <n>", "Number of days to look back", "7").option("--chain <chain>", "Chain to query (base, polygon, base_sepolia, or all)", "all").option("--limit <n>", "Max transactions to show", "20").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
2002
2647
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2003
2648
|
if (!client.isInitialized) {
|
|
2004
2649
|
console.log("\u274C Not initialized. Run: npx moltspay init");
|
|
@@ -2007,8 +2652,8 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2007
2652
|
const days = parseInt(options.days) || 7;
|
|
2008
2653
|
const limit = parseInt(options.limit) || 20;
|
|
2009
2654
|
const chain = options.chain?.toLowerCase() || "all";
|
|
2010
|
-
if (!["base", "polygon", "all"].includes(chain)) {
|
|
2011
|
-
console.log("\u274C Invalid chain. Use: base, polygon, or all");
|
|
2655
|
+
if (!["base", "polygon", "base_sepolia", "tempo_moderato", "all"].includes(chain)) {
|
|
2656
|
+
console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, tempo_moderato, or all");
|
|
2012
2657
|
return;
|
|
2013
2658
|
}
|
|
2014
2659
|
const wallet = client.address;
|
|
@@ -2023,9 +2668,21 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2023
2668
|
api: "https://polygon.blockscout.com/api/v2",
|
|
2024
2669
|
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
2025
2670
|
name: "Polygon"
|
|
2671
|
+
},
|
|
2672
|
+
base_sepolia: {
|
|
2673
|
+
api: "https://base-sepolia.blockscout.com/api/v2",
|
|
2674
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
2675
|
+
name: "Base Sepolia"
|
|
2676
|
+
},
|
|
2677
|
+
// Tempo explorer doesn't have public API yet
|
|
2678
|
+
tempo_moderato: {
|
|
2679
|
+
api: "",
|
|
2680
|
+
// No API available
|
|
2681
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
2682
|
+
name: "Tempo Moderato"
|
|
2026
2683
|
}
|
|
2027
2684
|
};
|
|
2028
|
-
const chainsToQuery = chain === "all" ? ["base", "polygon"] : [chain];
|
|
2685
|
+
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia", "tempo_moderato"] : [chain];
|
|
2029
2686
|
console.log(`
|
|
2030
2687
|
\u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
|
|
2031
2688
|
`);
|
|
@@ -2033,27 +2690,136 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2033
2690
|
for (const c of chainsToQuery) {
|
|
2034
2691
|
const explorer = explorers[c];
|
|
2035
2692
|
try {
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2693
|
+
if (c === "tempo_moderato") {
|
|
2694
|
+
const tempoTokens = [
|
|
2695
|
+
{ address: "0x20c0000000000000000000000000000000000000", name: "pathUSD" },
|
|
2696
|
+
{ address: "0x20c0000000000000000000000000000000000001", name: "alphaUSD" },
|
|
2697
|
+
{ address: "0x20c0000000000000000000000000000000000002", name: "betaUSD" },
|
|
2698
|
+
{ address: "0x20c0000000000000000000000000000000000003", name: "thetaUSD" }
|
|
2699
|
+
];
|
|
2700
|
+
const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
2701
|
+
const walletTopic = "0x000000000000000000000000" + wallet.toLowerCase().slice(2);
|
|
2702
|
+
let latestBlock = 0;
|
|
2703
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2704
|
+
try {
|
|
2705
|
+
const blockRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2706
|
+
method: "POST",
|
|
2707
|
+
headers: { "Content-Type": "application/json" },
|
|
2708
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 })
|
|
2709
|
+
});
|
|
2710
|
+
const blockData = await blockRes.json();
|
|
2711
|
+
if (blockData.result) {
|
|
2712
|
+
latestBlock = parseInt(blockData.result, 16);
|
|
2713
|
+
break;
|
|
2714
|
+
}
|
|
2715
|
+
} catch (e) {
|
|
2716
|
+
if (attempt === 2) throw e;
|
|
2717
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
if (latestBlock === 0) {
|
|
2721
|
+
console.log(" \u26A0\uFE0F Tempo Moderato: Could not get latest block");
|
|
2722
|
+
continue;
|
|
2723
|
+
}
|
|
2724
|
+
const maxBlocks = 1e5;
|
|
2725
|
+
const blocksPerDay = 172800;
|
|
2726
|
+
const requestedBlocks = blocksPerDay * days;
|
|
2727
|
+
const actualBlocks = Math.min(requestedBlocks, maxBlocks);
|
|
2728
|
+
const fromBlock = "0x" + Math.max(0, latestBlock - actualBlocks).toString(16);
|
|
2729
|
+
const toBlock = "0x" + latestBlock.toString(16);
|
|
2730
|
+
if (requestedBlocks > maxBlocks) {
|
|
2731
|
+
console.log(` \u2139\uFE0F Tempo: querying last ~14 hours (RPC limit: 100k blocks)`);
|
|
2732
|
+
}
|
|
2733
|
+
for (const token of tempoTokens) {
|
|
2734
|
+
try {
|
|
2735
|
+
const inRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2736
|
+
method: "POST",
|
|
2737
|
+
headers: { "Content-Type": "application/json" },
|
|
2738
|
+
body: JSON.stringify({
|
|
2739
|
+
jsonrpc: "2.0",
|
|
2740
|
+
method: "eth_getLogs",
|
|
2741
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, null, walletTopic] }],
|
|
2742
|
+
id: 1
|
|
2743
|
+
})
|
|
2744
|
+
});
|
|
2745
|
+
const inData = await inRes.json();
|
|
2746
|
+
if (inData.error) {
|
|
2747
|
+
console.log(` \u26A0\uFE0F ${token.name}: ${inData.error.message}`);
|
|
2748
|
+
continue;
|
|
2749
|
+
}
|
|
2750
|
+
if (inData.result && Array.isArray(inData.result)) {
|
|
2751
|
+
for (const log of inData.result) {
|
|
2752
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2753
|
+
if (timestamp < cutoffTime) continue;
|
|
2754
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2755
|
+
const from = "0x" + log.topics[1].slice(26);
|
|
2756
|
+
allTxns.push({
|
|
2757
|
+
chain: c,
|
|
2758
|
+
timestamp,
|
|
2759
|
+
type: "IN",
|
|
2760
|
+
amount,
|
|
2761
|
+
other: from,
|
|
2762
|
+
hash: log.transactionHash,
|
|
2763
|
+
token: token.name
|
|
2764
|
+
});
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const outRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2768
|
+
method: "POST",
|
|
2769
|
+
headers: { "Content-Type": "application/json" },
|
|
2770
|
+
body: JSON.stringify({
|
|
2771
|
+
jsonrpc: "2.0",
|
|
2772
|
+
method: "eth_getLogs",
|
|
2773
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, walletTopic, null] }],
|
|
2774
|
+
id: 1
|
|
2775
|
+
})
|
|
2776
|
+
});
|
|
2777
|
+
const outData = await outRes.json();
|
|
2778
|
+
if (outData.result && Array.isArray(outData.result)) {
|
|
2779
|
+
for (const log of outData.result) {
|
|
2780
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2781
|
+
if (timestamp < cutoffTime) continue;
|
|
2782
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2783
|
+
const to = "0x" + log.topics[2].slice(26);
|
|
2784
|
+
allTxns.push({
|
|
2785
|
+
chain: c,
|
|
2786
|
+
timestamp,
|
|
2787
|
+
type: "OUT",
|
|
2788
|
+
amount,
|
|
2789
|
+
other: to,
|
|
2790
|
+
hash: log.transactionHash,
|
|
2791
|
+
token: token.name
|
|
2792
|
+
});
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
} catch (tokenError) {
|
|
2796
|
+
continue;
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
} else {
|
|
2800
|
+
const url = `${explorer.api}/addresses/${wallet}/token-transfers?type=ERC-20&token=${explorer.usdc}`;
|
|
2801
|
+
const response = await fetch(url);
|
|
2802
|
+
const data = await response.json();
|
|
2803
|
+
if (data.items && Array.isArray(data.items)) {
|
|
2804
|
+
for (const tx of data.items) {
|
|
2805
|
+
const timestamp = new Date(tx.timestamp).getTime();
|
|
2806
|
+
if (timestamp < cutoffTime) continue;
|
|
2807
|
+
const isIncoming = tx.to.hash.toLowerCase() === wallet.toLowerCase();
|
|
2808
|
+
const decimals = parseInt(tx.total.decimals) || 6;
|
|
2809
|
+
allTxns.push({
|
|
2810
|
+
chain: c,
|
|
2811
|
+
timestamp,
|
|
2812
|
+
type: isIncoming ? "IN" : "OUT",
|
|
2813
|
+
amount: parseInt(tx.total.value) / Math.pow(10, decimals),
|
|
2814
|
+
other: isIncoming ? tx.from.hash : tx.to.hash,
|
|
2815
|
+
hash: tx.transaction_hash
|
|
2816
|
+
});
|
|
2817
|
+
}
|
|
2053
2818
|
}
|
|
2054
2819
|
}
|
|
2055
2820
|
} catch (error) {
|
|
2056
|
-
|
|
2821
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2822
|
+
console.log(` \u26A0\uFE0F ${explorer.name}: ${errMsg}`);
|
|
2057
2823
|
}
|
|
2058
2824
|
}
|
|
2059
2825
|
allTxns.sort((a, b) => b.timestamp - a.timestamp);
|
|
@@ -2066,8 +2832,12 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2066
2832
|
const color = tx.type === "IN" ? "\x1B[32m" : "\x1B[31m";
|
|
2067
2833
|
const reset = "\x1B[0m";
|
|
2068
2834
|
const date = new Date(tx.timestamp).toISOString().slice(5, 16).replace("T", " ");
|
|
2069
|
-
|
|
2070
|
-
|
|
2835
|
+
let chainLabel = tx.chain.toUpperCase();
|
|
2836
|
+
if (tx.chain === "tempo_moderato") chainLabel = "TEMPO";
|
|
2837
|
+
else if (tx.chain === "base_sepolia") chainLabel = "BASE_SEPOLIA";
|
|
2838
|
+
const chainTag = chain === "all" ? `[${chainLabel}] ` : "";
|
|
2839
|
+
const tokenName = tx.token || "USDC";
|
|
2840
|
+
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}`);
|
|
2071
2841
|
}
|
|
2072
2842
|
const inTotal = allTxns.filter((t) => t.type === "IN").reduce((s, t) => s + t.amount, 0);
|
|
2073
2843
|
const outTotal = allTxns.filter((t) => t.type === "OUT").reduce((s, t) => s + t.amount, 0);
|
|
@@ -2076,39 +2846,88 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2076
2846
|
`);
|
|
2077
2847
|
}
|
|
2078
2848
|
});
|
|
2079
|
-
program.command("services
|
|
2849
|
+
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) => {
|
|
2850
|
+
const MOLTSPAY_REGISTRY = "https://moltspay.com";
|
|
2080
2851
|
try {
|
|
2081
|
-
|
|
2082
|
-
|
|
2852
|
+
let services;
|
|
2853
|
+
let isRegistry = false;
|
|
2854
|
+
if (url) {
|
|
2855
|
+
const client = new MoltsPayClient();
|
|
2856
|
+
services = await client.getServices(url);
|
|
2857
|
+
} else {
|
|
2858
|
+
isRegistry = true;
|
|
2859
|
+
const params = new URLSearchParams();
|
|
2860
|
+
if (options.query) params.set("q", options.query);
|
|
2861
|
+
if (options.maxPrice) params.set("maxPrice", options.maxPrice);
|
|
2862
|
+
if (options.type) params.set("type", options.type);
|
|
2863
|
+
if (options.tag) params.set("tag", options.tag);
|
|
2864
|
+
const queryString = params.toString();
|
|
2865
|
+
const registryUrl = `${MOLTSPAY_REGISTRY}/registry/services${queryString ? "?" + queryString : ""}`;
|
|
2866
|
+
const res = await fetch(registryUrl);
|
|
2867
|
+
if (!res.ok) {
|
|
2868
|
+
throw new Error(`Registry request failed: ${res.status}`);
|
|
2869
|
+
}
|
|
2870
|
+
services = await res.json();
|
|
2871
|
+
}
|
|
2083
2872
|
if (options.json) {
|
|
2084
2873
|
console.log(JSON.stringify(services, null, 2));
|
|
2085
2874
|
} else {
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2875
|
+
const serviceList = services.services || [];
|
|
2876
|
+
if (isRegistry) {
|
|
2877
|
+
if (options.query) {
|
|
2878
|
+
console.log(`
|
|
2879
|
+
\u{1F50D} Search: "${options.query}" (${serviceList.length} results)
|
|
2880
|
+
`);
|
|
2881
|
+
} else {
|
|
2882
|
+
const filters = [];
|
|
2883
|
+
if (options.maxPrice) filters.push(`max $${options.maxPrice}`);
|
|
2884
|
+
if (options.type) filters.push(options.type);
|
|
2885
|
+
if (options.tag) filters.push(`#${options.tag}`);
|
|
2886
|
+
const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
|
|
2887
|
+
console.log(`
|
|
2888
|
+
\u{1F50D} MoltsPay Registry${filterStr} - ${serviceList.length} services
|
|
2089
2889
|
`);
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2890
|
+
}
|
|
2891
|
+
for (const svc of serviceList) {
|
|
2892
|
+
const name = (svc.name || svc.id).slice(0, 30).padEnd(30);
|
|
2893
|
+
const price = `$${svc.price}`.padEnd(8);
|
|
2894
|
+
const type = (svc.type || "unknown").padEnd(14);
|
|
2895
|
+
const provider = `@${svc.provider?.username || "unknown"}`;
|
|
2896
|
+
console.log(` ${name} ${price} ${type} ${provider}`);
|
|
2897
|
+
}
|
|
2898
|
+
if (serviceList.length > 0) {
|
|
2899
|
+
console.log(`
|
|
2900
|
+
\u{1F4A1} Use: moltspay pay <provider-url> <service-id>
|
|
2901
|
+
`);
|
|
2902
|
+
}
|
|
2094
2903
|
} else {
|
|
2095
|
-
|
|
2096
|
-
|
|
2904
|
+
if (services.provider) {
|
|
2905
|
+
console.log(`
|
|
2906
|
+
\u{1F3EA} ${services.provider.name}
|
|
2907
|
+
`);
|
|
2908
|
+
console.log(` ${services.provider.description || ""}`);
|
|
2909
|
+
console.log(` Wallet: ${services.provider.wallet}`);
|
|
2910
|
+
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";
|
|
2911
|
+
console.log(` Chains: ${chains}`);
|
|
2912
|
+
} else {
|
|
2913
|
+
console.log(`
|
|
2914
|
+
\u{1F3EA} Provider Services
|
|
2097
2915
|
`);
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
console.log("\n\u{1F4E6} Services:\n");
|
|
2101
|
-
for (const svc of services.services) {
|
|
2102
|
-
const status = svc.available !== false ? "\u2705" : "\u274C";
|
|
2103
|
-
console.log(` ${status} ${svc.id || svc.name}`);
|
|
2104
|
-
console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
|
|
2105
|
-
if (svc.description) {
|
|
2106
|
-
console.log(` ${svc.description}`);
|
|
2916
|
+
console.log(` ${serviceList.length} services available`);
|
|
2107
2917
|
}
|
|
2108
|
-
|
|
2109
|
-
|
|
2918
|
+
console.log("\n\u{1F4E6} Services:\n");
|
|
2919
|
+
for (const svc of serviceList) {
|
|
2920
|
+
const status = svc.available !== false ? "\u2705" : "\u274C";
|
|
2921
|
+
console.log(` ${status} ${svc.id || svc.name}`);
|
|
2922
|
+
console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
|
|
2923
|
+
if (svc.description) {
|
|
2924
|
+
console.log(` ${svc.description}`);
|
|
2925
|
+
}
|
|
2926
|
+
if (svc.provider && !services.provider) {
|
|
2927
|
+
console.log(` Provider: ${svc.provider.name || svc.provider.username}`);
|
|
2928
|
+
}
|
|
2929
|
+
console.log("");
|
|
2110
2930
|
}
|
|
2111
|
-
console.log("");
|
|
2112
2931
|
}
|
|
2113
2932
|
}
|
|
2114
2933
|
} catch (err) {
|
|
@@ -2322,8 +3141,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
|
|
|
2322
3141
|
process.exit(1);
|
|
2323
3142
|
}
|
|
2324
3143
|
});
|
|
2325
|
-
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base or
|
|
2326
|
-
const client = new MoltsPayClient();
|
|
3144
|
+
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) => {
|
|
3145
|
+
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2327
3146
|
if (!client.isInitialized) {
|
|
2328
3147
|
console.error("\u274C Wallet not initialized. Run: npx moltspay init");
|
|
2329
3148
|
process.exit(1);
|
|
@@ -2352,13 +3171,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2352
3171
|
params.image_base64 = imageData.toString("base64");
|
|
2353
3172
|
}
|
|
2354
3173
|
}
|
|
2355
|
-
if (!params.prompt) {
|
|
2356
|
-
console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
|
|
2357
|
-
process.exit(1);
|
|
2358
|
-
}
|
|
2359
3174
|
const chain = options.chain?.toLowerCase();
|
|
2360
|
-
if (chain && !["base", "polygon"].includes(chain)) {
|
|
2361
|
-
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon`);
|
|
3175
|
+
if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
3176
|
+
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
|
|
2362
3177
|
process.exit(1);
|
|
2363
3178
|
}
|
|
2364
3179
|
const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
|
|
@@ -2389,10 +3204,22 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2389
3204
|
console.log("");
|
|
2390
3205
|
}
|
|
2391
3206
|
try {
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
3207
|
+
let result;
|
|
3208
|
+
if (chain === "tempo_moderato") {
|
|
3209
|
+
if (!options.json) {
|
|
3210
|
+
console.log(" Protocol: MPP (Machine Payments Protocol)");
|
|
3211
|
+
console.log("");
|
|
3212
|
+
}
|
|
3213
|
+
const mppUrl = server.includes(service) ? server : `${server}/${service}`;
|
|
3214
|
+
result = await client.payWithMPP(mppUrl, {
|
|
3215
|
+
body: params
|
|
3216
|
+
});
|
|
3217
|
+
} else {
|
|
3218
|
+
result = await client.pay(server, service, params, {
|
|
3219
|
+
token,
|
|
3220
|
+
chain
|
|
3221
|
+
});
|
|
3222
|
+
}
|
|
2396
3223
|
if (options.json) {
|
|
2397
3224
|
console.log(JSON.stringify(result));
|
|
2398
3225
|
} else {
|