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.
Files changed (55) hide show
  1. package/README.md +143 -0
  2. package/dist/cdp/index.js.map +1 -1
  3. package/dist/cdp/index.mjs.map +1 -1
  4. package/dist/{cdp-DeohBe1o.d.ts → cdp-B5PhDUTC.d.ts} +1 -1
  5. package/dist/{cdp-p_eHuQpb.d.mts → cdp-D-5Hg3y_.d.mts} +1 -1
  6. package/dist/chains/index.d.mts +37 -1
  7. package/dist/chains/index.d.ts +37 -1
  8. package/dist/chains/index.js +22 -0
  9. package/dist/chains/index.js.map +1 -1
  10. package/dist/chains/index.mjs +19 -0
  11. package/dist/chains/index.mjs.map +1 -1
  12. package/dist/cli/index.js +1941 -204
  13. package/dist/cli/index.js.map +1 -1
  14. package/dist/cli/index.mjs +1938 -201
  15. package/dist/cli/index.mjs.map +1 -1
  16. package/dist/client/index.d.mts +46 -0
  17. package/dist/client/index.d.ts +46 -0
  18. package/dist/client/index.js +1059 -118
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +1055 -116
  21. package/dist/client/index.mjs.map +1 -1
  22. package/dist/client/web/index.d.mts +434 -0
  23. package/dist/client/web/index.mjs +1300 -0
  24. package/dist/client/web/index.mjs.map +1 -0
  25. package/dist/facilitators/index.d.mts +185 -6
  26. package/dist/facilitators/index.d.ts +185 -6
  27. package/dist/facilitators/index.js +497 -13
  28. package/dist/facilitators/index.js.map +1 -1
  29. package/dist/facilitators/index.mjs +492 -13
  30. package/dist/facilitators/index.mjs.map +1 -1
  31. package/dist/index.d.mts +77 -2
  32. package/dist/index.d.ts +77 -2
  33. package/dist/index.js +1865 -172
  34. package/dist/index.js.map +1 -1
  35. package/dist/index.mjs +1854 -167
  36. package/dist/index.mjs.map +1 -1
  37. package/dist/mcp/index.js +1062 -123
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/index.mjs +1059 -120
  40. package/dist/mcp/index.mjs.map +1 -1
  41. package/dist/{registry-OsEO2dOu.d.mts → registry-DIo0WNoO.d.mts} +8 -1
  42. package/dist/{registry-OsEO2dOu.d.ts → registry-DIo0WNoO.d.ts} +8 -1
  43. package/dist/server/index.d.mts +137 -2
  44. package/dist/server/index.d.ts +137 -2
  45. package/dist/server/index.js +757 -30
  46. package/dist/server/index.js.map +1 -1
  47. package/dist/server/index.mjs +757 -30
  48. package/dist/server/index.mjs.map +1 -1
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs.map +1 -1
  51. package/dist/wallet/index.js.map +1 -1
  52. package/dist/wallet/index.mjs.map +1 -1
  53. package/package.json +15 -2
  54. package/schemas/moltspay.services.schema.json +100 -6
  55. 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 verifyResult = await this.verify(paymentPayload, requirements);
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: ethers5 } = await import("ethers");
831
- const wallet = new ethers5.Wallet(this.serverPrivateKey);
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: ethers5 } = await import("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 = ethers5.verifyTypedData(
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: ethers5 } = await import("ethers");
894
- const provider = new ethers5.JsonRpcProvider(rpcUrl);
895
- const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
896
- const tokenContract = new ethers5.Contract(token, [
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 connection = new import_web32.Connection(chainConfig.rpc, "confirmed");
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)(connection, recipientATA);
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 connection.getLatestBlockhash();
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: "pathUSD", version: "1" },
1500
- USDT: { name: "alphaUSD", version: "1" }
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
- res.setHeader("Access-Control-Allow-Origin", "*");
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
- return await this.handleExecute(body, paymentHeader, res);
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
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
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
- res.writeHead(402, {
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: "exact",
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
- return requirements;
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((resolve, reject) => {
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
- resolve(body ? JSON.parse(body) : {});
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 import_os2 = require("os");
2660
- var import_path2 = require("path");
2661
- var import_ethers = require("ethers");
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 import_web35 = require("@solana/web3.js");
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, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
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 import_ethers.Wallet(this.walletData.privateKey);
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 import_ethers.ethers.JsonRpcProvider(chain.rpc);
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 = import_ethers.ethers.parseEther("0.0005");
4804
+ const minGasBalance = import_ethers3.ethers.parseEther("0.0005");
3091
4805
  if (nativeBalance < minGasBalance) {
3092
- const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
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 intent = {
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
- token: tokenConfig.address,
4846
+ tokenAddress: tokenConfig.address,
3131
4847
  service,
3132
- nonce: Date.now(),
3133
- // Use timestamp as nonce for simplicity
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.wallet.signTypedData(domain, types, intent);
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 import_web35.PublicKey(solanaFeePayer) : void 0;
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 import_web35.PublicKey(requirements.payTo);
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
- if (feePayerPubkey) {
3229
- transaction.partialSign(solanaWallet);
3230
- } else {
3231
- transaction.sign(solanaWallet);
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 import_ethers.ethers.Contract(
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 authorization = {
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
- validAfter: validAfter.toString(),
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
- verifyingContract: tokenConfig.address
3313
- };
3314
- const types = {
3315
- TransferWithAuthorization: [
3316
- { name: "from", type: "address" },
3317
- { name: "to", type: "address" },
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, import_path2.join)(this.configDir, "config.json");
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, import_path2.join)(this.configDir, "config.json");
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, import_path2.join)(this.configDir, "spending.json");
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, import_path2.join)(this.configDir, "spending.json");
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, import_path2.join)(this.configDir, "wallet.json");
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 = import_ethers.Wallet.createRandom();
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, import_path2.join)(configDir, "wallet.json");
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, import_path2.join)(configDir, "config.json");
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 import_ethers.ethers.JsonRpcProvider(chain.rpc);
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 import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
3466
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
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(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
3470
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
3471
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
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 import_ethers.ethers.JsonRpcProvider(chain.rpc);
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 import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
3499
- new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
3500
- new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
3501
- new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
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(import_ethers.ethers.formatUnits(pathUSD, 6)),
5191
+ usdc: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
3505
5192
  // pathUSD as default USDC
3506
- usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
5193
+ usdt: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
3507
5194
  // alphaUSD as default USDT
3508
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
5195
+ native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance)),
3509
5196
  tempo: {
3510
- pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
3511
- alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
3512
- betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
3513
- thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
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 import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
3520
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
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(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
3524
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
3525
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
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 import_ethers2 = require("ethers");
5340
+ var import_ethers4 = require("ethers");
3654
5341
 
3655
5342
  // src/wallet/createWallet.ts
3656
- var import_ethers3 = require("ethers");
5343
+ var import_ethers5 = require("ethers");
3657
5344
  var import_fs5 = require("fs");
3658
- var import_path3 = require("path");
5345
+ var import_path4 = require("path");
3659
5346
  var import_crypto = require("crypto");
3660
- var DEFAULT_STORAGE_DIR = (0, import_path3.join)(process.env.HOME || "~", ".moltspay");
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, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
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 = import_ethers3.ethers.Wallet.createRandom();
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, import_path3.dirname)(storagePath);
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, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
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, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
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, import_path3.join)(DEFAULT_STORAGE_DIR, DEFAULT_STORAGE_FILE);
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 import_ethers4 = require("ethers");
3774
- var TRANSFER_EVENT_TOPIC3 = import_ethers4.ethers.id("Transfer(address,address,uint256)");
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 import_ethers4.ethers.JsonRpcProvider(chain.rpc);
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 import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
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 import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
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: ethers5 } = await import("ethers");
4041
- const provider = new ethers5.JsonRpcProvider(this.chainConfig.rpc);
4042
- const usdcContract = new ethers5.Contract(
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: ethers5.formatEther(ethBalance)
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: ethers5 } = await import("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 ethers5.Interface([
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