@piprail/sdk 1.8.0 → 1.10.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 (30) hide show
  1. package/CHAINS.md +5 -3
  2. package/CHANGELOG.md +59 -0
  3. package/ERRORS.md +21 -8
  4. package/README.md +23 -0
  5. package/STANDARDS.md +5 -2
  6. package/dist/{algorand-IJJKE35X.cjs → algorand-MXUSKX46.cjs} +17 -17
  7. package/dist/{algorand-B67G4335.js → algorand-WGVF4KTU.js} +1 -1
  8. package/dist/{aptos-YQWTGFRZ.js → aptos-LPBLSEIQ.js} +1 -1
  9. package/dist/{aptos-X3G2UBYW.cjs → aptos-YT7SXWPF.cjs} +16 -16
  10. package/dist/{chunk-IQGT65WS.cjs → chunk-MDLZJGLY.cjs} +20 -16
  11. package/dist/{chunk-QDS6FBZP.js → chunk-SVMGHASK.js} +4 -0
  12. package/dist/index.cjs +823 -253
  13. package/dist/index.d.cts +458 -35
  14. package/dist/index.d.ts +458 -35
  15. package/dist/index.js +746 -176
  16. package/dist/{near-GGUHLXAF.cjs → near-7ZDNISUX.cjs} +19 -19
  17. package/dist/{near-7MBBCDUE.js → near-K6BDBABG.js} +1 -1
  18. package/dist/{solana-W24TCJV4.cjs → solana-PU7N2M64.cjs} +14 -14
  19. package/dist/{solana-7WJVZGDW.js → solana-S3UFI3FE.js} +1 -1
  20. package/dist/{stellar-HV6VGZX3.js → stellar-Q5PO23SC.js} +1 -1
  21. package/dist/{stellar-YMY3K2YB.cjs → stellar-VDQOFQEO.cjs} +21 -21
  22. package/dist/{sui-32KVESR5.cjs → sui-FKSMLKRF.cjs} +17 -17
  23. package/dist/{sui-2WFWVFJX.js → sui-WOXRKJXS.js} +1 -1
  24. package/dist/{ton-FIQGV2LC.cjs → ton-VK6KRJHP.cjs} +14 -14
  25. package/dist/{ton-DGZB7W4U.js → ton-WPTXGLVK.js} +1 -1
  26. package/dist/{tron-RLIL2FDI.js → tron-6GXBXTR4.js} +1 -1
  27. package/dist/{tron-ZSXAPZ2C.cjs → tron-WLOF5OUV.cjs} +24 -24
  28. package/dist/{xrpl-2PKP7HOI.cjs → xrpl-CMNI25BV.cjs} +21 -21
  29. package/dist/{xrpl-UEC2GYVV.js → xrpl-HEAPEXAM.js} +1 -1
  30. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  PaymentTimeoutError,
11
11
  PipRailError,
12
12
  RecipientNotReadyError,
13
+ SettlementError,
13
14
  UnknownTokenError,
14
15
  UnsupportedNetworkError,
15
16
  WrongChainError,
@@ -20,7 +21,7 @@ import {
20
21
  parseUnits,
21
22
  rejectForeignToken,
22
23
  toInsufficientFundsError
23
- } from "./chunk-QDS6FBZP.js";
24
+ } from "./chunk-SVMGHASK.js";
24
25
 
25
26
  // src/drivers/registry.ts
26
27
  var byFamily = /* @__PURE__ */ new Map();
@@ -63,7 +64,7 @@ function resolveNetwork(opts) {
63
64
  }
64
65
 
65
66
  // src/drivers/evm/index.ts
66
- import { BaseError, createPublicClient, erc20Abi as erc20Abi3, getAddress as getAddress2, http as http2, isAddress } from "viem";
67
+ import { BaseError, createPublicClient, erc20Abi as erc20Abi3, getAddress as getAddress3, http as http2, isAddress } from "viem";
67
68
 
68
69
  // src/drivers/evm/chains.ts
69
70
  import { defineChain } from "viem";
@@ -75,6 +76,7 @@ import {
75
76
  celo,
76
77
  hyperEvm,
77
78
  injective,
79
+ kaia,
78
80
  linea,
79
81
  mainnet,
80
82
  mantle,
@@ -236,6 +238,17 @@ var CHAINS = {
236
238
  tokens: {
237
239
  USDC: { address: "0x754704Bc059F8C67012fEd69BC8A327a5aafb603", decimals: 6, symbol: "USDC" }
238
240
  }
241
+ },
242
+ // Kaia (ex-Klaytn, chainId 8217) — Tether-native USD₮ verified on-chain 2026-06-08
243
+ // (0xd077…4fDb: symbol "USD₮", name "Tether USD", 6 dp, no bridge markers). Circle issues
244
+ // NO native USDC on Kaia (absent from Circle's list), so USDC is intentionally omitted —
245
+ // pay native KAIA or USD₮ (or pass a custom { address, decimals }). Asia's stablecoin-
246
+ // settlement chain, born from Kakao + LINE.
247
+ kaia: {
248
+ chain: kaia,
249
+ tokens: {
250
+ USDT: { address: "0xd077A400968890Eacc75cdc901F0356c943e4fDb", decimals: 6, symbol: "USDT" }
251
+ }
239
252
  }
240
253
  };
