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
@@ -21,18 +21,21 @@ var init_esm_shims = __esm({
21
21
  init_esm_shims();
22
22
  import { webcrypto } from "crypto";
23
23
  import { Command } from "commander";
24
- import { homedir as homedir3 } from "os";
25
- import { join as join5, dirname, resolve } from "path";
24
+ import { homedir as homedir4 } from "os";
25
+ import { join as join6, dirname as dirname2, resolve as resolve2 } from "path";
26
26
  import { existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync5, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
27
- import { spawn } from "child_process";
28
- import { ethers as ethers2 } from "ethers";
27
+ import { spawn as spawn2 } from "child_process";
28
+ import { ethers as ethers4 } from "ethers";
29
29
 
30
30
  // src/client/index.ts
31
31
  init_esm_shims();
32
+
33
+ // src/client/node/index.ts
34
+ init_esm_shims();
32
35
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
33
- import { homedir as homedir2 } from "os";
34
- import { join as join2 } from "path";
35
- import { Wallet, ethers } from "ethers";
36
+ import { homedir as homedir3 } from "os";
37
+ import { join as join3 } from "path";
38
+ import { Wallet as Wallet2, ethers as ethers2 } from "ethers";
36
39
 
37
40
  // src/chains/index.ts
38
41
  init_esm_shims();
@@ -208,6 +211,10 @@ function getChain(name) {
208
211
  }
209
212
  return config;
210
213
  }
214
+ var ALIPAY_CHAIN_ID = "alipay";
215
+ function isAlipayChainId(id) {
216
+ return id === ALIPAY_CHAIN_ID;
217
+ }
211
218
 
212
219
  // src/wallet/solana.ts
213
220
  init_esm_shims();
@@ -526,16 +533,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
526
533
  return this.supportedNetworks.includes(network);
527
534
  }
528
535
  };
529
- async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
536
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
530
537
  const chainConfig = SOLANA_CHAINS[chain];
531
- const connection = new Connection3(chainConfig.rpc, "confirmed");
538
+ const conn = connection ?? new Connection3(chainConfig.rpc, "confirmed");
532
539
  const mint = new PublicKey3(chainConfig.tokens.USDC.mint);
533
540
  const actualFeePayer = feePayerPubkey || senderPubkey;
534
541
  const senderATA = await getAssociatedTokenAddress2(mint, senderPubkey);
535
542
  const recipientATA = await getAssociatedTokenAddress2(mint, recipientPubkey);
536
543
  const transaction = new Transaction();
537
544
  try {
538
- await getAccount2(connection, recipientATA);
545
+ await getAccount2(conn, recipientATA);
539
546
  } catch {
540
547
  transaction.add(
541
548
  createAssociatedTokenAccountInstruction(
@@ -566,22 +573,944 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
566
573
  // decimals
567
574
  )
568
575
  );
569
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
576
+ const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
570
577
  transaction.recentBlockhash = blockhash;
571
578
  transaction.feePayer = actualFeePayer;
572
579
  return transaction;
573
580
  }
574
581
 
575
- // src/client/index.ts
582
+ // src/client/node/index.ts
576
583
  import { PublicKey as PublicKey4 } from "@solana/web3.js";
577
584
 
578
- // src/client/types.ts
585
+ // src/client/core/index.ts
579
586
  init_esm_shims();
580
587
 
581
- // src/client/index.ts
588
+ // src/client/core/types.ts
589
+ init_esm_shims();
582
590
  var X402_VERSION = 2;
583
591
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
584
592
  var PAYMENT_HEADER = "x-payment";
