@piprail/sdk 1.15.1 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } async function _asyncNullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return await rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } async function _asyncOptionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = await fn(value); } else if (op === 'call' || op === 'optionalCall') { value = await fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2;
2
2
 
3
3
 
4
4
 
@@ -136,9 +136,15 @@ var CHAINS = {
136
136
  bnb: {
137
137
  chain: _chains.bsc,
138
138
  tokens: {
139
- // Binance-Peg tokens on BNB Chain are 18 decimals (not the usual 6).
139
+ // Binance-Peg tokens on BNB Chain are 18 decimals (not the usual 6). USDC/USDT here are
140
+ // Binance-Peg (NOT EIP-3009) → the `exact` rail uses Permit2.
140
141
  USDC: { address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d", decimals: 18, symbol: "USDC" },
141
- USDT: { address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18, symbol: "USDT" }
142
+ USDT: { address: "0x55d398326f99059fF775485246999027B3197955", decimals: 18, symbol: "USDT" },
143
+ // FDUSD + USD1 ARE EIP-3009 (transferWithAuthorization) → the `exact` rail uses the gasless,
144
+ // no-Permit2-approve path. Both hardcode EIP-712 domain version "1" (no version() — the SDK
145
+ // derives it from DOMAIN_SEPARATOR). Verified on-chain (symbol/decimals/domain match).
146
+ FDUSD: { address: "0xc5f0f7b66764F6ec8C8Dff7BA683102295E16409", decimals: 18, symbol: "FDUSD" },
147
+ USD1: { address: "0x8d0D000Ee44948FC98c9B98A4FA4921476f08B0d", decimals: 18, symbol: "USD1" }
142
148
  }
143
149
  },
144
150
  avalanche: {
@@ -498,6 +504,9 @@ function sumTransfersTo(logs, asset, payTo) {
498
504
 
499
505
 
500
506
 
507
+
508
+
509
+
501
510
  var EXACT_NETWORK_SLUGS = {
502
511
  ethereum: 1,
503
512
  base: 8453,
@@ -505,7 +514,17 @@ var EXACT_NETWORK_SLUGS = {
505
514
  arbitrum: 42161,
506
515
  optimism: 10,
507
516
  polygon: 137,
508
- avalanche: 43114
517
+ avalanche: 43114,
518
+ bnb: 56,
519
+ bsc: 56,
520
+ // EIP-3009 USDC verified on-chain (authorizationState present) — gasless, no proxy:
521
+ sonic: 146,
522
+ linea: 59144,
523
+ celo: 42220,
524
+ unichain: 130,
525
+ worldchain: 480,
526
+ sei: 1329,
527
+ hyperevm: 999
509
528
  };
510
529
  function chainIdForExactNetwork(slug) {
511
530
  return _nullishCoalesce(EXACT_NETWORK_SLUGS[slug], () => ( null));
@@ -690,10 +709,25 @@ var eip3009Abi = [
690
709
  outputs: [{ type: "bool" }]
691
710
  },
692
711
  { type: "function", name: "name", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
693
- { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] }
712
+ { type: "function", name: "version", stateMutability: "view", inputs: [], outputs: [{ type: "string" }] },
713
+ // EIP-3009 tokens that DON'T expose version() still expose DOMAIN_SEPARATOR — we match it to
714
+ // DERIVE the hardcoded domain version (e.g. FDUSD / USD1 on BNB Chain both use "1").
715
+ { type: "function", name: "DOMAIN_SEPARATOR", stateMutability: "view", inputs: [], outputs: [{ type: "bytes32" }] }
694
716
  ];
695
717
  var ZERO_ADDR = "0x0000000000000000000000000000000000000000";
696
718
  var ZERO_NONCE = `0x${"00".repeat(32)}`;
719
+ var EIP712_DOMAIN_TYPEHASH = _viem.keccak256.call(void 0,
720
+ _viem.toHex.call(void 0, "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
721
+ );
722
+ var EXACT_DOMAIN_VERSION_CANDIDATES = ["1", "2"];
723
+ function eip712DomainSeparator(name, version, chainId, verifyingContract) {
724
+ return _viem.keccak256.call(void 0,
725
+ _viem.encodeAbiParameters.call(void 0,
726
+ [{ type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" }, { type: "uint256" }, { type: "address" }],
727
+ [EIP712_DOMAIN_TYPEHASH, _viem.keccak256.call(void 0, _viem.toHex.call(void 0, name)), _viem.keccak256.call(void 0, _viem.toHex.call(void 0, version)), BigInt(chainId), verifyingContract]
728
+ )
729
+ );
730
+ }
697
731
  async function readExactDomain(publicClient, asset) {
698
732
  if (asset === "native") return null;
699
733
  let token;
@@ -702,12 +736,10 @@ async function readExactDomain(publicClient, asset) {
702
736
  } catch (e7) {
703
737
  return null;
704
738
  }
739
+ let name;
705
740
  try {
706
- const [name, version] = await Promise.all([
741
+ const [n] = await Promise.all([
707
742
  publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "name" }),
708
- publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "version" }),
709
- // The EIP-3009 probe: this view exists only on EIP-3009 tokens; it reverts on
710
- // a plain ERC-20 / USDT, marking the token as not exact-payable.
711
743
  publicClient.readContract({
712
744
  address: token,
713
745
  abi: eip3009Abi,
@@ -715,11 +747,34 @@ async function readExactDomain(publicClient, asset) {
715
747
  args: [ZERO_ADDR, ZERO_NONCE]
716
748
  })
717
749
  ]);
718
- if (typeof name !== "string" || typeof version !== "string" || !name || !version) return null;
719
- return { name, version };
750
+ if (typeof n !== "string" || !n) return null;
751
+ name = n;
720
752
  } catch (e8) {
721
753
  return null;
722
754
  }
755
+ try {
756
+ const version = await publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "version" });
757
+ if (typeof version === "string" && version) return { name, version };
758
+ } catch (e9) {
759
+ }
760
+ return deriveExactDomainVersion(publicClient, token, name);
761
+ }
762
+ async function deriveExactDomainVersion(publicClient, token, name) {
763
+ let onchain;
764
+ let chainId;
765
+ try {
766
+ onchain = await publicClient.readContract({ address: token, abi: eip3009Abi, functionName: "DOMAIN_SEPARATOR" });
767
+ chainId = await _asyncNullishCoalesce(await _asyncOptionalChain([publicClient, 'access', async _6 => _6.chain, 'optionalAccess', async _7 => _7.id]), async () => ( await publicClient.getChainId()));
768
+ } catch (e10) {
769
+ return null;
770
+ }
771
+ const target = onchain.toLowerCase();
772
+ for (const version of EXACT_DOMAIN_VERSION_CANDIDATES) {
773
+ if (eip712DomainSeparator(name, version, chainId, token).toLowerCase() === target) {
774
+ return { name, version };
775
+ }
776
+ }
777
+ return null;
723
778
  }
724
779
  function shorten(msg) {
725
780
  const oneLine = msg.replace(/\s+/g, " ").trim();
@@ -765,7 +820,7 @@ async function verifyAndSettleExactEvm(input) {
765
820
  let fromCode;
766
821
  try {
767
822
  fromCode = await publicClient.getCode({ address: from });
768
- } catch (e9) {
823
+ } catch (e11) {
769
824
  return { ok: false, error: "tx_not_found", detail: `Could not read code at ${from} (transient RPC) \u2014 retry.` };
770
825
  }
771
826
  const isContractWallet = Boolean(fromCode && fromCode !== "0x");
@@ -801,7 +856,7 @@ async function verifyAndSettleExactEvm(input) {
801
856
  if (used) {
802
857
  return { ok: false, error: "tx_already_used", detail: `Authorization nonce ${nonce} already used or canceled on-chain.` };
803
858
  }
804
- } catch (e10) {
859
+ } catch (e12) {
805
860
  return { ok: false, error: "tx_not_found", detail: "Could not read authorizationState (transient RPC) \u2014 retry." };
806
861
  }
807
862
  const baseArgs = [from, to, value, validAfter, validBefore, nonce];
@@ -874,6 +929,377 @@ async function verifyAndSettleExactEvm(input) {
874
929
  };
875
930
  }
876
931
 
932
+ // src/drivers/evm/permit2.ts
933
+
934
+
935
+
936
+
937
+
938
+
939
+ var PERMIT2_ADDRESS = "0x000000000022D473030F116dDEE9F6B43aC78BA3";
940
+ var X402_EXACT_PERMIT2_PROXY = "0x402085c248EeA27D92E8b30b2C58ed07f9E20001";
941
+ var PERMIT2_PROXY_CHAIN_IDS = /* @__PURE__ */ new Set([
942
+ 1,
943
+ // Ethereum
944
+ 8453,
945
+ // Base
946
+ 84532,
947
+ // Base Sepolia
948
+ 42161,
949
+ // Arbitrum
950
+ 10,
951
+ // Optimism
952
+ 137,
953
+ // Polygon
954
+ 43114,
955
+ // Avalanche
956
+ 56,
957
+ // BNB
958
+ 42220,
959
+ // Celo
960
+ 480,
961
+ // World Chain
962
+ 1329,
963
+ // Sei
964
+ 999,
965
+ // HyperEVM
966
+ 143
967
+ // Monad
968
+ ]);
969
+ function isPermit2ProxyChain(chainId) {
970
+ return PERMIT2_PROXY_CHAIN_IDS.has(chainId);
971
+ }
972
+ var PERMIT2_WITNESS_TYPES = {
973
+ PermitWitnessTransferFrom: [
974
+ { name: "permitted", type: "TokenPermissions" },
975
+ { name: "spender", type: "address" },
976
+ { name: "nonce", type: "uint256" },
977
+ { name: "deadline", type: "uint256" },
978
+ { name: "witness", type: "Witness" }
979
+ ],
980
+ TokenPermissions: [
981
+ { name: "token", type: "address" },
982
+ { name: "amount", type: "uint256" }
983
+ ],
984
+ Witness: [
985
+ { name: "to", type: "address" },
986
+ { name: "validAfter", type: "uint256" }
987
+ ]
988
+ };
989
+ var x402Permit2ProxyAbi = [
990
+ {
991
+ type: "function",
992
+ name: "settle",
993
+ stateMutability: "nonpayable",
994
+ outputs: [],
995
+ inputs: [
996
+ {
997
+ name: "permit",
998
+ type: "tuple",
999
+ components: [
1000
+ {
1001
+ name: "permitted",
1002
+ type: "tuple",
1003
+ components: [
1004
+ { name: "token", type: "address" },
1005
+ { name: "amount", type: "uint256" }
1006
+ ]
1007
+ },
1008
+ { name: "nonce", type: "uint256" },
1009
+ { name: "deadline", type: "uint256" }
1010
+ ]
1011
+ },
1012
+ { name: "owner", type: "address" },
1013
+ {
1014
+ name: "witness",
1015
+ type: "tuple",
1016
+ components: [
1017
+ { name: "to", type: "address" },
1018
+ { name: "validAfter", type: "uint256" }
1019
+ ]
1020
+ },
1021
+ { name: "signature", type: "bytes" }
1022
+ ]
1023
+ }
1024
+ ];
1025
+ var permit2NonceBitmapAbi = [
1026
+ {
1027
+ type: "function",
1028
+ name: "nonceBitmap",
1029
+ stateMutability: "view",
1030
+ inputs: [
1031
+ { name: "owner", type: "address" },
1032
+ { name: "word", type: "uint256" }
1033
+ ],
1034
+ outputs: [{ type: "uint256" }]
1035
+ }
1036
+ ];
1037
+ function shorten2(msg) {
1038
+ const oneLine = msg.replace(/\s+/g, " ").trim();
1039
+ return oneLine.length > 200 ? `${oneLine.slice(0, 200)}\u2026` : oneLine;
1040
+ }
1041
+ function randomPermit2Nonce() {
1042
+ const g = globalThis.crypto;
1043
+ if (!_optionalChain([g, 'optionalAccess', _8 => _8.getRandomValues])) {
1044
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
1045
+ "this runtime lacks Web Crypto (globalThis.crypto.getRandomValues); the permit2 rail needs a CSPRNG nonce."
1046
+ );
1047
+ }
1048
+ const raw = new Uint8Array(32);
1049
+ g.getRandomValues(raw);
1050
+ return BigInt(`0x${[...raw].map((b) => b.toString(16).padStart(2, "0")).join("")}`);
1051
+ }
1052
+ async function ensurePermit2Allowance(input) {
1053
+ const { publicClient, walletClient, account, chain, token, amount } = input;
1054
+ let allowance;
1055
+ try {
1056
+ allowance = await publicClient.readContract({
1057
+ address: token,
1058
+ abi: _viem.erc20Abi,
1059
+ functionName: "allowance",
1060
+ args: [account.address, PERMIT2_ADDRESS]
1061
+ });
1062
+ } catch (err) {
1063
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
1064
+ `permit2: couldn't read the Permit2 allowance for ${token} (${shorten2(err instanceof Error ? err.message : String(err))}).`
1065
+ );
1066
+ }
1067
+ if (allowance >= amount) return void 0;
1068
+ try {
1069
+ const hash = await walletClient.writeContract({
1070
+ account,
1071
+ chain,
1072
+ address: token,
1073
+ abi: _viem.erc20Abi,
1074
+ functionName: "approve",
1075
+ args: [PERMIT2_ADDRESS, _viem.maxUint256]
1076
+ });
1077
+ await publicClient.waitForTransactionReceipt({ hash, confirmations: 1 });
1078
+ return hash;
1079
+ } catch (err) {
1080
+ throw _nullishCoalesce(_chunkPA6YD3HLcjs.toInsufficientFundsError.call(void 0, err), () => ( new (0, _chunkPA6YD3HLcjs.SettlementError)(
1081
+ `permit2: the one-time Permit2 approval for ${token} failed to broadcast (${shorten2(err instanceof Error ? err.message : String(err))}).`,
1082
+ { cause: err }
1083
+ )));
1084
+ }
1085
+ }
1086
+ async function payPermit2Evm(input) {
1087
+ const { publicClient, walletClient, account, chainId, chain, accept } = input;
1088
+ let code;
1089
+ try {
1090
+ code = await publicClient.getCode({ address: account.address });
1091
+ } catch (e13) {
1092
+ code = void 0;
1093
+ }
1094
+ if (code && code !== "0x") {
1095
+ throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
1096
+ `permit2 buyer rail requires an EOA signer; ${account.address} is a contract / EIP-1271 / EIP-7702-delegated account. Pay via onchain-proof.`
1097
+ );
1098
+ }
1099
+ const token = _viem.getAddress.call(void 0, accept.asset);
1100
+ const payTo = _viem.getAddress.call(void 0, accept.payTo);
1101
+ const value = BigInt(accept.amount);
1102
+ const approvalTx = await ensurePermit2Allowance({
1103
+ publicClient,
1104
+ walletClient,
1105
+ account,
1106
+ chain,
1107
+ token,
1108
+ amount: value
1109
+ });
1110
+ const nonce = randomPermit2Nonce();
1111
+ const deadline = BigInt(Math.floor(Date.now() / 1e3) + accept.maxTimeoutSeconds);
1112
+ const validAfter = 0n;
1113
+ const spender = _viem.getAddress.call(void 0, X402_EXACT_PERMIT2_PROXY);
1114
+ const from = account.address;
1115
+ const signature = await walletClient.signTypedData({
1116
+ account,
1117
+ domain: { name: "Permit2", chainId, verifyingContract: PERMIT2_ADDRESS },
1118
+ types: PERMIT2_WITNESS_TYPES,
1119
+ primaryType: "PermitWitnessTransferFrom",
1120
+ message: {
1121
+ permitted: { token, amount: value },
1122
+ spender,
1123
+ nonce,
1124
+ deadline,
1125
+ witness: { to: payTo, validAfter }
1126
+ }
1127
+ });
1128
+ const permit2Authorization = {
1129
+ permitted: { token, amount: value.toString() },
1130
+ from,
1131
+ spender,
1132
+ nonce: nonce.toString(),
1133
+ deadline: deadline.toString(),
1134
+ witness: { to: payTo, validAfter: validAfter.toString() }
1135
+ };
1136
+ return {
1137
+ payload: { signature, permit2Authorization },
1138
+ payerFrom: from,
1139
+ nonce: nonce.toString(),
1140
+ ...approvalTx ? { approvalTx } : {}
1141
+ };
1142
+ }
1143
+ async function verifyAndSettlePermit2Evm(input) {
1144
+ const { publicClient, walletClient, account, chain, payload, accept } = input;
1145
+ const token = _viem.getAddress.call(void 0, accept.asset);
1146
+ const payTo = _viem.getAddress.call(void 0, accept.payTo);
1147
+ const requiredAmount = BigInt(accept.amount);
1148
+ const proxy = _viem.getAddress.call(void 0, X402_EXACT_PERMIT2_PROXY);
1149
+ let from;
1150
+ let spender;
1151
+ let permittedToken;
1152
+ let witnessTo;
1153
+ let permittedAmount;
1154
+ let nonce;
1155
+ let deadline;
1156
+ let validAfter;
1157
+ const signature = payload.signature;
1158
+ try {
1159
+ const pa = payload.permit2Authorization;
1160
+ from = _viem.getAddress.call(void 0, pa.from);
1161
+ spender = _viem.getAddress.call(void 0, pa.spender);
1162
+ permittedToken = _viem.getAddress.call(void 0, pa.permitted.token);
1163
+ witnessTo = _viem.getAddress.call(void 0, pa.witness.to);
1164
+ permittedAmount = BigInt(pa.permitted.amount);
1165
+ nonce = BigInt(pa.nonce);
1166
+ deadline = BigInt(pa.deadline);
1167
+ validAfter = BigInt(pa.witness.validAfter);
1168
+ if (!/^0x[0-9a-fA-F]+$/.test(signature)) throw new Error("signature must be hex");
1169
+ } catch (err) {
1170
+ return {
1171
+ ok: false,
1172
+ error: "signature_invalid",
1173
+ detail: `Malformed permit2 authorization: ${err instanceof Error ? err.message : String(err)}.`
1174
+ };
1175
+ }
1176
+ if (witnessTo !== payTo) {
1177
+ return { ok: false, error: "wrong_recipient", detail: `Authorization pays witness.to ${witnessTo}, not ${payTo}.` };
1178
+ }
1179
+ if (permittedToken !== token) {
1180
+ return { ok: false, error: "signature_invalid", detail: `Authorization permits token ${permittedToken}, not the rail's ${token}.` };
1181
+ }
1182
+ if (spender !== proxy) {
1183
+ return { ok: false, error: "signature_invalid", detail: `Authorization spender ${spender} is not the x402ExactPermit2Proxy ${proxy}; it can't be settled here.` };
1184
+ }
1185
+ if (permittedAmount < requiredAmount) {
1186
+ return { ok: false, error: "amount_too_low", detail: `Permitted ${permittedAmount}, required ${requiredAmount}.` };
1187
+ }
1188
+ const now = BigInt(Math.floor(Date.now() / 1e3));
1189
+ if (deadline <= now) {
1190
+ return { ok: false, error: "payment_expired", detail: `Permit2 deadline ${deadline} <= now ${now}.` };
1191
+ }
1192
+ let fromCode;
1193
+ try {
1194
+ fromCode = await publicClient.getCode({ address: from });
1195
+ } catch (e14) {
1196
+ return { ok: false, error: "tx_not_found", detail: `Could not read code at ${from} (transient RPC) \u2014 retry.` };
1197
+ }
1198
+ if (!(fromCode && fromCode !== "0x")) {
1199
+ let recovered;
1200
+ try {
1201
+ recovered = await _viem.recoverTypedDataAddress.call(void 0, {
1202
+ domain: { name: "Permit2", chainId: chain.id, verifyingContract: PERMIT2_ADDRESS },
1203
+ types: PERMIT2_WITNESS_TYPES,
1204
+ primaryType: "PermitWitnessTransferFrom",
1205
+ message: {
1206
+ permitted: { token: permittedToken, amount: permittedAmount },
1207
+ spender,
1208
+ nonce,
1209
+ deadline,
1210
+ witness: { to: witnessTo, validAfter }
1211
+ },
1212
+ signature
1213
+ });
1214
+ } catch (err) {
1215
+ return { ok: false, error: "signature_invalid", detail: `Not a valid EIP-712 signature: ${shorten2(err instanceof Error ? err.message : String(err))}.` };
1216
+ }
1217
+ if (recovered !== from) {
1218
+ return { ok: false, error: "signature_invalid", detail: `Signature recovered to ${recovered}, not the authorizer ${from}.` };
1219
+ }
1220
+ }
1221
+ try {
1222
+ const word = nonce >> 8n;
1223
+ const bit = nonce & 0xffn;
1224
+ const bitmap = await publicClient.readContract({
1225
+ address: PERMIT2_ADDRESS,
1226
+ abi: permit2NonceBitmapAbi,
1227
+ functionName: "nonceBitmap",
1228
+ args: [from, word]
1229
+ });
1230
+ if ((bitmap >> bit & 1n) === 1n) {
1231
+ return { ok: false, error: "tx_already_used", detail: `Permit2 nonce ${nonce} already used or invalidated for ${from}.` };
1232
+ }
1233
+ } catch (e15) {
1234
+ return { ok: false, error: "tx_not_found", detail: "Could not read the Permit2 nonce bitmap (transient RPC) \u2014 retry." };
1235
+ }
1236
+ const settleArgs = [
1237
+ { permitted: { token: permittedToken, amount: permittedAmount }, nonce, deadline },
1238
+ from,
1239
+ { to: witnessTo, validAfter },
1240
+ signature
1241
+ ];
1242
+ try {
1243
+ await publicClient.simulateContract({
1244
+ account,
1245
+ address: proxy,
1246
+ abi: x402Permit2ProxyAbi,
1247
+ functionName: "settle",
1248
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1249
+ args: settleArgs
1250
+ });
1251
+ } catch (err) {
1252
+ const msg = err instanceof Error ? err.message : String(err);
1253
+ if (/nonce|invalidated|used/i.test(msg)) return { ok: false, error: "tx_already_used", detail: "Permit2 nonce is used or invalidated." };
1254
+ if (/expired|deadline|too early|not yet/i.test(msg)) return { ok: false, error: "payment_expired", detail: shorten2(msg) };
1255
+ if (/signature/i.test(msg)) return { ok: false, error: "signature_invalid", detail: shorten2(msg) };
1256
+ return { ok: false, error: "tx_reverted", detail: `permit2 settle would revert: ${shorten2(msg)}` };
1257
+ }
1258
+ let txHash;
1259
+ try {
1260
+ txHash = await walletClient.writeContract({
1261
+ account,
1262
+ chain,
1263
+ address: proxy,
1264
+ abi: x402Permit2ProxyAbi,
1265
+ functionName: "settle",
1266
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1267
+ args: settleArgs
1268
+ });
1269
+ } catch (err) {
1270
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
1271
+ `permit2 settle: the merchant relayer failed to broadcast the proxy settle (${shorten2(err instanceof Error ? err.message : String(err))}). The payer's signature is still valid and its nonce unused \u2014 fund/fix the relayer and the payer can retry.`,
1272
+ { cause: err }
1273
+ );
1274
+ }
1275
+ try {
1276
+ const confirmations = _nullishCoalesce(accept.extra.minConfirmations, () => ( 1));
1277
+ const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations });
1278
+ if (receipt.status !== "success") {
1279
+ return { ok: false, error: "tx_reverted", detail: `Settlement tx ${txHash} reverted on-chain.` };
1280
+ }
1281
+ } catch (err) {
1282
+ throw new (0, _chunkPA6YD3HLcjs.SettlementError)(
1283
+ `permit2 settle: broadcast ${txHash} but couldn't confirm it (${shorten2(err instanceof Error ? err.message : String(err))}).`,
1284
+ { cause: err }
1285
+ );
1286
+ }
1287
+ return {
1288
+ ok: true,
1289
+ receipt: {
1290
+ scheme: "exact",
1291
+ success: true,
1292
+ network: accept.network,
1293
+ transaction: txHash,
1294
+ asset: accept.asset,
1295
+ amount: accept.amount,
1296
+ payer: from,
1297
+ payTo: accept.payTo,
1298
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
1299
+ }
1300
+ };
1301
+ }
1302
+
877
1303
  // src/x402.ts
878
1304
  var HEADER_REQUIRED = "payment-required";
879
1305
  var HEADER_SIGNATURE = "payment-signature";
@@ -903,7 +1329,7 @@ function decodeBase64(b64) {
903
1329
  function fromBase64Json(b64) {
904
1330
  try {
905
1331
  return JSON.parse(decodeBase64(b64));
906
- } catch (e11) {
1332
+ } catch (e16) {
907
1333
  return null;
908
1334
  }
909
1335
  }
@@ -940,7 +1366,7 @@ async function parseChallenge(response) {
940
1366
  try {
941
1367
  const body = await response.clone().json();
942
1368
  if (isValidChallenge(body)) return body;
943
- } catch (e12) {
1369
+ } catch (e17) {
944
1370
  }
945
1371
  return null;
946
1372
  }
@@ -968,7 +1394,7 @@ function parseSignatureHeader(value) {
968
1394
  if (!parsed || typeof parsed !== "object") return null;
969
1395
  const v = parsed;
970
1396
  const accepted = v.accepted;
971
- const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _6 => _6.scheme]), () => ( v.scheme));
1397
+ const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _9 => _9.scheme]), () => ( v.scheme));
972
1398
  if (scheme !== "onchain-proof") return null;
