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.js
CHANGED
|
@@ -35,6 +35,7 @@ var init_cjs_shims = __esm({
|
|
|
35
35
|
|
|
36
36
|
// src/cli/index.ts
|
|
37
37
|
init_cjs_shims();
|
|
38
|
+
var import_crypto = require("crypto");
|
|
38
39
|
var import_commander = require("commander");
|
|
39
40
|
var import_os2 = require("os");
|
|
40
41
|
var import_path2 = require("path");
|
|
@@ -127,6 +128,35 @@ var CHAINS = {
|
|
|
127
128
|
explorer: "https://sepolia.basescan.org/address/",
|
|
128
129
|
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
129
130
|
avgBlockTime: 2
|
|
131
|
+
},
|
|
132
|
+
// ============ Tempo Testnet (Moderato) ============
|
|
133
|
+
tempo_moderato: {
|
|
134
|
+
name: "Tempo Moderato",
|
|
135
|
+
chainId: 42431,
|
|
136
|
+
rpc: "https://rpc.moderato.tempo.xyz",
|
|
137
|
+
tokens: {
|
|
138
|
+
// TIP-20 stablecoins on Tempo testnet (from mppx SDK)
|
|
139
|
+
// Note: Tempo uses USD as native gas token, not ETH
|
|
140
|
+
USDC: {
|
|
141
|
+
address: "0x20c0000000000000000000000000000000000000",
|
|
142
|
+
// pathUSD - primary testnet stablecoin
|
|
143
|
+
decimals: 6,
|
|
144
|
+
symbol: "USDC",
|
|
145
|
+
eip712Name: "pathUSD"
|
|
146
|
+
},
|
|
147
|
+
USDT: {
|
|
148
|
+
address: "0x20c0000000000000000000000000000000000001",
|
|
149
|
+
// alphaUSD
|
|
150
|
+
decimals: 6,
|
|
151
|
+
symbol: "USDT",
|
|
152
|
+
eip712Name: "alphaUSD"
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
156
|
+
explorer: "https://explore.testnet.tempo.xyz/address/",
|
|
157
|
+
explorerTx: "https://explore.testnet.tempo.xyz/tx/",
|
|
158
|
+
avgBlockTime: 0.5
|
|
159
|
+
// ~500ms finality
|
|
130
160
|
}
|
|
131
161
|
};
|
|
132
162
|
function getChain(name) {
|
|
@@ -565,30 +595,59 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
|
565
595
|
};
|
|
566
596
|
}
|
|
567
597
|
/**
|
|
568
|
-
* Get wallet balances on all supported chains (Base + Polygon)
|
|
598
|
+
* Get wallet balances on all supported chains (Base + Polygon + Tempo)
|
|
569
599
|
*/
|
|
570
600
|
async getAllBalances() {
|
|
571
601
|
if (!this.wallet) {
|
|
572
602
|
throw new Error("Client not initialized");
|
|
573
603
|
}
|
|
574
|
-
const supportedChains = ["base", "polygon", "base_sepolia"];
|
|
604
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
575
605
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
576
606
|
const results = {};
|
|
607
|
+
const tempoTokens = {
|
|
608
|
+
pathUSD: "0x20c0000000000000000000000000000000000000",
|
|
609
|
+
alphaUSD: "0x20c0000000000000000000000000000000000001",
|
|
610
|
+
betaUSD: "0x20c0000000000000000000000000000000000002",
|
|
611
|
+
thetaUSD: "0x20c0000000000000000000000000000000000003"
|
|
612
|
+
};
|
|
577
613
|
await Promise.all(
|
|
578
614
|
supportedChains.map(async (chainName) => {
|
|
579
615
|
try {
|
|
580
616
|
const chain = getChain(chainName);
|
|
581
617
|
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
618
|
+
if (chainName === "tempo_moderato") {
|
|
619
|
+
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
620
|
+
provider.getBalance(this.wallet.address),
|
|
621
|
+
new import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
622
|
+
new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
623
|
+
new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
624
|
+
new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
625
|
+
]);
|
|
626
|
+
results[chainName] = {
|
|
627
|
+
usdc: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
|
|
628
|
+
// pathUSD as default USDC
|
|
629
|
+
usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
|
|
630
|
+
// alphaUSD as default USDT
|
|
631
|
+
native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
|
|
632
|
+
tempo: {
|
|
633
|
+
pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
|
|
634
|
+
alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
|
|
635
|
+
betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
|
|
636
|
+
thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
} else {
|
|
640
|
+
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
641
|
+
provider.getBalance(this.wallet.address),
|
|
642
|
+
new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
643
|
+
new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
644
|
+
]);
|
|
645
|
+
results[chainName] = {
|
|
646
|
+
usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
647
|
+
usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
648
|
+
native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
|
|
649
|
+
};
|
|
650
|
+
}
|
|
592
651
|
} catch (err) {
|
|
593
652
|
results[chainName] = { usdc: 0, usdt: 0, native: 0 };
|
|
594
653
|
}
|
|
@@ -596,6 +655,121 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
|
|
|
596
655
|
);
|
|
597
656
|
return results;
|
|
598
657
|
}
|
|
658
|
+
/**
|
|
659
|
+
* Pay for a service using MPP (Machine Payments Protocol)
|
|
660
|
+
*
|
|
661
|
+
* This implements the MPP flow manually for EOA wallets:
|
|
662
|
+
* 1. Request service → get 402 with WWW-Authenticate
|
|
663
|
+
* 2. Parse payment requirements
|
|
664
|
+
* 3. Execute transfer on Tempo chain
|
|
665
|
+
* 4. Retry with transaction hash as credential
|
|
666
|
+
*
|
|
667
|
+
* @param url - Full URL of the MPP-enabled endpoint
|
|
668
|
+
* @param options - Request options (body, headers)
|
|
669
|
+
* @returns Response from the service
|
|
670
|
+
*/
|
|
671
|
+
async payWithMPP(url, options = {}) {
|
|
672
|
+
if (!this.wallet || !this.walletData) {
|
|
673
|
+
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
674
|
+
}
|
|
675
|
+
const { privateKeyToAccount } = await import("viem/accounts");
|
|
676
|
+
const { createWalletClient, createPublicClient, http } = await import("viem");
|
|
677
|
+
const { tempoModerato } = await import("viem/chains");
|
|
678
|
+
const { Actions } = await import("viem/tempo");
|
|
679
|
+
const privateKey = this.walletData.privateKey;
|
|
680
|
+
const account = privateKeyToAccount(privateKey);
|
|
681
|
+
console.log(`[MoltsPay] Making MPP request to: ${url}`);
|
|
682
|
+
console.log(`[MoltsPay] Using account: ${account.address}`);
|
|
683
|
+
const initResponse = await fetch(url, {
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: {
|
|
686
|
+
"Content-Type": "application/json",
|
|
687
|
+
...options.headers
|
|
688
|
+
},
|
|
689
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
690
|
+
});
|
|
691
|
+
if (initResponse.status !== 402) {
|
|
692
|
+
if (initResponse.ok) {
|
|
693
|
+
return initResponse.json();
|
|
694
|
+
}
|
|
695
|
+
const errorText = await initResponse.text();
|
|
696
|
+
throw new Error(`Request failed (${initResponse.status}): ${errorText}`);
|
|
697
|
+
}
|
|
698
|
+
const wwwAuth = initResponse.headers.get("www-authenticate");
|
|
699
|
+
if (!wwwAuth || !wwwAuth.toLowerCase().includes("payment")) {
|
|
700
|
+
throw new Error("No WWW-Authenticate Payment challenge in 402 response");
|
|
701
|
+
}
|
|
702
|
+
console.log(`[MoltsPay] Got 402, parsing payment challenge...`);
|
|
703
|
+
const parseAuthParam = (header, key) => {
|
|
704
|
+
const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
|
|
705
|
+
return match ? match[1] : null;
|
|
706
|
+
};
|
|
707
|
+
const challengeId = parseAuthParam(wwwAuth, "id");
|
|
708
|
+
const method = parseAuthParam(wwwAuth, "method");
|
|
709
|
+
const realm = parseAuthParam(wwwAuth, "realm");
|
|
710
|
+
const requestB64 = parseAuthParam(wwwAuth, "request");
|
|
711
|
+
if (method !== "tempo") {
|
|
712
|
+
throw new Error(`Unsupported payment method: ${method}`);
|
|
713
|
+
}
|
|
714
|
+
if (!requestB64) {
|
|
715
|
+
throw new Error("Missing request in WWW-Authenticate");
|
|
716
|
+
}
|
|
717
|
+
const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
|
|
718
|
+
const paymentRequest = JSON.parse(requestJson);
|
|
719
|
+
console.log(`[MoltsPay] Payment request:`, paymentRequest);
|
|
720
|
+
const { amount, currency, recipient, methodDetails } = paymentRequest;
|
|
721
|
+
const chainId = methodDetails?.chainId || 42431;
|
|
722
|
+
console.log(`[MoltsPay] Executing transfer on Tempo (chainId: ${chainId})...`);
|
|
723
|
+
console.log(`[MoltsPay] Amount: ${amount}, To: ${recipient}`);
|
|
724
|
+
const tempoChain = { ...tempoModerato, feeToken: currency };
|
|
725
|
+
const publicClient = createPublicClient({
|
|
726
|
+
chain: tempoChain,
|
|
727
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
728
|
+
});
|
|
729
|
+
const walletClient = createWalletClient({
|
|
730
|
+
account,
|
|
731
|
+
chain: tempoChain,
|
|
732
|
+
transport: http("https://rpc.moderato.tempo.xyz")
|
|
733
|
+
});
|
|
734
|
+
const txHash = await Actions.token.transfer(walletClient, {
|
|
735
|
+
to: recipient,
|
|
736
|
+
amount: BigInt(amount),
|
|
737
|
+
token: currency
|
|
738
|
+
});
|
|
739
|
+
console.log(`[MoltsPay] Transaction sent: ${txHash}`);
|
|
740
|
+
console.log(`[MoltsPay] Waiting for confirmation...`);
|
|
741
|
+
await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
742
|
+
console.log(`[MoltsPay] Transaction confirmed!`);
|
|
743
|
+
const challenge = {
|
|
744
|
+
id: challengeId,
|
|
745
|
+
realm,
|
|
746
|
+
method: "tempo",
|
|
747
|
+
intent: "charge",
|
|
748
|
+
request: paymentRequest
|
|
749
|
+
};
|
|
750
|
+
const credential = {
|
|
751
|
+
challenge,
|
|
752
|
+
payload: { hash: txHash, type: "hash" },
|
|
753
|
+
source: `did:pkh:eip155:${chainId}:${account.address}`
|
|
754
|
+
};
|
|
755
|
+
const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
756
|
+
console.log(`[MoltsPay] Retrying with payment credential...`);
|
|
757
|
+
const paidResponse = await fetch(url, {
|
|
758
|
+
method: "POST",
|
|
759
|
+
headers: {
|
|
760
|
+
"Content-Type": "application/json",
|
|
761
|
+
"Authorization": `Payment ${credentialB64}`,
|
|
762
|
+
...options.headers
|
|
763
|
+
},
|
|
764
|
+
body: options.body ? JSON.stringify(options.body) : void 0
|
|
765
|
+
});
|
|
766
|
+
if (!paidResponse.ok) {
|
|
767
|
+
const errorText = await paidResponse.text();
|
|
768
|
+
throw new Error(`Payment verification failed (${paidResponse.status}): ${errorText}`);
|
|
769
|
+
}
|
|
770
|
+
console.log(`[MoltsPay] Payment verified! Service completed.`);
|
|
771
|
+
return paidResponse.json();
|
|
772
|
+
}
|
|
599
773
|
};
|
|
600
774
|
|
|
601
775
|
// src/server/index.ts
|
|
@@ -835,6 +1009,127 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
835
1009
|
}
|
|
836
1010
|
};
|
|
837
1011
|
|
|
1012
|
+
// src/facilitators/tempo.ts
|
|
1013
|
+
init_cjs_shims();
|
|
1014
|
+
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1015
|
+
var TempoFacilitator = class extends BaseFacilitator {
|
|
1016
|
+
name = "tempo";
|
|
1017
|
+
displayName = "Tempo Testnet";
|
|
1018
|
+
supportedNetworks = ["eip155:42431"];
|
|
1019
|
+
// Tempo Moderato
|
|
1020
|
+
rpcUrl;
|
|
1021
|
+
constructor() {
|
|
1022
|
+
super();
|
|
1023
|
+
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
1024
|
+
}
|
|
1025
|
+
async healthCheck() {
|
|
1026
|
+
const start = Date.now();
|
|
1027
|
+
try {
|
|
1028
|
+
const response = await fetch(this.rpcUrl, {
|
|
1029
|
+
method: "POST",
|
|
1030
|
+
headers: { "Content-Type": "application/json" },
|
|
1031
|
+
body: JSON.stringify({
|
|
1032
|
+
jsonrpc: "2.0",
|
|
1033
|
+
method: "eth_chainId",
|
|
1034
|
+
params: [],
|
|
1035
|
+
id: 1
|
|
1036
|
+
})
|
|
1037
|
+
});
|
|
1038
|
+
const data = await response.json();
|
|
1039
|
+
const chainId = parseInt(data.result, 16);
|
|
1040
|
+
if (chainId !== 42431) {
|
|
1041
|
+
return { healthy: false, error: `Wrong chainId: ${chainId}` };
|
|
1042
|
+
}
|
|
1043
|
+
return { healthy: true, latencyMs: Date.now() - start };
|
|
1044
|
+
} catch (error) {
|
|
1045
|
+
return { healthy: false, error: String(error) };
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
async verify(paymentPayload, requirements) {
|
|
1049
|
+
try {
|
|
1050
|
+
const tempoPayload = paymentPayload.payload;
|
|
1051
|
+
if (!tempoPayload?.txHash) {
|
|
1052
|
+
return { valid: false, error: "Missing txHash in payment payload" };
|
|
1053
|
+
}
|
|
1054
|
+
const receipt = await this.getTransactionReceipt(tempoPayload.txHash);
|
|
1055
|
+
if (!receipt) {
|
|
1056
|
+
return { valid: false, error: "Transaction not found" };
|
|
1057
|
+
}
|
|
1058
|
+
if (receipt.status !== "0x1") {
|
|
1059
|
+
return { valid: false, error: "Transaction failed" };
|
|
1060
|
+
}
|
|
1061
|
+
const transferLog = receipt.logs.find(
|
|
1062
|
+
(log) => log.topics[0] === TRANSFER_EVENT_TOPIC
|
|
1063
|
+
);
|
|
1064
|
+
if (!transferLog) {
|
|
1065
|
+
return { valid: false, error: "No Transfer event found" };
|
|
1066
|
+
}
|
|
1067
|
+
const toAddress = "0x" + transferLog.topics[2].slice(26).toLowerCase();
|
|
1068
|
+
const expectedTo = requirements.payTo.toLowerCase();
|
|
1069
|
+
if (toAddress !== expectedTo) {
|
|
1070
|
+
return {
|
|
1071
|
+
valid: false,
|
|
1072
|
+
error: `Wrong recipient: ${toAddress}, expected ${expectedTo}`
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
const amount = BigInt(transferLog.data);
|
|
1076
|
+
const expectedAmount = BigInt(requirements.amount);
|
|
1077
|
+
if (amount < expectedAmount) {
|
|
1078
|
+
return {
|
|
1079
|
+
valid: false,
|
|
1080
|
+
error: `Insufficient amount: ${amount}, expected ${expectedAmount}`
|
|
1081
|
+
};
|
|
1082
|
+
}
|
|
1083
|
+
const tokenAddress = transferLog.address.toLowerCase();
|
|
1084
|
+
const expectedToken = requirements.asset.toLowerCase();
|
|
1085
|
+
if (tokenAddress !== expectedToken) {
|
|
1086
|
+
return {
|
|
1087
|
+
valid: false,
|
|
1088
|
+
error: `Wrong token: ${tokenAddress}, expected ${expectedToken}`
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
valid: true,
|
|
1093
|
+
details: {
|
|
1094
|
+
txHash: tempoPayload.txHash,
|
|
1095
|
+
from: "0x" + transferLog.topics[1].slice(26),
|
|
1096
|
+
to: toAddress,
|
|
1097
|
+
amount: amount.toString(),
|
|
1098
|
+
token: tokenAddress
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
} catch (error) {
|
|
1102
|
+
return { valid: false, error: `Verification failed: ${error}` };
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
async settle(paymentPayload, requirements) {
|
|
1106
|
+
const verifyResult = await this.verify(paymentPayload, requirements);
|
|
1107
|
+
if (!verifyResult.valid) {
|
|
1108
|
+
return { success: false, error: verifyResult.error };
|
|
1109
|
+
}
|
|
1110
|
+
const tempoPayload = paymentPayload.payload;
|
|
1111
|
+
return {
|
|
1112
|
+
success: true,
|
|
1113
|
+
transaction: tempoPayload.txHash,
|
|
1114
|
+
status: "settled"
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
async getTransactionReceipt(txHash) {
|
|
1118
|
+
const response = await fetch(this.rpcUrl, {
|
|
1119
|
+
method: "POST",
|
|
1120
|
+
headers: { "Content-Type": "application/json" },
|
|
1121
|
+
body: JSON.stringify({
|
|
1122
|
+
jsonrpc: "2.0",
|
|
1123
|
+
method: "eth_getTransactionReceipt",
|
|
1124
|
+
params: [txHash],
|
|
1125
|
+
id: 1
|
|
1126
|
+
})
|
|
1127
|
+
});
|
|
1128
|
+
const data = await response.json();
|
|
1129
|
+
return data.result;
|
|
1130
|
+
}
|
|
1131
|
+
};
|
|
1132
|
+
|
|
838
1133
|
// src/facilitators/registry.ts
|
|
839
1134
|
init_cjs_shims();
|
|
840
1135
|
var FacilitatorRegistry = class {
|
|
@@ -844,7 +1139,8 @@ var FacilitatorRegistry = class {
|
|
|
844
1139
|
roundRobinIndex = 0;
|
|
845
1140
|
constructor(selection) {
|
|
846
1141
|
this.registerFactory("cdp", (config) => new CDPFacilitator(config));
|
|
847
|
-
this.
|
|
1142
|
+
this.registerFactory("tempo", () => new TempoFacilitator());
|
|
1143
|
+
this.selection = selection || { primary: "cdp", fallback: ["tempo"], strategy: "failover" };
|
|
848
1144
|
}
|
|
849
1145
|
/**
|
|
850
1146
|
* Register a new facilitator factory
|
|
@@ -1068,6 +1364,9 @@ var X402_VERSION3 = 2;
|
|
|
1068
1364
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
1069
1365
|
var PAYMENT_HEADER2 = "x-payment";
|
|
1070
1366
|
var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
1367
|
+
var MPP_AUTH_HEADER = "authorization";
|
|
1368
|
+
var MPP_WWW_AUTH_HEADER = "www-authenticate";
|
|
1369
|
+
var MPP_RECEIPT_HEADER = "payment-receipt";
|
|
1071
1370
|
var TOKEN_ADDRESSES = {
|
|
1072
1371
|
"eip155:8453": {
|
|
1073
1372
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -1081,12 +1380,20 @@ var TOKEN_ADDRESSES = {
|
|
|
1081
1380
|
"eip155:137": {
|
|
1082
1381
|
USDC: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
1083
1382
|
USDT: "0xc2132D05D31c914a87C6611C10748AEb04B58e8F"
|
|
1383
|
+
},
|
|
1384
|
+
"eip155:42431": {
|
|
1385
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1386
|
+
USDC: "0x20c0000000000000000000000000000000000000",
|
|
1387
|
+
// pathUSD
|
|
1388
|
+
USDT: "0x20c0000000000000000000000000000000000001"
|
|
1389
|
+
// alphaUSD
|
|
1084
1390
|
}
|
|
1085
1391
|
};
|
|
1086
1392
|
var CHAIN_TO_NETWORK = {
|
|
1087
1393
|
"base": "eip155:8453",
|
|
1088
1394
|
"base_sepolia": "eip155:84532",
|
|
1089
|
-
"polygon": "eip155:137"
|
|
1395
|
+
"polygon": "eip155:137",
|
|
1396
|
+
"tempo_moderato": "eip155:42431"
|
|
1090
1397
|
};
|
|
1091
1398
|
var TOKEN_DOMAINS = {
|
|
1092
1399
|
// Base mainnet
|
|
@@ -1104,6 +1411,11 @@ var TOKEN_DOMAINS = {
|
|
|
1104
1411
|
"eip155:137": {
|
|
1105
1412
|
USDC: { name: "USD Coin", version: "2" },
|
|
1106
1413
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1414
|
+
},
|
|
1415
|
+
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1416
|
+
"eip155:42431": {
|
|
1417
|
+
USDC: { name: "pathUSD", version: "1" },
|
|
1418
|
+
USDT: { name: "alphaUSD", version: "1" }
|
|
1107
1419
|
}
|
|
1108
1420
|
};
|
|
1109
1421
|
function getTokenDomain(network, token) {
|
|
@@ -1161,9 +1473,11 @@ var MoltsPayServer = class {
|
|
|
1161
1473
|
};
|
|
1162
1474
|
this.useMainnet = process.env.USE_MAINNET?.toLowerCase() === "true";
|
|
1163
1475
|
this.networkId = this.useMainnet ? "eip155:8453" : "eip155:84532";
|
|
1476
|
+
const defaultFallback = ["tempo"];
|
|
1477
|
+
const envFallback = process.env.FACILITATOR_FALLBACK?.split(",").filter(Boolean);
|
|
1164
1478
|
const facilitatorConfig = options.facilitators || {
|
|
1165
1479
|
primary: process.env.FACILITATOR_PRIMARY || "cdp",
|
|
1166
|
-
fallback:
|
|
1480
|
+
fallback: envFallback || defaultFallback,
|
|
1167
1481
|
strategy: process.env.FACILITATOR_STRATEGY || "failover",
|
|
1168
1482
|
config: {
|
|
1169
1483
|
cdp: { useMainnet: this.useMainnet }
|
|
@@ -1257,8 +1571,8 @@ var MoltsPayServer = class {
|
|
|
1257
1571
|
async handleRequest(req, res) {
|
|
1258
1572
|
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
1259
1573
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1260
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment");
|
|
1261
|
-
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response");
|
|
1574
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1575
|
+
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
|
|
1262
1576
|
if (req.method === "OPTIONS") {
|
|
1263
1577
|
res.writeHead(204);
|
|
1264
1578
|
res.end();
|
|
@@ -1289,6 +1603,14 @@ var MoltsPayServer = class {
|
|
|
1289
1603
|
const paymentHeader = req.headers[PAYMENT_HEADER2];
|
|
1290
1604
|
return await this.handleProxy(body, paymentHeader, res);
|
|
1291
1605
|
}
|
|
1606
|
+
const servicePath = url.pathname.replace(/^\//, "");
|
|
1607
|
+
const skill = this.skills.get(servicePath);
|
|
1608
|
+
if (skill && (req.method === "POST" || req.method === "GET")) {
|
|
1609
|
+
const body = req.method === "POST" ? await this.readBody(req) : {};
|
|
1610
|
+
const authHeader = req.headers[MPP_AUTH_HEADER];
|
|
1611
|
+
const x402Header = req.headers[PAYMENT_HEADER2];
|
|
1612
|
+
return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
|
|
1613
|
+
}
|
|
1292
1614
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1293
1615
|
} catch (err) {
|
|
1294
1616
|
console.error("[MoltsPay] Error:", err);
|
|
@@ -1472,6 +1794,187 @@ var MoltsPayServer = class {
|
|
|
1472
1794
|
payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
|
|
1473
1795
|
}, responseHeaders);
|
|
1474
1796
|
}
|
|
1797
|
+
/**
|
|
1798
|
+
* Handle MPP (Machine Payments Protocol) request
|
|
1799
|
+
* Supports both x402 and MPP protocols on service endpoints
|
|
1800
|
+
*/
|
|
1801
|
+
async handleMPPRequest(skill, body, authHeader, x402Header, res) {
|
|
1802
|
+
const config = skill.config;
|
|
1803
|
+
const params = body || {};
|
|
1804
|
+
if (x402Header) {
|
|
1805
|
+
return await this.handleExecute({ service: config.id, params }, x402Header, res);
|
|
1806
|
+
}
|
|
1807
|
+
if (authHeader && authHeader.toLowerCase().startsWith("payment ")) {
|
|
1808
|
+
return await this.handleMPPPayment(skill, params, authHeader, res);
|
|
1809
|
+
}
|
|
1810
|
+
return this.sendMPPPaymentRequired(config, res);
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Handle MPP payment verification and service execution
|
|
1814
|
+
*/
|
|
1815
|
+
async handleMPPPayment(skill, params, authHeader, res) {
|
|
1816
|
+
const config = skill.config;
|
|
1817
|
+
const credentialMatch = authHeader.match(/Payment\s+(.+)/i);
|
|
1818
|
+
if (!credentialMatch) {
|
|
1819
|
+
return this.sendJson(res, 400, { error: "Invalid Authorization header format" });
|
|
1820
|
+
}
|
|
1821
|
+
let mppCredential;
|
|
1822
|
+
try {
|
|
1823
|
+
const base64 = credentialMatch[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1824
|
+
const decoded = Buffer.from(base64, "base64").toString("utf-8");
|
|
1825
|
+
mppCredential = JSON.parse(decoded);
|
|
1826
|
+
} catch (err) {
|
|
1827
|
+
console.error("[MoltsPay] Failed to parse MPP credential:", err);
|
|
1828
|
+
return this.sendJson(res, 400, { error: "Invalid payment credential encoding" });
|
|
1829
|
+
}
|
|
1830
|
+
let txHash;
|
|
1831
|
+
if (mppCredential.payload?.type === "hash" && mppCredential.payload?.hash) {
|
|
1832
|
+
txHash = mppCredential.payload.hash;
|
|
1833
|
+
} else if (mppCredential.payload?.type === "transaction") {
|
|
1834
|
+
return this.sendJson(res, 400, {
|
|
1835
|
+
error: "Transaction type not supported. Please use push mode (hash type)."
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
if (!txHash) {
|
|
1839
|
+
return this.sendJson(res, 400, { error: "Missing transaction hash in credential" });
|
|
1840
|
+
}
|
|
1841
|
+
let chainId = mppCredential.challenge?.request?.methodDetails?.chainId;
|
|
1842
|
+
if (!chainId && mppCredential.source) {
|
|
1843
|
+
const chainMatch = mppCredential.source.match(/eip155:(\d+)/);
|
|
1844
|
+
if (chainMatch) chainId = parseInt(chainMatch[1], 10);
|
|
1845
|
+
}
|
|
1846
|
+
chainId = chainId || 42431;
|
|
1847
|
+
const network = `eip155:${chainId}`;
|
|
1848
|
+
if (!this.isNetworkAccepted(network)) {
|
|
1849
|
+
return this.sendJson(res, 402, {
|
|
1850
|
+
error: `Network not accepted: ${network}`
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
const requirements = this.buildPaymentRequirements(
|
|
1854
|
+
config,
|
|
1855
|
+
network,
|
|
1856
|
+
this.getWalletForNetwork(network),
|
|
1857
|
+
"USDC"
|
|
1858
|
+
);
|
|
1859
|
+
const paymentPayload = {
|
|
1860
|
+
x402Version: X402_VERSION3,
|
|
1861
|
+
scheme: "exact",
|
|
1862
|
+
network,
|
|
1863
|
+
payload: {
|
|
1864
|
+
txHash,
|
|
1865
|
+
chainId
|
|
1866
|
+
}
|
|
1867
|
+
};
|
|
1868
|
+
console.log(`[MoltsPay] Verifying MPP payment: txHash=${txHash}, chainId=${chainId}`);
|
|
1869
|
+
const verification = await this.registry.verify(paymentPayload, requirements);
|
|
1870
|
+
if (!verification.valid) {
|
|
1871
|
+
return this.sendJson(res, 402, {
|
|
1872
|
+
error: `Payment verification failed: ${verification.error}`
|
|
1873
|
+
});
|
|
1874
|
+
}
|
|
1875
|
+
console.log(`[MoltsPay] Payment verified! Executing service: ${config.id}`);
|
|
1876
|
+
let result;
|
|
1877
|
+
try {
|
|
1878
|
+
result = await skill.handler(params);
|
|
1879
|
+
} catch (err) {
|
|
1880
|
+
console.error(`[MoltsPay] Skill execution error:`, err);
|
|
1881
|
+
return this.sendJson(res, 500, {
|
|
1882
|
+
error: `Service execution failed: ${err.message}`
|
|
1883
|
+
});
|
|
1884
|
+
}
|
|
1885
|
+
const receipt = {
|
|
1886
|
+
success: true,
|
|
1887
|
+
txHash,
|
|
1888
|
+
network,
|
|
1889
|
+
facilitator: verification.facilitator
|
|
1890
|
+
};
|
|
1891
|
+
const receiptEncoded = Buffer.from(JSON.stringify(receipt)).toString("base64");
|
|
1892
|
+
res.writeHead(200, {
|
|
1893
|
+
"Content-Type": "application/json",
|
|
1894
|
+
[MPP_RECEIPT_HEADER]: receiptEncoded
|
|
1895
|
+
});
|
|
1896
|
+
res.end(JSON.stringify({
|
|
1897
|
+
success: true,
|
|
1898
|
+
result,
|
|
1899
|
+
payment: {
|
|
1900
|
+
txHash,
|
|
1901
|
+
status: "verified",
|
|
1902
|
+
facilitator: verification.facilitator
|
|
1903
|
+
}
|
|
1904
|
+
}, null, 2));
|
|
1905
|
+
}
|
|
1906
|
+
/**
|
|
1907
|
+
* Return 402 with both x402 and MPP payment requirements
|
|
1908
|
+
*/
|
|
1909
|
+
sendMPPPaymentRequired(config, res) {
|
|
1910
|
+
const acceptedTokens = getAcceptedCurrencies(config);
|
|
1911
|
+
const providerChains = this.getProviderChains();
|
|
1912
|
+
const accepts = [];
|
|
1913
|
+
for (const chainConfig of providerChains) {
|
|
1914
|
+
for (const token of acceptedTokens) {
|
|
1915
|
+
if (chainConfig.tokens.includes(token)) {
|
|
1916
|
+
accepts.push(this.buildPaymentRequirements(config, chainConfig.network, chainConfig.wallet, token));
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
const x402PaymentRequired = {
|
|
1921
|
+
x402Version: X402_VERSION3,
|
|
1922
|
+
accepts,
|
|
1923
|
+
acceptedCurrencies: acceptedTokens,
|
|
1924
|
+
resource: {
|
|
1925
|
+
url: `/${config.id}`,
|
|
1926
|
+
description: `${config.name} - $${config.price} ${config.currency}`
|
|
1927
|
+
}
|
|
1928
|
+
};
|
|
1929
|
+
const x402Encoded = Buffer.from(JSON.stringify(x402PaymentRequired)).toString("base64");
|
|
1930
|
+
const tempoChain = providerChains.find((c) => c.network === "eip155:42431");
|
|
1931
|
+
let mppWwwAuth = "";
|
|
1932
|
+
if (tempoChain) {
|
|
1933
|
+
const challengeId = this.generateChallengeId();
|
|
1934
|
+
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
1935
|
+
const tokenAddress = TOKEN_ADDRESSES["eip155:42431"]?.USDC || "0x20c0000000000000000000000000000000000000";
|
|
1936
|
+
const mppRequest = {
|
|
1937
|
+
amount: amountInUnits,
|
|
1938
|
+
currency: tokenAddress,
|
|
1939
|
+
methodDetails: {
|
|
1940
|
+
chainId: 42431,
|
|
1941
|
+
feePayer: true
|
|
1942
|
+
},
|
|
1943
|
+
recipient: tempoChain.wallet
|
|
1944
|
+
};
|
|
1945
|
+
const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
|
|
1946
|
+
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
1947
|
+
mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
|
|
1948
|
+
}
|
|
1949
|
+
const headers = {
|
|
1950
|
+
"Content-Type": "application/problem+json",
|
|
1951
|
+
[PAYMENT_REQUIRED_HEADER2]: x402Encoded
|
|
1952
|
+
};
|
|
1953
|
+
if (mppWwwAuth) {
|
|
1954
|
+
headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
|
|
1955
|
+
}
|
|
1956
|
+
res.writeHead(402, headers);
|
|
1957
|
+
res.end(JSON.stringify({
|
|
1958
|
+
type: "https://paymentauth.org/problems/payment-required",
|
|
1959
|
+
title: "Payment Required",
|
|
1960
|
+
status: 402,
|
|
1961
|
+
detail: `Payment is required (${config.name}).`,
|
|
1962
|
+
service: config.id,
|
|
1963
|
+
price: config.price,
|
|
1964
|
+
currency: config.currency,
|
|
1965
|
+
acceptedCurrencies: acceptedTokens
|
|
1966
|
+
}, null, 2));
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Generate a unique challenge ID for MPP
|
|
1970
|
+
*/
|
|
1971
|
+
generateChallengeId() {
|
|
1972
|
+
const bytes = new Uint8Array(24);
|
|
1973
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
1974
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1975
|
+
}
|
|
1976
|
+
return Buffer.from(bytes).toString("base64url");
|
|
1977
|
+
}
|
|
1475
1978
|
/**
|
|
1476
1979
|
* Return 402 with x402 payment requirements (v2 format)
|
|
1477
1980
|
* Includes requirements for all chains and all accepted currencies
|
|
@@ -1846,6 +2349,26 @@ async function printQRCode(url) {
|
|
|
1846
2349
|
|
|
1847
2350
|
// src/cli/index.ts
|
|
1848
2351
|
var readline = __toESM(require("readline"));
|
|
2352
|
+
if (!globalThis.crypto) {
|
|
2353
|
+
globalThis.crypto = import_crypto.webcrypto;
|
|
2354
|
+
}
|
|
2355
|
+
function getVersion() {
|
|
2356
|
+
const locations = [
|
|
2357
|
+
(0, import_path2.join)(__dirname, "../../package.json"),
|
|
2358
|
+
(0, import_path2.join)(__dirname, "../package.json"),
|
|
2359
|
+
(0, import_path2.join)(process.cwd(), "node_modules/moltspay/package.json")
|
|
2360
|
+
];
|
|
2361
|
+
for (const loc of locations) {
|
|
2362
|
+
try {
|
|
2363
|
+
if ((0, import_fs4.existsSync)(loc)) {
|
|
2364
|
+
const pkg = JSON.parse((0, import_fs4.readFileSync)(loc, "utf-8"));
|
|
2365
|
+
if (pkg.name === "moltspay") return pkg.version;
|
|
2366
|
+
}
|
|
2367
|
+
} catch {
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
return "0.0.0";
|
|
2371
|
+
}
|
|
1849
2372
|
var program = new import_commander.Command();
|
|
1850
2373
|
var DEFAULT_CONFIG_DIR = (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
|
|
1851
2374
|
var PID_FILE = (0, import_path2.join)(DEFAULT_CONFIG_DIR, "server.pid");
|
|
@@ -1864,7 +2387,7 @@ function prompt(question) {
|
|
|
1864
2387
|
});
|
|
1865
2388
|
});
|
|
1866
2389
|
}
|
|
1867
|
-
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(
|
|
2390
|
+
program.name("moltspay").description("MoltsPay - Payment infrastructure for AI Agents").version(getVersion());
|
|
1868
2391
|
program.command("init").description("Initialize MoltsPay client (create wallet, set limits)").option("--chain <chain>", "Blockchain to use", "base").option("--max-per-tx <amount>", "Max amount per transaction").option("--max-per-day <amount>", "Max amount per day").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
1869
2392
|
console.log("\n\u{1F510} MoltsPay Client Setup\n");
|
|
1870
2393
|
if ((0, import_fs4.existsSync)((0, import_path2.join)(options.configDir, "wallet.json"))) {
|
|
@@ -1873,7 +2396,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
|
|
|
1873
2396
|
return;
|
|
1874
2397
|
}
|
|
1875
2398
|
let chain = options.chain;
|
|
1876
|
-
const supportedChains = ["base", "polygon", "base_sepolia"];
|
|
2399
|
+
const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato"];
|
|
1877
2400
|
if (!supportedChains.includes(chain)) {
|
|
1878
2401
|
console.error(`\u274C Unknown chain: ${chain}. Supported: ${supportedChains.join(", ")}`);
|
|
1879
2402
|
process.exit(1);
|
|
@@ -1961,11 +2484,9 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1961
2484
|
console.log(` Wallet: ${client.address}`);
|
|
1962
2485
|
console.log(` Chain: Base Sepolia (testnet)
|
|
1963
2486
|
`);
|
|
1964
|
-
console.log("\u{
|
|
1965
|
-
console.log("
|
|
1966
|
-
console.log("
|
|
1967
|
-
console.log(`\u{1F4A1} Send USDC to: ${client.address}
|
|
1968
|
-
`);
|
|
2487
|
+
console.log("\u{1F4A1} Use the MoltsPay faucet to get free testnet USDC:\n");
|
|
2488
|
+
console.log(" npx moltspay faucet\n");
|
|
2489
|
+
console.log(" Or get from Circle Faucet: https://faucet.circle.com/\n");
|
|
1969
2490
|
return;
|
|
1970
2491
|
}
|
|
1971
2492
|
console.log("\n\u{1F4B3} Fund your agent wallet\n");
|
|
@@ -1997,8 +2518,13 @@ program.command("fund <amount>").description("Fund wallet with USDC via Coinbase
|
|
|
1997
2518
|
console.log(`\u274C ${error.message}`);
|
|
1998
2519
|
}
|
|
1999
2520
|
});
|
|
2000
|
-
program.command("faucet").description("Request testnet
|
|
2521
|
+
program.command("faucet").description("Request testnet tokens from faucet (Base Sepolia or Tempo Moderato)").option("--chain <chain>", "Chain to get tokens on (base_sepolia or tempo_moderato)", "base_sepolia").option("--address <address>", "Wallet address (defaults to your wallet)").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).action(async (options) => {
|
|
2001
2522
|
let address = options.address;
|
|
2523
|
+
const chain = options.chain?.toLowerCase() || "base_sepolia";
|
|
2524
|
+
if (!["base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
2525
|
+
console.log("\u274C Invalid chain. Use: base_sepolia or tempo_moderato");
|
|
2526
|
+
return;
|
|
2527
|
+
}
|
|
2002
2528
|
if (!address) {
|
|
2003
2529
|
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2004
2530
|
if (client.isInitialized) {
|
|
@@ -2013,34 +2539,67 @@ program.command("faucet").description("Request testnet USDC from MoltsPay faucet
|
|
|
2013
2539
|
return;
|
|
2014
2540
|
}
|
|
2015
2541
|
console.log("\n\u{1F6B0} MoltsPay Testnet Faucet\n");
|
|
2016
|
-
|
|
2017
|
-
|
|
2542
|
+
if (chain === "tempo_moderato") {
|
|
2543
|
+
console.log(` Requesting testnet tokens on Tempo Moderato...`);
|
|
2544
|
+
console.log(` Address: ${address}
|
|
2018
2545
|
`);
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2546
|
+
try {
|
|
2547
|
+
const TEMPO_FAUCET_API = "https://docs.tempo.xyz/api/faucet";
|
|
2548
|
+
const response = await fetch(TEMPO_FAUCET_API, {
|
|
2549
|
+
method: "POST",
|
|
2550
|
+
headers: { "Content-Type": "application/json" },
|
|
2551
|
+
body: JSON.stringify({ address })
|
|
2552
|
+
});
|
|
2553
|
+
const result = await response.json();
|
|
2554
|
+
if (response.ok && result.data && result.data.length > 0) {
|
|
2555
|
+
console.log(`\u2705 Received testnet tokens!
|
|
2556
|
+
`);
|
|
2557
|
+
console.log(` Tokens: pathUSD, AlphaUSD, BetaUSD, ThetaUSD (1M each)`);
|
|
2558
|
+
console.log(` Transactions:`);
|
|
2559
|
+
for (const tx of result.data) {
|
|
2560
|
+
console.log(` https://explore.testnet.tempo.xyz/tx/${tx.hash}`);
|
|
2561
|
+
}
|
|
2562
|
+
console.log("\n\u{1F4A1} Use these tokens to test MPP payments:");
|
|
2563
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain tempo_moderato
|
|
2564
|
+
`);
|
|
2565
|
+
} else {
|
|
2566
|
+
console.log(`\u274C ${result.error || "Faucet request failed"}`);
|
|
2567
|
+
console.log("\n Try again later or use Tempo Wallet: https://wallet.tempo.xyz\n");
|
|
2568
|
+
}
|
|
2569
|
+
} catch (error) {
|
|
2570
|
+
console.log(`\u274C ${error.message}`);
|
|
2571
|
+
console.log("\n Try Tempo Wallet instead: https://wallet.tempo.xyz\n");
|
|
2032
2572
|
}
|
|
2033
|
-
|
|
2573
|
+
} else {
|
|
2574
|
+
console.log(` Requesting 1 USDC on Base Sepolia...`);
|
|
2575
|
+
console.log(` Address: ${address}
|
|
2034
2576
|
`);
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2577
|
+
try {
|
|
2578
|
+
const FAUCET_API = process.env.MOLTSPAY_FAUCET_API || "https://moltspay.com/api/v1/faucet";
|
|
2579
|
+
const response = await fetch(FAUCET_API, {
|
|
2580
|
+
method: "POST",
|
|
2581
|
+
headers: { "Content-Type": "application/json" },
|
|
2582
|
+
body: JSON.stringify({ address })
|
|
2583
|
+
});
|
|
2584
|
+
const result = await response.json();
|
|
2585
|
+
if (!response.ok) {
|
|
2586
|
+
console.log(`\u274C ${result.error || "Request failed"}`);
|
|
2587
|
+
if (result.hint) console.log(` ${result.hint}`);
|
|
2588
|
+
if (result.retry_after) console.log(` Retry after: ${result.retry_after}`);
|
|
2589
|
+
return;
|
|
2590
|
+
}
|
|
2591
|
+
console.log(`\u2705 Received ${result.amount} USDC!
|
|
2038
2592
|
`);
|
|
2039
|
-
|
|
2040
|
-
|
|
2593
|
+
console.log(` Transaction: ${result.transaction}`);
|
|
2594
|
+
console.log(` Explorer: ${result.explorer}`);
|
|
2595
|
+
console.log(` Faucet balance: ${result.faucet_balance} USDC remaining
|
|
2041
2596
|
`);
|
|
2042
|
-
|
|
2043
|
-
|
|
2597
|
+
console.log("\u{1F4A1} Use this USDC to test x402 payments:");
|
|
2598
|
+
console.log(` npx moltspay pay <service-url> <service-id> --chain base_sepolia
|
|
2599
|
+
`);
|
|
2600
|
+
} catch (error) {
|
|
2601
|
+
console.log(`\u274C ${error.message}`);
|
|
2602
|
+
}
|
|
2044
2603
|
}
|
|
2045
2604
|
});
|
|
2046
2605
|
program.command("status").description("Show wallet status and balance").option("--config-dir <dir>", "Config directory", DEFAULT_CONFIG_DIR).option("--json", "Output as JSON").action(async (options) => {
|
|
@@ -2072,8 +2631,26 @@ program.command("status").description("Show wallet status and balance").option("
|
|
|
2072
2631
|
console.log("");
|
|
2073
2632
|
console.log(" Balances:");
|
|
2074
2633
|
for (const [chainName, balance] of Object.entries(allBalances)) {
|
|
2075
|
-
|
|
2076
|
-
|
|
2634
|
+
let chainLabel;
|
|
2635
|
+
if (chainName === "base_sepolia") {
|
|
2636
|
+
chainLabel = "Base Sepolia";
|
|
2637
|
+
} else if (chainName === "tempo_moderato") {
|
|
2638
|
+
chainLabel = "Tempo Moderato";
|
|
2639
|
+
} else {
|
|
2640
|
+
chainLabel = chainName.charAt(0).toUpperCase() + chainName.slice(1);
|
|
2641
|
+
}
|
|
2642
|
+
if (chainName === "tempo_moderato" && balance.tempo) {
|
|
2643
|
+
const tempo = balance.tempo;
|
|
2644
|
+
const nativeStr = balance.native > 1e12 ? balance.native.toExponential(2) : balance.native.toFixed(2);
|
|
2645
|
+
console.log(` ${chainLabel}:`);
|
|
2646
|
+
console.log(` Native: ${nativeStr} TEMPO (for gas)`);
|
|
2647
|
+
console.log(` pathUSD: ${tempo.pathUSD.toFixed(2)}`);
|
|
2648
|
+
console.log(` alphaUSD: ${tempo.alphaUSD.toFixed(2)}`);
|
|
2649
|
+
console.log(` betaUSD: ${tempo.betaUSD.toFixed(2)}`);
|
|
2650
|
+
console.log(` thetaUSD: ${tempo.thetaUSD.toFixed(2)}`);
|
|
2651
|
+
} else {
|
|
2652
|
+
console.log(` ${chainLabel.padEnd(14)} ${balance.usdc.toFixed(2)} USDC | ${balance.usdt.toFixed(2)} USDT`);
|
|
2653
|
+
}
|
|
2077
2654
|
}
|
|
2078
2655
|
console.log("");
|
|
2079
2656
|
console.log(" Spending Limits:");
|
|
@@ -2091,8 +2668,8 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2091
2668
|
const days = parseInt(options.days) || 7;
|
|
2092
2669
|
const limit = parseInt(options.limit) || 20;
|
|
2093
2670
|
const chain = options.chain?.toLowerCase() || "all";
|
|
2094
|
-
if (!["base", "polygon", "base_sepolia", "all"].includes(chain)) {
|
|
2095
|
-
console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, or all");
|
|
2671
|
+
if (!["base", "polygon", "base_sepolia", "tempo_moderato", "all"].includes(chain)) {
|
|
2672
|
+
console.log("\u274C Invalid chain. Use: base, polygon, base_sepolia, tempo_moderato, or all");
|
|
2096
2673
|
return;
|
|
2097
2674
|
}
|
|
2098
2675
|
const wallet = client.address;
|
|
@@ -2112,9 +2689,16 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2112
2689
|
api: "https://base-sepolia.blockscout.com/api/v2",
|
|
2113
2690
|
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
2114
2691
|
name: "Base Sepolia"
|
|
2692
|
+
},
|
|
2693
|
+
// Tempo explorer doesn't have public API yet
|
|
2694
|
+
tempo_moderato: {
|
|
2695
|
+
api: "",
|
|
2696
|
+
// No API available
|
|
2697
|
+
usdc: "0x20c0000000000000000000000000000000000000",
|
|
2698
|
+
name: "Tempo Moderato"
|
|
2115
2699
|
}
|
|
2116
2700
|
};
|
|
2117
|
-
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia"] : [chain];
|
|
2701
|
+
const chainsToQuery = chain === "all" ? ["base", "polygon", "base_sepolia", "tempo_moderato"] : [chain];
|
|
2118
2702
|
console.log(`
|
|
2119
2703
|
\u{1F4DC} Transactions (last ${days} day${days > 1 ? "s" : ""})
|
|
2120
2704
|
`);
|
|
@@ -2122,27 +2706,136 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2122
2706
|
for (const c of chainsToQuery) {
|
|
2123
2707
|
const explorer = explorers[c];
|
|
2124
2708
|
try {
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2709
|
+
if (c === "tempo_moderato") {
|
|
2710
|
+
const tempoTokens = [
|
|
2711
|
+
{ address: "0x20c0000000000000000000000000000000000000", name: "pathUSD" },
|
|
2712
|
+
{ address: "0x20c0000000000000000000000000000000000001", name: "alphaUSD" },
|
|
2713
|
+
{ address: "0x20c0000000000000000000000000000000000002", name: "betaUSD" },
|
|
2714
|
+
{ address: "0x20c0000000000000000000000000000000000003", name: "thetaUSD" }
|
|
2715
|
+
];
|
|
2716
|
+
const transferTopic = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
2717
|
+
const walletTopic = "0x000000000000000000000000" + wallet.toLowerCase().slice(2);
|
|
2718
|
+
let latestBlock = 0;
|
|
2719
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
2720
|
+
try {
|
|
2721
|
+
const blockRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2722
|
+
method: "POST",
|
|
2723
|
+
headers: { "Content-Type": "application/json" },
|
|
2724
|
+
body: JSON.stringify({ jsonrpc: "2.0", method: "eth_blockNumber", params: [], id: 1 })
|
|
2725
|
+
});
|
|
2726
|
+
const blockData = await blockRes.json();
|
|
2727
|
+
if (blockData.result) {
|
|
2728
|
+
latestBlock = parseInt(blockData.result, 16);
|
|
2729
|
+
break;
|
|
2730
|
+
}
|
|
2731
|
+
} catch (e) {
|
|
2732
|
+
if (attempt === 2) throw e;
|
|
2733
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
if (latestBlock === 0) {
|
|
2737
|
+
console.log(" \u26A0\uFE0F Tempo Moderato: Could not get latest block");
|
|
2738
|
+
continue;
|
|
2739
|
+
}
|
|
2740
|
+
const maxBlocks = 1e5;
|
|
2741
|
+
const blocksPerDay = 172800;
|
|
2742
|
+
const requestedBlocks = blocksPerDay * days;
|
|
2743
|
+
const actualBlocks = Math.min(requestedBlocks, maxBlocks);
|
|
2744
|
+
const fromBlock = "0x" + Math.max(0, latestBlock - actualBlocks).toString(16);
|
|
2745
|
+
const toBlock = "0x" + latestBlock.toString(16);
|
|
2746
|
+
if (requestedBlocks > maxBlocks) {
|
|
2747
|
+
console.log(` \u2139\uFE0F Tempo: querying last ~14 hours (RPC limit: 100k blocks)`);
|
|
2748
|
+
}
|
|
2749
|
+
for (const token of tempoTokens) {
|
|
2750
|
+
try {
|
|
2751
|
+
const inRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2752
|
+
method: "POST",
|
|
2753
|
+
headers: { "Content-Type": "application/json" },
|
|
2754
|
+
body: JSON.stringify({
|
|
2755
|
+
jsonrpc: "2.0",
|
|
2756
|
+
method: "eth_getLogs",
|
|
2757
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, null, walletTopic] }],
|
|
2758
|
+
id: 1
|
|
2759
|
+
})
|
|
2760
|
+
});
|
|
2761
|
+
const inData = await inRes.json();
|
|
2762
|
+
if (inData.error) {
|
|
2763
|
+
console.log(` \u26A0\uFE0F ${token.name}: ${inData.error.message}`);
|
|
2764
|
+
continue;
|
|
2765
|
+
}
|
|
2766
|
+
if (inData.result && Array.isArray(inData.result)) {
|
|
2767
|
+
for (const log of inData.result) {
|
|
2768
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2769
|
+
if (timestamp < cutoffTime) continue;
|
|
2770
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2771
|
+
const from = "0x" + log.topics[1].slice(26);
|
|
2772
|
+
allTxns.push({
|
|
2773
|
+
chain: c,
|
|
2774
|
+
timestamp,
|
|
2775
|
+
type: "IN",
|
|
2776
|
+
amount,
|
|
2777
|
+
other: from,
|
|
2778
|
+
hash: log.transactionHash,
|
|
2779
|
+
token: token.name
|
|
2780
|
+
});
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
const outRes = await fetch("https://rpc.moderato.tempo.xyz", {
|
|
2784
|
+
method: "POST",
|
|
2785
|
+
headers: { "Content-Type": "application/json" },
|
|
2786
|
+
body: JSON.stringify({
|
|
2787
|
+
jsonrpc: "2.0",
|
|
2788
|
+
method: "eth_getLogs",
|
|
2789
|
+
params: [{ fromBlock, toBlock, address: token.address, topics: [transferTopic, walletTopic, null] }],
|
|
2790
|
+
id: 1
|
|
2791
|
+
})
|
|
2792
|
+
});
|
|
2793
|
+
const outData = await outRes.json();
|
|
2794
|
+
if (outData.result && Array.isArray(outData.result)) {
|
|
2795
|
+
for (const log of outData.result) {
|
|
2796
|
+
const timestamp = parseInt(log.blockTimestamp, 16) * 1e3;
|
|
2797
|
+
if (timestamp < cutoffTime) continue;
|
|
2798
|
+
const amount = parseInt(log.data, 16) / 1e6;
|
|
2799
|
+
const to = "0x" + log.topics[2].slice(26);
|
|
2800
|
+
allTxns.push({
|
|
2801
|
+
chain: c,
|
|
2802
|
+
timestamp,
|
|
2803
|
+
type: "OUT",
|
|
2804
|
+
amount,
|
|
2805
|
+
other: to,
|
|
2806
|
+
hash: log.transactionHash,
|
|
2807
|
+
token: token.name
|
|
2808
|
+
});
|
|
2809
|
+
}
|
|
2810
|
+
}
|
|
2811
|
+
} catch (tokenError) {
|
|
2812
|
+
continue;
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
} else {
|
|
2816
|
+
const url = `${explorer.api}/addresses/${wallet}/token-transfers?type=ERC-20&token=${explorer.usdc}`;
|
|
2817
|
+
const response = await fetch(url);
|
|
2818
|
+
const data = await response.json();
|
|
2819
|
+
if (data.items && Array.isArray(data.items)) {
|
|
2820
|
+
for (const tx of data.items) {
|
|
2821
|
+
const timestamp = new Date(tx.timestamp).getTime();
|
|
2822
|
+
if (timestamp < cutoffTime) continue;
|
|
2823
|
+
const isIncoming = tx.to.hash.toLowerCase() === wallet.toLowerCase();
|
|
2824
|
+
const decimals = parseInt(tx.total.decimals) || 6;
|
|
2825
|
+
allTxns.push({
|
|
2826
|
+
chain: c,
|
|
2827
|
+
timestamp,
|
|
2828
|
+
type: isIncoming ? "IN" : "OUT",
|
|
2829
|
+
amount: parseInt(tx.total.value) / Math.pow(10, decimals),
|
|
2830
|
+
other: isIncoming ? tx.from.hash : tx.to.hash,
|
|
2831
|
+
hash: tx.transaction_hash
|
|
2832
|
+
});
|
|
2833
|
+
}
|
|
2142
2834
|
}
|
|
2143
2835
|
}
|
|
2144
2836
|
} catch (error) {
|
|
2145
|
-
|
|
2837
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
2838
|
+
console.log(` \u26A0\uFE0F ${explorer.name}: ${errMsg}`);
|
|
2146
2839
|
}
|
|
2147
2840
|
}
|
|
2148
2841
|
allTxns.sort((a, b) => b.timestamp - a.timestamp);
|
|
@@ -2155,8 +2848,12 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2155
2848
|
const color = tx.type === "IN" ? "\x1B[32m" : "\x1B[31m";
|
|
2156
2849
|
const reset = "\x1B[0m";
|
|
2157
2850
|
const date = new Date(tx.timestamp).toISOString().slice(5, 16).replace("T", " ");
|
|
2158
|
-
|
|
2159
|
-
|
|
2851
|
+
let chainLabel = tx.chain.toUpperCase();
|
|
2852
|
+
if (tx.chain === "tempo_moderato") chainLabel = "TEMPO";
|
|
2853
|
+
else if (tx.chain === "base_sepolia") chainLabel = "BASE_SEPOLIA";
|
|
2854
|
+
const chainTag = chain === "all" ? `[${chainLabel}] ` : "";
|
|
2855
|
+
const tokenName = tx.token || "USDC";
|
|
2856
|
+
console.log(` ${color}${sign}${tx.amount.toFixed(2)} ${tokenName}${reset} | ${chainTag}${tx.type === "IN" ? "from" : "to"} ${tx.other.slice(0, 10)}...${tx.other.slice(-4)} | ${date}`);
|
|
2160
2857
|
}
|
|
2161
2858
|
const inTotal = allTxns.filter((t) => t.type === "IN").reduce((s, t) => s + t.amount, 0);
|
|
2162
2859
|
const outTotal = allTxns.filter((t) => t.type === "OUT").reduce((s, t) => s + t.amount, 0);
|
|
@@ -2165,39 +2862,88 @@ program.command("list").description("List recent transactions").option("--days <
|
|
|
2165
2862
|
`);
|
|
2166
2863
|
}
|
|
2167
2864
|
});
|
|
2168
|
-
program.command("services
|
|
2865
|
+
program.command("services [url]").description("List services from registry or a specific provider").option("-q, --query <keyword>", "Search by keyword (name, description, tags)").option("--max-price <price>", "Maximum price in USD").option("--type <type>", "Filter by type: api_service | file_download").option("--tag <tag>", "Filter by tag").option("--json", "Output as JSON").action(async (url, options) => {
|
|
2866
|
+
const MOLTSPAY_REGISTRY = "https://moltspay.com";
|
|
2169
2867
|
try {
|
|
2170
|
-
|
|
2171
|
-
|
|
2868
|
+
let services;
|
|
2869
|
+
let isRegistry = false;
|
|
2870
|
+
if (url) {
|
|
2871
|
+
const client = new MoltsPayClient();
|
|
2872
|
+
services = await client.getServices(url);
|
|
2873
|
+
} else {
|
|
2874
|
+
isRegistry = true;
|
|
2875
|
+
const params = new URLSearchParams();
|
|
2876
|
+
if (options.query) params.set("q", options.query);
|
|
2877
|
+
if (options.maxPrice) params.set("maxPrice", options.maxPrice);
|
|
2878
|
+
if (options.type) params.set("type", options.type);
|
|
2879
|
+
if (options.tag) params.set("tag", options.tag);
|
|
2880
|
+
const queryString = params.toString();
|
|
2881
|
+
const registryUrl = `${MOLTSPAY_REGISTRY}/registry/services${queryString ? "?" + queryString : ""}`;
|
|
2882
|
+
const res = await fetch(registryUrl);
|
|
2883
|
+
if (!res.ok) {
|
|
2884
|
+
throw new Error(`Registry request failed: ${res.status}`);
|
|
2885
|
+
}
|
|
2886
|
+
services = await res.json();
|
|
2887
|
+
}
|
|
2172
2888
|
if (options.json) {
|
|
2173
2889
|
console.log(JSON.stringify(services, null, 2));
|
|
2174
2890
|
} else {
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2891
|
+
const serviceList = services.services || [];
|
|
2892
|
+
if (isRegistry) {
|
|
2893
|
+
if (options.query) {
|
|
2894
|
+
console.log(`
|
|
2895
|
+
\u{1F50D} Search: "${options.query}" (${serviceList.length} results)
|
|
2178
2896
|
`);
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2897
|
+
} else {
|
|
2898
|
+
const filters = [];
|
|
2899
|
+
if (options.maxPrice) filters.push(`max $${options.maxPrice}`);
|
|
2900
|
+
if (options.type) filters.push(options.type);
|
|
2901
|
+
if (options.tag) filters.push(`#${options.tag}`);
|
|
2902
|
+
const filterStr = filters.length > 0 ? ` (${filters.join(", ")})` : "";
|
|
2903
|
+
console.log(`
|
|
2904
|
+
\u{1F50D} MoltsPay Registry${filterStr} - ${serviceList.length} services
|
|
2905
|
+
`);
|
|
2906
|
+
}
|
|
2907
|
+
for (const svc of serviceList) {
|
|
2908
|
+
const name = (svc.name || svc.id).slice(0, 30).padEnd(30);
|
|
2909
|
+
const price = `$${svc.price}`.padEnd(8);
|
|
2910
|
+
const type = (svc.type || "unknown").padEnd(14);
|
|
2911
|
+
const provider = `@${svc.provider?.username || "unknown"}`;
|
|
2912
|
+
console.log(` ${name} ${price} ${type} ${provider}`);
|
|
2913
|
+
}
|
|
2914
|
+
if (serviceList.length > 0) {
|
|
2915
|
+
console.log(`
|
|
2916
|
+
\u{1F4A1} Use: moltspay pay <provider-url> <service-id>
|
|
2917
|
+
`);
|
|
2918
|
+
}
|
|
2183
2919
|
} else {
|
|
2184
|
-
|
|
2185
|
-
|
|
2920
|
+
if (services.provider) {
|
|
2921
|
+
console.log(`
|
|
2922
|
+
\u{1F3EA} ${services.provider.name}
|
|
2186
2923
|
`);
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
console.log(`
|
|
2924
|
+
console.log(` ${services.provider.description || ""}`);
|
|
2925
|
+
console.log(` Wallet: ${services.provider.wallet}`);
|
|
2926
|
+
const chains = services.provider.chains ? Array.isArray(services.provider.chains) ? services.provider.chains.map((c) => typeof c === "string" ? c : c.chain).join(", ") : services.provider.chains : services.provider.chain || "base";
|
|
2927
|
+
console.log(` Chains: ${chains}`);
|
|
2928
|
+
} else {
|
|
2929
|
+
console.log(`
|
|
2930
|
+
\u{1F3EA} Provider Services
|
|
2931
|
+
`);
|
|
2932
|
+
console.log(` ${serviceList.length} services available`);
|
|
2196
2933
|
}
|
|
2197
|
-
|
|
2198
|
-
|
|
2934
|
+
console.log("\n\u{1F4E6} Services:\n");
|
|
2935
|
+
for (const svc of serviceList) {
|
|
2936
|
+
const status = svc.available !== false ? "\u2705" : "\u274C";
|
|
2937
|
+
console.log(` ${status} ${svc.id || svc.name}`);
|
|
2938
|
+
console.log(` ${svc.name} - $${svc.price} ${svc.currency}`);
|
|
2939
|
+
if (svc.description) {
|
|
2940
|
+
console.log(` ${svc.description}`);
|
|
2941
|
+
}
|
|
2942
|
+
if (svc.provider && !services.provider) {
|
|
2943
|
+
console.log(` Provider: ${svc.provider.name || svc.provider.username}`);
|
|
2944
|
+
}
|
|
2945
|
+
console.log("");
|
|
2199
2946
|
}
|
|
2200
|
-
console.log("");
|
|
2201
2947
|
}
|
|
2202
2948
|
}
|
|
2203
2949
|
} catch (err) {
|
|
@@ -2411,8 +3157,8 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
|
|
|
2411
3157
|
process.exit(1);
|
|
2412
3158
|
}
|
|
2413
3159
|
});
|
|
2414
|
-
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, or
|
|
2415
|
-
const client = new MoltsPayClient();
|
|
3160
|
+
program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, or tempo_moderato).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
|
|
3161
|
+
const client = new MoltsPayClient({ configDir: options.configDir });
|
|
2416
3162
|
if (!client.isInitialized) {
|
|
2417
3163
|
console.error("\u274C Wallet not initialized. Run: npx moltspay init");
|
|
2418
3164
|
process.exit(1);
|
|
@@ -2441,13 +3187,9 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2441
3187
|
params.image_base64 = imageData.toString("base64");
|
|
2442
3188
|
}
|
|
2443
3189
|
}
|
|
2444
|
-
if (!params.prompt) {
|
|
2445
|
-
console.error("\u274C Missing prompt. Use --prompt or pass JSON params");
|
|
2446
|
-
process.exit(1);
|
|
2447
|
-
}
|
|
2448
3190
|
const chain = options.chain?.toLowerCase();
|
|
2449
|
-
if (chain && !["base", "polygon", "base_sepolia"].includes(chain)) {
|
|
2450
|
-
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia`);
|
|
3191
|
+
if (chain && !["base", "polygon", "base_sepolia", "tempo_moderato"].includes(chain)) {
|
|
3192
|
+
console.error(`\u274C Unknown chain: ${chain}. Supported: base, polygon, base_sepolia, tempo_moderato`);
|
|
2451
3193
|
process.exit(1);
|
|
2452
3194
|
}
|
|
2453
3195
|
const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
|
|
@@ -2478,10 +3220,22 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
|
|
|
2478
3220
|
console.log("");
|
|
2479
3221
|
}
|
|
2480
3222
|
try {
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
3223
|
+
let result;
|
|
3224
|
+
if (chain === "tempo_moderato") {
|
|
3225
|
+
if (!options.json) {
|
|
3226
|
+
console.log(" Protocol: MPP (Machine Payments Protocol)");
|
|
3227
|
+
console.log("");
|
|
3228
|
+
}
|
|
3229
|
+
const mppUrl = server.includes(service) ? server : `${server}/${service}`;
|
|
3230
|
+
result = await client.payWithMPP(mppUrl, {
|
|
3231
|
+
body: params
|
|
3232
|
+
});
|
|
3233
|
+
} else {
|
|
3234
|
+
result = await client.pay(server, service, params, {
|
|
3235
|
+
token,
|
|
3236
|
+
chain
|
|
3237
|
+
});
|
|
3238
|
+
}
|
|
2485
3239
|
if (options.json) {
|
|
2486
3240
|
console.log(JSON.stringify(result));
|
|
2487
3241
|
} else {
|