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/cli/index.js CHANGED
@@ -37,18 +37,21 @@ var init_cjs_shims = __esm({
37
37
  init_cjs_shims();
38
38
  var import_crypto = require("crypto");
39
39
  var import_commander = require("commander");
40
- var import_os3 = require("os");
41
- var import_path3 = require("path");
40
+ var import_os4 = require("os");
41
+ var import_path4 = require("path");
42
42
  var import_fs5 = require("fs");
43
- var import_child_process = require("child_process");
44
- var import_ethers2 = require("ethers");
43
+ var import_child_process3 = require("child_process");
44
+ var import_ethers4 = require("ethers");
45
45
 
46
46
  // src/client/index.ts
47
47
  init_cjs_shims();
48
+
49
+ // src/client/node/index.ts
50
+ init_cjs_shims();
48
51
  var import_fs2 = require("fs");
49
- var import_os2 = require("os");
50
- var import_path2 = require("path");
51
- var import_ethers = require("ethers");
52
+ var import_os3 = require("os");
53
+ var import_path3 = require("path");
54
+ var import_ethers2 = require("ethers");
52
55
 
53
56
  // src/chains/index.ts
54
57
  init_cjs_shims();
@@ -224,6 +227,10 @@ function getChain(name) {
224
227
  }
225
228
  return config;
226
229
  }
230
+ var ALIPAY_CHAIN_ID = "alipay";
231
+ function isAlipayChainId(id) {
232
+ return id === ALIPAY_CHAIN_ID;
233
+ }
227
234
 
228
235
  // src/wallet/solana.ts
229
236
  init_cjs_shims();
@@ -532,16 +539,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
532
539
  return this.supportedNetworks.includes(network);
533
540
  }
534
541
  };
535
- async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
542
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
536
543
  const chainConfig = SOLANA_CHAINS[chain];
537
- const connection = new import_web33.Connection(chainConfig.rpc, "confirmed");
544
+ const conn = connection ?? new import_web33.Connection(chainConfig.rpc, "confirmed");
538
545
  const mint = new import_web33.PublicKey(chainConfig.tokens.USDC.mint);
539
546
  const actualFeePayer = feePayerPubkey || senderPubkey;
540
547
  const senderATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, senderPubkey);
541
548
  const recipientATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, recipientPubkey);
542
549
  const transaction = new import_web33.Transaction();
543
550
  try {
544
- await (0, import_spl_token2.getAccount)(connection, recipientATA);
551
+ await (0, import_spl_token2.getAccount)(conn, recipientATA);
545
552
  } catch {
546
553
  transaction.add(
547
554
  (0, import_spl_token2.createAssociatedTokenAccountInstruction)(
@@ -572,22 +579,944 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
572
579
  // decimals
573
580
  )
574
581
  );
575
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
582
+ const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
576
583
  transaction.recentBlockhash = blockhash;
577
584
  transaction.feePayer = actualFeePayer;
578
585
  return transaction;
579
586
  }
580
587
 
581
- // src/client/index.ts
582
- var import_web34 = require("@solana/web3.js");
588
+ // src/client/node/index.ts
589
+ var import_web35 = require("@solana/web3.js");
583
590
 
584
- // src/client/types.ts
591
+ // src/client/core/index.ts
585
592
  init_cjs_shims();
586
593
 
587
- // src/client/index.ts
594
+ // src/client/core/types.ts
595
+ init_cjs_shims();
588
596
  var X402_VERSION = 2;
589
597
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
590
598
  var PAYMENT_HEADER = "x-payment";