973
1399
  const payload = v.payload;
974
1400
  if (!payload || typeof payload.txHash !== "string" || typeof payload.nonce !== "string") {
@@ -981,30 +1407,45 @@ function parseExactPaymentHeader(value) {
981
1407
  if (!parsed || typeof parsed !== "object") return null;
982
1408
  const v = parsed;
983
1409
  const accepted = _nullishCoalesce(v.accepted, () => ( null));
984
- const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _7 => _7.scheme]), () => ( v.scheme));
1410
+ const scheme = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _10 => _10.scheme]), () => ( v.scheme));
985
1411
  if (scheme !== "exact") return null;
986
- const network = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _8 => _8.network]), () => ( v.network));
1412
+ const network = _nullishCoalesce(_optionalChain([accepted, 'optionalAccess', _11 => _11.network]), () => ( v.network));
987
1413
  if (typeof network !== "string") return null;
988
1414
  const payload = v.payload;
989
1415
  if (!payload || typeof payload !== "object") return null;
990
1416
  const signature = payload.signature;
991
- const authorization = payload.authorization;
992
- if (typeof signature !== "string" || !authorization || typeof authorization !== "object") return null;
993
- for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
994
- if (typeof authorization[k] !== "string") return null;
995
- }
1417
+ if (typeof signature !== "string") return null;
996
1418
  const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
