@piprail/sdk 1.20.1 → 1.21.1
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/CHANGELOG.md +71 -0
- package/dist/index.cjs +165 -65
- package/dist/index.d.cts +118 -37
- package/dist/index.d.ts +118 -37
- package/dist/index.js +156 -56
- package/dist/solana-IBVUZS54.js +724 -0
- package/dist/solana-WG7RGDSI.cjs +724 -0
- package/package.json +1 -1
- package/dist/solana-MPPE6K24.cjs +0 -364
- package/dist/solana-WDKWWF33.js +0 -364
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,77 @@ All notable changes to `@piprail/sdk` are documented here. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the
|
|
5
5
|
versions follow [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.21.1] — 2026-06-13 — facilitator hardening + gasless auto-routing + agent-facing docs
|
|
8
|
+
|
|
9
|
+
A correctness/robustness patch over 1.21.0, after a full re-review and **live mainnet tests of the
|
|
10
|
+
facilitator path on both Solana and Base (EVM)**. Opt-in surface unchanged; `onchain-proof` still the
|
|
11
|
+
default everywhere.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Gasless auto-routing now works on Solana.** The Solana driver's `estimateCost` now reports the
|
|
15
|
+
`exact` rail as **~0 buyer gas** (the fee payer — a facilitator like PayAI, or your relayer —
|
|
16
|
+
broadcasts and pays the SOL fee), mirroring the EVM driver. Before, it reported the same fee as
|
|
17
|
+
`onchain-proof`, so `planPayment()`/`fetch({ autoRoute: true })` wouldn't prefer the gasless rail —
|
|
18
|
+
now they correctly pick it. (Live-proven: autoRoute chooses `exact` even when the buyer holds SOL.)
|
|
19
|
+
- **Buyer EIP-3009 domain read is resilient to a flaky RPC.** `payExactEvm` now retries the on-chain
|
|
20
|
+
EIP-712 domain read once before concluding a token "isn't EIP-3009", so a rate-limited public RPC can
|
|
21
|
+
no longer misreport real USDC as un-payable and block an otherwise-valid gasless payment. The error
|
|
22
|
+
message, if it still fails, now names the transient-RPC possibility instead of asserting non-EIP-3009.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- **Permit2 can't be facilitator-settled — the gate now says so clearly.** A third-party facilitator
|
|
26
|
+
settles the standard EIP-3009 (EVM) / SVM (Solana) schemes, not PipRail's `x402ExactPermit2Proxy`. A
|
|
27
|
+
*forced* `exact: { method: 'permit2', settle: { facilitator } }` now throws a clear config error, and
|
|
28
|
+
an *auto*-selected Permit2 token is dropped to `onchain-proof`-only over a facilitator (rather than
|
|
29
|
+
advertising a rail it could never settle). Keyed off the resolved method, so Solana (`svm`) is unaffected.
|
|
30
|
+
- **Clearer facilitator-unreachable error.** When a Solana facilitator's `GET /supported` can't be read
|
|
31
|
+
at challenge time, the gate now explains the real cause (and points at `exact.settle.feePayer` /
|
|
32
|
+
`settle: 'self'`) instead of the misleading "none of the offered rails support it".
|
|
33
|
+
- **`PIPRAIL_AGENT_GUIDE` now teaches the gasless `exact` rail** — the two rails, that it's operator-opt-in,
|
|
34
|
+
that the on-chain method is auto-selected, and that on a timeout the `exact` `.ref` is an authorization
|
|
35
|
+
**nonce** (re-present the same authorization, never re-sign). Plus docs: the "whole model in 30 seconds"
|
|
36
|
+
(gas vs `onchain-proof` vs `exact`'s three methods), a "when the facilitator fails" breakdown, and
|
|
37
|
+
agent-toolkit/MCP gasless guidance.
|
|
38
|
+
|
|
39
|
+
## [1.21.0] — 2026-06-13 — standard `exact` rail on Solana (SVM) + fully-gasless facilitator mode
|
|
40
|
+
|
|
41
|
+
Opt-in, defaults unchanged. `onchain-proof` stays the default on every chain and is byte-identical.
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
- **The standard x402 `exact` rail now covers Solana**, not just EVM — so any standard x402
|
|
45
|
+
client/agent that speaks `exact` (the majority) can pay a PipRail Solana gate directly, and a
|
|
46
|
+
PipRail agent can pay any standard Solana `exact` server. Per the ratified `scheme_exact_svm.md`:
|
|
47
|
+
the buyer partial-signs an SPL `TransferChecked` transaction whose **fee payer is the merchant**,
|
|
48
|
+
and the gate co-signs as fee payer + broadcasts against your own RPC — **no facilitator, no
|
|
49
|
+
backend** (the same self-settle model as the EVM `exact` rail). Enable it exactly as on EVM:
|
|
50
|
+
`requirePayment({ chain: 'solana', token: 'USDC', amount, payTo, exact: { settle: 'self', relayer } })`,
|
|
51
|
+
and on the client `new PipRailClient({ chain: 'solana', wallet, schemes: ['onchain-proof', 'exact'] })`.
|
|
52
|
+
- **Buyer-gasless on Solana, for any SPL token.** The buyer signs the canonical
|
|
53
|
+
`[setComputeUnitLimit, setComputeUnitPrice, TransferChecked]` transaction and spends **zero SOL** —
|
|
54
|
+
only the token funds the payment. Gasless-ness is transaction-level (the fee payer), not token-level,
|
|
55
|
+
so **USDC and USDT are equally gasless** (no EIP-3009/Permit2 equivalent needed, unlike EVM). The fee
|
|
56
|
+
payer must be **distinct from `payTo`** (a scheme MUST-rule the SDK enforces), and the recipient's
|
|
57
|
+
token account must already exist (the exact rail won't create it — `onchain-proof` does).
|
|
58
|
+
- **Solana facilitator mode → _fully_ gasless (neither buyer nor merchant pays).** `exact: { settle: {
|
|
59
|
+
facilitator } }` now works on Solana, not just EVM: the gate auto-discovers the facilitator's
|
|
60
|
+
fee-payer pubkey from its `GET /supported`, advertises it, and forwards settlement — the **facilitator
|
|
61
|
+
pays the gas**. Live-proven on mainnet against **PayAI** (`https://facilitator.payai.network`, no API
|
|
62
|
+
key). Self-settle (your own relayer pays the sub-cent fee) remains available; PipRail hosts nothing
|
|
63
|
+
and is never the fee payer.
|
|
64
|
+
|
|
65
|
+
### Security (defense-in-depth, after an adversarial review)
|
|
66
|
+
- The gate counts **only authentically-signed** transfers toward the required amount (a
|
|
67
|
+
tiny-signed + large-unsigned multi-transfer can't reach the price), enforces fee-payer **isolation by
|
|
68
|
+
resolved pubkey** across every instruction (ALT-safe, not a literal index check), and **canonicalizes**
|
|
69
|
+
the SVM replay key so a base64-malleated re-submission can't bypass the replay claim.
|
|
70
|
+
|
|
71
|
+
### Changed (internal — no API change)
|
|
72
|
+
- The `exact` rail moved behind a new driver SPI, `ResolvedNetwork.resolveExactRail`, so the gate is
|
|
73
|
+
fully chain-agnostic (it no longer special-cases EVM). EVM's EIP-3009/Permit2 method-selection is
|
|
74
|
+
unchanged in behaviour; Solana plugs in `{ method: 'svm', extra: { feePayer, tokenProgram } }`.
|
|
75
|
+
- The wire types gained the SVM `exact` payload (`{ transaction }`) and the `extra` keys
|
|
76
|
+
`feePayer`/`memo`/`tokenProgram`; `assetTransferMethod` now also accepts `'svm'`.
|
|
77
|
+
|
|
7
78
|
## [1.20.1] — 2026-06-11 — gate replay store: bounded + exception-safe
|
|
8
79
|
|
|
9
80
|
Patch — internal robustness on the gate's built-in replay protection. No API change, no visible
|
package/dist/index.cjs
CHANGED
|
@@ -533,6 +533,37 @@ var EXACT_NETWORK_SLUGS = {
|
|
|
533
533
|
function chainIdForExactNetwork(slug) {
|
|
534
534
|
return _nullishCoalesce(EXACT_NETWORK_SLUGS[slug], () => ( null));
|
|
535
535
|
}
|
|
536
|
+
async function resolveExactRailEvm(input) {
|
|
537
|
+
const { asset, method, readDomain, permit2Supported } = input;
|
|
538
|
+
if (asset === "native") return null;
|
|
539
|
+
const want = method === "eip3009" || method === "permit2" ? method : "auto";
|
|
540
|
+
let chosen;
|
|
541
|
+
let extra = {};
|
|
542
|
+
if (want === "permit2") {
|
|
543
|
+
chosen = "permit2";
|
|
544
|
+
} else {
|
|
545
|
+
const d = await readDomain(asset);
|
|
546
|
+
if (d) {
|
|
547
|
+
chosen = "eip3009";
|
|
548
|
+
extra = { name: d.name, version: d.version };
|
|
549
|
+
} else if (want === "eip3009") {
|
|
550
|
+
throw new Error(
|
|
551
|
+
`requirePayment: exact \`method: 'eip3009'\` requested for ${asset}, 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.)`
|
|
552
|
+
);
|
|
553
|
+
} else {
|
|
554
|
+
chosen = "permit2";
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
if (chosen === "permit2" && !permit2Supported()) {
|
|
558
|
+
if (method === "permit2") {
|
|
559
|
+
throw new Error(
|
|
560
|
+
`requirePayment: exact \`method: 'permit2'\` needs the x402 Permit2 proxy deployed on this chain, but it isn't there. Offer an EIP-3009 token (gasless, no proxy), or drop \`exact\` on this chain. (See PERMIT2_PROXY_CHAIN_IDS.)`
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
return null;
|
|
564
|
+
}
|
|
565
|
+
return { method: chosen, extra };
|
|
566
|
+
}
|
|
536
567
|
var EIP3009_TYPES = {
|
|
537
568
|
TransferWithAuthorization: [
|
|
538
569
|
{ name: "from", type: "address" },
|
|
@@ -635,10 +666,14 @@ async function payExactEvm(input) {
|
|
|
635
666
|
`exact buyer rail requires an EOA signer; ${account.address} is a contract / EIP-1271 / EIP-7702-delegated account (no recoverable ECDSA signature). Pay via onchain-proof.`
|
|
636
667
|
);
|
|
637
668
|
}
|
|
638
|
-
|
|
669
|
+
let domain = await readExactDomain(publicClient, accept.asset);
|
|
670
|
+
if (!domain) {
|
|
671
|
+
await new Promise((r) => setTimeout(r, 300));
|
|
672
|
+
domain = await readExactDomain(publicClient, accept.asset);
|
|
673
|
+
}
|
|
639
674
|
if (!domain) {
|
|
640
675
|
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
641
|
-
`exact: ${accept.asset} on ${accept.network} isn't an EIP-3009 token (USDT needs Permit2; native
|
|
676
|
+
`exact: couldn't derive the EIP-712 domain for ${accept.asset} on ${accept.network}. Either it isn't an EIP-3009 token (USDT needs Permit2; native/plain ERC-20 aren't exact-payable) \u2014 pay via onchain-proof \u2014 OR your RPC couldn't read it (transient): if the gate advertised eip3009, pass a reliable rpcUrl and retry.`
|
|
642
677
|
);
|
|
643
678
|
}
|
|
644
679
|
const g = globalThis.crypto;
|
|
@@ -1417,11 +1452,14 @@ function parseExactPaymentHeader(value) {
|
|
|
1417
1452
|
if (typeof network !== "string") return null;
|
|
1418
1453
|
const payload = v.payload;
|
|
1419
1454
|
if (!payload || typeof payload !== "object") return null;
|
|
1420
|
-
const signature = payload.signature;
|
|
1421
|
-
if (typeof signature !== "string") return null;
|
|
1422
1455
|
const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
|
|
1423
1456
|
const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
|
|
1424
1457
|
const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
|
|
1458
|
+
if (typeof payload.transaction === "string") {
|
|
1459
|
+
return { ...base2, method: "svm", payload: { transaction: payload.transaction } };
|
|
1460
|
+
}
|
|
1461
|
+
const signature = payload.signature;
|
|
1462
|
+
if (typeof signature !== "string") return null;
|
|
1425
1463
|
const authorization = payload.authorization;
|
|
1426
1464
|
if (authorization && typeof authorization === "object") {
|
|
1427
1465
|
for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
|
|
@@ -1708,6 +1746,16 @@ function makeEvmNetwork(resolved) {
|
|
|
1708
1746
|
exactPermit2Supported() {
|
|
1709
1747
|
return isPermit2ProxyChain(resolved.chainId);
|
|
1710
1748
|
},
|
|
1749
|
+
// The gate's rail-advertisement SPI — EIP-3009 vs Permit2 selection (the pure helper),
|
|
1750
|
+
// injecting the on-chain domain read + the Permit2 proxy-presence check.
|
|
1751
|
+
async resolveExactRail({ asset, method }) {
|
|
1752
|
+
return resolveExactRailEvm({
|
|
1753
|
+
asset,
|
|
1754
|
+
method,
|
|
1755
|
+
readDomain: (a) => readExactDomain(publicClient, a),
|
|
1756
|
+
permit2Supported: () => isPermit2ProxyChain(resolved.chainId)
|
|
1757
|
+
});
|
|
1758
|
+
},
|
|
1711
1759
|
async settleExactSelf({ relayer, payload, accept }) {
|
|
1712
1760
|
const a = relayer._native;
|
|
1713
1761
|
if ("permit2Authorization" in payload) {
|
|
@@ -1720,6 +1768,9 @@ function makeEvmNetwork(resolved) {
|
|
|
1720
1768
|
accept
|
|
1721
1769
|
});
|
|
1722
1770
|
}
|
|
1771
|
+
if ("transaction" in payload) {
|
|
1772
|
+
return { ok: false, error: "signature_invalid", detail: "An SVM (Solana) payload was submitted to an EVM exact rail." };
|
|
1773
|
+
}
|
|
1723
1774
|
return verifyAndSettleExactEvm({
|
|
1724
1775
|
publicClient,
|
|
1725
1776
|
walletClient: a.walletClient,
|
|
@@ -1744,7 +1795,7 @@ var loaders = {
|
|
|
1744
1795
|
solana: async () => {
|
|
1745
1796
|
let mod;
|
|
1746
1797
|
try {
|
|
1747
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-
|
|
1798
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-WG7RGDSI.cjs")));
|
|
1748
1799
|
} catch (cause) {
|
|
1749
1800
|
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1750
1801
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
@@ -2770,7 +2821,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2770
2821
|
* before publishing, so retry with a brief backoff if a fresh listing is missing.
|
|
2771
2822
|
* - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
|
|
2772
2823
|
* `onchain-proof` rails by default, and standard `exact` rails too once you opt in
|
|
2773
|
-
* with `schemes: ['onchain-proof', 'exact']` (EVM
|
|
2824
|
+
* with `schemes: ['onchain-proof', 'exact']` (EVM EIP-3009/Permit2 + Solana SVM).
|
|
2774
2825
|
*/
|
|
2775
2826
|
async discover(opts = {}) {
|
|
2776
2827
|
const found = await searchOpenIndexes({
|
|
@@ -2955,7 +3006,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2955
3006
|
);
|
|
2956
3007
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
2957
3008
|
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
2958
|
-
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (EVM +
|
|
3009
|
+
`This 402 offers a standard 'exact' rail on ${net.network}, but the ${net.family} family can't pay 'exact' (supported on EVM + Solana today), and no 'onchain-proof' rail was offered.`
|
|
2959
3010
|
);
|
|
2960
3011
|
}
|
|
2961
3012
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
@@ -3335,7 +3386,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3335
3386
|
async payExactRail(net, wallet, accept, url, init, quote) {
|
|
3336
3387
|
if (!net.payExact) {
|
|
3337
3388
|
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
3338
|
-
`the ${net.family} family can't pay a standard 'exact' rail (EVM +
|
|
3389
|
+
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM + Solana today).`
|
|
3339
3390
|
);
|
|
3340
3391
|
}
|
|
3341
3392
|
throwIfAborted(_optionalChain([init, 'optionalAccess', _48 => _48.signal]));
|
|
@@ -3600,6 +3651,18 @@ straight from your wallet to the server; PipRail custodies nothing. Follow this.
|
|
|
3600
3651
|
and return the result.
|
|
3601
3652
|
Always plan before you pay so you never commit to a payment you cannot finish.
|
|
3602
3653
|
|
|
3654
|
+
## Gasless \u2014 the exact rail (zero gas for you)
|
|
3655
|
+
A 402 may offer up to two rails; you don't choose per payment \u2014 the client does, automatically:
|
|
3656
|
+
- onchain-proof (PipRail's default): you broadcast the payment yourself and pay the network gas
|
|
3657
|
+
(the native coin \u2014 ETH/SOL/\u2026). Works on every chain.
|
|
3658
|
+
- exact (the ratified x402 rail, opt-in): you only SIGN; the server \u2014 or a facilitator it chose
|
|
3659
|
+
(e.g. PayAI) \u2014 broadcasts it, so you pay ZERO gas (you need only the token, no native coin). It
|
|
3660
|
+
works on EVM + Solana, and the on-chain method (EIP-3009 / Permit2 / SVM) is picked automatically.
|
|
3661
|
+
When the exact scheme is enabled AND balance-aware routing is on, paying picks the cheapest
|
|
3662
|
+
settleable rail \u2014 i.e. the gasless exact one. Nothing changes in your loop: quote \u2192 plan \u2192 pay is
|
|
3663
|
+
identical. The exact scheme is OPT-IN by the operator (MCP: PIPRAIL_SCHEMES=onchain-proof,exact);
|
|
3664
|
+
you can't enable it yourself, but you can report when a 402 needs it (see UNSUPPORTED_SCHEME below).
|
|
3665
|
+
|
|
3603
3666
|
## Reading a refusal \u2014 never crash, never double-spend
|
|
3604
3667
|
A failed pay returns a STRUCTURED object, never a thrown error you must catch:
|
|
3605
3668
|
{ ok:false, code, reason, explain, ref?, reasonCode?, declined? }
|
|
@@ -3617,9 +3680,13 @@ Branch on \`code\` (always reliable). Key cases:
|
|
|
3617
3680
|
- code:'INSUFFICIENT_FUNDS' \u2014 top up the wallet (token and/or native gas), retry.
|
|
3618
3681
|
- code:'PAYMENT_TIMEOUT' / 'MAX_RETRIES_EXCEEDED' / 'CONFIRMATION_TIMEOUT' \u2014 the
|
|
3619
3682
|
payment may ALREADY be on-chain. Recover using the proof on \`.ref\` (re-verify
|
|
3620
|
-
or re-submit it); never re-pay \u2014 a fresh payment would double-spend.
|
|
3683
|
+
or re-submit it); never re-pay \u2014 a fresh payment would double-spend. On a gasless
|
|
3684
|
+
exact rail \`.ref\` is the authorization NONCE, not a tx hash: re-present the SAME
|
|
3685
|
+
signed authorization, never sign a fresh one (that would risk a double-spend).
|
|
3621
3686
|
- code:'NO_COMPATIBLE_ACCEPT' / 'UNSUPPORTED_SCHEME' \u2014 the 402 isn't payable on
|
|
3622
3687
|
your chain/scheme; \`explain\` says whether it's the wrong chain or a scheme to enable.
|
|
3688
|
+
If it's a standard x402 server offering an exact rail, that's a config fix the operator makes
|
|
3689
|
+
once (enable the exact scheme); report it, don't retry the same call blindly.
|
|
3623
3690
|
|
|
3624
3691
|
## Knowing your leash \u2014 call piprail_budget
|
|
3625
3692
|
piprail_budget tells you how much budget and time you have left, per
|
|
@@ -4012,6 +4079,24 @@ function buildX402DnsTxt(input) {
|
|
|
4012
4079
|
}
|
|
4013
4080
|
|
|
4014
4081
|
// src/facilitator.ts
|
|
4082
|
+
async function fetchFacilitatorFeePayer(url, network, timeoutMs = 8e3) {
|
|
4083
|
+
const base2 = url.replace(/\/+$/, "");
|
|
4084
|
+
const ctrl = new AbortController();
|
|
4085
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
4086
|
+
try {
|
|
4087
|
+
const res = await fetch(`${base2}/supported`, { signal: ctrl.signal });
|
|
4088
|
+
if (!res.ok) return void 0;
|
|
4089
|
+
const body = await res.json();
|
|
4090
|
+
const kinds = Array.isArray(_optionalChain([body, 'optionalAccess', _64 => _64.kinds])) ? body.kinds : [];
|
|
4091
|
+
const kind = kinds.find((k) => _optionalChain([k, 'optionalAccess', _65 => _65.scheme]) === "exact" && _optionalChain([k, 'optionalAccess', _66 => _66.network]) === network);
|
|
4092
|
+
const fp = _optionalChain([kind, 'optionalAccess', _67 => _67.extra, 'optionalAccess', _68 => _68.feePayer]);
|
|
4093
|
+
return typeof fp === "string" ? fp : void 0;
|
|
4094
|
+
} catch (e32) {
|
|
4095
|
+
return void 0;
|
|
4096
|
+
} finally {
|
|
4097
|
+
clearTimeout(timer);
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4015
4100
|
function safeStringify(value) {
|
|
4016
4101
|
return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
4017
4102
|
}
|
|
@@ -4035,7 +4120,7 @@ async function post(url, body, headers) {
|
|
|
4035
4120
|
let json = null;
|
|
4036
4121
|
try {
|
|
4037
4122
|
json = await res.json();
|
|
4038
|
-
} catch (
|
|
4123
|
+
} catch (e33) {
|
|
4039
4124
|
}
|
|
4040
4125
|
return { status: res.status, json };
|
|
4041
4126
|
}
|
|
@@ -4134,6 +4219,7 @@ function createPaymentGate(options) {
|
|
|
4134
4219
|
if (resolved) return resolved;
|
|
4135
4220
|
const p = (async () => {
|
|
4136
4221
|
const accepts = normaliseAccepts(options);
|
|
4222
|
+
const exactSkips = [];
|
|
4137
4223
|
const specs = await Promise.all(
|
|
4138
4224
|
accepts.map(async (a) => {
|
|
4139
4225
|
const net = await resolveNetwork2({ chain: a.chain, rpcUrl: _nullishCoalesce(a.rpcUrl, () => ( options.rpcUrl)) });
|
|
@@ -4147,13 +4233,17 @@ function createPaymentGate(options) {
|
|
|
4147
4233
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
4148
4234
|
const amountBase = _chunkPA6YD3HLcjs.parseUnits.call(void 0, a.amount, decimals);
|
|
4149
4235
|
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
4150
|
-
if (options.exact)
|
|
4236
|
+
if (options.exact) {
|
|
4237
|
+
const outcome = await resolveExactRail(net, asset);
|
|
4238
|
+
if (outcome.rail) spec.exact = outcome.rail;
|
|
4239
|
+
else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
|
|
4240
|
+
}
|
|
4151
4241
|
return spec;
|
|
4152
4242
|
})
|
|
4153
4243
|
);
|
|
4154
4244
|
if (options.exact && !specs.some((s) => s.exact)) {
|
|
4155
4245
|
throw new Error(
|
|
4156
|
-
"requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM ERC-20
|
|
4246
|
+
"requirePayment: `exact` was requested but none of the offered rails support it. " + (exactSkips.length > 0 ? exactSkips.join(" ") : "The standard `exact` rail is EVM ERC-20 (EIP-3009 \u2014 USDC / EURC \u2014 or Permit2, e.g. Binance-Peg USDC on BNB) or a Solana SPL token (SVM) \u2014 NOT native coins, NOT families without a standard `exact` scheme. Offer an EVM ERC-20 / Solana SPL token, or drop `exact`.")
|
|
4157
4247
|
);
|
|
4158
4248
|
}
|
|
4159
4249
|
return specs;
|
|
@@ -4166,53 +4256,48 @@ function createPaymentGate(options) {
|
|
|
4166
4256
|
}
|
|
4167
4257
|
async function resolveExactRail(net, asset) {
|
|
4168
4258
|
const cfg = options.exact;
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
if (want === "permit2") {
|
|
4176
|
-
method = "permit2";
|
|
4177
|
-
} else {
|
|
4178
|
-
const d = net.exactDomain ? await net.exactDomain(asset) : null;
|
|
4179
|
-
if (d) {
|
|
4180
|
-
method = "eip3009";
|
|
4181
|
-
domain = d;
|
|
4182
|
-
} else if (want === "eip3009") {
|
|
4259
|
+
const settle = cfg.settle;
|
|
4260
|
+
if (!net.resolveExactRail) return {};
|
|
4261
|
+
let relayer;
|
|
4262
|
+
let feePayer;
|
|
4263
|
+
if (settle === "self") {
|
|
4264
|
+
if (cfg.relayer === void 0) {
|
|
4183
4265
|
throw new Error(
|
|
4184
|
-
|
|
4266
|
+
"requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
|
|
4185
4267
|
);
|
|
4186
|
-
} else {
|
|
4187
|
-
method = "permit2";
|
|
4188
4268
|
}
|
|
4269
|
+
relayer = net.bindWallet(cfg.relayer);
|
|
4270
|
+
} else {
|
|
4271
|
+
feePayer = settle.feePayer;
|
|
4189
4272
|
}
|
|
4190
|
-
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4273
|
+
const method = _nullishCoalesce(cfg.method, () => ( "auto"));
|
|
4274
|
+
let info = await net.resolveExactRail({ asset, method, relayer, feePayer });
|
|
4275
|
+
if (!info && settle !== "self" && !feePayer && asset !== "native") {
|
|
4276
|
+
const discovered = await fetchFacilitatorFeePayer(settle.facilitator, net.network);
|
|
4277
|
+
if (!discovered) {
|
|
4278
|
+
return {
|
|
4279
|
+
skipReason: `${net.network}: couldn't read a fee payer from the facilitator (${settle.facilitator}/supported) \u2014 it may be down, or may not sponsor this network. Set \`exact.settle.feePayer\` explicitly to remove the runtime dependency, switch to \`settle: 'self'\` with your own relayer, or retry.`
|
|
4280
|
+
};
|
|
4195
4281
|
}
|
|
4196
|
-
|
|
4282
|
+
info = await net.resolveExactRail({ asset, method, relayer, feePayer: discovered });
|
|
4197
4283
|
}
|
|
4198
|
-
if (
|
|
4199
|
-
|
|
4284
|
+
if (!info) return {};
|
|
4285
|
+
if (settle !== "self" && info.method === "permit2") {
|
|
4286
|
+
if (cfg.method === "permit2") {
|
|
4200
4287
|
throw new Error(
|
|
4201
|
-
"requirePayment: exact `
|
|
4288
|
+
"requirePayment: exact `method: 'permit2'` can't be settled by a third-party facilitator \u2014 facilitators settle the standard EIP-3009 (EVM) / SVM (Solana) schemes, not PipRail\u2019s Permit2 proxy. Use an EIP-3009 token (USDC / EURC) with the facilitator, or `settle: 'self'` (your own relayer) to settle Permit2 yourself."
|
|
4202
4289
|
);
|
|
4203
4290
|
}
|
|
4204
|
-
|
|
4205
|
-
|
|
4291
|
+
return {
|
|
4292
|
+
skipReason: `${net.network}: this token isn't EIP-3009 (auto-selected Permit2), which a third-party facilitator can't settle \u2014 it would serve onchain-proof only here. Use an EIP-3009 token (USDC / EURC) with the facilitator, or \`settle: 'self'\` to settle Permit2 yourself.`
|
|
4293
|
+
};
|
|
4206
4294
|
}
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
kind: "facilitator",
|
|
4212
|
-
url: cfg.settle.facilitator,
|
|
4213
|
-
...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
|
|
4214
|
-
}
|
|
4295
|
+
const mode = settle === "self" ? { kind: "self", relayer } : {
|
|
4296
|
+
kind: "facilitator",
|
|
4297
|
+
url: settle.facilitator,
|
|
4298
|
+
...settle.authHeaders ? { authHeaders: settle.authHeaders } : {}
|
|
4215
4299
|
};
|
|
4300
|
+
return { rail: { method: info.method, ...info.extra ? { extra: info.extra } : {}, mode } };
|
|
4216
4301
|
}
|
|
4217
4302
|
const hasCustomStore = Boolean(options.isUsed || options.markUsed);
|
|
4218
4303
|
const localUsed = /* @__PURE__ */ new Map();
|
|
@@ -4269,11 +4354,11 @@ function createPaymentGate(options) {
|
|
|
4269
4354
|
maxTimeoutSeconds,
|
|
4270
4355
|
extra: {
|
|
4271
4356
|
assetTransferMethod: rail.method,
|
|
4272
|
-
...rail.domain ? { name: rail.domain.name, version: rail.domain.version } : {},
|
|
4273
4357
|
minConfirmations,
|
|
4274
4358
|
decimals: s.decimals,
|
|
4275
4359
|
amountFormatted: s.amountFormatted,
|
|
4276
|
-
...s.symbol ? { symbol: s.symbol } : {}
|
|
4360
|
+
...s.symbol ? { symbol: s.symbol } : {},
|
|
4361
|
+
...rail.extra
|
|
4277
4362
|
}
|
|
4278
4363
|
};
|
|
4279
4364
|
}
|
|
@@ -4289,7 +4374,7 @@ function createPaymentGate(options) {
|
|
|
4289
4374
|
const specs = await ready();
|
|
4290
4375
|
const nonce = genNonce();
|
|
4291
4376
|
const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
|
|
4292
|
-
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess',
|
|
4377
|
+
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _69 => _69.extensions]) };
|
|
4293
4378
|
const challenge2 = {
|
|
4294
4379
|
x402Version: 2,
|
|
4295
4380
|
resource: {
|
|
@@ -4297,7 +4382,7 @@ function createPaymentGate(options) {
|
|
|
4297
4382
|
...options.description ? { description: options.description } : {}
|
|
4298
4383
|
},
|
|
4299
4384
|
accepts: buildAccepts(specs, nonce),
|
|
4300
|
-
..._optionalChain([opts, 'optionalAccess',
|
|
4385
|
+
..._optionalChain([opts, 'optionalAccess', _70 => _70.error]) ? { error: opts.error } : {},
|
|
4301
4386
|
...Object.keys(extensions).length > 0 ? { extensions } : {}
|
|
4302
4387
|
};
|
|
4303
4388
|
return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
|
|
@@ -4320,7 +4405,7 @@ function createPaymentGate(options) {
|
|
|
4320
4405
|
let amountFormatted = receipt.amount;
|
|
4321
4406
|
try {
|
|
4322
4407
|
amountFormatted = _chunkPA6YD3HLcjs.formatUnits.call(void 0, BigInt(receipt.amount), spec.decimals);
|
|
4323
|
-
} catch (
|
|
4408
|
+
} catch (e34) {
|
|
4324
4409
|
}
|
|
4325
4410
|
return {
|
|
4326
4411
|
...receipt,
|
|
@@ -4334,7 +4419,7 @@ function createPaymentGate(options) {
|
|
|
4334
4419
|
if (!options.onPaidError) return;
|
|
4335
4420
|
try {
|
|
4336
4421
|
options.onPaidError(error, receipt);
|
|
4337
|
-
} catch (
|
|
4422
|
+
} catch (e35) {
|
|
4338
4423
|
}
|
|
4339
4424
|
}
|
|
4340
4425
|
function fireOnPaid(receipt) {
|
|
@@ -4426,10 +4511,23 @@ function createPaymentGate(options) {
|
|
|
4426
4511
|
`No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
|
|
4427
4512
|
);
|
|
4428
4513
|
}
|
|
4429
|
-
|
|
4430
|
-
|
|
4514
|
+
let nonce;
|
|
4515
|
+
let evmAuth = null;
|
|
4516
|
+
if ("transaction" in exact.payload) {
|
|
4517
|
+
try {
|
|
4518
|
+
nonce = Buffer.from(exact.payload.transaction, "base64").toString("base64");
|
|
4519
|
+
} catch (e36) {
|
|
4520
|
+
nonce = exact.payload.transaction;
|
|
4521
|
+
}
|
|
4522
|
+
} else if ("permit2Authorization" in exact.payload) {
|
|
4523
|
+
evmAuth = exact.payload.permit2Authorization;
|
|
4524
|
+
nonce = evmAuth.nonce;
|
|
4525
|
+
} else {
|
|
4526
|
+
evmAuth = exact.payload.authorization;
|
|
4527
|
+
nonce = evmAuth.nonce;
|
|
4528
|
+
}
|
|
4431
4529
|
if (await claimTx(nonce)) {
|
|
4432
|
-
return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
|
|
4530
|
+
return rejection("tx_already_used", `Authorization ${evmAuth ? `nonce ${nonce}` : "transaction"} was already redeemed.`);
|
|
4433
4531
|
}
|
|
4434
4532
|
const accept = buildExactAccept(spec);
|
|
4435
4533
|
const mode = spec.exact.mode;
|
|
@@ -4454,12 +4552,14 @@ function createPaymentGate(options) {
|
|
|
4454
4552
|
amount: accept.amount,
|
|
4455
4553
|
payTo: accept.payTo,
|
|
4456
4554
|
maxTimeoutSeconds: accept.maxTimeoutSeconds,
|
|
4457
|
-
//
|
|
4458
|
-
//
|
|
4459
|
-
extra: { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
|
|
4555
|
+
// The scheme's chain-specific `extra`, from the gate's OWN trusted rail: SVM forwards the
|
|
4556
|
+
// facilitator's `feePayer` (the gas sponsor); EVM forwards the token's EIP-712 domain.
|
|
4557
|
+
extra: accept.extra.assetTransferMethod === "svm" ? { feePayer: _nullishCoalesce(accept.extra.feePayer, () => ( "")) } : { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
|
|
4460
4558
|
},
|
|
4461
4559
|
receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
|
|
4462
|
-
|
|
4560
|
+
// The buyer address, for the receipt's `payer` fallback. EVM carries it in the
|
|
4561
|
+
// authorization; SVM doesn't (the facilitator returns the settled payer) → omit it.
|
|
4562
|
+
...evmAuth ? { payerHint: evmAuth.from } : {}
|
|
4463
4563
|
});
|
|
4464
4564
|
}
|
|
4465
4565
|
} catch (err) {
|
|
@@ -4537,7 +4637,7 @@ function isRetryableStatus(status) {
|
|
|
4537
4637
|
}
|
|
4538
4638
|
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
4539
4639
|
async function signBody(secret, body) {
|
|
4540
|
-
const subtle = _optionalChain([globalThis, 'access',
|
|
4640
|
+
const subtle = _optionalChain([globalThis, 'access', _71 => _71.crypto, 'optionalAccess', _72 => _72.subtle]);
|
|
4541
4641
|
if (!subtle) return null;
|
|
4542
4642
|
try {
|
|
4543
4643
|
const enc = new TextEncoder();
|
|
@@ -4547,7 +4647,7 @@ async function signBody(secret, body) {
|
|
|
4547
4647
|
const sig = await subtle.sign("HMAC", key, enc.encode(body));
|
|
4548
4648
|
const hex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4549
4649
|
return `sha256=${hex}`;
|
|
4550
|
-
} catch (
|
|
4650
|
+
} catch (e37) {
|
|
4551
4651
|
return null;
|
|
4552
4652
|
}
|
|
4553
4653
|
}
|
|
@@ -4607,8 +4707,8 @@ async function deliverReceipt(receipt, options) {
|
|
|
4607
4707
|
const retryable = status === void 0 ? true : isRetryableStatus(status);
|
|
4608
4708
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
4609
4709
|
try {
|
|
4610
|
-
_optionalChain([onAttempt, 'optionalCall',
|
|
4611
|
-
} catch (
|
|
4710
|
+
_optionalChain([onAttempt, 'optionalCall', _73 => _73({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
4711
|
+
} catch (e38) {
|
|
4612
4712
|
}
|
|
4613
4713
|
if (ok) return { delivered: true, attempts: attempt, status };
|
|
4614
4714
|
if (!willRetry) {
|