593
+
594
+ // src/client/core/chain-map.ts
595
+ init_esm_shims();
596
+ var NETWORK_TO_CHAIN = {
597
+ "eip155:8453": "base",
598
+ "eip155:137": "polygon",
599
+ "eip155:84532": "base_sepolia",
600
+ "eip155:42431": "tempo_moderato",
601
+ "eip155:56": "bnb",
602
+ "eip155:97": "bnb_testnet",
603
+ "solana:mainnet": "solana",
604
+ "solana:devnet": "solana_devnet"
605
+ };
606
+ var CHAIN_TO_NETWORK = Object.fromEntries(
607
+ Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
608
+ );
609
+ function networkToChainName(network) {
610
+ return NETWORK_TO_CHAIN[network] ?? null;
611
+ }
612
+
613
+ // src/client/core/base64.ts
614
+ init_esm_shims();
615
+ var BufferCtor = globalThis.Buffer;
616
+
617
+ // src/client/core/errors.ts
618
+ init_esm_shims();
619
+ var MoltsPayError = class extends Error {
620
+ code;
621
+ constructor(code, message) {
622
+ super(message);
623
+ this.name = "MoltsPayError";
624
+ this.code = code;
625
+ }
626
+ };
627
+
628
+ // src/client/core/eip3009.ts
629
+ init_esm_shims();
630
+ var EIP3009_TYPES = {
631
+ TransferWithAuthorization: [
632
+ { name: "from", type: "address" },
633
+ { name: "to", type: "address" },
634
+ { name: "value", type: "uint256" },
635
+ { name: "validAfter", type: "uint256" },
636
+ { name: "validBefore", type: "uint256" },
637
+ { name: "nonce", type: "bytes32" }
638
+ ]
639
+ };
640
+ function buildEIP3009TypedData(args) {
641
+ const validAfter = args.validAfter ?? "0";
642
+ const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
643
+ const authorization = {
644
+ from: args.from,
645
+ to: args.to,
646
+ value: args.value,
647
+ validAfter,
648
+ validBefore,
649
+ nonce: args.nonce
650
+ };
651
+ return {
652
+ domain: {
653
+ name: args.tokenName,
654
+ version: args.tokenVersion,
655
+ chainId: args.chainId,
656
+ verifyingContract: args.tokenAddress
657
+ },
658
+ types: EIP3009_TYPES,
659
+ primaryType: "TransferWithAuthorization",
660
+ message: authorization
661
+ };
662
+ }
663
+
664
+ // src/client/core/eip2612.ts
665
+ init_esm_shims();
666
+
667
+ // src/client/core/bnb-intent.ts
668
+ init_esm_shims();
669
+ var BNB_INTENT_TYPES = {
670
+ PaymentIntent: [
671
+ { name: "from", type: "address" },
672
+ { name: "to", type: "address" },
673
+ { name: "amount", type: "uint256" },
674
+ { name: "token", type: "address" },
675
+ { name: "service", type: "string" },
676
+ { name: "nonce", type: "uint256" },
677
+ { name: "deadline", type: "uint256" }
678
+ ]
679
+ };
680
+ var BNB_DOMAIN_NAME = "MoltsPay";
681
+ var BNB_DOMAIN_VERSION = "1";
682
+ function buildBnbIntentTypedData(args) {
683
+ const intent = {
684
+ from: args.from,
685
+ to: args.to,
686
+ amount: args.amount,
687
+ token: args.tokenAddress,
688
+ service: args.service,
689
+ nonce: args.nonce,
690
+ deadline: args.deadline
691
+ };
692
+ return {
693
+ domain: {
694
+ name: BNB_DOMAIN_NAME,
695
+ version: BNB_DOMAIN_VERSION,
696
+ chainId: args.chainId
697
+ },
698
+ types: BNB_INTENT_TYPES,
699
+ primaryType: "PaymentIntent",
700
+ message: intent
701
+ };
702
+ }
703
+
704
+ // src/client/core/solana-tx.ts
705
+ init_esm_shims();
706
+
707
+ // src/client/core/x402.ts
708
+ init_esm_shims();
709
+
710
+ // src/client/node/signer.ts
711
+ init_esm_shims();
712
+ import { ethers } from "ethers";
713
+ import { Transaction as Transaction2 } from "@solana/web3.js";
714
+ var NodeSigner = class {
715
+ evmWallet;
716
+ getSolanaKeypair;
717
+ constructor(evmWallet, options = {}) {
718
+ this.evmWallet = evmWallet;
719
+ this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
720
+ }
721
+ async getEvmAddress() {
722
+ return this.evmWallet.address;
723
+ }
724
+ async getSolanaAddress() {
725
+ const kp = this.getSolanaKeypair();
726
+ return kp ? kp.publicKey.toBase58() : null;
727
+ }
728
+ async signTypedData(envelope) {
729
+ const mutableTypes = {};
730
+ for (const [key, fields] of Object.entries(envelope.types)) {
731
+ mutableTypes[key] = [...fields];
732
+ }
733
+ return this.evmWallet.signTypedData(
734
+ envelope.domain,
735
+ mutableTypes,
736
+ envelope.message
737
+ );
738
+ }
739
+ async sendEvmTransaction(args) {
740
+ const chain = findChainByChainId(args.chainId);
741
+ if (!chain) {
742
+ throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
743
+ }
744
+ const provider = new ethers.JsonRpcProvider(chain.rpc);
745
+ const connected = this.evmWallet.connect(provider);
746
+ const tx = await connected.sendTransaction({
747
+ to: args.to,
748
+ data: args.data,
749
+ value: args.value ? BigInt(args.value) : 0n
750
+ });
751
+ return tx.hash;
752
+ }
753
+ async signSolanaTransaction(args) {
754
+ const kp = this.getSolanaKeypair();
755
+ if (!kp) {
756
+ throw new Error("signSolanaTransaction: no Solana wallet configured");
757
+ }
758
+ const tx = Transaction2.from(Buffer.from(args.transactionBase64, "base64"));
759
+ if (args.partialSign) {
760
+ tx.partialSign(kp);
761
+ } else {
762
+ tx.sign(kp);
763
+ }
764
+ return tx.serialize({ requireAllSignatures: false }).toString("base64");
765
+ }
766
+ };
767
+ function findChainByChainId(chainId) {
768
+ for (const cfg of Object.values(CHAINS)) {
769
+ if (cfg.chainId === chainId) {
770
+ return cfg;
771
+ }
772
+ }
773
+ return void 0;
774
+ }
775
+
776
+ // src/client/alipay/index.ts
777
+ init_esm_shims();
778
+ import { randomUUID } from "crypto";
779
+ import { mkdir, writeFile } from "fs/promises";
780
+ import { homedir as homedir2 } from "os";
781
+ import { join as join2 } from "path";
782
+
783
+ // src/client/alipay/cli.ts
784
+ init_esm_shims();
785
+ import { spawn } from "child_process";
786
+
787
+ // src/client/alipay/log.ts
788
+ init_esm_shims();
789
+ var RANK = { off: 0, info: 1, debug: 2 };
790
+ function normalizeLevel(v) {
791
+ const s = (v ?? "").toLowerCase();
792
+ return s === "info" || s === "debug" ? s : "off";
793
+ }
794
+ var level = normalizeLevel(process.env.MOLTSPAY_ALIPAY_LOG);
795
+ var sink = (line) => {
796
+ process.stderr.write(line + "\n");
797
+ };
798
+ function enabledFor(want) {
799
+ return RANK[level] >= RANK[want];
800
+ }
801
+ function render(event, fields) {
802
+ const parts = ["[moltspay:alipay]", String(fields.ts), event];
803
+ if (fields.flow) parts.push(`flow=${fields.flow}`);
804
+ if (fields.step) parts.push(`step=${fields.step}`);
805
+ if (typeof fields.ms === "number") parts.push(`${fields.ms}ms`);
806
+ for (const [k, v] of Object.entries(fields)) {
807
+ if (["ts", "level", "event", "flow", "step", "ms"].includes(k)) continue;
808
+ parts.push(`${k}=${typeof v === "string" ? v : JSON.stringify(v)}`);
809
+ }
810
+ return parts.join(" ");
811
+ }
812
+ function emit(lvl, event, fields) {
813
+ if (!enabledFor(lvl)) return;
814
+ const full = { ts: (/* @__PURE__ */ new Date()).toISOString(), level: lvl, event, ...fields };
815
+ try {
816
+ sink(render(event, full), full);
817
+ } catch {
818
+ }
819
+ }
820
+ var alipayLog = {
821
+ info: (event, fields = {}) => emit("info", event, fields),
822
+ debug: (event, fields = {}) => emit("debug", event, fields),
823
+ /** True if anything at all would be logged (cheap guard for hot paths). */
824
+ enabled: () => level !== "off"
825
+ };
826
+ async function timeStep(step, flow, fn) {
827
+ if (!alipayLog.enabled()) return fn();
828
+ const t0 = Date.now();
829
+ alipayLog.debug("step.start", { flow, step });
830
+ try {
831
+ const out = await fn();
832
+ alipayLog.info("step.end", { flow, step, ms: Date.now() - t0, ok: true });
833
+ return out;
834
+ } catch (err) {
835
+ alipayLog.info("step.end", {
836
+ flow,
837
+ step,
838
+ ms: Date.now() - t0,
839
+ ok: false,
840
+ err: err instanceof Error ? err.message : String(err)
841
+ });
842
+ throw err;
843
+ }
844
+ }
845
+
846
+ // src/client/alipay/cli.ts
847
+ var ALLOWED_ENV = /* @__PURE__ */ new Set([
848
+ "AIPAY_OUTPUT_CHANNEL",
849
+ "AIPAY_SESSION_ID",
850
+ "AIPAY_FRAMEWORK",
851
+ "AIPAY_MODEL",
852
+ "AIPAY_OS",
853
+ // Minimal survival set for spawn to find the binary and a home dir.
854
+ "PATH",
855
+ "HOME"
856
+ ]);
857
+ function filterEnv(env) {
858
+ return Object.fromEntries(
859
+ Object.entries(env).filter(([k]) => ALLOWED_ENV.has(k))
860
+ );
861
+ }
862
+ function makeLineSplitter(onLine) {
863
+ let buf = "";
864
+ return (chunk) => {
865
+ buf += chunk.toString("utf-8");
866
+ let nl;
867
+ while ((nl = buf.indexOf("\n")) !== -1) {
868
+ onLine(buf.slice(0, nl));
869
+ buf = buf.slice(nl + 1);
870
+ }
871
+ };
872
+ }
873
+ function profileEnv(step, flow) {
874
+ const want = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE;
875
+ const hook = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_HOOK;
876
+ if (!want || !hook) return {};
877
+ const steps = want.split(",").map((s) => s.trim());
878
+ if (!steps.includes("all") && !steps.includes(step)) return {};
879
+ const dir = process.env.MOLTSPAY_ALIPAY_CLI_PROFILE_DIR || "/tmp";
880
+ const safe = `${flow ?? "noflow"}-${step}-${Date.now()}`.replace(/[^a-zA-Z0-9._-]/g, "_");
881
+ const out = `${dir}/cli-profile-${safe}.json`;
882
+ alipayLog.info("cli.profile", { flow, step, out });
883
+ return {
884
+ NODE_OPTIONS: `--require=${hook}`,
885
+ MOLTSPAY_CLI_PROFILE_OUT: out
886
+ };
887
+ }
888
+ var runCli = (args, opts = {}) => {
889
+ const bin = opts.bin ?? "alipay-bot";
890
+ const step = opts.step ?? args[0] ?? "(no-arg)";
891
+ const startedAt = Date.now();
892
+ const lines = [];
893
+ const collect = (line) => {
894
+ lines.push(line);
895
+ alipayLog.debug("cli.line", {
896
+ flow: opts.flow,
897
+ step,
898
+ ms: Date.now() - startedAt,
899
+ preview: line.slice(0, 120)
900
+ });
901
+ opts.onLine?.(line);
902
+ };
903
+ alipayLog.debug("step.start", { flow: opts.flow, step });
904
+ return new Promise((resolve3, reject) => {
905
+ const child = spawn(bin, args, {
906
+ env: { ...filterEnv(process.env), ...profileEnv(step, opts.flow), ...opts.env ?? {} }
907
+ });
908
+ if (opts.signal) {
909
+ if (opts.signal.aborted) child.kill("SIGTERM");
910
+ opts.signal.addEventListener("abort", () => child.kill("SIGTERM"), { once: true });
911
+ }
912
+ let sawByte = false;
913
+ const onChunk = (stream) => (chunk) => {
914
+ const ms = Date.now() - startedAt;
915
+ if (!sawByte) {
916
+ sawByte = true;
917
+ alipayLog.debug("cli.firstbyte", { flow: opts.flow, step, ms });
918
+ }
919
+ alipayLog.debug("cli.chunk", { flow: opts.flow, step, ms, stream, bytes: chunk.length });
920
+ };
921
+ const onStdout = makeLineSplitter(collect);
922
+ const onStderr = makeLineSplitter(collect);
923
+ child.stdout?.on("data", onChunk("out"));
924
+ child.stderr?.on("data", onChunk("err"));
925
+ child.stdout?.on("data", onStdout);
926
+ child.stderr?.on("data", onStderr);
927
+ child.on("error", (err) => {
928
+ alipayLog.info("cli.exit", {
929
+ flow: opts.flow,
930
+ step,
931
+ ms: Date.now() - startedAt,
932
+ ok: false,
933
+ err: err.message
934
+ });
935
+ reject(err);
936
+ });
937
+ child.on("close", (code) => {
938
+ alipayLog.info("cli.exit", {
939
+ flow: opts.flow,
940
+ step,
941
+ ms: Date.now() - startedAt,
942
+ ok: (code ?? 1) === 0,
943
+ code: code ?? 1
944
+ });
945
+ resolve3({ exitCode: code ?? 1, lines });
946
+ });
947
+ });
948
+ };
949
+
950
+ // src/client/alipay/install.ts
951
+ init_esm_shims();
952
+ import { execFile } from "child_process";
953
+ import { promisify } from "util";
954
+
955
+ // src/client/alipay/errors.ts
956
+ init_esm_shims();
957
+ var AlipayCliNotFoundError = class extends MoltsPayError {
958
+ constructor(message) {
959
+ super("ALIPAY_CLI_NOT_FOUND", message);
960
+ this.name = "AlipayCliNotFoundError";
961
+ }
962
+ };
963
+ var AlipayCliVersionError = class extends MoltsPayError {
964
+ constructor(message) {
965
+ super("ALIPAY_CLI_VERSION", message);
966
+ this.name = "AlipayCliVersionError";
967
+ }
968
+ };
969
+ var NeedsWalletSetupError = class extends MoltsPayError {
970
+ constructor(message = "Alipay wallet not set up. Run: moltspay alipay apply") {
971
+ super("ALIPAY_NEEDS_WALLET_SETUP", message);
972
+ this.name = "NeedsWalletSetupError";
973
+ }
974
+ };
975
+ var AlipayPaymentRejectedError = class extends MoltsPayError {
976
+ constructor(message) {
977
+ super("ALIPAY_PAYMENT_REJECTED", message);
978
+ this.name = "AlipayPaymentRejectedError";
979
+ }
980
+ };
981
+ var AlipayPaymentTimeoutError = class extends MoltsPayError {
982
+ constructor(message) {
983
+ super("ALIPAY_PAYMENT_TIMEOUT", message);
984
+ this.name = "AlipayPaymentTimeoutError";
985
+ }
986
+ };
987
+ var AlipayProtocolError = class extends MoltsPayError {
988
+ constructor(message) {
989
+ super("ALIPAY_PROTOCOL", message);
990
+ this.name = "AlipayProtocolError";
991
+ }
992
+ };
993
+ var UnsupportedRailError = class extends MoltsPayError {
994
+ constructor(rail, message) {
995
+ super("UNSUPPORTED_RAIL", message ?? `Rail not supported by server: ${rail}`);
996
+ this.rail = rail;
997
+ this.name = "UnsupportedRailError";
998
+ }
999
+ };
1000
+
1001
+ // src/client/alipay/install.ts
1002
+ var execFileAsync = promisify(execFile);
1003
+ var MIN_CLI_VERSION = "0.3.15";
1004
+ function semverLt(a, b) {
1005
+ const pa = a.split(".").map((n) => parseInt(n, 10));
1006
+ const pb = b.split(".").map((n) => parseInt(n, 10));
1007
+ for (let i = 0; i < 3; i++) {
1008
+ const x = pa[i] ?? 0;
1009
+ const y = pb[i] ?? 0;
1010
+ if (x !== y) return x < y;
1011
+ }
1012
+ return false;
1013
+ }
1014
+ function parseVersion(stdout) {
1015
+ return stdout.match(/v?(\d+\.\d+\.\d+)/)?.[1] ?? null;
1016
+ }
1017
+ var defaultGetVersion = async () => {
1018
+ const { stdout } = await execFileAsync("alipay-bot", ["--version"]);
1019
+ return stdout;
1020
+ };
1021
+ async function ensureCliUncached(getVersion2) {
1022
+ let stdout;
1023
+ try {
1024
+ stdout = await getVersion2();
1025
+ } catch (e) {
1026
+ if (e?.code === "ENOENT") {
1027
+ throw new AlipayCliNotFoundError(
1028
+ "alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
1029
+ );
1030
+ }
1031
+ throw e;
1032
+ }
1033
+ const version = parseVersion(stdout);
1034
+ if (!version || semverLt(version, MIN_CLI_VERSION)) {
1035
+ throw new AlipayCliVersionError(
1036
+ `alipay-bot ${version ?? "?"} found, need \u2265 ${MIN_CLI_VERSION}. Run: npx -y @alipay/agent-payment@latest update`
1037
+ );
1038
+ }
1039
+ return version;
1040
+ }
1041
+ var cachedVersion = null;
1042
+ var inflight = null;
1043
+ async function ensureCli(getVersion2 = defaultGetVersion) {
1044
+ if (getVersion2 !== defaultGetVersion) {
1045
+ return ensureCliUncached(getVersion2);
1046
+ }
1047
+ if (cachedVersion) return cachedVersion;
1048
+ if (!inflight) {
1049
+ inflight = ensureCliUncached(getVersion2).then((v) => {
1050
+ cachedVersion = v;
1051
+ return v;
1052
+ }).finally(() => {
1053
+ inflight = null;
1054
+ });
1055
+ }
1056
+ return inflight;
1057
+ }
1058
+
1059
+ // src/client/alipay/poll.ts
1060
+ init_esm_shims();
1061
+ var POLL_INTERVAL_MS = 3e3;
1062
+ var POLL_MAX_INFLIGHT = 2;
1063
+ function resolveMaxInflight(override) {
1064
+ if (override !== void 0) return Math.max(1, Math.floor(override));
1065
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_MAX_INFLIGHT;
1066
+ if (raw !== void 0 && raw.trim() !== "") {
1067
+ const n = Number(raw);
1068
+ if (Number.isFinite(n) && n >= 1) return Math.floor(n);
1069
+ }
1070
+ return POLL_MAX_INFLIGHT;
1071
+ }
1072
+ function resolveLaunchGap(override) {
1073
+ if (override !== void 0) return Math.max(0, override);
1074
+ const raw = process.env.MOLTSPAY_ALIPAY_POLL_LAUNCH_MS;
1075
+ if (raw !== void 0 && raw.trim() !== "") {
1076
+ const n = Number(raw);
1077
+ if (Number.isFinite(n) && n >= 0) return n;
1078
+ }
1079
+ return POLL_INTERVAL_MS;
1080
+ }
1081
+ function parseStatus(lines) {
1082
+ const raw = lines.join("\n").trim();
1083
+ try {
1084
+ const json = JSON.parse(raw);
1085
+ if (json && typeof json === "object") {
1086
+ if (typeof json.success === "boolean") {
1087
+ if (json.success) return "paid";
1088
+ const err = String(json.errorCode ?? "").toUpperCase();
1089
+ if (/UNPAID|WAIT|PENDING|PROCESS|NOTPAY/.test(err)) return "pending";
1090
+ if (/CLOSED|CANCEL|FAIL|REJECT|REFUSE|TIMEOUT|EXPIRE/.test(err)) return "rejected";
1091
+ return "pending";
1092
+ }
1093
+ if (typeof json.code !== "undefined") {
1094
+ if (Number(json.code) === 200) return "paid";
1095
+ const msg = `${json.message ?? ""}${json.reason ?? ""}`;
1096
+ if (/关闭|失败|拒绝|取消|超时|已撤销/.test(msg)) return "rejected";
1097
+ return "pending";
1098
+ }
1099
+ if (typeof json.body === "string") {
1100
+ return classifyReportText(json.body);
1101
+ }
1102
+ }
1103
+ } catch {
1104
+ }
1105
+ return classifyReportText(raw);
1106
+ }
1107
+ function classifyReportText(s) {
1108
+ const text = s.toUpperCase();
1109
+ if (/查询支付状态成功并获取资源/.test(s) || /资源响应状态[^0-9\n]{0,8}200/.test(s) || /TRADE_SUCCESS|TRADE_FINISHED|"STATUS":\s*"FULFILLED"/.test(text)) return "paid";
1110
+ if (/TRADE_CLOSED|REJECTED|REFUSE|CANCEL/.test(text) || /交易关闭|支付失败|已拒绝|已取消|已撤销|交易超时/.test(s)) return "rejected";
1111
+ if (/WAIT_BUYER_PAY|UNPAID|PENDING|WAITING/.test(text) || /交易未支付|等待付款|待支付|未支付/.test(s)) return "pending";
1112
+ return "unknown";
1113
+ }
1114
+ function defaultSleep(ms, signal) {
1115
+ return new Promise((resolve3, reject) => {
1116
+ if (signal?.aborted) return reject(new Error("aborted"));
1117
+ const t = setTimeout(resolve3, ms);
1118
+ signal?.addEventListener(
1119
+ "abort",
1120
+ () => {
1121
+ clearTimeout(t);
1122
+ reject(new Error("aborted"));
1123
+ },
1124
+ { once: true }
1125
+ );
1126
+ });
1127
+ }
1128
+ var LAUNCH = /* @__PURE__ */ Symbol("launch");
1129
+ async function pollUntil(tradeNo, resourceUrl, opts) {
1130
+ const runner = opts.runner ?? runCli;
1131
+ const sleep = opts.sleep ?? defaultSleep;
1132
+ const now = opts.now ?? Date.now;
1133
+ const maxInflight = resolveMaxInflight(opts.maxInflight);
1134
+ const launchGap = resolveLaunchGap(opts.launchIntervalMs);
1135
+ const args = [
1136
+ "402-query-payment-status",
1137
+ "-t",
1138
+ tradeNo,
1139
+ "-r",
1140
+ resourceUrl,
1141
+ ...opts.method ? ["-m", opts.method] : [],
1142
+ ...opts.data ? ["-d", opts.data] : []
1143
+ ];
1144
+ const internal = new AbortController();
1145
+ const signal = opts.signal ? AbortSignal.any([opts.signal, internal.signal]) : internal.signal;
1146
+ let tick = 0;
1147
+ let lastLaunch = -Infinity;
1148
+ const inflight2 = /* @__PURE__ */ new Set();
1149
+ const launch = () => {
1150
+ tick += 1;
1151
+ const myTick = tick;
1152
+ lastLaunch = now();
1153
+ const run = (async () => {
1154
+ const { lines } = await runner(args, {
1155
+ signal,
1156
+ step: "query-payment-status",
1157
+ flow: tradeNo
1158
+ });
1159
+ const status = parseStatus(lines);
1160
+ alipayLog.debug("poll.tick", { flow: tradeNo, tick: myTick, status });
1161
+ return { status, lines };
1162
+ })();
1163
+ const tracked = run.finally(() => {
1164
+ inflight2.delete(tracked);
1165
+ });
1166
+ inflight2.add(tracked);
1167
+ };
1168
+ try {
1169
+ for (; ; ) {
1170
+ if (opts.signal?.aborted) throw new Error("aborted");
1171
+ const expired = now() >= opts.deadline;
1172
+ if (expired && inflight2.size === 0) {
1173
+ throw new AlipayPaymentTimeoutError(
1174
+ `Payment ${tradeNo} not completed before pay_before deadline`
1175
+ );
1176
+ }
1177
+ while (!expired && inflight2.size < maxInflight && now() - lastLaunch >= launchGap) {
1178
+ launch();
1179
+ }
1180
+ const waiters = [...inflight2];
1181
+ if (!expired && inflight2.size < maxInflight) {
1182
+ const untilNext = Math.max(0, launchGap - (now() - lastLaunch));
1183
+ waiters.push(sleep(untilNext, signal).then(() => LAUNCH, () => LAUNCH));
1184
+ }
1185
+ const winner = await Promise.race(waiters);
1186
+ if (winner === LAUNCH) continue;
1187
+ if (winner.status === "paid") return { status: "paid", lines: winner.lines };
1188
+ if (winner.status === "rejected") {
1189
+ throw new AlipayPaymentRejectedError(`Payment ${tradeNo} was rejected`);
1190
+ }
1191
+ }
1192
+ } finally {
1193
+ internal.abort();
1194
+ for (const p of inflight2) p.catch(() => void 0);
1195
+ }
1196
+ }
1197
+
1198
+ // src/client/alipay/index.ts
1199
+ var TRADE_NO_RE = /^\d{32}$/;
1200
+ function resolveSessionId(explicit, envSession) {
1201
+ return explicit ?? envSession ?? randomUUID();
1202
+ }
1203
+ function assertTradeNo(t) {
1204
+ if (!TRADE_NO_RE.test(t)) {
1205
+ throw new AlipayProtocolError(`invalid tradeNo (expect 32 digits): ${t}`);
1206
+ }
1207
+ }
1208
+ function parseTradeNo(lines) {
1209
+ for (const line of lines) {
1210
+ const labeled = line.match(/trade[_-]?no["'\s:=]+(\d{32})/i);
1211
+ if (labeled) return labeled[1];
1212
+ const bare = line.match(/\b(\d{32})\b/);
1213
+ if (bare) return bare[1];
1214
+ }
1215
+ return null;
1216
+ }
1217
+ function parsePaymentUrl(lines) {
1218
+ let paymentUrl;
1219
+ let shortenUrl;
1220
+ for (const line of lines) {
1221
+ const m = line.match(/(alipays?:\/\/\S+|https?:\/\/\S+)/i);
1222
+ if (!m) continue;
1223
+ const url = m[1].replace(/[)\]`>]+$/, "");
1224
+ if (/short|qr\.alipay|surl|\/s\//i.test(line) && !shortenUrl) shortenUrl = url;
1225
+ else if (!paymentUrl) paymentUrl = url;
1226
+ }
1227
+ if (!paymentUrl && shortenUrl) paymentUrl = shortenUrl;
1228
+ return { paymentUrl, shortenUrl };
1229
+ }
1230
+ function extractMedia(line) {
1231
+ const m = line.match(/^\s*MEDIA:\s*(.+?)\s*$/);
1232
+ return m ? m[1] : null;
1233
+ }
1234
+ function isWalletReady(lines) {
1235
+ const text = lines.join("\n").trim();
1236
+ try {
1237
+ const json = JSON.parse(text);
1238
+ if (json && typeof json.code !== "undefined") return Number(json.code) === 200;
1239
+ } catch {
1240
+ }
1241
+ return !/未开通|未开启|NOT[_\s-]*(OPEN|BOUND|SET)|NEEDS?[_\s-]*SETUP|NO[_\s-]*WALLET/i.test(text);
1242
+ }
1243
+ function extractResourceFromReport(report) {
1244
+ const label = report.search(/资源响应体/);
1245
+ const start = report.indexOf("{", label === -1 ? 0 : label);
1246
+ if (start === -1) return void 0;
1247
+ let depth = 0, inStr = false, esc = false;
1248
+ for (let i = start; i < report.length; i++) {
1249
+ const ch = report[i];
1250
+ if (inStr) {
1251
+ if (esc) esc = false;
1252
+ else if (ch === "\\") esc = true;
1253
+ else if (ch === '"') inStr = false;
1254
+ continue;
1255
+ }
1256
+ if (ch === '"') inStr = true;
1257
+ else if (ch === "{") depth++;
1258
+ else if (ch === "}" && --depth === 0) {
1259
+ const slice = report.slice(start, i + 1);
1260
+ try {
1261
+ const obj = JSON.parse(slice);
1262
+ const r = obj?.resourceResponse ?? obj?.result ?? obj?.data ?? obj?.body ?? obj;
1263
+ return typeof r === "string" ? r : JSON.stringify(r);
1264
+ } catch {
1265
+ return slice;
1266
+ }
1267
+ }
1268
+ }
1269
+ return void 0;
1270
+ }
1271
+ function extractBody(lines) {
1272
+ const text = lines.join("\n").trim();
1273
+ try {
1274
+ const json = JSON.parse(text);
1275
+ if (json && typeof json === "object") {
1276
+ if (json.resourceResponse !== void 0 && json.resourceResponse !== null) {
1277
+ const rr = json.resourceResponse;
1278
+ const r = typeof rr === "object" ? rr.result ?? rr.data ?? rr.body ?? rr : rr;
1279
+ return typeof r === "string" ? r : JSON.stringify(r);
1280
+ }
1281
+ if (typeof json.body === "string") {
1282
+ const inner = extractResourceFromReport(json.body);
1283
+ return inner ?? json.body;
1284
+ }
1285
+ const body = json.data ?? json.result ?? json.body ?? json.resource;
1286
+ if (body !== void 0) return typeof body === "string" ? body : JSON.stringify(body);
1287
+ return text;
1288
+ }
1289
+ } catch {
1290
+ }
1291
+ const idx = lines.findIndex((l) => /^\s*BODY:/.test(l));
1292
+ if (idx !== -1) {
1293
+ const first = lines[idx].replace(/^\s*BODY:\s*/, "");
1294
+ return [first, ...lines.slice(idx + 1)].join("\n").trim();
1295
+ }
1296
+ return lines.filter((l) => !/^\s*(MEDIA|STATUS|INFO):/.test(l)).join("\n").trim();
1297
+ }
1298
+ var DEFAULT_WALLET_TTL_MS = 10 * 60 * 1e3;
1299
+ function resolveWalletTtlMs() {
1300
+ const raw = process.env.MOLTSPAY_ALIPAY_WALLET_TTL_MS;
1301
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_WALLET_TTL_MS;
1302
+ const n = Number(raw);
1303
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_WALLET_TTL_MS;
1304
+ }
1305
+ var walletReadyUntil = /* @__PURE__ */ new Map();
1306
+ var DEFAULT_INTENT_TTL_MS = 10 * 60 * 1e3;
1307
+ function resolveIntentTtlMs() {
1308
+ const raw = process.env.MOLTSPAY_ALIPAY_INTENT_TTL_MS;
1309
+ if (raw === void 0 || raw.trim() === "") return DEFAULT_INTENT_TTL_MS;
1310
+ const n = Number(raw);
1311
+ return Number.isFinite(n) && n >= 0 ? n : DEFAULT_INTENT_TTL_MS;
1312
+ }
1313
+ var intentDoneUntil = /* @__PURE__ */ new Map();
1314
+ var AlipayClient = class {
1315
+ sessionId;
1316
+ configDir;
1317
+ framework;
1318
+ runner;
1319
+ getVersion;
1320
+ now;
1321
+ /** Only the default runner may use the process-level wallet cache. */
1322
+ walletCacheable;
1323
+ /** Only the default runner may use the process-level payment-intent cache. */
1324
+ intentCacheable;
1325
+ constructor(opts = {}) {
1326
+ this.sessionId = resolveSessionId(opts.sessionId, process.env.AIPAY_SESSION_ID);
1327
+ this.configDir = opts.configDir ?? join2(homedir2(), ".moltspay");
1328
+ this.framework = opts.framework ?? process.env.AIPAY_FRAMEWORK ?? "openclaw";
1329
+ this.runner = opts.runner ?? runCli;
1330
+ this.getVersion = opts.getVersion;
1331
+ this.now = opts.now ?? Date.now;
1332
+ this.walletCacheable = opts.cacheWallet ?? !opts.runner;
1333
+ this.intentCacheable = opts.cacheIntent ?? !opts.runner;
1334
+ }
1335
+ /**
1336
+ * Throws NeedsWalletSetupError unless alipay-bot reports an opened wallet.
1337
+ *
1338
+ * Skips the ~22s `check-wallet` spawn entirely when a prior call cached a
1339
+ * "ready" verdict within the TTL (see {@link DEFAULT_WALLET_TTL_MS}).
1340
+ */
1341
+ async checkWallet(signal, flow) {
1342
+ const ttlMs = resolveWalletTtlMs();
1343
+ const cacheKey = `${this.configDir}::${this.framework}`;
1344
+ const useCache = this.walletCacheable && ttlMs > 0;
1345
+ if (useCache) {
1346
+ const until = walletReadyUntil.get(cacheKey);
1347
+ if (until !== void 0 && this.now() < until) {
1348
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: true, ttlMsLeft: until - this.now() });
1349
+ return;
1350
+ }
1351
+ }
1352
+ const { lines } = await this.runner(["check-wallet"], { signal, step: "check-wallet", flow });
1353
+ if (!isWalletReady(lines)) {
1354
+ if (this.walletCacheable) walletReadyUntil.delete(cacheKey);
1355
+ throw new NeedsWalletSetupError(
1356
+ "Alipay wallet not opened. Run: moltspay alipay apply (then: moltspay alipay bind)"
1357
+ );
1358
+ }
1359
+ if (useCache) {
1360
+ walletReadyUntil.set(cacheKey, this.now() + ttlMs);
1361
+ alipayLog.info("wallet.cache", { flow, step: "check-wallet", hit: false, cachedForMs: ttlMs });
1362
+ }
1363
+ }
1364
+ /** Run the full 8-step flow and resolve with the resource body. */
1365
+ async pay402(opts) {
1366
+ const { resourceUrl, requirement, signal } = opts;
1367
+ const extra = requirement.extra ?? {};
1368
+ const paymentNeededHeader = String(extra.payment_needed_header ?? "");
1369
+ if (!paymentNeededHeader) {
1370
+ throw new AlipayProtocolError("alipay requirement missing extra.payment_needed_header");
1371
+ }
1372
+ const flow = this.sessionId;
1373
+ const flowStart = this.now();
1374
+ alipayLog.info("flow.start", { flow, resource: resourceUrl });
1375
+ const media = [];
1376
+ const onLine = (line) => {
1377
+ const m = extractMedia(line);
1378
+ if (m) media.push(m);
1379
+ else opts.onLine?.(line);
1380
+ };
1381
+ const intentSummary = opts.intentSummary?.trim() || `\u652F\u4ED8 ${requirement.amount ?? ""} ${requirement.asset ?? "CNY"}`.trim();
1382
+ await timeStep("ensure-cli", flow, () => ensureCli(this.getVersion));
1383
+ const intentTtlMs = resolveIntentTtlMs();
1384
+ const intentKey = `${this.configDir}::${this.framework}`;
1385
+ const useIntentCache = this.intentCacheable && intentTtlMs > 0;
1386
+ const intentUntil = useIntentCache ? intentDoneUntil.get(intentKey) : void 0;
1387
+ if (intentUntil !== void 0 && this.now() < intentUntil) {
1388
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: true, ttlMsLeft: intentUntil - this.now() });
1389
+ } else {
1390
+ await this.runner(
1391
+ [
1392
+ "payment-intent",
1393
+ "--session-id",
1394
+ this.sessionId,
1395
+ "--intent-summary",
1396
+ intentSummary,
1397
+ "--framework",
1398
+ this.framework
1399
+ ],
1400
+ { onLine, signal, step: "payment-intent", flow }
1401
+ );
1402
+ if (useIntentCache) {
1403
+ intentDoneUntil.set(intentKey, this.now() + intentTtlMs);
1404
+ alipayLog.info("intent.cache", { flow, step: "payment-intent", hit: false, cachedForMs: intentTtlMs });
1405
+ }
1406
+ }
1407
+ await this.checkWallet(signal, flow);
1408
+ const reqId = String(extra.out_trade_no ?? randomUUID());
1409
+ const dir = join2(this.configDir, "alipay");
1410
+ await mkdir(dir, { recursive: true });
1411
+ const challengeFile = join2(dir, `402_${reqId}.txt`);
1412
+ await writeFile(challengeFile, paymentNeededHeader, "utf-8");
1413
+ const payArgs = [
1414
+ "402-buyer-pay",
1415
+ "-f",
1416
+ challengeFile,
1417
+ "-r",
1418
+ resourceUrl,
1419
+ "-s",
1420
+ this.sessionId,
1421
+ "-i",
1422
+ intentSummary,
1423
+ "-w",
1424
+ this.framework
1425
+ ];
1426
+ if (opts.method) payArgs.push("-m", opts.method);
1427
+ if (opts.data) payArgs.push("-d", opts.data);
1428
+ const payRun = await this.runner(payArgs, { onLine, signal, step: "402-buyer-pay", flow });
1429
+ const tradeNo = parseTradeNo(payRun.lines);
1430
+ if (!tradeNo) {
1431
+ throw new AlipayProtocolError("402-buyer-pay did not return a tradeNo");
1432
+ }
1433
+ assertTradeNo(tradeNo);
1434
+ const { paymentUrl, shortenUrl } = parsePaymentUrl(payRun.lines);
1435
+ if (paymentUrl) {
1436
+ alipayLog.info("flow.pending", { flow, tradeNo, ms: this.now() - flowStart });
1437
+ opts.onPaymentPending?.({ paymentUrl, shortenUrl, tradeNo });
1438
+ }
1439
+ const pendingAt = this.now();
1440
+ const windowMs = (requirement.maxTimeoutSeconds ?? 30 * 60) * 1e3;
1441
+ const deadline = this.now() + (opts.timeoutMs ?? windowMs);
1442
+ const poll = await pollUntil(tradeNo, resourceUrl, {
1443
+ // No onLine: the status-poll output embeds the resource and must not reach
1444
+ // the log stream — the body is surfaced via the return value below (§9.3).
1445
+ deadline,
1446
+ signal,
1447
+ runner: this.runner,
1448
+ now: this.now,
1449
+ // Re-fetch the resource the same way it was paid (POST + body), else the
1450
+ // status poll defaults to GET and 404s on a POST-only `/execute`.
1451
+ method: opts.method,
1452
+ data: opts.data
1453
+ });
1454
+ alipayLog.info("flow.settled", { flow, tradeNo, ms: this.now() - pendingAt });
1455
+ const body = extractBody(poll.lines);
1456
+ void this.runner(["402-buyer-fulfillment-ack", "-t", tradeNo], {
1457
+ onLine,
1458
+ signal,
1459
+ step: "402-buyer-fulfillment-ack",
1460
+ flow
1461
+ }).catch(() => void 0);
1462
+ return { body, payment: { tradeNo, outTradeNo: reqId }, media };
1463
+ }
1464
+ };
1465
+
1466
+ // src/client/alipay/router.ts
1467
+ init_esm_shims();
1468
+ var ALIPAY_RAIL = "alipay";
1469
+ function railOf(req) {
1470
+ if (req.scheme === "alipay-aipay" || req.network === ALIPAY_RAIL) return ALIPAY_RAIL;
1471
+ return networkToChainName(req.network) ?? req.network;
1472
+ }
1473
+ function findRail(accepts, rail) {
1474
+ const requirement = accepts.find((r) => railOf(r) === rail);
1475
+ return requirement ? { rail, requirement } : null;
1476
+ }
1477
+ function selectRail(input) {
1478
+ const { serverAccepts, explicitRail, preference, availability } = input;
1479
+ if (!serverAccepts || serverAccepts.length === 0) {
1480
+ throw new UnsupportedRailError(explicitRail ?? "unknown", "Server offered no payment options");
1481
+ }
1482
+ if (explicitRail) {
1483
+ const hit = findRail(serverAccepts, explicitRail);
1484
+ if (!hit) {
1485
+ const offered = serverAccepts.map(railOf);
1486
+ throw new UnsupportedRailError(
1487
+ explicitRail,
1488
+ `Server doesn't accept rail '${explicitRail}'. Offered: ${offered.join(", ")}`
1489
+ );
1490
+ }
1491
+ return hit;
1492
+ }
1493
+ if (preference) {
1494
+ for (const pref of preference) {
1495
+ const hit = findRail(serverAccepts, pref);
1496
+ if (hit) return hit;
1497
+ }
1498
+ }
1499
+ if (availability?.evmReady) {
1500
+ const evm = serverAccepts.find((r) => railOf(r) !== ALIPAY_RAIL);
1501
+ if (evm) return { rail: railOf(evm), requirement: evm };
1502
+ }
1503
+ if (availability?.alipayReady) {
1504
+ const hit = findRail(serverAccepts, ALIPAY_RAIL);
1505
+ if (hit) return hit;
1506
+ }
1507
+ return { rail: railOf(serverAccepts[0]), requirement: serverAccepts[0] };
1508
+ }
1509
+
1510
+ // src/client/types.ts
1511
+ init_esm_shims();
1512
+
1513
+ // src/client/node/index.ts
585
1514
  var DEFAULT_CONFIG = {
586
1515
  chain: "base",
587
1516
  limits: {
@@ -594,15 +1523,24 @@ var MoltsPayClient = class {
594
1523
  config;
595
1524
  walletData = null;
596
1525
  wallet = null;
1526
+ signer = null;
597
1527
  todaySpending = 0;
598
1528
  lastSpendingReset = 0;
1529
+ railPreference;
1530
+ alipaySessionId;
599
1531
  constructor(options = {}) {
600
- this.configDir = options.configDir || join2(homedir2(), ".moltspay");
1532
+ this.configDir = options.configDir || join3(homedir3(), ".moltspay");
601
1533
  this.config = this.loadConfig();
1534
+ this.railPreference = options.railPreference ?? this.config.railPreference;
1535
+ this.alipaySessionId = options.alipaySessionId;
602
1536
  this.walletData = this.loadWallet();
603
1537
  this.loadSpending();
604
1538
  if (this.walletData) {
605
- this.wallet = new Wallet(this.walletData.privateKey);
1539
+ this.wallet = new Wallet2(this.walletData.privateKey);
1540
+ const configDir = this.configDir;
1541
+ this.signer = new NodeSigner(this.wallet, {
1542
+ getSolanaKeypair: () => loadSolanaWallet(configDir)
1543
+ });
606
1544
  }
607
1545
  }
608
1546
  /**
@@ -672,6 +1610,9 @@ var MoltsPayClient = class {
672
1610
  * @param options - Payment options (token selection)
673
1611
  */
674
1612
  async pay(serverUrl, service, params, options = {}) {
1613
+ if (options.rail === ALIPAY_RAIL) {
1614
+ return this.payViaAlipay(serverUrl, service, params, options);
1615
+ }
675
1616
  if (!this.wallet || !this.walletData) {
676
1617
  throw new Error("Client not initialized. Run: npx moltspay init");
677
1618
  }
@@ -730,20 +1671,6 @@ var MoltsPayClient = class {
730
1671
  } catch {
731
1672
  throw new Error("Invalid x-payment-required header");
732
1673
  }
733
- const networkToChainName = (network2) => {
734
- if (network2 === "solana:mainnet") return "solana";
735
- if (network2 === "solana:devnet") return "solana_devnet";
736
- const match = network2.match(/^eip155:(\d+)$/);
737
- if (!match) return null;
738
- const chainId = parseInt(match[1]);
739
- if (chainId === 8453) return "base";
740
- if (chainId === 137) return "polygon";
741
- if (chainId === 84532) return "base_sepolia";
742
- if (chainId === 42431) return "tempo_moderato";
743
- if (chainId === 56) return "bnb";
744
- if (chainId === 97) return "bnb_testnet";
745
- return null;
746
- };
747
1674
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
748
1675
  const userSpecifiedChain = options.chain;
749
1676
  let selectedChain;
@@ -877,6 +1804,76 @@ Please specify: --chain <chain_name>`
877
1804
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
878
1805
  return result.result || result;
879
1806
  }
1807
+ /**
1808
+ * Pay for a service over the Alipay fiat rail (2.0.0).
1809
+ *
1810
+ * Unlike the crypto path this needs no EVM wallet — it shells out to
1811
+ * alipay-bot via {@link AlipayClient}. Flow: hit the resource with no
1812
+ * payment to get the 402 challenge, confirm the server actually offers the
1813
+ * alipay rail (selectRail), then run the 8-step state machine and return the
1814
+ * resource body.
1815
+ */
1816
+ async payViaAlipay(serverUrl, service, params, options) {
1817
+ const flow = this.alipaySessionId;
1818
+ let executeUrl = `${serverUrl}/execute`;
1819
+ try {
1820
+ const services = await timeStep(
1821
+ "discover-services",
1822
+ flow,
1823
+ () => this.getServices(serverUrl)
1824
+ );
1825
+ const svc = services.services?.find((s) => s.id === service);
1826
+ if (svc?.endpoint) executeUrl = `${serverUrl}${svc.endpoint}`;
1827
+ } catch {
1828
+ }
1829
+ const requestBody = options.rawData ? { service, ...params } : { service, params };
1830
+ const bodyJson = JSON.stringify(requestBody);
1831
+ const res = await timeStep(
1832
+ "challenge-402",
1833
+ flow,
1834
+ () => fetch(executeUrl, {
1835
+ method: "POST",
1836
+ headers: { "Content-Type": "application/json" },
1837
+ body: bodyJson
1838
+ })
1839
+ );
1840
+ if (res.status !== 402) {
1841
+ const data = await res.json().catch(() => ({}));
1842
+ if (res.ok && data.result) return data.result;
1843
+ throw new Error(data.error || `Expected 402, got ${res.status}`);
1844
+ }
1845
+ const header = res.headers.get(PAYMENT_REQUIRED_HEADER);
1846
+ if (!header) throw new Error("Missing x-payment-required header on 402");
1847
+ const parsed = JSON.parse(Buffer.from(header, "base64").toString("utf-8"));
1848
+ const accepts = Array.isArray(parsed) ? parsed : parsed.accepts ?? [parsed];
1849
+ const { requirement } = selectRail({
1850
+ serverAccepts: accepts,
1851
+ explicitRail: ALIPAY_RAIL,
1852
+ preference: this.railPreference,
1853
+ availability: { evmReady: this.isInitialized }
1854
+ });
1855
+ const onLine = options.onLine ?? ((line) => process.stdout.write(line + "\n"));
1856
+ const alipay = new AlipayClient({
1857
+ sessionId: this.alipaySessionId,
1858
+ configDir: this.configDir
1859
+ });
1860
+ const result = await alipay.pay402({
1861
+ resourceUrl: executeUrl,
1862
+ requirement,
1863
+ method: "POST",
1864
+ data: bodyJson,
1865
+ onLine,
1866
+ onPaymentPending: options.onPaymentPending,
1867
+ timeoutMs: options.timeoutMs,
1868
+ signal: options.signal
1869
+ });
1870
+ try {
1871
+ const json = JSON.parse(result.body);
1872
+ return json.result ?? json;
1873
+ } catch {
1874
+ return { body: result.body, payment: result.payment, media: result.media };
1875
+ }
1876
+ }
880
1877
  /**
881
1878
  * Handle MPP (Machine Payments Protocol) payment flow
882
1879
  * Called when pay() detects WWW-Authenticate header in 402 response
@@ -972,14 +1969,14 @@ Please specify: --chain <chain_name>`
972
1969
  async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
973
1970
  const { to, amount, token, chainName, chain, spender } = paymentDetails;
974
1971
  const tokenConfig = chain.tokens[token];
975
- const provider = new ethers.JsonRpcProvider(chain.rpc);
1972
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
976
1973
  const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
977
1974
  const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
978
1975
  if (allowance < amountWeiCheck) {
979
1976
  const nativeBalance = await provider.getBalance(this.wallet.address);
980
- const minGasBalance = ethers.parseEther("0.0005");
1977
+ const minGasBalance = ethers2.parseEther("0.0005");
981
1978
  if (nativeBalance < minGasBalance) {
982
- const nativeBNB = parseFloat(ethers.formatEther(nativeBalance)).toFixed(4);
1979
+ const nativeBNB = parseFloat(ethers2.formatEther(nativeBalance)).toFixed(4);
983
1980
  const isTestnet = chainName === "bnb_testnet";
984
1981
  if (isTestnet) {
985
1982
  throw new Error(
@@ -1013,35 +2010,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1013
2010
  );
1014
2011
  }
1015
2012
  const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1016
- const intent = {
2013
+ const intentNonce = Date.now();
2014
+ const intentDeadline = Date.now() + 36e5;
2015
+ const envelope = buildBnbIntentTypedData({
1017
2016
  from: this.wallet.address,
1018
2017
  to,
1019
2018
  amount: amountWei,
1020
- token: tokenConfig.address,
2019
+ tokenAddress: tokenConfig.address,
1021
2020
  service,
1022
- nonce: Date.now(),
1023
- // Use timestamp as nonce for simplicity
1024
- deadline: Date.now() + 36e5
1025
- // 1 hour
1026
- };
1027
- const domain = {
1028
- name: "MoltsPay",
1029
- version: "1",
2021
+ nonce: intentNonce,
2022
+ deadline: intentDeadline,
1030
2023
  chainId: chain.chainId
1031
- };
1032
- const types = {
1033
- PaymentIntent: [
1034
- { name: "from", type: "address" },
1035
- { name: "to", type: "address" },
1036
- { name: "amount", type: "uint256" },
1037
- { name: "token", type: "address" },
1038
- { name: "service", type: "string" },
1039
- { name: "nonce", type: "uint256" },
1040
- { name: "deadline", type: "uint256" }
1041
- ]
1042
- };
2024
+ });
1043
2025
  console.log(`[MoltsPay] Signing BNB payment intent...`);
1044
- const signature = await this.wallet.signTypedData(domain, types, intent);
2026
+ const signature = await this.signer.signTypedData(envelope);
2027
+ const intent = envelope.message;
1045
2028
  const network = `eip155:${chain.chainId}`;
1046
2029
  const payload = {
1047
2030
  x402Version: 2,
@@ -1115,12 +2098,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1115
2098
  feePayerPubkey
1116
2099
  // Optional fee payer for gasless mode
1117
2100
  );
1118
- if (feePayerPubkey) {
1119
- transaction.partialSign(solanaWallet);
1120
- } else {
1121
- transaction.sign(solanaWallet);
1122
- }
1123
- const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
2101
+ const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
2102
+ const signedTx = await this.signer.signSolanaTransaction({
2103
+ transactionBase64: unsignedBase64,
2104
+ partialSign: !!feePayerPubkey
2105
+ });
1124
2106
  console.log(`[MoltsPay] Transaction signed, sending to server...`);
1125
2107
  const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1126
2108
  const payload = {
@@ -1167,7 +2149,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1167
2149
  * Check ERC20 allowance for a spender
1168
2150
  */
1169
2151
  async checkAllowance(tokenAddress, spender, provider) {
1170
- const contract = new ethers.Contract(
2152
+ const contract = new ethers2.Contract(
1171
2153
  tokenAddress,
1172
2154
  ["function allowance(address owner, address spender) view returns (uint256)"],
1173
2155
  provider
@@ -1178,41 +2160,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1178
2160
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
1179
2161
  * This only signs - no on-chain transaction, no gas needed.
1180
2162
  * Supports both USDC and USDT.
2163
+ *
2164
+ * Delegates typed-data construction to `core/eip3009.ts` and the signature
2165
+ * itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
2166
+ * flow with an EIP-1193 signer without duplicating typed-data layout.
1181
2167
  */
1182
2168
  async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
1183
- const validAfter = 0;
1184
- const validBefore = Math.floor(Date.now() / 1e3) + 3600;
1185
- const nonce = ethers.hexlify(ethers.randomBytes(32));
1186
2169
  const tokenConfig = chain.tokens[token];
1187
2170
  const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1188
- const authorization = {
2171
+ const nonce = ethers2.hexlify(ethers2.randomBytes(32));
2172
+ const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
2173
+ const tokenVersion = domainOverride?.version || "2";
2174
+ const envelope = buildEIP3009TypedData({
1189
2175
  from: this.wallet.address,
1190
2176
  to,
1191
2177
  value,
1192
- validAfter: validAfter.toString(),
1193
- validBefore: validBefore.toString(),
1194
- nonce
1195
- };
1196
- const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1197
- const tokenVersion = domainOverride?.version || "2";
1198
- const domain = {
1199
- name: tokenName,
1200
- version: tokenVersion,
2178
+ nonce,
1201
2179
  chainId: chain.chainId,
1202
- verifyingContract: tokenConfig.address
1203
- };
1204
- const types = {
1205
- TransferWithAuthorization: [
1206
- { name: "from", type: "address" },
1207
- { name: "to", type: "address" },
1208
- { name: "value", type: "uint256" },
1209
- { name: "validAfter", type: "uint256" },
1210
- { name: "validBefore", type: "uint256" },
1211
- { name: "nonce", type: "bytes32" }
1212
- ]
1213
- };
1214
- const signature = await this.wallet.signTypedData(domain, types, authorization);
1215
- return { authorization, signature };
2180
+ tokenAddress: tokenConfig.address,
2181
+ tokenName,
2182
+ tokenVersion
2183
+ });
2184
+ const signature = await this.signer.signTypedData(envelope);
2185
+ return { authorization: envelope.message, signature };
1216
2186
  }
1217
2187
  /**
1218
2188
  * Check spending limits
@@ -1244,7 +2214,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1244
2214
  }
1245
2215
  // --- Config & Wallet Management ---
1246
2216
  loadConfig() {
1247
- const configPath = join2(this.configDir, "config.json");
2217
+ const configPath = join3(this.configDir, "config.json");
1248
2218
  if (existsSync2(configPath)) {
1249
2219
  const content = readFileSync2(configPath, "utf-8");
1250
2220
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
@@ -1253,14 +2223,14 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1253
2223
  }
1254
2224
  saveConfig() {
1255
2225
  mkdirSync2(this.configDir, { recursive: true });
1256
- const configPath = join2(this.configDir, "config.json");
2226
+ const configPath = join3(this.configDir, "config.json");
1257
2227
  writeFileSync2(configPath, JSON.stringify(this.config, null, 2));
1258
2228
  }
1259
2229
  /**
1260
2230
  * Load spending data from disk
1261
2231
  */
1262
2232
  loadSpending() {
1263
- const spendingPath = join2(this.configDir, "spending.json");
2233
+ const spendingPath = join3(this.configDir, "spending.json");
1264
2234
  if (existsSync2(spendingPath)) {
1265
2235
  try {
1266
2236
  const data = JSON.parse(readFileSync2(spendingPath, "utf-8"));
@@ -1283,7 +2253,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1283
2253
  */
1284
2254
  saveSpending() {
1285
2255
  mkdirSync2(this.configDir, { recursive: true });
1286
- const spendingPath = join2(this.configDir, "spending.json");
2256
+ const spendingPath = join3(this.configDir, "spending.json");
1287
2257
  const data = {
1288
2258
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
1289
2259
  amount: this.todaySpending,
@@ -1292,7 +2262,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1292
2262
  writeFileSync2(spendingPath, JSON.stringify(data, null, 2));
1293
2263
  }
1294
2264
  loadWallet() {
1295
- const walletPath = join2(this.configDir, "wallet.json");
2265
+ const walletPath = join3(this.configDir, "wallet.json");
1296
2266
  if (existsSync2(walletPath)) {
1297
2267
  if (process.platform !== "win32") {
1298
2268
  try {
@@ -1316,13 +2286,13 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1316
2286
  */
1317
2287
  static init(configDir, options) {
1318
2288
  mkdirSync2(configDir, { recursive: true });
1319
- const wallet = Wallet.createRandom();
2289
+ const wallet = Wallet2.createRandom();
1320
2290
  const walletData = {
1321
2291
  address: wallet.address,
1322
2292
  privateKey: wallet.privateKey,
1323
2293
  createdAt: Date.now()
1324
2294
  };
1325
- const walletPath = join2(configDir, "wallet.json");
2295
+ const walletPath = join3(configDir, "wallet.json");
1326
2296
  writeFileSync2(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1327
2297
  const config = {
1328
2298
  chain: options.chain,
@@ -1331,7 +2301,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1331
2301
  maxPerDay: options.maxPerDay
1332
2302
  }
1333
2303
  };
1334
- const configPath = join2(configDir, "config.json");
2304
+ const configPath = join3(configDir, "config.json");
1335
2305
  writeFileSync2(configPath, JSON.stringify(config, null, 2));
1336
2306
  return { address: wallet.address, configDir };
1337
2307
  }
@@ -1348,17 +2318,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1348
2318
  } catch {
1349
2319
  throw new Error(`Unknown chain: ${this.config.chain}`);
1350
2320
  }
1351
- const provider = new ethers.JsonRpcProvider(chain.rpc);
2321
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
1352
2322
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
1353
2323
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1354
2324
  provider.getBalance(this.wallet.address),
1355
- new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1356
- new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2325
+ new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2326
+ new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1357
2327
  ]);
1358
2328
  return {
1359
- usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1360
- usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1361
- native: parseFloat(ethers.formatEther(nativeBalance))
2329
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2330
+ usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2331
+ native: parseFloat(ethers2.formatEther(nativeBalance))
1362
2332
  };
1363
2333
  }
1364
2334
  /**
@@ -1381,38 +2351,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1381
2351
  supportedChains.map(async (chainName) => {
1382
2352
  try {
1383
2353
  const chain = getChain(chainName);
1384
- const provider = new ethers.JsonRpcProvider(chain.rpc);
2354
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
1385
2355
  if (chainName === "tempo_moderato") {
1386
2356
  const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
1387
2357
  provider.getBalance(this.wallet.address),
1388
- new ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1389
- new ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1390
- new ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1391
- new ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
2358
+ new ethers2.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2359
+ new ethers2.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2360
+ new ethers2.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
2361
+ new ethers2.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
1392
2362
  ]);
1393
2363
  results[chainName] = {
1394
- usdc: parseFloat(ethers.formatUnits(pathUSD, 6)),
2364
+ usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
1395
2365
  // pathUSD as default USDC
1396
- usdt: parseFloat(ethers.formatUnits(alphaUSD, 6)),
2366
+ usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
1397
2367
  // alphaUSD as default USDT
1398
- native: parseFloat(ethers.formatEther(nativeBalance)),
2368
+ native: parseFloat(ethers2.formatEther(nativeBalance)),
1399
2369
  tempo: {
1400
- pathUSD: parseFloat(ethers.formatUnits(pathUSD, 6)),
1401
- alphaUSD: parseFloat(ethers.formatUnits(alphaUSD, 6)),
1402
- betaUSD: parseFloat(ethers.formatUnits(betaUSD, 6)),
1403
- thetaUSD: parseFloat(ethers.formatUnits(thetaUSD, 6))
2370
+ pathUSD: parseFloat(ethers2.formatUnits(pathUSD, 6)),
2371
+ alphaUSD: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
2372
+ betaUSD: parseFloat(ethers2.formatUnits(betaUSD, 6)),
2373
+ thetaUSD: parseFloat(ethers2.formatUnits(thetaUSD, 6))
1404
2374
  }
1405
2375
  };
1406
2376
  } else {
1407
2377
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1408
2378
  provider.getBalance(this.wallet.address),
1409
- new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1410
- new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
2379
+ new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
2380
+ new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1411
2381
  ]);
1412
2382
  results[chainName] = {
1413
- usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1414
- usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1415
- native: parseFloat(ethers.formatEther(nativeBalance))
2383
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
2384
+ usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
2385
+ native: parseFloat(ethers2.formatEther(nativeBalance))
1416
2386
  };
1417
2387
  }
1418
2388
  } catch (err) {
@@ -1770,16 +2740,40 @@ var CDPFacilitator = class extends BaseFacilitator {
1770
2740
 
1771
2741
  // src/facilitators/tempo.ts
1772
2742
  init_esm_shims();
2743
+ import { ethers as ethers3 } from "ethers";
1773
2744
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
2745
+ var TIP20_PERMIT_ABI = [
2746
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
2747
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
2748
+ ];
1774
2749
  var TempoFacilitator = class extends BaseFacilitator {
1775
2750
  name = "tempo";
1776
2751
  displayName = "Tempo Testnet";
1777
2752
  supportedNetworks = ["eip155:42431"];
1778
2753
  // Tempo Moderato
1779
2754
  rpcUrl;
2755
+ settlerWallet = null;
1780
2756
  constructor() {
1781
2757
  super();
1782
2758
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
2759
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
2760
+ if (settlerKey) {
2761
+ try {
2762
+ const provider = new ethers3.JsonRpcProvider(this.rpcUrl);
2763
+ this.settlerWallet = new ethers3.Wallet(settlerKey, provider);
2764
+ } catch (err) {
2765
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
2766
+ this.settlerWallet = null;
2767
+ }
2768
+ }
2769
+ }
2770
+ /**
2771
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
2772
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
2773
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
2774
+ */
2775
+ getSpenderAddress() {
2776
+ return this.settlerWallet?.address ?? null;
1783
2777
  }
1784
2778
  async healthCheck() {
1785
2779
  const start = Date.now();
@@ -1804,7 +2798,45 @@ var TempoFacilitator = class extends BaseFacilitator {
1804
2798
  return { healthy: false, error: String(error) };
1805
2799
  }
1806
2800
  }
1807
- async verify(paymentPayload, requirements) {
2801
+ async verify(paymentPayload, requirements) {
2802
+ const inner = paymentPayload.payload;
2803
+ if (inner && "permit" in inner && inner.permit) {
2804
+ return this.verifyPermit(inner, requirements);
2805
+ }
2806
+ return this.verifyTxHash(paymentPayload, requirements);
2807
+ }
2808
+ /**
2809
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
2810
+ * anything on-chain — actual submission happens in settlePermit().
2811
+ */
2812
+ async verifyPermit(payload, requirements) {
2813
+ if (!this.settlerWallet) {
2814
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
2815
+ }
2816
+ const p = payload.permit;
2817
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
2818
+ return { valid: false, error: "Invalid permit payload: missing fields" };
2819
+ }
2820
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
2821
+ return {
2822
+ valid: false,
2823
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
2824
+ };
2825
+ }
2826
+ const deadline = BigInt(p.deadline);
2827
+ const now = BigInt(Math.floor(Date.now() / 1e3));
2828
+ if (deadline <= now) {
2829
+ return { valid: false, error: "Permit deadline has expired" };
2830
+ }
2831
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
2832
+ return {
2833
+ valid: false,
2834
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
2835
+ };
2836
+ }
2837
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
2838
+ }
2839
+ async verifyTxHash(paymentPayload, requirements) {
1808
2840
  try {
1809
2841
  const tempoPayload = paymentPayload.payload;
1810
2842
  if (!tempoPayload?.txHash) {
@@ -1862,7 +2894,11 @@ var TempoFacilitator = class extends BaseFacilitator {
1862
2894
  }
1863
2895
  }
1864
2896
  async settle(paymentPayload, requirements) {
1865
- const verifyResult = await this.verify(paymentPayload, requirements);
2897
+ const inner = paymentPayload.payload;
2898
+ if (inner && "permit" in inner && inner.permit) {
2899
+ return this.settlePermit(inner, requirements);
2900
+ }
2901
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
1866
2902
  if (!verifyResult.valid) {
1867
2903
  return { success: false, error: verifyResult.error };
1868
2904
  }
@@ -1873,6 +2909,52 @@ var TempoFacilitator = class extends BaseFacilitator {
1873
2909
  status: "settled"
1874
2910
  };
1875
2911
  }
2912
+ /**
2913
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
2914
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
2915
+ * 2. pathUSD.transferFrom(owner, payTo, value)
2916
+ *
2917
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
2918
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
2919
+ */
2920
+ async settlePermit(payload, requirements) {
2921
+ if (!this.settlerWallet) {
2922
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
2923
+ }
2924
+ if (!requirements.asset || !requirements.payTo) {
2925
+ return { success: false, error: "Missing asset or payTo in requirements" };
2926
+ }
2927
+ const verifyResult = await this.verifyPermit(payload, requirements);
2928
+ if (!verifyResult.valid) {
2929
+ return { success: false, error: verifyResult.error };
2930
+ }
2931
+ const token = new ethers3.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
2932
+ const p = payload.permit;
2933
+ try {
2934
+ const permitTx = await token.permit(
2935
+ p.owner,
2936
+ p.spender,
2937
+ p.value,
2938
+ p.deadline,
2939
+ p.v,
2940
+ p.r,
2941
+ p.s
2942
+ );
2943
+ await permitTx.wait();
2944
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
2945
+ await transferTx.wait();
2946
+ return {
2947
+ success: true,
2948
+ transaction: transferTx.hash,
2949
+ status: "settled"
2950
+ };
2951
+ } catch (err) {
2952
+ return {
2953
+ success: false,
2954
+ error: `Tempo permit settlement failed: ${err.message}`
2955
+ };
2956
+ }
2957
+ }
1876
2958
  async getTransactionReceipt(txHash) {
1877
2959
  const response = await fetch(this.rpcUrl, {
1878
2960
  method: "POST",
@@ -2116,12 +3198,12 @@ var BNBFacilitator = class extends BaseFacilitator {
2116
3198
  return this.spenderAddress;
2117
3199
  }
2118
3200
  async getServerAddress() {
2119
- const { ethers: ethers3 } = await import("ethers");
2120
- const wallet = new ethers3.Wallet(this.serverPrivateKey);
3201
+ const { ethers: ethers5 } = await import("ethers");
3202
+ const wallet = new ethers5.Wallet(this.serverPrivateKey);
2121
3203
  return wallet.address;
2122
3204
  }
2123
3205
  async recoverIntentSigner(intent, chainId) {
2124
- const { ethers: ethers3 } = await import("ethers");
3206
+ const { ethers: ethers5 } = await import("ethers");
2125
3207
  const domain = {
2126
3208
  ...EIP712_DOMAIN,
2127
3209
  chainId
@@ -2135,7 +3217,7 @@ var BNBFacilitator = class extends BaseFacilitator {
2135
3217
  nonce: intent.nonce,
2136
3218
  deadline: intent.deadline
2137
3219
  };
2138
- const recoveredAddress = ethers3.verifyTypedData(
3220
+ const recoveredAddress = ethers5.verifyTypedData(
2139
3221
  domain,
2140
3222
  INTENT_TYPES,
2141
3223
  message,
@@ -2179,10 +3261,10 @@ var BNBFacilitator = class extends BaseFacilitator {
2179
3261
  return result.result || "0x0";
2180
3262
  }
2181
3263
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
2182
- const { ethers: ethers3 } = await import("ethers");
2183
- const provider = new ethers3.JsonRpcProvider(rpcUrl);
2184
- const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2185
- const tokenContract = new ethers3.Contract(token, [
3264
+ const { ethers: ethers5 } = await import("ethers");
3265
+ const provider = new ethers5.JsonRpcProvider(rpcUrl);
3266
+ const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
3267
+ const tokenContract = new ethers5.Contract(token, [
2186
3268
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2187
3269
  ], wallet);
2188
3270
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -2205,9 +3287,381 @@ var BNBFacilitator = class extends BaseFacilitator {
2205
3287
  }
2206
3288
  };
2207
3289
 
3290
+ // src/facilitators/alipay.ts
3291
+ init_esm_shims();
3292
+ import crypto2 from "crypto";
3293
+
3294
+ // src/facilitators/alipay/rsa2.ts
3295
+ init_esm_shims();
3296
+ import crypto from "crypto";
3297
+ function rsa2Sign(data, privateKeyPem) {
3298
+ const signer = crypto.createSign("RSA-SHA256");
3299
+ signer.update(data, "utf-8");
3300
+ signer.end();
3301
+ return signer.sign(privateKeyPem, "base64");
3302
+ }
3303
+
3304
+ // src/facilitators/alipay/encoding.ts
3305
+ init_esm_shims();
3306
+ function base64url(input) {
3307
+ return Buffer.from(input, "utf-8").toString("base64url");
3308
+ }
3309
+ function decodeBase64UrlWithPadFix(input) {
3310
+ const normalized = input.replace(/-/g, "+").replace(/_/g, "/");
3311
+ const padded = normalized + "=".repeat((4 - normalized.length % 4) % 4);
3312
+ return Buffer.from(padded, "base64").toString("utf-8");
3313
+ }
3314
+ function toPem(key, kind) {
3315
+ const trimmed = key.trim();
3316
+ if (trimmed.includes("-----BEGIN")) return trimmed;
3317
+ const label = kind === "PRIVATE" ? "PRIVATE KEY" : "PUBLIC KEY";
3318
+ const body = trimmed.replace(/\s+/g, "").match(/.{1,64}/g)?.join("\n") ?? "";
3319
+ return `-----BEGIN ${label}-----
3320
+ ${body}
3321
+ -----END ${label}-----
3322
+ `;
3323
+ }
3324
+
3325
+ // src/facilitators/alipay/openapi.ts
3326
+ init_esm_shims();
3327
+ function formatAlipayTimestamp(d = /* @__PURE__ */ new Date()) {
3328
+ const pad = (n) => String(n).padStart(2, "0");
3329
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
3330
+ }
3331
+ function buildSigningString(params) {
3332
+ return Object.keys(params).sort().map((k) => `${k}=${params[k]}`).join("&");
3333
+ }
3334
+ function responseWrapperKey(method) {
3335
+ return `${method.replace(/\./g, "_")}_response`;
3336
+ }
3337
+ async function alipayOpenApiCall(method, bizContent, config) {
3338
+ const publicParams = {
3339
+ app_id: config.app_id,
3340
+ method,
3341
+ format: "JSON",
3342
+ charset: "utf-8",
3343
+ sign_type: config.sign_type ?? "RSA2",
3344
+ timestamp: formatAlipayTimestamp(),
3345
+ version: "1.0",
3346
+ biz_content: JSON.stringify(bizContent)
3347
+ };
3348
+ const signingString = buildSigningString(publicParams);
3349
+ const sign = rsa2Sign(signingString, config.private_key_pem);
3350
+ const body = new URLSearchParams({ ...publicParams, sign }).toString();
3351
+ const response = await fetch(config.gateway_url, {
3352
+ method: "POST",
3353
+ headers: {
3354
+ "Content-Type": "application/x-www-form-urlencoded;charset=utf-8"
3355
+ },
3356
+ body
3357
+ });
3358
+ if (!response.ok) {
3359
+ const text = await response.text().catch(() => "<unreadable>");
3360
+ throw new Error(
3361
+ `Alipay Open API HTTP ${response.status} for ${method}: ${text.slice(0, 500)}`
3362
+ );
3363
+ }
3364
+ const json = await response.json();
3365
+ const wrapperKey = responseWrapperKey(method);
3366
+ const business = json[wrapperKey];
3367
+ if (business === void 0 || business === null || typeof business !== "object") {
3368
+ throw new Error(
3369
+ `Alipay Open API response missing "${wrapperKey}" wrapper for ${method}: ${JSON.stringify(json).slice(0, 500)}`
3370
+ );
3371
+ }
3372
+ return business;
3373
+ }
3374
+
3375
+ // src/facilitators/alipay.ts
3376
+ var ALIPAY_NETWORK = "alipay";
3377
+ var ALIPAY_SCHEME = "alipay-aipay";
3378
+ var ALIPAY_GATEWAY_PROD = "https://openapi.alipay.com/gateway.do";
3379
+ var ALIPAY_AMOUNT_REGEX = /^\d+(\.\d{1,2})?$/;
3380
+ var ALIPAY_PAY_BEFORE_MS = 30 * 60 * 1e3;
3381
+ var ALIPAY_SIGNING_FIELDS = [
3382
+ "amount",
3383
+ "currency",
3384
+ "goods_name",
3385
+ "out_trade_no",
3386
+ "pay_before",
3387
+ "resource_id",
3388
+ "seller_id",
3389
+ "service_id"
3390
+ ];
3391
+ var AlipayFacilitator = class extends BaseFacilitator {
3392
+ name = "alipay";
3393
+ displayName = "Alipay AI \u6536";
3394
+ supportedNetworks = [ALIPAY_NETWORK];
3395
+ config;
3396
+ constructor(config) {
3397
+ super();
3398
+ this.config = {
3399
+ gateway_url: ALIPAY_GATEWAY_PROD,
3400
+ sign_type: "RSA2",
3401
+ ...config
3402
+ };
3403
+ }
3404
+ /**
3405
+ * Build the 402 challenge for a service: signs the 8-field payload with
3406
+ * RSA2, packages the nested `{protocol, method}` JSON as Base64URL for
3407
+ * `Payment-Needed`, and emits the parallel x402 `accepts[]` entry.
3408
+ */
3409
+ async createPaymentRequirements(opts) {
3410
+ if (!ALIPAY_AMOUNT_REGEX.test(opts.priceCny)) {
3411
+ throw new Error(
3412
+ `AlipayFacilitator.createPaymentRequirements: priceCny "${opts.priceCny}" does not match /^\\d+(\\.\\d{1,2})?$/ (unit is \u5143, not \u5206; e.g. "1.00" not "100")`
3413
+ );
3414
+ }
3415
+ const now = /* @__PURE__ */ new Date();
3416
+ const outTradeNo = opts.outTradeNo ?? generateOutTradeNo();
3417
+ const payBefore = formatPayBefore(now);
3418
+ const signedFields = {
3419
+ amount: opts.priceCny,
3420
+ currency: "CNY",
3421
+ goods_name: opts.goodsName,
3422
+ out_trade_no: outTradeNo,
3423
+ pay_before: payBefore,
3424
+ resource_id: opts.resourceId,
3425
+ seller_id: this.config.seller_id,
3426
+ service_id: opts.serviceId
3427
+ };
3428
+ const signingString = ALIPAY_SIGNING_FIELDS.map((k) => `${k}=${signedFields[k]}`).join("&");
3429
+ const seller_signature = rsa2Sign(signingString, this.config.private_key_pem);
3430
+ const challenge = {
3431
+ protocol: {
3432
+ out_trade_no: outTradeNo,
3433
+ amount: signedFields.amount,
3434
+ currency: signedFields.currency,
3435
+ resource_id: signedFields.resource_id,
3436
+ pay_before: payBefore,
3437
+ seller_signature,
3438
+ seller_sign_type: this.config.sign_type ?? "RSA2",
3439
+ seller_unique_id: this.config.seller_id
3440
+ },
3441
+ method: {
3442
+ seller_name: this.config.seller_name,
3443
+ seller_id: this.config.seller_id,
3444
+ seller_app_id: this.config.app_id,
3445
+ goods_name: signedFields.goods_name,
3446
+ seller_unique_id_key: "seller_id",
3447
+ service_id: signedFields.service_id
3448
+ }
3449
+ };
3450
+ const paymentNeededHeader = base64url(JSON.stringify(challenge));
3451
+ const x402Accepts = {
3452
+ scheme: ALIPAY_SCHEME,
3453
+ network: ALIPAY_NETWORK,
3454
+ asset: "CNY",
3455
+ amount: opts.priceCny,
3456
+ payTo: this.config.seller_id,
3457
+ maxTimeoutSeconds: ALIPAY_PAY_BEFORE_MS / 1e3,
3458
+ extra: {
3459
+ payment_needed_header: paymentNeededHeader,
3460
+ out_trade_no: outTradeNo,
3461
+ pay_before: payBefore,
3462
+ service_id: signedFields.service_id
3463
+ }
3464
+ };
3465
+ return { x402Accepts, paymentNeededHeader };
3466
+ }
3467
+ /**
3468
+ * Verify a `Payment-Proof` by calling
3469
+ * `alipay.aipay.agent.payment.verify` against the Alipay Open API.
3470
+ *
3471
+ * The proof is conveyed via `paymentPayload.payload`, which may be:
3472
+ * - a raw Base64URL string (the `Payment-Proof` header value), or
3473
+ * - an object `{ paymentProof: string }` / `{ proofHeader: string }`.
3474
+ *
3475
+ * All failure modes (malformed payload, network errors, Alipay
3476
+ * `code != 10000`) return `{ valid: false, error }`. No exception
3477
+ * escapes, regardless of client-supplied input.
3478
+ */
3479
+ async verify(paymentPayload, _requirements) {
3480
+ try {
3481
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3482
+ const decoded = decodeProof(proofHeader);
3483
+ const response = await alipayOpenApiCall(
3484
+ "alipay.aipay.agent.payment.verify",
3485
+ {
3486
+ payment_proof: decoded.protocol.payment_proof,
3487
+ trade_no: decoded.protocol.trade_no,
3488
+ client_session: decoded.method.client_session
3489
+ },
3490
+ this.getOpenApiConfig()
3491
+ );
3492
+ if (response.code !== "10000") {
3493
+ return {
3494
+ valid: false,
3495
+ error: `alipay verify ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3496
+ details: {
3497
+ code: response.code,
3498
+ sub_code: response.sub_code,
3499
+ sub_msg: response.sub_msg
3500
+ }
3501
+ };
3502
+ }
3503
+ return {
3504
+ valid: true,
3505
+ details: {
3506
+ trade_no: response.trade_no ?? decoded.protocol.trade_no,
3507
+ amount: response.amount,
3508
+ out_trade_no: response.out_trade_no,
3509
+ resource_id: response.resource_id,
3510
+ active: response.active
3511
+ }
3512
+ };
3513
+ } catch (e) {
3514
+ return {
3515
+ valid: false,
3516
+ error: e instanceof Error ? e.message : String(e)
3517
+ };
3518
+ }
3519
+ }
3520
+ /**
3521
+ * Settle by calling `alipay.aipay.agent.fulfillment.confirm` after the
3522
+ * service resource has been returned to the buyer.
3523
+ *
3524
+ * Per the design (see ALIPAY-INTEGRATION-DESIGN.md §5.1, risk row
3525
+ * "履约确认失败"), this is **fire-and-forget**: the caller (registry /
3526
+ * server) is expected to log fulfillment failures but NOT roll back
3527
+ * the already-delivered resource.
3528
+ *
3529
+ * Re-decodes `paymentPayload.payload` to extract `trade_no` (verify
3530
+ * does the same; the redundant Base64URL decode is negligible).
3531
+ */
3532
+ async settle(paymentPayload, _requirements) {
3533
+ try {
3534
+ const proofHeader = extractProofHeader(paymentPayload.payload);
3535
+ const decoded = decodeProof(proofHeader);
3536
+ const tradeNo = decoded.protocol.trade_no;
3537
+ const response = await alipayOpenApiCall(
3538
+ "alipay.aipay.agent.fulfillment.confirm",
3539
+ { trade_no: tradeNo },
3540
+ this.getOpenApiConfig()
3541
+ );
3542
+ if (response.code !== "10000") {
3543
+ return {
3544
+ success: false,
3545
+ transaction: tradeNo,
3546
+ error: `alipay fulfillment ${response.code}: ${response.sub_msg ?? response.msg ?? "unknown"}`,
3547
+ status: "fulfillment_failed"
3548
+ };
3549
+ }
3550
+ return {
3551
+ success: true,
3552
+ transaction: tradeNo,
3553
+ status: "fulfilled"
3554
+ };
3555
+ } catch (e) {
3556
+ return {
3557
+ success: false,
3558
+ error: e instanceof Error ? e.message : String(e)
3559
+ };
3560
+ }
3561
+ }
3562
+ /**
3563
+ * Validate that the configured RSA keys parse and that the Open API
3564
+ * gateway is reachable. Does NOT make a real business API call (would
3565
+ * burn quota and could appear as a legitimate verify attempt in logs).
3566
+ */
3567
+ async healthCheck() {
3568
+ const start = Date.now();
3569
+ try {
3570
+ crypto2.createPrivateKey(this.config.private_key_pem);
3571
+ } catch (e) {
3572
+ return {
3573
+ healthy: false,
3574
+ error: `merchant private_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3575
+ };
3576
+ }
3577
+ try {
3578
+ crypto2.createPublicKey(this.config.alipay_public_key_pem);
3579
+ } catch (e) {
3580
+ return {
3581
+ healthy: false,
3582
+ error: `alipay_public_key_pem parse failed: ${e instanceof Error ? e.message : String(e)}`
3583
+ };
3584
+ }
3585
+ const gatewayUrl = this.config.gateway_url ?? ALIPAY_GATEWAY_PROD;
3586
+ const controller = new AbortController();
3587
+ const timeout = setTimeout(() => controller.abort(), 5e3);
3588
+ const response = await fetch(gatewayUrl, {
3589
+ method: "HEAD",
3590
+ signal: controller.signal
3591
+ }).catch(() => null);
3592
+ clearTimeout(timeout);
3593
+ const latencyMs = Date.now() - start;
3594
+ if (!response) {
3595
+ return { healthy: false, error: `gateway unreachable: ${gatewayUrl}`, latencyMs };
3596
+ }
3597
+ return { healthy: true, latencyMs };
3598
+ }
3599
+ /** Bundle the facilitator config into the shape openapi.ts wants. */
3600
+ getOpenApiConfig() {
3601
+ return {
3602
+ gateway_url: this.config.gateway_url ?? ALIPAY_GATEWAY_PROD,
3603
+ app_id: this.config.app_id,
3604
+ private_key_pem: this.config.private_key_pem,
3605
+ alipay_public_key_pem: this.config.alipay_public_key_pem,
3606
+ sign_type: this.config.sign_type
3607
+ };
3608
+ }
3609
+ };
3610
+ function generateOutTradeNo() {
3611
+ return "VID" + crypto2.randomBytes(22).toString("base64url").slice(0, 29);
3612
+ }
3613
+ function formatPayBefore(now) {
3614
+ const expiry = new Date(now.getTime() + ALIPAY_PAY_BEFORE_MS);
3615
+ return expiry.toISOString().replace(/\.\d{3}Z$/, "Z");
3616
+ }
3617
+ function extractProofHeader(payload) {
3618
+ if (typeof payload === "string") {
3619
+ if (payload.length === 0) {
3620
+ throw new Error("alipay Payment-Proof is empty");
3621
+ }
3622
+ return payload;
3623
+ }
3624
+ if (payload !== null && typeof payload === "object") {
3625
+ const obj = payload;
3626
+ const candidate = obj.paymentProof ?? obj.proofHeader;
3627
+ if (typeof candidate === "string" && candidate.length > 0) {
3628
+ return candidate;
3629
+ }
3630
+ }
3631
+ throw new Error(
3632
+ "alipay payment payload must be a Base64URL string or {paymentProof: string} / {proofHeader: string}"
3633
+ );
3634
+ }
3635
+ function decodeProof(proofHeader) {
3636
+ let parsed;
3637
+ try {
3638
+ parsed = JSON.parse(decodeBase64UrlWithPadFix(proofHeader));
3639
+ } catch (e) {
3640
+ throw new Error(
3641
+ `failed to decode Payment-Proof: ${e instanceof Error ? e.message : String(e)}`
3642
+ );
3643
+ }
3644
+ if (parsed === null || typeof parsed !== "object") {
3645
+ throw new Error("decoded Payment-Proof is not an object");
3646
+ }
3647
+ const obj = parsed;
3648
+ const protocol = obj.protocol;
3649
+ const method = obj.method;
3650
+ if (!protocol || typeof protocol.payment_proof !== "string") {
3651
+ throw new Error("decoded Payment-Proof missing protocol.payment_proof");
3652
+ }
3653
+ if (typeof protocol.trade_no !== "string") {
3654
+ throw new Error("decoded Payment-Proof missing protocol.trade_no");
3655
+ }
3656
+ if (!method || typeof method.client_session !== "string") {
3657
+ throw new Error("decoded Payment-Proof missing method.client_session");
3658
+ }
3659
+ return parsed;
3660
+ }
3661
+
2208
3662
  // src/facilitators/registry.ts
2209
3663
  init_esm_shims();
2210
- import { Keypair as Keypair4 } from "@solana/web3.js";
3664
+ import { Keypair as Keypair5 } from "@solana/web3.js";
2211
3665
  import bs582 from "bs58";
2212
3666
  var FacilitatorRegistry = class {
2213
3667
  factories = /* @__PURE__ */ new Map();
@@ -2223,13 +3677,14 @@ var FacilitatorRegistry = class {
2223
3677
  const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2224
3678
  if (feePayerKey) {
2225
3679
  try {
2226
- feePayerKeypair = Keypair4.fromSecretKey(bs582.decode(feePayerKey));
3680
+ feePayerKeypair = Keypair5.fromSecretKey(bs582.decode(feePayerKey));
2227
3681
  } catch (e) {
2228
3682
  console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2229
3683
  }
2230
3684
  }
2231
3685
  return new SolanaFacilitator({ feePayerKeypair });
2232
3686
  });
3687
+ this.registerFactory("alipay", (config) => new AlipayFacilitator(config));
2233
3688
  this.selection = selection || { primary: "cdp", fallback: ["tempo", "bnb", "solana"], strategy: "failover" };
2234
3689
  }
2235
3690
  /**
@@ -2457,6 +3912,11 @@ var PAYMENT_RESPONSE_HEADER = "x-payment-response";
2457
3912
  var MPP_AUTH_HEADER = "authorization";
2458
3913
  var MPP_WWW_AUTH_HEADER = "www-authenticate";
2459
3914
  var MPP_RECEIPT_HEADER = "payment-receipt";
3915
+ var ALIPAY_PAYMENT_NEEDED_HEADER = "payment-needed";
3916
+ var ALIPAY_PAYMENT_PROOF_HEADER = "payment-proof";
3917
+ function headerSafe(value) {
3918
+ return String(value ?? "").replace(/[^\x20-\x7E]/g, (ch) => encodeURIComponent(ch)).replace(/"/g, "%22");
3919
+ }
2460
3920
  var TOKEN_ADDRESSES = {
2461
3921
  "eip155:8453": {
2462
3922
  USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
@@ -2498,7 +3958,7 @@ var TOKEN_ADDRESSES = {
2498
3958
  // Devnet USDC
2499
3959
  }
2500
3960
  };
2501
- var CHAIN_TO_NETWORK = {
3961
+ var CHAIN_TO_NETWORK2 = {
2502
3962
  "base": "eip155:8453",
2503
3963
  "base_sepolia": "eip155:84532",
2504
3964
  "polygon": "eip155:137",
@@ -2529,9 +3989,13 @@ var TOKEN_DOMAINS = {
2529
3989
  USDT: { name: "(PoS) Tether USD", version: "2" }
2530
3990
  },
2531
3991
  // Tempo Moderato testnet - TIP-20 stablecoins
3992
+ // Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
3993
+ // See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
3994
+ // All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
3995
+ // the token symbol with first letter capitalized + version "1".
2532
3996
  "eip155:42431": {
2533
- USDC: { name: "pathUSD", version: "1" },
2534
- USDT: { name: "alphaUSD", version: "1" }
3997
+ USDC: { name: "PathUSD", version: "1" },
3998
+ USDT: { name: "AlphaUSD", version: "1" }
2535
3999
  },
2536
4000
  // BNB Smart Chain mainnet
2537
4001
  "eip155:56": {
@@ -2588,6 +4052,8 @@ var MoltsPayServer = class {
2588
4052
  registry;
2589
4053
  networkId;
2590
4054
  useMainnet;
4055
+ /** Alipay AI 收 facilitator instance, set when `provider.alipay` is configured (2.0.0). */
4056
+ alipayFacilitator = null;
2591
4057
  constructor(servicesPath, options = {}) {
2592
4058
  loadEnvFile2();
2593
4059
  const content = readFileSync4(servicesPath, "utf-8");
@@ -2609,7 +4075,38 @@ var MoltsPayServer = class {
2609
4075
  cdp: { useMainnet: this.useMainnet }
2610
4076
  }
2611
4077
  };
4078
+ const providerAlipay = this.manifest.provider.alipay;
4079
+ if (providerAlipay) {
4080
+ try {
4081
+ const baseDir = path3.dirname(servicesPath);
4082
+ const resolvePem = (p, kind) => toPem(readFileSync4(path3.isAbsolute(p) ? p : path3.resolve(baseDir, p), "utf-8"), kind);
4083
+ const alipayFacilitatorConfig = {
4084
+ seller_id: providerAlipay.seller_id,
4085
+ app_id: providerAlipay.app_id,
4086
+ seller_name: providerAlipay.seller_name,
4087
+ service_id_default: providerAlipay.service_id_default,
4088
+ private_key_pem: resolvePem(providerAlipay.private_key_path, "PRIVATE"),
4089
+ alipay_public_key_pem: resolvePem(providerAlipay.alipay_public_key_path, "PUBLIC"),
4090
+ gateway_url: providerAlipay.gateway_url,
4091
+ sign_type: providerAlipay.sign_type
4092
+ };
4093
+ facilitatorConfig.config = {
4094
+ ...facilitatorConfig.config,
4095
+ alipay: alipayFacilitatorConfig
4096
+ };
4097
+ facilitatorConfig.fallback = facilitatorConfig.fallback || [];
4098
+ if (facilitatorConfig.primary !== "alipay" && !facilitatorConfig.fallback.includes("alipay")) {
4099
+ facilitatorConfig.fallback.push("alipay");
4100
+ }
4101
+ } catch (err) {
4102
+ throw new Error(`[MoltsPay] Alipay rail configured but key load failed: ${err.message}`);
4103
+ }
4104
+ }
2612
4105
  this.registry = new FacilitatorRegistry(facilitatorConfig);
4106
+ if (providerAlipay) {
4107
+ this.alipayFacilitator = this.registry.get("alipay");
4108
+ console.log(`[MoltsPay] Alipay AI \u6536 rail enabled (seller ${providerAlipay.seller_id})`);
4109
+ }
2613
4110
  const primaryFacilitator = this.registry.get(facilitatorConfig.primary);
2614
4111
  console.log(`[MoltsPay] Loaded ${this.manifest.services.length} services from ${servicesPath}`);
2615
4112
  console.log(`[MoltsPay] Provider: ${this.manifest.provider.name}`);
@@ -2654,14 +4151,14 @@ var MoltsPayServer = class {
2654
4151
  const chainName = typeof c === "string" ? c : c.chain;
2655
4152
  const explicitWallet = typeof c === "object" ? c.wallet : null;
2656
4153
  return {
2657
- network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
4154
+ network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
2658
4155
  wallet: getWalletForChain(chainName, explicitWallet || void 0),
2659
4156
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
2660
4157
  };
2661
4158
  });
2662
4159
  }
2663
4160
  const chain = provider.chain || "base";
2664
- const network = CHAIN_TO_NETWORK[chain] || this.networkId;
4161
+ const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
2665
4162
  return [{
2666
4163
  network,
2667
4164
  wallet: getWalletForChain(chain),
@@ -2699,14 +4196,63 @@ var MoltsPayServer = class {
2699
4196
  console.log(` GET /health - Health check (incl. facilitators)`);
2700
4197
  });
2701
4198
  }
4199
+ /**
4200
+ * Apply CORS response headers according to the `cors` option.
4201
+ *
4202
+ * Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
4203
+ * and works for every browser client whose origin does not need to send cookies.
4204
+ *
4205
+ * `cors: false`: emit no CORS headers. Same-origin only.
4206
+ * `cors: string[]`: origin allowlist — echo the origin back iff it matches.
4207
+ * `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
4208
+ *
4209
+ * The required-for-Web response headers are always exposed when CORS is active:
4210
+ * `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
4211
+ */
4212
+ applyCorsHeaders(req, res) {
4213
+ const cors = this.options.cors;
4214
+ if (cors === false) {
4215
+ return;
4216
+ }
4217
+ const requestOrigin = req.headers.origin ?? "*";
4218
+ if (cors === void 0 || cors === true) {
4219
+ this.writeCorsHeaders(res, "*");
4220
+ return;
4221
+ }
4222
+ if (Array.isArray(cors)) {
4223
+ if (cors.includes(requestOrigin)) {
4224
+ this.writeCorsHeaders(res, requestOrigin);
4225
+ res.setHeader("Vary", "Origin");
4226
+ }
4227
+ return;
4228
+ }
4229
+ const opt = cors;
4230
+ const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
4231
+ if (!isAllowed) {
4232
+ return;
4233
+ }
4234
+ this.writeCorsHeaders(res, requestOrigin);
4235
+ res.setHeader("Vary", "Origin");
4236
+ if (opt.credentials) {
4237
+ res.setHeader("Access-Control-Allow-Credentials", "true");
4238
+ }
4239
+ const maxAge = opt.maxAge ?? 600;
4240
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
4241
+ }
4242
+ writeCorsHeaders(res, origin) {
4243
+ res.setHeader("Access-Control-Allow-Origin", origin);
4244
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
4245
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization, Payment-Proof");
4246
+ res.setHeader(
4247
+ "Access-Control-Expose-Headers",
4248
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt, Payment-Needed"
4249
+ );
4250
+ }
2702
4251
  /**
2703
4252
  * Handle incoming request
2704
4253
  */
2705
4254
  async handleRequest(req, res) {
2706
- res.setHeader("Access-Control-Allow-Origin", "*");
2707
- res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2708
- res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2709
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
4255
+ this.applyCorsHeaders(req, res);
2710
4256
  if (req.method === "OPTIONS") {
2711
4257
  res.writeHead(204);
2712
4258
  res.end();
@@ -2726,7 +4272,8 @@ var MoltsPayServer = class {
2726
4272
  if (url.pathname === "/execute" && req.method === "POST") {
2727
4273
  const body = await this.readBody(req);
2728
4274
  const paymentHeader = req.headers[PAYMENT_HEADER2];
2729
- return await this.handleExecute(body, paymentHeader, res);
4275
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4276
+ return await this.handleExecute(body, paymentHeader, res, proofHeader);
2730
4277
  }
2731
4278
  if (url.pathname === "/proxy" && req.method === "POST") {
2732
4279
  const clientIP = req.headers["x-real-ip"] || req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.socket.remoteAddress || "";
@@ -2744,7 +4291,8 @@ var MoltsPayServer = class {
2744
4291
  const body = req.method === "POST" ? await this.readBody(req) : {};
2745
4292
  const authHeader = req.headers[MPP_AUTH_HEADER];
2746
4293
  const x402Header = req.headers[PAYMENT_HEADER2];
2747
- return await this.handleMPPRequest(skill, body, authHeader, x402Header, res);
4294
+ const proofHeader = req.headers[ALIPAY_PAYMENT_PROOF_HEADER];
4295
+ return await this.handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader);
2748
4296
  }
2749
4297
  this.sendJson(res, 404, { error: "Not found" });
2750
4298
  } catch (err) {
@@ -2841,7 +4389,7 @@ var MoltsPayServer = class {
2841
4389
  /**
2842
4390
  * POST /execute - Execute service with x402 payment
2843
4391
  */
2844
- async handleExecute(body, paymentHeader, res) {
4392
+ async handleExecute(body, paymentHeader, res, proofHeader) {
2845
4393
  const { service, params } = body;
2846
4394
  if (!service) {
2847
4395
  return this.sendJson(res, 400, { error: "Missing service" });
@@ -2855,6 +4403,15 @@ var MoltsPayServer = class {
2855
4403
  return this.sendJson(res, 400, { error: `Missing required param: ${key}` });
2856
4404
  }
2857
4405
  }
4406
+ if (proofHeader) {
4407
+ const alipayPayment = {
4408
+ x402Version: X402_VERSION3,
4409
+ scheme: ALIPAY_SCHEME,
4410
+ network: ALIPAY_NETWORK,
4411
+ payload: proofHeader
4412
+ };
4413
+ return this.handleAlipayExecute(skill, params || {}, alipayPayment, res);
4414
+ }
2858
4415
  if (!paymentHeader) {
2859
4416
  return this.sendPaymentRequired(skill.config, res);
2860
4417
  }
@@ -2865,6 +4422,11 @@ var MoltsPayServer = class {
2865
4422
  } catch {
2866
4423
  return this.sendJson(res, 400, { error: "Invalid X-Payment header" });
2867
4424
  }
4425
+ const payScheme = payment.accepted?.scheme || payment.scheme;
4426
+ const payNetwork = payment.accepted?.network || payment.network;
4427
+ if (payScheme === ALIPAY_SCHEME || (payNetwork ? isAlipayChainId(payNetwork) : false)) {
4428
+ return this.handleAlipayExecute(skill, params || {}, payment, res);
4429
+ }
2868
4430
  const validation = this.validatePayment(payment, skill.config);
2869
4431
  if (!validation.valid) {
2870
4432
  return this.sendJson(res, 402, { error: validation.error });
@@ -2929,6 +4491,14 @@ var MoltsPayServer = class {
2929
4491
  console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2930
4492
  } catch (err) {
2931
4493
  console.error("[MoltsPay] Settlement failed:", err.message);
4494
+ settlement = { success: false, error: err.message, facilitator: "none" };
4495
+ }
4496
+ if (!settlement?.success) {
4497
+ return this.sendJson(res, 402, {
4498
+ error: "Payment settlement failed",
4499
+ message: settlement?.error || "Settlement returned no success state",
4500
+ facilitator: settlement?.facilitator
4501
+ });
2932
4502
  }
2933
4503
  }
2934
4504
  const responseHeaders = {};
@@ -2949,13 +4519,111 @@ var MoltsPayServer = class {
2949
4519
  payment: settlement?.success ? { transaction: settlement.transaction, status: "settled", facilitator: settlement.facilitator } : { status: "pending" }
2950
4520
  }, responseHeaders);
2951
4521
  }
4522
+ /**
4523
+ * Execute a service paid via the Alipay AI 收 fiat rail (2.0.0).
4524
+ *
4525
+ * Differs from the EVM/SVM path: no token detection, no EIP-3009/permit
4526
+ * validation. Verify hits the Alipay Open API (`payment.verify`). Settlement
4527
+ * (`fulfillment.confirm`) is FIRE-AND-FORGET per ALIPAY-INTEGRATION-DESIGN
4528
+ * §5.1: a confirm failure is logged but does NOT fail the already-delivered
4529
+ * response (the buyer's payment proof was already verified).
4530
+ */
4531
+ async handleAlipayExecute(skill, params, payment, res) {
4532
+ if (!this.alipayFacilitator) {
4533
+ return this.sendJson(res, 402, { error: "Alipay rail not configured on this server" });
4534
+ }
4535
+ const requirements = {
4536
+ scheme: ALIPAY_SCHEME,
4537
+ network: ALIPAY_NETWORK,
4538
+ asset: "CNY",
4539
+ amount: skill.config.alipay?.price_cny || "0",
4540
+ payTo: this.manifest.provider.alipay?.seller_id || "",
4541
+ maxTimeoutSeconds: 1800
4542
+ };
4543
+ console.log(`[MoltsPay] Verifying Alipay payment...`);
4544
+ const verifyResult = await this.registry.verify(payment, requirements);
4545
+ if (!verifyResult.valid) {
4546
+ return this.sendJson(res, 402, {
4547
+ error: `Payment verification failed: ${verifyResult.error}`,
4548
+ facilitator: verifyResult.facilitator
4549
+ });
4550
+ }
4551
+ console.log(`[MoltsPay] Alipay payment verified by ${verifyResult.facilitator}`);
4552
+ const timeoutSeconds = parseInt(process.env.SKILL_TIMEOUT_SECONDS || "1200");
4553
+ console.log(`[MoltsPay] Executing skill: ${skill.id} (timeout: ${timeoutSeconds}s)`);
4554
+ let result;
4555
+ try {
4556
+ result = await Promise.race([
4557
+ skill.handler(params),
4558
+ new Promise(
4559
+ (_, reject) => setTimeout(() => reject(new Error(`Skill timeout after ${timeoutSeconds}s`)), timeoutSeconds * 1e3)
4560
+ )
4561
+ ]);
4562
+ } catch (err) {
4563
+ console.error("[MoltsPay] Skill execution failed:", err.message);
4564
+ return this.sendJson(res, 500, {
4565
+ error: "Service execution failed",
4566
+ message: err.message
4567
+ });
4568
+ }
4569
+ let settlement;
4570
+ try {
4571
+ settlement = await this.registry.settle(payment, requirements);
4572
+ if (settlement.success) {
4573
+ console.log(`[MoltsPay] Alipay fulfillment confirmed: ${settlement.transaction}`);
4574
+ } else {
4575
+ console.error(`[MoltsPay] Alipay fulfillment confirm failed (non-fatal): ${settlement.error}`);
4576
+ }
4577
+ } catch (err) {
4578
+ console.error(`[MoltsPay] Alipay fulfillment confirm threw (non-fatal): ${err.message}`);
4579
+ settlement = { success: false, error: err.message, facilitator: "alipay" };
4580
+ }
4581
+ const responseHeaders = {};
4582
+ if (settlement.success) {
4583
+ responseHeaders[PAYMENT_RESPONSE_HEADER] = Buffer.from(JSON.stringify({
4584
+ success: true,
4585
+ transaction: settlement.transaction,
4586
+ network: ALIPAY_NETWORK,
4587
+ facilitator: settlement.facilitator
4588
+ })).toString("base64");
4589
+ }
4590
+ this.sendJson(res, 200, {
4591
+ success: true,
4592
+ result,
4593
+ payment: settlement.success ? { transaction: settlement.transaction, status: "fulfilled", facilitator: settlement.facilitator } : { status: "delivered_unconfirmed", error: settlement.error }
4594
+ }, responseHeaders);
4595
+ }
4596
+ /**
4597
+ * Build the Alipay 402 challenge for a service, or null when the alipay rail
4598
+ * isn't configured for this server or this service. Returns the x402
4599
+ * `accepts[]` entry plus the Base64URL `Payment-Needed` header value so the
4600
+ * 402 responders can dual-emit both the x402 and legacy alipay-bot formats.
4601
+ */
4602
+ async buildAlipayChallenge(config) {
4603
+ if (!this.alipayFacilitator || !config.alipay) return null;
4604
+ try {
4605
+ const req = await this.alipayFacilitator.createPaymentRequirements({
4606
+ serviceId: config.alipay.service_id || this.manifest.provider.alipay.service_id_default,
4607
+ priceCny: config.alipay.price_cny,
4608
+ goodsName: config.alipay.goods_name,
4609
+ resourceId: `/execute?service=${config.id}`
4610
+ });
4611
+ return { accepts: req.x402Accepts, paymentNeededHeader: req.paymentNeededHeader };
4612
+ } catch (err) {
4613
+ console.error(`[MoltsPay] Alipay challenge build failed for ${config.id}: ${err.message}`);
4614
+ return null;
4615
+ }
4616
+ }
2952
4617
  /**
2953
4618
  * Handle MPP (Machine Payments Protocol) request
2954
4619
  * Supports both x402 and MPP protocols on service endpoints
2955
4620
  */
2956
- async handleMPPRequest(skill, body, authHeader, x402Header, res) {
4621
+ async handleMPPRequest(skill, body, authHeader, x402Header, res, proofHeader) {
2957
4622
  const config = skill.config;
2958
4623
  const params = body || {};
4624
+ if (proofHeader) {
4625
+ return await this.handleExecute({ service: config.id, params }, void 0, res, proofHeader);
4626
+ }
2959
4627
  if (x402Header) {
2960
4628
  return await this.handleExecute({ service: config.id, params }, x402Header, res);
2961
4629
  }
@@ -3061,7 +4729,7 @@ var MoltsPayServer = class {
3061
4729
  /**
3062
4730
  * Return 402 with both x402 and MPP payment requirements
3063
4731
  */
3064
- sendMPPPaymentRequired(config, res) {
4732
+ async sendMPPPaymentRequired(config, res) {
3065
4733
  const acceptedTokens = getAcceptedCurrencies(config);
3066
4734
  const providerChains = this.getProviderChains();
3067
4735
  const accepts = [];
@@ -3072,6 +4740,10 @@ var MoltsPayServer = class {
3072
4740
  }
3073
4741
  }
3074
4742
  }
4743
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4744
+ if (alipayChallenge) {
4745
+ accepts.push(alipayChallenge.accepts);
4746
+ }
3075
4747
  const x402PaymentRequired = {
3076
4748
  x402Version: X402_VERSION3,
3077
4749
  accepts,
@@ -3099,7 +4771,7 @@ var MoltsPayServer = class {
3099
4771
  };
3100
4772
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3101
4773
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3102
- mppWwwAuth = `Payment id="${challengeId}", realm="${this.manifest.provider.name}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
4774
+ mppWwwAuth = `Payment id="${challengeId}", realm="${headerSafe(this.manifest.provider.name)}", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3103
4775
  }
3104
4776
  const headers = {
3105
4777
  "Content-Type": "application/problem+json",
@@ -3108,6 +4780,9 @@ var MoltsPayServer = class {
3108
4780
  if (mppWwwAuth) {
3109
4781
  headers[MPP_WWW_AUTH_HEADER] = mppWwwAuth;
3110
4782
  }
4783
+ if (alipayChallenge) {
4784
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4785
+ }
3111
4786
  res.writeHead(402, headers);
3112
4787
  res.end(JSON.stringify({
3113
4788
  type: "https://paymentauth.org/problems/payment-required",
@@ -3134,7 +4809,7 @@ var MoltsPayServer = class {
3134
4809
  * Return 402 with x402 payment requirements (v2 format)
3135
4810
  * Includes requirements for all chains and all accepted currencies
3136
4811
  */
3137
- sendPaymentRequired(config, res) {
4812
+ async sendPaymentRequired(config, res) {
3138
4813
  const acceptedTokens = getAcceptedCurrencies(config);
3139
4814
  const providerChains = this.getProviderChains();
3140
4815
  const accepts = [];
@@ -3145,6 +4820,10 @@ var MoltsPayServer = class {
3145
4820
  }
3146
4821
  }
3147
4822
  }
4823
+ const alipayChallenge = await this.buildAlipayChallenge(config);
4824
+ if (alipayChallenge) {
4825
+ accepts.push(alipayChallenge.accepts);
4826
+ }
3148
4827
  const acceptedChains = providerChains.map((c) => {
3149
4828
  if (c.network === "eip155:8453") return "base";
3150
4829
  if (c.network === "eip155:137") return "polygon";
@@ -3162,10 +4841,14 @@ var MoltsPayServer = class {
3162
4841
  }
3163
4842
  };
3164
4843
  const encoded = Buffer.from(JSON.stringify(paymentRequired)).toString("base64");
3165
- res.writeHead(402, {
4844
+ const headers = {
3166
4845
  "Content-Type": "application/json",
3167
4846
  [PAYMENT_REQUIRED_HEADER2]: encoded
3168
- });
4847
+ };
4848
+ if (alipayChallenge) {
4849
+ headers[ALIPAY_PAYMENT_NEEDED_HEADER] = alipayChallenge.paymentNeededHeader;
4850
+ }
4851
+ res.writeHead(402, headers);
3169
4852
  res.end(JSON.stringify({
3170
4853
  error: "Payment required",
3171
4854
  message: `Service requires $${config.price} ${config.currency}`,
@@ -3183,7 +4866,7 @@ var MoltsPayServer = class {
3183
4866
  }
3184
4867
  const scheme = payment.accepted?.scheme || payment.scheme;
3185
4868
  const network = payment.accepted?.network || payment.network || this.networkId;
3186
- if (scheme !== "exact") {
4869
+ if (scheme !== "exact" && scheme !== "permit") {
3187
4870
  return { valid: false, error: `Unsupported scheme: ${scheme}` };
3188
4871
  }
3189
4872
  if (!this.isNetworkAccepted(network)) {
@@ -3205,8 +4888,10 @@ var MoltsPayServer = class {
3205
4888
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
3206
4889
  const tokenAddress = tokenAddresses[selectedToken];
3207
4890
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
4891
+ const isTempo = selectedNetwork === "eip155:42431";
4892
+ const scheme = isTempo ? "permit" : "exact";
3208
4893
  const requirements = {
3209
- scheme: "exact",
4894
+ scheme,
3210
4895
  network: selectedNetwork,
3211
4896
  asset: tokenAddress,
3212
4897
  amount: amountInUnits,
@@ -3234,6 +4919,16 @@ var MoltsPayServer = class {
3234
4919
  };
3235
4920
  }
3236
4921
  }
4922
+ if (isTempo) {
4923
+ const tempoFacilitator = this.registry.get("tempo");
4924
+ const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
4925
+ if (tempoSpender) {
4926
+ requirements.extra = {
4927
+ ...requirements.extra || {},
4928
+ tempoSpender
4929
+ };
4930
+ }
4931
+ }
3237
4932
  return requirements;
3238
4933
  }
3239
4934
  /**
@@ -3260,12 +4955,12 @@ var MoltsPayServer = class {
3260
4955
  return accepted.includes(token);
3261
4956
  }
3262
4957
  async readBody(req) {
3263
- return new Promise((resolve2, reject) => {
4958
+ return new Promise((resolve3, reject) => {
3264
4959
  let body = "";
3265
4960
  req.on("data", (chunk) => body += chunk);
3266
4961
  req.on("end", () => {
3267
4962
  try {
3268
- resolve2(body ? JSON.parse(body) : {});
4963
+ resolve3(body ? JSON.parse(body) : {});
3269
4964
  } catch {
3270
4965
  reject(new Error("Invalid JSON"));
3271
4966
  }
@@ -3368,10 +5063,10 @@ var MoltsPayServer = class {
3368
5063
  }
3369
5064
  const scheme = payment.accepted?.scheme || payment.scheme;
3370
5065
  const network = payment.accepted?.network || payment.network;
3371
- if (scheme !== "exact") {
5066
+ if (scheme !== "exact" && scheme !== "permit") {
3372
5067
  return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3373
5068
  }
3374
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
5069
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3375
5070
  if (network !== expectedNetwork) {
3376
5071
  return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3377
5072
  }
@@ -3523,7 +5218,7 @@ var MoltsPayServer = class {
3523
5218
  };
3524
5219
  const mppRequestEncoded = Buffer.from(JSON.stringify(mppRequest)).toString("base64");
3525
5220
  const expiresAt = new Date(Date.now() + 5 * 60 * 1e3).toISOString();
3526
- const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${config.name}", expires="${expiresAt}"`;
5221
+ const wwwAuth = `Payment id="${challengeId}", realm="MoltsPay Proxy", method="tempo", intent="charge", request="${mppRequestEncoded}", description="${headerSafe(config.name)}", expires="${expiresAt}"`;
3527
5222
  res.writeHead(402, {
3528
5223
  "Content-Type": "application/problem+json",
3529
5224
  [MPP_WWW_AUTH_HEADER]: wwwAuth
@@ -3633,7 +5328,7 @@ var MoltsPayServer = class {
3633
5328
  buildProxyPaymentRequirements(config, wallet, token, chain) {
3634
5329
  const amountInUnits = Math.floor(config.price * 1e6).toString();
3635
5330
  const acceptedTokens = getAcceptedCurrencies(config);
3636
- const networkId = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
5331
+ const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3637
5332
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
3638
5333
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
3639
5334
  const tokenAddress = tokenAddresses[selectedToken];
@@ -3693,10 +5388,10 @@ init_esm_shims();
3693
5388
  async function printQRCode(url) {
3694
5389
  const qrcodeModule = await import("qrcode-terminal");
3695
5390
  const qrcode = qrcodeModule.default || qrcodeModule;
3696
- return new Promise((resolve2) => {
5391
+ return new Promise((resolve3) => {
3697
5392
  qrcode.generate(url, { small: true }, (qr) => {
3698
5393
  console.log(qr);
3699
- resolve2();
5394
+ resolve3();
3700
5395
  });
3701
5396
  });
3702
5397
  }
@@ -3708,9 +5403,9 @@ if (!globalThis.crypto) {
3708
5403
  }
3709
5404
  function getVersion() {
3710
5405
  const locations = [
3711
- join5(__dirname, "../../package.json"),
3712
- join5(__dirname, "../package.json"),
3713
- join5(process.cwd(), "node_modules/moltspay/package.json")
5406
+ join6(__dirname, "../../package.json"),
5407
+ join6(__dirname, "../package.json"),
5408
+ join6(process.cwd(), "node_modules/moltspay/package.json")
3714
5409
  ];
3715
5410
  for (const loc of locations) {
3716
5411
  try {
@@ -3731,7 +5426,7 @@ var ERC20_APPROVE_ABI = [
3731
5426
  ];
3732
5427
  async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3733
5428
  const chainConfig = CHAINS[chain];
3734
- const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
5429
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
3735
5430
  const wallet = client.getWallet();
3736
5431
  if (!wallet) {
3737
5432
  console.log(" \u274C No wallet found");
@@ -3740,15 +5435,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3740
5435
  const signer = wallet.connect(provider);
3741
5436
  console.log(` Spender: ${spenderAddress}`);
3742
5437
  let bnbBalance = await provider.getBalance(wallet.address);
3743
- const minGasRequired = ethers2.parseEther("0.0005");
5438
+ const minGasRequired = ethers4.parseEther("0.0005");
3744
5439
  if (bnbBalance < minGasRequired) {
3745
5440
  if (sponsorGas && BNB_SPONSOR_KEY) {
3746
5441
  console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3747
5442
  try {
3748
- const sponsorWallet = new ethers2.Wallet(BNB_SPONSOR_KEY, provider);
5443
+ const sponsorWallet = new ethers4.Wallet(BNB_SPONSOR_KEY, provider);
3749
5444
  const tx = await sponsorWallet.sendTransaction({
3750
5445
  to: wallet.address,
3751
- value: ethers2.parseEther("0.001")
5446
+ value: ethers4.parseEther("0.001")
3752
5447
  });
3753
5448
  await tx.wait();
3754
5449
  console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
@@ -3767,7 +5462,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3767
5462
  }
3768
5463
  for (const tokenSymbol of ["USDT", "USDC"]) {
3769
5464
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3770
- const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
5465
+ const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3771
5466
  const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3772
5467
  if (allowance > 0n) {
3773
5468
  console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
@@ -3775,7 +5470,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3775
5470
  }
3776
5471
  console.log(` \u23F3 Approving ${tokenSymbol}...`);
3777
5472
  try {
3778
- const tx = await tokenContract.approve(spenderAddress, ethers2.MaxUint256);
5473
+ const tx = await tokenContract.approve(spenderAddress, ethers4.MaxUint256);
3779
5474
  await tx.wait();
3780
5475
  console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3781
5476
  } catch (err) {
@@ -3786,10 +5481,10 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3786
5481
  }
3787
5482
  async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3788
5483
  const chainConfig = CHAINS[chain];
3789
- const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
5484
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
3790
5485
  let spenderAddress = null;
3791
5486
  try {
3792
- const walletPath = join5(configDir, "wallet.json");
5487
+ const walletPath = join6(configDir, "wallet.json");
3793
5488
  const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
3794
5489
  spenderAddress = walletData.approvals?.[chain] || null;
3795
5490
  } catch {
@@ -3800,15 +5495,15 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
3800
5495
  }
3801
5496
  for (const tokenSymbol of ["USDT", "USDC"]) {
3802
5497
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3803
- const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
5498
+ const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3804
5499
  const allowance = await tokenContract.allowance(address, spenderAddress);
3805
5500
  result[tokenSymbol.toLowerCase()] = allowance > 0n;
3806
5501
  }
3807
5502
  return result;
3808
5503
  }
3809
5504
  var program = new Command();
3810
- var DEFAULT_CONFIG_DIR2 = join5(homedir3(), ".moltspay");
3811
- var PID_FILE = join5(DEFAULT_CONFIG_DIR2, "server.pid");
5505
+ var DEFAULT_CONFIG_DIR2 = join6(homedir4(), ".moltspay");
5506
+ var PID_FILE = join6(DEFAULT_CONFIG_DIR2, "server.pid");
3812
5507
  if (!existsSync5(DEFAULT_CONFIG_DIR2)) {
3813
5508
  mkdirSync3(DEFAULT_CONFIG_DIR2, { recursive: true });
3814
5509
  }
@@ -3817,10 +5512,10 @@ function prompt(question) {
3817
5512
  input: process.stdin,
3818
5513
  output: process.stdout
3819
5514
  });
3820
- return new Promise((resolve2) => {
5515
+ return new Promise((resolve3) => {
3821
5516
  rl.question(question, (answer) => {
3822
5517
  rl.close();
3823
- resolve2(answer.trim());
5518
+ resolve3(answer.trim());
3824
5519
  });
3825
5520
  });
3826
5521
  }
@@ -3848,7 +5543,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3848
5543
  console.log(`
3849
5544
  \u2705 Solana wallet created: ${address}`);
3850
5545
  console.log(`
3851
- \u{1F4C1} Config saved to: ${join5(options.configDir, "wallet-solana.json")}`);
5546
+ \u{1F4C1} Config saved to: ${join6(options.configDir, "wallet-solana.json")}`);
3852
5547
  console.log(`
3853
5548
  \u26A0\uFE0F IMPORTANT: Back up your wallet file!`);
3854
5549
  console.log(` This file contains your private key!
@@ -3863,7 +5558,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3863
5558
  return;
3864
5559
  }
3865
5560
  console.log("\n\u{1F510} MoltsPay Client Setup\n");
3866
- if (existsSync5(join5(options.configDir, "wallet.json"))) {
5561
+ if (existsSync5(join6(options.configDir, "wallet.json"))) {
3867
5562
  console.log('\u26A0\uFE0F EVM wallet already initialized. Use "moltspay config" to update settings.');
3868
5563
  console.log(` Config dir: ${options.configDir}`);
3869
5564
  return;
@@ -3889,7 +5584,7 @@ program.command("init").description("Initialize MoltsPay client (create wallet,
3889
5584
  console.log(`
3890
5585
  \u{1F4C1} Config saved to: ${result.configDir}`);
3891
5586
  console.log(`
3892
- \u26A0\uFE0F IMPORTANT: Back up ${join5(result.configDir, "wallet.json")}`);
5587
+ \u26A0\uFE0F IMPORTANT: Back up ${join6(result.configDir, "wallet.json")}`);
3893
5588
  console.log(` This file contains your private key!
3894
5589
  `);
3895
5590
  if (chain === "bnb" || chain === "bnb_testnet") {
@@ -4054,7 +5749,7 @@ program.command("approve").description("Approve a spender address for BNB chain
4054
5749
  \u{1F510} Approving spender for ${chain}...
4055
5750
  `);
4056
5751
  await setupBNBApprovals(client, chain, options.spender, false);
4057
- const walletPath = join5(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
5752
+ const walletPath = join6(options.configDir || DEFAULT_CONFIG_DIR2, "wallet.json");
4058
5753
  try {
4059
5754
  const walletData = JSON.parse(readFileSync5(walletPath, "utf-8"));
4060
5755
  walletData.approvals = walletData.approvals || {};
@@ -4691,17 +6386,17 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4691
6386
  const handlers = /* @__PURE__ */ new Map();
4692
6387
  let provider = null;
4693
6388
  for (const inputPath of allPaths) {
4694
- const resolvedPath = resolve(inputPath);
6389
+ const resolvedPath = resolve2(inputPath);
4695
6390
  let manifestPath;
4696
6391
  let skillDir;
4697
6392
  let isSkillDir = false;
4698
- if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
4699
- manifestPath = join5(resolvedPath, "moltspay.services.json");
6393
+ if (existsSync5(join6(resolvedPath, "moltspay.services.json"))) {
6394
+ manifestPath = join6(resolvedPath, "moltspay.services.json");
4700
6395
  skillDir = resolvedPath;
4701
6396
  isSkillDir = true;
4702
6397
  } else if (existsSync5(resolvedPath) && resolvedPath.endsWith(".json")) {
4703
6398
  manifestPath = resolvedPath;
4704
- skillDir = dirname(resolvedPath);
6399
+ skillDir = dirname2(resolvedPath);
4705
6400
  } else if (existsSync5(resolvedPath)) {
4706
6401
  console.error(`\u274C No moltspay.services.json found in: ${resolvedPath}`);
4707
6402
  continue;
@@ -4718,7 +6413,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4718
6413
  let skillModule = null;
4719
6414
  if (isSkillDir) {
4720
6415
  let entryPoint = "index.js";
4721
- const pkgJsonPath = join5(skillDir, "package.json");
6416
+ const pkgJsonPath = join6(skillDir, "package.json");
4722
6417
  if (existsSync5(pkgJsonPath)) {
4723
6418
  try {
4724
6419
  const pkgJson = JSON.parse(readFileSync5(pkgJsonPath, "utf-8"));
@@ -4728,7 +6423,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4728
6423
  } catch {
4729
6424
  }
4730
6425
  }
4731
- const modulePath = join5(skillDir, entryPoint);
6426
+ const modulePath = join6(skillDir, entryPoint);
4732
6427
  if (existsSync5(modulePath)) {
4733
6428
  try {
4734
6429
  skillModule = await import(modulePath);
@@ -4754,7 +6449,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4754
6449
  const workdir = skillDir;
4755
6450
  handlers.set(service.id, async (params) => {
4756
6451
  return new Promise((resolvePromise, reject) => {
4757
- const proc = spawn("sh", ["-c", service.command], {
6452
+ const proc = spawn2("sh", ["-c", service.command], {
4758
6453
  cwd: workdir,
4759
6454
  stdio: ["pipe", "pipe", "pipe"]
4760
6455
  });
@@ -4807,7 +6502,7 @@ program.command("start <paths...>").description("Start MoltsPay server from skil
4807
6502
  provider,
4808
6503
  services: allServices
4809
6504
  };
4810
- const tempManifestPath = join5(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
6505
+ const tempManifestPath = join6(DEFAULT_CONFIG_DIR2, "combined-manifest.json");
4811
6506
  writeFileSync3(tempManifestPath, JSON.stringify(combinedManifest, null, 2));
4812
6507
  console.log(`
4813
6508
  \u{1F4CB} Combined manifest: ${allServices.length} services`);
@@ -4870,7 +6565,7 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
4870
6565
  }
4871
6566
  process.kill(pid, "SIGTERM");
4872
6567
  console.log("\u2705 Sent SIGTERM to server");
4873
- await new Promise((resolve2) => setTimeout(resolve2, 1e3));
6568
+ await new Promise((resolve3) => setTimeout(resolve3, 1e3));
4874
6569
  try {
4875
6570
  process.kill(pid, 0);
4876
6571
  console.log("\u26A0\uFE0F Server still running, sending SIGKILL...");
@@ -4886,9 +6581,10 @@ program.command("stop").description("Stop the running MoltsPay server").action(a
4886
6581
  process.exit(1);
4887
6582
  }
4888
6583
  });
4889
- 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) => {
6584
+ 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) => {
4890
6585
  const client = new MoltsPayClient({ configDir: options.configDir });
4891
- if (!client.isInitialized) {
6586
+ const useAlipay = options.rail?.toLowerCase() === "alipay";
6587
+ if (!useAlipay && !client.isInitialized) {
4892
6588
  console.error("\u274C Wallet not initialized. Run: npx moltspay init");
4893
6589
  process.exit(1);
4894
6590
  }
@@ -4916,7 +6612,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4916
6612
  if (imagePath.startsWith("http://") || imagePath.startsWith("https://")) {
4917
6613
  params.image_url = imagePath;
4918
6614
  } else {
4919
- const filePath = resolve(imagePath);
6615
+ const filePath = resolve2(imagePath);
4920
6616
  if (!existsSync5(filePath)) {
4921
6617
  console.error(`\u274C Image file not found: ${filePath}`);
4922
6618
  process.exit(1);
@@ -4933,7 +6629,7 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4933
6629
  }
4934
6630
  const imageDisplay = params.image_url || (params.image_base64 ? `[local file: ${options.image}]` : null);
4935
6631
  const token = (options.token || "USDC").toUpperCase();
4936
- if (token === "USDT") {
6632
+ if (!useAlipay && token === "USDT") {
4937
6633
  const balance = await client.getBalance();
4938
6634
  if (balance.native < 1e-4) {
4939
6635
  console.log("\n\u26A0\uFE0F USDT requires a small amount of ETH for gas (~$0.01)");
@@ -4957,13 +6653,31 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4957
6653
  console.log(` Prompt: ${params.prompt}`);
4958
6654
  }
4959
6655
  if (imageDisplay) console.log(` Image: ${imageDisplay}`);
4960
- console.log(` Chain: ${chain || "(auto)"}`);
4961
- console.log(` Token: ${token}`);
4962
- console.log(` Wallet: ${client.address}`);
6656
+ if (useAlipay) {
6657
+ console.log(` Rail: alipay (CNY via alipay-bot)`);
6658
+ } else {
6659
+ console.log(` Chain: ${chain || "(auto)"}`);
6660
+ console.log(` Token: ${token}`);
6661
+ console.log(` Wallet: ${client.address}`);
6662
+ }
4963
6663
  console.log("");
4964
6664
  }
4965
6665
  try {
4966
- const result = await client.pay(server, service, params, {
6666
+ const result = await client.pay(server, service, params, useAlipay ? {
6667
+ rail: "alipay",
6668
+ rawData: useRawData,
6669
+ onPaymentPending: ({ paymentUrl, shortenUrl }) => {
6670
+ if (!options.json) {
6671
+ process.stdout.write(`
6672
+ \u{1F4F2} \u8BF7\u7528\u652F\u4ED8\u5B9D\u626B\u7801\u6216\u8BBF\u95EE\uFF1A${shortenUrl ?? paymentUrl}
6673
+
6674
+ `);
6675
+ }
6676
+ },
6677
+ onLine: (line) => {
6678
+ if (!options.json) process.stdout.write(line + "\n");
6679
+ }
6680
+ } : {
4967
6681
  token,
4968
6682
  chain,
4969
6683
  rawData: useRawData
@@ -4984,11 +6698,34 @@ program.command("pay <server> <service> [params]").description("Pay for a servic
4984
6698
  process.exit(1);
4985
6699
  }
4986
6700
  });
6701
+ program.command("alipay <action> [args...]").description("Alipay wallet setup via alipay-bot: check | apply | bind").allowUnknownOption().action(async (action, args) => {
6702
+ const map = {
6703
+ check: "check-wallet",
6704
+ apply: "apply-wallet",
6705
+ bind: "bind-wallet"
6706
+ };
6707
+ const sub = map[action] ?? action;
6708
+ const child = spawn2("alipay-bot", [sub, ...args ?? []], {
6709
+ stdio: "inherit",
6710
+ env: filterEnv(process.env)
6711
+ });
6712
+ child.on("error", (e) => {
6713
+ if (e?.code === "ENOENT") {
6714
+ console.error(
6715
+ "\u274C alipay-bot not installed. Run: npx -y @alipay/agent-payment install-cli"
6716
+ );
6717
+ } else {
6718
+ console.error(`\u274C ${e.message}`);
6719
+ }
6720
+ process.exit(1);
6721
+ });
6722
+ child.on("exit", (code) => process.exit(code ?? 1));
6723
+ });
4987
6724
  program.command("validate <path>").description("Validate a moltspay.services.json file against the schema").action(async (inputPath) => {
4988
- const resolvedPath = resolve(inputPath);
6725
+ const resolvedPath = resolve2(inputPath);
4989
6726
  let manifestPath;
4990
- if (existsSync5(join5(resolvedPath, "moltspay.services.json"))) {
4991
- manifestPath = join5(resolvedPath, "moltspay.services.json");
6727
+ if (existsSync5(join6(resolvedPath, "moltspay.services.json"))) {
6728
+ manifestPath = join6(resolvedPath, "moltspay.services.json");
4992
6729
  } else if (resolvedPath.endsWith(".json") && existsSync5(resolvedPath)) {
4993
6730
  manifestPath = resolvedPath;
4994
6731
  } else {