997
1419
  const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
998
- return {
999
- x402Version,
1000
- network,
1001
- ...asset ? { asset } : {},
1002
- payload: {
1003
- signature,
1004
- authorization
1005
- },
1006
- raw: v
1007
- };
1420
+ const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
1421
+ const authorization = payload.authorization;
1422
+ if (authorization && typeof authorization === "object") {
1423
+ for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
1424
+ if (typeof authorization[k] !== "string") return null;
1425
+ }
1426
+ return {
1427
+ ...base2,
1428
+ method: "eip3009",
1429
+ payload: { signature, authorization }
1430
+ };
1431
+ }
1432
+ const p2 = payload.permit2Authorization;
1433
+ if (p2 && typeof p2 === "object") {
1434
+ const permitted = p2.permitted;
1435
+ const witness = p2.witness;
1436
+ if (!permitted || typeof permitted !== "object" || !witness || typeof witness !== "object") return null;
1437
+ if (typeof permitted.token !== "string" || typeof permitted.amount !== "string") return null;
1438
+ if (typeof witness.to !== "string" || typeof witness.validAfter !== "string") return null;
1439
+ for (const k of ["from", "spender", "nonce", "deadline"]) {
1440
+ if (typeof p2[k] !== "string") return null;
1441
+ }
1442
+ return {
1443
+ ...base2,
1444
+ method: "permit2",
1445
+ payload: { signature, permit2Authorization: p2 }
1446
+ };
1447
+ }
1448
+ return null;
1008
1449
  }
