@piprail/sdk 1.9.0 → 1.11.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 (29) hide show
  1. package/CHANGELOG.md +70 -0
  2. package/ERRORS.md +21 -8
  3. package/README.md +20 -0
  4. package/STANDARDS.md +5 -2
  5. package/dist/{algorand-IJJKE35X.cjs → algorand-MXUSKX46.cjs} +17 -17
  6. package/dist/{algorand-B67G4335.js → algorand-WGVF4KTU.js} +1 -1
  7. package/dist/{aptos-YQWTGFRZ.js → aptos-LPBLSEIQ.js} +1 -1
  8. package/dist/{aptos-X3G2UBYW.cjs → aptos-YT7SXWPF.cjs} +16 -16
  9. package/dist/{chunk-IQGT65WS.cjs → chunk-MDLZJGLY.cjs} +20 -16
  10. package/dist/{chunk-QDS6FBZP.js → chunk-SVMGHASK.js} +4 -0
  11. package/dist/index.cjs +897 -265
  12. package/dist/index.d.cts +504 -45
  13. package/dist/index.d.ts +504 -45
  14. package/dist/index.js +820 -188
  15. package/dist/{near-GGUHLXAF.cjs → near-7ZDNISUX.cjs} +19 -19
  16. package/dist/{near-7MBBCDUE.js → near-K6BDBABG.js} +1 -1
  17. package/dist/{solana-W24TCJV4.cjs → solana-PU7N2M64.cjs} +14 -14
  18. package/dist/{solana-7WJVZGDW.js → solana-S3UFI3FE.js} +1 -1
  19. package/dist/{stellar-HV6VGZX3.js → stellar-Q5PO23SC.js} +1 -1
  20. package/dist/{stellar-YMY3K2YB.cjs → stellar-VDQOFQEO.cjs} +21 -21
  21. package/dist/{sui-32KVESR5.cjs → sui-FKSMLKRF.cjs} +17 -17
  22. package/dist/{sui-2WFWVFJX.js → sui-WOXRKJXS.js} +1 -1
  23. package/dist/{ton-FIQGV2LC.cjs → ton-VK6KRJHP.cjs} +14 -14
  24. package/dist/{ton-DGZB7W4U.js → ton-WPTXGLVK.js} +1 -1
  25. package/dist/{tron-RLIL2FDI.js → tron-6GXBXTR4.js} +1 -1
  26. package/dist/{tron-ZSXAPZ2C.cjs → tron-WLOF5OUV.cjs} +24 -24
  27. package/dist/{xrpl-2PKP7HOI.cjs → xrpl-CMNI25BV.cjs} +21 -21
  28. package/dist/{xrpl-UEC2GYVV.js → xrpl-HEAPEXAM.js} +1 -1
  29. package/package.json +1 -1
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";
@@ -483,20 +484,367 @@ function sumTransfersTo(logs, asset, payTo) {
483
484
  return { total, from };
484
485
  }
485
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
+
486
822
  // src/x402.ts
487
823
  var HEADER_REQUIRED = "payment-required";
488
824
  var HEADER_SIGNATURE = "payment-signature";
489
825
  var HEADER_RESPONSE = "payment-response";
490
- function decodeBase64(b64) {
491
- if (typeof atob === "function") return atob(b64);
492
- if (typeof Buffer !== "undefined") return Buffer.from(b64, "base64").toString("utf8");
493
- throw new Error("No base64 decoder available in this runtime.");
494
- }
826
+ var HEADER_SIGNATURE_V1 = "x-payment";
827
+ var HEADER_RESPONSE_V1 = "x-payment-response";
495
828
  function encodeBase64(str) {
496
- if (typeof btoa === "function") return btoa(str);
497
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
+ }
498
836
  throw new Error("No base64 encoder available in this runtime.");