599
+
600
+ // src/client/core/chain-map.ts
601
+ init_cjs_shims();
602
+ var NETWORK_TO_CHAIN = {
603
+ "eip155:8453": "base",
604
+ "eip155:137": "polygon",
605
+ "eip155:84532": "base_sepolia",
606
+ "eip155:42431": "tempo_moderato",
607
+ "eip155:56": "bnb",
608
+ "eip155:97": "bnb_testnet",
609
+ "solana:mainnet": "solana",
610
+ "solana:devnet": "solana_devnet"
611
+ };
612
+ var CHAIN_TO_NETWORK = Object.fromEntries(
613
+ Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
614
+ );
615
+ function networkToChainName(network) {
616
+ return NETWORK_TO_CHAIN[network] ?? null;
617
+ }
618
+
619
+ // src/client/core/base64.ts
620
+ init_cjs_shims();
621
+ var BufferCtor = globalThis.Buffer;
622
+
623
+ // src/client/core/errors.ts
624
+ init_cjs_shims();
625
+ var MoltsPayError = class extends Error {
626
+ code;
627
+ constructor(code, message) {
628
+ super(message);
629
+ this.name = "MoltsPayError";
630
+ this.code = code;
631
+ }
632
+ };
633
+
634
+ // src/client/core/eip3009.ts
635
+ init_cjs_shims();
636
+ var EIP3009_TYPES = {
637
+ TransferWithAuthorization: [
638
+ { name: "from", type: "address" },
639
+ { name: "to", type: "address" },
640
+ { name: "value", type: "uint256" },
641
+ { name: "validAfter", type: "uint256" },
642
+ { name: "validBefore", type: "uint256" },
643
+ { name: "nonce", type: "bytes32" }
644
+ ]
645
+ };
646
+ function buildEIP3009TypedData(args) {
647
+ const validAfter = args.validAfter ?? "0";
648
+ const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
649
+ const authorization = {
650
+ from: args.from,
651
+ to: args.to,
652
+ value: args.value,
653
+ validAfter,
654
+ validBefore,
655
+ nonce: args.nonce
656
+ };
657
+ return {
658
+ domain: {
659
+ name: args.tokenName,
660
+ version: args.tokenVersion,
661
+ chainId: args.chainId,
662
+ verifyingContract: args.tokenAddress
663
+ },
664
+ types: EIP3009_TYPES,
665
+ primaryType: "TransferWithAuthorization",
666
+ message: authorization
667
+ };
668
+ }
669
+
670
+ // src/client/core/eip2612.ts
671
+ init_cjs_shims();
672
+
673
+ // src/client/core/bnb-intent.ts
674
+ init_cjs_shims();
675
+ var BNB_INTENT_TYPES = {
676
+ PaymentIntent: [
677
+ { name: "from", type: "address" },
678
+ { name: "to", type: "address" },
679
+ { name: "amount", type: "uint256" },
680
+ { name: "token", type: "address" },
681
+ { name: "service", type: "string" },
682
+ { name: "nonce", type: "uint256" },
683
+ { name: "deadline", type: "uint256" }
684
+ ]
685
+ };
686
+ var BNB_DOMAIN_NAME = "MoltsPay";
687
+ var BNB_DOMAIN_VERSION = "1";
688
+ function buildBnbIntentTypedData(args) {
689
+ const intent = {
690
+ from: args.from,
691
+ to: args.to,
692
+ amount: args.amount,
693
+ token: args.tokenAddress,
694
+ service: args.service,
695
+ nonce: args.nonce,
696
+ deadline: args.deadline
697
+ };
698
+ return {
699
+ domain: {
700
+ name: BNB_DOMAIN_NAME,
701
+ version: BNB_DOMAIN_VERSION,
702
+ chainId: args.chainId
703
+ },
704
+ types: BNB_INTENT_TYPES,
705
+ primaryType: "PaymentIntent",
706
+ message: intent
707
+ };
708
+ }
709
+
710
+ // src/client/core/solana-tx.ts
711
+ init_cjs_shims();
712
+
713
+ // src/client/core/x402.ts
714
+ init_cjs_shims();
715
+
716
+ // src/client/node/signer.ts
717
+ init_cjs_shims();
718
+ var import_ethers = require("ethers");
719
+ var import_web34 = require("@solana/web3.js");
720
+ var NodeSigner = class {
721
+ evmWallet;
722
+ getSolanaKeypair;
723
+ constructor(evmWallet, options = {}) {
724
+ this.evmWallet = evmWallet;
725
+ this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
726
+ }
727
+ async getEvmAddress() {
728
+ return this.evmWallet.address;
729
+ }
730
+ async getSolanaAddress() {
731
+ const kp = this.getSolanaKeypair();
732
+ return kp ? kp.publicKey.toBase58() : null;
733
+ }
734
+ async signTypedData(envelope) {
735
+ const mutableTypes = {};
736
+ for (const [key, fields] of Object.entries(envelope.types)) {
737
+ mutableTypes[key] = [...fields];
738
+ }
739
+ return this.evmWallet.signTypedData(
740
+ envelope.domain,
741
+ mutableTypes,
742
+ envelope.message
743
+ );
744
+ }
745
+ async sendEvmTransaction(args) {
746
+ const chain = findChainByChainId(args.chainId);
747
+ if (!chain) {
748
+ throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
749
+ }
750
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
751
+ const connected = this.evmWallet.connect(provider);
752
+ const tx = await connected.sendTransaction({
753
+ to: args.to,
754
+ data: args.data,
755
+ value: args.value ? BigInt(args.value) : 0n
756
+ });
757
+ return tx.hash;
758
+ }
759
+ async signSolanaTransaction(args) {
760
+ const kp = this.getSolanaKeypair();
761
+ if (!kp) {
762
+ throw new Error("signSolanaTransaction: no Solana wallet configured");
763
+ }
764
+ const tx = import_web34.Transaction.from(Buffer.from(args.transactionBase64, "base64"));
765
+ if (args.partialSign) {
766
+ tx.partialSign(kp);
767
+ } else {
768
+ tx.sign(kp);
769
+ }
770
+ return tx.serialize({ requireAllSignatures: false }).toString("base64");
771
+ }
772
+ };
773
+ function findChainByChainId(chainId) {
774
+ for (const cfg of Object.values(CHAINS)) {
775
+ if (cfg.chainId === chainId) {
776
+ return cfg;
777
+ }
778
+ }
779
+ return void 0;
780
+ }
781
+
782
+ // src/client/alipay/index.ts
783
+ init_cjs_shims();
784
+ var import_node_crypto = require("crypto");
785
+ var import_promises = require("fs/promises");
786
+ var import_os2 = require("os");
787
+ var import_path2 = require("path");
788
+
789
+ // src/client/alipay/cli.ts
790
+ init_cjs_shims();
791
+ var import_child_process = require("child_process");
792
+
793
+ // src/client/alipay/log.ts
794
+ init_cjs_shims();
795
+ var RANK = { off: 0, info: 1, debug: 2 };
796
+ function normalizeLevel(v) {
797
+ const s = (v ?? "").toLowerCase();
798
+ return s === "info" || s === "debug" ? s : "off";
799
+ }
800
+ var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
801
+ var sink = (line) => {
802
+ process.stderr.write(line + "\n");
803
+ };
804
+ function enabledFor(want) {
805
+ return RANK[level] >= RANK[want];
806
+ }
807
+ function render(event, fields) {
808
+ const parts = ["[moltspay:alipay]", String(fields.ts), event];
809
+ if (fields.flow) parts.push(`flow=${fields.flow}`);
810
+ if (fields.step) parts.push(`step=${fields.step}`);
811
+ if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
812
+ for (const [k, v] of Object.entries(fields)) {
813
+ if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
814
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
815
+ }
816
+ return parts.join(" ");
817
+ }
818
+ function emit(lvl, event, fields) {
819
+ if (!enabledFor(lvl)) return;
820
+ const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
821
+ try {
822
+ sink(render(event, full), full);
823
+ } catch {
824
+ }
825
+ }
826
+ var alipayLog = {
827
+ info: (event, fields = {}) => emit("info", event, fields),
828
+ debug: (event, fields = {}) => emit("debug", event, fields),
829
+ /** True if anything at all would be logged (cheap guard for hot paths). */
830
+ enabled: () => level !== "off"
831
+ };
832
+ async function timeStep(step, flow, fn) {
833
+ if (!alipayLog.enabled()) return fn();
834
+ const t0 = Date.now();
835
+ alipayLog.debug("step.start", { flow, step });
836
+ try {
837
+ const out = await fn();
838
+ alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
839
+ return out;
840
+ } catch (err) {
841
+ alipayLog.info("step.end", {
842
+ flow,
843
+ step,
844
+ ms: Date.now() - t0,
845
+ ok: false,
846
+ err: err instanceof Error ? err.message : String(err)
847
+ });
848
+ throw err;
849
+ }
850
+ }
851
+
852
+ // src/client/alipay/cli.ts
853
+ var ALLOWED_ENV = /* @__PURE__ */ new Set([
854
+ "AIPAY_OUTPUT_CHANNEL",
855
+ "AIPAY_SESSION_ID",
856
+ "AIPAY_FRAMEWORK",
857
+ "AIPAY_MODEL",
858
+ "AIPAY_OS",
859
+ // Minimal survival set for spawn to find the binary and a home dir.
860
+ "PATH",
861
+ "HOME"
862
+ ]);
863
+ function filterEnv(env) {
864
+ return Object.fromEntries(
865
+ Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
866
+ );
867
+ }
868
+ function makeLineSplitter(onLine) {
869
+ let buf = "";
870
+ return (chunk) => {
871
+ buf += chunk.toString("utf-8");
872
+ let nl;
873
+ while ((nl = buf.indexOf("\n")) !== -1) {
874
+ onLine(buf.slice(0, nl));
875
+ buf = buf.slice(nl + 1);
876
+ }
877
+ };
878
+ }
879
+ function profileEnv(step, flow) {
880
+ const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
881
+ const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
882
+ if (!want || !hook) return {};
883
+ const steps = want.split(",").map((s) => s.trim());
884
+ if (!steps.includes("all") && !steps.includes(step)) return {};
885
+ const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
886
+ const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
887
+ const out = `${dir}/cli-profile-${safe}.json`;
888
+ alipayLog.info("cli.profile", { flow, step, out });
889
+ return {
890
+ NODE_OPTIONS: `--require=${hook}`,
891
+ MOLTSPAY_CLI_PROFILE_OUT: out
892
+ };
893
+ }
894
+ var runCli = (args, opts = {}) => {
895
+ const bin = opts.bin ?? "alipay-bot";
896
+ const step = opts.step ?? args[0] ?? "(no-arg)";
897
+ const startedAt = Date.now();
898
+ const lines = [];
899
+ const collect = (line) => {
900
+ lines.push(line);
901
+ alipayLog.debug("cli.line", {
902
+ flow: opts.flow,
903
+ step,
904
+ ms: Date.now() - startedAt,
905
+ preview: line.slice(0, 120)
906
+ });
907
+ opts.onLine?.(line);
908
+ };
909
+ alipayLog.debug("step.start", { flow: opts.flow, step });
910
+ return new Promise((resolve3, reject) => {
911
+ const child = (0, import_child_process.spawn)(bin, args, {
912
+ env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
913
+ });
914
+ if (opts.signal) {
915
+ if (opts.signal.aborted) child.kill("SIGTERM");
916
+ opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
917
+ }
918
+ let sawByte = false;
919
+ const onChunk = (stream) => (chunk) => {
920
+ const ms = Date.now() - startedAt;
921
+ if (!sawByte) {
922
+ sawByte = true;
923
+ alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
924
+ }
925
+ alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
926
+ };
927
+ const onStdout = makeLineSplitter(collect);
928
+ const onStderr = makeLineSplitter(collect);
929
+ child.stdout?.on("data", onChunk("out"));
930
+ child.stderr?.on("data", onChunk("err"));
931
+ child.stdout?.on("data", onStdout);
932
+ child.stderr?.on("data", onStderr);
933
+ child.on("error", (err) => {
934
+ alipayLog.info("cli.exit", {
935
+ flow: opts.flow,
936
+ step,
937
+ ms: Date.now() - startedAt,
938
+ ok: false,
939
+ err: err.message
940
+ });
941
+ reject(err);
942
+ });
943
+ child.on("close", (code) => {
944
+ alipayLog.info("cli.exit", {
945
+ flow: opts.flow,
946
+ step,
947
+ ms: Date.now() - startedAt,
948
+ ok: (code ?? 1) === 0,
949
+ code: code ?? 1
950
+ });
951
+ resolve3({ exitCode: code ?? 1, lines });
952
+ });
953
+ });
954
+ };
955
+
956
+ // src/client/alipay/install.ts
957
+ init_cjs_shims();
958
+ var import_child_process2 = require("child_process");
959
+ var import_util = require("util");
960
+
961
+ // src/client/alipay/errors.ts
962
+ init_cjs_shims();
963
+ var AlipayCliNotFoundError = class extends MoltsPayError {
964
+ constructor(message) {
965
+ super("ALIPAY_CLI_NOT_FOUND", message);
966
+ this.name = "AlipayCliNotFoundError";
967
+ }
968
+ };
969
+ var AlipayCliVersionError = class extends MoltsPayError {
970
+ constructor(message) {
971
+ super("ALIPAY_CLI_VERSION", message);
972
+ this.name = "AlipayCliVersionError";
973
+ }
974
+ };
975
+ var NeedsWalletSetupError = class extends MoltsPayError {
976
+ constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
977
+ super("ALIPAY_NEEDS_WALLET_SETUP", message);
978
+ this.name = "NeedsWalletSetupError";
979
+ }
980
+ };
981
+ var AlipayPaymentRejectedError = class extends MoltsPayError {
982
+ constructor(message) {
983
+ super("ALIPAY_PAYMENT_REJECTED", message);
984
+ this.name = "AlipayPaymentRejectedError";
985
+ }
986
+ };
987
+ var AlipayPaymentTimeoutError = class extends MoltsPayError {
988
+ constructor(message) {
989
+ super("ALIPAY_PAYMENT_TIMEOUT", message);
990
+ this.name = "AlipayPaymentTimeoutError";
991
+ }
992
+ };
993
+ var AlipayProtocolError = class extends MoltsPayError {
994
+ constructor(message) {
995
+ super("ALIPAY_PROTOCOL", message);
996
+ this.name = "AlipayProtocolError";
997
+ }
998
+ };
999
+ var UnsupportedRailError = class extends MoltsPayError {
1000
+ constructor(rail, message) {
1001
+ super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
1002
+ this.rail = rail;
1003
+ this.name = "UnsupportedRailError";
1004
+ }
1005
+ };
1006
+
1007
+ // src/client/alipay/install.ts
1008
+ var execFileAsync = (0, import_util.promisify)(import_child_process2.execFile);
1009
+ var MIN_CLI_VERSION = "0.3.15";
1010
+ function semverLt(a, b) {
1011
+ const pa = a.split(".").map((n) => parseInt(n, 10));
1012
+ const pb = b.split(".").map((n) => parseInt(n, 10));
1013
+ for (let i = 0; i < 3; i++) {
1014
+ const x = pa[i] ?? 0;
1015
+ const y = pb[i] ?? 0;
1016
+ if (x !== y) return x < y;
1017
+ }
1018
+ return false;
1019
+ }
1020
+ function parseVersion(stdout) {
1021
+ return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
1022
+ }
1023
+ var defaultGetVersion = async () => {
1024
+ const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
1025
+ return stdout;
1026
+ };
1027
+ async function ensureCliUncached(getVersion2) {
1028
+ let stdout;
1029
+ try {
1030
+ stdout = await getVersion2();
1031
+ } catch (e) {
1032
+ if (e?.code === "ENOENT") {
1033
+ throw new AlipayCliNotFoundError(
1034
+ "alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
1035
+ );
1036
+ }
1037
+ throw e;
1038
+ }
1039
+ const version = parseVersion(stdout);
1040
+ if (!version || semverLt(version, MIN_CLI_VERSION)) {
1041
+ throw new AlipayCliVersionError(
1042
+ `alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
1043
+ );
1044
+ }
1045
+ return version;
1046
+ }
1047
+ var cachedVersion = null;
1048
+ var inflight = null;
1049
+ async function ensureCli(getVersion2 = defaultGetVersion) {
1050
+ if (getVersion2 !== defaultGetVersion) {
1051
+ return ensureCliUncached(getVersion2);
1052
+ }
1053
+ if (cachedVersion) return cachedVersion;
1054
+ if (!inflight) {
1055
+ inflight = ensureCliUncached(getVersion2).then((v) => {
1056
+ cachedVersion = v;
1057
+ return v;
1058
+ }).finally(() => {
1059
+ inflight = null;
1060
+ });
1061
+ }
1062
+ return inflight;
1063
+ }
1064
+
1065
+ // src/client/alipay/poll.ts
1066
+ init_cjs_shims();
1067
+ var POLL_INTERVAL_MS = 3e3;
1068
+ var POLL_MAX_INFLIGHT = 2;
1069
+ function resolveMaxInflight(override) {
1070
+ if (override !== void 0) return Math.max(1, Math.floor(override));
1071
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
1072
+ if (raw !== void 0 && raw.trim() !== "") {
1073
+ const n = Number(raw);
1074
+ if (Number.isFinite(n) && n >= 1) return Math.floor(n);
1075
+ }
1076
+ return POLL_MAX_INFLIGHT;
1077
+ }
1078
+ function resolveLaunchGap(override) {
1079
+ if (override !== void 0) return Math.max(0, override);
1080
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
1081
+ if (raw !== void 0 && raw.trim() !== "") {
1082
+ const n = Number(raw);
1083
+ if (Number.isFinite(n) && n >= 0) return n;
1084
+ }
1085
+ return POLL_INTERVAL_MS;
1086
+ }
1087
+ function parseStatus(lines) {
1088
+ const raw = lines.join("\n").trim();
1089
+ try {
1090
+ const json = JSON.parse(raw);
1091
+ if (json && typeof json === "object") {
1092
+ if (typeof json.success === "boolean") {
1093
+ if (json.success) return "paid";
1094
+ const err = String(json.errorCode ?? "").toUpperCase();
1095
+ if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
1096
+ if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
1097
+ return "pending";
1098
+ }
1099
+ if (typeof json.code !== "undefined") {
1100
+ if (Number(json.code) === 200) return "paid";
1101
+ const msg = `${json.message ?? ""}${json.reason ?? ""}`;
1102
+ if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
1103
+ return "pending";
1104
+ }
1105
+ if (typeof json.body === "string") {
1106
+ return classifyReportText(json.body);
1107
+ }
1108
+ }
1109
+ } catch {
1110
+ }
1111
+ return classifyReportText(raw);
1112
+ }
1113
+ function classifyReportText(s) {
1114
+ const text = s.toUpperCase();
1115
+ if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
1116
+ if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
1117
+ if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
1118
+ return "unknown";
1119
+ }
1120
+ function defaultSleep(ms, signal) {
1121
+ return new Promise((resolve3, reject) => {
1122
+ if (signal?.aborted) return reject(new Error("aborted"));
1123
+ const t = setTimeout(resolve3, ms);
1124
+ signal?.addEventListener(
1125
+ "abort",
1126
+ () => {
1127
+ clearTimeout(t);
1128
+ reject(new Error("aborted"));
1129
+ },
1130
+ { once: true }
1131
+ );
1132
+ });
1133
+ }
1134
+ var LAUNCH = /* @__PURE__ */ Symbol("launch");
1135
+ async function pollUntil(tradeNo, resourceUrl, opts) {
1136
+ const runner = opts.runner ?? runCli;
1137
+ const sleep = opts.sleep ?? defaultSleep;
1138
+ const now = opts.now ?? Date.now;
1139
+ const maxInflight = resolveMaxInflight(opts.maxInflight);
1140
+ const launchGap = resolveLaunchGap(opts.launchIntervalMs);
1141
+ const args = [
1142
+ "402-query-payment-status",
1143
+ "-t",
1144
+ tradeNo,
1145
+ "-r",
1146
+ resourceUrl,
1147
+ ...opts.method ? ["-m", opts.method] : [],
1148
+ ...opts.data ? ["-d", opts.data] : []
1149
+ ];
1150
+ const internal = new AbortController();
1151
+ const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
1152
+ let tick = 0;
1153
+ let lastLaunch = -Infinity;
1154
+ const inflight2 = /* @__PURE__ */ new Set();
1155
+ const launch = () => {
1156
+ tick += 1;
1157
+ const myTick = tick;
1158
+ lastLaunch = now();
1159
+ const run = (async () => {
1160
+ const { lines } = await runner(args, {
1161
+ signal,
1162
+ step: "query-payment-status",
1163
+ flow: tradeNo
1164
+ });
1165
+ const status = parseStatus(lines);
1166
+ alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
1167
+ return { status, lines };
1168
+ })();
1169
+ const tracked = run.finally(() => {
1170
+ inflight2.delete(tracked);
1171
+ });
1172
+ inflight2.add(tracked);
1173
+ };
1174
+ try {
1175
+ for (; ; ) {
1176
+ if (opts.signal?.aborted) throw new Error("aborted");
1177
+ const expired = now() >= opts.deadline;
1178
+ if (expired && inflight2.size === 0) {
1179
+ throw new AlipayPaymentTimeoutError(
1180
+ `Payment ${tradeNo} not completed before pay_before deadline`
1181
+ );
1182
+ }
1183
+ while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
1184
+ launch();
1185
+ }
1186
+ const waiters = [...inflight2];
1187
+ if (!expired && inflight2.size < maxInflight) {
1188
+ const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
1189
+ waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
1190
+ }
1191
+ const winner = await Promise.race(waiters);
1192
+ if (winner === LAUNCH) continue;
1193
+ if (winner.status === "paid") return { status: "paid", lines: winner.lines };
1194
+ if (winner.status === "rejected") {
1195
+ throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
1196
+ }
1197
+ }
1198
+ } finally {
1199
+ internal.abort();
1200
+ for (const p of inflight2) p.catch(() => void 0);
1201
+ }
1202
+ }
1203
+
1204
+ // src/client/alipay/index.ts
1205
+ var TRADE_NO_RE = /^\d{32}$/;
1206
+ function resolveSessionId(explicit, envSession) {
1207
+ return explicit ?? envSession ?? (0, import_node_crypto.randomUUID)();
1208
+ }
1209
+ function assertTradeNo(t) {
1210
+ if (!TRADE_NO_RE.test(t)) {
1211
+ throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
1212
+ }
1213
+ }
1214
+ function parseTradeNo(lines) {
1215
+ for (const line of lines) {
1216
+ const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
1217
+ if (labeled) return labeled[1];
1218
+ const bare = line.match(/\b(\d{32})\b/);
1219
+ if (bare) return bare[1];
1220
+ }
1221
+ return null;
1222
+ }
1223
+ function parsePaymentUrl(lines) {
1224
+ let paymentUrl;
1225
+ let shortenUrl;
1226
+ for (const line of lines) {
1227
+ const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
1228
+ if (!m) continue;
1229
+ const url = m[1].replace(/[)\]`>]+$/, "");
1230
+ if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
1231
+ else if (!paymentUrl) paymentUrl = url;
1232
+ }
1233
+ if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
1234
+ return { paymentUrl, shortenUrl };
1235
+ }
1236
+ function extractMedia(line) {
1237
+ const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
1238
+ return m ? m[1] : null;
1239
+ }
1240
+ function isWalletReady(lines) {
1241
+ const text = lines.join("\n").trim();
1242
+ try {
1243
+ const json = JSON.parse(text);
1244
+ if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
1245
+ } catch {
1246
+ }
1247
+ return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
1248
+ }
1249
+ function extractResourceFromReport(report) {
1250
+ const label = report.search(/资源响应体/);
1251
+ const start = report.indexOf("{", label === -1 ? 0 : label);
1252
+ if (start === -1) return void 0;
1253
+ let depth = 0, inStr = false, esc = false;
1254
+ for (let i = start; i < report.length; i++) {
1255
+ const ch = report[i];
1256
+ if (inStr) {
1257
+ if (esc) esc = false;
1258
+ else if (ch === "\\") esc = true;
1259
+ else if (ch === '"') inStr = false;
1260
+ continue;
1261
+ }
1262
+ if (ch === '"') inStr = true;
1263
+ else if (ch === "{") depth++;
1264
+ else if (ch === "}" && --depth === 0) {
1265
+ const slice = report.slice(start, i + 1);
1266
+ try {
1267
+ const obj = JSON.parse(slice);
1268
+ const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
1269
+ return typeof r === "string" ? r : JSON.stringify(r);
1270
+ } catch {
1271
+ return slice;
1272
+ }
1273
+ }
1274
+ }
1275
+ return void 0;
1276
+ }
1277
+ function extractBody(lines) {
1278
+ const text = lines.join("\n").trim();
1279
+ try {
1280
+ const json = JSON.parse(text);
1281
+ if (json && typeof json === "object") {
1282
+ if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
1283
+ const rr = json.resourceResponse;
1284
+ const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
1285
+ return typeof r === "string" ? r : JSON.stringify(r);
1286
+ }
1287
+ if (typeof json.body === "string") {
1288
+ const inner = extractResourceFromReport(json.body);
1289
+ return inner ?? json.body;
1290
+ }
1291
+ const body = json.data ?? json.result ?? json.body ?? json.resource;
1292
+ if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
1293
+ return text;
1294
+ }
1295
+ } catch {
1296
+ }
1297
+ const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
1298
+ if (idx !== -1) {
1299
+ const first = lines[idx].replace(/^\s*BODY:\s*/, "");
1300
+ return [first, ...lines.slice(idx + 1)].join("\n").trim();
1301
+ }
1302
+ return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
1303
+ }
1304
+ var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
1305
+ function resolveWalletTtlMs() {
1306
+ const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
1307
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
1308
+ const n = Number(raw);
1309
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
1310
+ }
1311
+ var walletReadyUntil = /* @__PURE__ */ new Map();
1312
+ var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
1313
+ function resolveIntentTtlMs() {
1314
+ const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
1315
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
1316
+ const n = Number(raw);
1317
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
1318
+ }
1319
+ var intentDoneUntil = /* @__PURE__ */ new Map();
1320
+ var AlipayClient = class {
1321
+ sessionId;
1322
+ configDir;
1323
+ framework;
1324
+ runner;
1325
+ getVersion;
1326
+ now;
1327
+ /** Only the default runner may use the process-level wallet cache. */
1328
+ walletCacheable;
1329
+ /** Only the default runner may use the process-level payment-intent cache. */
1330
+ intentCacheable;
1331
+ constructor(opts = {}) {
1332
+ this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
1333
+ this.configDir = opts.configDir ?? (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1334
+ this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
1335
+ this.runner = opts.runner ?? runCli;
1336
+ this.getVersion = opts.getVersion;
1337
+ this.now = opts.now ?? Date.now;
1338
+ this.walletCacheable = opts.cacheWallet ?? !opts.runner;
1339
+ this.intentCacheable = opts.cacheIntent ?? !opts.runner;
1340
+ }
1341
+ /**
1342
+ * Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
1343
+ *
1344
+ * Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
1345
+ * "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
1346
+ */
1347
+ async checkWallet(signal, flow) {
1348
+ const ttlMs = resolveWalletTtlMs();
1349
+ const cacheKey = `${this.configDir}::${this.framework}`;
1350
+ const useCache = this.walletCacheable && ttlMs > 0;
1351
+ if (useCache) {
1352
+ const until = walletReadyUntil.get(cacheKey);
1353
+ if (until !== void 0 && this.now() < until) {
1354
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
1355
+ return;
1356
+ }
1357
+ }
1358
+ const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
1359
+ if (!isWalletReady(lines)) {
1360
+ if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
1361
+ throw new NeedsWalletSetupError(
1362
+ "Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
1363
+ );
1364
+ }
1365
+ if (useCache) {
1366
+ walletReadyUntil.set(cacheKey, this.now() + ttlMs);
1367
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
1368
+ }
1369
+ }
1370
+ /** Run the full 8-step flow and resolve with the resource body. */
1371
+ async pay402(opts) {
1372
+ const { resourceUrl, requirement, signal } = opts;
1373
+ const extra = requirement.extra ?? {};
1374
+ const paymentNeededHeader = String(extra.payment_needed_header ?? "");
1375
+ if (!paymentNeededHeader) {
1376
+ throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
1377
+ }
1378
+ const flow = this.sessionId;
1379
+ const flowStart = this.now();
1380
+ alipayLog.info("flow.start", { flow, resource: resourceUrl });
1381
+ const media = [];
1382
+ const onLine = (line) => {
1383
+ const m = extractMedia(line);
1384
+ if (m) media.push(m);
1385
+ else opts.onLine?.(line);
1386
+ };
1387
+ const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1388
+ await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1389
+ const intentTtlMs = resolveIntentTtlMs();
1390
+ const intentKey = `${this.configDir}::${this.framework}`;
1391
+ const useIntentCache = this.intentCacheable && intentTtlMs > 0;
1392
+ const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
1393
+ if (intentUntil !== void 0 && this.now() < intentUntil) {
1394
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
1395
+ } else {
1396
+ await this.runner(
1397
+ [
1398
+ "payment-intent",
1399
+ "--session-id",
1400
+ this.sessionId,
1401
+ "--intent-summary",
1402
+ intentSummary,
1403
+ "--framework",
1404
+ this.framework
1405
+ ],
1406
+ { onLine, signal, step: "payment-intent", flow }
1407
+ );
1408
+ if (useIntentCache) {
1409
+ intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
1410
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
1411
+ }
1412
+ }
1413
+ await this.checkWallet(signal, flow);
1414
+ const reqId = String(extra.out_trade_no ?? (0, import_node_crypto.randomUUID)());
1415
+ const dir = (0, import_path2.join)(this.configDir, "alipay");
1416
+ await (0, import_promises.mkdir)(dir, { recursive: true });
1417
+ const challengeFile = (0, import_path2.join)(dir, `402_${reqId}.txt`);
1418
+ await (0, import_promises.writeFile)(challengeFile, paymentNeededHeader, "utf-8");
1419
+ const payArgs = [
1420
+ "402-buyer-pay",
1421
+ "-f",
1422
+ challengeFile,
1423
+ "-r",
1424
+ resourceUrl,
1425
+ "-s",
1426
+ this.sessionId,
1427
+ "-i",
1428
+ intentSummary,
1429
+ "-w",
1430
+ this.framework
1431
+ ];
1432
+ if (opts.method) payArgs.push("-m", opts.method);
1433
+ if (opts.data) payArgs.push("-d", opts.data);
1434
+ const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
1435
+ const tradeNo = parseTradeNo(payRun.lines);
1436
+ if (!tradeNo) {
1437
+ throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
1438
+ }
1439
+ assertTradeNo(tradeNo);
1440
+ const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
1441
+ if (paymentUrl) {
1442
+ alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
1443
+ opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
1444
+ }
1445
+ const pendingAt = this.now();
1446
+ const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
1447
+ const deadline = this.now() + (opts.timeoutMs ?? windowMs);
1448
+ const poll = await pollUntil(tradeNo, resourceUrl, {
1449
+ // No onLine: the status-poll output embeds the resource and must not reach
1450
+ // the log stream — the body is surfaced via the return value below (§9.3).
1451
+ deadline,
1452
+ signal,
1453
+ runner: this.runner,
1454
+ now: this.now,
1455
+ // Re-fetch the resource the same way it was paid (POST + body), else the
1456
+ // status poll defaults to GET and 404s on a POST-only `/execute`.
1457
+ method: opts.method,
1458
+ data: opts.data
1459
+ });
1460
+ alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
1461
+ const body = extractBody(poll.lines);
1462
+ void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
1463
+ onLine,
1464
+ signal,
1465
+ step: "402-buyer-fulfillment-ack",
1466
+ flow
1467
+ }).catch(() => void 0);
1468
+ return { body, payment: { tradeNo, outTradeNo: reqId }, media };
1469
+ }
1470
+ };
1471
+
1472
+ // src/client/alipay/router.ts
1473
+ init_cjs_shims();
1474
+ var ALIPAY_RAIL = "alipay";
1475
+ function railOf(req) {
1476
+ if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1477
+ return networkToChainName(req.network) ?? req.network;
1478
+ }
1479
+ function findRail(accepts, rail) {
1480
+ const requirement = accepts.find((r) => railOf(r) === rail);
1481
+ return requirement ? { rail, requirement } : null;
1482
+ }
1483
+ function selectRail(input) {
1484
+ const { serverAccepts, explicitRail, preference, availability } = input;
1485
+ if (!serverAccepts || serverAccepts.length === 0) {
1486
+ throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
1487
+ }
1488
+ if (explicitRail) {
1489
+ const hit = findRail(serverAccepts, explicitRail);
1490
+ if (!hit) {
1491
+ const offered = serverAccepts.map(railOf);
1492
+ throw new UnsupportedRailError(
1493
+ explicitRail,
1494
+ `Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
1495
+ );
1496
+ }
1497
+ return hit;
1498
+ }
1499
+ if (preference) {
1500
+ for (const pref of preference) {
1501
+ const hit = findRail(serverAccepts, pref);
1502
+ if (hit) return hit;
1503
+ }
1504
+ }
1505
+ if (availability?.evmReady) {
1506
+ const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
1507
+ if (evm) return { rail: railOf(evm), requirement: evm };
1508
+ }
1509
+ if (availability?.alipayReady) {
1510
+ const hit = findRail(serverAccepts, ALIPAY_RAIL);
1511
+ if (hit) return hit;
1512
+ }
1513
+ return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1514
+ }
1515
+
1516
+ // src/client/types.ts
1517
+ init_cjs_shims();
1518
+
1519
+ // src/client/node/index.ts
591
1520
  var DEFAULT_CONFIG = {
592
1521
  chain: "base",
593
1522
  limits: {
@@ -600,15 +1529,24 @@ var MoltsPayClient = class {
600
1529
  config;
601
1530
  walletData = null;
602
1531
  wallet = null;
1532
+ signer = null;
603
1533
  todaySpending = 0;
604
1534
  lastSpendingReset = 0;
1535
+ railPreference;
1536
+ alipaySessionId;
605
1537
  constructor(options = {}) {
606
- this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
1538
+ this.configDir = options.configDir || (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
607
1539
  this.config = this.loadConfig();
1540
+ this.railPreference = options.railPreference ?? this.config.railPreference;
1541
+ this.alipaySessionId = options.alipaySessionId;
608
1542
  this.walletData = this.loadWallet();
609
1543
  this.loadSpending();
610
1544
  if (this.walletData) {
611
- this.wallet = new import_ethers.Wallet(this.walletData.privateKey);
1545
+ this.wallet = new import_ethers2.Wallet(this.walletData.privateKey);
1546
+ const configDir = this.configDir;
1547
+ this.signer = new NodeSigner(this.wallet, {
1548
+ getSolanaKeypair: () => loadSolanaWallet(configDir)
1549
+ });
612
1550
  }
613
1551
  }
614
1552
  /**
@@ -678,6 +1616,9 @@ var MoltsPayClient = class {
678
1616
  * @param options - Payment options (token selection)
679
1617
  */
680
1618
  async pay(serverUrl, service, params, options = {}) {
1619
+ if (options.rail === ALIPAY_RAIL) {
1620
+ return this.payViaAlipay(serverUrl, service, params, options);
1621
+ }
681
1622
  if (!this.wallet || !this.walletData) {
682
1623
  throw new Error("Client not initialized. Run: npx moltspay init");
683
1624
  }
@@ -736,20 +1677,6 @@ var MoltsPayClient = class {
736
1677
  } catch {
737
1678
  throw new Error("Invalid x-payment-required header");
738
1679
  }
739
- const networkToChainName = (network2) => {
740
- if (network2 === "solana:mainnet") return "solana";
741
- if (network2 === "solana:devnet") return "solana_devnet";
742
- const match = network2.match(/^eip155:(\d+)$/);
743
- if (!match) return null;
744
- const chainId = parseInt(match[1]);
745
- if (chainId === 8453) return "base";
746
- if (chainId === 137) return "polygon";
747
- if (chainId === 84532) return "base_sepolia";
748
- if (chainId === 42431) return "tempo_moderato";
749
- if (chainId === 56) return "bnb";
750
- if (chainId === 97) return "bnb_testnet";
751
- return null;
752
- };
753
1680
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
754
1681
  const userSpecifiedChain = options.chain;
755
1682
  let selectedChain;
@@ -883,6 +1810,76 @@ Please specify: --chain <chain_name>`
883
1810
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
884
1811
  return result.result || result;
885
1812
  }
1813
+ /**
1814
+ * Pay for a service over the Alipay fiat rail (2.0.0).
1815
+ *
1816
+ * Unlike the crypto path this needs no EVM wallet — it shells out to
1817
+ * alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
1818
+ * payment to get the 402 challenge, confirm the server actually offers the
1819
+ * alipay rail (selectRail), then run the 8-step state machine and return the
1820
+ * resource body.
1821
+ */
1822
+ async payViaAlipay(serverUrl, service, params, options) {
1823
+ const flow = this.alipaySessionId;
1824
+ let executeUrl = `${serverUrl}/execute`;
1825
+ try {
1826
+ const services = await timeStep(
1827
+ "discover-services",
1828
+ flow,
1829
+ () => this.getServices(serverUrl)
1830
+ );
1831
+ const svc = services.services?.find((s) => s.id === service);
1832
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1833
+ } catch {
1834
+ }
1835
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1836
+ const bodyJson = JSON.stringify(requestBody);
1837
+ const res = await timeStep(
1838
+ "challenge-402",
1839
+ flow,
1840
+ () => fetch(executeUrl, {
1841
+ method: "POST",
1842
+ headers: { "Content-Type": "application/json" },
1843
+ body: bodyJson
1844
+ })
1845
+ );
1846
+ if (res.status !== 402) {
1847
+ const data = await res.json().catch(() => ({}));
1848
+ if (res.ok && data.result) return data.result;
1849
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
1850
+ }
1851
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
1852
+ if (!header) throw new Error("Missing x-payment-required header on 402");
1853
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
1854
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
1855
+ const { requirement } = selectRail({
1856
+ serverAccepts: accepts,
1857
+ explicitRail: ALIPAY_RAIL,
1858
+ preference: this.railPreference,
1859
+ availability: { evmReady: this.isInitialized }
1860
+ });
1861
+ const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
1862
+ const alipay = new AlipayClient({
1863
+ sessionId: this.alipaySessionId,
1864
+ configDir: this.configDir
1865
+ });
1866
+ const result = await alipay.pay402({
1867
+ resourceUrl: executeUrl,
1868
+ requirement,
1869
+ method: "POST",
1870
+ data: bodyJson,
1871
+ onLine,
1872
+ onPaymentPending: options.onPaymentPending,
1873
+ timeoutMs: options.timeoutMs,
1874
+ signal: options.signal
1875
+ });
1876
+ try {
1877
+ const json = JSON.parse(result.body);
1878
+ return json.result ?? json;
1879
+ } catch {
1880
+ return { body: result.body, payment: result.payment, media: result.media };
1881
+ }
1882
+ }
886
1883
  /**
887
1884
  * Handle MPP (Machine Payments Protocol) payment flow
888
1885
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -978,14 +1975,14 @@ Please specify: --chain <chain_name>`
978
1975
  async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
979
1976
  const { to, amount, token, chainName, chain, spender } = paymentDetails;
980
1977
  const tokenConfig = chain.tokens[token];
981
- const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
1978
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
982
1979
  const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
983
1980
  const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
984
1981
  if (allowance < amountWeiCheck) {
985
1982
  const nativeBalance = await provider.getBalance(this.wallet.address);
986
- const minGasBalance = import_ethers.ethers.parseEther("0.0005");
1983
+ const minGasBalance = import_ethers2.ethers.parseEther("0.0005");
987
1984
  if (nativeBalance < minGasBalance) {
988
- const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
1985
+ const nativeBNB = parseFloat(import_ethers2.ethers.formatEther(nativeBalance)).toFixed(4);
989
1986
  const isTestnet = chainName === "bnb_testnet";
990
1987
  if (isTestnet) {
991
1988
  throw new Error(
@@ -1019,35 +2016,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1019
2016
  );
1020
2017
  }
1021
2018
  const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1022
- const intent = {
2019
+ const intentNonce = Date.now();
2020
+ const intentDeadline = Date.now() + 36e5;
2021
+ const envelope = buildBnbIntentTypedData({
1023
2022
  from: this.wallet.address,
1024
2023
  to,
1025
2024
  amount: amountWei,
1026
- token: tokenConfig.address,
2025
+ tokenAddress: tokenConfig.address,
1027
2026
  service,
1028
- nonce: Date.now(),
1029
- // Use timestamp as nonce for simplicity
1030
- deadline: Date.now() + 36e5
1031
- // 1 hour
1032
- };
1033
- const domain = {
1034
- name: "MoltsPay",
1035
- version: "1",
2027
+ nonce: intentNonce,
2028
+ deadline: intentDeadline,
1036
2029
  chainId: chain.chainId
1037
- };
1038
- const types = {
1039
- PaymentIntent: [
1040
- { name: "from", type: "address" },
1041
- { name: "to", type: "address" },
1042
- { name: "amount", type: "uint256" },
1043
- { name: "token", type: "address" },
1044
- { name: "service", type: "string" },
1045
- { name: "nonce", type: "uint256" },
1046
- { name: "deadline", type: "uint256" }
1047
- ]
1048
- };
2030
+ });
1049
2031
  console.log(`[MoltsPay] Signing BNB payment intent...`);
1050
- const signature = await this.wallet.signTypedData(domain, types, intent);
2032
+ const signature = await this.signer.signTypedData(envelope);
2033
+ const intent = envelope.message;
1051
2034
  const network = `eip155:${chain.chainId}`;
1052
2035
  const payload = {
1053
2036
  x402Version: 2,
@@ -1108,11 +2091,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1108
2091
  throw new Error("Missing payTo address in payment requirements");
1109
2092
  }
1110
2093
  const solanaFeePayer = requirements.extra?.solanaFeePayer;
1111
- const feePayerPubkey = solanaFeePayer ? new import_web34.PublicKey(solanaFeePayer) : void 0;
2094
+ const feePayerPubkey = solanaFeePayer ? new import_web35.PublicKey(solanaFeePayer) : void 0;
1112
2095
  if (feePayerPubkey) {
1113
2096
  console.log(`[MoltsPay] Gasless mode: server pays fees`);
1114
2097
  }
1115
- const recipientPubkey = new import_web34.PublicKey(requirements.payTo);
2098
+ const recipientPubkey = new import_web35.PublicKey(requirements.payTo);
1116
2099
  const transaction = await createSolanaPaymentTransaction(
1117
2100
  solanaWallet.publicKey,
1118
2101
  recipientPubkey,
@@ -1121,12 +2104,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1121
2104
  feePayerPubkey
1122
2105
  // Optional fee payer for gasless mode
1123
2106
  );
1124
- if (feePayerPubkey) {
1125
- transaction.partialSign(solanaWallet);
1126
- } else {
1127
- transaction.sign(solanaWallet);
1128
- }
1129
- const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
2107
+ const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
2108
+ const signedTx = await this.signer.signSolanaTransaction({
2109
+ transactionBase64: unsignedBase64,
2110
+ partialSign: !!feePayerPubkey
2111
+ });
1130
2112
  console.log(`[MoltsPay] Transaction signed, sending to server...`);
1131
2113
  const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1132
2114
  const payload = {
@@ -1173,7 +2155,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1173
2155
  * Check ERC20 allowance for a spender
1174
2156
  */
1175
2157
  async checkAllowance(tokenAddress, spender, provider) {
1176
- const contract = new import_ethers.ethers.Contract(
2158
+ const contract = new import_ethers2.ethers.Contract(
1177
2159
  tokenAddress,
1178
2160
  ["function allowance(address owner, address spender) view returns (uint256)"],
1179
2161
  provider
@@ -1184,41 +2166,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1184
2166
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
1185
2167
  * This only signs - no on-chain transaction, no gas needed.
1186
2168
  * Supports both USDC and USDT.
2169
+ *
2170
+ * Delegates typed-data construction to `core/eip3009.ts` and the signature
2171
+ * itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
2172
+ * flow with an EIP-1193 signer without duplicating typed-data layout.
1187
2173
  */
1188
2174
  async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
1189
- const validAfter = 0;
1190
- const validBefore = Math.floor(Date.now() / 1e3) + 3600;
1191
- const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
1192
2175
  const tokenConfig = chain.tokens[token];
1193
2176
  const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1194
- const authorization = {
2177
+ const nonce = import_ethers2.ethers.hexlify(import_ethers2.ethers.randomBytes(32));
2178
+ const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
2179
+ const tokenVersion = domainOverride?.version || "2";
2180
+ const envelope = buildEIP3009TypedData({
1195
2181
  from: this.wallet.address,
1196
2182
  to,
1197
2183
  value,
1198
- validAfter: validAfter.toString(),
1199
- validBefore: validBefore.toString(),
1200
- nonce
1201
- };
1202
- const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1203
- const tokenVersion = domainOverride?.version || "2";
1204
- const domain = {
1205
- name: tokenName,
1206
- version: tokenVersion,
2184
+ nonce,
1207
2185
  chainId: chain.chainId,
1208
- verifyingContract: tokenConfig.address
1209
- };
1210
- const types = {
1211
- TransferWithAuthorization: [
1212
- { name: "from", type: "address" },
1213
- { name: "to", type: "address" },
1214
- { name: "value", type: "uint256" },
1215
- { name: "validAfter", type: "uint256" },
1216
- { name: "validBefore", type: "uint256" },
1217
- { name: "nonce", type: "bytes32" }
1218
- ]
1219
- };
1220
- const signature = await this.wallet.signTypedData(domain, types, authorization);
1221
- return { authorization, signature };
2186
+ tokenAddress: tokenConfig.address,
2187
+ tokenName,
2188
+ tokenVersion
2189
+ });
2190
+ const signature = await this.signer.signTypedData(envelope);
2191
+ return { authorization: envelope.message, signature };
1222
2192
  }
1223
2193
  /**
1224
2194
  * Check spending limits
@@ -1250,7 +2220,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1250
2220
  }
1251
2221
  // --- Config & Wallet Management ---
1252
2222
  loadConfig() {
1253
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
2223
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
1254
2224
  if ((0, import_fs2.existsSync)(configPath)) {
1255
2225
  const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
1256
2226
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
@@ -1259,14 +2229,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1259
2229
  }
1260
2230
  saveConfig() {
1261
2231
  (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1262
- const configPath = (0, import_path2.join)(this.configDir, "config.json");
2232
+ const configPath = (0, import_path3.join)(this.configDir, "config.json");
1263
2233
  (0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
1264
2234
  }
1265
2235
  /**
1266
2236
  * Load spending data from disk
1267
2237
  */
1268
2238
  loadSpending() {
1269
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2239
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
1270
2240
  if ((0, import_fs2.existsSync)(spendingPath)) {
1271
2241
  try {
1272
2242
  const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
@@ -1289,7 +2259,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1289
2259
  */
1290
2260
  saveSpending() {
1291
2261
  (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1292
- const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
2262
+ const spendingPath = (0, import_path3.join)(this.configDir, "spending.json");
1293
2263
  const data = {
1294
2264
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1295
2265
  amount: this.todaySpending,
@@ -1298,7 +2268,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1298
2268
  (0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
1299
2269
  }
1300
2270
  loadWallet() {
1301
- const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
2271
+ const walletPath = (0, import_path3.join)(this.configDir, "wallet.json");
1302
2272
  if ((0, import_fs2.existsSync)(walletPath)) {
1303
2273
  if (process.platform !== "win32") {
1304
2274
  try {
@@ -1322,13 +2292,13 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1322
2292
  */
1323
2293
  static init(configDir, options) {
1324
2294
  (0, import_fs2.mkdirSync)(configDir, { recursive: true });
1325
- const wallet = import_ethers.Wallet.createRandom();
2295
+ const wallet = import_ethers2.Wallet.createRandom();
1326
2296
  const walletData = {
1327
2297
  address: wallet.address,
1328
2298
  privateKey: wallet.privateKey,
1329
2299
  createdAt: Date.now()
1330
2300
  };
1331
- const walletPath = (0, import_path2.join)(configDir, "wallet.json");
2301
+ const walletPath = (0, import_path3.join)(configDir, "wallet.json");
1332
2302
  (0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1333
2303
  const config = {
1334
2304
  chain: options.chain,
@@ -1337,7 +2307,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1337
2307
  maxPerDay: options.maxPerDay
1338
2308
  }
1339
2309
  };
1340
- const configPath = (0, import_path2.join)(configDir, "config.json");
2310
+ const configPath = (0, import_path3.join)(configDir, "config.json");
1341
2311
  (0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
1342
2312
  return { address: wallet.address, configDir };
1343
2313
  }
@@ -1354,17 +2324,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1354
2324
  } catch {
1355
2325
  throw new Error(`Unknown chain: ${this.config.chain}`);
1356
2326
  }
1357
- const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
2327
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
1358
2328
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
1359
2329
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1360
2330
  provider.getBalance(this.wallet.address),
1361
- new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1362
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2331
+ new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2332
+ new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1363
2333
  ]);
1364
2334
  return {
1365
- usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1366
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1367
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
2335
+ usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2336
+ usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2337
+ native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
1368
2338
  };
1369
2339
  }
1370
2340
  /**
@@ -1387,38 +2357,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1387
2357
  supportedChains.map(async (chainName) => {
1388
2358
  try {
1389
2359
  const chain = getChain(chainName);
1390
- const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
2360
+ const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
1391
2361
  if (chainName === "tempo_moderato") {
1392
2362
  const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
1393
2363
  provider.getBalance(this.wallet.address),
1394
- new import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1395
- new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1396
- new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1397
- new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2364
+ new import_ethers2.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2365
+ new import_ethers2.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2366
+ new import_ethers2.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2367
+ new import_ethers2.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
1398
2368
  ]);
1399
2369
  results[chainName] = {
1400
- usdc: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
2370
+ usdc: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
1401
2371
  // pathUSD as default USDC
1402
- usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
2372
+ usdt: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
1403
2373
  // alphaUSD as default USDT
1404
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
2374
+ native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance)),
1405
2375
  tempo: {
1406
- pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
1407
- alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
1408
- betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
1409
- thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
2376
+ pathUSD: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
2377
+ alphaUSD: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
2378
+ betaUSD: parseFloat(import_ethers2.ethers.formatUnits(betaUSD, 6)),
2379
+ thetaUSD: parseFloat(import_ethers2.ethers.formatUnits(thetaUSD, 6))
1410
2380
  }
1411
2381
  };
1412
2382
  } else {
1413
2383
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1414
2384
  provider.getBalance(this.wallet.address),
1415
- new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1416
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2385
+ new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2386
+ new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1417
2387
  ]);
1418
2388
  results[chainName] = {
1419
- usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1420
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1421
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
2389
+ usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2390
+ usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2391
+ native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
1422
2392
  };
1423
2393
  }
1424
2394
  } catch (err) {
@@ -1776,16 +2746,40 @@ var CDPFacilitator = class extends BaseFacilitator {
1776
2746
 
1777
2747
  // src/facilitators/tempo.ts
1778
2748
  init_cjs_shims();
2749
+ var import_ethers3 = require("ethers");
1779
2750
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
2751
+ var TIP20_PERMIT_ABI = [
2752
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
2753
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
2754
+ ];
1780
2755
  var TempoFacilitator = class extends BaseFacilitator {
1781
2756
  name = "tempo";
1782
2757
  displayName = "Tempo Testnet";
1783
2758
  supportedNetworks = ["eip155:42431"];
1784
2759
  // Tempo Moderato
1785
2760
  rpcUrl;
2761
+ settlerWallet = null;
1786
2762
  constructor() {
1787
2763
  super();
1788
2764
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
2765
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
2766
+ if (settlerKey) {
2767
+ try {
2768
+ const provider = new import_ethers3.ethers.JsonRpcProvider(this.rpcUrl);
2769
+ this.settlerWallet = new import_ethers3.ethers.Wallet(settlerKey, provider);
2770
+ } catch (err) {
2771
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
2772
+ this.settlerWallet = null;
2773
+ }
2774
+ }
2775
+ }
2776
+ /**
2777
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
2778
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
2779
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
2780
+ */
2781
+ getSpenderAddress() {
2782
+ return this.settlerWallet?.address ?? null;
1789
2783
  }
1790
2784
  async healthCheck() {
1791
2785
  const start = Date.now();
@@ -1810,7 +2804,45 @@ var TempoFacilitator = class extends BaseFacilitator {
1810
2804
  return { healthy: false, error: String(error) };
1811
2805
  }
1812
2806
  }
1813
- async verify(paymentPayload, requirements) {
2807
+ async verify(paymentPayload, requirements) {
2808
+ const inner = paymentPayload.payload;
2809
+ if (inner && "permit" in inner && inner.permit) {
2810
+ return this.verifyPermit(inner, requirements);
2811
+ }
2812
+ return this.verifyTxHash(paymentPayload, requirements);
2813
+ }
2814
+ /**
2815
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
2816
+ * anything on-chain — actual submission happens in settlePermit().
2817
+ */
2818
+ async verifyPermit(payload, requirements) {
2819
+ if (!this.settlerWallet) {
2820
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
2821
+ }
2822
+ const p = payload.permit;
2823
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
2824
+ return { valid: false, error: "Invalid permit payload: missing fields" };
2825
+ }
2826
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
2827
+ return {
2828
+ valid: false,
2829
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
2830
+ };
2831
+ }
2832
+ const deadline = BigInt(p.deadline);
2833
+ const now = BigInt(Math.floor(Date.now() / 1e3));
2834
+ if (deadline <= now) {
2835
+ return { valid: false, error: "Permit deadline has expired" };
2836
+ }
2837
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
2838
+ return {
2839
+ valid: false,
2840
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
2841
+ };
2842
+ }
2843
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
2844
+ }
2845
+ async verifyTxHash(paymentPayload, requirements) {
1814
2846
  try {
1815
2847
  const tempoPayload = paymentPayload.payload;
1816
2848
  if (!tempoPayload?.txHash) {
@@ -1868,7 +2900,11 @@ var TempoFacilitator = class extends BaseFacilitator {
1868
2900
  }
1869
2901
  }
1870
2902
  async settle(paymentPayload, requirements) {
1871
- const verifyResult = await this.verify(paymentPayload, requirements);
2903
+ const inner = paymentPayload.payload;
2904
+ if (inner && "permit" in inner && inner.permit) {
2905
+ return this.settlePermit(inner, requirements);
2906
+ }
2907
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
1872
2908
  if (!verifyResult.valid) {
1873
2909
  return { success: false, error: verifyResult.error };
1874
2910
  }
@@ -1879,6 +2915,52 @@ var TempoFacilitator = class extends BaseFacilitator {
1879
2915
  status: "settled"
1880
2916
  };
1881
2917
  }
2918
+ /**
2919
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
2920
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
2921
+ * 2. pathUSD.transferFrom(owner, payTo, value)
2922
+ *
2923
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
2924
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
2925
+ */
2926
+ async settlePermit(payload, requirements) {
2927
+ if (!this.settlerWallet) {
2928
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
2929
+ }
2930
+ if (!requirements.asset || !requirements.payTo) {
2931
+ return { success: false, error: "Missing asset or payTo in requirements" };
2932
+ }
2933
+ const verifyResult = await this.verifyPermit(payload, requirements);
2934
+ if (!verifyResult.valid) {
2935
+ return { success: false, error: verifyResult.error };
2936
+ }
2937
+ const token = new import_ethers3.ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
2938
+ const p = payload.permit;
2939
+ try {
2940
+ const permitTx = await token.permit(
2941
+ p.owner,
2942
+ p.spender,
2943
+ p.value,
2944
+ p.deadline,
2945
+ p.v,
2946
+ p.r,
2947
+ p.s
2948
+ );
2949
+ await permitTx.wait();
2950
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
2951
+ await transferTx.wait();
2952
+ return {
2953
+ success: true,
2954
+ transaction: transferTx.hash,
2955
+ status: "settled"
2956
+ };
2957
+ } catch (err) {
2958
+ return {
2959
+ success: false,
2960
+ error: `Tempo permit settlement failed: ${err.message}`
2961
+ };
2962
+ }
2963
+ }
1882
2964
  async getTransactionReceipt(txHash) {
1883
2965
  const response = await fetch(this.rpcUrl, {
1884
2966
  method: "POST",
@@ -2122,12 +3204,12 @@ var BNBFacilitator = class extends BaseFacilitator {
2122
3204
  return this.spenderAddress;
2123
3205
  }
2124
3206
  async getServerAddress() {
2125
- const { ethers: ethers3 } = await import("ethers");
2126
- const wallet = new ethers3.Wallet(this.serverPrivateKey);
3207
+ const { ethers: ethers5 } = await import("ethers");
3208
+ const wallet = new ethers5.Wallet(this.serverPrivateKey);
2127
3209
  return wallet.address;
2128
3210
  }
2129
3211
  async recoverIntentSigner(intent, chainId) {
2130
- const { ethers: ethers3 } = await import("ethers");
3212
+ const { ethers: ethers5 } = await import("ethers");
2131
3213
  const domain = {
2132
3214
  ...EIP712_DOMAIN,
2133
3215
  chainId
@@ -2141,7 +3223,7 @@ var BNBFacilitator = class extends BaseFacilitator {
2141
3223
  nonce: intent.nonce,
2142
3224
  deadline: intent.deadline
2143
3225
  };
2144
- const recoveredAddress = ethers3.verifyTypedData(
3226
+ const recoveredAddress = ethers5.verifyTypedData(
2145
3227
  domain,
2146
3228
  INTENT_TYPES,
2147
3229
  message,
@@ -2185,10 +3267,10 @@ var BNBFacilitator = class extends BaseFacilitator {
2185
3267
  return result.result || "0x0";
2186
3268
  }
2187
3269
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
2188
- const { ethers: ethers3 } = await import("ethers");
2189
- const provider = new ethers3.JsonRpcProvider(rpcUrl);
2190
- const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2191
- const tokenContract = new ethers3.Contract(token, [
3270
+ const { ethers: ethers5 } = await import("ethers");
3271
+ const provider = new ethers5.JsonRpcProvider(rpcUrl);
3272
+ const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
3273
+ const tokenContract = new ethers5.Contract(token, [
2192
3274
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2193
3275
  ], wallet);
2194
3276
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -2211,9 +3293,381 @@ var BNBFacilitator = class extends BaseFacilitator {
2211
3293
  }
2212
3294
  };
2213
3295
 
3296
+ // src/facilitators/alipay.ts
3297
+ init_cjs_shims();
3298
+ var import_node_crypto3 = __toESM(require("crypto"));
3299
+
3300
+ // src/facilitators/alipay/rsa2.ts
3301
+ init_cjs_shims();
3302
+ var import_node_crypto2 = __toESM(require("crypto"));
3303
+ function rsa2Sign(data, privateKeyPem) {
3304
+ const signer = import_node_crypto2.default.createSign("RSA-SHA256");
3305
+ signer.update(data, "utf-8");
3306
+ signer.end();
3307
+ return signer.sign(privateKeyPem, "base64");
3308
+ }
3309
+
3310
+ // src/facilitators/alipay/encoding.ts
3311
+ init_cjs_shims();
3312
+ function base64url(input) {
3313
+ return Buffer.from(input, "utf-8").toString("base64url");
3314
+ }
3315
+ function decodeBase64UrlWithPadFix(input) {
3316
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
3317
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3318
+ return Buffer.from(padded, "base64").toString("utf-8");
3319
+ }
3320
+ function toPem(key, kind) {
3321
+ const trimmed = key.trim();
3322
+ if (trimmed.includes("-----BEGIN")) return trimmed;
3323
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
3324
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
3325
+ return `-----BEGIN ${label}-----
3326
+ ${body}
3327
+ -----END ${label}-----
3328
+ `;
3329
+ }
3330
+
3331
+ // src/facilitators/alipay/openapi.ts
3332
+ init_cjs_shims();
3333
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
3334
+ const pad = (n) => String(n).padStart(2, "0");
3335
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
3336
+ }
3337
+ function buildSigningString(params) {
3338
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
3339
+ }
3340
+ function responseWrapperKey(method) {
3341
+ return `${method.replace(/\./g, "_")}_response`;
3342
+ }
3343
+ async function alipayOpenApiCall(method, bizContent, config) {
3344
+ const publicParams = {
3345
+ app_id: config.app_id,
3346
+ method,
3347
+ format: "JSON",
3348
+ charset: "utf-8",
3349
+ sign_type: config.sign_type ?? "RSA2",
3350
+ timestamp: formatAlipayTimestamp(),
3351
+ version: "1.0",
3352
+ biz_content: JSON.stringify(bizContent)
3353
+ };
3354
+ const signingString = buildSigningString(publicParams);
3355
+ const sign = rsa2Sign(signingString, config.private_key_pem);
3356
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
3357
+ const response = await fetch(config.gateway_url, {
3358
+ method: "POST",
3359
+ headers: {
3360
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
3361
+ },
3362
+ body
3363
+ });
3364
+ if (!response.ok) {
3365
+ const text = await response.text().catch(() => "<unreadable>");
3366
+ throw new Error(
3367
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
3368
+ );
3369
+ }
3370
+ const json = await response.json();
3371
+ const wrapperKey = responseWrapperKey(method);
3372
+ const business = json[wrapperKey];
3373
+ if (business === void 0 || business === null || typeof business !== "object") {
3374
+ throw new Error(
3375
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
3376
+ );
3377
+ }
3378
+ return business;
3379
+ }
3380
+
3381
+ // src/facilitators/alipay.ts
3382
+ var ALIPAY_NETWORK = "alipay";
3383
+ var ALIPAY_SCHEME = "alipay-aipay";
3384
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
3385
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
3386
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
3387
+ var ALIPAY_SIGNING_FIELDS = [
3388
+ "amount",
3389
+ "currency",
3390
+ "goods_name",
3391
+ "out_trade_no",
3392
+ "pay_before",
3393
+ "resource_id",
3394
+ "seller_id",
3395
+ "service_id"
3396
+ ];
3397
+ var AlipayFacilitator = class extends BaseFacilitator {
3398
+ name = "alipay";
3399
+ displayName = "Alipay AI \u6536";
3400
+ supportedNetworks = [ALIPAY_NETWORK];
3401
+ config;
3402
+ constructor(config) {
3403
+ super();
3404
+ this.config = {
3405
+ gateway_url: ALIPAY_GATEWAY_PROD,
3406
+ sign_type: "RSA2",
3407
+ ...config
3408
+ };
3409
+ }
3410
+ /**
3411
+ * Build the 402 challenge for a service: signs the 8-field payload with
3412
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
3413
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
3414
+ */
3415
+ async createPaymentRequirements(opts) {
3416
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
3417
+ throw new Error(
3418
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
3419
+ );
3420
+ }
3421
+ const now = /* @__PURE__ */ new Date();
3422
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
3423
+ const payBefore = formatPayBefore(now);
3424
+ const signedFields = {
3425
+ amount: opts.priceCny,
3426
+ currency: "CNY",
3427
+ goods_name: opts.goodsName,
3428
+ out_trade_no: outTradeNo,
3429
+ pay_before: payBefore,
3430
+ resource_id: opts.resourceId,
3431
+ seller_id: this.config.seller_id,
3432
+ service_id: opts.serviceId
3433
+ };
3434
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
3435
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
3436
+ const challenge = {
3437
+ protocol: {
3438
+ out_trade_no: outTradeNo,
3439
+ amount: signedFields.amount,
3440
+ currency: signedFields.currency,
3441
+ resource_id: signedFields.resource_id,
3442
+ pay_before: payBefore,
3443
+ seller_signature,
3444
+ seller_sign_type: this.config.sign_type ?? "RSA2",
3445
+ seller_unique_id: this.config.seller_id
3446
+ },
3447
+ method: {
3448
+ seller_name: this.config.seller_name,
3449
+ seller_id: this.config.seller_id,
3450
+ seller_app_id: this.config.app_id,
3451
+ goods_name: signedFields.goods_name,
3452
+ seller_unique_id_key: "seller_id",
3453
+ service_id: signedFields.service_id
3454
+ }
3455
+ };
3456
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
3457
+ const x402Accepts = {
3458
+ scheme: ALIPAY_SCHEME,
3459
+ network: ALIPAY_NETWORK,
3460
+ asset: "CNY",
3461
+ amount: opts.priceCny,
3462
+ payTo: this.config.seller_id,
3463
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
3464
+ extra: {
3465
+ payment_needed_header: paymentNeededHeader,
3466
+ out_trade_no: outTradeNo,
3467
+ pay_before: payBefore,
3468
+ service_id: signedFields.service_id
3469
+ }
3470
+ };
3471
+ return { x402Accepts, paymentNeededHeader };
3472
+ }
3473
+ /**
3474
+ * Verify a `Payment-Proof` by calling
3475
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
3476
+ *
3477
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
3478
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
3479
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
3480
+ *
3481
+ * All failure modes (malformed payload, network errors, Alipay
3482
+ * `code != 10000`) return `{ valid: false, error }`. No exception
3483
+ * escapes, regardless of client-supplied input.
3484
+ */
3485
+ async verify(paymentPayload, _requirements) {
3486
+ try {
3487
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3488
+ const decoded = decodeProof(proofHeader);
3489
+ const response = await alipayOpenApiCall(
3490
+ "alipay.aipay.agent.payment.verify",
3491
+ {
3492
+ payment_proof: decoded.protocol.payment_proof,
3493
+ trade_no: decoded.protocol.trade_no,
3494
+ client_session: decoded.method.client_session
3495
+ },
3496
+ this.getOpenApiConfig()
3497
+ );
3498
+ if (response.code !== "10000") {
3499
+ return {
3500
+ valid: false,
3501
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3502
+ details: {
3503
+ code: response.code,
3504
+ sub_code: response.sub_code,
3505
+ sub_msg: response.sub_msg
3506
+ }
3507
+ };
3508
+ }
3509
+ return {
3510
+ valid: true,
3511
+ details: {
3512
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
3513
+ amount: response.amount,
3514
+ out_trade_no: response.out_trade_no,
3515
+ resource_id: response.resource_id,
3516
+ active: response.active
3517
+ }
3518
+ };
3519
+ } catch (e) {
3520
+ return {
3521
+ valid: false,
3522
+ error: e instanceof Error ? e.message : String(e)
3523
+ };
3524
+ }
3525
+ }
3526
+ /**
3527
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
3528
+ * service resource has been returned to the buyer.
3529
+ *
3530
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
3531
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
3532
+ * server) is expected to log fulfillment failures but NOT roll back
3533
+ * the already-delivered resource.
3534
+ *
3535
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
3536
+ * does the same; the redundant Base64URL decode is negligible).
3537
+ */
3538
+ async settle(paymentPayload, _requirements) {
3539
+ try {
3540
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3541
+ const decoded = decodeProof(proofHeader);
3542
+ const tradeNo = decoded.protocol.trade_no;
3543
+ const response = await alipayOpenApiCall(
3544
+ "alipay.aipay.agent.fulfillment.confirm",
3545
+ { trade_no: tradeNo },
3546
+ this.getOpenApiConfig()
3547
+ );
3548
+ if (response.code !== "10000") {
3549
+ return {
3550
+ success: false,
3551
+ transaction: tradeNo,
3552
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3553
+ status: "fulfillment_failed"
3554
+ };
3555
+ }
3556
+ return {
3557
+ success: true,
3558
+ transaction: tradeNo,
3559
+ status: "fulfilled"
3560
+ };
3561
+ } catch (e) {
3562
+ return {
3563
+ success: false,
3564
+ error: e instanceof Error ? e.message : String(e)
3565
+ };
3566
+ }
3567
+ }
3568
+ /**
3569
+ * Validate that the configured RSA keys parse and that the Open API
3570
+ * gateway is reachable. Does NOT make a real business API call (would
3571
+ * burn quota and could appear as a legitimate verify attempt in logs).
3572
+ */
3573
+ async healthCheck() {
3574
+ const start = Date.now();
3575
+ try {
3576
+ import_node_crypto3.default.createPrivateKey(this.config.private_key_pem);
3577
+ } catch (e) {
3578
+ return {
3579
+ healthy: false,
3580
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3581
+ };
3582
+ }
3583
+ try {
3584
+ import_node_crypto3.default.createPublicKey(this.config.alipay_public_key_pem);
3585
+ } catch (e) {
3586
+ return {
3587
+ healthy: false,
3588
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3589
+ };
3590
+ }
3591
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
3592
+ const controller = new AbortController();
3593
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3594
+ const response = await fetch(gatewayUrl, {
3595
+ method: "HEAD",
3596
+ signal: controller.signal
3597
+ }).catch(() => null);
3598
+ clearTimeout(timeout);
3599
+ const latencyMs = Date.now() - start;
3600
+ if (!response) {
3601
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
3602
+ }
3603
+ return { healthy: true, latencyMs };
3604
+ }
3605
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
3606
+ getOpenApiConfig() {
3607
+ return {
3608
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
3609
+ app_id: this.config.app_id,
3610
+ private_key_pem: this.config.private_key_pem,
3611
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
3612
+ sign_type: this.config.sign_type
3613
+ };
3614
+ }
3615
+ };
3616
+ function generateOutTradeNo() {
3617
+ return "VID" + import_node_crypto3.default.randomBytes(22).toString("base64url").slice(0, 29);
3618
+ }
3619
+ function formatPayBefore(now) {
3620
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
3621
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
3622
+ }
3623
+ function extractProofHeader(payload) {
3624
+ if (typeof payload === "string") {
3625
+ if (payload.length === 0) {
3626
+ throw new Error("alipay Payment-Proof is empty");
3627
+ }
3628
+ return payload;
3629
+ }
3630
+ if (payload !== null && typeof payload === "object") {
3631
+ const obj = payload;
3632
+ const candidate = obj.paymentProof ?? obj.proofHeader;
3633
+ if (typeof candidate === "string" && candidate.length > 0) {
3634
+ return candidate;
3635
+ }
3636
+ }
3637
+ throw new Error(
3638
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
3639
+ );
3640
+ }
3641
+ function decodeProof(proofHeader) {
3642
+ let parsed;
3643
+ try {
3644
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
3645
+ } catch (e) {
3646
+ throw new Error(
3647
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
3648
+ );
3649
+ }
3650
+ if (parsed === null || typeof parsed !== "object") {
3651
+ throw new Error("decoded Payment-Proof is not an object");
3652
+ }
3653
+ const obj = parsed;
3654
+ const protocol = obj.protocol;
3655
+ const method = obj.method;
3656
+ if (!protocol || typeof protocol.payment_proof !== "string") {
3657
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
3658
+ }
3659
+ if (typeof protocol.trade_no !== "string") {
3660
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
3661
+ }
3662
+ if (!method || typeof method.client_session !== "string") {
3663
+ throw new Error("decoded Payment-Proof missing method.client_session");
3664
+ }
3665
+ return parsed;
3666
+ }
3667
+
2214
3668
  // src/facilitators/registry.ts
2215
3669
  init_cjs_shims();
2216
- var import_web35 = require("@solana/web3.js");
3670
+ var import_web36 = require("@solana/web3.js");
2217
3671
  var import_bs582 = __toESM(require("bs58"));
2218
3672
  var FacilitatorRegistry = class {
2219
3673
  factories = /* @__PURE__ */ new Map();
@@ -2229,13 +3683,14 @@ var FacilitatorRegistry = class {
2229
3683
  const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2230
3684
  if (feePayerKey) {
2231
3685
  try {
2232
- feePayerKeypair = import_web35.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
3686
+ feePayerKeypair = import_web36.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
2233
3687
  } catch (e) {
2234
3688
  console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2235
3689
  }
2236
3690
  }
2237
3691
  return new SolanaFacilitator({ feePayerKeypair });
2238
3692
  });
3693
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
2239
3694
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
2240
3695
  }
2241
3696
  /**
@@ -2463,6 +3918,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
2463
3918
  var MPP_AUTH_HEADER = "authorization";
2464
3919
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
2465
3920
  var MPP_RECEIPT_HEADER = "payment-receipt";
3921
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
3922
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
3923
+ function headerSafe(value) {
3924
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
3925
+ }
2466
3926
  var TOKEN_ADDRESSES = {
2467
3927
  "eip155:8453": {
2468
3928
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2504,7 +3964,7 @@ var TOKEN_ADDRESSES = {
2504
3964
  // Devnet USDC
2505
3965
  }
2506
3966
  };
2507
- var CHAIN_TO_NETWORK = {
3967
+ var CHAIN_TO_NETWORK2 = {
2508
3968
  "base": "eip155:8453",
2509
3969
  "base_sepolia": "eip155:84532",
2510
3970
  "polygon": "eip155:137",
@@ -2535,9 +3995,13 @@ var TOKEN_DOMAINS = {
2535
3995
  USDT: { name: "(PoS) Tether USD", version: "2" }
2536
3996
  },
2537
3997
  // Tempo Moderato testnet - TIP-20 stablecoins
3998
+ // Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
3999
+ // See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
4000
+ // All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
4001
+ // the token symbol with first letter capitalized + version "1".
2538
4002
  "eip155:42431": {
2539
- USDC: { name: "pathUSD", version: "1" },
2540
- USDT: { name: "alphaUSD", version: "1" }
4003
+ USDC: { name: "PathUSD", version: "1" },
4004
+ USDT: { name: "AlphaUSD", version: "1" }
2541
4005
  },
2542
4006
  // BNB Smart Chain mainnet
2543
4007
  "eip155:56": {
@@ -2594,6 +4058,8 @@ var MoltsPayServer = class {
2594
4058
  registry;
2595
4059
  networkId;
2596
4060
  useMainnet;
4061
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
4062
+ alipayFacilitator = null;
2597
4063
  constructor(servicesPath, options = {}) {
2598
4064
  loadEnvFile2();
2599
4065
  const content = (0, import_fs4.readFileSync)(servicesPath, "utf-8");
@@ -2615,7 +4081,38 @@ var MoltsPayServer = class {
2615
4081
  cdp: { useMainnet: this.useMainnet }
2616
4082
  }
2617
4083
  };
4084
+ const providerAlipay = this.manifest.provider.alipay;
4085
+ if (providerAlipay) {
4086
+ try {
4087
+ const baseDir = path2.dirname(servicesPath);
4088
+ const resolvePem = (p, kind) => toPem((0, import_fs4.readFileSync)(path2.isAbsolute(p) ? p : path2.resolve(baseDir, p), "utf-8"), kind);
4089
+ const alipayFacilitatorConfig = {
4090
+ seller_id: providerAlipay.seller_id,
4091
+ app_id: providerAlipay.app_id,
4092
+ seller_name: providerAlipay.seller_name,
4093
+ service_id_default: providerAlipay.service_id_default,
4094
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
4095
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
4096
+ gateway_url: providerAlipay.gateway_url,
4097
+ sign_type: providerAlipay.sign_type
4098
+ };
4099
+ facilitatorConfig.config = {
4100
+ ...facilitatorConfig.config,
4101
+ alipay: alipayFacilitatorConfig
4102
+ };
4103
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
4104
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
4105
+ facilitatorConfig.fallback.push("alipay");
4106
+ }
4107
+ } catch (err) {
4108
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
4109
+ }
4110
+ }
2618
4111
  this.registry = new FacilitatorRegistry(facilitatorConfig);
4112
+ if (providerAlipay) {
4113
+ this.alipayFacilitator = this.registry.get("alipay");
4114
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
4115
+ }
2619
4116
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
2620
4117
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
2621
4118
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -2660,14 +4157,14 @@ var MoltsPayServer = class {
2660
4157
  const chainName = typeof c === "string" ? c : c.chain;
2661
4158
  const explicitWallet = typeof c === "object" ? c.wallet : null;
2662
4159
  return {
2663
- network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
4160
+ network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
2664
4161
  wallet: getWalletForChain(chainName, explicitWallet || void 0),
2665
4162
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
2666
4163
  };
2667
4164
  });
2668
4165
  }
2669
4166
  const chain = provider.chain || "base";
2670
- const network = CHAIN_TO_NETWORK[chain] || this.networkId;
4167
+ const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
2671
4168
  return [{
2672
4169
  network,
2673
4170
  wallet: getWalletForChain(chain),
@@ -2705,14 +4202,63 @@ var MoltsPayServer = class {
2705
4202
  console.log(` GET /health - Health check (incl. facilitators)`);
2706
4203
  });
2707
4204
  }
4205
+ /**
4206
+ * Apply CORS response headers according to the `cors` option.
4207
+ *
4208
+ * Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
4209
+ * and works for every browser client whose origin does not need to send cookies.
4210
+ *
4211
+ * `cors: false`: emit no CORS headers. Same-origin only.
4212
+ * `cors: string[]`: origin allowlist — echo the origin back iff it matches.
4213
+ * `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
4214
+ *
4215
+ * The required-for-Web response headers are always exposed when CORS is active:
4216
+ * `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
4217
+ */
4218
+ applyCorsHeaders(req, res) {
4219
+ const cors = this.options.cors;
4220
+ if (cors === false) {
4221
+ return;
4222
+ }
4223
+ const requestOrigin = req.headers.origin ?? "*";
4224
+ if (cors === void 0 || cors === true) {
4225
+ this.writeCorsHeaders(res, "*");
4226
+ return;
4227
+ }
4228
+ if (Array.isArray(cors)) {
4229
+ if (cors.includes(requestOrigin)) {
4230
+ this.writeCorsHeaders(res, requestOrigin);
4231
+ res.setHeader("Vary", "Origin");
4232
+ }
4233
+ return;
4234
+ }
4235
+ const opt = cors;
4236
+ const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
4237
+ if (!isAllowed) {
4238
+ return;
4239
+ }
4240
+ this.writeCorsHeaders(res, requestOrigin);
4241
+ res.setHeader("Vary", "Origin");
4242
+ if (opt.credentials) {
4243
+ res.setHeader("Access-Control-Allow-Credentials", "true");
4244
+ }
4245
+ const maxAge = opt.maxAge ?? 600;
4246
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
4247
+ }
4248
+ writeCorsHeaders(res, origin) {
4249
+ res.setHeader("Access-Control-Allow-Origin", origin);
4250
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
4251
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
4252
+ res.setHeader(
4253
+ "Access-Control-Expose-Headers",
4254
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
4255
+ );
4256
+ }
2708
4257
  /**
2709
4258
  * Handle incoming request
2710
4259
  */
2711
4260
  async handleRequest(req, res) {
2712
- res.setHeader("Access-Control-Allow-Origin", "*");
2713
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2714
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2715
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
4261
+ this.applyCorsHeaders(req, res);
2716
4262
  if (req.method === "OPTIONS") {
2717
4263
  res.writeHead(204);
2718
4264
  res.end();
@@ -2732,7 +4278,8 @@ var MoltsPayServer = class {
2732
4278
  if (url.pathname === "/execute" && req.method === "POST") {
2733
4279
  const body = await this.readBody(req);
2734
4280
  const paymentHeader = req.headers[PAYMENT_HEADER2];
2735
- return await this.handleExecute(body, paymentHeader, res);
4281
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4282
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
2736
4283
  }
2737
4284
  if (url.pathname === "/proxy" && req.method === "POST") {
2738
4285
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -2750,7 +4297,8 @@ var MoltsPayServer = class {
2750
4297
  const body = req.method === "POST" ? await this.readBody(req) : {};
2751
4298
  const authHeader = req.headers[MPP_AUTH_HEADER];
2752
4299
  const x402Header = req.headers[PAYMENT_HEADER2];
2753
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
4300
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4301
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
2754
4302
  }
2755
4303
  this.sendJson(res, 404, { error: "Not found" });
2756
4304
  } catch (err) {
@@ -2847,7 +4395,7 @@ var MoltsPayServer = class {
2847
4395
  /**
2848
4396
  * POST /execute - Execute service with x402 payment
2849
4397
  */
2850
- async handleExecute(body, paymentHeader, res) {
4398
+ async handleExecute(body, paymentHeader, res, proofHeader) {
2851
4399
  const { service, params } = body;
2852
4400
  if (!service) {
2853
4401
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -2861,6 +4409,15 @@ var MoltsPayServer = class {
2861
4409
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
2862
4410
  }
2863
4411
  }
4412
+ if (proofHeader) {
4413
+ const alipayPayment = {
4414
+ x402Version: X402_VERSION3,
4415
+ scheme: ALIPAY_SCHEME,
4416
+ network: ALIPAY_NETWORK,
4417
+ payload: proofHeader
4418
+ };
4419
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
4420
+ }
2864
4421
  if (!paymentHeader) {
2865
4422
  return this.sendPaymentRequired(skill.config, res);
2866
4423
  }
@@ -2871,6 +4428,11 @@ var MoltsPayServer = class {
2871
4428
  } catch {
2872
4429
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2873
4430
  }
4431
+ const payScheme = payment.accepted?.scheme || payment.scheme;
4432
+ const payNetwork = payment.accepted?.network || payment.network;
4433
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
4434
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
4435
+ }
2874
4436
  const validation = this.validatePayment(payment, skill.config);
2875
4437
  if (!validation.valid) {
2876
4438
  return this.sendJson(res, 402, { error: validation.error });
@@ -2935,6 +4497,14 @@ var MoltsPayServer = class {
2935
4497
  console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2936
4498
  } catch (err) {
2937
4499
  console.error("[MoltsPay] Settlement failed:", err.message);
4500
+ settlement = { success: false, error: err.message, facilitator: "none" };
4501
+ }
4502
+ if (!settlement?.success) {
4503
+ return this.sendJson(res, 402, {
4504
+ error: "Payment settlement failed",
4505
+ message: settlement?.error || "Settlement returned no success state",
4506
+ facilitator: settlement?.facilitator
4507
+ });
2938
4508
  }
2939
4509
  }
2940
4510
  const responseHeaders = {};
@@ -2955,13 +4525,111 @@ var MoltsPayServer = class {
2955
4525
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
2956
4526
  }, responseHeaders);
2957
4527
  }
4528
+ /**
4529
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
4530
+ *
4531
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
4532
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
4533
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
4534
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
4535
+ * response (the buyer's payment proof was already verified).
4536
+ */
4537
+ async handleAlipayExecute(skill, params, payment, res) {
4538
+ if (!this.alipayFacilitator) {
4539
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
4540
+ }
4541
+ const requirements = {
4542
+ scheme: ALIPAY_SCHEME,
4543
+ network: ALIPAY_NETWORK,
4544
+ asset: "CNY",
4545
+ amount: skill.config.alipay?.price_cny || "0",
4546
+ payTo: this.manifest.provider.alipay?.seller_id || "",
4547
+ maxTimeoutSeconds: 1800
4548
+ };
4549
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
4550
+ const verifyResult = await this.registry.verify(payment, requirements);
4551
+ if (!verifyResult.valid) {
4552
+ return this.sendJson(res, 402, {
4553
+ error: `Payment verification failed: ${verifyResult.error}`,
4554
+ facilitator: verifyResult.facilitator
4555
+ });
4556
+ }
4557
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
4558
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
4559
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
4560
+ let result;
4561
+ try {
4562
+ result = await Promise.race([
4563
+ skill.handler(params),
4564
+ new Promise(
4565
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
4566
+ )
4567
+ ]);
4568
+ } catch (err) {
4569
+ console.error("[MoltsPay] Skill execution failed:", err.message);
4570
+ return this.sendJson(res, 500, {
4571
+ error: "Service execution failed",
4572
+ message: err.message
4573
+ });
4574
+ }
4575
+ let settlement;
4576
+ try {
4577
+ settlement = await this.registry.settle(payment, requirements);
4578
+ if (settlement.success) {
4579
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
4580
+ } else {
4581
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
4582
+ }
4583
+ } catch (err) {
4584
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
4585
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
4586
+ }
4587
+ const responseHeaders = {};
4588
+ if (settlement.success) {
4589
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
4590
+ success: true,
4591
+ transaction: settlement.transaction,
4592
+ network: ALIPAY_NETWORK,
4593
+ facilitator: settlement.facilitator
4594
+ })).toString("base64");
4595
+ }
4596
+ this.sendJson(res, 200, {
4597
+ success: true,
4598
+ result,
4599
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
4600
+ }, responseHeaders);
4601
+ }
4602
+ /**
4603
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
4604
+ * isn't configured for this server or this service. Returns the x402
4605
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
4606
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
4607
+ */
4608
+ async buildAlipayChallenge(config) {
4609
+ if (!this.alipayFacilitator || !config.alipay) return null;
4610
+ try {
4611
+ const req = await this.alipayFacilitator.createPaymentRequirements({
4612
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
4613
+ priceCny: config.alipay.price_cny,
4614
+ goodsName: config.alipay.goods_name,
4615
+ resourceId: `/execute?service=${config.id}`
4616
+ });
4617
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
4618
+ } catch (err) {
4619
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
4620
+ return null;
4621
+ }
4622
+ }
2958
4623
  /**
2959
4624
  * Handle MPP (Machine Payments Protocol) request
2960
4625
  * Supports both x402 and MPP protocols on service endpoints
2961
4626
  */
2962
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
4627
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
2963
4628
  const config = skill.config;
2964
4629
  const params = body || {};
4630
+ if (proofHeader) {
4631
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
4632
+ }
2965
4633
  if (x402Header) {
2966
4634
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
2967
4635
  }
@@ -3067,7 +4735,7 @@ var MoltsPayServer = class {
3067
4735
  /**
3068
4736
  * Return 402 with both x402 and MPP payment requirements
3069
4737
  */
3070
- sendMPPPaymentRequired(config, res) {
4738
+ async sendMPPPaymentRequired(config, res) {
3071
4739
  const acceptedTokens = getAcceptedCurrencies(config);
3072
4740
  const providerChains = this.getProviderChains();
3073
4741
  const accepts = [];
@@ -3078,6 +4746,10 @@ var MoltsPayServer = class {
3078
4746
  }
3079
4747
  }
3080
4748
  }
4749
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4750
+ if (alipayChallenge) {
4751
+ accepts.push(alipayChallenge.accepts);
4752
+ }
3081
4753
  const x402PaymentRequired = {
3082
4754
  x402Version: X402_VERSION3,
3083
4755
  accepts,
@@ -3105,7 +4777,7 @@ var MoltsPayServer = class {
3105
4777
  };
3106
4778
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3107
4779
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3108
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
4780
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3109
4781
  }
3110
4782
  const headers = {
3111
4783
  "Content-Type": "application/problem+json",
@@ -3114,6 +4786,9 @@ var MoltsPayServer = class {
3114
4786
  if (mppWwwAuth) {
3115
4787
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
3116
4788
  }
4789
+ if (alipayChallenge) {
4790
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4791
+ }
3117
4792
  res.writeHead(402, headers);
3118
4793
  res.end(JSON.stringify({
3119
4794
  type: "https://paymentauth.org/problems/payment-required",
@@ -3140,7 +4815,7 @@ var MoltsPayServer = class {
3140
4815
  * Return 402 with x402 payment requirements (v2 format)
3141
4816
  * Includes requirements for all chains and all accepted currencies
3142
4817
  */
3143
- sendPaymentRequired(config, res) {
4818
+ async sendPaymentRequired(config, res) {
3144
4819
  const acceptedTokens = getAcceptedCurrencies(config);
3145
4820
  const providerChains = this.getProviderChains();
3146
4821
  const accepts = [];
@@ -3151,6 +4826,10 @@ var MoltsPayServer = class {
3151
4826
  }
3152
4827
  }
3153
4828
  }
4829
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4830
+ if (alipayChallenge) {
4831
+ accepts.push(alipayChallenge.accepts);
4832
+ }
3154
4833
  const acceptedChains = providerChains.map((c) => {
3155
4834
  if (c.network === "eip155:8453") return "base";
3156
4835
  if (c.network === "eip155:137") return "polygon";
@@ -3168,10 +4847,14 @@ var MoltsPayServer = class {
3168
4847
  }
3169
4848
  };
3170
4849
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
3171
- res.writeHead(402, {
4850
+ const headers = {
3172
4851
  "Content-Type": "application/json",
3173
4852
  [PAYMENT_REQUIRED_HEADER2]: encoded
3174
- });
4853
+ };
4854
+ if (alipayChallenge) {
4855
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4856
+ }
4857
+ res.writeHead(402, headers);
3175
4858
  res.end(JSON.stringify({
3176
4859
  error: "Payment required",
3177
4860
  message: `Service requires $${config.price} ${config.currency}`,
@@ -3189,7 +4872,7 @@ var MoltsPayServer = class {
3189
4872
  }
3190
4873
  const scheme = payment.accepted?.scheme || payment.scheme;
3191
4874
  const network = payment.accepted?.network || payment.network || this.networkId;
3192
- if (scheme !== "exact") {
4875
+ if (scheme !== "exact" && scheme !== "permit") {
3193
4876
  return { valid: false, error: `Unsupported scheme: ${scheme}` };
3194
4877
  }
3195
4878
  if (!this.isNetworkAccepted(network)) {
@@ -3211,8 +4894,10 @@ var MoltsPayServer = class {
3211
4894
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
3212
4895
  const tokenAddress = tokenAddresses[selectedToken];
3213
4896
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
4897
+ const isTempo = selectedNetwork === "eip155:42431";
4898
+ const scheme = isTempo ? "permit" : "exact";
3214
4899
  const requirements = {
3215
- scheme: "exact",
4900
+ scheme,
3216
4901
  network: selectedNetwork,
3217
4902
  asset: tokenAddress,
3218
4903
  amount: amountInUnits,
@@ -3240,6 +4925,16 @@ var MoltsPayServer = class {
3240
4925
  };
3241
4926
  }
3242
4927
  }
4928
+ if (isTempo) {
4929
+ const tempoFacilitator = this.registry.get("tempo");
4930
+ const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
4931
+ if (tempoSpender) {
4932
+ requirements.extra = {
4933
+ ...requirements.extra || {},
4934
+ tempoSpender
4935
+ };
4936
+ }
4937
+ }
3243
4938
  return requirements;
3244
4939
  }
3245
4940
  /**
@@ -3266,12 +4961,12 @@ var MoltsPayServer = class {
3266
4961
  return accepted.includes(token);
3267
4962
  }
3268
4963
  async readBody(req) {
3269
- return new Promise((resolve2, reject) => {
4964
+ return new Promise((resolve3, reject) => {
3270
4965
  let body = "";
3271
4966
  req.on("data", (chunk) => body += chunk);
3272
4967
  req.on("end", () => {
3273
4968
  try {
3274
- resolve2(body ? JSON.parse(body) : {});
4969
+ resolve3(body ? JSON.parse(body) : {});
3275
4970
  } catch {
3276
4971
  reject(new Error("Invalid JSON"));
3277
4972
  }
@@ -3374,10 +5069,10 @@ var MoltsPayServer = class {
3374
5069
  }
3375
5070
  const scheme = payment.accepted?.scheme || payment.scheme;
3376
5071
  const network = payment.accepted?.network || payment.network;
3377
- if (scheme !== "exact") {
5072
+ if (scheme !== "exact" && scheme !== "permit") {
3378
5073
  return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3379
5074
  }
3380
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
5075
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3381
5076
  if (network !== expectedNetwork) {
3382
5077
  return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3383
5078
  }
@@ -3529,7 +5224,7 @@ var MoltsPayServer = class {
3529
5224
  };
3530
5225
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3531
5226
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3532
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
5227
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3533
5228
  res.writeHead(402, {
3534
5229
  "Content-Type": "application/problem+json",
3535
5230
  [MPP_WWW_AUTH_HEADER]: wwwAuth
@@ -3639,7 +5334,7 @@ var MoltsPayServer = class {
3639
5334
  buildProxyPaymentRequirements(config, wallet, token, chain) {
3640
5335
  const amountInUnits = Math.floor(config.price * 1e6).toString();
3641
5336
  const acceptedTokens = getAcceptedCurrencies(config);
3642
- const networkId = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
5337
+ const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3643
5338
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
3644
5339
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
3645
5340
  const tokenAddress = tokenAddresses[selectedToken];
@@ -3699,10 +5394,10 @@ init_cjs_shims();
3699
5394
  async function printQRCode(url) {
3700
5395
  const qrcodeModule = await import("qrcode-terminal");
3701
5396
  const qrcode = qrcodeModule.default || qrcodeModule;
3702
- return new Promise((resolve2) => {
5397
+ return new Promise((resolve3) => {
3703
5398
  qrcode.generate(url, { small: true }, (qr) => {
3704
5399
  console.log(qr);
3705
- resolve2();
5400
+ resolve3();
3706
5401
  });
3707
5402
  });
3708
5403
  }
@@ -3714,9 +5409,9 @@ if (!globalThis.crypto) {
3714
5409
  }
3715
5410
  function getVersion() {
3716
5411
  const locations = [
3717
- (0, import_path3.join)(__dirname, "../../package.json"),
3718
- (0, import_path3.join)(__dirname, "../package.json"),
3719
- (0, import_path3.join)(process.cwd(), "node_modules/moltspay/package.json")
5412
+ (0, import_path4.join)(__dirname, "../../package.json"),
5413
+ (0, import_path4.join)(__dirname, "../package.json"),
5414
+ (0, import_path4.join)(process.cwd(), "node_modules/moltspay/package.json")
3720
5415
  ];
3721
5416
  for (const loc of locations) {
3722
5417
  try {
@@ -3737,7 +5432,7 @@ var ERC20_APPROVE_ABI = [
3737
5432
  ];
3738
5433
  async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3739
5434
  const chainConfig = CHAINS[chain];
3740
- const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
5435
+ const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
3741
5436
  const wallet = client.getWallet();
3742
5437
  if (!wallet) {
3743
5438
  console.log(" \u274C No wallet found");
@@ -3746,15 +5441,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3746
5441
  const signer = wallet.connect(provider);
3747
5442
  console.log(` Spender: ${spenderAddress}`);
3748
5443
  let bnbBalance = await provider.getBalance(wallet.address);
3749
- const minGasRequired = import_ethers2.ethers.parseEther("0.0005");
5444
+ const minGasRequired = import_ethers4.ethers.parseEther("0.0005");
3750
5445
  if (bnbBalance < minGasRequired) {
3751
5446
  if (sponsorGas && BNB_SPONSOR_KEY) {
3752
5447
  console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3753
5448
  try {
3754
- const sponsorWallet = new import_ethers2.ethers.Wallet(BNB_SPONSOR_KEY, provider);
5449
+ const sponsorWallet = new import_ethers4.ethers.Wallet(BNB_SPONSOR_KEY, provider);
3755
5450
  const tx = await sponsorWallet.sendTransaction({
3756
5451
  to: wallet.address,
3757
- value: import_ethers2.ethers.parseEther("0.001")
5452
+ value: import_ethers4.ethers.parseEther("0.001")
3758
5453
  });
3759
5454
  await tx.wait();
3760
5455
  console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
@@ -3773,7 +5468,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3773
5468
  }
3774
5469
  for (const tokenSymbol of ["USDT", "USDC"]) {
3775
5470
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3776
- const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
5471
+ const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3777
5472
  const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3778
5473
  if (allowance > 0n) {
3779
5474
  console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
@@ -3781,7 +5476,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3781
5476
  }
3782
5477
  console.log(` \u23F3 Approving ${tokenSymbol}...`);
3783
5478
  try {
3784
- const tx = await tokenContract.approve(spenderAddress, import_ethers2.ethers.MaxUint256);
5479
+ const tx = await tokenContract.approve(spenderAddress, import_ethers4.ethers.MaxUint256);
3785
5480
  await tx.wait();
3786
5481
  console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3787
5482
  } catch (err) {
@@ -3792,10 +5487,10 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3792
5487
  }
3793
5488
  async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3794
5489
  const chainConfig = CHAINS[chain];
3795
- const provider = new import_ethers2.ethers.JsonRpcProvider(chainConfig.rpc);
5490
+ const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
3796
5491
  let spenderAddress = null;
3797
5492
  try {
3798
- const walletPath = (0, import_path3.join)(configDir, "wallet.json");
5493
+ const walletPath = (0, import_path4.join)(configDir, "wallet.json");
3799
5494
  const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
3800
5495
  spenderAddress = walletData.approvals?.[chain] || null;
3801
5496
  } catch {
@@ -3806,15 +5501,15 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
3806
5501
  }
3807
5502
  for (const tokenSymbol of ["USDT", "USDC"]) {
3808
5503
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3809
- const tokenContract = new import_ethers2.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
5504
+ const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3810
5505
  const allowance = await tokenContract.allowance(address, spenderAddress);
3811
5506
  result[tokenSymbol.toLowerCase()] = allowance > 0n;
3812
5507
  }
3813
5508
  return result;
3814
5509
  }
3815
5510
  var program = new import_commander.Command();
3816
- var DEFAULT_CONFIG_DIR2 = (0, import_path3.join)((0, import_os3.homedir)(), ".moltspay");
3817
- var PID_FILE = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "server.pid");
5511
+ var DEFAULT_CONFIG_DIR2 = (0, import_path4.join)((0, import_os4.homedir)(), ".moltspay");
5512
+ var PID_FILE = (0, import_path4.join)(DEFAULT_CONFIG_DIR2, "server.pid");
3818
5513
  if (!(0, import_fs5.existsSync)(DEFAULT_CONFIG_DIR2)) {
3819
5514
  (0, import_fs5.mkdirSync)(DEFAULT_CONFIG_DIR2, { recursive: true });
3820
5515
  }
@@ -3823,10 +5518,10 @@ function prompt(question) {
3823
5518
  input: process.stdin,
3824
5519
  output: process.stdout
3825
5520
  });
3826
- return new Promise((resolve2) => {
5521
+ return new Promise((resolve3) => {
3827
5522
  rl.question(question, (answer) => {
3828
5523
  rl.close();
3829
- resolve2(answer.trim());
5524
+ resolve3(answer.trim());
3830
5525
  });
3831
5526
  });
3832
5527
  }
@@ -3854,7 +5549,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3854
5549
  console.log(`
3855
5550
  \u2705 Solana wallet created: ${address}`);
3856
5551
  console.log(`
3857
- \u{1F4C1} Config saved to: ${(0, import_path3.join)(options.configDir, "wallet-solana.json")}`);
5552
+ \u{1F4C1} Config saved to: ${(0, import_path4.join)(options.configDir, "wallet-solana.json")}`);
3858
5553
  console.log(`
3859
5554
  \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
3860
5555
  console.log(` This file contains your private key!
@@ -3869,7 +5564,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3869
5564
  return;
3870
5565
  }
3871
5566
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
3872
- if ((0, import_fs5.existsSync)((0, import_path3.join)(options.configDir, "wallet.json"))) {
5567
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(options.configDir, "wallet.json"))) {
3873
5568
  console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
3874
5569
  console.log(` Config dir: ${options.configDir}`);
3875
5570
  return;
@@ -3895,7 +5590,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3895
5590
  console.log(`
3896
5591
  \u{1F4C1} Config saved to: ${result.configDir}`);
3897
5592
  console.log(`
3898
- \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path3.join)(result.configDir, "wallet.json")}`);
5593
+ \u26A0\uFE0F IMPORTANT: Back up ${(0, import_path4.join)(result.configDir, "wallet.json")}`);
3899
5594
  console.log(` This file contains your private key!
3900
5595
  `);
3901
5596
  if (chain === "bnb" || chain === "bnb_testnet") {
@@ -4060,7 +5755,7 @@ program.command("approve").description("Approve a spender address for BNB chain
4060
5755
  \u{1F510} Approving spender for ${chain}...
4061
5756
  `);
4062
5757
  await setupBNBApprovals(client, chain, options.spender, false);
4063
- const walletPath = (0, import_path3.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
5758
+ const walletPath = (0, import_path4.join)(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4064
5759
  try {
4065
5760
  const walletData = JSON.parse((0, import_fs5.readFileSync)(walletPath, "utf-8"));
4066
5761
  walletData.approvals = walletData.approvals || {};
@@ -4697,17 +6392,17 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4697
6392
  const handlers = /* @__PURE__ */ new Map();
4698
6393
  let provider = null;
4699
6394
  for (const inputPath of allPaths) {
4700
- const resolvedPath = (0, import_path3.resolve)(inputPath);
6395
+ const resolvedPath = (0, import_path4.resolve)(inputPath);
4701
6396
  let manifestPath;
4702
6397
  let skillDir;
4703
6398
  let isSkillDir = false;
4704
- if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4705
- manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
6399
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(resolvedPath, "moltspay.services.json"))) {
6400
+ manifestPath = (0, import_path4.join)(resolvedPath, "moltspay.services.json");
4706
6401
  skillDir = resolvedPath;
4707
6402
  isSkillDir = true;
4708
6403
  } else if ((0, import_fs5.existsSync)(resolvedPath) && resolvedPath.endsWith(".json")) {
4709
6404
  manifestPath = resolvedPath;
4710
- skillDir = (0, import_path3.dirname)(resolvedPath);
6405
+ skillDir = (0, import_path4.dirname)(resolvedPath);
4711
6406
  } else if ((0, import_fs5.existsSync)(resolvedPath)) {
4712
6407
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
4713
6408
  continue;
@@ -4724,7 +6419,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4724
6419
  let skillModule = null;
4725
6420
  if (isSkillDir) {
4726
6421
  let entryPoint = "index.js";
4727
- const pkgJsonPath = (0, import_path3.join)(skillDir, "package.json");
6422
+ const pkgJsonPath = (0, import_path4.join)(skillDir, "package.json");
4728
6423
  if ((0, import_fs5.existsSync)(pkgJsonPath)) {
4729
6424
  try {
4730
6425
  const pkgJson = JSON.parse((0, import_fs5.readFileSync)(pkgJsonPath, "utf-8"));
@@ -4734,7 +6429,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4734
6429
  } catch {
4735
6430
  }
4736
6431
  }
4737
- const modulePath = (0, import_path3.join)(skillDir, entryPoint);
6432
+ const modulePath = (0, import_path4.join)(skillDir, entryPoint);
4738
6433
  if ((0, import_fs5.existsSync)(modulePath)) {
4739
6434
  try {
4740
6435
  skillModule = await import(modulePath);
@@ -4760,7 +6455,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4760
6455
  const workdir = skillDir;
4761
6456
  handlers.set(service.id, async (params) => {
4762
6457
  return new Promise((resolvePromise, reject) => {
4763
- const proc = (0, import_child_process.spawn)("sh", ["-c", service.command], {
6458
+ const proc = (0, import_child_process3.spawn)("sh", ["-c", service.command], {
4764
6459
  cwd: workdir,
4765
6460
  stdio: ["pipe", "pipe", "pipe"]
4766
6461
  });
@@ -4813,7 +6508,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4813
6508
  provider,
4814
6509
  services: allServices
4815
6510
  };
4816
- const tempManifestPath = (0, import_path3.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
6511
+ const tempManifestPath = (0, import_path4.join)(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
4817
6512
  (0, import_fs5.writeFileSync)(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
4818
6513
  console.log(`
4819
6514
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
@@ -4876,7 +6571,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
4876
6571
  }
4877
6572
  process.kill(pid, "SIGTERM");
4878
6573
  console.log("\u2705 Sent SIGTERM to server");
4879
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
6574
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
4880
6575
  try {
4881
6576
  process.kill(pid, 0);
4882
6577
  console.log("\u26A0\uFE0F Server still running, sending SIGKILL...");
@@ -4892,9 +6587,10 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
4892
6587
  process.exit(1);
4893
6588
  }
4894
6589
  });
4895
- program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
6590
+ program.command("pay <server> <service> [params]").description("Pay for a service and get the result").option("--prompt <text>", "Prompt for the service").option("--image <path>", "Image URL or local file path").option("--data <json>", "Raw JSON data to send (for custom input formats)").option("--token <token>", "Token to pay with (USDC or USDT)", "USDC").option("--chain <chain>", "Chain to pay on (base, polygon, base_sepolia, tempo_moderato, solana, or solana_devnet).").option("--rail <rail>", 'Payment rail: a chain name, or "alipay" for the Alipay fiat rail (CNY via alipay-bot)').option("--config-dir <dir>", "Config directory with wallet.json", DEFAULT_CONFIG_DIR2).option("--json", "Output raw JSON only").action(async (server, service, paramsJson, options) => {
4896
6591
  const client = new MoltsPayClient({ configDir: options.configDir });
4897
- if (!client.isInitialized) {
6592
+ const useAlipay = options.rail?.toLowerCase() === "alipay";
6593
+ if (!useAlipay && !client.isInitialized) {
4898
6594
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
4899
6595
  process.exit(1);
4900
6596
  }
@@ -4922,7 +6618,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4922
6618
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
4923
6619
  params.image_url = imagePath;
4924
6620
  } else {
4925
- const filePath = (0, import_path3.resolve)(imagePath);
6621
+ const filePath = (0, import_path4.resolve)(imagePath);
4926
6622
  if (!(0, import_fs5.existsSync)(filePath)) {
4927
6623
  console.error(`\u274C Image file not found: ${filePath}`);
4928
6624
  process.exit(1);
@@ -4939,7 +6635,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4939
6635
  }
4940
6636
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
4941
6637
  const token = (options.token || "USDC").toUpperCase();
4942
- if (token === "USDT") {
6638
+ if (!useAlipay && token === "USDT") {
4943
6639
  const balance = await client.getBalance();
4944
6640
  if (balance.native < 1e-4) {
4945
6641
  console.log("\n\u26A0\uFE0F USDT requires a small amount of ETH for gas (~$0.01)");
@@ -4963,13 +6659,31 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4963
6659
  console.log(` Prompt: ${params.prompt}`);
4964
6660
  }
4965
6661
  if (imageDisplay) console.log(` Image: ${imageDisplay}`);
4966
- console.log(` Chain: ${chain || "(auto)"}`);
4967
- console.log(` Token: ${token}`);
4968
- console.log(` Wallet: ${client.address}`);
6662
+ if (useAlipay) {
6663
+ console.log(` Rail: alipay (CNY via alipay-bot)`);
6664
+ } else {
6665
+ console.log(` Chain: ${chain || "(auto)"}`);
6666
+ console.log(` Token: ${token}`);
6667
+ console.log(` Wallet: ${client.address}`);
6668
+ }
4969
6669
  console.log("");
4970
6670
  }
4971
6671
  try {
4972
- const result = await client.pay(server, service, params, {
6672
+ const result = await client.pay(server, service, params, useAlipay ? {
6673
+ rail: "alipay",
6674
+ rawData: useRawData,
6675
+ onPaymentPending: ({ paymentUrl, shortenUrl }) => {
6676
+ if (!options.json) {
6677
+ process.stdout.write(`
6678
+ \u{1F4F2} \u8BF7\u7528\u652F\u4ED8\u5B9D\u626B\u7801\u6216\u8BBF\u95EE\uFF1A${shortenUrl ?? paymentUrl}
6679
+
6680
+ `);
6681
+ }
6682
+ },
6683
+ onLine: (line) => {
6684
+ if (!options.json) process.stdout.write(line + "\n");
6685
+ }
6686
+ } : {
4973
6687
  token,
4974
6688
  chain,
4975
6689
  rawData: useRawData
@@ -4990,11 +6704,34 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4990
6704
  process.exit(1);
4991
6705
  }
4992
6706
  });
6707
+ program.command("alipay <action> [args...]").description("Alipay wallet setup via alipay-bot: check | apply | bind").allowUnknownOption().action(async (action, args) => {
6708
+ const map = {
6709
+ check: "check-wallet",
6710
+ apply: "apply-wallet",
6711
+ bind: "bind-wallet"
6712
+ };
6713
+ const sub = map[action] ?? action;
6714
+ const child = (0, import_child_process3.spawn)("alipay-bot", [sub, ...args ?? []], {
6715
+ stdio: "inherit",
6716
+ env: filterEnv(process.env)
6717
+ });
6718
+ child.on("error", (e) => {
6719
+ if (e?.code === "ENOENT") {
6720
+ console.error(
6721
+ "\u274C alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
6722
+ );
6723
+ } else {
6724
+ console.error(`\u274C ${e.message}`);
6725
+ }
6726
+ process.exit(1);
6727
+ });
6728
+ child.on("exit", (code) => process.exit(code ?? 1));
6729
+ });
4993
6730
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
4994
- const resolvedPath = (0, import_path3.resolve)(inputPath);
6731
+ const resolvedPath = (0, import_path4.resolve)(inputPath);
4995
6732
  let manifestPath;
4996
- if ((0, import_fs5.existsSync)((0, import_path3.join)(resolvedPath, "moltspay.services.json"))) {
4997
- manifestPath = (0, import_path3.join)(resolvedPath, "moltspay.services.json");
6733
+ if ((0, import_fs5.existsSync)((0, import_path4.join)(resolvedPath, "moltspay.services.json"))) {
6734
+ manifestPath = (0, import_path4.join)(resolvedPath, "moltspay.services.json");
4998
6735
  } else if (resolvedPath.endsWith(".json") && (0, import_fs5.existsSync)(resolvedPath)) {
4999
6736
  manifestPath = resolvedPath;
5000
6737
  } else {