1009
1450
  function isValidChallenge(value) {
1010
1451
  if (!value || typeof value !== "object") return false;
@@ -1040,7 +1481,7 @@ var evmDriver = {
1040
1481
  let resolved;
1041
1482
  try {
1042
1483
  resolved = resolveChain(opts.chain, opts.rpcUrl);
1043
- } catch (e13) {
1484
+ } catch (e18) {
1044
1485
  return null;
1045
1486
  }
1046
1487
  return makeEvmNetwork(resolved);
@@ -1096,7 +1537,7 @@ function makeEvmNetwork(resolved) {
1096
1537
  let normalized;
1097
1538
  try {
1098
1539
  normalized = _viem.getAddress.call(void 0, asset);
1099
- } catch (e14) {
1540
+ } catch (e19) {
1100
1541
  return null;
1101
1542
  }
1102
1543
  for (const info of Object.values(resolved.tokens)) {
@@ -1157,12 +1598,13 @@ function makeEvmNetwork(resolved) {
1157
1598
  async estimateCost(accept) {
1158
1599
  const { decimals, symbol } = resolved.chain.nativeCurrency;
1159
1600
  if (accept.scheme === "exact") {
1601
+ const permit2 = accept.extra.assetTransferMethod === "permit2";
1160
1602
  return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
1161
1603
  symbol,
1162
1604
  decimals,
1163
1605
  fee: 0n,
1164
1606
  basis: "estimated",
1165
- detail: "gasless \u2014 the server/facilitator settles the signed authorization"
1607
+ detail: permit2 ? "gasless after a one-time Permit2 approval; the server/facilitator settles the signed authorization" : "gasless \u2014 the server/facilitator settles the signed authorization"
1166
1608
  });
1167
1609
  }
1168
1610
  const gasLimit = accept.asset === "native" ? 21000n : 65000n;
@@ -1175,7 +1617,7 @@ function makeEvmNetwork(resolved) {
1175
1617
  basis: "estimated",
1176
1618
  detail: `~${gasLimit} gas @ ${gasPrice} wei/gas`
1177
1619
  });
1178
- } catch (e15) {
1620
+ } catch (e20) {
1179
1621
  const gasPrice = 5000000000n;
1180
1622
  return _chunkPA6YD3HLcjs.nativeCost.call(void 0, {
1181
1623
  symbol,
@@ -1198,7 +1640,7 @@ function makeEvmNetwork(resolved) {
1198
1640
  functionName: "balanceOf",
1199
1641
  args: [owner]
1200
1642
  });
1201
- } catch (e16) {
1643
+ } catch (e21) {
1202
1644
  token = null;
1203
1645
  }
1204
1646
  return { token, native };
@@ -1226,12 +1668,24 @@ function makeEvmNetwork(resolved) {
1226
1668
  minConfirmations: accept.extra.minConfirmations
1227
1669
  });
1228
1670
  },
1229
- // Standard x402 `exact` rail (EIP-3009), BUYER side — EVM only. Re-derives the
1230
- // token's EIP-712 domain on-chain, signs an authorization with the agent's own
1231
- // key, and returns it for the client to frame into PAYMENT-SIGNATURE. Never
1232
- // broadcasts. Throws UnsupportedSchemeError for a non-EIP-3009 token / contract signer.
1671
+ // Standard x402 `exact` rail, BUYER side — EVM only. Routes on the rail's
1672
+ // `assetTransferMethod`: `permit2` (any ERC-20 e.g. Binance-Peg USDC on BNB, signs a
1673
+ // Permit2 witness transfer + lazily does the one-time approval) or `eip3009` (re-derives
1674
+ // the token's EIP-712 domain on-chain + signs transferWithAuthorization). Never broadcasts.
1675
+ // Throws UnsupportedSchemeError for a contract signer (or a non-EIP-3009 token on the eip3009 path).
1233
1676
  async payExact(wallet, accept) {
1234
1677
  const a = wallet._native;
1678
+ if (accept.extra.assetTransferMethod === "permit2") {
1679
+ const { payload: payload2, payerFrom: payerFrom2, nonce: nonce2 } = await payPermit2Evm({
1680
+ publicClient,
1681
+ walletClient: a.walletClient,
1682
+ account: a.account,
1683
+ chainId: resolved.chainId,
1684
+ chain: resolved.chain,
1685
+ accept
1686
+ });
1687
+ return { payload: payload2, accepted: accept, payerFrom: payerFrom2, nonce: nonce2 };
1688
+ }
1235
1689
  const { payload, payerFrom, nonce } = await payExactEvm({
1236
1690
  publicClient,
1237
1691
  walletClient: a.walletClient,
@@ -1245,8 +1699,23 @@ function makeEvmNetwork(resolved) {
1245
1699
  async exactDomain(asset) {
1246
1700
  return readExactDomain(publicClient, asset);
1247
1701
  },
1702
+ // Whether the Permit2 transfer method can settle here (proxy deployed). EIP-3009
1703
+ // needs no proxy; this only gates the Permit2 fallback for non-EIP-3009 tokens.
1704
+ exactPermit2Supported() {
1705
+ return isPermit2ProxyChain(resolved.chainId);
1706
+ },
1248
1707
  async settleExactSelf({ relayer, payload, accept }) {
1249
1708
  const a = relayer._native;
1709
+ if ("permit2Authorization" in payload) {
1710
+ return verifyAndSettlePermit2Evm({
1711
+ publicClient,
1712
+ walletClient: a.walletClient,
1713
+ account: a.account,
1714
+ chain: resolved.chain,
1715
+ payload,
1716
+ accept
1717
+ });
1718
+ }
1250
1719
  return verifyAndSettleExactEvm({
1251
1720
  publicClient,
1252
1721
  walletClient: a.walletClient,
@@ -1486,7 +1955,7 @@ async function searchOpenIndexes(opts = {}) {
1486
1955
  async function safeSearch(run) {
1487
1956
  try {
1488
1957
  return await run();
1489
- } catch (e17) {
1958
+ } catch (e22) {
1490
1959
  return [];
1491
1960
  }
1492
1961
  }
@@ -1592,7 +2061,7 @@ async function register402Index(input) {
1592
2061
  if (res.ok) {
1593
2062
  const body = await res.json().catch(() => ({}));
1594
2063
  const msg = typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1595
- const live = _optionalChain([body, 'access', _9 => _9.service, 'optionalAccess', _10 => _10.status]) === "active";
2064
+ const live = _optionalChain([body, 'access', _12 => _12.service, 'optionalAccess', _13 => _13.status]) === "active";
1596
2065
  return {
1597
2066
  source: "402index",
1598
2067
  ok: true,
@@ -1619,7 +2088,7 @@ async function readIndexError(res) {
1619
2088
  (p) => typeof p === "string" && p.length > 0
1620
2089
  );
1621
2090
  return parts.length ? [...new Set(parts)].join(" \u2014 ") : void 0;
1622
- } catch (e18) {
2091
+ } catch (e23) {
1623
2092
  return void 0;
1624
2093
  }
1625
2094
  }
@@ -1729,11 +2198,11 @@ async function readSiwxInfo(res) {
1729
2198
  try {
1730
2199
  const body = await res.json();
1731
2200
  const ext = body.extensions;
1732
- const siwx = _optionalChain([ext, 'optionalAccess', _11 => _11["sign-in-with-x"]]);
1733
- const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _12 => _12.info]), () => ( siwx));
1734
- if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess', _13 => _13.supportedChains]))) {
2201
+ const siwx = _optionalChain([ext, 'optionalAccess', _14 => _14["sign-in-with-x"]]);
2202
+ const info = _nullishCoalesce(_optionalChain([siwx, 'optionalAccess', _15 => _15.info]), () => ( siwx));
2203
+ if (info && info.chainId == null && Array.isArray(_optionalChain([siwx, 'optionalAccess', _16 => _16.supportedChains]))) {
1735
2204
  const evm = siwx.supportedChains.find(
1736
- (c) => typeof _optionalChain([c, 'optionalAccess', _14 => _14.chainId]) === "string" && c.chainId.startsWith("eip155:")
2205
+ (c) => typeof _optionalChain([c, 'optionalAccess', _17 => _17.chainId]) === "string" && c.chainId.startsWith("eip155:")
1737
2206
  );
1738
2207
  if (evm && typeof evm.chainId === "string") info.chainId = evm.chainId;
1739
2208
  }
@@ -1741,7 +2210,7 @@ async function readSiwxInfo(res) {
1741
2210
  return info;
1742
2211
  }
1743
2212
  return null;
1744
- } catch (e19) {
2213
+ } catch (e24) {
1745
2214
  return null;
1746
2215
  }
1747
2216
  }
@@ -1789,7 +2258,7 @@ function mapRails(accepts) {
1789
2258
  }
1790
2259
  function matchesQuery(r, query) {
1791
2260
  const q = query.toLowerCase();
1792
- return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _15 => _15.name, 'optionalAccess', _16 => _16.toLowerCase, 'call', _17 => _17(), 'access', _18 => _18.includes, 'call', _19 => _19(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _20 => _20.description, 'optionalAccess', _21 => _21.toLowerCase, 'call', _22 => _22(), 'access', _23 => _23.includes, 'call', _24 => _24(q)]), () => ( false)));
2261
+ return r.resource.toLowerCase().includes(q) || (_nullishCoalesce(_optionalChain([r, 'access', _18 => _18.name, 'optionalAccess', _19 => _19.toLowerCase, 'call', _20 => _20(), 'access', _21 => _21.includes, 'call', _22 => _22(q)]), () => ( false))) || (_nullishCoalesce(_optionalChain([r, 'access', _23 => _23.description, 'optionalAccess', _24 => _24.toLowerCase, 'call', _25 => _25(), 'access', _26 => _26.includes, 'call', _27 => _27(q)]), () => ( false)));
1793
2262
  }
1794
2263
  function pickString(o, ...keys) {
1795
2264
  for (const k of keys) {
@@ -1822,7 +2291,7 @@ function hostOf(url) {
1822
2291
  try {
1823
2292
  const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(url) ? url : `https://${url}`;
1824
2293
  return new URL(withScheme).hostname || url;
1825
- } catch (e20) {
2294
+ } catch (e25) {
1826
2295
  return url;
1827
2296
  }
1828
2297
  }
@@ -1970,7 +2439,7 @@ var SpendLedger = (_class = class {constructor() { _class.prototype.__init.call(
1970
2439
  }
1971
2440
  /** Running total (base units) already spent on this (network, asset). */
1972
2441
  totalFor(network, asset) {
1973
- return _nullishCoalesce(_optionalChain([this, 'access', _25 => _25.buckets, 'access', _26 => _26.get, 'call', _27 => _27(keyFor(network, asset)), 'optionalAccess', _28 => _28.total]), () => ( 0n));
2442
+ return _nullishCoalesce(_optionalChain([this, 'access', _28 => _28.buckets, 'access', _29 => _29.get, 'call', _30 => _30(keyFor(network, asset)), 'optionalAccess', _31 => _31.total]), () => ( 0n));
1974
2443
  }
1975
2444
  /**
1976
2445
  * Sum of base-unit amounts for (network, asset) whose record `at` (ISO
@@ -2083,7 +2552,7 @@ var PipRailClient = (_class2 = class {
2083
2552
  safeEmit(event) {
2084
2553
  try {
2085
2554
  this.onEvent(event);
2086
- } catch (e21) {
2555
+ } catch (e26) {
2087
2556
  }
2088
2557
  }
2089
2558
  /** Auto-mount the chain's driver, resolve the network, and bind the wallet — once. */
@@ -2113,7 +2582,7 @@ var PipRailClient = (_class2 = class {
2113
2582
  * as-is) or a plain object (serialised as JSON).
2114
2583
  */
2115
2584
  post(url, body, init) {
2116
- const headers = new Headers(_optionalChain([init, 'optionalAccess', _29 => _29.headers]));
2585
+ const headers = new Headers(_optionalChain([init, 'optionalAccess', _32 => _32.headers]));
2117
2586
  let payload;
2118
2587
  if (body === void 0 || body === null) {
2119
2588
  payload = void 0;
@@ -2144,7 +2613,7 @@ var PipRailClient = (_class2 = class {
2144
2613
  * "0.05 USDC on Base, within budget → pay it." No funds move.
2145
2614
  */
2146
2615
  async quote(url, init) {
2147
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _30 => _30.method]), () => ( "GET")) });
2616
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _33 => _33.method]), () => ( "GET")) });
2148
2617
  if (res.status !== 402) return null;
2149
2618
  const { quote } = await this.resolveChallenge(url, res, this.resolveSchemes());
2150
2619
  return quote;
@@ -2163,7 +2632,7 @@ var PipRailClient = (_class2 = class {
2163
2632
  * on Tron, where a USD₮ transfer can cost real TRX.
2164
2633
  */
2165
2634
  async estimateCost(url, init) {
2166
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _31 => _31.method]), () => ( "GET")) });
2635
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _34 => _34.method]), () => ( "GET")) });
2167
2636
  if (res.status !== 402) return null;