499
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
+ }
500
848
  function fromBase64Json(b64) {
501
849
  try {
502
850
  return JSON.parse(decodeBase64(b64));
@@ -557,6 +905,36 @@ function parseSignatureHeader(value) {
557
905
  }
558
906
  return parsed;
559
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
+ }
560
938
  function isValidChallenge(value) {
561
939
  if (!value || typeof value !== "object") return false;
562
940
  const v = value;
@@ -568,7 +946,7 @@ function isValidChallenge(value) {
568
946
  function isValidReceipt(value) {
569
947
  if (!value || typeof value !== "object") return false;
570
948
  const v = value;
571
- if (v.scheme !== "onchain-proof") return false;
949
+ if (v.scheme !== "onchain-proof" && v.scheme !== "exact") return false;
572
950
  if (typeof v.transaction !== "string" && typeof v.txHash !== "string") return false;
573
951
  if (typeof v.payer !== "string") return false;
574
952
  return true;
@@ -646,12 +1024,12 @@ function makeEvmNetwork(resolved) {
646
1024
  }
647
1025
  let normalized;
648
1026
  try {
649
- normalized = getAddress2(asset);
1027
+ normalized = getAddress3(asset);
650
1028
  } catch {
651
1029
  return null;
652
1030
  }
653
1031
  for (const info of Object.values(resolved.tokens)) {
654
- if (getAddress2(info.address) === normalized) {
1032
+ if (getAddress3(info.address) === normalized) {
655
1033
  return { symbol: info.symbol, decimals: info.decimals };
656
1034
  }
657
1035
  }
@@ -735,7 +1113,7 @@ function makeEvmNetwork(resolved) {
735
1113
  let token = null;
736
1114
  try {
737
1115
  token = await publicClient.readContract({
738
- address: getAddress2(asset),
1116
+ address: getAddress3(asset),
739
1117
  abi: erc20Abi3,
740
1118
  functionName: "balanceOf",
741
1119
  args: [owner]
@@ -767,6 +1145,21 @@ function makeEvmNetwork(resolved) {
767
1145
  accept,
768
1146
  minConfirmations: accept.extra.minConfirmations
769
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
+ });
770
1163
  }
771
1164
  };
772
1165
  }
@@ -783,7 +1176,7 @@ var loaders = {
783
1176
  solana: async () => {
784
1177
  let mod;
785
1178
  try {
786
- mod = await import("./solana-7WJVZGDW.js");
1179
+ mod = await import("./solana-S3UFI3FE.js");
787
1180
  } catch (cause) {
788
1181
  throw new MissingDriverError(
789
1182
  `Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
@@ -795,7 +1188,7 @@ var loaders = {
795
1188
  ton: async () => {
796
1189
  let mod;
797
1190
  try {
798
- mod = await import("./ton-DGZB7W4U.js");
1191
+ mod = await import("./ton-WPTXGLVK.js");
799
1192
  } catch (cause) {
800
1193
  throw new MissingDriverError(
801
1194
  `TON selected, but its packages aren't installed. Run: npm install @ton/ton @ton/core @ton/crypto`,
@@ -807,7 +1200,7 @@ var loaders = {
807
1200
  stellar: async () => {
808
1201
  let mod;
809
1202
  try {
810
- mod = await import("./stellar-HV6VGZX3.js");
1203
+ mod = await import("./stellar-Q5PO23SC.js");
811
1204
  } catch (cause) {
812
1205
  throw new MissingDriverError(
813
1206
  `Stellar selected, but its package isn't installed. Run: npm install @stellar/stellar-sdk`,
@@ -819,7 +1212,7 @@ var loaders = {
819
1212
  xrpl: async () => {
820
1213
  let mod;
821
1214
  try {
822
- mod = await import("./xrpl-UEC2GYVV.js");
1215
+ mod = await import("./xrpl-HEAPEXAM.js");
823
1216
  } catch (cause) {
824
1217
  throw new MissingDriverError(
825
1218
  `XRPL selected, but its package isn't installed. Run: npm install xrpl`,
@@ -831,7 +1224,7 @@ var loaders = {
831
1224
  tron: async () => {
832
1225
  let mod;
833
1226
  try {
834
- mod = await import("./tron-RLIL2FDI.js");
1227
+ mod = await import("./tron-6GXBXTR4.js");
835
1228
  } catch (cause) {
836
1229
  throw new MissingDriverError(
837
1230
  `Tron selected, but its package isn't installed. Run: npm install tronweb`,
@@ -843,7 +1236,7 @@ var loaders = {
843
1236
  sui: async () => {
844
1237
  let mod;
845
1238
  try {
846
- mod = await import("./sui-2WFWVFJX.js");
1239
+ mod = await import("./sui-WOXRKJXS.js");
847
1240
  } catch (cause) {
848
1241
  throw new MissingDriverError(
849
1242
  `Sui selected, but its package isn't installed. Run: npm install @mysten/sui`,
@@ -855,7 +1248,7 @@ var loaders = {
855
1248
  near: async () => {
856
1249
  let mod;
857
1250
  try {
858
- mod = await import("./near-7MBBCDUE.js");
1251
+ mod = await import("./near-K6BDBABG.js");
859
1252
  } catch (cause) {
860
1253
  throw new MissingDriverError(
861
1254
  `NEAR selected, but its package isn't installed. Run: npm install near-api-js`,
@@ -867,7 +1260,7 @@ var loaders = {
867
1260
  aptos: async () => {
868
1261
  let mod;
869
1262
  try {
870
- mod = await import("./aptos-YQWTGFRZ.js");
1263
+ mod = await import("./aptos-LPBLSEIQ.js");
871
1264
  } catch (cause) {
872
1265
  throw new MissingDriverError(
873
1266
  `Aptos selected, but its package isn't installed. Run: npm install @aptos-labs/ts-sdk`,
@@ -879,7 +1272,7 @@ var loaders = {
879
1272
  algorand: async () => {
880
1273
  let mod;
881
1274
  try {
882
- mod = await import("./algorand-B67G4335.js");
1275
+ mod = await import("./algorand-WGVF4KTU.js");
883
1276
  } catch (cause) {
884
1277
  throw new MissingDriverError(
885
1278
  `Algorand selected, but its package isn't installed. Run: npm install algosdk`,
@@ -912,6 +1305,42 @@ async function resolveNetwork2(opts) {
912
1305
  }
913
1306
 
914
1307
  // src/indexes.ts
1308
+ var DIRECTORY_INFO = {
1309
+ "402index": {
1310
+ source: "402index",
1311
+ review: "probe-sync",
1312
+ auth: "none",
1313
+ chains: null,
1314
+ onSuccess: "pending-review",
1315
+ readByDiscover: true,
1316
+ caveat: "402 Index probes your URL on submit, then lists it as PENDING REVIEW \u2014 a self-registered resource is NOT in search until approved. Verify your domain on 402index.io for instant approval; otherwise it appears after manual review, so retry discover() later."
1317
+ },
1318
+ x402scan: {
1319
+ source: "x402scan",
1320
+ review: "probe-sync",
1321
+ auth: "siwx",
1322
+ chains: ["eip155:8453", "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"],
1323
+ onSuccess: "live",
1324
+ readByDiscover: false,
1325
+ caveat: "x402scan lists Base/Solana only, needs one wallet signature (SIWX), and requires a resolvable input schema (from /openapi.json or the bazaar extension in the 402 body). It goes live on x402scan.com immediately on success \u2014 but discover() does NOT read x402scan, so the listing won't appear in discover() results."
1326
+ },
1327
+ bazaar: {
1328
+ source: "bazaar",
1329
+ review: "settle-coupled",
1330
+ auth: "facilitator-only",
1331
+ chains: null,
1332
+ onSuccess: "not-listable",
1333
+ readByDiscover: true,
1334
+ caveat: "CDP Bazaar has no register endpoint \u2014 it catalogs a resource only when its own facilitator settles a payment. PipRail verifies locally with no facilitator, so a PipRail resource cannot be listed here (you can still READ Bazaar to find others). List on 402 Index or x402scan instead."
1335
+ }
1336
+ };
1337
+ function getDirectoryInfo(source) {
1338
+ return DIRECTORY_INFO[source];
1339
+ }
1340
+ function decorateOutcome(o) {
1341
+ const info = DIRECTORY_INFO[o.source];
1342
+ return { ...o, visibility: o.ok ? info.onSuccess : "not-listable", note: info.caveat };
1343
+ }
915
1344
  var BAZAAR_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery/resources";
916
1345
  var INDEX402_SEARCH = "https://402index.io/api/v1/services";
917
1346
  var INDEX402_REGISTER = "https://402index.io/api/v1/register";
@@ -1064,7 +1493,13 @@ async function register402Index(input) {
1064
1493
  body: JSON.stringify(payload)
1065
1494
  });
1066
1495
  if (res.ok) {
1067
- return { source: "402index", ok: true, status: res.status, detail: "Listed on 402 Index (searchable at 402index.io)." };
1496
+ const msg = await readIndexMessage(res);
1497
+ return {
1498
+ source: "402index",
1499
+ ok: true,
1500
+ status: res.status,
1501
+ detail: msg ?? "Registered on 402 Index \u2014 pending review (verify your domain on 402index.io for instant approval)."
1502
+ };
1068
1503
  }
1069
1504
  const why = await readIndexError(res);
1070
1505
  return {
@@ -1077,6 +1512,14 @@ async function register402Index(input) {
1077
1512
  return { source: "402index", ok: false, detail: errMsg(err) };
1078
1513
  }
1079
1514
  }
1515
+ async function readIndexMessage(res) {
1516
+ try {
1517
+ const body = await res.json();
1518
+ return typeof body.message === "string" && body.message.length > 0 ? body.message : void 0;
1519
+ } catch {
1520
+ return void 0;
1521
+ }
1522
+ }
1080
1523
  async function readIndexError(res) {
1081
1524
  try {
1082
1525
  const body = await res.json();
@@ -1515,9 +1958,15 @@ var PipRailClient = class {
1515
1958
  *
1516
1959
  * Nothing PipRail-hosted: these are third-party open directories. Never throws
1517
1960
  * for a read problem — an index that's down or changed simply contributes
1518
- * nothing. Honest caveat: index results are cross-scheme (mostly the
1519
- * mainstream `exact` scheme); `fetch()` pays only `onchain-proof` rails
1520
- * directly (pay `exact` resources with the experimental `drivers/evm/exact.ts`).
1961
+ * nothing. Honest caveats (see {@link DIRECTORY_INFO}):
1962
+ * - Reads **`bazaar` + `402index`** only — **NOT `x402scan`** (its reads are paid). A
1963
+ * resource you registered on x402scan is live there but will NOT appear here; don't
1964
+ * read that absence as failure. (Passing `sources:['x402scan']` explicitly yields `[]`.)
1965
+ * - A resource just listed via {@link register} may not appear yet — 402 Index reviews
1966
+ * before publishing, so retry with a brief backoff if a fresh listing is missing.
1967
+ * - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
1968
+ * only `onchain-proof` rails directly (pay `exact` resources with the experimental
1969
+ * `drivers/evm/exact.ts`).
1521
1970
  */
1522
1971
  async discover(opts = {}) {
1523
1972
  const found = await searchOpenIndexes({
@@ -1542,12 +1991,27 @@ var PipRailClient = class {
1542
1991
  }
1543
1992
  /**
1544
1993
  * List a resource you run on the OPEN x402 registries, so agents can find it.
1545
- * Default target is **402 Index** — one POST, no auth, no signature, no payment
1546
- * (searchable within seconds). Add `'x402scan'` to also register via SIWX (one
1547
- * wallet signature; EVM + a Base/Solana rail). Returns one {@link RegisterOutcome}
1548
- * per target — a target the chain can't satisfy comes back `{ ok:false, detail }`,
1549
- * never a throw. An explicit, developer-invoked action; it moves no funds, and
1550
- * nothing is PipRail-hosted — you're listing on third-party open directories.
1994
+ * Default target is **402 Index** — one POST, no auth, no signature, no payment.
1995
+ * Add `'x402scan'` to also register via SIWX (one wallet signature; EVM + a
1996
+ * Base/Solana rail). Returns one {@link RegisterOutcome} per target — a target the
1997
+ * chain can't satisfy comes back `{ ok:false, detail }`, never a throw. An explicit,
1998
+ * developer-invoked action; it moves no funds, and nothing is PipRail-hosted —
1999
+ * you're listing on third-party open directories.
2000
+ *
2001
+ * **Listing is asynchronous — each outcome carries a `visibility` + `note` so an
2002
+ * agent knows when/where the resource is findable (don't assume `ok:true` means
2003
+ * "searchable now"):**
2004
+ * - **402 Index** → `visibility:'pending-review'`. It probes your URL on submit, then lists it
2005
+ * PENDING REVIEW — not searchable until approved (verify your domain on 402index.io for instant
2006
+ * approval), so `discover()` returns nothing for a fresh listing until then. Retry later.
2007
+ * - **x402scan** → `visibility:'live'`, but **`discover()` does NOT read x402scan** — the
2008
+ * listing is real on x402scan.com yet won't show up in `discover()`. Base/Solana only;
2009
+ * needs a resolvable input schema (`/openapi.json` or the `extensions.bazaar` block).
2010
+ * - **Bazaar** → `visibility:'not-listable'` for PipRail (it lists only what its facilitator
2011
+ * settles; PipRail uses none). You can still READ Bazaar via {@link discover} to find others.
2012
+ *
2013
+ * The per-source facts live in {@link DIRECTORY_INFO} (importable) if you'd rather branch
2014
+ * on them before calling.
1551
2015
  */
1552
2016
  async register(url, opts = {}) {
1553
2017
  const targets = opts.targets ?? ["402index"];
@@ -1586,7 +2050,7 @@ var PipRailClient = class {
1586
2050
  });
1587
2051
  }
1588
2052
  }
1589
- return outcomes;
2053
+ return outcomes.map(decorateOutcome);
1590
2054
  }
1591
2055
  /**
1592
2056
  * The discovery signer for the bound wallet (its address + a message signer),
@@ -1663,7 +2127,10 @@ var PipRailClient = class {
1663
2127
  const chosen = priced.find((p) => p.quote.withinPolicy) ?? priced[0];
1664
2128
  return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
1665
2129
  }
1666
- /** The candidate accepts this client could pay: our scheme, on the bound network. */
2130
+ /** The candidate accepts this client could pay: our scheme, on the bound network.
2131
+ * A dual-advertised challenge may also carry standard `exact` rails — the PipRail
2132
+ * client ignores those (it pays the backendless `onchain-proof` rail); the type
2133
+ * predicate narrows the `X402AnyAccept` union to the rails we settle. */
1667
2134
  gatherCandidates(net, challenge) {
1668
2135
  return challenge.accepts.filter(
1669
2136
  (a) => a.scheme === "onchain-proof" && net.supports(a.network)
@@ -2026,6 +2493,14 @@ function isReplayableBodyInit(value) {
2026
2493
  async function readInvalidReason(response) {
2027
2494
  try {
2028
2495
  const body = await response.clone().json();
2496
+ const ext = body?.extensions;
2497
+ const piprail = ext?.piprail;
2498
+ if (piprail && typeof piprail.code === "string") {
2499
+ return {
2500
+ error: piprail.code,
2501
+ detail: typeof piprail.detail === "string" ? piprail.detail : ""
2502
+ };
2503
+ }
2029
2504
  if (body && (body.status === "invalid" || typeof body.error === "string")) {
2030
2505
  return {
2031
2506
  error: typeof body.error === "string" ? body.error : "no error code",
@@ -2223,7 +2698,7 @@ function paymentTools(client) {
2223
2698
  },
2224
2699
  {
2225
2700
  name: "piprail_register",
2226
- description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; searchable within seconds. Returns one outcome per index ({ source, ok, detail }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
2701
+ description: "List an x402 payment-gated resource YOU run on the open indexes so other agents can discover it. Default target is 402 Index \u2014 no auth, no signature, no payment; a self-registered listing is pending review (verify your domain on 402index.io for instant approval). Returns one outcome per index ({ source, ok, detail, visibility, note }); a step the chain can't satisfy comes back ok:false with the reason. Moves no funds; nothing is PipRail-hosted.",
2227
2702
  annotations: {
2228
2703
  title: "Register an x402 endpoint",
2229
2704
  readOnlyHint: false,
@@ -2257,6 +2732,100 @@ function paymentTools(client) {
2257
2732
  ];
2258
2733
  }
2259
2734
 
2735
+ // src/facilitator.ts
2736
+ function safeStringify(value) {
2737
+ return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
2738
+ }
2739
+ function mapReason(reason) {
2740
+ const r = (reason ?? "").toLowerCase();
2741
+ if (r.includes("signature")) return "signature_invalid";
2742
+ if (r.includes("recipient")) return "wrong_recipient";
2743
+ if (r.includes("value") || r.includes("amount")) return "amount_too_low";
2744
+ if (r.includes("valid_before") || r.includes("valid_after") || r.includes("expired")) return "payment_expired";
2745
+ if (r.includes("used") || r.includes("replay") || r.includes("nonce") || r.includes("transaction_state")) {
2746
+ return "tx_already_used";
2747
+ }
2748
+ return "tx_reverted";
2749
+ }
2750
+ async function post(url, body, headers) {
2751
+ const res = await fetch(url, {
2752
+ method: "POST",
2753
+ headers: { "content-type": "application/json", ...headers },
2754
+ body: safeStringify(body)
2755
+ });
2756
+ let json = null;
2757
+ try {
2758
+ json = await res.json();
2759
+ } catch {
2760
+ }
2761
+ return { status: res.status, json };
2762
+ }
2763
+ async function settleViaFacilitator(input) {
2764
+ const base2 = input.url.replace(/\/+$/, "");
2765
+ const body = {
2766
+ x402Version: input.x402Version,
2767
+ paymentPayload: input.paymentPayload,
2768
+ paymentRequirements: input.paymentRequirements
2769
+ };
2770
+ const auth = input.authHeaders ? await input.authHeaders() : {};
2771
+ let verify;
2772
+ try {
2773
+ verify = await post(`${base2}/verify`, body, auth);
2774
+ } catch (err) {
2775
+ throw new SettlementError(
2776
+ `exact settle (facilitator ${base2}): /verify request failed (${err instanceof Error ? err.message : String(err)}).`,
2777
+ { cause: err }
2778
+ );
2779
+ }
2780
+ if (verify.status !== 200) {
2781
+ throw new SettlementError(
2782
+ `exact settle (facilitator ${base2}): /verify returned HTTP ${verify.status} (transport/auth error).`
2783
+ );
2784
+ }
2785
+ const vr = verify.json ?? {};
2786
+ if (vr.isValid === false) {
2787
+ return {
2788
+ ok: false,
2789
+ error: mapReason(vr.invalidReason),
2790
+ detail: `Facilitator rejected the payment: ${vr.invalidReason ?? "invalid"}${vr.invalidMessage ? ` \u2014 ${vr.invalidMessage}` : ""}.`
2791
+ };
2792
+ }
2793
+ let settle;
2794
+ try {
2795
+ settle = await post(`${base2}/settle`, body, auth);
2796
+ } catch (err) {
2797
+ throw new SettlementError(
2798
+ `exact settle (facilitator ${base2}): /settle request failed (${err instanceof Error ? err.message : String(err)}).`,
2799
+ { cause: err }
2800
+ );
2801
+ }
2802
+ if (settle.status !== 200) {
2803
+ throw new SettlementError(
2804
+ `exact settle (facilitator ${base2}): /settle returned HTTP ${settle.status} (transport/auth error).`
2805
+ );
2806
+ }
2807
+ const sr = settle.json ?? {};
2808
+ if (!sr.success) {
2809
+ return {
2810
+ ok: false,
2811
+ error: mapReason(sr.errorReason),
2812
+ detail: `Facilitator settlement failed: ${sr.errorReason ?? "unknown"}${sr.errorMessage ? ` \u2014 ${sr.errorMessage}` : ""}.`
2813
+ };
2814
+ }
2815
+ const receipt = {
2816
+ scheme: "exact",
2817
+ success: true,
2818
+ network: input.receipt.network,
2819
+ transaction: sr.transaction,
2820
+ asset: input.receipt.asset,
2821
+ amount: input.receipt.amount,
2822
+ payer: sr.payer ?? input.payerHint ?? "",
2823
+ payTo: input.receipt.payTo,
2824
+ verifiedAt: (/* @__PURE__ */ new Date()).toISOString()
2825
+ };
2826
+ return { ok: true, receipt };
2827
+ }
2828
+
2260
2829
  // src/server.ts
2261
2830
  function toInvalidBody(result) {
2262
2831
  return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
@@ -2283,9 +2852,10 @@ function createPaymentGate(options) {
2283
2852
  const genNonce = options.generateNonce ?? (() => globalThis.crypto.randomUUID());
2284
2853
  let resolved;
2285
2854
  function ready() {
2286
- return resolved ??= (async () => {
2855
+ if (resolved) return resolved;
2856
+ const p = (async () => {
2287
2857
  const accepts = normaliseAccepts(options);
2288
- return Promise.all(
2858
+ const specs = await Promise.all(
2289
2859
  accepts.map(async (a) => {
2290
2860
  const net = await resolveNetwork2({ chain: a.chain, rpcUrl: a.rpcUrl ?? options.rpcUrl });
2291
2861
  const payTo = a.payTo ?? options.payTo;
@@ -2297,10 +2867,52 @@ function createPaymentGate(options) {
2297
2867
  net.assertValidPayTo(payTo);
2298
2868
  const { asset, decimals, symbol } = net.resolveToken(a.token);
2299
2869
  const amountBase = parseUnits(a.amount, decimals);
2300
- return { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
2870
+ const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
2871
+ if (options.exact) spec.exact = await resolveExactRail(net, asset);
2872
+ return spec;
2301
2873
  })
2302
2874
  );
2875
+ if (options.exact && !specs.some((s) => s.exact)) {
2876
+ throw new Error(
2877
+ "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`."
2878
+ );
2879
+ }
2880
+ return specs;
2303
2881
  })();
2882
+ p.catch(() => {
2883
+ if (resolved === p) resolved = void 0;
2884
+ });
2885
+ resolved = p;
2886
+ return p;
2887
+ }
2888
+ async function resolveExactRail(net, asset) {
2889
+ const cfg = options.exact;
2890
+ if (net.family !== "evm" || asset === "native" || !net.exactDomain || !net.settleExactSelf) {
2891
+ return void 0;
2892
+ }
2893
+ const domain = await net.exactDomain(asset);
2894
+ if (!domain) {
2895
+ throw new Error(
2896
+ `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.)`
2897
+ );
2898
+ }
2899
+ if (cfg.settle === "self") {
2900
+ if (cfg.relayer === void 0) {
2901
+ throw new Error(
2902
+ "requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts transferWithAuthorization), e.g. exact: { settle: 'self', relayer: { privateKey } }."
2903
+ );
2904
+ }
2905
+ const relayer = net.bindWallet(cfg.relayer);
2906
+ return { domain, mode: { kind: "self", relayer } };
2907
+ }
2908
+ return {
2909
+ domain,
2910
+ mode: {
2911
+ kind: "facilitator",
2912
+ url: cfg.settle.facilitator,
2913
+ ...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
2914
+ }
2915
+ };
2304
2916
  }
2305
2917
  const hasCustomStore = Boolean(options.isUsed || options.markUsed);
2306
2918
  const localUsed = /* @__PURE__ */ new Set();
@@ -2337,93 +2949,191 @@ function createPaymentGate(options) {
2337
2949
  }
2338
2950
  };
2339
2951
  }
2340
- async function challenge(resourceUrl = "") {
2952
+ function buildExactAccept(s) {
2953
+ const d = s.exact.domain;
2954
+ return {
2955
+ scheme: "exact",
2956
+ network: s.net.network,
2957
+ amount: s.amountBase.toString(),
2958
+ asset: s.asset,
2959
+ payTo: s.payTo,
2960
+ maxTimeoutSeconds,
2961
+ extra: {
2962
+ assetTransferMethod: "eip3009",
2963
+ name: d.name,
2964
+ version: d.version,
2965
+ minConfirmations,
2966
+ decimals: s.decimals,
2967
+ amountFormatted: s.amountFormatted,
2968
+ ...s.symbol ? { symbol: s.symbol } : {}
2969
+ }
2970
+ };
2971
+ }
2972
+ function buildAccepts(specs, nonce) {
2973
+ const out = [];
2974
+ for (const s of specs) {
2975
+ if (s.exact) out.push(buildExactAccept(s));
2976
+ out.push(buildAccept(s, nonce));
2977
+ }
2978
+ return out;
2979
+ }
2980
+ async function makeChallenge(resourceUrl, opts) {
2341
2981
  const specs = await ready();
2342
2982
  const nonce = genNonce();
2343
2983
  const challenge2 = {
2344
2984
  x402Version: 2,
2345
- error: null,
2346
2985
  resource: {
2347
2986
  url: resourceUrl,
2348
2987
  ...options.description ? { description: options.description } : {}
2349
2988
  },
2350
- accepts: specs.map((s) => buildAccept(s, nonce))
2989
+ accepts: buildAccepts(specs, nonce),
2990
+ ...opts?.error ? { error: opts.error } : {},
2991
+ ...opts?.extensions ? { extensions: opts.extensions } : {}
2351
2992
  };
2352
2993
  return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
2353
2994
  }
2995
+ async function challenge(resourceUrl = "") {
2996
+ return makeChallenge(resourceUrl);
2997
+ }
2354
2998
  async function asChallenge() {
2355
- const { challenge: c, requiredHeader } = await challenge();
2999
+ const { challenge: c, requiredHeader } = await makeChallenge("");
2356
3000
  return { kind: "challenge", challenge: c, requiredHeader, statusCode: 402 };
2357
3001
  }
3002
+ async function rejection(code, detail) {
3003
+ const { challenge: c, requiredHeader } = await makeChallenge("", {
3004
+ error: `${code}: ${detail}`,
3005
+ extensions: { piprail: { code, detail } }
3006
+ });
3007
+ return { kind: "invalid", error: code, detail, challenge: c, requiredHeader, statusCode: 402 };
3008
+ }
3009
+ function fireOnPaid(receipt) {
3010
+ if (options.onPaid) {
3011
+ try {
3012
+ options.onPaid(receipt);
3013
+ } catch {
3014
+ }
3015
+ }
3016
+ }
2358
3017
  async function describe(resourceUrl = "") {
2359
3018
  const specs = await ready();
2360
- const accepts = specs.map((s) => ({
2361
- scheme: "onchain-proof",
2362
- network: s.net.network,
2363
- asset: s.asset,
2364
- payTo: s.payTo,
2365
- amount: s.amountBase.toString(),
2366
- amountFormatted: s.amountFormatted,
2367
- decimals: s.decimals,
2368
- maxTimeoutSeconds,
2369
- ...s.symbol ? { symbol: s.symbol } : {}
2370
- }));
3019
+ const accepts = [];
3020
+ for (const s of specs) {
3021
+ const base2 = {
3022
+ network: s.net.network,
3023
+ asset: s.asset,
3024
+ payTo: s.payTo,
3025
+ amount: s.amountBase.toString(),
3026
+ amountFormatted: s.amountFormatted,
3027
+ decimals: s.decimals,
3028
+ maxTimeoutSeconds,
3029
+ ...s.symbol ? { symbol: s.symbol } : {}
3030
+ };
3031
+ if (s.exact) accepts.push({ scheme: "exact", ...base2 });
3032
+ accepts.push({ scheme: "onchain-proof", ...base2 });
3033
+ }
2371
3034
  return {
2372
3035
  url: resourceUrl,
2373
3036
  ...options.description ? { description: options.description } : {},
2374
3037
  accepts
2375
3038
  };
2376
3039
  }
2377
- async function verify(paymentSignature) {
2378
- const raw = normaliseHeader(paymentSignature);
2379
- if (!raw) return asChallenge();
2380
- const sig = parseSignatureHeader(raw);
2381
- if (!sig || !sig.accepted || typeof sig.accepted.network !== "string" || typeof sig.accepted.asset !== "string") {
2382
- return asChallenge();
2383
- }
3040
+ async function verifyOnchainProof(sig) {
2384
3041
  const specs = await ready();
2385
3042
  const spec = specs.find(
2386
3043
  (s) => s.net.network === sig.accepted.network && s.asset === sig.accepted.asset
2387
3044
  );
2388
3045
  if (!spec) {
2389
- return {
2390
- kind: "invalid",
2391
- error: "transfer_not_found",
2392
- 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(", ")}).`,
2393
- statusCode: 402
2394
- };
3046
+ return rejection(
3047
+ "transfer_not_found",
3048
+ `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(", ")}).`
3049
+ );
2395
3050
  }
2396
3051
  const ref = sig.payload.txHash;
2397
- if (await claimTx(ref)) {
2398
- return {
2399
- kind: "invalid",
2400
- error: "tx_already_used",
2401
- detail: `Proof ${ref} was already redeemed.`,
2402
- statusCode: 402
2403
- };
2404
- }
3052
+ if (await claimTx(ref)) return rejection("tx_already_used", `Proof ${ref} was already redeemed.`);
2405
3053
  const result = await spec.net.verify(ref, buildAccept(spec, sig.payload.nonce));
2406
3054
  if (!result.ok) {
2407
3055
  await settleTx(ref, false);
2408
- return {
2409
- kind: "invalid",
2410
- error: result.error,
2411
- detail: result.detail,
2412
- statusCode: 402
2413
- };
3056
+ return rejection(result.error, result.detail);
2414
3057
  }
2415
3058
  await settleTx(ref, true);
2416
- if (options.onPaid) {
2417
- try {
2418
- options.onPaid(result.receipt);
2419
- } catch {
3059
+ fireOnPaid(result.receipt);
3060
+ return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
3061
+ }
3062
+ async function verifyExact(exact) {
3063
+ const specs = await ready();
3064
+ const exactSpecs = specs.filter((s) => s.exact);
3065
+ if (exactSpecs.length === 0) {
3066
+ return rejection("transfer_not_found", "This resource offers no standard `exact` rail.");
3067
+ }
3068
+ const isCaip = exact.network.startsWith("eip155:");
3069
+ let candidates = isCaip ? exactSpecs.filter((s) => s.net.network === exact.network) : exactSpecs;
3070
+ if (exact.asset) {
3071
+ candidates = candidates.filter((s) => s.asset.toLowerCase() === exact.asset.toLowerCase());
3072
+ }
3073
+ let spec = candidates[0];
3074
+ if (!isCaip && !exact.asset && exactSpecs.length > 1) spec = void 0;
3075
+ if (!spec && !isCaip && !exact.asset && exactSpecs.length === 1) spec = exactSpecs[0];
3076
+ if (!spec || !spec.exact) {
3077
+ return rejection(
3078
+ "transfer_not_found",
3079
+ `No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
3080
+ );
3081
+ }
3082
+ const nonce = exact.payload.authorization.nonce;
3083
+ if (await claimTx(nonce)) {
3084
+ return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
3085
+ }
3086
+ const accept = buildExactAccept(spec);
3087
+ const mode = spec.exact.mode;
3088
+ let result;
3089
+ try {
3090
+ if (mode.kind === "self") {
3091
+ result = await spec.net.settleExactSelf({ relayer: mode.relayer, payload: exact.payload, accept });
3092
+ } else {
3093
+ result = await settleViaFacilitator({
3094
+ url: mode.url,
3095
+ ...mode.authHeaders ? { authHeaders: mode.authHeaders } : {},
3096
+ // PipRail always builds a v2-shaped paymentRequirements (CAIP-2 network + `amount`),
3097
+ // so force x402Version:2 — echoing a v1 client's version here would hand the facilitator
3098
+ // a self-inconsistent request (v1 envelope, v2 requirements). The inner payload is
3099
+ // byte-identical across versions, so forwarding it verbatim is fine.
3100
+ x402Version: 2,
3101
+ paymentPayload: exact.raw,
3102
+ paymentRequirements: {
3103
+ scheme: "exact",
3104
+ network: accept.network,
3105
+ asset: accept.asset,
3106
+ amount: accept.amount,
3107
+ payTo: accept.payTo,
3108
+ maxTimeoutSeconds: accept.maxTimeoutSeconds,
3109
+ extra: { name: accept.extra.name, version: accept.extra.version }
3110
+ },
3111
+ receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
3112
+ payerHint: exact.payload.authorization.from
3113
+ });
2420
3114
  }
3115
+ } catch (err) {
3116
+ await settleTx(nonce, false);
3117
+ throw err;
2421
3118
  }
2422
- return {
2423
- kind: "paid",
2424
- receipt: result.receipt,
2425
- receiptHeader: buildReceiptHeader(result.receipt)
2426
- };
3119
+ if (!result.ok) {
3120
+ await settleTx(nonce, false);
3121
+ return rejection(result.error, result.detail);
3122
+ }
3123
+ await settleTx(nonce, true);
3124
+ fireOnPaid(result.receipt);
3125
+ return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
3126
+ }
3127
+ async function verify(paymentSignature) {
3128
+ const raw = normaliseHeader(paymentSignature);
3129
+ if (!raw) return asChallenge();
3130
+ const sig = parseSignatureHeader(raw);
3131
+ if (sig && sig.accepted && typeof sig.accepted.network === "string" && typeof sig.accepted.asset === "string") {
3132
+ return verifyOnchainProof(sig);
3133
+ }
3134
+ const exact = parseExactPaymentHeader(raw);
3135
+ if (exact) return verifyExact(exact);
3136
+ return asChallenge();
2427
3137
  }
2428
3138
  return { challenge, verify, describe };
2429
3139
  }
@@ -2432,14 +3142,20 @@ function requirePayment(options) {
2432
3142
  return async (req, res, next) => {
2433
3143
  let result;
2434
3144
  try {
2435
- result = await gate.verify(req.headers[HEADER_SIGNATURE]);
3145
+ result = await gate.verify(req.headers[HEADER_SIGNATURE] ?? req.headers[HEADER_SIGNATURE_V1]);
2436
3146
  } catch (err) {
3147
+ if (err instanceof SettlementError) {
3148
+ res.status(502);
3149
+ res.json({ x402Version: 2, error: "settlement_failed", detail: err.message });
3150
+ return;
3151
+ }
2437
3152
  next(err);
2438
3153
  return;
2439
3154
  }
2440
3155
  switch (result.kind) {
2441
3156
  case "paid":
2442
3157
  res.setHeader(HEADER_RESPONSE, result.receiptHeader);
3158
+ res.setHeader(HEADER_RESPONSE_V1, result.receiptHeader);
2443
3159
  return next();
2444
3160
  case "challenge":
2445
3161
  res.setHeader(HEADER_REQUIRED, result.requiredHeader);
@@ -2447,8 +3163,9 @@ function requirePayment(options) {
2447
3163
  res.json(result.challenge);
2448
3164
  return;
2449
3165
  case "invalid":
3166
+ res.setHeader(HEADER_REQUIRED, result.requiredHeader);
2450
3167
  res.status(result.statusCode);
2451
- res.json(toInvalidBody(result));
3168
+ res.json(result.challenge);
2452
3169
  return;
2453
3170
  }
2454
3171
  };
@@ -2458,104 +3175,6 @@ function normaliseHeader(value) {
2458
3175
  return value;
2459
3176
  }
2460
3177
 
2461
- // src/drivers/evm/exact.ts
2462
- var EXACT_NETWORK_SLUGS = {
2463
- ethereum: 1,
2464
- base: 8453,
2465
- "base-sepolia": 84532,
2466
- arbitrum: 42161,
2467
- optimism: 10,
2468
- polygon: 137,
2469
- avalanche: 43114
2470
- };
2471
- function chainIdForExactNetwork(slug) {
2472
- return EXACT_NETWORK_SLUGS[slug] ?? null;
2473
- }
2474
- var EIP3009_TYPES = {
2475
- TransferWithAuthorization: [
2476
- { name: "from", type: "address" },
2477
- { name: "to", type: "address" },
2478
- { name: "value", type: "uint256" },
2479
- { name: "validAfter", type: "uint256" },
2480
- { name: "validBefore", type: "uint256" },
2481
- { name: "nonce", type: "bytes32" }
2482
- ]
2483
- };
2484
- function parseExactRequirements(body) {
2485
- if (!body || typeof body !== "object") return null;
2486
- const accepts = body.accepts;
2487
- if (!Array.isArray(accepts)) return null;
2488
- const out = [];
2489
- for (const raw of accepts) {
2490
- if (!raw || typeof raw !== "object") continue;
2491
- const a = raw;
2492
- if (a.scheme !== "exact") continue;
2493
- const amount = a.maxAmountRequired ?? a.amount;
2494
- if (typeof a.network !== "string" || typeof amount !== "string" || typeof a.asset !== "string" || typeof a.payTo !== "string") {
2495
- continue;
2496
- }
2497
- out.push({
2498
- scheme: "exact",
2499
- network: a.network,
2500
- maxAmountRequired: amount,
2501
- asset: a.asset,
2502
- payTo: a.payTo,
2503
- maxTimeoutSeconds: typeof a.maxTimeoutSeconds === "number" ? a.maxTimeoutSeconds : 600,
2504
- ...a.extra && typeof a.extra === "object" ? { extra: a.extra } : {},
2505
- ...typeof a.description === "string" ? { description: a.description } : {},
2506
- ...typeof a.resource === "string" ? { resource: a.resource } : {}
2507
- });
2508
- }
2509
- return out;
2510
- }
2511
- async function buildExactAuthorization(params) {
2512
- const { account, accept, chainId, now, nonce } = params;
2513
- if (!account.signTypedData) {
2514
- throw new Error("buildExactAuthorization: the account cannot sign EIP-712 typed data.");
2515
- }
2516
- const authorization = {
2517
- from: account.address,
2518
- to: accept.payTo,
2519
- value: accept.maxAmountRequired,
2520
- validAfter: "0",
2521
- validBefore: String(now + accept.maxTimeoutSeconds),
2522
- nonce
2523
- };
2524
- const signature = await account.signTypedData({
2525
- domain: {
2526
- name: accept.extra?.name ?? "USD Coin",
2527
- version: accept.extra?.version ?? "2",
2528
- chainId,
2529
- verifyingContract: accept.asset
2530
- },
2531
- types: EIP3009_TYPES,
2532
- primaryType: "TransferWithAuthorization",
2533
- message: {
2534
- from: authorization.from,
2535
- to: authorization.to,
2536
- value: BigInt(authorization.value),
2537
- validAfter: BigInt(authorization.validAfter),
2538
- validBefore: BigInt(authorization.validBefore),
2539
- nonce: authorization.nonce
2540
- }
2541
- });
2542
- return { authorization, signature };
2543
- }
2544
- function base64(str) {
2545
- if (typeof btoa === "function") return btoa(str);
2546
- if (typeof Buffer !== "undefined") return Buffer.from(str, "utf8").toString("base64");
2547
- throw new Error("No base64 encoder available in this runtime.");
2548
- }
2549
- function encodeXPaymentHeader(input) {
2550
- const payload = {
2551
- x402Version: input.x402Version ?? 1,
2552
- scheme: "exact",
2553
- network: input.network,
2554
- payload: { signature: input.signature, authorization: input.authorization }
2555
- };
2556
- return base64(JSON.stringify(payload));
2557
- }
2558
-
2559
3178
  // src/discovery.ts
2560
3179
  var GENERATOR = "@piprail/sdk \xB7 https://piprail.com";
2561
3180
  function pathOf(url) {
@@ -2613,9 +3232,15 @@ function buildX402DnsTxt(input) {
2613
3232
  export {
2614
3233
  CHAINS,
2615
3234
  ConfirmationTimeoutError,
3235
+ DIRECTORY_INFO,
2616
3236
  EIP3009_TYPES,
2617
3237
  EXACT_NETWORK_SLUGS,
2618
3238
  GENERATOR,
3239
+ HEADER_REQUIRED,
3240
+ HEADER_RESPONSE,
3241
+ HEADER_RESPONSE_V1,
3242
+ HEADER_SIGNATURE,
3243
+ HEADER_SIGNATURE_V1,
2619
3244
  InsufficientFundsError,
2620
3245
  InvalidEnvelopeError,
2621
3246
  MaxRetriesExceededError,
@@ -2627,6 +3252,7 @@ export {
2627
3252
  PipRailClient,
2628
3253
  PipRailError,
2629
3254
  RecipientNotReadyError,
3255
+ SettlementError,
2630
3256
  UnknownTokenError,
2631
3257
  UnsupportedNetworkError,
2632
3258
  WrongChainError,
@@ -2640,22 +3266,28 @@ export {
2640
3266
  buildX402DnsTxt,
2641
3267
  chainIdForExactNetwork,
2642
3268
  createPaymentGate,
3269
+ decorateOutcome,
3270
+ eip3009Abi,
2643
3271
  encodeXPaymentHeader,
2644
3272
  evaluatePolicy,
3273
+ getDirectoryInfo,
2645
3274
  normalizeNetwork,
2646
3275
  parseChallenge,
3276
+ parseExactPaymentHeader,
2647
3277
  parseExactRequirements,
2648
3278
  parseReceipt,
2649
3279
  parseSignatureHeader,
2650
3280
  paymentTools,
2651
3281
  pickAccept,
2652
3282
  planAcross,
3283
+ readExactDomain,
2653
3284
  register402Index,
2654
3285
  registerDriver,
2655
3286
  registerX402Scan,
2656
3287
  requirePayment,
2657
3288
  resolveChain,
2658
3289
  searchOpenIndexes,
3290
+ settleViaFacilitator,
2659
3291
  toInsufficientFundsError,
2660
3292
  toInvalidBody
2661
3293
  };