241
254
  function isViemChain(input) {
@@ -471,20 +484,367 @@ function sumTransfersTo(logs, asset, payTo) {
471
484
  return { total, from };
472
485
  }
473
486
 
487
+ // src/drivers/evm/exact.ts
488
+ import {
489
+ getAddress as getAddress2,
490
+ parseSignature,
491
+ recoverTypedDataAddress
492
+ } from "viem";
493
+ var EXACT_NETWORK_SLUGS = {
494
+ ethereum: 1,
495
+ base: 8453,
496
+ "base-sepolia": 84532,
497
+ arbitrum: 42161,
498
+ optimism: 10,
499
+ polygon: 137,
500
+ avalanche: 43114
501
+ };
502
+ function chainIdForExactNetwork(slug) {
503
+ return EXACT_NETWORK_SLUGS[slug] ?? null;
504
+ }
505
+ var EIP3009_TYPES = {
506
+ TransferWithAuthorization: [
507
+ { name: "from", type: "address" },
508
+ { name: "to", type: "address" },
509
+ { name: "value", type: "uint256" },
510
+ { name: "validAfter", type: "uint256" },
511
+ { name: "validBefore", type: "uint256" },
512
+ { name: "nonce", type: "bytes32" }
513
+ ]
514
+ };
515
+ function parseExactRequirements(body) {
516
+ if (!body || typeof body !== "object") return null;
517
+ const accepts = body.accepts;
518
+ if (!Array.isArray(accepts)) return null;
519
+ const out = [];
520
+ for (const raw of accepts) {
521
+ if (!raw || typeof raw !== "object") continue;
522
+ const a = raw;
523
+ if (a.scheme !== "exact") continue;
524
+ const amount = a.maxAmountRequired ?? a.amount;
525
+ if (typeof a.network !== "string" || typeof amount !== "string" || typeof a.asset !== "string" || typeof a.payTo !== "string") {
526
+ continue;
527
+ }
528
+ out.push({
529
+ scheme: "exact",
530
+ network: a.network,
531
+ maxAmountRequired: amount,
532
+ asset: a.asset,
533
+ payTo: a.payTo,
534
+ maxTimeoutSeconds: typeof a.maxTimeoutSeconds === "number" ? a.maxTimeoutSeconds : 600,
535
+ ...a.extra && typeof a.extra === "object" ? { extra: a.extra } : {},
536
+ ...typeof a.description === "string" ? { description: a.description } : {},
537
+ ...typeof a.resource === "string" ? { resource: a.resource } : {}
538
+ });
539
+ }
540
+ return out;
541
+ }
542
+ async function buildExactAuthorization(params) {
543
+ const { account, accept, chainId, now, nonce } = params;
544
+ if (!account.signTypedData) {
545
+ throw new Error("buildExactAuthorization: the account cannot sign EIP-712 typed data.");
546
+ }
547
+ const authorization = {
548
+ from: account.address,
549
+ to: accept.payTo,
550
+ value: accept.maxAmountRequired,
551
+ validAfter: "0",
552
+ validBefore: String(now + accept.maxTimeoutSeconds),
553
+ nonce
554
+ };
555
+ const signature = await account.signTypedData({
556
+ domain: {
557
+ name: accept.extra?.name ?? "USD Coin",
558
+ version: accept.extra?.version ?? "2",
559
+ chainId,
560
+ verifyingContract: accept.asset
561
+ },
562
+ types: EIP3009_TYPES,
563
+ primaryType: "TransferWithAuthorization",
564
+ message: {
565
+ from: authorization.from,
566
+ to: authorization.to,
567
+ value: BigInt(authorization.value),
568
+ validAfter: BigInt(authorization.validAfter),
569
+ validBefore: BigInt(authorization.validBefore),
570
+ nonce: authorization.nonce
571
+ }
572
+ });
573
+ return { authorization, signature };
574
+ }
575
+ function base64(str) {
576
+ if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
577
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
578
+ const bytes = new TextEncoder().encode(str);
579
+ let binary = "";
580
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
581
+ return btoa(binary);
582
+ }
583
+ throw new Error("No base64 encoder available in this runtime.");
584
+ }
585
+ function encodeXPaymentHeader(input) {
586
+ const payload = {
587
+ x402Version: input.x402Version ?? 1,
588
+ scheme: "exact",
589
+ network: input.network,
590
+ payload: { signature: input.signature, authorization: input.authorization }
591
+ };
592
+ return base64(JSON.stringify(payload));
593
+ }
594
+ var eip3009Abi = [
595
+ {
596
+ type: "function",
597
+ name: "transferWithAuthorization",
598
+ stateMutability: "nonpayable",
599
+ outputs: [],
600
+ inputs: [
601
+ { name: "from", type: "address" },
602
+ { name: "to", type: "address" },
603
+ { name: "value", type: "uint256" },
604
+ { name: "validAfter", type: "uint256" },
605
+ { name: "validBefore", type: "uint256" },
606
+ { name: "nonce", type: "bytes32" },
607
+ { name: "v", type: "uint8" },
608
+ { name: "r", type: "bytes32" },
609
+ { name: "s", type: "bytes32" }
610
+ ]
611
+ },
612
+ {
613
+ type: "function",
614
+ name: "transferWithAuthorization",
615
+ stateMutability: "nonpayable",
616
+ outputs: [],
617
+ inputs: [
618
+ { name: "from", type: "address" },
619
+ { name: "to", type: "address" },
620
+ { name: "value", type: "uint256" },
621
+ { name: "validAfter", type: "uint256" },
622
+ { name: "validBefore", type: "uint256" },
623
+ { name: "nonce", type: "bytes32" },
624
+ { name: "signature", type: "bytes" }
625
+ ]
626
+ },
627
+ {
628
+ type: "function",
629
+ name: "authorizationState",
630
+ stateMutability: "view",
631
+ inputs: [
632
+ { name: "authorizer", type: "address" },
633
+ { name: "nonce", type: "bytes32" }
634
+ ],
635
+ outputs: [{ type: "bool" }]
636
+ },
637
+ { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
638
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }
639
+ ];
640
+ var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
641
+ var ZERO_NONCE = `0x${"00".repeat(32)}`;
642
+ async function readExactDomain(publicClient, asset) {
643
+ if (asset === "native") return null;
644
+ let token;
645
+ try {
646
+ token = getAddress2(asset);
647
+ } catch {
648
+ return null;
649
+ }
650
+ try {
651
+ const [name, version] = await Promise.all([
652
+ publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "name" }),
653
+ publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "version" }),
654
+ // The EIP-3009 probe: this view exists only on EIP-3009 tokens; it reverts on
655
+ // a plain ERC-20 / USDT, marking the token as not exact-payable.
656
+ publicClient.readContract({
657
+ address: token,
658
+ abi: eip3009Abi,
659
+ functionName: "authorizationState",
660
+ args: [ZERO_ADDR, ZERO_NONCE]
661
+ })
662
+ ]);
663
+ if (typeof name !== "string" || typeof version !== "string" || !name || !version) return null;
664
+ return { name, version };
665
+ } catch {
666
+ return null;
667
+ }
668
+ }
669
+ function shorten(msg) {
670
+ const oneLine = msg.replace(/\s+/g, " ").trim();
671
+ return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
672
+ }
673
+ async function verifyAndSettleExactEvm(input) {
674
+ const { publicClient, walletClient, account, chain, payload, accept } = input;
675
+ const token = getAddress2(accept.asset);
676
+ const payTo = getAddress2(accept.payTo);
677
+ const requiredAmount = BigInt(accept.amount);
678
+ let from;
679
+ let to;
680
+ let value;
681
+ let validAfter;
682
+ let validBefore;
683
+ let nonce;
684
+ try {
685
+ from = getAddress2(payload.authorization.from);
686
+ to = getAddress2(payload.authorization.to);
687
+ value = BigInt(payload.authorization.value);
688
+ validAfter = BigInt(payload.authorization.validAfter);
689
+ validBefore = BigInt(payload.authorization.validBefore);
690
+ nonce = payload.authorization.nonce;
691
+ if (!/^0x[0-9a-fA-F]{64}$/.test(nonce)) throw new Error("nonce must be 32-byte hex");
692
+ if (!/^0x[0-9a-fA-F]+$/.test(payload.signature)) throw new Error("signature must be hex");
693
+ } catch (err) {
694
+ return {
695
+ ok: false,
696
+ error: "signature_invalid",
697
+ detail: `Malformed exact authorization: ${err instanceof Error ? err.message : String(err)}.`
698
+ };
699
+ }
700
+ if (to !== payTo) {
701
+ return { ok: false, error: "wrong_recipient", detail: `Authorization pays ${to}, not ${payTo}.` };
702
+ }
703
+ if (value < requiredAmount) {
704
+ return { ok: false, error: "amount_too_low", detail: `Authorized ${value}, required ${requiredAmount}.` };
705
+ }
706
+ const now = BigInt(Math.floor(Date.now() / 1e3));
707
+ if (validBefore <= now) {
708
+ return { ok: false, error: "payment_expired", detail: `Authorization expired: validBefore ${validBefore} <= now ${now}.` };
709
+ }
710
+ let fromCode;
711
+ try {
712
+ fromCode = await publicClient.getCode({ address: from });
713
+ } catch {
714
+ return { ok: false, error: "tx_not_found", detail: `Could not read code at ${from} (transient RPC) \u2014 retry.` };
715
+ }
716
+ const isContractWallet = Boolean(fromCode && fromCode !== "0x");
717
+ if (!isContractWallet) {
718
+ let recovered;
719
+ try {
720
+ recovered = await recoverTypedDataAddress({
721
+ domain: {
722
+ name: accept.extra.name,
723
+ version: accept.extra.version,
724
+ chainId: chain.id,
725
+ verifyingContract: token
726
+ },
727
+ types: EIP3009_TYPES,
728
+ primaryType: "TransferWithAuthorization",
729
+ message: { from, to, value, validAfter, validBefore, nonce },
730
+ signature: payload.signature
731
+ });
732
+ } catch (err) {
733
+ return { ok: false, error: "signature_invalid", detail: `Not a valid EIP-712 signature: ${shorten(err instanceof Error ? err.message : String(err))}.` };
734
+ }
735
+ if (recovered !== from) {
736
+ return { ok: false, error: "signature_invalid", detail: `Signature recovered to ${recovered}, not the authorizer ${from}.` };
737
+ }
738
+ }
739
+ try {
740
+ const used = await publicClient.readContract({
741
+ address: token,
742
+ abi: eip3009Abi,
743
+ functionName: "authorizationState",
744
+ args: [from, nonce]
745
+ });
746
+ if (used) {
747
+ return { ok: false, error: "tx_already_used", detail: `Authorization nonce ${nonce} already used or canceled on-chain.` };
748
+ }
749
+ } catch {
750
+ return { ok: false, error: "tx_not_found", detail: "Could not read authorizationState (transient RPC) \u2014 retry." };
751
+ }
752
+ const baseArgs = [from, to, value, validAfter, validBefore, nonce];
753
+ const isEcdsa = !isContractWallet && payload.signature.length - 2 === 130;
754
+ let writeArgs;
755
+ if (isEcdsa) {
756
+ const { r, s, yParity } = parseSignature(payload.signature);
757
+ writeArgs = [...baseArgs, BigInt(yParity + 27), r, s];
758
+ } else {
759
+ writeArgs = [...baseArgs, payload.signature];
760
+ }
761
+ try {
762
+ await publicClient.simulateContract({
763
+ account,
764
+ address: token,
765
+ abi: eip3009Abi,
766
+ functionName: "transferWithAuthorization",
767
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
768
+ args: writeArgs
769
+ });
770
+ } catch (err) {
771
+ const msg = err instanceof Error ? err.message : String(err);
772
+ if (/used or canceled/i.test(msg)) return { ok: false, error: "tx_already_used", detail: "Authorization is used or canceled." };
773
+ if (/expired|not yet valid/i.test(msg)) return { ok: false, error: "payment_expired", detail: shorten(msg) };
774
+ if (/invalid signature/i.test(msg)) return { ok: false, error: "signature_invalid", detail: shorten(msg) };
775
+ return { ok: false, error: "tx_reverted", detail: `transferWithAuthorization would revert: ${shorten(msg)}` };
776
+ }
777
+ let txHash;
778
+ try {
779
+ txHash = await walletClient.writeContract({
780
+ account,
781
+ chain,
782
+ address: token,
783
+ abi: eip3009Abi,
784
+ functionName: "transferWithAuthorization",
785
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
786
+ args: writeArgs
787
+ });
788
+ } catch (err) {
789
+ throw new SettlementError(
790
+ `exact settle: the merchant relayer failed to broadcast transferWithAuthorization (${shorten(err instanceof Error ? err.message : String(err))}). The payer's authorization is still valid and unused \u2014 fund/fix the relayer and the payer can retry.`,
791
+ { cause: err }
792
+ );
793
+ }
794
+ try {
795
+ const confirmations = accept.extra.minConfirmations ?? 1;
796
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations });
797
+ if (receipt.status !== "success") {
798
+ return { ok: false, error: "tx_reverted", detail: `Settlement tx ${txHash} reverted on-chain.` };
799
+ }
800
+ } catch (err) {
801
+ throw new SettlementError(
802
+ `exact settle: broadcast ${txHash} but couldn't confirm it (${shorten(err instanceof Error ? err.message : String(err))}).`,
803
+ { cause: err }
804
+ );
805
+ }
806
+ return {
807
+ ok: true,
808
+ receipt: {
809
+ scheme: "exact",
810
+ success: true,
811
+ network: accept.network,
812
+ transaction: txHash,
813
+ asset: accept.asset,
814
+ amount: accept.amount,
815
+ payer: from,
816
+ payTo: accept.payTo,
817
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
818
+ }
819
+ };
820
+ }
821
+
474
822
  // src/x402.ts
