@piprail/sdk 1.20.0 → 1.21.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/CHANGELOG.md +61 -0
- package/dist/index.cjs +142 -68
- package/dist/index.d.cts +117 -36
- package/dist/index.d.ts +117 -36
- package/dist/index.js +133 -59
- package/dist/solana-4EMMGGDR.js +715 -0
- package/dist/solana-CCVSMOKS.cjs +715 -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,67 @@ 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.0] — 2026-06-13 — standard `exact` rail on Solana (SVM) + fully-gasless facilitator mode
|
|
8
|
+
|
|
9
|
+
Opt-in, defaults unchanged. `onchain-proof` stays the default on every chain and is byte-identical.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- **The standard x402 `exact` rail now covers Solana**, not just EVM — so any standard x402
|
|
13
|
+
client/agent that speaks `exact` (the majority) can pay a PipRail Solana gate directly, and a
|
|
14
|
+
PipRail agent can pay any standard Solana `exact` server. Per the ratified `scheme_exact_svm.md`:
|
|
15
|
+
the buyer partial-signs an SPL `TransferChecked` transaction whose **fee payer is the merchant**,
|
|
16
|
+
and the gate co-signs as fee payer + broadcasts against your own RPC — **no facilitator, no
|
|
17
|
+
backend** (the same self-settle model as the EVM `exact` rail). Enable it exactly as on EVM:
|
|
18
|
+
`requirePayment({ chain: 'solana', token: 'USDC', amount, payTo, exact: { settle: 'self', relayer } })`,
|
|
19
|
+
and on the client `new PipRailClient({ chain: 'solana', wallet, schemes: ['onchain-proof', 'exact'] })`.
|
|
20
|
+
- **Buyer-gasless on Solana, for any SPL token.** The buyer signs the canonical
|
|
21
|
+
`[setComputeUnitLimit, setComputeUnitPrice, TransferChecked]` transaction and spends **zero SOL** —
|
|
22
|
+
only the token funds the payment. Gasless-ness is transaction-level (the fee payer), not token-level,
|
|
23
|
+
so **USDC and USDT are equally gasless** (no EIP-3009/Permit2 equivalent needed, unlike EVM). The fee
|
|
24
|
+
payer must be **distinct from `payTo`** (a scheme MUST-rule the SDK enforces), and the recipient's
|
|
25
|
+
token account must already exist (the exact rail won't create it — `onchain-proof` does).
|
|
26
|
+
- **Solana facilitator mode → _fully_ gasless (neither buyer nor merchant pays).** `exact: { settle: {
|
|
27
|
+
facilitator } }` now works on Solana, not just EVM: the gate auto-discovers the facilitator's
|
|
28
|
+
fee-payer pubkey from its `GET /supported`, advertises it, and forwards settlement — the **facilitator
|
|
29
|
+
pays the gas**. Live-proven on mainnet against **PayAI** (`https://facilitator.payai.network`, no API
|
|
30
|
+
key). Self-settle (your own relayer pays the sub-cent fee) remains available; PipRail hosts nothing
|
|
31
|
+
and is never the fee payer.
|
|
32
|
+
|
|
33
|
+
### Security (defense-in-depth, after an adversarial review)
|
|
34
|
+
- The gate counts **only authentically-signed** transfers toward the required amount (a
|
|
35
|
+
tiny-signed + large-unsigned multi-transfer can't reach the price), enforces fee-payer **isolation by
|
|
36
|
+
resolved pubkey** across every instruction (ALT-safe, not a literal index check), and **canonicalizes**
|
|
37
|
+
the SVM replay key so a base64-malleated re-submission can't bypass the replay claim.
|
|
38
|
+
|
|
39
|
+
### Changed (internal — no API change)
|
|
40
|
+
- The `exact` rail moved behind a new driver SPI, `ResolvedNetwork.resolveExactRail`, so the gate is
|
|
41
|
+
fully chain-agnostic (it no longer special-cases EVM). EVM's EIP-3009/Permit2 method-selection is
|
|
42
|
+
unchanged in behaviour; Solana plugs in `{ method: 'svm', extra: { feePayer, tokenProgram } }`.
|
|
43
|
+
- The wire types gained the SVM `exact` payload (`{ transaction }`) and the `extra` keys
|
|
44
|
+
`feePayer`/`memo`/`tokenProgram`; `assetTransferMethod` now also accepts `'svm'`.
|
|
45
|
+
|
|
46
|
+
## [1.20.1] — 2026-06-11 — gate replay store: bounded + exception-safe
|
|
47
|
+
|
|
48
|
+
Patch — internal robustness on the gate's built-in replay protection. No API change, no visible
|
|
49
|
+
behaviour change, defaults identical.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- **Bounded the default used-proof set.** It's now evicted past the replay window
|
|
53
|
+
(`maxTimeoutSeconds`) instead of growing for the life of the process — safe because the driver's
|
|
54
|
+
recency check rejects any proof that old anyway, so a dropped entry still can't be replayed. A
|
|
55
|
+
long-lived gate no longer slowly leaks memory. Custom `isUsed`/`markUsed` stores are unaffected
|
|
56
|
+
(give them a TTL = the window).
|
|
57
|
+
- **`onchain-proof` verification is now claim-release exception-safe.** If a driver's `verify()`
|
|
58
|
+
*throws* (an unexpected RPC exception) rather than returning a rejection, the gate now releases the
|
|
59
|
+
proof reservation before rethrowing — so a transient blip can't permanently burn an otherwise-valid
|
|
60
|
+
proof. This matches the `exact` path, which already did it.
|
|
61
|
+
|
|
62
|
+
### Docs
|
|
63
|
+
- Rewrote **[Replay protection & recovery](https://docs.piprail.com/accepting-payments/replay-protection/)**
|
|
64
|
+
with the full "paid but didn't receive — what happens to the payment?" model (a recoverability
|
|
65
|
+
matrix, the at-most-once-by-design rationale, the bounded-memory behaviour, and the client's
|
|
66
|
+
never-re-pay `.ref` recovery).
|
|
67
|
+
|
|
7
68
|
## [1.20.0] — 2026-06-11 — discovery hardening: conformance-locked, accurate timing, PipRail-attributed
|
|
8
69
|
|
|
9
70
|
A minor release focused on the discovery/registration subsystem — verified live against the real
|
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" },
|
|
@@ -1417,11 +1448,14 @@ function parseExactPaymentHeader(value) {
|
|
|
1417
1448
|
if (typeof network !== "string") return null;
|
|
1418
1449
|
const payload = v.payload;
|
|
1419
1450
|
if (!payload || typeof payload !== "object") return null;
|
|
1420
|
-
const signature = payload.signature;
|
|
1421
|
-
if (typeof signature !== "string") return null;
|
|
1422
1451
|
const x402Version = typeof v.x402Version === "number" ? v.x402Version : 2;
|
|
1423
1452
|
const asset = accepted && typeof accepted.asset === "string" ? accepted.asset : void 0;
|
|
1424
1453
|
const base2 = { x402Version, network, ...asset ? { asset } : {}, raw: v };
|
|
1454
|
+
if (typeof payload.transaction === "string") {
|
|
1455
|
+
return { ...base2, method: "svm", payload: { transaction: payload.transaction } };
|
|
1456
|
+
}
|
|
1457
|
+
const signature = payload.signature;
|
|
1458
|
+
if (typeof signature !== "string") return null;
|
|
1425
1459
|
const authorization = payload.authorization;
|
|
1426
1460
|
if (authorization && typeof authorization === "object") {
|
|
1427
1461
|
for (const k of ["from", "to", "value", "validAfter", "validBefore", "nonce"]) {
|
|
@@ -1708,6 +1742,16 @@ function makeEvmNetwork(resolved) {
|
|
|
1708
1742
|
exactPermit2Supported() {
|
|
1709
1743
|
return isPermit2ProxyChain(resolved.chainId);
|
|
1710
1744
|
},
|
|
1745
|
+
// The gate's rail-advertisement SPI — EIP-3009 vs Permit2 selection (the pure helper),
|
|
1746
|
+
// injecting the on-chain domain read + the Permit2 proxy-presence check.
|
|
1747
|
+
async resolveExactRail({ asset, method }) {
|
|
1748
|
+
return resolveExactRailEvm({
|
|
1749
|
+
asset,
|
|
1750
|
+
method,
|
|
1751
|
+
readDomain: (a) => readExactDomain(publicClient, a),
|
|
1752
|
+
permit2Supported: () => isPermit2ProxyChain(resolved.chainId)
|
|
1753
|
+
});
|
|
1754
|
+
},
|
|
1711
1755
|
async settleExactSelf({ relayer, payload, accept }) {
|
|
1712
1756
|
const a = relayer._native;
|
|
1713
1757
|
if ("permit2Authorization" in payload) {
|
|
@@ -1720,6 +1764,9 @@ function makeEvmNetwork(resolved) {
|
|
|
1720
1764
|
accept
|
|
1721
1765
|
});
|
|
1722
1766
|
}
|
|
1767
|
+
if ("transaction" in payload) {
|
|
1768
|
+
return { ok: false, error: "signature_invalid", detail: "An SVM (Solana) payload was submitted to an EVM exact rail." };
|
|
1769
|
+
}
|
|
1723
1770
|
return verifyAndSettleExactEvm({
|
|
1724
1771
|
publicClient,
|
|
1725
1772
|
walletClient: a.walletClient,
|
|
@@ -1744,7 +1791,7 @@ var loaders = {
|
|
|
1744
1791
|
solana: async () => {
|
|
1745
1792
|
let mod;
|
|
1746
1793
|
try {
|
|
1747
|
-
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-
|
|
1794
|
+
mod = await Promise.resolve().then(() => _interopRequireWildcard(require("./solana-CCVSMOKS.cjs")));
|
|
1748
1795
|
} catch (cause) {
|
|
1749
1796
|
throw new (0, _chunkPA6YD3HLcjs.MissingDriverError)(
|
|
1750
1797
|
`Solana selected, but its packages aren't installed. Run: npm install @solana/web3.js @solana/spl-token bs58`,
|
|
@@ -2770,7 +2817,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2770
2817
|
* before publishing, so retry with a brief backoff if a fresh listing is missing.
|
|
2771
2818
|
* - Results are cross-scheme (mostly the mainstream `exact` scheme); `fetch()` pays
|
|
2772
2819
|
* `onchain-proof` rails by default, and standard `exact` rails too once you opt in
|
|
2773
|
-
* with `schemes: ['onchain-proof', 'exact']` (EVM
|
|
2820
|
+
* with `schemes: ['onchain-proof', 'exact']` (EVM EIP-3009/Permit2 + Solana SVM).
|
|
2774
2821
|
*/
|
|
2775
2822
|
async discover(opts = {}) {
|
|
2776
2823
|
const found = await searchOpenIndexes({
|
|
@@ -2955,7 +3002,7 @@ var PipRailClient = (_class2 = class {
|
|
|
2955
3002
|
);
|
|
2956
3003
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
2957
3004
|
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 +
|
|
3005
|
+
`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
3006
|
);
|
|
2960
3007
|
}
|
|
2961
3008
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
@@ -3335,7 +3382,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3335
3382
|
async payExactRail(net, wallet, accept, url, init, quote) {
|
|
3336
3383
|
if (!net.payExact) {
|
|
3337
3384
|
throw new (0, _chunkPA6YD3HLcjs.UnsupportedSchemeError)(
|
|
3338
|
-
`the ${net.family} family can't pay a standard 'exact' rail (EVM +
|
|
3385
|
+
`the ${net.family} family can't pay a standard 'exact' rail (supported on EVM + Solana today).`
|
|
3339
3386
|
);
|
|
3340
3387
|
}
|
|
3341
3388
|
throwIfAborted(_optionalChain([init, 'optionalAccess', _48 => _48.signal]));
|
|
@@ -4012,6 +4059,24 @@ function buildX402DnsTxt(input) {
|
|
|
4012
4059
|
}
|
|
4013
4060
|
|
|
4014
4061
|
// src/facilitator.ts
|
|
4062
|
+
async function fetchFacilitatorFeePayer(url, network, timeoutMs = 8e3) {
|
|
4063
|
+
const base2 = url.replace(/\/+$/, "");
|
|
4064
|
+
const ctrl = new AbortController();
|
|
4065
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
4066
|
+
try {
|
|
4067
|
+
const res = await fetch(`${base2}/supported`, { signal: ctrl.signal });
|
|
4068
|
+
if (!res.ok) return void 0;
|
|
4069
|
+
const body = await res.json();
|
|
4070
|
+
const kinds = Array.isArray(_optionalChain([body, 'optionalAccess', _64 => _64.kinds])) ? body.kinds : [];
|
|
4071
|
+
const kind = kinds.find((k) => _optionalChain([k, 'optionalAccess', _65 => _65.scheme]) === "exact" && _optionalChain([k, 'optionalAccess', _66 => _66.network]) === network);
|
|
4072
|
+
const fp = _optionalChain([kind, 'optionalAccess', _67 => _67.extra, 'optionalAccess', _68 => _68.feePayer]);
|
|
4073
|
+
return typeof fp === "string" ? fp : void 0;
|
|
4074
|
+
} catch (e32) {
|
|
4075
|
+
return void 0;
|
|
4076
|
+
} finally {
|
|
4077
|
+
clearTimeout(timer);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4015
4080
|
function safeStringify(value) {
|
|
4016
4081
|
return JSON.stringify(value, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
4017
4082
|
}
|
|
@@ -4035,7 +4100,7 @@ async function post(url, body, headers) {
|
|
|
4035
4100
|
let json = null;
|
|
4036
4101
|
try {
|
|
4037
4102
|
json = await res.json();
|
|
4038
|
-
} catch (
|
|
4103
|
+
} catch (e33) {
|
|
4039
4104
|
}
|
|
4040
4105
|
return { status: res.status, json };
|
|
4041
4106
|
}
|
|
@@ -4153,7 +4218,7 @@ function createPaymentGate(options) {
|
|
|
4153
4218
|
);
|
|
4154
4219
|
if (options.exact && !specs.some((s) => s.exact)) {
|
|
4155
4220
|
throw new Error(
|
|
4156
|
-
"requirePayment: `exact` was requested but none of the offered rails support it. The standard `exact` rail is EVM ERC-20
|
|
4221
|
+
"requirePayment: `exact` was requested but none of the offered rails support it. 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
4222
|
);
|
|
4158
4223
|
}
|
|
4159
4224
|
return specs;
|
|
@@ -4166,63 +4231,51 @@ function createPaymentGate(options) {
|
|
|
4166
4231
|
}
|
|
4167
4232
|
async function resolveExactRail(net, asset) {
|
|
4168
4233
|
const cfg = options.exact;
|
|
4169
|
-
if (net.
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
const want = _nullishCoalesce(cfg.method, () => ( "auto"));
|
|
4173
|
-
let method;
|
|
4174
|
-
let domain;
|
|
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") {
|
|
4183
|
-
throw new Error(
|
|
4184
|
-
`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.)`
|
|
4185
|
-
);
|
|
4186
|
-
} else {
|
|
4187
|
-
method = "permit2";
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4190
|
-
if (method === "permit2" && !(_nullishCoalesce(_optionalChain([net, 'access', _64 => _64.exactPermit2Supported, 'optionalCall', _65 => _65()]), () => ( false)))) {
|
|
4191
|
-
if (cfg.method === "permit2") {
|
|
4192
|
-
throw new Error(
|
|
4193
|
-
`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.)`
|
|
4194
|
-
);
|
|
4195
|
-
}
|
|
4196
|
-
return void 0;
|
|
4197
|
-
}
|
|
4234
|
+
if (!net.resolveExactRail) return void 0;
|
|
4235
|
+
let relayer;
|
|
4236
|
+
let feePayer;
|
|
4198
4237
|
if (cfg.settle === "self") {
|
|
4199
4238
|
if (cfg.relayer === void 0) {
|
|
4200
4239
|
throw new Error(
|
|
4201
4240
|
"requirePayment: exact `settle: 'self'` needs a `relayer` wallet (the gas-paying key that broadcasts the settle), e.g. exact: { settle: 'self', relayer: { privateKey } }."
|
|
4202
4241
|
);
|
|
4203
4242
|
}
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4243
|
+
relayer = net.bindWallet(cfg.relayer);
|
|
4244
|
+
} else {
|
|
4245
|
+
feePayer = cfg.settle.feePayer;
|
|
4246
|
+
}
|
|
4247
|
+
const method = _nullishCoalesce(cfg.method, () => ( "auto"));
|
|
4248
|
+
let info = await net.resolveExactRail({ asset, method, relayer, feePayer });
|
|
4249
|
+
if (!info && cfg.settle !== "self" && !feePayer) {
|
|
4250
|
+
const discovered = await fetchFacilitatorFeePayer(cfg.settle.facilitator, net.network);
|
|
4251
|
+
if (discovered) info = await net.resolveExactRail({ asset, method, relayer, feePayer: discovered });
|
|
4252
|
+
}
|
|
4253
|
+
if (!info) return void 0;
|
|
4254
|
+
const mode = cfg.settle === "self" ? { kind: "self", relayer } : {
|
|
4255
|
+
kind: "facilitator",
|
|
4256
|
+
url: cfg.settle.facilitator,
|
|
4257
|
+
...cfg.settle.authHeaders ? { authHeaders: cfg.settle.authHeaders } : {}
|
|
4215
4258
|
};
|
|
4259
|
+
return { method: info.method, ...info.extra ? { extra: info.extra } : {}, mode };
|
|
4216
4260
|
}
|
|
4217
4261
|
const hasCustomStore = Boolean(options.isUsed || options.markUsed);
|
|
4218
|
-
const localUsed = /* @__PURE__ */ new
|
|
4262
|
+
const localUsed = /* @__PURE__ */ new Map();
|
|
4263
|
+
const replayWindowMs = maxTimeoutSeconds * 1e3;
|
|
4264
|
+
function pruneUsed(now) {
|
|
4265
|
+
for (const [key, expiry] of localUsed) {
|
|
4266
|
+
if (expiry > now) break;
|
|
4267
|
+
localUsed.delete(key);
|
|
4268
|
+
}
|
|
4269
|
+
}
|
|
4219
4270
|
async function claimTx(ref) {
|
|
4220
4271
|
if (hasCustomStore) {
|
|
4221
4272
|
return options.isUsed ? Boolean(await options.isUsed(ref)) : false;
|
|
4222
4273
|
}
|
|
4223
4274
|
const key = ref.toLowerCase();
|
|
4275
|
+
const now = Date.now();
|
|
4276
|
+
pruneUsed(now);
|
|
4224
4277
|
if (localUsed.has(key)) return true;
|
|
4225
|
-
localUsed.
|
|
4278
|
+
localUsed.set(key, now + replayWindowMs);
|
|
4226
4279
|
return false;
|
|
4227
4280
|
}
|
|
4228
4281
|
async function settleTx(ref, ok) {
|
|
@@ -4260,11 +4313,11 @@ function createPaymentGate(options) {
|
|
|
4260
4313
|
maxTimeoutSeconds,
|
|
4261
4314
|
extra: {
|
|
4262
4315
|
assetTransferMethod: rail.method,
|
|
4263
|
-
...rail.domain ? { name: rail.domain.name, version: rail.domain.version } : {},
|
|
4264
4316
|
minConfirmations,
|
|
4265
4317
|
decimals: s.decimals,
|
|
4266
4318
|
amountFormatted: s.amountFormatted,
|
|
4267
|
-
...s.symbol ? { symbol: s.symbol } : {}
|
|
4319
|
+
...s.symbol ? { symbol: s.symbol } : {},
|
|
4320
|
+
...rail.extra
|
|
4268
4321
|
}
|
|
4269
4322
|
};
|
|
4270
4323
|
}
|
|
@@ -4280,7 +4333,7 @@ function createPaymentGate(options) {
|
|
|
4280
4333
|
const specs = await ready();
|
|
4281
4334
|
const nonce = genNonce();
|
|
4282
4335
|
const bazaar = options.discovery ? { bazaar: buildBazaarExtension(options.discovery === true ? {} : options.discovery) } : void 0;
|
|
4283
|
-
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess',
|
|
4336
|
+
const extensions = { ...bazaar, ..._optionalChain([opts, 'optionalAccess', _69 => _69.extensions]) };
|
|
4284
4337
|
const challenge2 = {
|
|
4285
4338
|
x402Version: 2,
|
|
4286
4339
|
resource: {
|
|
@@ -4288,7 +4341,7 @@ function createPaymentGate(options) {
|
|
|
4288
4341
|
...options.description ? { description: options.description } : {}
|
|
4289
4342
|
},
|
|
4290
4343
|
accepts: buildAccepts(specs, nonce),
|
|
4291
|
-
..._optionalChain([opts, 'optionalAccess',
|
|
4344
|
+
..._optionalChain([opts, 'optionalAccess', _70 => _70.error]) ? { error: opts.error } : {},
|
|
4292
4345
|
...Object.keys(extensions).length > 0 ? { extensions } : {}
|
|
4293
4346
|
};
|
|
4294
4347
|
return { challenge: challenge2, requiredHeader: buildChallengeHeader(challenge2) };
|
|
@@ -4311,7 +4364,7 @@ function createPaymentGate(options) {
|
|
|
4311
4364
|
let amountFormatted = receipt.amount;
|
|
4312
4365
|
try {
|
|
4313
4366
|
amountFormatted = _chunkPA6YD3HLcjs.formatUnits.call(void 0, BigInt(receipt.amount), spec.decimals);
|
|
4314
|
-
} catch (
|
|
4367
|
+
} catch (e34) {
|
|
4315
4368
|
}
|
|
4316
4369
|
return {
|
|
4317
4370
|
...receipt,
|
|
@@ -4325,7 +4378,7 @@ function createPaymentGate(options) {
|
|
|
4325
4378
|
if (!options.onPaidError) return;
|
|
4326
4379
|
try {
|
|
4327
4380
|
options.onPaidError(error, receipt);
|
|
4328
|
-
} catch (
|
|
4381
|
+
} catch (e35) {
|
|
4329
4382
|
}
|
|
4330
4383
|
}
|
|
4331
4384
|
function fireOnPaid(receipt) {
|
|
@@ -4382,7 +4435,13 @@ function createPaymentGate(options) {
|
|
|
4382
4435
|
}
|
|
4383
4436
|
const ref = sig.payload.txHash;
|
|
4384
4437
|
if (await claimTx(ref)) return rejection("tx_already_used", `Proof ${ref} was already redeemed.`);
|
|
4385
|
-
|
|
4438
|
+
let result;
|
|
4439
|
+
try {
|
|
4440
|
+
result = await spec.net.verify(ref, buildAccept(spec, sig.payload.nonce));
|
|
4441
|
+
} catch (err) {
|
|
4442
|
+
await settleTx(ref, false);
|
|
4443
|
+
throw err;
|
|
4444
|
+
}
|
|
4386
4445
|
if (!result.ok) {
|
|
4387
4446
|
await settleTx(ref, false);
|
|
4388
4447
|
return rejection(result.error, result.detail);
|
|
@@ -4411,10 +4470,23 @@ function createPaymentGate(options) {
|
|
|
4411
4470
|
`No \`exact\` rail offered for ${exact.network}${exact.asset ? `/${exact.asset}` : ""} (offered: ${exactSpecs.map((s) => `${s.asset}@${s.net.network}`).join(", ")}).`
|
|
4412
4471
|
);
|
|
4413
4472
|
}
|
|
4414
|
-
|
|
4415
|
-
|
|
4473
|
+
let nonce;
|
|
4474
|
+
let evmAuth = null;
|
|
4475
|
+
if ("transaction" in exact.payload) {
|
|
4476
|
+
try {
|
|
4477
|
+
nonce = Buffer.from(exact.payload.transaction, "base64").toString("base64");
|
|
4478
|
+
} catch (e36) {
|
|
4479
|
+
nonce = exact.payload.transaction;
|
|
4480
|
+
}
|
|
4481
|
+
} else if ("permit2Authorization" in exact.payload) {
|
|
4482
|
+
evmAuth = exact.payload.permit2Authorization;
|
|
4483
|
+
nonce = evmAuth.nonce;
|
|
4484
|
+
} else {
|
|
4485
|
+
evmAuth = exact.payload.authorization;
|
|
4486
|
+
nonce = evmAuth.nonce;
|
|
4487
|
+
}
|
|
4416
4488
|
if (await claimTx(nonce)) {
|
|
4417
|
-
return rejection("tx_already_used", `Authorization nonce ${nonce} was already redeemed.`);
|
|
4489
|
+
return rejection("tx_already_used", `Authorization ${evmAuth ? `nonce ${nonce}` : "transaction"} was already redeemed.`);
|
|
4418
4490
|
}
|
|
4419
4491
|
const accept = buildExactAccept(spec);
|
|
4420
4492
|
const mode = spec.exact.mode;
|
|
@@ -4439,12 +4511,14 @@ function createPaymentGate(options) {
|
|
|
4439
4511
|
amount: accept.amount,
|
|
4440
4512
|
payTo: accept.payTo,
|
|
4441
4513
|
maxTimeoutSeconds: accept.maxTimeoutSeconds,
|
|
4442
|
-
//
|
|
4443
|
-
//
|
|
4444
|
-
extra: { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
|
|
4514
|
+
// The scheme's chain-specific `extra`, from the gate's OWN trusted rail: SVM forwards the
|
|
4515
|
+
// facilitator's `feePayer` (the gas sponsor); EVM forwards the token's EIP-712 domain.
|
|
4516
|
+
extra: accept.extra.assetTransferMethod === "svm" ? { feePayer: _nullishCoalesce(accept.extra.feePayer, () => ( "")) } : { name: _nullishCoalesce(accept.extra.name, () => ( "")), version: _nullishCoalesce(accept.extra.version, () => ( "")) }
|
|
4445
4517
|
},
|
|
4446
4518
|
receipt: { network: accept.network, asset: accept.asset, payTo: accept.payTo, amount: accept.amount },
|
|
4447
|
-
|
|
4519
|
+
// The buyer address, for the receipt's `payer` fallback. EVM carries it in the
|
|
4520
|
+
// authorization; SVM doesn't (the facilitator returns the settled payer) → omit it.
|
|
4521
|
+
...evmAuth ? { payerHint: evmAuth.from } : {}
|
|
4448
4522
|
});
|
|
4449
4523
|
}
|
|
4450
4524
|
} catch (err) {
|
|
@@ -4522,7 +4596,7 @@ function isRetryableStatus(status) {
|
|
|
4522
4596
|
}
|
|
4523
4597
|
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
4524
4598
|
async function signBody(secret, body) {
|
|
4525
|
-
const subtle = _optionalChain([globalThis, 'access',
|
|
4599
|
+
const subtle = _optionalChain([globalThis, 'access', _71 => _71.crypto, 'optionalAccess', _72 => _72.subtle]);
|
|
4526
4600
|
if (!subtle) return null;
|
|
4527
4601
|
try {
|
|
4528
4602
|
const enc = new TextEncoder();
|
|
@@ -4532,7 +4606,7 @@ async function signBody(secret, body) {
|
|
|
4532
4606
|
const sig = await subtle.sign("HMAC", key, enc.encode(body));
|
|
4533
4607
|
const hex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
4534
4608
|
return `sha256=${hex}`;
|
|
4535
|
-
} catch (
|
|
4609
|
+
} catch (e37) {
|
|
4536
4610
|
return null;
|
|
4537
4611
|
}
|
|
4538
4612
|
}
|
|
@@ -4592,8 +4666,8 @@ async function deliverReceipt(receipt, options) {
|
|
|
4592
4666
|
const retryable = status === void 0 ? true : isRetryableStatus(status);
|
|
4593
4667
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
4594
4668
|
try {
|
|
4595
|
-
_optionalChain([onAttempt, 'optionalCall',
|
|
4596
|
-
} catch (
|
|
4669
|
+
_optionalChain([onAttempt, 'optionalCall', _73 => _73({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
4670
|
+
} catch (e38) {
|
|
4597
4671
|
}
|
|
4598
4672
|
if (ok) return { delivered: true, attempts: attempt, status };
|
|
4599
4673
|
if (!willRetry) {
|