moltspay 1.5.0 → 2.0.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 +143 -0
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/index.mjs.map +1 -1
- package/dist/{cdp-DeohBe1o.d.ts → cdp-B5PhDUTC.d.ts} +1 -1
- package/dist/{cdp-p_eHuQpb.d.mts → cdp-D-5Hg3y_.d.mts} +1 -1
- package/dist/chains/index.d.mts +37 -1
- package/dist/chains/index.d.ts +37 -1
- package/dist/chains/index.js +22 -0
- package/dist/chains/index.js.map +1 -1
- package/dist/chains/index.mjs +19 -0
- package/dist/chains/index.mjs.map +1 -1
- package/dist/cli/index.js +1941 -204
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +1938 -201
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +46 -0
- package/dist/client/index.d.ts +46 -0
- package/dist/client/index.js +1059 -118
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +1055 -116
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/web/index.d.mts +434 -0
- package/dist/client/web/index.mjs +1300 -0
- package/dist/client/web/index.mjs.map +1 -0
- package/dist/facilitators/index.d.mts +185 -6
- package/dist/facilitators/index.d.ts +185 -6
- package/dist/facilitators/index.js +497 -13
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +492 -13
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/index.d.mts +77 -2
- package/dist/index.d.ts +77 -2
- package/dist/index.js +1865 -172
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1854 -167
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.js +1062 -123
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/index.mjs +1059 -120
- package/dist/mcp/index.mjs.map +1 -1
- package/dist/{registry-OsEO2dOu.d.mts → registry-DIo0WNoO.d.mts} +8 -1
- package/dist/{registry-OsEO2dOu.d.ts → registry-DIo0WNoO.d.ts} +8 -1
- package/dist/server/index.d.mts +137 -2
- package/dist/server/index.d.ts +137 -2
- package/dist/server/index.js +757 -30
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +757 -30
- package/dist/server/index.mjs.map +1 -1
- package/dist/verify/index.js.map +1 -1
- package/dist/verify/index.mjs.map +1 -1
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +15 -2
- package/schemas/moltspay.services.schema.json +100 -6
- package/scripts/postinstall.js +91 -0
package/dist/index.js
CHANGED
|
@@ -38,8 +38,10 @@ __export(index_exports, {
|
|
|
38
38
|
FacilitatorRegistry: () => FacilitatorRegistry,
|
|
39
39
|
MoltsPayClient: () => MoltsPayClient,
|
|
40
40
|
MoltsPayServer: () => MoltsPayServer,
|
|
41
|
+
alipayLog: () => alipayLog,
|
|
41
42
|
createRegistry: () => createRegistry,
|
|
42
43
|
createWallet: () => createWallet,
|
|
44
|
+
getAlipayLogLevel: () => getAlipayLogLevel,
|
|
43
45
|
getCDPWalletAddress: () => getCDPWalletAddress,
|
|
44
46
|
getChain: () => getChain,
|
|
45
47
|
getChainById: () => getChainById,
|
|
@@ -51,6 +53,10 @@ __export(index_exports, {
|
|
|
51
53
|
listChains: () => listChains,
|
|
52
54
|
loadCDPWallet: () => loadCDPWallet,
|
|
53
55
|
loadWallet: () => loadWallet,
|
|
56
|
+
resetIntentCache: () => resetIntentCache,
|
|
57
|
+
resetWalletCache: () => resetWalletCache,
|
|
58
|
+
setAlipayLogLevel: () => setAlipayLogLevel,
|
|
59
|
+
setAlipayLogSink: () => setAlipayLogSink,
|
|
54
60
|
verifyPayment: () => verifyPayment,
|
|
55
61
|
waitForTransaction: () => waitForTransaction,
|
|
56
62
|
walletExists: () => walletExists
|
|
@@ -288,6 +294,9 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
288
294
|
}
|
|
289
295
|
};
|
|
290
296
|
|
|
297
|
+
// src/facilitators/tempo.ts
|
|
298
|
+
var import_ethers = require("ethers");
|
|
299
|
+
|
|
291
300
|
// src/chains/index.ts
|
|
292
301
|
var CHAINS = {
|
|
293
302
|
// ============ Mainnet ============
|
|
@@ -467,6 +476,10 @@ function listChains() {
|
|
|
467
476
|
function getChainById(chainId) {
|
|
468
477
|
return Object.values(CHAINS).find((c) => c.chainId === chainId);
|
|
469
478
|
}
|
|
479
|
+
var ALIPAY_CHAIN_ID = "alipay";
|
|
480
|
+
function isAlipayChainId(id) {
|
|
481
|
+
return id === ALIPAY_CHAIN_ID;
|
|
482
|
+
}
|
|
470
483
|
var ERC20_ABI = [
|
|
471
484
|
"function balanceOf(address owner) view returns (uint256)",
|
|
472
485
|
"function transfer(address to, uint256 amount) returns (bool)",
|
|
@@ -483,15 +496,38 @@ var ERC20_ABI = [
|
|
|
483
496
|
|
|
484
497
|
// src/facilitators/tempo.ts
|
|
485
498
|
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
499
|
+
var TIP20_PERMIT_ABI = [
|
|
500
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
501
|
+
"function transferFrom(address from, address to, uint256 value) returns (bool)"
|
|
502
|
+
];
|
|
486
503
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
487
504
|
name = "tempo";
|
|
488
505
|
displayName = "Tempo Testnet";
|
|
489
506
|
supportedNetworks = ["eip155:42431"];
|
|
490
507
|
// Tempo Moderato
|
|
491
508
|
rpcUrl;
|
|
509
|
+
settlerWallet = null;
|
|
492
510
|
constructor() {
|
|
493
511
|
super();
|
|
494
512
|
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
513
|
+
const settlerKey = process.env.TEMPO_SETTLER_KEY;
|
|
514
|
+
if (settlerKey) {
|
|
515
|
+
try {
|
|
516
|
+
const provider = new import_ethers.ethers.JsonRpcProvider(this.rpcUrl);
|
|
517
|
+
this.settlerWallet = new import_ethers.ethers.Wallet(settlerKey, provider);
|
|
518
|
+
} catch (err) {
|
|
519
|
+
console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
|
|
520
|
+
this.settlerWallet = null;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
|
|
526
|
+
* Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
|
|
527
|
+
* Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
|
|
528
|
+
*/
|
|
529
|
+
getSpenderAddress() {
|
|
530
|
+
return this.settlerWallet?.address ?? null;
|
|
495
531
|
}
|
|
496
532
|
async healthCheck() {
|
|
497
533
|
const start = Date.now();
|
|
@@ -517,6 +553,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
517
553
|
}
|
|
518
554
|
}
|
|
519
555
|
async verify(paymentPayload, requirements) {
|
|
556
|
+
const inner = paymentPayload.payload;
|
|
557
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
558
|
+
return this.verifyPermit(inner, requirements);
|
|
559
|
+
}
|
|
560
|
+
return this.verifyTxHash(paymentPayload, requirements);
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Structural validation of an EIP-2612 permit payload. Does NOT submit
|
|
564
|
+
* anything on-chain — actual submission happens in settlePermit().
|
|
565
|
+
*/
|
|
566
|
+
async verifyPermit(payload, requirements) {
|
|
567
|
+
if (!this.settlerWallet) {
|
|
568
|
+
return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
569
|
+
}
|
|
570
|
+
const p = payload.permit;
|
|
571
|
+
if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
|
|
572
|
+
return { valid: false, error: "Invalid permit payload: missing fields" };
|
|
573
|
+
}
|
|
574
|
+
if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
|
|
575
|
+
return {
|
|
576
|
+
valid: false,
|
|
577
|
+
error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
const deadline = BigInt(p.deadline);
|
|
581
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
582
|
+
if (deadline <= now) {
|
|
583
|
+
return { valid: false, error: "Permit deadline has expired" };
|
|
584
|
+
}
|
|
585
|
+
if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
|
|
586
|
+
return {
|
|
587
|
+
valid: false,
|
|
588
|
+
error: `Permit value ${p.value} is less than required ${requirements.amount}`
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
return { valid: true, details: { scheme: "permit", owner: p.owner } };
|
|
592
|
+
}
|
|
593
|
+
async verifyTxHash(paymentPayload, requirements) {
|
|
520
594
|
try {
|
|
521
595
|
const tempoPayload = paymentPayload.payload;
|
|
522
596
|
if (!tempoPayload?.txHash) {
|
|
@@ -574,7 +648,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
574
648
|
}
|
|
575
649
|
}
|
|
576
650
|
async settle(paymentPayload, requirements) {
|
|
577
|
-
const
|
|
651
|
+
const inner = paymentPayload.payload;
|
|
652
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
653
|
+
return this.settlePermit(inner, requirements);
|
|
654
|
+
}
|
|
655
|
+
const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
|
|
578
656
|
if (!verifyResult.valid) {
|
|
579
657
|
return { success: false, error: verifyResult.error };
|
|
580
658
|
}
|
|
@@ -585,6 +663,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
585
663
|
status: "settled"
|
|
586
664
|
};
|
|
587
665
|
}
|
|
666
|
+
/**
|
|
667
|
+
* EIP-2612 permit settlement path. Submits two transactions on Tempo:
|
|
668
|
+
* 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
|
|
669
|
+
* 2. pathUSD.transferFrom(owner, payTo, value)
|
|
670
|
+
*
|
|
671
|
+
* The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
|
|
672
|
+
* native tTEMPO required; any held TIP-20 token balance covers fees).
|
|
673
|
+
*/
|
|
674
|
+
async settlePermit(payload, requirements) {
|
|
675
|
+
if (!this.settlerWallet) {
|
|
676
|
+
return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
677
|
+
}
|
|
678
|
+
if (!requirements.asset || !requirements.payTo) {
|
|
679
|
+
return { success: false, error: "Missing asset or payTo in requirements" };
|
|
680
|
+
}
|
|
681
|
+
const verifyResult = await this.verifyPermit(payload, requirements);
|
|
682
|
+
if (!verifyResult.valid) {
|
|
683
|
+
return { success: false, error: verifyResult.error };
|
|
684
|
+
}
|
|
685
|
+
const token = new import_ethers.ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
|
|
686
|
+
const p = payload.permit;
|
|
687
|
+
try {
|
|
688
|
+
const permitTx = await token.permit(
|
|
689
|
+
p.owner,
|
|
690
|
+
p.spender,
|
|
691
|
+
p.value,
|
|
692
|
+
p.deadline,
|
|
693
|
+
p.v,
|
|
694
|
+
p.r,
|
|
695
|
+
p.s
|
|
696
|
+
);
|
|
697
|
+
await permitTx.wait();
|
|
698
|
+
const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
|
|
699
|
+
await transferTx.wait();
|
|
700
|
+
return {
|
|
701
|
+
success: true,
|
|
702
|
+
transaction: transferTx.hash,
|
|
703
|
+
status: "settled"
|
|
704
|
+
};
|
|
705
|
+
} catch (err) {
|
|
706
|
+
return {
|
|
707
|
+
success: false,
|
|
708
|
+
error: `Tempo permit settlement failed: ${err.message}`
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
}
|
|
588
712
|
async getTransactionReceipt(txHash) {
|
|
589
713
|
const response = await fetch(this.rpcUrl, {
|
|
590
714
|
method: "POST",
|
|
@@ -827,12 +951,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
827
951
|
return this.spenderAddress;
|
|
828
952
|
}
|
|
829
953
|
async getServerAddress() {
|
|
830
|
-
const { ethers:
|
|
831
|
-
const wallet = new
|
|
954
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
955
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey);
|
|
832
956
|
return wallet.address;
|
|
833
957
|
}
|
|
834
958
|
async recoverIntentSigner(intent, chainId) {
|
|
835
|
-
const { ethers:
|
|
959
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
836
960
|
const domain = {
|
|
837
961
|
...EIP712_DOMAIN,
|
|
838
962
|
chainId
|
|
@@ -846,7 +970,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
846
970
|
nonce: intent.nonce,
|
|
847
971
|
deadline: intent.deadline
|
|
848
972
|
};
|
|
849
|
-
const recoveredAddress =
|
|
973
|
+
const recoveredAddress = ethers7.verifyTypedData(
|
|
850
974
|
domain,
|
|
851
975
|
INTENT_TYPES,
|
|
852
976
|
message,
|
|
@@ -890,10 +1014,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
890
1014
|
return result.result || "0x0";
|
|
891
1015
|
}
|
|
892
1016
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
893
|
-
const { ethers:
|
|
894
|
-
const provider = new
|
|
895
|
-
const wallet = new
|
|
896
|
-
const tokenContract = new
|
|
1017
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
1018
|
+
const provider = new ethers7.JsonRpcProvider(rpcUrl);
|
|
1019
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey, provider);
|
|
1020
|
+
const tokenContract = new ethers7.Contract(token, [
|
|
897
1021
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
898
1022
|
], wallet);
|
|
899
1023
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -1119,16 +1243,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
|
|
|
1119
1243
|
return this.supportedNetworks.includes(network);
|
|
1120
1244
|
}
|
|
1121
1245
|
};
|
|
1122
|
-
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
|
|
1246
|
+
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
|
|
1123
1247
|
const chainConfig = SOLANA_CHAINS[chain];
|
|
1124
|
-
const
|
|
1248
|
+
const conn = connection ?? new import_web32.Connection(chainConfig.rpc, "confirmed");
|
|
1125
1249
|
const mint = new import_web32.PublicKey(chainConfig.tokens.USDC.mint);
|
|
1126
1250
|
const actualFeePayer = feePayerPubkey || senderPubkey;
|
|
1127
1251
|
const senderATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, senderPubkey);
|
|
1128
1252
|
const recipientATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, recipientPubkey);
|
|
1129
1253
|
const transaction = new import_web32.Transaction();
|
|
1130
1254
|
try {
|
|
1131
|
-
await (0, import_spl_token.getAccount)(
|
|
1255
|
+
await (0, import_spl_token.getAccount)(conn, recipientATA);
|
|
1132
1256
|
} catch {
|
|
1133
1257
|
transaction.add(
|
|
1134
1258
|
(0, import_spl_token.createAssociatedTokenAccountInstruction)(
|
|
@@ -1159,12 +1283,380 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
|
|
|
1159
1283
|
// decimals
|
|
1160
1284
|
)
|
|
1161
1285
|
);
|
|
1162
|
-
const { blockhash, lastValidBlockHeight } = await
|
|
1286
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
|
|
1163
1287
|
transaction.recentBlockhash = blockhash;
|
|
1164
1288
|
transaction.feePayer = actualFeePayer;
|
|
1165
1289
|
return transaction;
|
|
1166
1290
|
}
|
|
1167
1291
|
|
|
1292
|
+
// src/facilitators/alipay.ts
|
|
1293
|
+
var import_node_crypto2 = __toESM(require("crypto"));
|
|
1294
|
+
|
|
1295
|
+
// src/facilitators/alipay/rsa2.ts
|
|
1296
|
+
var import_node_crypto = __toESM(require("crypto"));
|
|
1297
|
+
function rsa2Sign(data, privateKeyPem) {
|
|
1298
|
+
const signer = import_node_crypto.default.createSign("RSA-SHA256");
|
|
1299
|
+
signer.update(data, "utf-8");
|
|
1300
|
+
signer.end();
|
|
1301
|
+
return signer.sign(privateKeyPem, "base64");
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
// src/facilitators/alipay/encoding.ts
|
|
1305
|
+
function base64url(input) {
|
|
1306
|
+
return Buffer.from(input, "utf-8").toString("base64url");
|
|
1307
|
+
}
|
|
1308
|
+
function decodeBase64UrlWithPadFix(input) {
|
|
1309
|
+
const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
|
|
1310
|
+
const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
|
|
1311
|
+
return Buffer.from(padded, "base64").toString("utf-8");
|
|
1312
|
+
}
|
|
1313
|
+
function toPem(key, kind) {
|
|
1314
|
+
const trimmed = key.trim();
|
|
1315
|
+
if (trimmed.includes("-----BEGIN")) return trimmed;
|
|
1316
|
+
const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
|
|
1317
|
+
const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
|
|
1318
|
+
return `-----BEGIN ${label}-----
|
|
1319
|
+
${body}
|
|
1320
|
+
-----END ${label}-----
|
|
1321
|
+
`;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// src/facilitators/alipay/openapi.ts
|
|
1325
|
+
function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
1326
|
+
const pad = (n) => String(n).padStart(2, "0");
|
|
1327
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
1328
|
+
}
|
|
1329
|
+
function buildSigningString(params) {
|
|
1330
|
+
return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
|
|
1331
|
+
}
|
|
1332
|
+
function responseWrapperKey(method) {
|
|
1333
|
+
return `${method.replace(/\./g, "_")}_response`;
|
|
1334
|
+
}
|
|
1335
|
+
async function alipayOpenApiCall(method, bizContent, config) {
|
|
1336
|
+
const publicParams = {
|
|
1337
|
+
app_id: config.app_id,
|
|
1338
|
+
method,
|
|
1339
|
+
format: "JSON",
|
|
1340
|
+
charset: "utf-8",
|
|
1341
|
+
sign_type: config.sign_type ?? "RSA2",
|
|
1342
|
+
timestamp: formatAlipayTimestamp(),
|
|
1343
|
+
version: "1.0",
|
|
1344
|
+
biz_content: JSON.stringify(bizContent)
|
|
1345
|
+
};
|
|
1346
|
+
const signingString = buildSigningString(publicParams);
|
|
1347
|
+
const sign = rsa2Sign(signingString, config.private_key_pem);
|
|
1348
|
+
const body = new URLSearchParams({ ...publicParams, sign }).toString();
|
|
1349
|
+
const response = await fetch(config.gateway_url, {
|
|
1350
|
+
method: "POST",
|
|
1351
|
+
headers: {
|
|
1352
|
+
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
|
|
1353
|
+
},
|
|
1354
|
+
body
|
|
1355
|
+
});
|
|
1356
|
+
if (!response.ok) {
|
|
1357
|
+
const text = await response.text().catch(() => "<unreadable>");
|
|
1358
|
+
throw new Error(
|
|
1359
|
+
`Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
const json = await response.json();
|
|
1363
|
+
const wrapperKey = responseWrapperKey(method);
|
|
1364
|
+
const business = json[wrapperKey];
|
|
1365
|
+
if (business === void 0 || business === null || typeof business !== "object") {
|
|
1366
|
+
throw new Error(
|
|
1367
|
+
`Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
|
|
1368
|
+
);
|
|
1369
|
+
}
|
|
1370
|
+
return business;
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// src/facilitators/alipay.ts
|
|
1374
|
+
var ALIPAY_NETWORK = "alipay";
|
|
1375
|
+
var ALIPAY_SCHEME = "alipay-aipay";
|
|
1376
|
+
var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
|
|
1377
|
+
var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
|
|
1378
|
+
var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
|
|
1379
|
+
var ALIPAY_SIGNING_FIELDS = [
|
|
1380
|
+
"amount",
|
|
1381
|
+
"currency",
|
|
1382
|
+
"goods_name",
|
|
1383
|
+
"out_trade_no",
|
|
1384
|
+
"pay_before",
|
|
1385
|
+
"resource_id",
|
|
1386
|
+
"seller_id",
|
|
1387
|
+
"service_id"
|
|
1388
|
+
];
|
|
1389
|
+
var AlipayFacilitator = class extends BaseFacilitator {
|
|
1390
|
+
name = "alipay";
|
|
1391
|
+
displayName = "Alipay AI \u6536";
|
|
1392
|
+
supportedNetworks = [ALIPAY_NETWORK];
|
|
1393
|
+
config;
|
|
1394
|
+
constructor(config) {
|
|
1395
|
+
super();
|
|
1396
|
+
this.config = {
|
|
1397
|
+
gateway_url: ALIPAY_GATEWAY_PROD,
|
|
1398
|
+
sign_type: "RSA2",
|
|
1399
|
+
...config
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Build the 402 challenge for a service: signs the 8-field payload with
|
|
1404
|
+
* RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
|
|
1405
|
+
* `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
|
|
1406
|
+
*/
|
|
1407
|
+
async createPaymentRequirements(opts) {
|
|
1408
|
+
if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
|
|
1409
|
+
throw new Error(
|
|
1410
|
+
`AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
|
|
1411
|
+
);
|
|
1412
|
+
}
|
|
1413
|
+
const now = /* @__PURE__ */ new Date();
|
|
1414
|
+
const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
|
|
1415
|
+
const payBefore = formatPayBefore(now);
|
|
1416
|
+
const signedFields = {
|
|
1417
|
+
amount: opts.priceCny,
|
|
1418
|
+
currency: "CNY",
|
|
1419
|
+
goods_name: opts.goodsName,
|
|
1420
|
+
out_trade_no: outTradeNo,
|
|
1421
|
+
pay_before: payBefore,
|
|
1422
|
+
resource_id: opts.resourceId,
|
|
1423
|
+
seller_id: this.config.seller_id,
|
|
1424
|
+
service_id: opts.serviceId
|
|
1425
|
+
};
|
|
1426
|
+
const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
|
|
1427
|
+
const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
|
|
1428
|
+
const challenge = {
|
|
1429
|
+
protocol: {
|
|
1430
|
+
out_trade_no: outTradeNo,
|
|
1431
|
+
amount: signedFields.amount,
|
|
1432
|
+
currency: signedFields.currency,
|
|
1433
|
+
resource_id: signedFields.resource_id,
|
|
1434
|
+
pay_before: payBefore,
|
|
1435
|
+
seller_signature,
|
|
1436
|
+
seller_sign_type: this.config.sign_type ?? "RSA2",
|
|
1437
|
+
seller_unique_id: this.config.seller_id
|
|
1438
|
+
},
|
|
1439
|
+
method: {
|
|
1440
|
+
seller_name: this.config.seller_name,
|
|
1441
|
+
seller_id: this.config.seller_id,
|
|
1442
|
+
seller_app_id: this.config.app_id,
|
|
1443
|
+
goods_name: signedFields.goods_name,
|
|
1444
|
+
seller_unique_id_key: "seller_id",
|
|
1445
|
+
service_id: signedFields.service_id
|
|
1446
|
+
}
|
|
1447
|
+
};
|
|
1448
|
+
const paymentNeededHeader = base64url(JSON.stringify(challenge));
|
|
1449
|
+
const x402Accepts = {
|
|
1450
|
+
scheme: ALIPAY_SCHEME,
|
|
1451
|
+
network: ALIPAY_NETWORK,
|
|
1452
|
+
asset: "CNY",
|
|
1453
|
+
amount: opts.priceCny,
|
|
1454
|
+
payTo: this.config.seller_id,
|
|
1455
|
+
maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
|
|
1456
|
+
extra: {
|
|
1457
|
+
payment_needed_header: paymentNeededHeader,
|
|
1458
|
+
out_trade_no: outTradeNo,
|
|
1459
|
+
pay_before: payBefore,
|
|
1460
|
+
service_id: signedFields.service_id
|
|
1461
|
+
}
|
|
1462
|
+
};
|
|
1463
|
+
return { x402Accepts, paymentNeededHeader };
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Verify a `Payment-Proof` by calling
|
|
1467
|
+
* `alipay.aipay.agent.payment.verify` against the Alipay Open API.
|
|
1468
|
+
*
|
|
1469
|
+
* The proof is conveyed via `paymentPayload.payload`, which may be:
|
|
1470
|
+
* - a raw Base64URL string (the `Payment-Proof` header value), or
|
|
1471
|
+
* - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
|
|
1472
|
+
*
|
|
1473
|
+
* All failure modes (malformed payload, network errors, Alipay
|
|
1474
|
+
* `code != 10000`) return `{ valid: false, error }`. No exception
|
|
1475
|
+
* escapes, regardless of client-supplied input.
|
|
1476
|
+
*/
|
|
1477
|
+
async verify(paymentPayload, _requirements) {
|
|
1478
|
+
try {
|
|
1479
|
+
const proofHeader = extractProofHeader(paymentPayload.payload);
|
|
1480
|
+
const decoded = decodeProof(proofHeader);
|
|
1481
|
+
const response = await alipayOpenApiCall(
|
|
1482
|
+
"alipay.aipay.agent.payment.verify",
|
|
1483
|
+
{
|
|
1484
|
+
payment_proof: decoded.protocol.payment_proof,
|
|
1485
|
+
trade_no: decoded.protocol.trade_no,
|
|
1486
|
+
client_session: decoded.method.client_session
|
|
1487
|
+
},
|
|
1488
|
+
this.getOpenApiConfig()
|
|
1489
|
+
);
|
|
1490
|
+
if (response.code !== "10000") {
|
|
1491
|
+
return {
|
|
1492
|
+
valid: false,
|
|
1493
|
+
error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
|
|
1494
|
+
details: {
|
|
1495
|
+
code: response.code,
|
|
1496
|
+
sub_code: response.sub_code,
|
|
1497
|
+
sub_msg: response.sub_msg
|
|
1498
|
+
}
|
|
1499
|
+
};
|
|
1500
|
+
}
|
|
1501
|
+
return {
|
|
1502
|
+
valid: true,
|
|
1503
|
+
details: {
|
|
1504
|
+
trade_no: response.trade_no ?? decoded.protocol.trade_no,
|
|
1505
|
+
amount: response.amount,
|
|
1506
|
+
out_trade_no: response.out_trade_no,
|
|
1507
|
+
resource_id: response.resource_id,
|
|
1508
|
+
active: response.active
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
} catch (e) {
|
|
1512
|
+
return {
|
|
1513
|
+
valid: false,
|
|
1514
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
/**
|
|
1519
|
+
* Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
|
|
1520
|
+
* service resource has been returned to the buyer.
|
|
1521
|
+
*
|
|
1522
|
+
* Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
|
|
1523
|
+
* "履约确认失败"), this is **fire-and-forget**: the caller (registry /
|
|
1524
|
+
* server) is expected to log fulfillment failures but NOT roll back
|
|
1525
|
+
* the already-delivered resource.
|
|
1526
|
+
*
|
|
1527
|
+
* Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
|
|
1528
|
+
* does the same; the redundant Base64URL decode is negligible).
|
|
1529
|
+
*/
|
|
1530
|
+
async settle(paymentPayload, _requirements) {
|
|
1531
|
+
try {
|
|
1532
|
+
const proofHeader = extractProofHeader(paymentPayload.payload);
|
|
1533
|
+
const decoded = decodeProof(proofHeader);
|
|
1534
|
+
const tradeNo = decoded.protocol.trade_no;
|
|
1535
|
+
const response = await alipayOpenApiCall(
|
|
1536
|
+
"alipay.aipay.agent.fulfillment.confirm",
|
|
1537
|
+
{ trade_no: tradeNo },
|
|
1538
|
+
this.getOpenApiConfig()
|
|
1539
|
+
);
|
|
1540
|
+
if (response.code !== "10000") {
|
|
1541
|
+
return {
|
|
1542
|
+
success: false,
|
|
1543
|
+
transaction: tradeNo,
|
|
1544
|
+
error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
|
|
1545
|
+
status: "fulfillment_failed"
|
|
1546
|
+
};
|
|
1547
|
+
}
|
|
1548
|
+
return {
|
|
1549
|
+
success: true,
|
|
1550
|
+
transaction: tradeNo,
|
|
1551
|
+
status: "fulfilled"
|
|
1552
|
+
};
|
|
1553
|
+
} catch (e) {
|
|
1554
|
+
return {
|
|
1555
|
+
success: false,
|
|
1556
|
+
error: e instanceof Error ? e.message : String(e)
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
/**
|
|
1561
|
+
* Validate that the configured RSA keys parse and that the Open API
|
|
1562
|
+
* gateway is reachable. Does NOT make a real business API call (would
|
|
1563
|
+
* burn quota and could appear as a legitimate verify attempt in logs).
|
|
1564
|
+
*/
|
|
1565
|
+
async healthCheck() {
|
|
1566
|
+
const start = Date.now();
|
|
1567
|
+
try {
|
|
1568
|
+
import_node_crypto2.default.createPrivateKey(this.config.private_key_pem);
|
|
1569
|
+
} catch (e) {
|
|
1570
|
+
return {
|
|
1571
|
+
healthy: false,
|
|
1572
|
+
error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
try {
|
|
1576
|
+
import_node_crypto2.default.createPublicKey(this.config.alipay_public_key_pem);
|
|
1577
|
+
} catch (e) {
|
|
1578
|
+
return {
|
|
1579
|
+
healthy: false,
|
|
1580
|
+
error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
|
|
1581
|
+
};
|
|
1582
|
+
}
|
|
1583
|
+
const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
|
|
1584
|
+
const controller = new AbortController();
|
|
1585
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
1586
|
+
const response = await fetch(gatewayUrl, {
|
|
1587
|
+
method: "HEAD",
|
|
1588
|
+
signal: controller.signal
|
|
1589
|
+
}).catch(() => null);
|
|
1590
|
+
clearTimeout(timeout);
|
|
1591
|
+
const latencyMs = Date.now() - start;
|
|
1592
|
+
if (!response) {
|
|
1593
|
+
return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
|
|
1594
|
+
}
|
|
1595
|
+
return { healthy: true, latencyMs };
|
|
1596
|
+
}
|
|
1597
|
+
/** Bundle the facilitator config into the shape openapi.ts wants. */
|
|
1598
|
+
getOpenApiConfig() {
|
|
1599
|
+
return {
|
|
1600
|
+
gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
|
|
1601
|
+
app_id: this.config.app_id,
|
|
1602
|
+
private_key_pem: this.config.private_key_pem,
|
|
1603
|
+
alipay_public_key_pem: this.config.alipay_public_key_pem,
|
|
1604
|
+
sign_type: this.config.sign_type
|
|
1605
|
+
};
|
|
1606
|
+
}
|
|
1607
|
+
};
|
|
1608
|
+
function generateOutTradeNo() {
|
|
1609
|
+
return "VID" + import_node_crypto2.default.randomBytes(22).toString("base64url").slice(0, 29);
|
|
1610
|
+
}
|
|
1611
|
+
function formatPayBefore(now) {
|
|
1612
|
+
const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
|
|
1613
|
+
return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
1614
|
+
}
|
|
1615
|
+
function extractProofHeader(payload) {
|
|
1616
|
+
if (typeof payload === "string") {
|
|
1617
|
+
if (payload.length === 0) {
|
|
1618
|
+
throw new Error("alipay Payment-Proof is empty");
|
|
1619
|
+
}
|
|
1620
|
+
return payload;
|
|
1621
|
+
}
|
|
1622
|
+
if (payload !== null && typeof payload === "object") {
|
|
1623
|
+
const obj = payload;
|
|
1624
|
+
const candidate = obj.paymentProof ?? obj.proofHeader;
|
|
1625
|
+
if (typeof candidate === "string" && candidate.length > 0) {
|
|
1626
|
+
return candidate;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
throw new Error(
|
|
1630
|
+
"alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
|
|
1631
|
+
);
|
|
1632
|
+
}
|
|
1633
|
+
function decodeProof(proofHeader) {
|
|
1634
|
+
let parsed;
|
|
1635
|
+
try {
|
|
1636
|
+
parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
|
|
1637
|
+
} catch (e) {
|
|
1638
|
+
throw new Error(
|
|
1639
|
+
`failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
|
|
1640
|
+
);
|
|
1641
|
+
}
|
|
1642
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
1643
|
+
throw new Error("decoded Payment-Proof is not an object");
|
|
1644
|
+
}
|
|
1645
|
+
const obj = parsed;
|
|
1646
|
+
const protocol = obj.protocol;
|
|
1647
|
+
const method = obj.method;
|
|
1648
|
+
if (!protocol || typeof protocol.payment_proof !== "string") {
|
|
1649
|
+
throw new Error("decoded Payment-Proof missing protocol.payment_proof");
|
|
1650
|
+
}
|
|
1651
|
+
if (typeof protocol.trade_no !== "string") {
|
|
1652
|
+
throw new Error("decoded Payment-Proof missing protocol.trade_no");
|
|
1653
|
+
}
|
|
1654
|
+
if (!method || typeof method.client_session !== "string") {
|
|
1655
|
+
throw new Error("decoded Payment-Proof missing method.client_session");
|
|
1656
|
+
}
|
|
1657
|
+
return parsed;
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1168
1660
|
// src/facilitators/registry.ts
|
|
1169
1661
|
var import_web33 = require("@solana/web3.js");
|
|
1170
1662
|
var import_bs58 = __toESM(require("bs58"));
|
|
@@ -1189,6 +1681,7 @@ var FacilitatorRegistry = class {
|
|
|
1189
1681
|
}
|
|
1190
1682
|
return new SolanaFacilitator({ feePayerKeypair });
|
|
1191
1683
|
});
|
|
1684
|
+
this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
|
|
1192
1685
|
this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
|
|
1193
1686
|
}
|
|
1194
1687
|
/**
|
|
@@ -1423,6 +1916,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
|
|
|
1423
1916
|
var MPP_AUTH_HEADER = "authorization";
|
|
1424
1917
|
var MPP_WWW_AUTH_HEADER = "www-authenticate";
|
|
1425
1918
|
var MPP_RECEIPT_HEADER = "payment-receipt";
|
|
1919
|
+
var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
|
|
1920
|
+
var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
|
|
1921
|
+
function headerSafe(value) {
|
|
1922
|
+
return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
|
|
1923
|
+
}
|
|
1426
1924
|
var TOKEN_ADDRESSES = {
|
|
1427
1925
|
"eip155:8453": {
|
|
1428
1926
|
USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
@@ -1495,9 +1993,13 @@ var TOKEN_DOMAINS = {
|
|
|
1495
1993
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1496
1994
|
},
|
|
1497
1995
|
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1996
|
+
// Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
|
|
1997
|
+
// See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
|
|
1998
|
+
// All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
|
|
1999
|
+
// the token symbol with first letter capitalized + version "1".
|
|
1498
2000
|
"eip155:42431": {
|
|
1499
|
-
USDC: { name: "
|
|
1500
|
-
USDT: { name: "
|
|
2001
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
2002
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
1501
2003
|
},
|
|
1502
2004
|
// BNB Smart Chain mainnet
|
|
1503
2005
|
"eip155:56": {
|
|
@@ -1554,6 +2056,8 @@ var MoltsPayServer = class {
|
|
|
1554
2056
|
registry;
|
|
1555
2057
|
networkId;
|
|
1556
2058
|
useMainnet;
|
|
2059
|
+
/** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
|
|
2060
|
+
alipayFacilitator = null;
|
|
1557
2061
|
constructor(servicesPath, options = {}) {
|
|
1558
2062
|
loadEnvFile2();
|
|
1559
2063
|
const content = (0, import_fs2.readFileSync)(servicesPath, "utf-8");
|
|
@@ -1575,7 +2079,38 @@ var MoltsPayServer = class {
|
|
|
1575
2079
|
cdp: { useMainnet: this.useMainnet }
|
|
1576
2080
|
}
|
|
1577
2081
|
};
|
|
2082
|
+
const providerAlipay = this.manifest.provider.alipay;
|
|
2083
|
+
if (providerAlipay) {
|
|
2084
|
+
try {
|
|
2085
|
+
const baseDir = path2.dirname(servicesPath);
|
|
2086
|
+
const resolvePem = (p, kind) => toPem((0, import_fs2.readFileSync)(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
|
|
2087
|
+
const alipayFacilitatorConfig = {
|
|
2088
|
+
seller_id: providerAlipay.seller_id,
|
|
2089
|
+
app_id: providerAlipay.app_id,
|
|
2090
|
+
seller_name: providerAlipay.seller_name,
|
|
2091
|
+
service_id_default: providerAlipay.service_id_default,
|
|
2092
|
+
private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
|
|
2093
|
+
alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
|
|
2094
|
+
gateway_url: providerAlipay.gateway_url,
|
|
2095
|
+
sign_type: providerAlipay.sign_type
|
|
2096
|
+
};
|
|
2097
|
+
facilitatorConfig.config = {
|
|
2098
|
+
...facilitatorConfig.config,
|
|
2099
|
+
alipay: alipayFacilitatorConfig
|
|
2100
|
+
};
|
|
2101
|
+
facilitatorConfig.fallback = facilitatorConfig.fallback || [];
|
|
2102
|
+
if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
|
|
2103
|
+
facilitatorConfig.fallback.push("alipay");
|
|
2104
|
+
}
|
|
2105
|
+
} catch (err) {
|
|
2106
|
+
throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
1578
2109
|
this.registry = new FacilitatorRegistry(facilitatorConfig);
|
|
2110
|
+
if (providerAlipay) {
|
|
2111
|
+
this.alipayFacilitator = this.registry.get("alipay");
|
|
2112
|
+
console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
|
|
2113
|
+
}
|
|
1579
2114
|
const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
|
|
1580
2115
|
console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
|
|
1581
2116
|
console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
|
|
@@ -1665,14 +2200,63 @@ var MoltsPayServer = class {
|
|
|
1665
2200
|
console.log(` GET /health - Health check (incl. facilitators)`);
|
|
1666
2201
|
});
|
|
1667
2202
|
}
|
|
2203
|
+
/**
|
|
2204
|
+
* Apply CORS response headers according to the `cors` option.
|
|
2205
|
+
*
|
|
2206
|
+
* Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
|
|
2207
|
+
* and works for every browser client whose origin does not need to send cookies.
|
|
2208
|
+
*
|
|
2209
|
+
* `cors: false`: emit no CORS headers. Same-origin only.
|
|
2210
|
+
* `cors: string[]`: origin allowlist — echo the origin back iff it matches.
|
|
2211
|
+
* `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
|
|
2212
|
+
*
|
|
2213
|
+
* The required-for-Web response headers are always exposed when CORS is active:
|
|
2214
|
+
* `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
|
|
2215
|
+
*/
|
|
2216
|
+
applyCorsHeaders(req, res) {
|
|
2217
|
+
const cors = this.options.cors;
|
|
2218
|
+
if (cors === false) {
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
2221
|
+
const requestOrigin = req.headers.origin ?? "*";
|
|
2222
|
+
if (cors === void 0 || cors === true) {
|
|
2223
|
+
this.writeCorsHeaders(res, "*");
|
|
2224
|
+
return;
|
|
2225
|
+
}
|
|
2226
|
+
if (Array.isArray(cors)) {
|
|
2227
|
+
if (cors.includes(requestOrigin)) {
|
|
2228
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
2229
|
+
res.setHeader("Vary", "Origin");
|
|
2230
|
+
}
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
const opt = cors;
|
|
2234
|
+
const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
|
|
2235
|
+
if (!isAllowed) {
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
2239
|
+
res.setHeader("Vary", "Origin");
|
|
2240
|
+
if (opt.credentials) {
|
|
2241
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
2242
|
+
}
|
|
2243
|
+
const maxAge = opt.maxAge ?? 600;
|
|
2244
|
+
res.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
2245
|
+
}
|
|
2246
|
+
writeCorsHeaders(res, origin) {
|
|
2247
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
2248
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2249
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
|
|
2250
|
+
res.setHeader(
|
|
2251
|
+
"Access-Control-Expose-Headers",
|
|
2252
|
+
"X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
|
|
2253
|
+
);
|
|
2254
|
+
}
|
|
1668
2255
|
/**
|
|
1669
2256
|
* Handle incoming request
|
|
1670
2257
|
*/
|
|
1671
2258
|
async handleRequest(req, res) {
|
|
1672
|
-
|
|
1673
|
-
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1674
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1675
|
-
res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
|
|
2259
|
+
this.applyCorsHeaders(req, res);
|
|
1676
2260
|
if (req.method === "OPTIONS") {
|
|
1677
2261
|
res.writeHead(204);
|
|
1678
2262
|
res.end();
|
|
@@ -1692,7 +2276,8 @@ var MoltsPayServer = class {
|
|
|
1692
2276
|
if (url.pathname === "/execute" && req.method === "POST") {
|
|
1693
2277
|
const body = await this.readBody(req);
|
|
1694
2278
|
const paymentHeader = req.headers[PAYMENT_HEADER];
|
|
1695
|
-
|
|
2279
|
+
const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
|
|
2280
|
+
return await this.handleExecute(body, paymentHeader, res, proofHeader);
|
|
1696
2281
|
}
|
|
1697
2282
|
if (url.pathname === "/proxy" && req.method === "POST") {
|
|
1698
2283
|
const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
|
|
@@ -1710,7 +2295,8 @@ var MoltsPayServer = class {
|
|
|
1710
2295
|
const body = req.method === "POST" ? await this.readBody(req) : {};
|
|
1711
2296
|
const authHeader = req.headers[MPP_AUTH_HEADER];
|
|
1712
2297
|
const x402Header = req.headers[PAYMENT_HEADER];
|
|
1713
|
-
|
|
2298
|
+
const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
|
|
2299
|
+
return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
|
|
1714
2300
|
}
|
|
1715
2301
|
this.sendJson(res, 404, { error: "Not found" });
|
|
1716
2302
|
} catch (err) {
|
|
@@ -1807,7 +2393,7 @@ var MoltsPayServer = class {
|
|
|
1807
2393
|
/**
|
|
1808
2394
|
* POST /execute - Execute service with x402 payment
|
|
1809
2395
|
*/
|
|
1810
|
-
async handleExecute(body, paymentHeader, res) {
|
|
2396
|
+
async handleExecute(body, paymentHeader, res, proofHeader) {
|
|
1811
2397
|
const { service, params } = body;
|
|
1812
2398
|
if (!service) {
|
|
1813
2399
|
return this.sendJson(res, 400, { error: "Missing service" });
|
|
@@ -1821,6 +2407,15 @@ var MoltsPayServer = class {
|
|
|
1821
2407
|
return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
|
|
1822
2408
|
}
|
|
1823
2409
|
}
|
|
2410
|
+
if (proofHeader) {
|
|
2411
|
+
const alipayPayment = {
|
|
2412
|
+
x402Version: X402_VERSION2,
|
|
2413
|
+
scheme: ALIPAY_SCHEME,
|
|
2414
|
+
network: ALIPAY_NETWORK,
|
|
2415
|
+
payload: proofHeader
|
|
2416
|
+
};
|
|
2417
|
+
return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
|
|
2418
|
+
}
|
|
1824
2419
|
if (!paymentHeader) {
|
|
1825
2420
|
return this.sendPaymentRequired(skill.config, res);
|
|
1826
2421
|
}
|
|
@@ -1831,6 +2426,11 @@ var MoltsPayServer = class {
|
|
|
1831
2426
|
} catch {
|
|
1832
2427
|
return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
|
|
1833
2428
|
}
|
|
2429
|
+
const payScheme = payment.accepted?.scheme || payment.scheme;
|
|
2430
|
+
const payNetwork = payment.accepted?.network || payment.network;
|
|
2431
|
+
if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
|
|
2432
|
+
return this.handleAlipayExecute(skill, params || {}, payment, res);
|
|
2433
|
+
}
|
|
1834
2434
|
const validation = this.validatePayment(payment, skill.config);
|
|
1835
2435
|
if (!validation.valid) {
|
|
1836
2436
|
return this.sendJson(res, 402, { error: validation.error });
|
|
@@ -1895,6 +2495,14 @@ var MoltsPayServer = class {
|
|
|
1895
2495
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
1896
2496
|
} catch (err) {
|
|
1897
2497
|
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
2498
|
+
settlement = { success: false, error: err.message, facilitator: "none" };
|
|
2499
|
+
}
|
|
2500
|
+
if (!settlement?.success) {
|
|
2501
|
+
return this.sendJson(res, 402, {
|
|
2502
|
+
error: "Payment settlement failed",
|
|
2503
|
+
message: settlement?.error || "Settlement returned no success state",
|
|
2504
|
+
facilitator: settlement?.facilitator
|
|
2505
|
+
});
|
|
1898
2506
|
}
|
|
1899
2507
|
}
|
|
1900
2508
|
const responseHeaders = {};
|
|
@@ -1915,13 +2523,111 @@ var MoltsPayServer = class {
|
|
|
1915
2523
|
payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
|
|
1916
2524
|
}, responseHeaders);
|
|
1917
2525
|
}
|
|
2526
|
+
/**
|
|
2527
|
+
* Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
|
|
2528
|
+
*
|
|
2529
|
+
* Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
|
|
2530
|
+
* validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
|
|
2531
|
+
* (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
|
|
2532
|
+
* §5.1: a confirm failure is logged but does NOT fail the already-delivered
|
|
2533
|
+
* response (the buyer's payment proof was already verified).
|
|
2534
|
+
*/
|
|
2535
|
+
async handleAlipayExecute(skill, params, payment, res) {
|
|
2536
|
+
if (!this.alipayFacilitator) {
|
|
2537
|
+
return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
|
|
2538
|
+
}
|
|
2539
|
+
const requirements = {
|
|
2540
|
+
scheme: ALIPAY_SCHEME,
|
|
2541
|
+
network: ALIPAY_NETWORK,
|
|
2542
|
+
asset: "CNY",
|
|
2543
|
+
amount: skill.config.alipay?.price_cny || "0",
|
|
2544
|
+
payTo: this.manifest.provider.alipay?.seller_id || "",
|
|
2545
|
+
maxTimeoutSeconds: 1800
|
|
2546
|
+
};
|
|
2547
|
+
console.log(`[MoltsPay] Verifying Alipay payment...`);
|
|
2548
|
+
const verifyResult = await this.registry.verify(payment, requirements);
|
|
2549
|
+
if (!verifyResult.valid) {
|
|
2550
|
+
return this.sendJson(res, 402, {
|
|
2551
|
+
error: `Payment verification failed: ${verifyResult.error}`,
|
|
2552
|
+
facilitator: verifyResult.facilitator
|
|
2553
|
+
});
|
|
2554
|
+
}
|
|
2555
|
+
console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
|
|
2556
|
+
const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
|
|
2557
|
+
console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
|
|
2558
|
+
let result;
|
|
2559
|
+
try {
|
|
2560
|
+
result = await Promise.race([
|
|
2561
|
+
skill.handler(params),
|
|
2562
|
+
new Promise(
|
|
2563
|
+
(_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
|
|
2564
|
+
)
|
|
2565
|
+
]);
|
|
2566
|
+
} catch (err) {
|
|
2567
|
+
console.error("[MoltsPay] Skill execution failed:", err.message);
|
|
2568
|
+
return this.sendJson(res, 500, {
|
|
2569
|
+
error: "Service execution failed",
|
|
2570
|
+
message: err.message
|
|
2571
|
+
});
|
|
2572
|
+
}
|
|
2573
|
+
let settlement;
|
|
2574
|
+
try {
|
|
2575
|
+
settlement = await this.registry.settle(payment, requirements);
|
|
2576
|
+
if (settlement.success) {
|
|
2577
|
+
console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
|
|
2578
|
+
} else {
|
|
2579
|
+
console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
|
|
2580
|
+
}
|
|
2581
|
+
} catch (err) {
|
|
2582
|
+
console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
|
|
2583
|
+
settlement = { success: false, error: err.message, facilitator: "alipay" };
|
|
2584
|
+
}
|
|
2585
|
+
const responseHeaders = {};
|
|
2586
|
+
if (settlement.success) {
|
|
2587
|
+
responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
|
|
2588
|
+
success: true,
|
|
2589
|
+
transaction: settlement.transaction,
|
|
2590
|
+
network: ALIPAY_NETWORK,
|
|
2591
|
+
facilitator: settlement.facilitator
|
|
2592
|
+
})).toString("base64");
|
|
2593
|
+
}
|
|
2594
|
+
this.sendJson(res, 200, {
|
|
2595
|
+
success: true,
|
|
2596
|
+
result,
|
|
2597
|
+
payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
|
|
2598
|
+
}, responseHeaders);
|
|
2599
|
+
}
|
|
2600
|
+
/**
|
|
2601
|
+
* Build the Alipay 402 challenge for a service, or null when the alipay rail
|
|
2602
|
+
* isn't configured for this server or this service. Returns the x402
|
|
2603
|
+
* `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
|
|
2604
|
+
* 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
|
|
2605
|
+
*/
|
|
2606
|
+
async buildAlipayChallenge(config) {
|
|
2607
|
+
if (!this.alipayFacilitator || !config.alipay) return null;
|
|
2608
|
+
try {
|
|
2609
|
+
const req = await this.alipayFacilitator.createPaymentRequirements({
|
|
2610
|
+
serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
|
|
2611
|
+
priceCny: config.alipay.price_cny,
|
|
2612
|
+
goodsName: config.alipay.goods_name,
|
|
2613
|
+
resourceId: `/execute?service=${config.id}`
|
|
2614
|
+
});
|
|
2615
|
+
return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
|
|
2616
|
+
} catch (err) {
|
|
2617
|
+
console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
|
|
2618
|
+
return null;
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
1918
2621
|
/**
|
|
1919
2622
|
* Handle MPP (Machine Payments Protocol) request
|
|
1920
2623
|
* Supports both x402 and MPP protocols on service endpoints
|
|
1921
2624
|
*/
|
|
1922
|
-
async handleMPPRequest(skill, body, authHeader, x402Header, res) {
|
|
2625
|
+
async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
|
|
1923
2626
|
const config = skill.config;
|
|
1924
2627
|
const params = body || {};
|
|
2628
|
+
if (proofHeader) {
|
|
2629
|
+
return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
|
|
2630
|
+
}
|
|
1925
2631
|
if (x402Header) {
|
|
1926
2632
|
return await this.handleExecute({ service: config.id, params }, x402Header, res);
|
|
1927
2633
|
}
|
|
@@ -2027,7 +2733,7 @@ var MoltsPayServer = class {
|
|
|
2027
2733
|
/**
|
|
2028
2734
|
* Return 402 with both x402 and MPP payment requirements
|
|
2029
2735
|
*/
|
|
2030
|
-
sendMPPPaymentRequired(config, res) {
|
|
2736
|
+
async sendMPPPaymentRequired(config, res) {
|
|
2031
2737
|
const acceptedTokens = getAcceptedCurrencies(config);
|
|
2032
2738
|
const providerChains = this.getProviderChains();
|
|
2033
2739
|
const accepts = [];
|
|
@@ -2038,6 +2744,10 @@ var MoltsPayServer = class {
|
|
|
2038
2744
|
}
|
|
2039
2745
|
}
|
|
2040
2746
|
}
|
|
2747
|
+
const alipayChallenge = await this.buildAlipayChallenge(config);
|
|
2748
|
+
if (alipayChallenge) {
|
|
2749
|
+
accepts.push(alipayChallenge.accepts);
|
|
2750
|
+
}
|
|
2041
2751
|
const x402PaymentRequired = {
|
|
2042
2752
|
x402Version: X402_VERSION2,
|
|
2043
2753
|
accepts,
|
|
@@ -2065,7 +2775,7 @@ var MoltsPayServer = class {
|
|
|
2065
2775
|
};
|
|
2066
2776
|
const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
|
|
2067
2777
|
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
2068
|
-
mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
|
|
2778
|
+
mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
|
|
2069
2779
|
}
|
|
2070
2780
|
const headers = {
|
|
2071
2781
|
"Content-Type": "application/problem+json",
|
|
@@ -2074,6 +2784,9 @@ var MoltsPayServer = class {
|
|
|
2074
2784
|
if (mppWwwAuth) {
|
|
2075
2785
|
headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
|
|
2076
2786
|
}
|
|
2787
|
+
if (alipayChallenge) {
|
|
2788
|
+
headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
|
|
2789
|
+
}
|
|
2077
2790
|
res.writeHead(402, headers);
|
|
2078
2791
|
res.end(JSON.stringify({
|
|
2079
2792
|
type: "https://paymentauth.org/problems/payment-required",
|
|
@@ -2100,7 +2813,7 @@ var MoltsPayServer = class {
|
|
|
2100
2813
|
* Return 402 with x402 payment requirements (v2 format)
|
|
2101
2814
|
* Includes requirements for all chains and all accepted currencies
|
|
2102
2815
|
*/
|
|
2103
|
-
sendPaymentRequired(config, res) {
|
|
2816
|
+
async sendPaymentRequired(config, res) {
|
|
2104
2817
|
const acceptedTokens = getAcceptedCurrencies(config);
|
|
2105
2818
|
const providerChains = this.getProviderChains();
|
|
2106
2819
|
const accepts = [];
|
|
@@ -2111,6 +2824,10 @@ var MoltsPayServer = class {
|
|
|
2111
2824
|
}
|
|
2112
2825
|
}
|
|
2113
2826
|
}
|
|
2827
|
+
const alipayChallenge = await this.buildAlipayChallenge(config);
|
|
2828
|
+
if (alipayChallenge) {
|
|
2829
|
+
accepts.push(alipayChallenge.accepts);
|
|
2830
|
+
}
|
|
2114
2831
|
const acceptedChains = providerChains.map((c) => {
|
|
2115
2832
|
if (c.network === "eip155:8453") return "base";
|
|
2116
2833
|
if (c.network === "eip155:137") return "polygon";
|
|
@@ -2128,10 +2845,14 @@ var MoltsPayServer = class {
|
|
|
2128
2845
|
}
|
|
2129
2846
|
};
|
|
2130
2847
|
const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
|
|
2131
|
-
|
|
2848
|
+
const headers = {
|
|
2132
2849
|
"Content-Type": "application/json",
|
|
2133
2850
|
[PAYMENT_REQUIRED_HEADER]: encoded
|
|
2134
|
-
}
|
|
2851
|
+
};
|
|
2852
|
+
if (alipayChallenge) {
|
|
2853
|
+
headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
|
|
2854
|
+
}
|
|
2855
|
+
res.writeHead(402, headers);
|
|
2135
2856
|
res.end(JSON.stringify({
|
|
2136
2857
|
error: "Payment required",
|
|
2137
2858
|
message: `Service requires $${config.price} ${config.currency}`,
|
|
@@ -2149,7 +2870,7 @@ var MoltsPayServer = class {
|
|
|
2149
2870
|
}
|
|
2150
2871
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2151
2872
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
2152
|
-
if (scheme !== "exact") {
|
|
2873
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2153
2874
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
2154
2875
|
}
|
|
2155
2876
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -2171,8 +2892,10 @@ var MoltsPayServer = class {
|
|
|
2171
2892
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
2172
2893
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
2173
2894
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
2895
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
2896
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
2174
2897
|
const requirements = {
|
|
2175
|
-
scheme
|
|
2898
|
+
scheme,
|
|
2176
2899
|
network: selectedNetwork,
|
|
2177
2900
|
asset: tokenAddress,
|
|
2178
2901
|
amount: amountInUnits,
|
|
@@ -2200,8 +2923,18 @@ var MoltsPayServer = class {
|
|
|
2200
2923
|
};
|
|
2201
2924
|
}
|
|
2202
2925
|
}
|
|
2203
|
-
|
|
2204
|
-
|
|
2926
|
+
if (isTempo) {
|
|
2927
|
+
const tempoFacilitator = this.registry.get("tempo");
|
|
2928
|
+
const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
|
|
2929
|
+
if (tempoSpender) {
|
|
2930
|
+
requirements.extra = {
|
|
2931
|
+
...requirements.extra || {},
|
|
2932
|
+
tempoSpender
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
return requirements;
|
|
2937
|
+
}
|
|
2205
2938
|
/**
|
|
2206
2939
|
* Detect which token is being used in the payment
|
|
2207
2940
|
* Checks across all supported networks
|
|
@@ -2226,12 +2959,12 @@ var MoltsPayServer = class {
|
|
|
2226
2959
|
return accepted.includes(token);
|
|
2227
2960
|
}
|
|
2228
2961
|
async readBody(req) {
|
|
2229
|
-
return new Promise((
|
|
2962
|
+
return new Promise((resolve2, reject) => {
|
|
2230
2963
|
let body = "";
|
|
2231
2964
|
req.on("data", (chunk) => body += chunk);
|
|
2232
2965
|
req.on("end", () => {
|
|
2233
2966
|
try {
|
|
2234
|
-
|
|
2967
|
+
resolve2(body ? JSON.parse(body) : {});
|
|
2235
2968
|
} catch {
|
|
2236
2969
|
reject(new Error("Invalid JSON"));
|
|
2237
2970
|
}
|
|
@@ -2334,7 +3067,7 @@ var MoltsPayServer = class {
|
|
|
2334
3067
|
}
|
|
2335
3068
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2336
3069
|
const network = payment.accepted?.network || payment.network;
|
|
2337
|
-
if (scheme !== "exact") {
|
|
3070
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2338
3071
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
2339
3072
|
}
|
|
2340
3073
|
const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
|
|
@@ -2489,7 +3222,7 @@ var MoltsPayServer = class {
|
|
|
2489
3222
|
};
|
|
2490
3223
|
const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
|
|
2491
3224
|
const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
|
|
2492
|
-
const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
|
|
3225
|
+
const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
|
|
2493
3226
|
res.writeHead(402, {
|
|
2494
3227
|
"Content-Type": "application/problem+json",
|
|
2495
3228
|
[MPP_WWW_AUTH_HEADER]: wwwAuth
|
|
@@ -2654,11 +3387,11 @@ var MoltsPayServer = class {
|
|
|
2654
3387
|
}
|
|
2655
3388
|
};
|
|
2656
3389
|
|
|
2657
|
-
// src/client/index.ts
|
|
3390
|
+
// src/client/node/index.ts
|
|
2658
3391
|
var import_fs4 = require("fs");
|
|
2659
|
-
var
|
|
2660
|
-
var
|
|
2661
|
-
var
|
|
3392
|
+
var import_os3 = require("os");
|
|
3393
|
+
var import_path3 = require("path");
|
|
3394
|
+
var import_ethers3 = require("ethers");
|
|
2662
3395
|
|
|
2663
3396
|
// src/wallet/solana.ts
|
|
2664
3397
|
var import_web34 = require("@solana/web3.js");
|
|
@@ -2687,11 +3420,924 @@ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
|
|
|
2687
3420
|
}
|
|
2688
3421
|
}
|
|
2689
3422
|
|
|
2690
|
-
// src/client/index.ts
|
|
2691
|
-
var
|
|
3423
|
+
// src/client/node/index.ts
|
|
3424
|
+
var import_web36 = require("@solana/web3.js");
|
|
3425
|
+
|
|
3426
|
+
// src/client/core/types.ts
|
|
2692
3427
|
var X402_VERSION3 = 2;
|
|
2693
3428
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
2694
3429
|
var PAYMENT_HEADER2 = "x-payment";
|
|
3430
|
+
|
|
3431
|
+
// src/client/core/chain-map.ts
|
|
3432
|
+
var NETWORK_TO_CHAIN = {
|
|
3433
|
+
"eip155:8453": "base",
|
|
3434
|
+
"eip155:137": "polygon",
|
|
3435
|
+
"eip155:84532": "base_sepolia",
|
|
3436
|
+
"eip155:42431": "tempo_moderato",
|
|
3437
|
+
"eip155:56": "bnb",
|
|
3438
|
+
"eip155:97": "bnb_testnet",
|
|
3439
|
+
"solana:mainnet": "solana",
|
|
3440
|
+
"solana:devnet": "solana_devnet"
|
|
3441
|
+
};
|
|
3442
|
+
var CHAIN_TO_NETWORK2 = Object.fromEntries(
|
|
3443
|
+
Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
|
|
3444
|
+
);
|
|
3445
|
+
function networkToChainName(network) {
|
|
3446
|
+
return NETWORK_TO_CHAIN[network] ?? null;
|
|
3447
|
+
}
|
|
3448
|
+
|
|
3449
|
+
// src/client/core/base64.ts
|
|
3450
|
+
var BufferCtor = globalThis.Buffer;
|
|
3451
|
+
|
|
3452
|
+
// src/client/core/errors.ts
|
|
3453
|
+
var MoltsPayError = class extends Error {
|
|
3454
|
+
code;
|
|
3455
|
+
constructor(code, message) {
|
|
3456
|
+
super(message);
|
|
3457
|
+
this.name = "MoltsPayError";
|
|
3458
|
+
this.code = code;
|
|
3459
|
+
}
|
|
3460
|
+
};
|
|
3461
|
+
|
|
3462
|
+
// src/client/core/eip3009.ts
|
|
3463
|
+
var EIP3009_TYPES = {
|
|
3464
|
+
TransferWithAuthorization: [
|
|
3465
|
+
{ name: "from", type: "address" },
|
|
3466
|
+
{ name: "to", type: "address" },
|
|
3467
|
+
{ name: "value", type: "uint256" },
|
|
3468
|
+
{ name: "validAfter", type: "uint256" },
|
|
3469
|
+
{ name: "validBefore", type: "uint256" },
|
|
3470
|
+
{ name: "nonce", type: "bytes32" }
|
|
3471
|
+
]
|
|
3472
|
+
};
|
|
3473
|
+
function buildEIP3009TypedData(args) {
|
|
3474
|
+
const validAfter = args.validAfter ?? "0";
|
|
3475
|
+
const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
|
|
3476
|
+
const authorization = {
|
|
3477
|
+
from: args.from,
|
|
3478
|
+
to: args.to,
|
|
3479
|
+
value: args.value,
|
|
3480
|
+
validAfter,
|
|
3481
|
+
validBefore,
|
|
3482
|
+
nonce: args.nonce
|
|
3483
|
+
};
|
|
3484
|
+
return {
|
|
3485
|
+
domain: {
|
|
3486
|
+
name: args.tokenName,
|
|
3487
|
+
version: args.tokenVersion,
|
|
3488
|
+
chainId: args.chainId,
|
|
3489
|
+
verifyingContract: args.tokenAddress
|
|
3490
|
+
},
|
|
3491
|
+
types: EIP3009_TYPES,
|
|
3492
|
+
primaryType: "TransferWithAuthorization",
|
|
3493
|
+
message: authorization
|
|
3494
|
+
};
|
|
3495
|
+
}
|
|
3496
|
+
|
|
3497
|
+
// src/client/core/bnb-intent.ts
|
|
3498
|
+
var BNB_INTENT_TYPES = {
|
|
3499
|
+
PaymentIntent: [
|
|
3500
|
+
{ name: "from", type: "address" },
|
|
3501
|
+
{ name: "to", type: "address" },
|
|
3502
|
+
{ name: "amount", type: "uint256" },
|
|
3503
|
+
{ name: "token", type: "address" },
|
|
3504
|
+
{ name: "service", type: "string" },
|
|
3505
|
+
{ name: "nonce", type: "uint256" },
|
|
3506
|
+
{ name: "deadline", type: "uint256" }
|
|
3507
|
+
]
|
|
3508
|
+
};
|
|
3509
|
+
var BNB_DOMAIN_NAME = "MoltsPay";
|
|
3510
|
+
var BNB_DOMAIN_VERSION = "1";
|
|
3511
|
+
function buildBnbIntentTypedData(args) {
|
|
3512
|
+
const intent = {
|
|
3513
|
+
from: args.from,
|
|
3514
|
+
to: args.to,
|
|
3515
|
+
amount: args.amount,
|
|
3516
|
+
token: args.tokenAddress,
|
|
3517
|
+
service: args.service,
|
|
3518
|
+
nonce: args.nonce,
|
|
3519
|
+
deadline: args.deadline
|
|
3520
|
+
};
|
|
3521
|
+
return {
|
|
3522
|
+
domain: {
|
|
3523
|
+
name: BNB_DOMAIN_NAME,
|
|
3524
|
+
version: BNB_DOMAIN_VERSION,
|
|
3525
|
+
chainId: args.chainId
|
|
3526
|
+
},
|
|
3527
|
+
types: BNB_INTENT_TYPES,
|
|
3528
|
+
primaryType: "PaymentIntent",
|
|
3529
|
+
message: intent
|
|
3530
|
+
};
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
// src/client/node/signer.ts
|
|
3534
|
+
var import_ethers2 = require("ethers");
|
|
3535
|
+
var import_web35 = require("@solana/web3.js");
|
|
3536
|
+
var NodeSigner = class {
|
|
3537
|
+
evmWallet;
|
|
3538
|
+
getSolanaKeypair;
|
|
3539
|
+
constructor(evmWallet, options = {}) {
|
|
3540
|
+
this.evmWallet = evmWallet;
|
|
3541
|
+
this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
|
|
3542
|
+
}
|
|
3543
|
+
async getEvmAddress() {
|
|
3544
|
+
return this.evmWallet.address;
|
|
3545
|
+
}
|
|
3546
|
+
async getSolanaAddress() {
|
|
3547
|
+
const kp = this.getSolanaKeypair();
|
|
3548
|
+
return kp ? kp.publicKey.toBase58() : null;
|
|
3549
|
+
}
|
|
3550
|
+
async signTypedData(envelope) {
|
|
3551
|
+
const mutableTypes = {};
|
|
3552
|
+
for (const [key, fields] of Object.entries(envelope.types)) {
|
|
3553
|
+
mutableTypes[key] = [...fields];
|
|
3554
|
+
}
|
|
3555
|
+
return this.evmWallet.signTypedData(
|
|
3556
|
+
envelope.domain,
|
|
3557
|
+
mutableTypes,
|
|
3558
|
+
envelope.message
|
|
3559
|
+
);
|
|
3560
|
+
}
|
|
3561
|
+
async sendEvmTransaction(args) {
|
|
3562
|
+
const chain = findChainByChainId(args.chainId);
|
|
3563
|
+
if (!chain) {
|
|
3564
|
+
throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
|
|
3565
|
+
}
|
|
3566
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
3567
|
+
const connected = this.evmWallet.connect(provider);
|
|
3568
|
+
const tx = await connected.sendTransaction({
|
|
3569
|
+
to: args.to,
|
|
3570
|
+
data: args.data,
|
|
3571
|
+
value: args.value ? BigInt(args.value) : 0n
|
|
3572
|
+
});
|
|
3573
|
+
return tx.hash;
|
|
3574
|
+
}
|
|
3575
|
+
async signSolanaTransaction(args) {
|
|
3576
|
+
const kp = this.getSolanaKeypair();
|
|
3577
|
+
if (!kp) {
|
|
3578
|
+
throw new Error("signSolanaTransaction: no Solana wallet configured");
|
|
3579
|
+
}
|
|
3580
|
+
const tx = import_web35.Transaction.from(Buffer.from(args.transactionBase64, "base64"));
|
|
3581
|
+
if (args.partialSign) {
|
|
3582
|
+
tx.partialSign(kp);
|
|
3583
|
+
} else {
|
|
3584
|
+
tx.sign(kp);
|
|
3585
|
+
}
|
|
3586
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
3587
|
+
}
|
|
3588
|
+
};
|
|
3589
|
+
function findChainByChainId(chainId) {
|
|
3590
|
+
for (const cfg of Object.values(CHAINS)) {
|
|
3591
|
+
if (cfg.chainId === chainId) {
|
|
3592
|
+
return cfg;
|
|
3593
|
+
}
|
|
3594
|
+
}
|
|
3595
|
+
return void 0;
|
|
3596
|
+
}
|
|
3597
|
+
|
|
3598
|
+
// src/client/alipay/index.ts
|
|
3599
|
+
var import_node_crypto3 = require("crypto");
|
|
3600
|
+
var import_promises = require("fs/promises");
|
|
3601
|
+
var import_os2 = require("os");
|
|
3602
|
+
var import_path2 = require("path");
|
|
3603
|
+
|
|
3604
|
+
// src/client/alipay/cli.ts
|
|
3605
|
+
var import_child_process = require("child_process");
|
|
3606
|
+
|
|
3607
|
+
// src/client/alipay/log.ts
|
|
3608
|
+
var RANK = { off: 0, info: 1, debug: 2 };
|
|
3609
|
+
function normalizeLevel(v) {
|
|
3610
|
+
const s = (v ?? "").toLowerCase();
|
|
3611
|
+
return s === "info" || s === "debug" ? s : "off";
|
|
3612
|
+
}
|
|
3613
|
+
var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
|
|
3614
|
+
var sink = (line) => {
|
|
3615
|
+
process.stderr.write(line + "\n");
|
|
3616
|
+
};
|
|
3617
|
+
function setAlipayLogLevel(next) {
|
|
3618
|
+
level = next;
|
|
3619
|
+
}
|
|
3620
|
+
function getAlipayLogLevel() {
|
|
3621
|
+
return level;
|
|
3622
|
+
}
|
|
3623
|
+
function setAlipayLogSink(fn) {
|
|
3624
|
+
sink = fn;
|
|
3625
|
+
}
|
|
3626
|
+
function enabledFor(want) {
|
|
3627
|
+
return RANK[level] >= RANK[want];
|
|
3628
|
+
}
|
|
3629
|
+
function render(event, fields) {
|
|
3630
|
+
const parts = ["[moltspay:alipay]", String(fields.ts), event];
|
|
3631
|
+
if (fields.flow) parts.push(`flow=${fields.flow}`);
|
|
3632
|
+
if (fields.step) parts.push(`step=${fields.step}`);
|
|
3633
|
+
if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
|
|
3634
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
3635
|
+
if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
|
|
3636
|
+
parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
|
|
3637
|
+
}
|
|
3638
|
+
return parts.join(" ");
|
|
3639
|
+
}
|
|
3640
|
+
function emit(lvl, event, fields) {
|
|
3641
|
+
if (!enabledFor(lvl)) return;
|
|
3642
|
+
const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
|
|
3643
|
+
try {
|
|
3644
|
+
sink(render(event, full), full);
|
|
3645
|
+
} catch {
|
|
3646
|
+
}
|
|
3647
|
+
}
|
|
3648
|
+
var alipayLog = {
|
|
3649
|
+
info: (event, fields = {}) => emit("info", event, fields),
|
|
3650
|
+
debug: (event, fields = {}) => emit("debug", event, fields),
|
|
3651
|
+
/** True if anything at all would be logged (cheap guard for hot paths). */
|
|
3652
|
+
enabled: () => level !== "off"
|
|
3653
|
+
};
|
|
3654
|
+
async function timeStep(step, flow, fn) {
|
|
3655
|
+
if (!alipayLog.enabled()) return fn();
|
|
3656
|
+
const t0 = Date.now();
|
|
3657
|
+
alipayLog.debug("step.start", { flow, step });
|
|
3658
|
+
try {
|
|
3659
|
+
const out = await fn();
|
|
3660
|
+
alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
|
|
3661
|
+
return out;
|
|
3662
|
+
} catch (err) {
|
|
3663
|
+
alipayLog.info("step.end", {
|
|
3664
|
+
flow,
|
|
3665
|
+
step,
|
|
3666
|
+
ms: Date.now() - t0,
|
|
3667
|
+
ok: false,
|
|
3668
|
+
err: err instanceof Error ? err.message : String(err)
|
|
3669
|
+
});
|
|
3670
|
+
throw err;
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
// src/client/alipay/cli.ts
|
|
3675
|
+
var ALLOWED_ENV = /* @__PURE__ */ new Set([
|
|
3676
|
+
"AIPAY_OUTPUT_CHANNEL",
|
|
3677
|
+
"AIPAY_SESSION_ID",
|
|
3678
|
+
"AIPAY_FRAMEWORK",
|
|
3679
|
+
"AIPAY_MODEL",
|
|
3680
|
+
"AIPAY_OS",
|
|
3681
|
+
// Minimal survival set for spawn to find the binary and a home dir.
|
|
3682
|
+
"PATH",
|
|
3683
|
+
"HOME"
|
|
3684
|
+
]);
|
|
3685
|
+
function filterEnv(env) {
|
|
3686
|
+
return Object.fromEntries(
|
|
3687
|
+
Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
|
|
3688
|
+
);
|
|
3689
|
+
}
|
|
3690
|
+
function makeLineSplitter(onLine) {
|
|
3691
|
+
let buf = "";
|
|
3692
|
+
return (chunk) => {
|
|
3693
|
+
buf += chunk.toString("utf-8");
|
|
3694
|
+
let nl;
|
|
3695
|
+
while ((nl = buf.indexOf("\n")) !== -1) {
|
|
3696
|
+
onLine(buf.slice(0, nl));
|
|
3697
|
+
buf = buf.slice(nl + 1);
|
|
3698
|
+
}
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3701
|
+
function profileEnv(step, flow) {
|
|
3702
|
+
const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
|
|
3703
|
+
const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
|
|
3704
|
+
if (!want || !hook) return {};
|
|
3705
|
+
const steps = want.split(",").map((s) => s.trim());
|
|
3706
|
+
if (!steps.includes("all") && !steps.includes(step)) return {};
|
|
3707
|
+
const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
|
|
3708
|
+
const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
3709
|
+
const out = `${dir}/cli-profile-${safe}.json`;
|
|
3710
|
+
alipayLog.info("cli.profile", { flow, step, out });
|
|
3711
|
+
return {
|
|
3712
|
+
NODE_OPTIONS: `--require=${hook}`,
|
|
3713
|
+
MOLTSPAY_CLI_PROFILE_OUT: out
|
|
3714
|
+
};
|
|
3715
|
+
}
|
|
3716
|
+
var runCli = (args, opts = {}) => {
|
|
3717
|
+
const bin = opts.bin ?? "alipay-bot";
|
|
3718
|
+
const step = opts.step ?? args[0] ?? "(no-arg)";
|
|
3719
|
+
const startedAt = Date.now();
|
|
3720
|
+
const lines = [];
|
|
3721
|
+
const collect = (line) => {
|
|
3722
|
+
lines.push(line);
|
|
3723
|
+
alipayLog.debug("cli.line", {
|
|
3724
|
+
flow: opts.flow,
|
|
3725
|
+
step,
|
|
3726
|
+
ms: Date.now() - startedAt,
|
|
3727
|
+
preview: line.slice(0, 120)
|
|
3728
|
+
});
|
|
3729
|
+
opts.onLine?.(line);
|
|
3730
|
+
};
|
|
3731
|
+
alipayLog.debug("step.start", { flow: opts.flow, step });
|
|
3732
|
+
return new Promise((resolve2, reject) => {
|
|
3733
|
+
const child = (0, import_child_process.spawn)(bin, args, {
|
|
3734
|
+
env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
|
|
3735
|
+
});
|
|
3736
|
+
if (opts.signal) {
|
|
3737
|
+
if (opts.signal.aborted) child.kill("SIGTERM");
|
|
3738
|
+
opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
|
|
3739
|
+
}
|
|
3740
|
+
let sawByte = false;
|
|
3741
|
+
const onChunk = (stream) => (chunk) => {
|
|
3742
|
+
const ms = Date.now() - startedAt;
|
|
3743
|
+
if (!sawByte) {
|
|
3744
|
+
sawByte = true;
|
|
3745
|
+
alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
|
|
3746
|
+
}
|
|
3747
|
+
alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
|
|
3748
|
+
};
|
|
3749
|
+
const onStdout = makeLineSplitter(collect);
|
|
3750
|
+
const onStderr = makeLineSplitter(collect);
|
|
3751
|
+
child.stdout?.on("data", onChunk("out"));
|
|
3752
|
+
child.stderr?.on("data", onChunk("err"));
|
|
3753
|
+
child.stdout?.on("data", onStdout);
|
|
3754
|
+
child.stderr?.on("data", onStderr);
|
|
3755
|
+
child.on("error", (err) => {
|
|
3756
|
+
alipayLog.info("cli.exit", {
|
|
3757
|
+
flow: opts.flow,
|
|
3758
|
+
step,
|
|
3759
|
+
ms: Date.now() - startedAt,
|
|
3760
|
+
ok: false,
|
|
3761
|
+
err: err.message
|
|
3762
|
+
});
|
|
3763
|
+
reject(err);
|
|
3764
|
+
});
|
|
3765
|
+
child.on("close", (code) => {
|
|
3766
|
+
alipayLog.info("cli.exit", {
|
|
3767
|
+
flow: opts.flow,
|
|
3768
|
+
step,
|
|
3769
|
+
ms: Date.now() - startedAt,
|
|
3770
|
+
ok: (code ?? 1) === 0,
|
|
3771
|
+
code: code ?? 1
|
|
3772
|
+
});
|
|
3773
|
+
resolve2({ exitCode: code ?? 1, lines });
|
|
3774
|
+
});
|
|
3775
|
+
});
|
|
3776
|
+
};
|
|
3777
|
+
|
|
3778
|
+
// src/client/alipay/install.ts
|
|
3779
|
+
var import_child_process2 = require("child_process");
|
|
3780
|
+
var import_util = require("util");
|
|
3781
|
+
|
|
3782
|
+
// src/client/alipay/errors.ts
|
|
3783
|
+
var AlipayCliNotFoundError = class extends MoltsPayError {
|
|
3784
|
+
constructor(message) {
|
|
3785
|
+
super("ALIPAY_CLI_NOT_FOUND", message);
|
|
3786
|
+
this.name = "AlipayCliNotFoundError";
|
|
3787
|
+
}
|
|
3788
|
+
};
|
|
3789
|
+
var AlipayCliVersionError = class extends MoltsPayError {
|
|
3790
|
+
constructor(message) {
|
|
3791
|
+
super("ALIPAY_CLI_VERSION", message);
|
|
3792
|
+
this.name = "AlipayCliVersionError";
|
|
3793
|
+
}
|
|
3794
|
+
};
|
|
3795
|
+
var NeedsWalletSetupError = class extends MoltsPayError {
|
|
3796
|
+
constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
|
|
3797
|
+
super("ALIPAY_NEEDS_WALLET_SETUP", message);
|
|
3798
|
+
this.name = "NeedsWalletSetupError";
|
|
3799
|
+
}
|
|
3800
|
+
};
|
|
3801
|
+
var AlipayPaymentRejectedError = class extends MoltsPayError {
|
|
3802
|
+
constructor(message) {
|
|
3803
|
+
super("ALIPAY_PAYMENT_REJECTED", message);
|
|
3804
|
+
this.name = "AlipayPaymentRejectedError";
|
|
3805
|
+
}
|
|
3806
|
+
};
|
|
3807
|
+
var AlipayPaymentTimeoutError = class extends MoltsPayError {
|
|
3808
|
+
constructor(message) {
|
|
3809
|
+
super("ALIPAY_PAYMENT_TIMEOUT", message);
|
|
3810
|
+
this.name = "AlipayPaymentTimeoutError";
|
|
3811
|
+
}
|
|
3812
|
+
};
|
|
3813
|
+
var AlipayProtocolError = class extends MoltsPayError {
|
|
3814
|
+
constructor(message) {
|
|
3815
|
+
super("ALIPAY_PROTOCOL", message);
|
|
3816
|
+
this.name = "AlipayProtocolError";
|
|
3817
|
+
}
|
|
3818
|
+
};
|
|
3819
|
+
var UnsupportedRailError = class extends MoltsPayError {
|
|
3820
|
+
constructor(rail, message) {
|
|
3821
|
+
super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
|
|
3822
|
+
this.rail = rail;
|
|
3823
|
+
this.name = "UnsupportedRailError";
|
|
3824
|
+
}
|
|
3825
|
+
};
|
|
3826
|
+
|
|
3827
|
+
// src/client/alipay/install.ts
|
|
3828
|
+
var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
|
|
3829
|
+
var MIN_CLI_VERSION = "0.3.15";
|
|
3830
|
+
function semverLt(a, b) {
|
|
3831
|
+
const pa = a.split(".").map((n) => parseInt(n, 10));
|
|
3832
|
+
const pb = b.split(".").map((n) => parseInt(n, 10));
|
|
3833
|
+
for (let i = 0; i < 3; i++) {
|
|
3834
|
+
const x = pa[i] ?? 0;
|
|
3835
|
+
const y = pb[i] ?? 0;
|
|
3836
|
+
if (x !== y) return x < y;
|
|
3837
|
+
}
|
|
3838
|
+
return false;
|
|
3839
|
+
}
|
|
3840
|
+
function parseVersion(stdout) {
|
|
3841
|
+
return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
|
|
3842
|
+
}
|
|
3843
|
+
var defaultGetVersion = async () => {
|
|
3844
|
+
const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
|
|
3845
|
+
return stdout;
|
|
3846
|
+
};
|
|
3847
|
+
async function ensureCliUncached(getVersion) {
|
|
3848
|
+
let stdout;
|
|
3849
|
+
try {
|
|
3850
|
+
stdout = await getVersion();
|
|
3851
|
+
} catch (e) {
|
|
3852
|
+
if (e?.code === "ENOENT") {
|
|
3853
|
+
throw new AlipayCliNotFoundError(
|
|
3854
|
+
"alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
|
|
3855
|
+
);
|
|
3856
|
+
}
|
|
3857
|
+
throw e;
|
|
3858
|
+
}
|
|
3859
|
+
const version = parseVersion(stdout);
|
|
3860
|
+
if (!version || semverLt(version, MIN_CLI_VERSION)) {
|
|
3861
|
+
throw new AlipayCliVersionError(
|
|
3862
|
+
`alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
|
|
3863
|
+
);
|
|
3864
|
+
}
|
|
3865
|
+
return version;
|
|
3866
|
+
}
|
|
3867
|
+
var cachedVersion = null;
|
|
3868
|
+
var inflight = null;
|
|
3869
|
+
async function ensureCli(getVersion = defaultGetVersion) {
|
|
3870
|
+
if (getVersion !== defaultGetVersion) {
|
|
3871
|
+
return ensureCliUncached(getVersion);
|
|
3872
|
+
}
|
|
3873
|
+
if (cachedVersion) return cachedVersion;
|
|
3874
|
+
if (!inflight) {
|
|
3875
|
+
inflight = ensureCliUncached(getVersion).then((v) => {
|
|
3876
|
+
cachedVersion = v;
|
|
3877
|
+
return v;
|
|
3878
|
+
}).finally(() => {
|
|
3879
|
+
inflight = null;
|
|
3880
|
+
});
|
|
3881
|
+
}
|
|
3882
|
+
return inflight;
|
|
3883
|
+
}
|
|
3884
|
+
|
|
3885
|
+
// src/client/alipay/poll.ts
|
|
3886
|
+
var POLL_INTERVAL_MS = 3e3;
|
|
3887
|
+
var POLL_MAX_INFLIGHT = 2;
|
|
3888
|
+
function resolveMaxInflight(override) {
|
|
3889
|
+
if (override !== void 0) return Math.max(1, Math.floor(override));
|
|
3890
|
+
const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
|
|
3891
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
3892
|
+
const n = Number(raw);
|
|
3893
|
+
if (Number.isFinite(n) && n >= 1) return Math.floor(n);
|
|
3894
|
+
}
|
|
3895
|
+
return POLL_MAX_INFLIGHT;
|
|
3896
|
+
}
|
|
3897
|
+
function resolveLaunchGap(override) {
|
|
3898
|
+
if (override !== void 0) return Math.max(0, override);
|
|
3899
|
+
const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
|
|
3900
|
+
if (raw !== void 0 && raw.trim() !== "") {
|
|
3901
|
+
const n = Number(raw);
|
|
3902
|
+
if (Number.isFinite(n) && n >= 0) return n;
|
|
3903
|
+
}
|
|
3904
|
+
return POLL_INTERVAL_MS;
|
|
3905
|
+
}
|
|
3906
|
+
function parseStatus(lines) {
|
|
3907
|
+
const raw = lines.join("\n").trim();
|
|
3908
|
+
try {
|
|
3909
|
+
const json = JSON.parse(raw);
|
|
3910
|
+
if (json && typeof json === "object") {
|
|
3911
|
+
if (typeof json.success === "boolean") {
|
|
3912
|
+
if (json.success) return "paid";
|
|
3913
|
+
const err = String(json.errorCode ?? "").toUpperCase();
|
|
3914
|
+
if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
|
|
3915
|
+
if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
|
|
3916
|
+
return "pending";
|
|
3917
|
+
}
|
|
3918
|
+
if (typeof json.code !== "undefined") {
|
|
3919
|
+
if (Number(json.code) === 200) return "paid";
|
|
3920
|
+
const msg = `${json.message ?? ""}${json.reason ?? ""}`;
|
|
3921
|
+
if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
|
|
3922
|
+
return "pending";
|
|
3923
|
+
}
|
|
3924
|
+
if (typeof json.body === "string") {
|
|
3925
|
+
return classifyReportText(json.body);
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
} catch {
|
|
3929
|
+
}
|
|
3930
|
+
return classifyReportText(raw);
|
|
3931
|
+
}
|
|
3932
|
+
function classifyReportText(s) {
|
|
3933
|
+
const text = s.toUpperCase();
|
|
3934
|
+
if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
|
|
3935
|
+
if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
|
|
3936
|
+
if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
|
|
3937
|
+
return "unknown";
|
|
3938
|
+
}
|
|
3939
|
+
function defaultSleep(ms, signal) {
|
|
3940
|
+
return new Promise((resolve2, reject) => {
|
|
3941
|
+
if (signal?.aborted) return reject(new Error("aborted"));
|
|
3942
|
+
const t = setTimeout(resolve2, ms);
|
|
3943
|
+
signal?.addEventListener(
|
|
3944
|
+
"abort",
|
|
3945
|
+
() => {
|
|
3946
|
+
clearTimeout(t);
|
|
3947
|
+
reject(new Error("aborted"));
|
|
3948
|
+
},
|
|
3949
|
+
{ once: true }
|
|
3950
|
+
);
|
|
3951
|
+
});
|
|
3952
|
+
}
|
|
3953
|
+
var LAUNCH = /* @__PURE__ */ Symbol("launch");
|
|
3954
|
+
async function pollUntil(tradeNo, resourceUrl, opts) {
|
|
3955
|
+
const runner = opts.runner ?? runCli;
|
|
3956
|
+
const sleep = opts.sleep ?? defaultSleep;
|
|
3957
|
+
const now = opts.now ?? Date.now;
|
|
3958
|
+
const maxInflight = resolveMaxInflight(opts.maxInflight);
|
|
3959
|
+
const launchGap = resolveLaunchGap(opts.launchIntervalMs);
|
|
3960
|
+
const args = [
|
|
3961
|
+
"402-query-payment-status",
|
|
3962
|
+
"-t",
|
|
3963
|
+
tradeNo,
|
|
3964
|
+
"-r",
|
|
3965
|
+
resourceUrl,
|
|
3966
|
+
...opts.method ? ["-m", opts.method] : [],
|
|
3967
|
+
...opts.data ? ["-d", opts.data] : []
|
|
3968
|
+
];
|
|
3969
|
+
const internal = new AbortController();
|
|
3970
|
+
const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
|
|
3971
|
+
let tick = 0;
|
|
3972
|
+
let lastLaunch = -Infinity;
|
|
3973
|
+
const inflight2 = /* @__PURE__ */ new Set();
|
|
3974
|
+
const launch = () => {
|
|
3975
|
+
tick += 1;
|
|
3976
|
+
const myTick = tick;
|
|
3977
|
+
lastLaunch = now();
|
|
3978
|
+
const run = (async () => {
|
|
3979
|
+
const { lines } = await runner(args, {
|
|
3980
|
+
signal,
|
|
3981
|
+
step: "query-payment-status",
|
|
3982
|
+
flow: tradeNo
|
|
3983
|
+
});
|
|
3984
|
+
const status = parseStatus(lines);
|
|
3985
|
+
alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
|
|
3986
|
+
return { status, lines };
|
|
3987
|
+
})();
|
|
3988
|
+
const tracked = run.finally(() => {
|
|
3989
|
+
inflight2.delete(tracked);
|
|
3990
|
+
});
|
|
3991
|
+
inflight2.add(tracked);
|
|
3992
|
+
};
|
|
3993
|
+
try {
|
|
3994
|
+
for (; ; ) {
|
|
3995
|
+
if (opts.signal?.aborted) throw new Error("aborted");
|
|
3996
|
+
const expired = now() >= opts.deadline;
|
|
3997
|
+
if (expired && inflight2.size === 0) {
|
|
3998
|
+
throw new AlipayPaymentTimeoutError(
|
|
3999
|
+
`Payment ${tradeNo} not completed before pay_before deadline`
|
|
4000
|
+
);
|
|
4001
|
+
}
|
|
4002
|
+
while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
|
|
4003
|
+
launch();
|
|
4004
|
+
}
|
|
4005
|
+
const waiters = [...inflight2];
|
|
4006
|
+
if (!expired && inflight2.size < maxInflight) {
|
|
4007
|
+
const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
|
|
4008
|
+
waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
|
|
4009
|
+
}
|
|
4010
|
+
const winner = await Promise.race(waiters);
|
|
4011
|
+
if (winner === LAUNCH) continue;
|
|
4012
|
+
if (winner.status === "paid") return { status: "paid", lines: winner.lines };
|
|
4013
|
+
if (winner.status === "rejected") {
|
|
4014
|
+
throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
} finally {
|
|
4018
|
+
internal.abort();
|
|
4019
|
+
for (const p of inflight2) p.catch(() => void 0);
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
|
|
4023
|
+
// src/client/alipay/index.ts
|
|
4024
|
+
var TRADE_NO_RE = /^\d{32}$/;
|
|
4025
|
+
function resolveSessionId(explicit, envSession) {
|
|
4026
|
+
return explicit ?? envSession ?? (0, import_node_crypto3.randomUUID)();
|
|
4027
|
+
}
|
|
4028
|
+
function assertTradeNo(t) {
|
|
4029
|
+
if (!TRADE_NO_RE.test(t)) {
|
|
4030
|
+
throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
function parseTradeNo(lines) {
|
|
4034
|
+
for (const line of lines) {
|
|
4035
|
+
const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
|
|
4036
|
+
if (labeled) return labeled[1];
|
|
4037
|
+
const bare = line.match(/\b(\d{32})\b/);
|
|
4038
|
+
if (bare) return bare[1];
|
|
4039
|
+
}
|
|
4040
|
+
return null;
|
|
4041
|
+
}
|
|
4042
|
+
function parsePaymentUrl(lines) {
|
|
4043
|
+
let paymentUrl;
|
|
4044
|
+
let shortenUrl;
|
|
4045
|
+
for (const line of lines) {
|
|
4046
|
+
const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
|
|
4047
|
+
if (!m) continue;
|
|
4048
|
+
const url = m[1].replace(/[)\]`>]+$/, "");
|
|
4049
|
+
if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
|
|
4050
|
+
else if (!paymentUrl) paymentUrl = url;
|
|
4051
|
+
}
|
|
4052
|
+
if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
|
|
4053
|
+
return { paymentUrl, shortenUrl };
|
|
4054
|
+
}
|
|
4055
|
+
function extractMedia(line) {
|
|
4056
|
+
const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
|
|
4057
|
+
return m ? m[1] : null;
|
|
4058
|
+
}
|
|
4059
|
+
function isWalletReady(lines) {
|
|
4060
|
+
const text = lines.join("\n").trim();
|
|
4061
|
+
try {
|
|
4062
|
+
const json = JSON.parse(text);
|
|
4063
|
+
if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
|
|
4064
|
+
} catch {
|
|
4065
|
+
}
|
|
4066
|
+
return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
|
|
4067
|
+
}
|
|
4068
|
+
function extractResourceFromReport(report) {
|
|
4069
|
+
const label = report.search(/资源响应体/);
|
|
4070
|
+
const start = report.indexOf("{", label === -1 ? 0 : label);
|
|
4071
|
+
if (start === -1) return void 0;
|
|
4072
|
+
let depth = 0, inStr = false, esc = false;
|
|
4073
|
+
for (let i = start; i < report.length; i++) {
|
|
4074
|
+
const ch = report[i];
|
|
4075
|
+
if (inStr) {
|
|
4076
|
+
if (esc) esc = false;
|
|
4077
|
+
else if (ch === "\\") esc = true;
|
|
4078
|
+
else if (ch === '"') inStr = false;
|
|
4079
|
+
continue;
|
|
4080
|
+
}
|
|
4081
|
+
if (ch === '"') inStr = true;
|
|
4082
|
+
else if (ch === "{") depth++;
|
|
4083
|
+
else if (ch === "}" && --depth === 0) {
|
|
4084
|
+
const slice = report.slice(start, i + 1);
|
|
4085
|
+
try {
|
|
4086
|
+
const obj = JSON.parse(slice);
|
|
4087
|
+
const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
|
|
4088
|
+
return typeof r === "string" ? r : JSON.stringify(r);
|
|
4089
|
+
} catch {
|
|
4090
|
+
return slice;
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
return void 0;
|
|
4095
|
+
}
|
|
4096
|
+
function extractBody(lines) {
|
|
4097
|
+
const text = lines.join("\n").trim();
|
|
4098
|
+
try {
|
|
4099
|
+
const json = JSON.parse(text);
|
|
4100
|
+
if (json && typeof json === "object") {
|
|
4101
|
+
if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
|
|
4102
|
+
const rr = json.resourceResponse;
|
|
4103
|
+
const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
|
|
4104
|
+
return typeof r === "string" ? r : JSON.stringify(r);
|
|
4105
|
+
}
|
|
4106
|
+
if (typeof json.body === "string") {
|
|
4107
|
+
const inner = extractResourceFromReport(json.body);
|
|
4108
|
+
return inner ?? json.body;
|
|
4109
|
+
}
|
|
4110
|
+
const body = json.data ?? json.result ?? json.body ?? json.resource;
|
|
4111
|
+
if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
|
|
4112
|
+
return text;
|
|
4113
|
+
}
|
|
4114
|
+
} catch {
|
|
4115
|
+
}
|
|
4116
|
+
const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
|
|
4117
|
+
if (idx !== -1) {
|
|
4118
|
+
const first = lines[idx].replace(/^\s*BODY:\s*/, "");
|
|
4119
|
+
return [first, ...lines.slice(idx + 1)].join("\n").trim();
|
|
4120
|
+
}
|
|
4121
|
+
return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
|
|
4122
|
+
}
|
|
4123
|
+
var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
|
|
4124
|
+
function resolveWalletTtlMs() {
|
|
4125
|
+
const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
|
|
4126
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
|
|
4127
|
+
const n = Number(raw);
|
|
4128
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
|
|
4129
|
+
}
|
|
4130
|
+
var walletReadyUntil = /* @__PURE__ */ new Map();
|
|
4131
|
+
function resetWalletCache() {
|
|
4132
|
+
walletReadyUntil.clear();
|
|
4133
|
+
}
|
|
4134
|
+
var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
|
|
4135
|
+
function resolveIntentTtlMs() {
|
|
4136
|
+
const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
|
|
4137
|
+
if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
|
|
4138
|
+
const n = Number(raw);
|
|
4139
|
+
return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
|
|
4140
|
+
}
|
|
4141
|
+
var intentDoneUntil = /* @__PURE__ */ new Map();
|
|
4142
|
+
function resetIntentCache() {
|
|
4143
|
+
intentDoneUntil.clear();
|
|
4144
|
+
}
|
|
4145
|
+
var AlipayClient = class {
|
|
4146
|
+
sessionId;
|
|
4147
|
+
configDir;
|
|
4148
|
+
framework;
|
|
4149
|
+
runner;
|
|
4150
|
+
getVersion;
|
|
4151
|
+
now;
|
|
4152
|
+
/** Only the default runner may use the process-level wallet cache. */
|
|
4153
|
+
walletCacheable;
|
|
4154
|
+
/** Only the default runner may use the process-level payment-intent cache. */
|
|
4155
|
+
intentCacheable;
|
|
4156
|
+
constructor(opts = {}) {
|
|
4157
|
+
this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
|
|
4158
|
+
this.configDir = opts.configDir ?? (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
|
|
4159
|
+
this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
|
|
4160
|
+
this.runner = opts.runner ?? runCli;
|
|
4161
|
+
this.getVersion = opts.getVersion;
|
|
4162
|
+
this.now = opts.now ?? Date.now;
|
|
4163
|
+
this.walletCacheable = opts.cacheWallet ?? !opts.runner;
|
|
4164
|
+
this.intentCacheable = opts.cacheIntent ?? !opts.runner;
|
|
4165
|
+
}
|
|
4166
|
+
/**
|
|
4167
|
+
* Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
|
|
4168
|
+
*
|
|
4169
|
+
* Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
|
|
4170
|
+
* "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
|
|
4171
|
+
*/
|
|
4172
|
+
async checkWallet(signal, flow) {
|
|
4173
|
+
const ttlMs = resolveWalletTtlMs();
|
|
4174
|
+
const cacheKey = `${this.configDir}::${this.framework}`;
|
|
4175
|
+
const useCache = this.walletCacheable && ttlMs > 0;
|
|
4176
|
+
if (useCache) {
|
|
4177
|
+
const until = walletReadyUntil.get(cacheKey);
|
|
4178
|
+
if (until !== void 0 && this.now() < until) {
|
|
4179
|
+
alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
|
|
4180
|
+
return;
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
|
|
4184
|
+
if (!isWalletReady(lines)) {
|
|
4185
|
+
if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
|
|
4186
|
+
throw new NeedsWalletSetupError(
|
|
4187
|
+
"Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
|
|
4188
|
+
);
|
|
4189
|
+
}
|
|
4190
|
+
if (useCache) {
|
|
4191
|
+
walletReadyUntil.set(cacheKey, this.now() + ttlMs);
|
|
4192
|
+
alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
/** Run the full 8-step flow and resolve with the resource body. */
|
|
4196
|
+
async pay402(opts) {
|
|
4197
|
+
const { resourceUrl, requirement, signal } = opts;
|
|
4198
|
+
const extra = requirement.extra ?? {};
|
|
4199
|
+
const paymentNeededHeader = String(extra.payment_needed_header ?? "");
|
|
4200
|
+
if (!paymentNeededHeader) {
|
|
4201
|
+
throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
|
|
4202
|
+
}
|
|
4203
|
+
const flow = this.sessionId;
|
|
4204
|
+
const flowStart = this.now();
|
|
4205
|
+
alipayLog.info("flow.start", { flow, resource: resourceUrl });
|
|
4206
|
+
const media = [];
|
|
4207
|
+
const onLine = (line) => {
|
|
4208
|
+
const m = extractMedia(line);
|
|
4209
|
+
if (m) media.push(m);
|
|
4210
|
+
else opts.onLine?.(line);
|
|
4211
|
+
};
|
|
4212
|
+
const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
|
|
4213
|
+
await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
|
|
4214
|
+
const intentTtlMs = resolveIntentTtlMs();
|
|
4215
|
+
const intentKey = `${this.configDir}::${this.framework}`;
|
|
4216
|
+
const useIntentCache = this.intentCacheable && intentTtlMs > 0;
|
|
4217
|
+
const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
|
|
4218
|
+
if (intentUntil !== void 0 && this.now() < intentUntil) {
|
|
4219
|
+
alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
|
|
4220
|
+
} else {
|
|
4221
|
+
await this.runner(
|
|
4222
|
+
[
|
|
4223
|
+
"payment-intent",
|
|
4224
|
+
"--session-id",
|
|
4225
|
+
this.sessionId,
|
|
4226
|
+
"--intent-summary",
|
|
4227
|
+
intentSummary,
|
|
4228
|
+
"--framework",
|
|
4229
|
+
this.framework
|
|
4230
|
+
],
|
|
4231
|
+
{ onLine, signal, step: "payment-intent", flow }
|
|
4232
|
+
);
|
|
4233
|
+
if (useIntentCache) {
|
|
4234
|
+
intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
|
|
4235
|
+
alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
|
|
4236
|
+
}
|
|
4237
|
+
}
|
|
4238
|
+
await this.checkWallet(signal, flow);
|
|
4239
|
+
const reqId = String(extra.out_trade_no ?? (0, import_node_crypto3.randomUUID)());
|
|
4240
|
+
const dir = (0, import_path2.join)(this.configDir, "alipay");
|
|
4241
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
4242
|
+
const challengeFile = (0, import_path2.join)(dir, `402_${reqId}.txt`);
|
|
4243
|
+
await (0, import_promises.writeFile)(challengeFile, paymentNeededHeader, "utf-8");
|
|
4244
|
+
const payArgs = [
|
|
4245
|
+
"402-buyer-pay",
|
|
4246
|
+
"-f",
|
|
4247
|
+
challengeFile,
|
|
4248
|
+
"-r",
|
|
4249
|
+
resourceUrl,
|
|
4250
|
+
"-s",
|
|
4251
|
+
this.sessionId,
|
|
4252
|
+
"-i",
|
|
4253
|
+
intentSummary,
|
|
4254
|
+
"-w",
|
|
4255
|
+
this.framework
|
|
4256
|
+
];
|
|
4257
|
+
if (opts.method) payArgs.push("-m", opts.method);
|
|
4258
|
+
if (opts.data) payArgs.push("-d", opts.data);
|
|
4259
|
+
const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
|
|
4260
|
+
const tradeNo = parseTradeNo(payRun.lines);
|
|
4261
|
+
if (!tradeNo) {
|
|
4262
|
+
throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
|
|
4263
|
+
}
|
|
4264
|
+
assertTradeNo(tradeNo);
|
|
4265
|
+
const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
|
|
4266
|
+
if (paymentUrl) {
|
|
4267
|
+
alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
|
|
4268
|
+
opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
|
|
4269
|
+
}
|
|
4270
|
+
const pendingAt = this.now();
|
|
4271
|
+
const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
|
|
4272
|
+
const deadline = this.now() + (opts.timeoutMs ?? windowMs);
|
|
4273
|
+
const poll = await pollUntil(tradeNo, resourceUrl, {
|
|
4274
|
+
// No onLine: the status-poll output embeds the resource and must not reach
|
|
4275
|
+
// the log stream — the body is surfaced via the return value below (§9.3).
|
|
4276
|
+
deadline,
|
|
4277
|
+
signal,
|
|
4278
|
+
runner: this.runner,
|
|
4279
|
+
now: this.now,
|
|
4280
|
+
// Re-fetch the resource the same way it was paid (POST + body), else the
|
|
4281
|
+
// status poll defaults to GET and 404s on a POST-only `/execute`.
|
|
4282
|
+
method: opts.method,
|
|
4283
|
+
data: opts.data
|
|
4284
|
+
});
|
|
4285
|
+
alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
|
|
4286
|
+
const body = extractBody(poll.lines);
|
|
4287
|
+
void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
|
|
4288
|
+
onLine,
|
|
4289
|
+
signal,
|
|
4290
|
+
step: "402-buyer-fulfillment-ack",
|
|
4291
|
+
flow
|
|
4292
|
+
}).catch(() => void 0);
|
|
4293
|
+
return { body, payment: { tradeNo, outTradeNo: reqId }, media };
|
|
4294
|
+
}
|
|
4295
|
+
};
|
|
4296
|
+
|
|
4297
|
+
// src/client/alipay/router.ts
|
|
4298
|
+
var ALIPAY_RAIL = "alipay";
|
|
4299
|
+
function railOf(req) {
|
|
4300
|
+
if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
|
|
4301
|
+
return networkToChainName(req.network) ?? req.network;
|
|
4302
|
+
}
|
|
4303
|
+
function findRail(accepts, rail) {
|
|
4304
|
+
const requirement = accepts.find((r) => railOf(r) === rail);
|
|
4305
|
+
return requirement ? { rail, requirement } : null;
|
|
4306
|
+
}
|
|
4307
|
+
function selectRail(input) {
|
|
4308
|
+
const { serverAccepts, explicitRail, preference, availability } = input;
|
|
4309
|
+
if (!serverAccepts || serverAccepts.length === 0) {
|
|
4310
|
+
throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
|
|
4311
|
+
}
|
|
4312
|
+
if (explicitRail) {
|
|
4313
|
+
const hit = findRail(serverAccepts, explicitRail);
|
|
4314
|
+
if (!hit) {
|
|
4315
|
+
const offered = serverAccepts.map(railOf);
|
|
4316
|
+
throw new UnsupportedRailError(
|
|
4317
|
+
explicitRail,
|
|
4318
|
+
`Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
return hit;
|
|
4322
|
+
}
|
|
4323
|
+
if (preference) {
|
|
4324
|
+
for (const pref of preference) {
|
|
4325
|
+
const hit = findRail(serverAccepts, pref);
|
|
4326
|
+
if (hit) return hit;
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
if (availability?.evmReady) {
|
|
4330
|
+
const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
|
|
4331
|
+
if (evm) return { rail: railOf(evm), requirement: evm };
|
|
4332
|
+
}
|
|
4333
|
+
if (availability?.alipayReady) {
|
|
4334
|
+
const hit = findRail(serverAccepts, ALIPAY_RAIL);
|
|
4335
|
+
if (hit) return hit;
|
|
4336
|
+
}
|
|
4337
|
+
return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
|
|
4338
|
+
}
|
|
4339
|
+
|
|
4340
|
+
// src/client/node/index.ts
|
|
2695
4341
|
var DEFAULT_CONFIG = {
|
|
2696
4342
|
chain: "base",
|
|
2697
4343
|
limits: {
|
|
@@ -2704,15 +4350,24 @@ var MoltsPayClient = class {
|
|
|
2704
4350
|
config;
|
|
2705
4351
|
walletData = null;
|
|
2706
4352
|
wallet = null;
|
|
4353
|
+
signer = null;
|
|
2707
4354
|
todaySpending = 0;
|
|
2708
4355
|
lastSpendingReset = 0;
|
|
4356
|
+
railPreference;
|
|
4357
|
+
alipaySessionId;
|
|
2709
4358
|
constructor(options = {}) {
|
|
2710
|
-
this.configDir = options.configDir || (0,
|
|
4359
|
+
this.configDir = options.configDir || (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
|
|
2711
4360
|
this.config = this.loadConfig();
|
|
4361
|
+
this.railPreference = options.railPreference ?? this.config.railPreference;
|
|
4362
|
+
this.alipaySessionId = options.alipaySessionId;
|
|
2712
4363
|
this.walletData = this.loadWallet();
|
|
2713
4364
|
this.loadSpending();
|
|
2714
4365
|
if (this.walletData) {
|
|
2715
|
-
this.wallet = new
|
|
4366
|
+
this.wallet = new import_ethers3.Wallet(this.walletData.privateKey);
|
|
4367
|
+
const configDir = this.configDir;
|
|
4368
|
+
this.signer = new NodeSigner(this.wallet, {
|
|
4369
|
+
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
4370
|
+
});
|
|
2716
4371
|
}
|
|
2717
4372
|
}
|
|
2718
4373
|
/**
|
|
@@ -2782,6 +4437,9 @@ var MoltsPayClient = class {
|
|
|
2782
4437
|
* @param options - Payment options (token selection)
|
|
2783
4438
|
*/
|
|
2784
4439
|
async pay(serverUrl, service, params, options = {}) {
|
|
4440
|
+
if (options.rail === ALIPAY_RAIL) {
|
|
4441
|
+
return this.payViaAlipay(serverUrl, service, params, options);
|
|
4442
|
+
}
|
|
2785
4443
|
if (!this.wallet || !this.walletData) {
|
|
2786
4444
|
throw new Error("Client not initialized. Run: npx moltspay init");
|
|
2787
4445
|
}
|
|
@@ -2840,20 +4498,6 @@ var MoltsPayClient = class {
|
|
|
2840
4498
|
} catch {
|
|
2841
4499
|
throw new Error("Invalid x-payment-required header");
|
|
2842
4500
|
}
|
|
2843
|
-
const networkToChainName = (network2) => {
|
|
2844
|
-
if (network2 === "solana:mainnet") return "solana";
|
|
2845
|
-
if (network2 === "solana:devnet") return "solana_devnet";
|
|
2846
|
-
const match = network2.match(/^eip155:(\d+)$/);
|
|
2847
|
-
if (!match) return null;
|
|
2848
|
-
const chainId = parseInt(match[1]);
|
|
2849
|
-
if (chainId === 8453) return "base";
|
|
2850
|
-
if (chainId === 137) return "polygon";
|
|
2851
|
-
if (chainId === 84532) return "base_sepolia";
|
|
2852
|
-
if (chainId === 42431) return "tempo_moderato";
|
|
2853
|
-
if (chainId === 56) return "bnb";
|
|
2854
|
-
if (chainId === 97) return "bnb_testnet";
|
|
2855
|
-
return null;
|
|
2856
|
-
};
|
|
2857
4501
|
const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
|
|
2858
4502
|
const userSpecifiedChain = options.chain;
|
|
2859
4503
|
let selectedChain;
|
|
@@ -2987,6 +4631,76 @@ Please specify: --chain <chain_name>`
|
|
|
2987
4631
|
console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
|
|
2988
4632
|
return result.result || result;
|
|
2989
4633
|
}
|
|
4634
|
+
/**
|
|
4635
|
+
* Pay for a service over the Alipay fiat rail (2.0.0).
|
|
4636
|
+
*
|
|
4637
|
+
* Unlike the crypto path this needs no EVM wallet — it shells out to
|
|
4638
|
+
* alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
|
|
4639
|
+
* payment to get the 402 challenge, confirm the server actually offers the
|
|
4640
|
+
* alipay rail (selectRail), then run the 8-step state machine and return the
|
|
4641
|
+
* resource body.
|
|
4642
|
+
*/
|
|
4643
|
+
async payViaAlipay(serverUrl, service, params, options) {
|
|
4644
|
+
const flow = this.alipaySessionId;
|
|
4645
|
+
let executeUrl = `${serverUrl}/execute`;
|
|
4646
|
+
try {
|
|
4647
|
+
const services = await timeStep(
|
|
4648
|
+
"discover-services",
|
|
4649
|
+
flow,
|
|
4650
|
+
() => this.getServices(serverUrl)
|
|
4651
|
+
);
|
|
4652
|
+
const svc = services.services?.find((s) => s.id === service);
|
|
4653
|
+
if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
|
|
4654
|
+
} catch {
|
|
4655
|
+
}
|
|
4656
|
+
const requestBody = options.rawData ? { service, ...params } : { service, params };
|
|
4657
|
+
const bodyJson = JSON.stringify(requestBody);
|
|
4658
|
+
const res = await timeStep(
|
|
4659
|
+
"challenge-402",
|
|
4660
|
+
flow,
|
|
4661
|
+
() => fetch(executeUrl, {
|
|
4662
|
+
method: "POST",
|
|
4663
|
+
headers: { "Content-Type": "application/json" },
|
|
4664
|
+
body: bodyJson
|
|
4665
|
+
})
|
|
4666
|
+
);
|
|
4667
|
+
if (res.status !== 402) {
|
|
4668
|
+
const data = await res.json().catch(() => ({}));
|
|
4669
|
+
if (res.ok && data.result) return data.result;
|
|
4670
|
+
throw new Error(data.error || `Expected 402, got ${res.status}`);
|
|
4671
|
+
}
|
|
4672
|
+
const header = res.headers.get(PAYMENT_REQUIRED_HEADER2);
|
|
4673
|
+
if (!header) throw new Error("Missing x-payment-required header on 402");
|
|
4674
|
+
const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
|
|
4675
|
+
const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
|
|
4676
|
+
const { requirement } = selectRail({
|
|
4677
|
+
serverAccepts: accepts,
|
|
4678
|
+
explicitRail: ALIPAY_RAIL,
|
|
4679
|
+
preference: this.railPreference,
|
|
4680
|
+
availability: { evmReady: this.isInitialized }
|
|
4681
|
+
});
|
|
4682
|
+
const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
|
|
4683
|
+
const alipay = new AlipayClient({
|
|
4684
|
+
sessionId: this.alipaySessionId,
|
|
4685
|
+
configDir: this.configDir
|
|
4686
|
+
});
|
|
4687
|
+
const result = await alipay.pay402({
|
|
4688
|
+
resourceUrl: executeUrl,
|
|
4689
|
+
requirement,
|
|
4690
|
+
method: "POST",
|
|
4691
|
+
data: bodyJson,
|
|
4692
|
+
onLine,
|
|
4693
|
+
onPaymentPending: options.onPaymentPending,
|
|
4694
|
+
timeoutMs: options.timeoutMs,
|
|
4695
|
+
signal: options.signal
|
|
4696
|
+
});
|
|
4697
|
+
try {
|
|
4698
|
+
const json = JSON.parse(result.body);
|
|
4699
|
+
return json.result ?? json;
|
|
4700
|
+
} catch {
|
|
4701
|
+
return { body: result.body, payment: result.payment, media: result.media };
|
|
4702
|
+
}
|
|
4703
|
+
}
|
|
2990
4704
|
/**
|
|
2991
4705
|
* Handle MPP (Machine Payments Protocol) payment flow
|
|
2992
4706
|
* Called when pay() detects WWW-Authenticate header in 402 response
|
|
@@ -3082,14 +4796,14 @@ Please specify: --chain <chain_name>`
|
|
|
3082
4796
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
3083
4797
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
3084
4798
|
const tokenConfig = chain.tokens[token];
|
|
3085
|
-
const provider = new
|
|
4799
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3086
4800
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
3087
4801
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
3088
4802
|
if (allowance < amountWeiCheck) {
|
|
3089
4803
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
3090
|
-
const minGasBalance =
|
|
4804
|
+
const minGasBalance = import_ethers3.ethers.parseEther("0.0005");
|
|
3091
4805
|
if (nativeBalance < minGasBalance) {
|
|
3092
|
-
const nativeBNB = parseFloat(
|
|
4806
|
+
const nativeBNB = parseFloat(import_ethers3.ethers.formatEther(nativeBalance)).toFixed(4);
|
|
3093
4807
|
const isTestnet = chainName === "bnb_testnet";
|
|
3094
4808
|
if (isTestnet) {
|
|
3095
4809
|
throw new Error(
|
|
@@ -3123,35 +4837,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3123
4837
|
);
|
|
3124
4838
|
}
|
|
3125
4839
|
const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3126
|
-
const
|
|
4840
|
+
const intentNonce = Date.now();
|
|
4841
|
+
const intentDeadline = Date.now() + 36e5;
|
|
4842
|
+
const envelope = buildBnbIntentTypedData({
|
|
3127
4843
|
from: this.wallet.address,
|
|
3128
4844
|
to,
|
|
3129
4845
|
amount: amountWei,
|
|
3130
|
-
|
|
4846
|
+
tokenAddress: tokenConfig.address,
|
|
3131
4847
|
service,
|
|
3132
|
-
nonce:
|
|
3133
|
-
|
|
3134
|
-
deadline: Date.now() + 36e5
|
|
3135
|
-
// 1 hour
|
|
3136
|
-
};
|
|
3137
|
-
const domain = {
|
|
3138
|
-
name: "MoltsPay",
|
|
3139
|
-
version: "1",
|
|
4848
|
+
nonce: intentNonce,
|
|
4849
|
+
deadline: intentDeadline,
|
|
3140
4850
|
chainId: chain.chainId
|
|
3141
|
-
};
|
|
3142
|
-
const types = {
|
|
3143
|
-
PaymentIntent: [
|
|
3144
|
-
{ name: "from", type: "address" },
|
|
3145
|
-
{ name: "to", type: "address" },
|
|
3146
|
-
{ name: "amount", type: "uint256" },
|
|
3147
|
-
{ name: "token", type: "address" },
|
|
3148
|
-
{ name: "service", type: "string" },
|
|
3149
|
-
{ name: "nonce", type: "uint256" },
|
|
3150
|
-
{ name: "deadline", type: "uint256" }
|
|
3151
|
-
]
|
|
3152
|
-
};
|
|
4851
|
+
});
|
|
3153
4852
|
console.log(`[MoltsPay] Signing BNB payment intent...`);
|
|
3154
|
-
const signature = await this.
|
|
4853
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
4854
|
+
const intent = envelope.message;
|
|
3155
4855
|
const network = `eip155:${chain.chainId}`;
|
|
3156
4856
|
const payload = {
|
|
3157
4857
|
x402Version: 2,
|
|
@@ -3212,11 +4912,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3212
4912
|
throw new Error("Missing payTo address in payment requirements");
|
|
3213
4913
|
}
|
|
3214
4914
|
const solanaFeePayer = requirements.extra?.solanaFeePayer;
|
|
3215
|
-
const feePayerPubkey = solanaFeePayer ? new
|
|
4915
|
+
const feePayerPubkey = solanaFeePayer ? new import_web36.PublicKey(solanaFeePayer) : void 0;
|
|
3216
4916
|
if (feePayerPubkey) {
|
|
3217
4917
|
console.log(`[MoltsPay] Gasless mode: server pays fees`);
|
|
3218
4918
|
}
|
|
3219
|
-
const recipientPubkey = new
|
|
4919
|
+
const recipientPubkey = new import_web36.PublicKey(requirements.payTo);
|
|
3220
4920
|
const transaction = await createSolanaPaymentTransaction(
|
|
3221
4921
|
solanaWallet.publicKey,
|
|
3222
4922
|
recipientPubkey,
|
|
@@ -3225,12 +4925,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3225
4925
|
feePayerPubkey
|
|
3226
4926
|
// Optional fee payer for gasless mode
|
|
3227
4927
|
);
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
}
|
|
3233
|
-
const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
|
|
4928
|
+
const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
4929
|
+
const signedTx = await this.signer.signSolanaTransaction({
|
|
4930
|
+
transactionBase64: unsignedBase64,
|
|
4931
|
+
partialSign: !!feePayerPubkey
|
|
4932
|
+
});
|
|
3234
4933
|
console.log(`[MoltsPay] Transaction signed, sending to server...`);
|
|
3235
4934
|
const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
|
|
3236
4935
|
const payload = {
|
|
@@ -3277,7 +4976,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3277
4976
|
* Check ERC20 allowance for a spender
|
|
3278
4977
|
*/
|
|
3279
4978
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
3280
|
-
const contract = new
|
|
4979
|
+
const contract = new import_ethers3.ethers.Contract(
|
|
3281
4980
|
tokenAddress,
|
|
3282
4981
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
3283
4982
|
provider
|
|
@@ -3288,41 +4987,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3288
4987
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
3289
4988
|
* This only signs - no on-chain transaction, no gas needed.
|
|
3290
4989
|
* Supports both USDC and USDT.
|
|
4990
|
+
*
|
|
4991
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
4992
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
4993
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
3291
4994
|
*/
|
|
3292
4995
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
3293
|
-
const validAfter = 0;
|
|
3294
|
-
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
3295
|
-
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
3296
4996
|
const tokenConfig = chain.tokens[token];
|
|
3297
4997
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3298
|
-
const
|
|
4998
|
+
const nonce = import_ethers3.ethers.hexlify(import_ethers3.ethers.randomBytes(32));
|
|
4999
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
5000
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
5001
|
+
const envelope = buildEIP3009TypedData({
|
|
3299
5002
|
from: this.wallet.address,
|
|
3300
5003
|
to,
|
|
3301
5004
|
value,
|
|
3302
|
-
|
|
3303
|
-
validBefore: validBefore.toString(),
|
|
3304
|
-
nonce
|
|
3305
|
-
};
|
|
3306
|
-
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
3307
|
-
const tokenVersion = domainOverride?.version || "2";
|
|
3308
|
-
const domain = {
|
|
3309
|
-
name: tokenName,
|
|
3310
|
-
version: tokenVersion,
|
|
5005
|
+
nonce,
|
|
3311
5006
|
chainId: chain.chainId,
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
{ name: "value", type: "uint256" },
|
|
3319
|
-
{ name: "validAfter", type: "uint256" },
|
|
3320
|
-
{ name: "validBefore", type: "uint256" },
|
|
3321
|
-
{ name: "nonce", type: "bytes32" }
|
|
3322
|
-
]
|
|
3323
|
-
};
|
|
3324
|
-
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
3325
|
-
return { authorization, signature };
|
|
5007
|
+
tokenAddress: tokenConfig.address,
|
|
5008
|
+
tokenName,
|
|
5009
|
+
tokenVersion
|
|
5010
|
+
});
|
|
5011
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
5012
|
+
return { authorization: envelope.message, signature };
|
|
3326
5013
|
}
|
|
3327
5014
|
/**
|
|
3328
5015
|
* Check spending limits
|
|
@@ -3354,7 +5041,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3354
5041
|
}
|
|
3355
5042
|
// --- Config & Wallet Management ---
|
|
3356
5043
|
loadConfig() {
|
|
3357
|
-
const configPath = (0,
|
|
5044
|
+
const configPath = (0, import_path3.join)(this.configDir, "config.json");
|
|
3358
5045
|
if ((0, import_fs4.existsSync)(configPath)) {
|
|
3359
5046
|
const content = (0, import_fs4.readFileSync)(configPath, "utf-8");
|
|
3360
5047
|
return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
|
|
@@ -3363,14 +5050,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3363
5050
|
}
|
|
3364
5051
|
saveConfig() {
|
|
3365
5052
|
(0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
|
|
3366
|
-
const configPath = (0,
|
|
5053
|
+
const configPath = (0, import_path3.join)(this.configDir, "config.json");
|
|
3367
5054
|
(0, import_fs4.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
|
|
3368
5055
|
}
|
|
3369
5056
|
/**
|
|
3370
5057
|
* Load spending data from disk
|
|
3371
5058
|
*/
|
|
3372
5059
|
loadSpending() {
|
|
3373
|
-
const spendingPath = (0,
|
|
5060
|
+
const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
|
|
3374
5061
|
if ((0, import_fs4.existsSync)(spendingPath)) {
|
|
3375
5062
|
try {
|
|
3376
5063
|
const data = JSON.parse((0, import_fs4.readFileSync)(spendingPath, "utf-8"));
|
|
@@ -3393,7 +5080,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3393
5080
|
*/
|
|
3394
5081
|
saveSpending() {
|
|
3395
5082
|
(0, import_fs4.mkdirSync)(this.configDir, { recursive: true });
|
|
3396
|
-
const spendingPath = (0,
|
|
5083
|
+
const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
|
|
3397
5084
|
const data = {
|
|
3398
5085
|
date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
|
|
3399
5086
|
amount: this.todaySpending,
|
|
@@ -3402,7 +5089,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3402
5089
|
(0, import_fs4.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
|
|
3403
5090
|
}
|
|
3404
5091
|
loadWallet() {
|
|
3405
|
-
const walletPath = (0,
|
|
5092
|
+
const walletPath = (0, import_path3.join)(this.configDir, "wallet.json");
|
|
3406
5093
|
if ((0, import_fs4.existsSync)(walletPath)) {
|
|
3407
5094
|
if (process.platform !== "win32") {
|
|
3408
5095
|
try {
|
|
@@ -3426,13 +5113,13 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3426
5113
|
*/
|
|
3427
5114
|
static init(configDir, options) {
|
|
3428
5115
|
(0, import_fs4.mkdirSync)(configDir, { recursive: true });
|
|
3429
|
-
const wallet =
|
|
5116
|
+
const wallet = import_ethers3.Wallet.createRandom();
|
|
3430
5117
|
const walletData = {
|
|
3431
5118
|
address: wallet.address,
|
|
3432
5119
|
privateKey: wallet.privateKey,
|
|
3433
5120
|
createdAt: Date.now()
|
|
3434
5121
|
};
|
|
3435
|
-
const walletPath = (0,
|
|
5122
|
+
const walletPath = (0, import_path3.join)(configDir, "wallet.json");
|
|
3436
5123
|
(0, import_fs4.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
|
|
3437
5124
|
const config = {
|
|
3438
5125
|
chain: options.chain,
|
|
@@ -3441,7 +5128,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3441
5128
|
maxPerDay: options.maxPerDay
|
|
3442
5129
|
}
|
|
3443
5130
|
};
|
|
3444
|
-
const configPath = (0,
|
|
5131
|
+
const configPath = (0, import_path3.join)(configDir, "config.json");
|
|
3445
5132
|
(0, import_fs4.writeFileSync)(configPath, JSON.stringify(config, null, 2));
|
|
3446
5133
|
return { address: wallet.address, configDir };
|
|
3447
5134
|
}
|
|
@@ -3458,17 +5145,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3458
5145
|
} catch {
|
|
3459
5146
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
3460
5147
|
}
|
|
3461
|
-
const provider = new
|
|
5148
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3462
5149
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
3463
5150
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3464
5151
|
provider.getBalance(this.wallet.address),
|
|
3465
|
-
new
|
|
3466
|
-
new
|
|
5152
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
5153
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3467
5154
|
]);
|
|
3468
5155
|
return {
|
|
3469
|
-
usdc: parseFloat(
|
|
3470
|
-
usdt: parseFloat(
|
|
3471
|
-
native: parseFloat(
|
|
5156
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
5157
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
5158
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
3472
5159
|
};
|
|
3473
5160
|
}
|
|
3474
5161
|
/**
|
|
@@ -3491,38 +5178,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3491
5178
|
supportedChains.map(async (chainName) => {
|
|
3492
5179
|
try {
|
|
3493
5180
|
const chain = getChain(chainName);
|
|
3494
|
-
const provider = new
|
|
5181
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3495
5182
|
if (chainName === "tempo_moderato") {
|
|
3496
5183
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
3497
5184
|
provider.getBalance(this.wallet.address),
|
|
3498
|
-
new
|
|
3499
|
-
new
|
|
3500
|
-
new
|
|
3501
|
-
new
|
|
5185
|
+
new import_ethers3.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
5186
|
+
new import_ethers3.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
5187
|
+
new import_ethers3.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
5188
|
+
new import_ethers3.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3502
5189
|
]);
|
|
3503
5190
|
results[chainName] = {
|
|
3504
|
-
usdc: parseFloat(
|
|
5191
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
3505
5192
|
// pathUSD as default USDC
|
|
3506
|
-
usdt: parseFloat(
|
|
5193
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
3507
5194
|
// alphaUSD as default USDT
|
|
3508
|
-
native: parseFloat(
|
|
5195
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance)),
|
|
3509
5196
|
tempo: {
|
|
3510
|
-
pathUSD: parseFloat(
|
|
3511
|
-
alphaUSD: parseFloat(
|
|
3512
|
-
betaUSD: parseFloat(
|
|
3513
|
-
thetaUSD: parseFloat(
|
|
5197
|
+
pathUSD: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
5198
|
+
alphaUSD: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
5199
|
+
betaUSD: parseFloat(import_ethers3.ethers.formatUnits(betaUSD, 6)),
|
|
5200
|
+
thetaUSD: parseFloat(import_ethers3.ethers.formatUnits(thetaUSD, 6))
|
|
3514
5201
|
}
|
|
3515
5202
|
};
|
|
3516
5203
|
} else {
|
|
3517
5204
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3518
5205
|
provider.getBalance(this.wallet.address),
|
|
3519
|
-
new
|
|
3520
|
-
new
|
|
5206
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
5207
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3521
5208
|
]);
|
|
3522
5209
|
results[chainName] = {
|
|
3523
|
-
usdc: parseFloat(
|
|
3524
|
-
usdt: parseFloat(
|
|
3525
|
-
native: parseFloat(
|
|
5210
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
5211
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
5212
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
3526
5213
|
};
|
|
3527
5214
|
}
|
|
3528
5215
|
} catch (err) {
|
|
@@ -3650,14 +5337,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3650
5337
|
};
|
|
3651
5338
|
|
|
3652
5339
|
// src/wallet/Wallet.ts
|
|
3653
|
-
var
|
|
5340
|
+
var import_ethers4 = require("ethers");
|
|
3654
5341
|
|
|
3655
5342
|
// src/wallet/createWallet.ts
|
|
3656
|
-
var
|
|
5343
|
+
var import_ethers5 = require("ethers");
|
|
3657
5344
|
var import_fs5 = require("fs");
|
|
3658
|
-
var
|
|
5345
|
+
var import_path4 = require("path");
|
|
3659
5346
|
var import_crypto = require("crypto");
|
|
3660
|
-
var DEFAULT_STORAGE_DIR = (0,
|
|
5347
|
+
var DEFAULT_STORAGE_DIR = (0, import_path4.join)(process.env.HOME || "~", ".moltspay");
|
|
3661
5348
|
var DEFAULT_STORAGE_FILE = "wallet.json";
|
|
3662
5349
|
function encryptPrivateKey(privateKey, password) {
|
|
3663
5350
|
const salt = (0, import_crypto.randomBytes)(16);
|
|
@@ -3680,7 +5367,7 @@ function decryptPrivateKey(encrypted, password, iv, salt) {
|
|
|
3680
5367
|
return decrypted;
|
|
3681
5368
|
}
|
|
3682
5369
|
function createWallet(options = {}) {
|
|
3683
|
-
const storagePath = options.storagePath || (0,
|
|
5370
|
+
const storagePath = options.storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
3684
5371
|
if ((0, import_fs5.existsSync)(storagePath) && !options.overwrite) {
|
|
3685
5372
|
try {
|
|
3686
5373
|
const existing = JSON.parse((0, import_fs5.readFileSync)(storagePath, "utf8"));
|
|
@@ -3698,7 +5385,7 @@ function createWallet(options = {}) {
|
|
|
3698
5385
|
}
|
|
3699
5386
|
}
|
|
3700
5387
|
try {
|
|
3701
|
-
const wallet =
|
|
5388
|
+
const wallet = import_ethers5.ethers.Wallet.createRandom();
|
|
3702
5389
|
const walletData = {
|
|
3703
5390
|
address: wallet.address,
|
|
3704
5391
|
label: options.label,
|
|
@@ -3714,7 +5401,7 @@ function createWallet(options = {}) {
|
|
|
3714
5401
|
} else {
|
|
3715
5402
|
walletData.privateKey = wallet.privateKey;
|
|
3716
5403
|
}
|
|
3717
|
-
const dir = (0,
|
|
5404
|
+
const dir = (0, import_path4.dirname)(storagePath);
|
|
3718
5405
|
if (!(0, import_fs5.existsSync)(dir)) {
|
|
3719
5406
|
(0, import_fs5.mkdirSync)(dir, { recursive: true });
|
|
3720
5407
|
}
|
|
@@ -3733,7 +5420,7 @@ function createWallet(options = {}) {
|
|
|
3733
5420
|
}
|
|
3734
5421
|
}
|
|
3735
5422
|
function loadWallet(options = {}) {
|
|
3736
|
-
const storagePath = options.storagePath || (0,
|
|
5423
|
+
const storagePath = options.storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
3737
5424
|
if (!(0, import_fs5.existsSync)(storagePath)) {
|
|
3738
5425
|
return { success: false, error: "Wallet not found. Run createWallet() first." };
|
|
3739
5426
|
}
|
|
@@ -3753,7 +5440,7 @@ function loadWallet(options = {}) {
|
|
|
3753
5440
|
}
|
|
3754
5441
|
}
|
|
3755
5442
|
function getWalletAddress(storagePath) {
|
|
3756
|
-
const path4 = storagePath || (0,
|
|
5443
|
+
const path4 = storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
3757
5444
|
if (!(0, import_fs5.existsSync)(path4)) {
|
|
3758
5445
|
return null;
|
|
3759
5446
|
}
|
|
@@ -3765,13 +5452,13 @@ function getWalletAddress(storagePath) {
|
|
|
3765
5452
|
}
|
|
3766
5453
|
}
|
|
3767
5454
|
function walletExists(storagePath) {
|
|
3768
|
-
const path4 = storagePath || (0,
|
|
5455
|
+
const path4 = storagePath || (0, import_path4.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
|
|
3769
5456
|
return (0, import_fs5.existsSync)(path4);
|
|
3770
5457
|
}
|
|
3771
5458
|
|
|
3772
5459
|
// src/verify/index.ts
|
|
3773
|
-
var
|
|
3774
|
-
var TRANSFER_EVENT_TOPIC3 =
|
|
5460
|
+
var import_ethers6 = require("ethers");
|
|
5461
|
+
var TRANSFER_EVENT_TOPIC3 = import_ethers6.ethers.id("Transfer(address,address,uint256)");
|
|
3775
5462
|
async function verifyPayment(params) {
|
|
3776
5463
|
const { txHash, expectedAmount, expectedTo, expectedToken } = params;
|
|
3777
5464
|
let chain;
|
|
@@ -3788,7 +5475,7 @@ async function verifyPayment(params) {
|
|
|
3788
5475
|
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
3789
5476
|
}
|
|
3790
5477
|
try {
|
|
3791
|
-
const provider = new
|
|
5478
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chain.rpc);
|
|
3792
5479
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3793
5480
|
if (!receipt) {
|
|
3794
5481
|
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
@@ -3860,7 +5547,7 @@ async function getTransactionStatus(txHash, chain = "base") {
|
|
|
3860
5547
|
return { status: "not_found" };
|
|
3861
5548
|
}
|
|
3862
5549
|
try {
|
|
3863
|
-
const provider = new
|
|
5550
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3864
5551
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3865
5552
|
if (!receipt) {
|
|
3866
5553
|
const tx = await provider.getTransaction(txHash);
|
|
@@ -3897,7 +5584,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
|
|
|
3897
5584
|
} catch (e) {
|
|
3898
5585
|
return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
|
|
3899
5586
|
}
|
|
3900
|
-
const provider = new
|
|
5587
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3901
5588
|
try {
|
|
3902
5589
|
const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
|
|
3903
5590
|
if (!receipt) {
|
|
@@ -4037,9 +5724,9 @@ var CDPWallet = class {
|
|
|
4037
5724
|
* Get USDC balance
|
|
4038
5725
|
*/
|
|
4039
5726
|
async getBalance() {
|
|
4040
|
-
const { ethers:
|
|
4041
|
-
const provider = new
|
|
4042
|
-
const usdcContract = new
|
|
5727
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
5728
|
+
const provider = new ethers7.JsonRpcProvider(this.chainConfig.rpc);
|
|
5729
|
+
const usdcContract = new ethers7.Contract(
|
|
4043
5730
|
this.chainConfig.usdc,
|
|
4044
5731
|
["function balanceOf(address) view returns (uint256)"],
|
|
4045
5732
|
provider
|
|
@@ -4050,7 +5737,7 @@ var CDPWallet = class {
|
|
|
4050
5737
|
]);
|
|
4051
5738
|
return {
|
|
4052
5739
|
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
4053
|
-
eth:
|
|
5740
|
+
eth: ethers7.formatEther(ethBalance)
|
|
4054
5741
|
};
|
|
4055
5742
|
}
|
|
4056
5743
|
/**
|
|
@@ -4068,7 +5755,7 @@ var CDPWallet = class {
|
|
|
4068
5755
|
}
|
|
4069
5756
|
try {
|
|
4070
5757
|
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
4071
|
-
const { ethers:
|
|
5758
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
4072
5759
|
const cdp = new CdpClient({
|
|
4073
5760
|
apiKeyId: creds.apiKeyId,
|
|
4074
5761
|
apiKeySecret: creds.apiKeySecret,
|
|
@@ -4076,7 +5763,7 @@ var CDPWallet = class {
|
|
|
4076
5763
|
});
|
|
4077
5764
|
const account = await cdp.evm.getAccount({ address: this.address });
|
|
4078
5765
|
const amountWei = BigInt(Math.floor(params.amount * 1e6));
|
|
4079
|
-
const iface = new
|
|
5766
|
+
const iface = new ethers7.Interface([
|
|
4080
5767
|
"function transfer(address to, uint256 amount) returns (bool)"
|
|
4081
5768
|
]);
|
|
4082
5769
|
const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);
|
|
@@ -4131,8 +5818,10 @@ var CDPWallet = class {
|
|
|
4131
5818
|
FacilitatorRegistry,
|
|
4132
5819
|
MoltsPayClient,
|
|
4133
5820
|
MoltsPayServer,
|
|
5821
|
+
alipayLog,
|
|
4134
5822
|
createRegistry,
|
|
4135
5823
|
createWallet,
|
|
5824
|
+
getAlipayLogLevel,
|
|
4136
5825
|
getCDPWalletAddress,
|
|
4137
5826
|
getChain,
|
|
4138
5827
|
getChainById,
|
|
@@ -4144,6 +5833,10 @@ var CDPWallet = class {
|
|
|
4144
5833
|
listChains,
|
|
4145
5834
|
loadCDPWallet,
|
|
4146
5835
|
loadWallet,
|
|
5836
|
+
resetIntentCache,
|
|
5837
|
+
resetWalletCache,
|
|
5838
|
+
setAlipayLogLevel,
|
|
5839
|
+
setAlipayLogSink,
|
|
4147
5840
|
verifyPayment,
|
|
4148
5841
|
waitForTransaction,
|
|
4149
5842
|
walletExists
|