475
823
  var HEADER_REQUIRED = "payment-required";
476
824
  var HEADER_SIGNATURE = "payment-signature";
477
825
  var HEADER_RESPONSE = "payment-response";
478
- function decodeBase64(b64) {
479
- if (typeof atob === "function") return atob(b64);
480
- if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
481
- throw new Error("No base64 decoder available in this runtime.");
482
- }
826
+ var HEADER_SIGNATURE_V1 = "x-payment";
827
+ var HEADER_RESPONSE_V1 = "x-payment-response";
483
828
  function encodeBase64(str) {
484
- if (typeof btoa === "function") return btoa(str);
485
829
  if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
830
+ if (typeof btoa === "function" && typeof TextEncoder !== "undefined") {
831
+ const bytes = new TextEncoder().encode(str);
832
+ let binary = "";
833
+ for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]);
834
+ return btoa(binary);
835
+ }
486
836
  throw new Error("No base64 encoder available in this runtime.");
487
837
  }
838
+ function decodeBase64(b64) {
839
+ if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
840
+ if (typeof atob === "function" && typeof TextDecoder !== "undefined") {
841
+ const binary = atob(b64);
842
+ const bytes = new Uint8Array(binary.length);
843
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
844
+ return new TextDecoder().decode(bytes);
845
+ }
846
+ throw new Error("No base64 decoder available in this runtime.");
847
+ }
488
848
  function fromBase64Json(b64) {
489
849
  try {
490
850
  return JSON.parse(decodeBase64(b64));
@@ -545,6 +905,36 @@ function parseSignatureHeader(value) {
545
905
  }
546
906
  return parsed;
547
907
  }
908
+ function parseExactPaymentHeader(value) {
909
+ const parsed = fromBase64Json(value);
910
+ if (!parsed || typeof parsed !== "object") return null;
911
+ const v = parsed;
912
+ const accepted = v.accepted ?? null;
913
+ const scheme = accepted?.scheme ?? v.scheme;
914
+ if (scheme !== "exact") return null;
915
+ const network = accepted?.network ?? v.network;
916
+ if (typeof network !== "string") return null;
917
+ const payload = v.payload;
918
+ if (!payload || typeof payload !== "object") return null;
919
+ const signature = payload.signature;
920
+ const authorization = payload.authorization;
921
+ if (typeof signature !== "string" || !authorization || typeof authorization !== "object") return null;
922
+ for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
923
+ if (typeof authorization[k] !== "string") return null;
924
+ }
925
+ const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
926
+ const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
927
+ return {
928
+ x402Version,
929
+ network,
930
+ ...asset ? { asset } : {},
931
+ payload: {
932
+ signature,
933
+ authorization
934
+ },
935
+ raw: v
936
+ };
937
+ }
548
938
  function isValidChallenge(value) {
549
939
  if (!value || typeof value !== "object") return false;
550
940
  const v = value;
@@ -556,7 +946,7 @@ function isValidChallenge(value) {
556
946
  function isValidReceipt(value) {
557
947
  if (!value || typeof value !== "object") return false;
558
948
  const v = value;
559
- if (v.scheme !== "onchain-proof") return false;
949
+ if (v.scheme !== "onchain-proof" && v.scheme !== "exact") return false;
560
950
  if (typeof v.transaction !== "string" && typeof v.txHash !== "string") return false;
561
951
  if (typeof v.payer !== "string") return false;
562
952
  return true;
@@ -634,12 +1024,12 @@ function makeEvmNetwork(resolved) {
634
1024
  }
635
1025
  let normalized;
636
1026
  try {
637
- normalized = getAddress2(asset);
1027
+ normalized = getAddress3(asset);
638
1028
  } catch {
639
1029
  return null;
640
1030
  }
641
1031
  for (const info of Object.values(resolved.tokens)) {
642
- if (getAddress2(info.address) === normalized) {
1032
+ if (getAddress3(info.address) === normalized) {
643
1033
  return { symbol: info.symbol, decimals: info.decimals };
644
1034
  }
645
1035
  }
@@ -723,7 +1113,7 @@ function makeEvmNetwork(resolved) {
723
1113
  let token = null;
724
1114
  try {
725
1115
  token = await publicClient.readContract({
726
- address: getAddress2(asset),
1116
+ address: getAddress3(asset),
727
1117
  abi: erc20Abi3,
728
1118
  functionName: "balanceOf",
729
1119
  args: [owner]
@@ -755,6 +1145,21 @@ function makeEvmNetwork(resolved) {
755
1145
  accept,
756
1146
  minConfirmations: accept.extra.minConfirmations
757
1147
  });
1148
+ },
1149
+ // Standard x402 `exact` rail (EIP-3009), seller side — EVM only.
1150
+ async exactDomain(asset) {
1151
+ return readExactDomain(publicClient, asset);
1152
+ },
1153
+ async settleExactSelf({ relayer, payload, accept }) {
1154
+ const a = relayer._native;
1155
+ return verifyAndSettleExactEvm({
1156
+ publicClient,
1157
+ walletClient: a.walletClient,
1158
+ account: a.account,
1159
+ chain: resolved.chain,
1160
+ payload,
1161
+ accept
1162
+ });
758
1163
  }
759
1164
  };
760
1165
  }
@@ -771,7 +1176,7 @@ var loaders = {
771
1176
  solana: async () => {
772
1177
  let mod;
773
1178
  try {
774
- mod = await import("./solana-7WJVZGDW.js");
1179
+ mod = await import("./solana-S3UFI3FE.js");
775
1180
  } catch (cause) {
776
1181
  throw new MissingDriverError(
777
1182
  `Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
@@ -783,7 +1188,7 @@ var loaders = {
783
1188
  ton: async () => {
784
1189
  let mod;
785
1190
  try {
786
- mod = await import("./ton-DGZB7W4U.js");
1191
+ mod = await import("./ton-WPTXGLVK.js");
787
1192
  } catch (cause) {
788
1193
  throw new MissingDriverError(
789
1194
  `TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
@@ -795,7 +1200,7 @@ var loaders = {
795
1200
  stellar: async () => {
796
1201
  let mod;
797
1202
  try {
798
- mod = await import("./stellar-HV6VGZX3.js");
1203
+ mod = await import("./stellar-Q5PO23SC.js");
799
1204
  } catch (cause) {
800
1205
  throw new MissingDriverError(
801
1206
  `Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
@@ -807,7 +1212,7 @@ var loaders = {
807
1212
  xrpl: async () => {
808
1213
  let mod;
809
1214
  try {
810
- mod = await import("./xrpl-UEC2GYVV.js");
1215
+ mod = await import("./xrpl-HEAPEXAM.js");
811
1216
  } catch (cause) {
812
1217
  throw new MissingDriverError(
813
1218
  `XRPL selected, but its package isn't installed. Run: npm install xrpl`,
@@ -819,7 +1224,7 @@ var loaders = {
819
1224
  tron: async () => {
820
1225
  let mod;
821
1226
  try {
822
- mod = await import("./tron-RLIL2FDI.js");
1227
+ mod = await import("./tron-6GXBXTR4.js");
823
1228
  } catch (cause) {
824
1229
  throw new MissingDriverError(
825
1230
  `Tron selected, but its package isn't installed. Run: npm install tronweb`,
@@ -831,7 +1236,7 @@ var loaders = {
831
1236
  sui: async () => {
832
1237
  let mod;
833
1238
  try {
834
- mod = await import("./sui-2WFWVFJX.js");
1239
+ mod = await import("./sui-WOXRKJXS.js");
835
1240
  } catch (cause) {
836
1241
  throw new MissingDriverError(
837
1242
  `Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
@@ -843,7 +1248,7 @@ var loaders = {
843
1248
  near: async () => {
844
1249
  let mod;
845
1250
  try {
846
- mod = await import("./near-7MBBCDUE.js");
1251
+ mod = await import("./near-K6BDBABG.js");
847
1252
  } catch (cause) {
848
1253
  throw new MissingDriverError(
849
1254
  `NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
@@ -855,7 +1260,7 @@ var loaders = {
855
1260
  aptos: async () => {
856
1261
  let mod;
857
1262
  try {
858
- mod = await import("./aptos-YQWTGFRZ.js");
1263
+ mod = await import("./aptos-LPBLSEIQ.js");
859
1264
  } catch (cause) {
860
1265
  throw new MissingDriverError(
861
1266
  `Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
@@ -867,7 +1272,7 @@ var loaders = {
867
1272
  algorand: async () => {
868
1273
  let mod;
869
1274
  try {
870
- mod = await import("./algorand-B67G4335.js");
1275
+ mod = await import("./algorand-WGVF4KTU.js");
871
1276
  } catch (cause) {
872
1277
  throw new MissingDriverError(
873
1278
  `Algorand selected, but its package isn't installed. Run: npm install algosdk`,
@@ -1651,7 +2056,10 @@ var PipRailClient = class {
1651
2056
  const chosen = priced.find((p) => p.quote.withinPolicy) ?? priced[0];
1652
2057
  return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
1653
2058
  }
1654
- /** The candidate accepts this client could pay: our scheme, on the bound network. */
2059
+ /** The candidate accepts this client could pay: our scheme, on the bound network.
2060
+ * A dual-advertised challenge may also carry standard `exact` rails — the PipRail
2061
+ * client ignores those (it pays the backendless `onchain-proof` rail); the type
2062
+ * predicate narrows the `X402AnyAccept` union to the rails we settle. */
1655
2063
  gatherCandidates(net, challenge) {
1656
2064
  return challenge.accepts.filter(
1657
2065
  (a) => a.scheme === "onchain-proof" && net.supports(a.network)
@@ -2014,6 +2422,14 @@ function isReplayableBodyInit(value) {
2014
2422
  async function readInvalidReason(response) {
2015
2423
  try {
2016
2424
  const body = await response.clone().json();
2425
+ const ext = body?.extensions;
2426
+ const piprail = ext?.piprail;
2427
+ if (piprail && typeof piprail.code === "string") {
2428
+ return {
2429
+ error: piprail.code,
2430
+ detail: typeof piprail.detail === "string" ? piprail.detail : ""
2431
+ };
2432
+ }
2017
2433
  if (body && (body.status === "invalid" || typeof body.error === "string")) {
2018
2434
  return {
2019
2435
  error: typeof body.error === "string" ? body.error : "no error code",
@@ -2245,6 +2661,100 @@ function paymentTools(client) {
2245
2661
  ];
2246
2662
  }
2247
2663
 
2664
+ // src/facilitator.ts
2665
+ function safeStringify(value) {
2666
+ return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
2667
+ }
2668
+ function mapReason(reason) {
2669
+ const r = (reason ?? "").toLowerCase();
2670
+ if (r.includes("signature")) return "signature_invalid";
2671
+ if (r.includes("recipient")) return "wrong_recipient";
2672
+ if (r.includes("value") || r.includes("amount")) return "amount_too_low";
2673
+ if (r.includes("valid_before") || r.includes("valid_after") || r.includes("expired")) return "payment_expired";
2674
+ if (r.includes("used") || r.includes("replay") || r.includes("nonce") || r.includes("transaction_state")) {
2675
+ return "tx_already_used";
2676
+ }
2677
+ return "tx_reverted";
2678
+ }
2679
+ async function post(url, body, headers) {
2680
+ const res = await fetch(url, {
2681
+ method: "POST",
2682
+ headers: { "content-type": "application/json", ...headers },
2683
+ body: safeStringify(body)
2684
+ });
2685
+ let json = null;
2686
+ try {
2687
+ json = await res.json();
2688
+ } catch {
2689
+ }
2690
+ return { status: res.status, json };
2691
+ }
2692
+ async function settleViaFacilitator(input) {
2693
+ const base2 = input.url.replace(/\/+$/, "");
2694
+ const body = {
2695
+ x402Version: input.x402Version,
2696
+ paymentPayload: input.paymentPayload,
2697
+ paymentRequirements: input.paymentRequirements
2698
+ };
2699
+ const auth = input.authHeaders ? await input.authHeaders() : {};
2700
+ let verify;
2701
+ try {
2702
+ verify = await post(`${base2}/verify`, body, auth);
2703
+ } catch (err) {
2704
+ throw new SettlementError(
2705
+ `exact settle (facilitator ${base2}): /verify request failed (${err instanceof Error ? err.message : String(err)}).`,
2706
+ { cause: err }
2707
+ );
2708
+ }
2709
+ if (verify.status !== 200) {
2710
+ throw new SettlementError(
2711
+ `exact settle (facilitator ${base2}): /verify returned HTTP ${verify.status} (transport/auth error).`
2712
+ );
2713
+ }
2714
+ const vr = verify.json ?? {};
2715
+ if (vr.isValid === false) {
2716
+ return {
2717
+ ok: false,
2718
+ error: mapReason(vr.invalidReason),
2719
+ detail: `Facilitator rejected the payment: ${vr.invalidReason ?? "invalid"}${vr.invalidMessage ? ` \u2014 ${vr.invalidMessage}` : ""}.`
2720
+ };
2721
+ }
2722
+ let settle;
2723
+ try {
2724
+ settle = await post(`${base2}/settle`, body, auth);
2725
+ } catch (err) {
2726
+ throw new SettlementError(
2727
+ `exact settle (facilitator ${base2}): /settle request failed (${err instanceof Error ? err.message : String(err)}).`,
2728
+ { cause: err }
2729
+ );
2730
+ }
2731
+ if (settle.status !== 200) {
2732
+ throw new SettlementError(
2733
+ `exact settle (facilitator ${base2}): /settle returned HTTP ${settle.status} (transport/auth error).`
2734
+ );
2735
+ }
2736
+ const sr = settle.json ?? {};
2737
+ if (!sr.success) {
2738
+ return {
2739
+ ok: false,
2740
+ error: mapReason(sr.errorReason),
2741
+ detail: `Facilitator settlement failed: ${sr.errorReason ?? "unknown"}${sr.errorMessage ? ` \u2014 ${sr.errorMessage}` : ""}.`
2742
+ };
2743
+ }
2744
+ const receipt = {
2745
+ scheme: "exact",
2746
+ success: true,
2747
+ network: input.receipt.network,
2748
+ transaction: sr.transaction,
2749
+ asset: input.receipt.asset,
2750
+ amount: input.receipt.amount,
2751
+ payer: sr.payer ?? input.payerHint ?? "",
2752
+ payTo: input.receipt.payTo,
2753
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
2754
+ };
2755
+ return { ok: true, receipt };
2756
+ }
2757
+
2248
2758
  // src/server.ts
2249
2759
  function toInvalidBody(result) {
2250
2760
  return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
@@ -2271,9 +2781,10 @@ function createPaymentGate(options) {
2271
2781
  const genNonce = options.generateNonce ?? (() => globalThis.crypto.randomUUID());
2272
2782
  let resolved;
2273
2783
  function ready() {
2274
- return resolved ??= (async () => {
2784
+ if (resolved) return resolved;
2785
+ const p = (async () => {
2275
2786
  const accepts = normaliseAccepts(options);
2276
- return Promise.all(
2787
+ const specs = await Promise.all(
2277
2788
  accepts.map(async (a) => {
2278
2789
  const net = await resolveNetwork2({ chain: a.chain, rpcUrl: a.rpcUrl ?? options.rpcUrl });
2279
2790
  const payTo = a.payTo ?? options.payTo;
@@ -2285,10 +2796,52 @@ function createPaymentGate(options) {
2285
2796
  net.assertValidPayTo(payTo);
2286
2797
  const { asset, decimals, symbol } = net.resolveToken(a.token);
2287
2798
  const amountBase = parseUnits(a.amount, decimals);
2288
- return { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
2799
+ const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
2800
+ if (options.exact) spec.exact = await resolveExactRail(net, asset);
2801
+ return spec;
2289
2802
  })
2290
2803
  );
2804
+ if (options.exact && !specs.some((s) => s.exact)) {
2805
+ throw new Error(
2806
+ "requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM + EIP-3009 only (USDC / EURC) \u2014 not native coins, not USDT, not non-EVM chains. Offer an EVM EIP-3009 token, or drop `exact`."
2807
+ );
2808
+ }
2809
+ return specs;
2291
2810
  })();
2811
+ p.catch(() => {
2812
+ if (resolved === p) resolved = void 0;
2813
+ });
2814
+ resolved = p;
2815
+ return p;
2816
+ }
2817
+ async function resolveExactRail(net, asset) {
2818
+ const cfg = options.exact;
2819
+ if (net.family !== "evm" || asset === "native" || !net.exactDomain || !net.settleExactSelf) {
2820
+ return void 0;
2821
+ }
2822
+ const domain = await net.exactDomain(asset);
2823
+ if (!domain) {
2824
+ throw new Error(
2825
+ `requirePayment: \`exact\` requested for asset ${asset} on ${net.network}, but it isn't an EIP-3009 token (couldn't read name()/version()/authorizationState). The exact rail supports USDC / EURC and other EIP-3009 tokens \u2014 USDT and native coins need onchain-proof. (Or check your rpcUrl is reachable.)`
2826
+ );
2827
+ }
2828
+ if (cfg.settle === "self") {
2829
+ if (cfg.relayer === void 0) {
2830
+ throw new Error(
2831
+ "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts transferWithAuthorization), e.g. exact: { settle: 'self', relayer: { privateKey } }."
2832
+ );
2833
+ }
2834
+ const relayer = net.bindWallet(cfg.relayer);
2835
+ return { domain, mode: { kind: "self", relayer } };
2836
+ }
2837
+ return {
2838
+ domain,
2839
+ mode: {
2840
+ kind: "facilitator",
2841
+ url: cfg.settle.facilitator,
2842
+ ...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
2843
+ }
2844
+ };
2292
2845
  }
2293
2846
  const hasCustomStore = Boolean(options.isUsed || options.markUsed);
2294
2847
  const localUsed = /* @__PURE__ */ new Set();
@@ -2325,93 +2878,191 @@ function createPaymentGate(options) {
2325
2878
  }
2326
2879
  };
2327
2880
  }
2328
- async function challenge(resourceUrl = "") {
2881
+ function buildExactAccept(s) {
2882
+ const d = s.exact.domain;
2883
+ return {
2884
+ scheme: "exact",
2885
+ network: s.net.network,
2886
+ amount: s.amountBase.toString(),
2887
+ asset: s.asset,
2888
+ payTo: s.payTo,
2889
+ maxTimeoutSeconds,
2890
+ extra: {
2891
+ assetTransferMethod: "eip3009",
2892
+ name: d.name,
2893
+ version: d.version,
2894
+ minConfirmations,
2895
+ decimals: s.decimals,
2896
+ amountFormatted: s.amountFormatted,
2897
+ ...s.symbol ? { symbol: s.symbol } : {}
2898
+ }
2899
+ };
2900
+ }
2901
+ function buildAccepts(specs, nonce) {
2902
+ const out = [];
2903
+ for (const s of specs) {
2904
+ if (s.exact) out.push(buildExactAccept(s));
2905
+ out.push(buildAccept(s, nonce));
2906
+ }
2907
+ return out;
2908
+ }
2909
+ async function makeChallenge(resourceUrl, opts) {
2329
2910
  const specs = await ready();
2330
2911
  const nonce = genNonce();
2331
2912
  const challenge2 = {
2332
2913
  x402Version: 2,
2333
- error: null,
2334
2914
  resource: {
2335
2915
  url: resourceUrl,
2336
2916
  ...options.description ? { description: options.description } : {}
2337
2917
  },
2338
- accepts: specs.map((s) => buildAccept(s, nonce))
2918
+ accepts: buildAccepts(specs, nonce),
2919
+ ...opts?.error ? { error: opts.error } : {},
2920
+ ...opts?.extensions ? { extensions: opts.extensions } : {}
2339
2921
  };
2340
2922
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
2341
2923
  }
2924
+ async function challenge(resourceUrl = "") {
2925
+ return makeChallenge(resourceUrl);
2926
+ }
2342
2927
  async function asChallenge() {
2343
- const { challenge: c, requiredHeader } = await challenge();
2928
+ const { challenge: c, requiredHeader } = await makeChallenge("");
2344
2929
  return { kind: "challenge", challenge: c, requiredHeader, statusCode: 402 };
2345
2930
  }
2931
+ async function rejection(code, detail) {
2932
+ const { challenge: c, requiredHeader } = await makeChallenge("", {
2933
+ error: `${code}: ${detail}`,
2934
+ extensions: { piprail: { code, detail } }
2935
+ });
2936
+ return { kind: "invalid", error: code, detail, challenge: c, requiredHeader, statusCode: 402 };
2937
+ }
2938
+ function fireOnPaid(receipt) {
2939
+ if (options.onPaid) {
2940
+ try {
2941
+ options.onPaid(receipt);
2942
+ } catch {
2943
+ }
2944
+ }
2945
+ }
2346
2946
  async function describe(resourceUrl = "") {
2347
2947
  const specs = await ready();
2348
- const accepts = specs.map((s) => ({
2349
- scheme: "onchain-proof",
2350
- network: s.net.network,
2351
- asset: s.asset,
2352
- payTo: s.payTo,
2353
- amount: s.amountBase.toString(),
2354
- amountFormatted: s.amountFormatted,
2355
- decimals: s.decimals,
2356
- maxTimeoutSeconds,
2357
- ...s.symbol ? { symbol: s.symbol } : {}
2358
- }));
2948
+ const accepts = [];
2949
+ for (const s of specs) {
2950
+ const base2 = {
2951
+ network: s.net.network,
2952
+ asset: s.asset,
2953
+ payTo: s.payTo,
2954
+ amount: s.amountBase.toString(),
2955
+ amountFormatted: s.amountFormatted,
2956
+ decimals: s.decimals,
2957
+ maxTimeoutSeconds,
2958
+ ...s.symbol ? { symbol: s.symbol } : {}
2959
+ };
2960
+ if (s.exact) accepts.push({ scheme: "exact", ...base2 });
2961
+ accepts.push({ scheme: "onchain-proof", ...base2 });
2962
+ }
2359
2963
  return {
2360
2964
  url: resourceUrl,
2361
2965
  ...options.description ? { description: options.description } : {},
2362
2966
  accepts
2363
2967
  };
2364
2968
  }
2365
- async function verify(paymentSignature) {
2366
- const raw = normaliseHeader(paymentSignature);
2367
- if (!raw) return asChallenge();
2368
- const sig = parseSignatureHeader(raw);
2369
- if (!sig || !sig.accepted || typeof sig.accepted.network !== "string" || typeof sig.accepted.asset !== "string") {
2370
- return asChallenge();
2371
- }
2969
+ async function verifyOnchainProof(sig) {
2372
2970
  const specs = await ready();
2373
2971
  const spec = specs.find(
2374
2972
  (s) => s.net.network === sig.accepted.network && s.asset === sig.accepted.asset
2375
2973
  );
2376
2974
  if (!spec) {
2377
- return {
2378
- kind: "invalid",
2379
- error: "transfer_not_found",
2380
- detail: `Proof claims ${sig.accepted.asset} on ${sig.accepted.network}, which this resource doesn't accept (offered: ${specs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`,
2381
- statusCode: 402
2382
- };
2975
+ return rejection(
2976
+ "transfer_not_found",
2977
+ `Proof claims ${sig.accepted.asset} on ${sig.accepted.network}, which this resource doesn't accept (offered: ${specs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
2978
+ );
2383
2979
  }
2384
2980
  const ref = sig.payload.txHash;
2385
- if (await claimTx(ref)) {
2386
- return {
2387
- kind: "invalid",
2388
- error: "tx_already_used",
2389
- detail: `Proof ${ref} was already redeemed.`,
2390
- statusCode: 402
2391
- };
2392
- }
2981
+ if (await claimTx(ref)) return rejection("tx_already_used", `Proof ${ref} was already redeemed.`);
2393
2982
  const result = await spec.net.verify(ref, buildAccept(spec, sig.payload.nonce));
2394
2983
  if (!result.ok) {
2395
2984
  await settleTx(ref, false);
2396
- return {
2397
- kind: "invalid",
2398
- error: result.error,
2399
- detail: result.detail,
2400
- statusCode: 402
2401
- };
2985
+ return rejection(result.error, result.detail);
2402
2986
  }
2403
2987
  await settleTx(ref, true);
2404
- if (options.onPaid) {
2405
- try {
2406
- options.onPaid(result.receipt);
2407
- } catch {
2988
+ fireOnPaid(result.receipt);
2989
+ return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
2990
+ }
2991
+ async function verifyExact(exact) {
2992
+ const specs = await ready();
2993
+ const exactSpecs = specs.filter((s) => s.exact);
2994
+ if (exactSpecs.length === 0) {
2995
+ return rejection("transfer_not_found", "This resource offers no standard `exact` rail.");
2996
+ }
2997
+ const isCaip = exact.network.startsWith("eip155:");
2998
+ let candidates = isCaip ? exactSpecs.filter((s) => s.net.network === exact.network) : exactSpecs;
2999
+ if (exact.asset) {
3000
+ candidates = candidates.filter((s) => s.asset.toLowerCase() === exact.asset.toLowerCase());
3001
+ }
3002
+ let spec = candidates[0];
3003
+ if (!isCaip && !exact.asset && exactSpecs.length > 1) spec = void 0;
3004
+ if (!spec && !isCaip && !exact.asset && exactSpecs.length === 1) spec = exactSpecs[0];
3005
+ if (!spec || !spec.exact) {
3006
+ return rejection(
3007
+ "transfer_not_found",
3008
+ `No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
3009
+ );
3010
+ }
3011
+ const nonce = exact.payload.authorization.nonce;
3012
+ if (await claimTx(nonce)) {
3013
+ return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
3014
+ }
3015
+ const accept = buildExactAccept(spec);
3016
+ const mode = spec.exact.mode;
3017
+ let result;
3018
+ try {
3019
+ if (mode.kind === "self") {
3020
+ result = await spec.net.settleExactSelf({ relayer: mode.relayer, payload: exact.payload, accept });
3021
+ } else {
3022
+ result = await settleViaFacilitator({
3023
+ url: mode.url,
3024
+ ...mode.authHeaders ? { authHeaders: mode.authHeaders } : {},
3025
+ // PipRail always builds a v2-shaped paymentRequirements (CAIP-2 network + `amount`),
3026
+ // so force x402Version:2 — echoing a v1 client's version here would hand the facilitator
3027
+ // a self-inconsistent request (v1 envelope, v2 requirements). The inner payload is
3028
+ // byte-identical across versions, so forwarding it verbatim is fine.
3029
+ x402Version: 2,
3030
+ paymentPayload: exact.raw,
3031
+ paymentRequirements: {
3032
+ scheme: "exact",
3033
+ network: accept.network,
3034
+ asset: accept.asset,
3035
+ amount: accept.amount,
3036
+ payTo: accept.payTo,
3037
+ maxTimeoutSeconds: accept.maxTimeoutSeconds,
3038
+ extra: { name: accept.extra.name, version: accept.extra.version }
3039
+ },
3040
+ receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
3041
+ payerHint: exact.payload.authorization.from
3042
+ });
2408
3043
  }
3044
+ } catch (err) {
3045
+ await settleTx(nonce, false);
3046
+ throw err;
2409
3047
  }
2410
- return {
2411
- kind: "paid",
2412
- receipt: result.receipt,
2413
- receiptHeader: buildReceiptHeader(result.receipt)
2414
- };
3048
+ if (!result.ok) {
3049
+ await settleTx(nonce, false);
3050
+ return rejection(result.error, result.detail);
3051
+ }
3052
+ await settleTx(nonce, true);
3053
+ fireOnPaid(result.receipt);
3054
+ return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
3055
+ }
3056
+ async function verify(paymentSignature) {
3057
+ const raw = normaliseHeader(paymentSignature);
3058
+ if (!raw) return asChallenge();
3059
+ const sig = parseSignatureHeader(raw);
3060
+ if (sig && sig.accepted && typeof sig.accepted.network === "string" && typeof sig.accepted.asset === "string") {
3061
+ return verifyOnchainProof(sig);
3062
+ }
3063
+ const exact = parseExactPaymentHeader(raw);
3064
+ if (exact) return verifyExact(exact);
3065
+ return asChallenge();
2415
3066
  }
2416
3067
  return { challenge, verify, describe };
2417
3068
  }
@@ -2420,14 +3071,20 @@ function requirePayment(options) {
2420
3071
  return async (req, res, next) => {
2421
3072
  let result;
2422
3073
  try {
2423
- result = await gate.verify(req.headers[HEADER_SIGNATURE]);
3074
+ result = await gate.verify(req.headers[HEADER_SIGNATURE] ?? req.headers[HEADER_SIGNATURE_V1]);
2424
3075
  } catch (err) {
3076
+ if (err instanceof SettlementError) {
3077
+ res.status(502);
3078
+ res.json({ x402Version: 2, error: "settlement_failed", detail: err.message });
3079
+ return;
3080
+ }
2425
3081
  next(err);
2426
3082
  return;
2427
3083
  }
2428
3084
  switch (result.kind) {
2429
3085
  case "paid":
2430
3086
  res.setHeader(HEADER_RESPONSE, result.receiptHeader);
3087
+ res.setHeader(HEADER_RESPONSE_V1, result.receiptHeader);
2431
3088
  return next();
2432
3089
  case "challenge":
2433
3090
  res.setHeader(HEADER_REQUIRED, result.requiredHeader);
@@ -2435,8 +3092,9 @@ function requirePayment(options) {
2435
3092
  res.json(result.challenge);
2436
3093
  return;
2437
3094
  case "invalid":
3095
+ res.setHeader(HEADER_REQUIRED, result.requiredHeader);
2438
3096
  res.status(result.statusCode);
2439
- res.json(toInvalidBody(result));
3097
+ res.json(result.challenge);
2440
3098
  return;
2441
3099
  }
2442
3100
  };
@@ -2446,104 +3104,6 @@ function normaliseHeader(value) {
2446
3104
  return value;
2447
3105
  }
2448
3106
 
2449
- // src/drivers/evm/exact.ts
2450
- var EXACT_NETWORK_SLUGS = {
2451
- ethereum: 1,
2452
- base: 8453,
2453
- "base-sepolia": 84532,
2454
- arbitrum: 42161,
2455
- optimism: 10,
2456
- polygon: 137,
2457
- avalanche: 43114
2458
- };
2459
- function chainIdForExactNetwork(slug) {
2460
- return EXACT_NETWORK_SLUGS[slug] ?? null;
2461
- }
2462
- var EIP3009_TYPES = {
2463
- TransferWithAuthorization: [
2464
- { name: "from", type: "address" },
2465
- { name: "to", type: "address" },
2466
- { name: "value", type: "uint256" },
2467
- { name: "validAfter", type: "uint256" },
2468
- { name: "validBefore", type: "uint256" },
2469
- { name: "nonce", type: "bytes32" }
2470
- ]
2471
- };
2472
- function parseExactRequirements(body) {
2473
- if (!body || typeof body !== "object") return null;
2474
- const accepts = body.accepts;
2475
- if (!Array.isArray(accepts)) return null;
2476
- const out = [];
2477
- for (const raw of accepts) {
2478
- if (!raw || typeof raw !== "object") continue;
2479
- const a = raw;
2480
- if (a.scheme !== "exact") continue;
2481
- const amount = a.maxAmountRequired ?? a.amount;
2482
- if (typeof a.network !== "string" || typeof amount !== "string" || typeof a.asset !== "string" || typeof a.payTo !== "string") {
2483
- continue;
2484
- }
2485
- out.push({
2486
- scheme: "exact",
2487
- network: a.network,
2488
- maxAmountRequired: amount,
2489
- asset: a.asset,
2490
- payTo: a.payTo,
2491
- maxTimeoutSeconds: typeof a.maxTimeoutSeconds === "number" ? a.maxTimeoutSeconds : 600,
2492
- ...a.extra && typeof a.extra === "object" ? { extra: a.extra } : {},
2493
- ...typeof a.description === "string" ? { description: a.description } : {},
2494
- ...typeof a.resource === "string" ? { resource: a.resource } : {}
2495
- });
2496
- }
2497
- return out;
2498
- }
2499
- async function buildExactAuthorization(params) {
2500
- const { account, accept, chainId, now, nonce } = params;
2501
- if (!account.signTypedData) {
2502
- throw new Error("buildExactAuthorization: the account cannot sign EIP-712 typed data.");
2503
- }
2504
- const authorization = {
2505
- from: account.address,
2506
- to: accept.payTo,
2507
- value: accept.maxAmountRequired,
2508
- validAfter: "0",
2509
- validBefore: String(now + accept.maxTimeoutSeconds),
2510
- nonce
2511
- };
2512
- const signature = await account.signTypedData({
2513
- domain: {
2514
- name: accept.extra?.name ?? "USD Coin",
2515
- version: accept.extra?.version ?? "2",
2516
- chainId,
2517
- verifyingContract: accept.asset
2518
- },
2519
- types: EIP3009_TYPES,
2520
- primaryType: "TransferWithAuthorization",
2521
- message: {
2522
- from: authorization.from,
2523
- to: authorization.to,
2524
- value: BigInt(authorization.value),
2525
- validAfter: BigInt(authorization.validAfter),
2526
- validBefore: BigInt(authorization.validBefore),
2527
- nonce: authorization.nonce
2528
- }
2529
- });
2530
- return { authorization, signature };
2531
- }
2532
- function base64(str) {
2533
- if (typeof btoa === "function") return btoa(str);
2534
- if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
2535
- throw new Error("No base64 encoder available in this runtime.");
2536
- }
2537
- function encodeXPaymentHeader(input) {
2538
- const payload = {
2539
- x402Version: input.x402Version ?? 1,
2540
- scheme: "exact",
2541
- network: input.network,
2542
- payload: { signature: input.signature, authorization: input.authorization }
2543
- };
2544
- return base64(JSON.stringify(payload));
2545
- }
2546
-
2547
3107
  // src/discovery.ts
2548
3108
  var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2549
3109
  function pathOf(url) {
@@ -2604,6 +3164,11 @@ export {
2604
3164
  EIP3009_TYPES,
2605
3165
  EXACT_NETWORK_SLUGS,
2606
3166
  GENERATOR,
3167
+ HEADER_REQUIRED,
3168
+ HEADER_RESPONSE,
3169
+ HEADER_RESPONSE_V1,
3170
+ HEADER_SIGNATURE,
3171
+ HEADER_SIGNATURE_V1,
2607
3172
  InsufficientFundsError,
2608
3173
  InvalidEnvelopeError,
2609
3174
  MaxRetriesExceededError,
@@ -2615,6 +3180,7 @@ export {
2615
3180
  PipRailClient,
2616
3181
  PipRailError,
2617
3182
  RecipientNotReadyError,
3183
+ SettlementError,
2618
3184
  UnknownTokenError,
2619
3185
  UnsupportedNetworkError,
2620
3186
  WrongChainError,
@@ -2628,22 +3194,26 @@ export {
2628
3194
  buildX402DnsTxt,
2629
3195
  chainIdForExactNetwork,
2630
3196
  createPaymentGate,
3197
+ eip3009Abi,
2631
3198
  encodeXPaymentHeader,
2632
3199
  evaluatePolicy,
2633
3200
  normalizeNetwork,
2634
3201
  parseChallenge,
3202
+ parseExactPaymentHeader,
2635
3203
  parseExactRequirements,
2636
3204
  parseReceipt,
2637
3205
  parseSignatureHeader,
2638
3206
  paymentTools,
2639
3207
  pickAccept,
2640
3208
  planAcross,
3209
+ readExactDomain,
2641
3210
  register402Index,
2642
3211
  registerDriver,
2643
3212
  registerX402Scan,
2644
3213
  requirePayment,
2645
3214
  resolveChain,
2646
3215
  searchOpenIndexes,
3216
+ settleViaFacilitator,
2647
3217
  toInsufficientFundsError,
2648
3218
  toInvalidBody
2649
3219
  };