2168
2637
  const { net, accept, quote } = await this.resolveChallenge(url, res, this.resolveSchemes());
2169
2638
  const cost = await net.estimateCost(accept);
@@ -2186,8 +2655,8 @@ var PipRailClient = (_class2 = class {
2186
2655
  return {
2187
2656
  session: {
2188
2657
  start,
2189
- expiresAt: _optionalChain([view, 'optionalAccess', _32 => _32.expiresAt]) != null ? new Date(view.expiresAt).toISOString() : null,
2190
- secondsRemaining: _nullishCoalesce(_optionalChain([view, 'optionalAccess', _33 => _33.secondsRemaining]), () => ( null))
2658
+ expiresAt: _optionalChain([view, 'optionalAccess', _35 => _35.expiresAt]) != null ? new Date(view.expiresAt).toISOString() : null,
2659
+ secondsRemaining: _nullishCoalesce(_optionalChain([view, 'optionalAccess', _36 => _36.secondsRemaining]), () => ( null))
2191
2660
  },
2192
2661
  byAsset: this.remaining()
2193
2662
  };
@@ -2200,7 +2669,7 @@ var PipRailClient = (_class2 = class {
2200
2669
  * never throws, never sums across tokens (no price oracle). PROCESS-SCOPED.
2201
2670
  */
2202
2671
  remaining() {
2203
- const maxTotal = _optionalChain([this, 'access', _34 => _34.opts, 'access', _35 => _35.policy, 'optionalAccess', _36 => _36.maxTotal]);
2672
+ const maxTotal = _optionalChain([this, 'access', _37 => _37.opts, 'access', _38 => _38.policy, 'optionalAccess', _39 => _39.maxTotal]);
2204
2673
  return this.ledger.assetBuckets().map((b) => {
2205
2674
  const base2 = {
2206
2675
  network: b.network,
@@ -2252,7 +2721,7 @@ var PipRailClient = (_class2 = class {
2252
2721
  * the plan yourself. No funds move.
2253
2722
  */
2254
2723
  async planPayment(url, init) {
2255
- const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _37 => _37.method]), () => ( "GET")) });
2724
+ const res = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: _nullishCoalesce(_optionalChain([init, 'optionalAccess', _40 => _40.method]), () => ( "GET")) });
2256
2725
  if (res.status !== 402) return null;
2257
2726
  const challenge = await parseChallenge(res);
2258
2727
  if (!challenge) {
@@ -2419,7 +2888,7 @@ var PipRailClient = (_class2 = class {
2419
2888
  * streams throw `NonReplayableBodyError`.
2420
2889
  */
2421
2890
  async fetch(url, init) {
2422
- const body = _optionalChain([init, 'optionalAccess', _38 => _38.body]);
2891
+ const body = _optionalChain([init, 'optionalAccess', _41 => _41.body]);
2423
2892
  if (body !== void 0 && body !== null && !isReplayableBodyInit(body)) {
2424
2893
  throw new (0, _chunkPA6YD3HLcjs.NonReplayableBodyError)(
2425
2894
  "fetch(): init.body is not replayable. Pass a string, FormData, URLSearchParams, ArrayBuffer, or Blob \u2014 not a ReadableStream."
@@ -2427,12 +2896,12 @@ var PipRailClient = (_class2 = class {
2427
2896
  }
2428
2897
  const firstResponse = await fetch(url, init);
2429
2898
  if (firstResponse.status !== 402) return firstResponse;
2430
- const schemes = this.resolveSchemes(_optionalChain([init, 'optionalAccess', _39 => _39.schemes]));
2899
+ const schemes = this.resolveSchemes(_optionalChain([init, 'optionalAccess', _42 => _42.schemes]));
2431
2900
  const resolved = await this.resolveChallenge(url, firstResponse, schemes);
2432
2901
  const { net, wallet, challenge } = resolved;
2433
2902
  let accept = resolved.accept;
2434
2903
  let quote = resolved.quote;
2435
- const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _40 => _40.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2904
+ const autoRoute = _nullishCoalesce(_nullishCoalesce(_optionalChain([init, 'optionalAccess', _43 => _43.autoRoute]), () => ( this.opts.autoRoute)), () => ( false));
2436
2905
  if (autoRoute) {
2437
2906
  const plan = await this.planFromChallenge(net, wallet, challenge, url, schemes);
2438
2907
  if (!plan.best) {
@@ -2645,13 +3114,13 @@ var PipRailClient = (_class2 = class {
2645
3114
  }
2646
3115
  const amountBase = BigInt(accept.amount);
2647
3116
  const described = net.describeAsset(accept.asset);
2648
- const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _41 => _41.decimals]), () => ( accept.extra.decimals));
3117
+ const decimals = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _44 => _44.decimals]), () => ( accept.extra.decimals));
2649
3118
  if (decimals === void 0) {
2650
3119
  throw new (0, _chunkPA6YD3HLcjs.InvalidEnvelopeError)(
2651
3120
  `challenge for ${accept.asset} on ${accept.network} states no decimals and the SDK doesn't recognise the token \u2014 refusing to price it.`
2652
3121
  );
2653
3122
  }
2654
- const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _42 => _42.symbol]), () => ( accept.extra.symbol));
3123
+ const symbol = _nullishCoalesce(_optionalChain([described, 'optionalAccess', _45 => _45.symbol]), () => ( accept.extra.symbol));
2655
3124
  const amountFormatted = _chunkPA6YD3HLcjs.formatUnits.call(void 0, amountBase, decimals);
2656
3125
  const intent = {
2657
3126
  host: hostOf2(url),
@@ -2781,7 +3250,7 @@ var PipRailClient = (_class2 = class {
2781
3250
  accepted: accept,
2782
3251
  payload: { nonce: accept.extra.nonce, txHash: ref }
2783
3252
  };
2784
- const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _43 => _43.headers]));
3253
+ const headers = new Headers(_optionalChain([originalInit, 'optionalAccess', _46 => _46.headers]));
2785
3254
  headers.set(HEADER_SIGNATURE, buildSignatureHeader(signature));
2786
3255
  let lastResponse = null;
2787
3256
  let lastReason = null;
@@ -2796,7 +3265,7 @@ var PipRailClient = (_class2 = class {
2796
3265
  () => timeoutController.abort(),
2797
3266
  this.retryTimeoutMs
2798
3267
  );
2799
- const signal = _optionalChain([originalInit, 'optionalAccess', _44 => _44.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
3268
+ const signal = _optionalChain([originalInit, 'optionalAccess', _47 => _47.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, originalInit.signal]) : timeoutController.signal;
2800
3269
  try {
2801
3270
  lastResponse = await fetch(url, {
2802
3271
  ..._nullishCoalesce(originalInit, () => ( {})),
@@ -2855,9 +3324,9 @@ var PipRailClient = (_class2 = class {
2855
3324
  `the ${net.family} family can't pay a standard 'exact' rail (EVM + EIP-3009 only).`
2856
3325
  );
2857
3326
  }
2858
- throwIfAborted(_optionalChain([init, 'optionalAccess', _45 => _45.signal]));
3327
+ throwIfAborted(_optionalChain([init, 'optionalAccess', _48 => _48.signal]));
2859
3328
  const { payload, accepted, payerFrom, nonce } = await net.payExact(wallet, accept);
2860
- const headers = new Headers(_optionalChain([init, 'optionalAccess', _46 => _46.headers]));
3329
+ const headers = new Headers(_optionalChain([init, 'optionalAccess', _49 => _49.headers]));
2861
3330
  headers.set(HEADER_SIGNATURE, buildExactSignatureHeader({ accepted, payload }));
2862
3331
  const rejectDefinitive = (why2) => {
2863
3332
  this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})` });
@@ -2874,12 +3343,12 @@ var PipRailClient = (_class2 = class {
2874
3343
  if (Date.now() >= deadline) break;
2875
3344
  await new Promise((r) => setTimeout(r, Math.min(2e3, 400 * 2 ** (attempt - 1))));
2876
3345
  }
2877
- throwIfAborted(_optionalChain([init, 'optionalAccess', _47 => _47.signal]));
3346
+ throwIfAborted(_optionalChain([init, 'optionalAccess', _50 => _50.signal]));
2878
3347
  const budget = Math.min(this.retryTimeoutMs, deadline - Date.now());
2879
3348
  if (budget <= 0) break;
2880
3349
  const timeoutController = new AbortController();
2881
3350
  const timeoutId = setTimeout(() => timeoutController.abort(), budget);
2882
- const signal = _optionalChain([init, 'optionalAccess', _48 => _48.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, init.signal]) : timeoutController.signal;
3351
+ const signal = _optionalChain([init, 'optionalAccess', _51 => _51.signal]) && typeof AbortSignal.any === "function" ? AbortSignal.any([timeoutController.signal, init.signal]) : timeoutController.signal;
2883
3352
  let response;
2884
3353
  try {
2885
3354
  response = await fetch(url, { ..._nullishCoalesce(init, () => ( {})), headers, signal });
@@ -2900,7 +3369,7 @@ var PipRailClient = (_class2 = class {
2900
3369
  if (response.ok && !(settle && settle.success === false)) {
2901
3370
  const receipt = parseReceipt(response);
2902
3371
  this.safeEmit({ kind: "payment-settled", receipt, ...settle ? { settle } : {} });
2903
- const ref = _optionalChain([settle, 'optionalAccess', _49 => _49.transaction]) || _optionalChain([receipt, 'optionalAccess', _50 => _50.transaction]) || `eip3009-nonce:${nonce}`;
3372
+ const ref = _optionalChain([settle, 'optionalAccess', _52 => _52.transaction]) || _optionalChain([receipt, 'optionalAccess', _53 => _53.transaction]) || `eip3009-nonce:${nonce}`;
2904
3373
  this.recordSpend(quote, ref);
2905
3374
  return response;
2906
3375
  }
@@ -2924,14 +3393,14 @@ var PipRailClient = (_class2 = class {
2924
3393
  }
2925
3394
  }, _class2);
2926
3395
  function throwIfAborted(signal) {
2927
- if (_optionalChain([signal, 'optionalAccess', _51 => _51.aborted])) {
3396
+ if (_optionalChain([signal, 'optionalAccess', _54 => _54.aborted])) {
2928
3397
  throw _nullishCoalesce(signal.reason, () => ( new DOMException("This operation was aborted.", "AbortError")));
2929
3398
  }
2930
3399
  }
2931
3400
  function safeBig(s) {
2932
3401
  try {
2933
3402
  return BigInt(s);
2934
- } catch (e22) {
3403
+ } catch (e27) {
2935
3404
  return 0n;
2936
3405
  }
2937
3406
  }
@@ -2967,10 +3436,10 @@ function buildFundingHint(options, chainLabel) {
2967
3436
  return `Couldn't fully read your wallet on ${chainLabel} (RPC throttled) \u2014 retry; you may already be able to pay ${target.quote.amountFormatted} ${sym}.`;
2968
3437
  }
2969
3438
  const parts = [];
2970
- if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _52 => _52.shortfall, 'optionalAccess', _53 => _53.token])) {
3439
+ if (target.blockers.includes("INSUFFICIENT_TOKEN") && _optionalChain([target, 'access', _55 => _55.shortfall, 'optionalAccess', _56 => _56.token])) {
2971
3440
  parts.push(`top up ${target.shortfall.token} ${sym}`);
2972
3441
  }
2973
- if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _54 => _54.shortfall, 'optionalAccess', _55 => _55.native])) {
3442
+ if (target.blockers.includes("INSUFFICIENT_GAS") && _optionalChain([target, 'access', _57 => _57.shortfall, 'optionalAccess', _58 => _58.native])) {
2974
3443
  parts.push(`add ~${target.shortfall.native} ${target.cost.feeSymbol} for gas`);
2975
3444
  }
2976
3445
  return parts.length ? `Can't settle on ${chainLabel}: ${parts.join(" and ")} (to pay ${target.quote.amountFormatted} ${sym}).` : `Can't settle on ${chainLabel} for ${target.quote.amountFormatted} ${sym}.`;
@@ -2984,7 +3453,7 @@ async function planAcross(clients, url, init) {
2984
3453
  const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
2985
3454
  return {
2986
3455
  url,
2987
- network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _56 => _56.accept, 'access', _57 => _57.network]), () => ( live[0].network)),
3456
+ network: _nullishCoalesce(_optionalChain([best, 'optionalAccess', _59 => _59.accept, 'access', _60 => _60.network]), () => ( live[0].network)),
2988
3457
  status,
2989
3458
  payable: best !== null,
2990
3459
  best,
@@ -3014,7 +3483,7 @@ function reasonCodeForPolicy(code) {
3014
3483
  function hostOf2(url) {
3015
3484
  try {
3016
3485
  return new URL(url).hostname;
3017
- } catch (e23) {
3486
+ } catch (e28) {
3018
3487
  return url;
3019
3488
  }
3020
3489
  }
@@ -3031,8 +3500,8 @@ function isReplayableBodyInit(value) {
3031
3500
  async function readInvalidReason(response) {
3032
3501
  try {
3033
3502
  const body = await response.clone().json();
3034
- const ext = _optionalChain([body, 'optionalAccess', _58 => _58.extensions]);
3035
- const piprail = _optionalChain([ext, 'optionalAccess', _59 => _59.piprail]);
3503
+ const ext = _optionalChain([body, 'optionalAccess', _61 => _61.extensions]);
3504
+ const piprail = _optionalChain([ext, 'optionalAccess', _62 => _62.piprail]);
3036
3505
  if (piprail && typeof piprail.code === "string") {
3037
3506
  return {
3038
3507
  error: piprail.code,
@@ -3051,10 +3520,10 @@ async function readInvalidReason(response) {
3051
3520
  detail: typeof body.invalidMessage === "string" ? body.invalidMessage : ""
3052
3521
  };
3053
3522
  }
3054
- } catch (e24) {
3523
+ } catch (e29) {
3055
3524
  }
3056
3525
  const settle = parseSettleResponse(response);
3057
- if (_optionalChain([settle, 'optionalAccess', _60 => _60.errorReason])) return { error: settle.errorReason, detail: "" };
3526
+ if (_optionalChain([settle, 'optionalAccess', _63 => _63.errorReason])) return { error: settle.errorReason, detail: "" };
3058
3527
  return null;
3059
3528
  }
3060
3529
 
@@ -3168,7 +3637,7 @@ async function readBody(res) {
3168
3637
  if (!text) return null;
3169
3638
  try {
3170
3639
  return JSON.parse(text);
3171
- } catch (e25) {
3640
+ } catch (e30) {
3172
3641
  return text;
3173
3642
  }
3174
3643
  }
@@ -3479,7 +3948,7 @@ function buildBazaarExtension(descriptor = {}) {
3479
3948
  function pathOf(url) {
3480
3949
  try {
3481
3950
  return new URL(url).pathname || "/";
3482
- } catch (e26) {
3951
+ } catch (e31) {
3483
3952
  return url.startsWith("/") ? url : `/${url}`;
3484
3953
  }
3485
3954
  }
@@ -3552,7 +4021,7 @@ async function post(url, body, headers) {
3552
4021
  let json = null;
3553
4022
  try {
3554
4023
  json = await res.json();
3555
- } catch (e27) {
4024
+ } catch (e32) {
3556
4025
  }
3557
4026
  return { status: res.status, json };
3558
4027
  }
@@ -3670,7 +4139,7 @@ function createPaymentGate(options) {
3670
4139
  );
3671
4140
  if (options.exact && !specs.some((s) => s.exact)) {
3672
4141
  throw new Error(
3673
- "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`."
4142
+ "requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM ERC-20 only \u2014 EIP-3009 (USDC / EURC) or Permit2 (any ERC-20, e.g. Binance-Peg USDC on BNB) \u2014 NOT native coins, NOT non-EVM chains. Offer an EVM ERC-20 token, or drop `exact`."
3674
4143
  );
3675
4144
  }
3676
4145
  return specs;
@@ -3683,26 +4152,47 @@ function createPaymentGate(options) {
3683
4152
  }
3684
4153
  async function resolveExactRail(net, asset) {
3685
4154
  const cfg = options.exact;
3686
- if (net.family !== "evm" || asset === "native" || !net.exactDomain || !net.settleExactSelf) {
4155
+ if (net.family !== "evm" || asset === "native" || !net.settleExactSelf) {
3687
4156
  return void 0;
3688
4157
  }
3689
- const domain = await net.exactDomain(asset);
3690
- if (!domain) {
3691
- throw new Error(
3692
- `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.)`
3693
- );
4158
+ const want = _nullishCoalesce(cfg.method, () => ( "auto"));
4159
+ let method;
4160
+ let domain;
4161
+ if (want === "permit2") {
4162
+ method = "permit2";
4163
+ } else {
4164
+ const d = net.exactDomain ? await net.exactDomain(asset) : null;
4165
+ if (d) {
4166
+ method = "eip3009";
4167
+ domain = d;
4168
+ } else if (want === "eip3009") {
4169
+ throw new Error(
4170
+ `requirePayment: exact \`method: 'eip3009'\` requested for ${asset} on ${net.network}, but it isn't an EIP-3009 token (no name()/version()/authorizationState). Use \`method: 'permit2'\` (any ERC-20, e.g. Binance-Peg USDC on BNB) or \`'auto'\`. (Or check your rpcUrl is reachable.)`
4171
+ );
4172
+ } else {
4173
+ method = "permit2";
4174
+ }
4175
+ }
4176
+ if (method === "permit2" && !(_nullishCoalesce(_optionalChain([net, 'access', _64 => _64.exactPermit2Supported, 'optionalCall', _65 => _65()]), () => ( false)))) {
4177
+ if (cfg.method === "permit2") {
4178
+ throw new Error(
4179
+ `requirePayment: exact \`method: 'permit2'\` needs the x402 Permit2 proxy deployed on ${net.network}, but it isn't there. Offer an EIP-3009 token (gasless, no proxy), or drop \`exact\` on this chain. (See PERMIT2_PROXY_CHAIN_IDS.)`
4180
+ );
4181
+ }
4182
+ return void 0;
3694
4183
  }
3695
4184
  if (cfg.settle === "self") {
3696
4185
  if (cfg.relayer === void 0) {
3697
4186
  throw new Error(
3698
- "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts transferWithAuthorization), e.g. exact: { settle: 'self', relayer: { privateKey } }."
4187
+ "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
3699
4188
  );
3700
4189
  }
3701
4190
  const relayer = net.bindWallet(cfg.relayer);
3702
- return { domain, mode: { kind: "self", relayer } };
4191
+ return { method, ...domain ? { domain } : {}, mode: { kind: "self", relayer } };
3703
4192
  }
3704
4193
  return {
3705
- domain,
4194
+ method,
4195
+ ...domain ? { domain } : {},
3706
4196
  mode: {
3707
4197
  kind: "facilitator",
3708
4198
  url: cfg.settle.facilitator,
@@ -3746,7 +4236,7 @@ function createPaymentGate(options) {
3746
4236
  };
3747
4237
  }
3748
4238
  function buildExactAccept(s) {
3749
- const d = s.exact.domain;
4239
+ const rail = s.exact;
3750
4240
  return {
3751
4241
  scheme: "exact",
3752
4242
  network: s.net.network,
@@ -3755,9 +4245,8 @@ function createPaymentGate(options) {
3755
4245
  payTo: s.payTo,
3756
4246
  maxTimeoutSeconds,
3757
4247
  extra: {
3758
- assetTransferMethod: "eip3009",
3759
- name: d.name,
3760
- version: d.version,
4248
+ assetTransferMethod: rail.method,
4249
+ ...rail.domain ? { name: rail.domain.name, version: rail.domain.version } : {},
3761
4250
  minConfirmations,
3762
4251
  decimals: s.decimals,
3763
4252
  amountFormatted: s.amountFormatted,
@@ -3777,7 +4266,7 @@ function createPaymentGate(options) {
3777
4266
  const specs = await ready();
3778
4267
  const nonce = genNonce();
3779
4268
  const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
3780
- const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _61 => _61.extensions]) };
4269
+ const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _66 => _66.extensions]) };
3781
4270
  const challenge2 = {
3782
4271
  x402Version: 2,
3783
4272
  resource: {
@@ -3785,7 +4274,7 @@ function createPaymentGate(options) {
3785
4274
  ...options.description ? { description: options.description } : {}
3786
4275
  },
3787
4276
  accepts: buildAccepts(specs, nonce),
3788
- ..._optionalChain([opts, 'optionalAccess', _62 => _62.error]) ? { error: opts.error } : {},
4277
+ ..._optionalChain([opts, 'optionalAccess', _67 => _67.error]) ? { error: opts.error } : {},
3789
4278
  ...Object.keys(extensions).length > 0 ? { extensions } : {}
3790
4279
  };
3791
4280
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
@@ -3804,13 +4293,44 @@ function createPaymentGate(options) {
3804
4293
  });
3805
4294
  return { kind: "invalid", error: code, detail, challenge: c, requiredHeader, statusCode: 402 };
3806
4295
  }
4296
+ function enrichReceipt(spec, receipt) {
4297
+ let amountFormatted = receipt.amount;
4298
+ try {
4299
+ amountFormatted = _chunkPA6YD3HLcjs.formatUnits.call(void 0, BigInt(receipt.amount), spec.decimals);
4300
+ } catch (e33) {
4301
+ }
4302
+ return {
4303
+ ...receipt,
4304
+ decimals: spec.decimals,
4305
+ ...spec.symbol ? { symbol: spec.symbol } : {},
4306
+ amountFormatted,
4307
+ idempotencyKey: receipt.transaction
4308
+ };
4309
+ }
4310
+ function reportOnPaidError(error, receipt) {
4311
+ if (!options.onPaidError) return;
4312
+ try {
4313
+ options.onPaidError(error, receipt);
4314
+ } catch (e34) {
4315
+ }
4316
+ }
3807
4317
  function fireOnPaid(receipt) {
3808
- if (options.onPaid) {
3809
- try {
3810
- options.onPaid(receipt);
3811
- } catch (e28) {
3812
- }
4318
+ if (!options.onPaid) return;
4319
+ let outcome;
4320
+ try {
4321
+ outcome = options.onPaid(receipt);
4322
+ } catch (err) {
4323
+ reportOnPaidError(err, receipt);
4324
+ return;
3813
4325
  }
4326
+ if (outcome != null && typeof outcome.then === "function") {
4327
+ return Promise.resolve(outcome).catch((err) => reportOnPaidError(err, receipt));
4328
+ }
4329
+ }
4330
+ async function deliverOnPaid(spec, receipt) {
4331
+ const paid = enrichReceipt(spec, receipt);
4332
+ if (options.awaitOnPaid) await fireOnPaid(paid);
4333
+ else void fireOnPaid(paid);
3814
4334
  }
3815
4335
  async function describe(resourceUrl = "") {
3816
4336
  const specs = await ready();
@@ -3854,7 +4374,7 @@ function createPaymentGate(options) {
3854
4374
  return rejection(result.error, result.detail);
3855
4375
  }
3856
4376
  await settleTx(ref, true);
3857
- fireOnPaid(result.receipt);
4377
+ await deliverOnPaid(spec, result.receipt);
3858
4378
  return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
3859
4379
  }
3860
4380
  async function verifyExact(exact) {
@@ -3877,7 +4397,8 @@ function createPaymentGate(options) {
3877
4397
  `No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
3878
4398
  );
3879
4399
  }
3880
- const nonce = exact.payload.authorization.nonce;
4400
+ const auth = "permit2Authorization" in exact.payload ? exact.payload.permit2Authorization : exact.payload.authorization;
4401
+ const nonce = auth.nonce;
3881
4402
  if (await claimTx(nonce)) {
3882
4403
  return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
3883
4404
  }
@@ -3909,7 +4430,7 @@ function createPaymentGate(options) {
3909
4430
  extra: { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
3910
4431
  },
3911
4432
  receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
3912
- payerHint: exact.payload.authorization.from
4433
+ payerHint: auth.from
3913
4434
  });
3914
4435
  }
3915
4436
  } catch (err) {
@@ -3921,7 +4442,7 @@ function createPaymentGate(options) {
3921
4442
  return rejection(result.error, result.detail);
3922
4443
  }
3923
4444
  await settleTx(nonce, true);
3924
- fireOnPaid(result.receipt);
4445
+ await deliverOnPaid(spec, result.receipt);
3925
4446
  return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
3926
4447
  }
3927
4448
  async function verify(paymentSignature) {
@@ -3975,6 +4496,116 @@ function normaliseHeader(value) {
3975
4496
  return value;
3976
4497
  }
3977
4498
 
4499
+ // src/receipts.ts
4500
+ var DEFAULT_RETRIES = 5;
4501
+ var DEFAULT_TIMEOUT_MS = 1e4;
4502
+ function defaultBackoff(attempt) {
4503
+ const base2 = Math.min(3e4, 2 ** (attempt - 1) * 500);
4504
+ return Math.round(base2 * (0.5 + Math.random()));
4505
+ }
4506
+ function isRetryableStatus(status) {
4507
+ return status === 408 || status === 429 || status >= 500;
4508
+ }
4509
+ var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
4510
+ async function signBody(secret, body) {
4511
+ const subtle = _optionalChain([globalThis, 'access', _68 => _68.crypto, 'optionalAccess', _69 => _69.subtle]);
4512
+ if (!subtle) return null;
4513
+ try {
4514
+ const enc = new TextEncoder();
4515
+ const key = await subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, [
4516
+ "sign"
4517
+ ]);
4518
+ const sig = await subtle.sign("HMAC", key, enc.encode(body));
4519
+ const hex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
4520
+ return `sha256=${hex}`;
4521
+ } catch (e35) {
4522
+ return null;
4523
+ }
4524
+ }
4525
+ async function deliverReceipt(receipt, options) {
4526
+ const {
4527
+ url,
4528
+ secret,
4529
+ retries = DEFAULT_RETRIES,
4530
+ timeoutMs = DEFAULT_TIMEOUT_MS,
4531
+ headers = {},
4532
+ signatureHeader = "piprail-signature",
4533
+ idempotencyHeader = "idempotency-key",
4534
+ backoff = defaultBackoff,
4535
+ fetchImpl = globalThis.fetch,
4536
+ onAttempt
4537
+ } = options;
4538
+ if (typeof fetchImpl !== "function") {
4539
+ return { delivered: false, attempts: 0, error: "no fetch implementation available" };
4540
+ }
4541
+ const body = JSON.stringify(receipt);
4542
+ const signature = secret ? await signBody(secret, body) : null;
4543
+ const baseHeaders = {
4544
+ ...headers,
4545
+ "content-type": "application/json",
4546
+ [idempotencyHeader]: receipt.idempotencyKey,
4547
+ ...signature ? { [signatureHeader]: signature } : {}
4548
+ };
4549
+ const maxAttempts = Math.max(1, retries + 1);
4550
+ let lastStatus;
4551
+ let lastError;
4552
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4553
+ const controller = new AbortController();
4554
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
4555
+ let ok = false;
4556
+ let status;
4557
+ let error;
4558
+ try {
4559
+ const res = await fetchImpl(url, {
4560
+ method: "POST",
4561
+ headers: baseHeaders,
4562
+ body,
4563
+ signal: controller.signal
4564
+ });
4565
+ status = res.status;
4566
+ lastStatus = status;
4567
+ ok = res.ok;
4568
+ if (!ok) {
4569
+ error = `HTTP ${status}`;
4570
+ lastError = error;
4571
+ }
4572
+ } catch (err) {
4573
+ error = err instanceof Error ? err.message : String(err);
4574
+ lastError = error;
4575
+ } finally {
4576
+ clearTimeout(timer);
4577
+ }
4578
+ const retryable = status === void 0 ? true : isRetryableStatus(status);
4579
+ const willRetry = !ok && retryable && attempt < maxAttempts;
4580
+ try {
4581
+ _optionalChain([onAttempt, 'optionalCall', _70 => _70({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
4582
+ } catch (e36) {
4583
+ }
4584
+ if (ok) return { delivered: true, attempts: attempt, status };
4585
+ if (!willRetry) {
4586
+ return {
4587
+ delivered: false,
4588
+ attempts: attempt,
4589
+ ...lastStatus !== void 0 ? { status: lastStatus } : {},
4590
+ ...lastError ? { error: lastError } : {}
4591
+ };
4592
+ }
4593
+ await sleep(backoff(attempt));
4594
+ }
4595
+ return {
4596
+ delivered: false,
4597
+ attempts: maxAttempts,
4598
+ ...lastStatus !== void 0 ? { status: lastStatus } : {},
4599
+ ...lastError ? { error: lastError } : {}
4600
+ };
4601
+ }
4602
+
4603
+
4604
+
4605
+
4606
+
4607
+
4608
+
3978
4609
 
3979
4610
 
3980
4611
 
@@ -4047,4 +4678,4 @@ function normaliseHeader(value) {
4047
4678
 
4048
4679
 
4049
4680
 
4050
- exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkPA6YD3HLcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkPA6YD3HLcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkPA6YD3HLcjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkPA6YD3HLcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkPA6YD3HLcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkPA6YD3HLcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkPA6YD3HLcjs.NonReplayableBodyError; exports.PIPRAIL_AGENT_GUIDE = PIPRAIL_AGENT_GUIDE; exports.PaymentDeclinedError = _chunkPA6YD3HLcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkPA6YD3HLcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkPA6YD3HLcjs.PipRailError; exports.RecipientNotReadyError = _chunkPA6YD3HLcjs.RecipientNotReadyError; exports.SettlementError = _chunkPA6YD3HLcjs.SettlementError; exports.UnknownTokenError = _chunkPA6YD3HLcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkPA6YD3HLcjs.UnsupportedNetworkError; exports.UnsupportedSchemeError = _chunkPA6YD3HLcjs.UnsupportedSchemeError; exports.WrongChainError = _chunkPA6YD3HLcjs.WrongChainError; exports.WrongFamilyError = _chunkPA6YD3HLcjs.WrongFamilyError; exports.agentGuide = agentGuide; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildExactSignatureHeader = buildExactSignatureHeader; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.classifyChallenge = classifyChallenge; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.explainDecline = explainDecline; exports.formatSpendReport = formatSpendReport; exports.getDirectoryInfo = getDirectoryInfo; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSettleResponse = parseSettleResponse; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.summarizePlan = summarizePlan; exports.toInsufficientFundsError = _chunkPA6YD3HLcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;
4681
+ exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkPA6YD3HLcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkPA6YD3HLcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkPA6YD3HLcjs.InvalidEnvelopeError; exports.MaxRetriesExceededError = _chunkPA6YD3HLcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkPA6YD3HLcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkPA6YD3HLcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkPA6YD3HLcjs.NonReplayableBodyError; exports.PERMIT2_ADDRESS = PERMIT2_ADDRESS; exports.PERMIT2_PROXY_CHAIN_IDS = PERMIT2_PROXY_CHAIN_IDS; exports.PERMIT2_WITNESS_TYPES = PERMIT2_WITNESS_TYPES; exports.PIPRAIL_AGENT_GUIDE = PIPRAIL_AGENT_GUIDE; exports.PaymentDeclinedError = _chunkPA6YD3HLcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkPA6YD3HLcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkPA6YD3HLcjs.PipRailError; exports.RecipientNotReadyError = _chunkPA6YD3HLcjs.RecipientNotReadyError; exports.SettlementError = _chunkPA6YD3HLcjs.SettlementError; exports.UnknownTokenError = _chunkPA6YD3HLcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkPA6YD3HLcjs.UnsupportedNetworkError; exports.UnsupportedSchemeError = _chunkPA6YD3HLcjs.UnsupportedSchemeError; exports.WrongChainError = _chunkPA6YD3HLcjs.WrongChainError; exports.WrongFamilyError = _chunkPA6YD3HLcjs.WrongFamilyError; exports.X402_EXACT_PERMIT2_PROXY = X402_EXACT_PERMIT2_PROXY; exports.agentGuide = agentGuide; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildExactSignatureHeader = buildExactSignatureHeader; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.classifyChallenge = classifyChallenge; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.deliverReceipt = deliverReceipt; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.explainDecline = explainDecline; exports.formatSpendReport = formatSpendReport; exports.getDirectoryInfo = getDirectoryInfo; exports.isPermit2ProxyChain = isPermit2ProxyChain; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseReceipt = parseReceipt; exports.parseSettleResponse = parseSettleResponse; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.summarizePlan = summarizePlan; exports.toInsufficientFundsError = _chunkPA6YD3HLcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;