moltspay 1.2.1 → 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 +89 -14
- package/dist/cdp/index.d.mts +1 -1
- package/dist/cdp/index.d.ts +1 -1
- package/dist/cdp/index.js +53 -30368
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs +37 -30360
- 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 +29 -0
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +29 -0
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +863 -109
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +867 -109
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +25 -2
- package/dist/client/index.d.ts +25 -2
- package/dist/client/index.js +195 -12
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +185 -12
- package/dist/client/index.mjs.map +1 -1
- package/dist/facilitators/index.d.mts +15 -53
- package/dist/facilitators/index.d.ts +15 -53
- package/dist/facilitators/index.js +234 -1
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +233 -1
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/{index-DgJPZMBG.d.mts → index-On9ZaGDW.d.mts} +1 -1
- package/dist/{index-DgJPZMBG.d.ts → index-On9ZaGDW.d.ts} +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +718 -30584
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +711 -30587
- 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 +443 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +443 -5
- 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 +29 -0
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs +29 -0
- 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 +29 -0
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +29 -0
- 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";
|
|
@@ -107,6 +112,35 @@ var CHAINS = {
|
|
|
107
112
|
explorer: "https://sepolia.basescan.org/address/",
|
|
108
113
|
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
109
114
|
avgBlockTime: 2
|
|
115
|
+
},
|
|
116
|
+
// ============ Tempo Testnet (Moderato) ============
|
|
117
|
+
tempo_moderato: {
|
|
118
|
+
name: "Tempo Moderato",
|
|
119
|
+
chainId: 42431,
|
|
120
|
+
rpc: "https://rpc.moderato.tempo.xyz",
|
|
121
|
+
tokens: {
|
|
122
|
+
// TIP-20 stablecoins on Tempo testnet (from mppx SDK)
|
|
123
|
+
// Note: Tempo uses USD as native gas token, not ETH
|
|
124
|
+
USDC: {
|
|
125
|
+
address: "0x20c0000000000000000000000000000000000000",
|
|
126
|
+
// pathUSD - primary testnet stablecoin
|
|
127
|
+
decimals: 6,
|
|
128
|
+
symbol: "USDC",
|
|
129
|
+
eip712Name: "pathUSD"
|
|
130
|
+
},
|
|
131
|
+
USDT: {
|
|
132
|
+
address: "0x20c0000000000000000000000000000000000001",
|
|
133
|
+
// alphaUSD
|
|
134
|
+
decimals: 6,
|
|
135
|
+
symbol: "USDT",
|
|
136
|
+
eip712Name: "alphaUSD"
|
|
137
|
+
}
|
|
138
|
+
},
|
|
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
|
|
110
144
|
}
|
|
111
145
|
};
|
|
112
146
|
function getChain(name) {
|
|
@@ -545,30 +579,59 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
|
545
579
|
};
|
|
546
580
|
}
|
|
547
581
|
/**
|
|
548
|
-
* Get wallet balances on all supported chains (Base + Polygon)
|
|
582
|
+
* Get wallet balances on all supported chains (Base + Polygon + Tempo)
|
|
549
583
|
*/
|
|
550
584
|
async getAllBalances() {
|
|
551
585
|
if (!this.wallet) {
|
|
552
586
|
throw new Error("Client not initialized");
|
|
553
587
|
}
|
|
554
|
-
const supportedChains = ["base", "polygon", "base_sepolia"];
|
|
588
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
555
589
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
556
590
|
const results = {};
|
|
591
|
+
const tempoTokens = {
|
|
592
|
+
pathUSD: "0x20c0000000000000000000000000000000000000",
|
|
593
|
+
alphaUSD: "0x20c0000000000000000000000000000000000001",
|
|
594
|
+
betaUSD: "0x20c0000000000000000000000000000000000002",
|
|
595
|
+
thetaUSD: "0x20c0000000000000000000000000000000000003"
|
|
596
|
+
};
|
|
557
597
|
await Promise.all(
|
|
558
598
|
supportedChains.map(async (chainName) => {
|
|
559
599
|
try {
|
|
560
600
|
const chain = getChain(chainName);
|
|
561
601
|
const provider = new ethers.JsonRpcProvider(chain.rpc);
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
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
|
+
}
|
|
572
635
|
} catch (err) {
|
|
573
636
|
results[chainName] = { usdc: 0, usdt: 0, native: 0 };
|
|
574
637
|
}
|
|
@@ -576,6 +639,121 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
|
576
639
|
);
|
|
577
640
|
return results;
|
|
578
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
|
+
}
|
|
579
757
|
};
|
|
580
758
|
|
|
581
759
|
// src/server/index.ts
|
|
@@ -815,6 +993,127 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
815
993
|
}
|
|
816
994
|
};
|
|
817
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;
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
|
|
818
1117
|
// src/facilitators/registry.ts
|
|
819
1118
|
init_esm_shims();
|
|
820
1119
|
var FacilitatorRegistry = class {
|
|
@@ -824,7 +1123,8 @@ var FacilitatorRegistry = class {
|
|
|
824
1123
|
roundRobinIndex = 0;
|
|
825
1124
|
constructor(selection) {
|
|
826
1125
|
this.registerFactory("cdp", (config) => new CDPFacilitator(config));
|
|
827
|
-
this.
|
|
1126
|
+
this.registerFactory("tempo", () => new TempoFacilitator());
|
|
1127
|
+
this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
|
|
828
1128
|
}
|
|
829
1129
|
/**
|
|
830
1130
|
* Register a new facilitator factory
|
|
@@ -1048,6 +1348,9 @@ var X402_VERSION3 = 2;
|
|
|
1048
1348
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
1049
1349
|
var PAYMENT_HEADER2 = "x-payment";
|
|
1050
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";
|
|
1051
1354
|
var TOKEN_ADDRESSES = {
|
|
1052
1355
|
"eip155:8453": {
|
|
1053
1356
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -1061,12 +1364,20 @@ var TOKEN_ADDRESSES = {
|
|
|
1061
1364
|
"eip155:137": {
|
|
1062
1365
|
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
1063
1366
|
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
|
|
1367
|
+
},
|
|
1368
|
+
"eip155:42431": {
|
|
1369
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1370
|
+
USDC: "0x20c0000000000000000000000000000000000000",
|
|
1371
|
+
// pathUSD
|
|
1372
|
+
USDT: "0x20c0000000000000000000000000000000000001"
|
|
1373
|
+
// alphaUSD
|
|
1064
1374
|
}
|
|
1065
1375
|
};
|
|
1066
1376
|
var CHAIN_TO_NETWORK = {
|
|
1067
1377
|
"base": "eip155:8453",
|
|
1068
1378
|
"base_sepolia": "eip155:84532",
|
|
1069
|
-
"polygon": "eip155:137"
|
|
1379
|
+
"polygon": "eip155:137",
|
|
1380
|
+
"tempo_moderato": "eip155:42431"
|
|
1070
1381
|
};
|
|
1071
1382
|
var TOKEN_DOMAINS = {
|
|
1072
1383
|
// Base mainnet
|
|
@@ -1084,6 +1395,11 @@ var TOKEN_DOMAINS = {
|
|
|
1084
1395
|
"eip155:137": {
|
|
1085
1396
|
USDC: { name: "USD Coin", version: "2" },
|
|
1086
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" }
|
|
1087
1403
|
}
|
|
1088
1404
|
};
|
|
1089
1405
|
function getTokenDomain(network, token) {
|
|
@@ -1141,9 +1457,11 @@ var MoltsPayServer = class {
|
|
|
1141
1457
|
};
|
|
1142
1458
|
this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
1143
1459
|
this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
|
|
1460
|
+
const defaultFallback = ["tempo"];
|
|
1461
|
+
const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
|
|
1144
1462
|
const facilitatorConfig = options.facilitators || {
|
|
1145
1463
|
primary: process.env.FACILITATOR_PRIMARY || "cdp",
|
|
1146
|
-
fallback:
|
|
1464
|
+
fallback: envFallback || defaultFallback,
|
|
1147
1465
|
strategy: process.env.FACILITATOR_STRATEGY || "failover",
|
|
1148
1466
|
config: {
|
|
1149
1467
|
cdp: { useMainnet: this.useMainnet }
|
|
@@ -1237,8 +1555,8 @@ var MoltsPayServer = class {
|
|
|
1237
1555
|
async handleRequest(req, res) {
|
|
1238
1556
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1239
1557
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1240
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
1241
|
-
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");
|
|
1242
1560
|
if (req.method === "OPTIONS") {
|
|
1243
1561
|
res.writeHead(204);
|
|
1244
1562
|
res.end();
|
|
@@ -1269,6 +1587,14 @@ var MoltsPayServer = class {
|
|
|
1269
1587
|
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1270
1588
|
return await this.handleProxy(body, paymentHeader, res);
|
|
1271
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
|
+
}
|
|
1272
1598
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1273
1599
|
} catch (err) {
|
|
1274
1600
|
console.error("[MoltsPay] Error:", err);
|
|
@@ -1452,6 +1778,187 @@ var MoltsPayServer = class {
|
|
|
1452
1778
|
payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
|
|
1453
1779
|
}, responseHeaders);
|
|
1454
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
|
+
}
|
|
1455
1962
|
/**
|
|
1456
1963
|
* Return 402 with x402 payment requirements (v2 format)
|
|
1457
1964
|
* Includes requirements for all chains and all accepted currencies
|
|
@@ -1826,6 +2333,26 @@ async function printQRCode(url) {
|
|
|
1826
2333
|
|
|
1827
2334
|
// src/cli/index.ts
|
|
1828
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
|
+
}
|
|
1829
2356
|
var program = new Command();
|
|
1830
2357
|
var DEFAULT_CONFIG_DIR = join4(homedir2(), ".moltspay");
|
|
1831
2358
|
var PID_FILE = join4(DEFAULT_CONFIG_DIR, "server.pid");
|
|
@@ -1844,7 +2371,7 @@ function prompt(question) {
|
|
|
1844
2371
|
});
|
|
1845
2372
|
});
|
|
1846
2373
|
}
|
|
1847
|
-
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(
|
|
2374
|
+
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
|
|
1848
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) => {
|
|
1849
2376
|
console.log("\n\u{1F510} MoltsPay Client Setup\n");
|
|
1850
2377
|
if (existsSync4(join4(options.configDir, "wallet.json"))) {
|
|
@@ -1853,7 +2380,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
|
|
|
1853
2380
|
return;
|
|
1854
2381
|
}
|
|
1855
2382
|
let chain = options.chain;
|
|
1856
|
-
const supportedChains = ["base", "polygon", "base_sepolia"];
|
|
2383
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
1857
2384
|
if (!supportedChains.includes(chain)) {
|
|
1858
2385
|
console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
|
|
1859
2386
|
process.exit(1);
|
|
@@ -1941,11 +2468,9 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1941
2468
|
console.log(` Wallet: ${client.address}`);
|
|
1942
2469
|
console.log(` Chain: Base Sepolia (testnet)
|
|
1943
2470
|
`);
|
|
1944
|
-
console.log("\u{
|
|
1945
|
-
console.log("
|
|
1946
|
-
console.log("
|
|
1947
|
-
console.log(`\u{1F4A1} Send USDC to: ${client.address}
|
|
1948
|
-
`);
|
|
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");
|
|
1949
2474
|
return;
|
|
1950
2475
|
}
|
|
1951
2476
|
console.log("\n\u{1F4B3} Fund your agent wallet\n");
|
|
@@ -1977,8 +2502,13 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1977
2502
|
console.log(`\u274C ${error.message}`);
|
|
1978
2503
|
}
|
|
1979
2504
|
});
|
|
1980
|
-
program.command("faucet").description("Request testnet
|
|
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) => {
|
|
1981
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
|
+
}
|
|
1982
2512
|
if (!address) {
|
|
1983
2513
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
1984
2514
|
if (client.isInitialized) {
|
|
@@ -1993,34 +2523,67 @@ program.command("faucet").description("Request testnet USDC from MoltsPay faucet
|
|
|
1993
2523
|
return;
|
|
1994
2524
|
}
|
|
1995
2525
|
console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
|
|
1996
|
-
|
|
1997
|
-
|
|
2526
|
+
if (chain === "tempo_moderato") {
|
|
2527
|
+
console.log(` Requesting testnet tokens on Tempo Moderato...`);
|
|
2528
|
+
console.log(` Address: ${address}
|
|
1998
2529
|
`);
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
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");
|
|
2012
2556
|
}
|
|
2013
|
-
|
|
2557
|
+
} else {
|
|
2558
|
+
console.log(` Requesting 1 USDC on Base Sepolia...`);
|
|
2559
|
+
console.log(` Address: ${address}
|
|
2014
2560
|
`);
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
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!
|
|
2018
2576
|
`);
|
|
2019
|
-
|
|
2020
|
-
|
|
2577
|
+
console.log(` Transaction: ${result.transaction}`);
|
|
2578
|
+
console.log(` Explorer: ${result.explorer}`);
|
|
2579
|
+
console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
|
|
2021
2580
|
`);
|
|
2022
|
-
|
|
2023
|
-
|
|
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
|
+
}
|
|
2024
2587
|
}
|
|
2025
2588
|
});
|
|
2026
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) => {
|
|
@@ -2052,8 +2615,26 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
2052
2615
|
console.log("");
|
|
2053
2616
|
console.log(" Balances:");
|
|
2054
2617
|
for (const [chainName, balance] of Object.entries(allBalances)) {
|
|
2055
|
-
|
|
2056
|
-
|
|
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
|
+
}
|
|
2057
2638
|
}
|
|
2058
2639
|
console.log("");
|
|
2059
2640
|
console.log(" Spending Limits:");
|
|
@@ -2071,8 +2652,8 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2071
2652
|
const days = parseInt(options.days) || 7;
|
|
2072
2653
|
const limit = parseInt(options.limit) || 20;
|
|
2073
2654
|
const chain = options.chain?.toLowerCase() || "all";
|
|
2074
|
-
if (!["base", "polygon", "base_sepolia", "all"].includes(chain)) {
|
|
2075
|
-
console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, 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");
|
|
2076
2657
|
return;
|
|
2077
2658
|
}
|
|
2078
2659
|
const wallet = client.address;
|
|
@@ -2092,9 +2673,16 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2092
2673
|
api: "https://base-sepolia.blockscout.com/api/v2",
|
|
2093
2674
|
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
2094
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"
|
|
2095
2683
|
}
|
|
2096
2684
|
};
|
|
2097
|
-
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia"] : [chain];
|
|
2685
|
+
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia", "tempo_moderato"] : [chain];
|
|
2098
2686
|
console.log(`
|
|
2099
2687
|
\u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
|
|
2100
2688
|
`);
|
|
@@ -2102,27 +2690,136 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2102
2690
|
for (const c of chainsToQuery) {
|
|
2103
2691
|
const explorer = explorers[c];
|
|
2104
2692
|
try {
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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
|
+
}
|
|
2122
2818
|
}
|
|
2123
2819
|
}
|
|
2124
2820
|
} catch (error) {
|
|
2125
|
-
|
|
2821
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2822
|
+
console.log(` \u26A0\uFE0F ${explorer.name}: ${errMsg}`);
|
|
2126
2823
|
}
|
|
2127
2824
|
}
|
|
2128
2825
|
allTxns.sort((a, b) => b.timestamp - a.timestamp);
|
|
@@ -2135,8 +2832,12 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2135
2832
|
const color = tx.type === "IN" ? "\x1B[32m" : "\x1B[31m";
|
|
2136
2833
|
const reset = "\x1B[0m";
|
|
2137
2834
|
const date = new Date(tx.timestamp).toISOString().slice(5, 16).replace("T", " ");
|
|
2138
|
-
|
|
2139
|
-
|
|
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}`);
|
|
2140
2841
|
}
|
|
2141
2842
|
const inTotal = allTxns.filter((t) => t.type === "IN").reduce((s, t) => s + t.amount, 0);
|
|
2142
2843
|
const outTotal = allTxns.filter((t) => t.type === "OUT").reduce((s, t) => s + t.amount, 0);
|
|
@@ -2145,39 +2846,88 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2145
2846
|
`);
|
|
2146
2847
|
}
|
|
2147
2848
|
});
|
|
2148
|
-
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";
|
|
2149
2851
|
try {
|
|
2150
|
-
|
|
2151
|
-
|
|
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
|
+
}
|
|
2152
2872
|
if (options.json) {
|
|
2153
2873
|
console.log(JSON.stringify(services, null, 2));
|
|
2154
2874
|
} else {
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2875
|
+
const serviceList = services.services || [];
|
|
2876
|
+
if (isRegistry) {
|
|
2877
|
+
if (options.query) {
|
|
2878
|
+
console.log(`
|
|
2879
|
+
\u{1F50D} Search: "${options.query}" (${serviceList.length} results)
|
|
2158
2880
|
`);
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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
|
|
2889
|
+
`);
|
|
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
|
+
}
|
|
2163
2903
|
} else {
|
|
2164
|
-
|
|
2165
|
-
|
|
2904
|
+
if (services.provider) {
|
|
2905
|
+
console.log(`
|
|
2906
|
+
\u{1F3EA} ${services.provider.name}
|
|
2166
2907
|
`);
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
console.log(`
|
|
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
|
|
2915
|
+
`);
|
|
2916
|
+
console.log(` ${serviceList.length} services available`);
|
|
2176
2917
|
}
|
|
2177
|
-
|
|
2178
|
-
|
|
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("");
|
|
2179
2930
|
}
|
|
2180
|
-
console.log("");
|
|
2181
2931
|
}
|
|
2182
2932
|
}
|
|
2183
2933
|
} catch (err) {
|
|
@@ -2391,8 +3141,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
|
|
|
2391
3141
|
process.exit(1);
|
|
2392
3142
|
}
|
|
2393
3143
|
});
|
|
2394
|
-
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, or
|
|
2395
|
-
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 });
|
|
2396
3146
|
if (!client.isInitialized) {
|
|
2397
3147
|
console.error("\u274C Wallet not initialized. Run: npx moltspay init");
|
|
2398
3148
|
process.exit(1);
|
|
@@ -2421,13 +3171,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2421
3171
|
params.image_base64 = imageData.toString("base64");
|
|
2422
3172
|
}
|
|
2423
3173
|
}
|
|
2424
|
-
if (!params.prompt) {
|
|
2425
|
-
console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
|
|
2426
|
-
process.exit(1);
|
|
2427
|
-
}
|
|
2428
3174
|
const chain = options.chain?.toLowerCase();
|
|
2429
|
-
if (chain && !["base", "polygon", "base_sepolia"].includes(chain)) {
|
|
2430
|
-
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia`);
|
|
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`);
|
|
2431
3177
|
process.exit(1);
|
|
2432
3178
|
}
|
|
2433
3179
|
const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
|
|
@@ -2458,10 +3204,22 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2458
3204
|
console.log("");
|
|
2459
3205
|
}
|
|
2460
3206
|
try {
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
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
|
+
}
|
|
2465
3223
|
if (options.json) {
|
|
2466
3224
|
console.log(JSON.stringify(result));
|
|
2467
3225
|
} else {
|