@piprail/sdk 2.1.1 → 2.3.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 +55 -0
- package/dist/index.cjs +126 -75
- package/dist/index.d.cts +32 -3
- package/dist/index.d.ts +32 -3
- package/dist/index.js +122 -71
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,59 @@ 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
|
+
## [2.3.0] — 2026-06-17 — `exact: true` zero-config gasless gate
|
|
8
|
+
|
|
9
|
+
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical. A new
|
|
10
|
+
opt-in shorthand makes the gasless `exact` rail one line, and it degrades gracefully instead of
|
|
11
|
+
breaking when no facilitator covers a chain.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`exact: true` on `requirePayment` / `createPaymentGate` — zero-config gasless.** Equivalent to
|
|
16
|
+
`exact: { settle: 'keyless' }`: the gate auto-advertises a gasless `exact` rail and, at settle
|
|
17
|
+
time, picks the first known **keyless** (no-API-key) facilitator for the chain from the built-in
|
|
18
|
+
`KNOWN_FACILITATORS` map, so buyers pay no gas and the merchant runs no relayer. One line, no
|
|
19
|
+
facilitator URL, no relayer key.
|
|
20
|
+
- **`ExactRailOption.settle` accepts `'keyless'`** alongside `'self'` and `{ facilitator }`, and
|
|
21
|
+
`exact` accepts `boolean | ExactRailOption`. The boolean shorthand normalizes to
|
|
22
|
+
`{ settle: 'keyless' }`.
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **Graceful degrade for the soft path.** When `exact: true` (or `settle: 'keyless'`) is set but no
|
|
27
|
+
keyless facilitator covers the offered chain, the gate **does not throw** — it logs a clear,
|
|
28
|
+
production-visible warning and serves the `onchain-proof` floor (buyers pay their own gas), so a
|
|
29
|
+
resource never goes dark over a coverage gap. An **explicit** `settle: 'self'` or
|
|
30
|
+
`settle: { facilitator }` still throws on a coverage gap (you asked for a specific rail; a silent
|
|
31
|
+
fallback would hide a misconfiguration). Suppress the soft-path hints with `PIPRAIL_NO_HINTS=1`.
|
|
32
|
+
- **A failed gasless settlement returns a clear fallback hint.** When a facilitator settle fails at
|
|
33
|
+
pay time, the 502 body now carries a `fallback` field telling the caller the resource also accepts
|
|
34
|
+
`onchain-proof` — retry by paying that rail yourself.
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Both changes are additive and backward-compatible — defaults and the zero-config 402 stay
|
|
39
|
+
byte-identical; only previously-skipped cases become newly handled.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **The `exact` buyer matches a rail's network whether it's a CAIP-2 id _or_ a chain slug.** The pay
|
|
44
|
+
path now normalizes the offered network (`eip155:8453`, `base`, `bsc`, `56`, …) before matching it to
|
|
45
|
+
the bound chain — the same normalization discovery already used — so a PipRail client interoperates
|
|
46
|
+
with any x402 server or facilitator regardless of how it labels the network. Strictly additive: a
|
|
47
|
+
CAIP-2 label behaves exactly as before; only a chain slug that resolves to the **bound** chain becomes
|
|
48
|
+
newly payable (a different-chain or unrecognized label is never selected, and the trusted EIP-712
|
|
49
|
+
domain still fixes the chain id at signing). Fixes `exact` rails that some facilitators label by slug
|
|
50
|
+
being silently unpayable.
|
|
51
|
+
|
|
52
|
+
### Added
|
|
53
|
+
|
|
54
|
+
- **`facilitatorCoverage()` / `parseFacilitatorSupported()` surface two optional per-kind fields** —
|
|
55
|
+
`x402Version` and `assetTransferMethod` (`'eip3009' | 'permit2'`) — when a facilitator's `GET
|
|
56
|
+
/supported` advertises them, so coverage can tell a v1 rail from a v2 rail and an EIP-3009 kind from a
|
|
57
|
+
Permit2 one. Omitted entirely when absent (no `undefined` keys), so a facilitator reporting neither
|
|
58
|
+
parses exactly as before.
|
|
59
|
+
|
|
7
60
|
## [2.1.1] — 2026-06-15 — discoverability polish (post-2.1.0 audit)
|
|
8
61
|
|
|
9
62
|
A consistency patch from a deep docs↔source audit of the 2.1.0 discoverability surface. No
|
|
@@ -1196,6 +1249,8 @@ straight into your wallet. The API is small and self-contained.
|
|
|
1196
1249
|
to your wallet; PipRail never holds funds.
|
|
1197
1250
|
- `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
|
|
1198
1251
|
|
|
1252
|
+
[2.3.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1253
|
+
[2.2.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1199
1254
|
[2.1.1]: https://www.npmjs.com/package/@piprail/sdk
|
|
1200
1255
|
[2.1.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1201
1256
|
[2.0.2]: https://www.npmjs.com/package/@piprail/sdk
|
package/dist/index.cjs
CHANGED
|
@@ -3220,7 +3220,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3220
3220
|
const candidates = this.gatherCandidates(net, challenge, schemes);
|
|
3221
3221
|
if (candidates.length === 0) {
|
|
3222
3222
|
const exactOnNet = challenge.accepts.some(
|
|
3223
|
-
(a) => a.scheme === "exact" &&
|
|
3223
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network)
|
|
3224
3224
|
);
|
|
3225
3225
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
3226
3226
|
throw new (0, _chunkJG6KRAW6cjs.UnsupportedSchemeError)(
|
|
@@ -3229,7 +3229,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3229
3229
|
}
|
|
3230
3230
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
3231
3231
|
const payable = challenge.accepts.some(
|
|
3232
|
-
(a) => a.scheme === "exact" &&
|
|
3232
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network) && net.describeAsset(a.asset) != null
|
|
3233
3233
|
);
|
|
3234
3234
|
if (payable) {
|
|
3235
3235
|
throw new (0, _chunkJG6KRAW6cjs.NoCompatibleAcceptError)(
|
|
@@ -3249,6 +3249,17 @@ var PipRailClient = (_class2 = class {
|
|
|
3249
3249
|
const chosen = _nullishCoalesce(priced.find((p) => p.quote.withinPolicy), () => ( priced[0]));
|
|
3250
3250
|
return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
|
|
3251
3251
|
}
|
|
3252
|
+
/** Match a foreign-supplied network string against the bound driver, tolerating a
|
|
3253
|
+
* SLUG ('bsc', 'base', '56') the SAME way discovery's `railOnNetwork` already does —
|
|
3254
|
+
* normalize to CAIP-2 first, since a foreign/AEON/community 402 may label the network
|
|
3255
|
+
* with a slug (AEON serves v1 duplicate kinds '56'/'bsc'). ADDITIVE: a value that's
|
|
3256
|
+
* already CAIP-2 passes through `normalizeNetwork` UNCHANGED, so every existing
|
|
3257
|
+
* exact-CAIP-2 match is byte-identical; only slugs resolving to the bound chain become
|
|
3258
|
+
* newly matchable (an unknown slug stays unresolved → still unmatched; a different
|
|
3259
|
+
* chain's slug resolves elsewhere → still unmatched). */
|
|
3260
|
+
supportsNetwork(net, network) {
|
|
3261
|
+
return net.supports(normalizeNetwork(network));
|
|
3262
|
+
}
|
|
3252
3263
|
/** The candidate accepts this client could pay, on the bound network. Always the
|
|
3253
3264
|
* backendless `onchain-proof` rails; PLUS standard `exact` rails when `schemes`
|
|
3254
3265
|
* enables them AND the driver can settle them (EVM `payExact` + a recognised
|
|
@@ -3259,14 +3270,14 @@ var PipRailClient = (_class2 = class {
|
|
|
3259
3270
|
if (schemes.includes("onchain-proof")) {
|
|
3260
3271
|
out.push(
|
|
3261
3272
|
...challenge.accepts.filter(
|
|
3262
|
-
(a) => a.scheme === "onchain-proof" &&
|
|
3273
|
+
(a) => a.scheme === "onchain-proof" && this.supportsNetwork(net, a.network)
|
|
3263
3274
|
)
|
|
3264
3275
|
);
|
|
3265
3276
|
}
|
|
3266
3277
|
if (schemes.includes("exact")) {
|
|
3267
3278
|
out.push(
|
|
3268
3279
|
...challenge.accepts.filter(
|
|
3269
|
-
(a) => a.scheme === "exact" &&
|
|
3280
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network) && typeof net.payExact === "function" && net.describeAsset(a.asset) != null && // a foreign rail's maxTimeoutSeconds must be a usable positive integer, or
|
|
3270
3281
|
// signing it would build a NaN/garbage validBefore — drop it silently
|
|
3271
3282
|
// (symmetric with an unrecognised token) rather than leak a raw SyntaxError.
|
|
3272
3283
|
Number.isInteger(a.maxTimeoutSeconds) && a.maxTimeoutSeconds > 0
|
|
@@ -3508,7 +3519,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3508
3519
|
);
|
|
3509
3520
|
}
|
|
3510
3521
|
async payAndConfirm(net, wallet, accept) {
|
|
3511
|
-
if (!
|
|
3522
|
+
if (!this.supportsNetwork(net, accept.network)) {
|
|
3512
3523
|
throw new (0, _chunkJG6KRAW6cjs.WrongChainError)(
|
|
3513
3524
|
`Challenge expects ${accept.network} but client is on ${net.network}.`
|
|
3514
3525
|
);
|
|
@@ -4761,7 +4772,15 @@ function parseFacilitatorSupported(body) {
|
|
|
4761
4772
|
const o = k;
|
|
4762
4773
|
if (typeof o.scheme !== "string" || typeof o.network !== "string") continue;
|
|
4763
4774
|
const fp = _optionalChain([o, 'access', _77 => _77.extra, 'optionalAccess', _78 => _78.feePayer]);
|
|
4764
|
-
|
|
4775
|
+
const ver = o.x402Version;
|
|
4776
|
+
const method = _optionalChain([o, 'access', _79 => _79.extra, 'optionalAccess', _80 => _80.assetTransferMethod]);
|
|
4777
|
+
out.push({
|
|
4778
|
+
scheme: o.scheme,
|
|
4779
|
+
network: o.network,
|
|
4780
|
+
...typeof fp === "string" ? { feePayer: fp } : {},
|
|
4781
|
+
...typeof ver === "number" ? { x402Version: ver } : {},
|
|
4782
|
+
...typeof method === "string" ? { assetTransferMethod: method } : {}
|
|
4783
|
+
});
|
|
4765
4784
|
}
|
|
4766
4785
|
return out;
|
|
4767
4786
|
}
|
|
@@ -4872,6 +4891,63 @@ async function settleViaFacilitator(input) {
|
|
|
4872
4891
|
return { ok: true, receipt };
|
|
4873
4892
|
}
|
|
4874
4893
|
|
|
4894
|
+
// src/facilitators.ts
|
|
4895
|
+
var KNOWN_FACILITATORS = {
|
|
4896
|
+
// Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
|
|
4897
|
+
// exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
|
|
4898
|
+
"eip155:8453": [
|
|
4899
|
+
{
|
|
4900
|
+
url: "https://facilitator.payai.network",
|
|
4901
|
+
keyless: true,
|
|
4902
|
+
schemes: ["exact"],
|
|
4903
|
+
settles: ["eip3009"],
|
|
4904
|
+
note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
|
|
4905
|
+
},
|
|
4906
|
+
{
|
|
4907
|
+
url: "https://facilitator.xpay.sh",
|
|
4908
|
+
keyless: true,
|
|
4909
|
+
schemes: ["exact"],
|
|
4910
|
+
settles: ["eip3009"],
|
|
4911
|
+
note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
|
|
4912
|
+
}
|
|
4913
|
+
],
|
|
4914
|
+
// Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
|
|
4915
|
+
// by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
|
|
4916
|
+
// read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
|
|
4917
|
+
// returns 401 (an API key is required), so they are not keyless for settlement.
|
|
4918
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
|
|
4919
|
+
{
|
|
4920
|
+
url: "https://facilitator.payai.network",
|
|
4921
|
+
keyless: true,
|
|
4922
|
+
schemes: ["exact"],
|
|
4923
|
+
settles: ["svm"],
|
|
4924
|
+
note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
|
|
4925
|
+
},
|
|
4926
|
+
{
|
|
4927
|
+
url: "https://pay.openfacilitator.io",
|
|
4928
|
+
keyless: true,
|
|
4929
|
+
schemes: ["exact"],
|
|
4930
|
+
settles: ["svm"],
|
|
4931
|
+
note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
|
|
4932
|
+
},
|
|
4933
|
+
{
|
|
4934
|
+
url: "https://facilitator.corbits.dev",
|
|
4935
|
+
keyless: true,
|
|
4936
|
+
schemes: ["exact"],
|
|
4937
|
+
settles: ["svm"],
|
|
4938
|
+
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
4939
|
+
}
|
|
4940
|
+
]
|
|
4941
|
+
};
|
|
4942
|
+
function knownFacilitatorsFor(network) {
|
|
4943
|
+
return _nullishCoalesce(KNOWN_FACILITATORS[network], () => ( []));
|
|
4944
|
+
}
|
|
4945
|
+
function firstKeylessFacilitator(network, method) {
|
|
4946
|
+
return knownFacilitatorsFor(network).find(
|
|
4947
|
+
(f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
|
|
4948
|
+
);
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4875
4951
|
// src/server.ts
|
|
4876
4952
|
function toInvalidBody(result) {
|
|
4877
4953
|
return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
|
|
@@ -4892,10 +4968,16 @@ function normaliseAccepts(options) {
|
|
|
4892
4968
|
"requirePayment: provide either { chain, token, amount } or a non-empty `accept: [{ chain, token, amount }, \u2026]`."
|
|
4893
4969
|
);
|
|
4894
4970
|
}
|
|
4971
|
+
function normaliseExactOption(exact) {
|
|
4972
|
+
if (!exact) return void 0;
|
|
4973
|
+
if (exact === true) return { settle: "keyless" };
|
|
4974
|
+
return exact;
|
|
4975
|
+
}
|
|
4895
4976
|
function createPaymentGate(options) {
|
|
4896
4977
|
const minConfirmations = _nullishCoalesce(options.minConfirmations, () => ( 1));
|
|
4897
4978
|
const maxTimeoutSeconds = _nullishCoalesce(options.maxTimeoutSeconds, () => ( 600));
|
|
4898
4979
|
const genNonce = _nullishCoalesce(options.generateNonce, () => ( (() => globalThis.crypto.randomUUID())));
|
|
4980
|
+
const exactOption = normaliseExactOption(options.exact);
|
|
4899
4981
|
let resolved;
|
|
4900
4982
|
function ready() {
|
|
4901
4983
|
if (resolved) return resolved;
|
|
@@ -4915,7 +4997,7 @@ function createPaymentGate(options) {
|
|
|
4915
4997
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
4916
4998
|
const amountBase = _chunkJG6KRAW6cjs.parseUnits.call(void 0, a.amount, decimals);
|
|
4917
4999
|
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
4918
|
-
if (
|
|
5000
|
+
if (exactOption) {
|
|
4919
5001
|
const outcome = await resolveExactRail(net, asset);
|
|
4920
5002
|
if (outcome.rail) spec.exact = outcome.rail;
|
|
4921
5003
|
else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
|
|
@@ -4923,10 +5005,17 @@ function createPaymentGate(options) {
|
|
|
4923
5005
|
return spec;
|
|
4924
5006
|
})
|
|
4925
5007
|
);
|
|
4926
|
-
if (
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
5008
|
+
if (exactOption && !specs.some((s) => s.exact)) {
|
|
5009
|
+
const why = 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.";
|
|
5010
|
+
if (exactOption.settle === "keyless") {
|
|
5011
|
+
if (typeof process === "undefined" || !_optionalChain([process, 'optionalAccess', _81 => _81.env, 'optionalAccess', _82 => _82.PIPRAIL_NO_HINTS])) {
|
|
5012
|
+
console.warn(
|
|
5013
|
+
`[piprail] exact: true \u2014 no offered chain has a gasless \`exact\` rail available, so this gate serves ONCHAIN-PROOF ONLY (buyers PAY GAS \u2014 the fallback when no facilitator can sponsor). ${why} To be gasless: pin \`exact: { settle: { facilitator } }\` or self-settle \`exact: { settle: 'self', relayer }\`. (Suppress with PIPRAIL_NO_HINTS=1.)`
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
5016
|
+
} else {
|
|
5017
|
+
throw new Error("requirePayment: `exact` was requested but none of the offered rails support it. " + why);
|
|
5018
|
+
}
|
|
4930
5019
|
}
|
|
4931
5020
|
return specs;
|
|
4932
5021
|
})();
|
|
@@ -4937,9 +5026,23 @@ function createPaymentGate(options) {
|
|
|
4937
5026
|
return p;
|
|
4938
5027
|
}
|
|
4939
5028
|
async function resolveExactRail(net, asset) {
|
|
4940
|
-
const cfg =
|
|
4941
|
-
const settle = cfg.settle;
|
|
5029
|
+
const cfg = exactOption;
|
|
4942
5030
|
if (!net.resolveExactRail) return {};
|
|
5031
|
+
let settle = cfg.settle;
|
|
5032
|
+
if (settle === "keyless") {
|
|
5033
|
+
const picked = firstKeylessFacilitator(net.network);
|
|
5034
|
+
if (!picked) {
|
|
5035
|
+
return {
|
|
5036
|
+
skipReason: `${net.network}: \`exact: true\` found no known keyless facilitator for this network. Pass \`exact: { settle: { facilitator } }\`, \`exact: { settle: 'self', relayer }\`, or see the coverage map (KNOWN_FACILITATORS / docs.piprail.com).`
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
if (typeof process === "undefined" || _optionalChain([process, 'optionalAccess', _83 => _83.env, 'optionalAccess', _84 => _84.NODE_ENV]) !== "production" && !_optionalChain([process, 'optionalAccess', _85 => _85.env, 'optionalAccess', _86 => _86.PIPRAIL_NO_HINTS])) {
|
|
5040
|
+
console.warn(
|
|
5041
|
+
`[piprail] exact: keyless rail on ${net.network} auto-settles via ${picked.url} (zero-config; pin \`exact.settle.facilitator\` in production).`
|
|
5042
|
+
);
|
|
5043
|
+
}
|
|
5044
|
+
settle = { facilitator: picked.url };
|
|
5045
|
+
}
|
|
4943
5046
|
let relayer;
|
|
4944
5047
|
let feePayer;
|
|
4945
5048
|
if (settle === "self") {
|
|
@@ -5067,7 +5170,7 @@ function createPaymentGate(options) {
|
|
|
5067
5170
|
instruction: describeChallenge({ x402Version: 2, resource: { url: resourceUrl }, accepts }),
|
|
5068
5171
|
...endpointInfo ? { endpoint: endpointInfo } : {}
|
|
5069
5172
|
});
|
|
5070
|
-
const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess',
|
|
5173
|
+
const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _87 => _87.extensions]), () => ( {}));
|
|
5071
5174
|
const rejectionPiprail = _nullishCoalesce(rejectionExt.piprail, () => ( {}));
|
|
5072
5175
|
const bodyPiprail = { ..._nullishCoalesce(selfDescribe, () => ( {})), ...rejectionPiprail };
|
|
5073
5176
|
const bodyExtensions = {
|
|
@@ -5087,7 +5190,7 @@ function createPaymentGate(options) {
|
|
|
5087
5190
|
...options.mimeType ? { mimeType: options.mimeType } : {}
|
|
5088
5191
|
},
|
|
5089
5192
|
accepts,
|
|
5090
|
-
..._optionalChain([opts, 'optionalAccess',
|
|
5193
|
+
..._optionalChain([opts, 'optionalAccess', _88 => _88.error]) ? { error: opts.error } : {},
|
|
5091
5194
|
...Object.keys(bodyExtensions).length > 0 ? { extensions: bodyExtensions } : {}
|
|
5092
5195
|
};
|
|
5093
5196
|
const headerChallenge = {
|
|
@@ -5311,7 +5414,12 @@ function requirePayment(options) {
|
|
|
5311
5414
|
} catch (err) {
|
|
5312
5415
|
if (err instanceof _chunkJG6KRAW6cjs.SettlementError) {
|
|
5313
5416
|
res.status(502);
|
|
5314
|
-
res.json({
|
|
5417
|
+
res.json({
|
|
5418
|
+
x402Version: 2,
|
|
5419
|
+
error: "settlement_failed",
|
|
5420
|
+
detail: err.message,
|
|
5421
|
+
fallback: "The gasless `exact` settlement failed. This resource also accepts the `onchain-proof` scheme \u2014 retry by paying that rail yourself (you broadcast the transfer and pay the gas). It is the fallback when no facilitator can sponsor the gas."
|
|
5422
|
+
});
|
|
5315
5423
|
return;
|
|
5316
5424
|
}
|
|
5317
5425
|
next(err);
|
|
@@ -5340,63 +5448,6 @@ function normaliseHeader(value) {
|
|
|
5340
5448
|
return value;
|
|
5341
5449
|
}
|
|
5342
5450
|
|
|
5343
|
-
// src/facilitators.ts
|
|
5344
|
-
var KNOWN_FACILITATORS = {
|
|
5345
|
-
// Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
|
|
5346
|
-
// exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
|
|
5347
|
-
"eip155:8453": [
|
|
5348
|
-
{
|
|
5349
|
-
url: "https://facilitator.payai.network",
|
|
5350
|
-
keyless: true,
|
|
5351
|
-
schemes: ["exact"],
|
|
5352
|
-
settles: ["eip3009"],
|
|
5353
|
-
note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
|
|
5354
|
-
},
|
|
5355
|
-
{
|
|
5356
|
-
url: "https://facilitator.xpay.sh",
|
|
5357
|
-
keyless: true,
|
|
5358
|
-
schemes: ["exact"],
|
|
5359
|
-
settles: ["eip3009"],
|
|
5360
|
-
note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
|
|
5361
|
-
}
|
|
5362
|
-
],
|
|
5363
|
-
// Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
|
|
5364
|
-
// by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
|
|
5365
|
-
// read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
|
|
5366
|
-
// returns 401 (an API key is required), so they are not keyless for settlement.
|
|
5367
|
-
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
|
|
5368
|
-
{
|
|
5369
|
-
url: "https://facilitator.payai.network",
|
|
5370
|
-
keyless: true,
|
|
5371
|
-
schemes: ["exact"],
|
|
5372
|
-
settles: ["svm"],
|
|
5373
|
-
note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
|
|
5374
|
-
},
|
|
5375
|
-
{
|
|
5376
|
-
url: "https://pay.openfacilitator.io",
|
|
5377
|
-
keyless: true,
|
|
5378
|
-
schemes: ["exact"],
|
|
5379
|
-
settles: ["svm"],
|
|
5380
|
-
note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
|
|
5381
|
-
},
|
|
5382
|
-
{
|
|
5383
|
-
url: "https://facilitator.corbits.dev",
|
|
5384
|
-
keyless: true,
|
|
5385
|
-
schemes: ["exact"],
|
|
5386
|
-
settles: ["svm"],
|
|
5387
|
-
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
5388
|
-
}
|
|
5389
|
-
]
|
|
5390
|
-
};
|
|
5391
|
-
function knownFacilitatorsFor(network) {
|
|
5392
|
-
return _nullishCoalesce(KNOWN_FACILITATORS[network], () => ( []));
|
|
5393
|
-
}
|
|
5394
|
-
function firstKeylessFacilitator(network, method) {
|
|
5395
|
-
return knownFacilitatorsFor(network).find(
|
|
5396
|
-
(f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
|
|
5397
|
-
);
|
|
5398
|
-
}
|
|
5399
|
-
|
|
5400
5451
|
// src/receipts.ts
|
|
5401
5452
|
var DEFAULT_RETRIES = 5;
|
|
5402
5453
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
@@ -5409,7 +5460,7 @@ function isRetryableStatus(status) {
|
|
|
5409
5460
|
}
|
|
5410
5461
|
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
5411
5462
|
async function signBody(secret, body) {
|
|
5412
|
-
const subtle = _optionalChain([globalThis, 'access',
|
|
5463
|
+
const subtle = _optionalChain([globalThis, 'access', _89 => _89.crypto, 'optionalAccess', _90 => _90.subtle]);
|
|
5413
5464
|
if (!subtle) return null;
|
|
5414
5465
|
try {
|
|
5415
5466
|
const enc = new TextEncoder();
|
|
@@ -5479,7 +5530,7 @@ async function deliverReceipt(receipt, options) {
|
|
|
5479
5530
|
const retryable = status === void 0 ? true : isRetryableStatus(status);
|
|
5480
5531
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
5481
5532
|
try {
|
|
5482
|
-
_optionalChain([onAttempt, 'optionalCall',
|
|
5533
|
+
_optionalChain([onAttempt, 'optionalCall', _91 => _91({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
5483
5534
|
} catch (e40) {
|
|
5484
5535
|
}
|
|
5485
5536
|
if (ok) return { delivered: true, attempts: attempt, status };
|
package/dist/index.d.cts
CHANGED
|
@@ -5562,6 +5562,15 @@ declare class PipRailClient {
|
|
|
5562
5562
|
* `quote()` (read-only) and `fetch()` (which then authorises + pays).
|
|
5563
5563
|
*/
|
|
5564
5564
|
private resolveChallenge;
|
|
5565
|
+
/** Match a foreign-supplied network string against the bound driver, tolerating a
|
|
5566
|
+
* SLUG ('bsc', 'base', '56') the SAME way discovery's `railOnNetwork` already does —
|
|
5567
|
+
* normalize to CAIP-2 first, since a foreign/AEON/community 402 may label the network
|
|
5568
|
+
* with a slug (AEON serves v1 duplicate kinds '56'/'bsc'). ADDITIVE: a value that's
|
|
5569
|
+
* already CAIP-2 passes through `normalizeNetwork` UNCHANGED, so every existing
|
|
5570
|
+
* exact-CAIP-2 match is byte-identical; only slugs resolving to the bound chain become
|
|
5571
|
+
* newly matchable (an unknown slug stays unresolved → still unmatched; a different
|
|
5572
|
+
* chain's slug resolves elsewhere → still unmatched). */
|
|
5573
|
+
private supportsNetwork;
|
|
5565
5574
|
/** The candidate accepts this client could pay, on the bound network. Always the
|
|
5566
5575
|
* backendless `onchain-proof` rails; PLUS standard `exact` rails when `schemes`
|
|
5567
5576
|
* enables them AND the driver can settle them (EVM `payExact` + a recognised
|
|
@@ -6361,7 +6370,11 @@ interface AcceptOption {
|
|
|
6361
6370
|
* relayer key needed. (EVM facilitators are also the path onto Coinbase's Bazaar directory.)
|
|
6362
6371
|
*/
|
|
6363
6372
|
interface ExactRailOption {
|
|
6364
|
-
|
|
6373
|
+
/** How the gate settles an inbound `exact` payment. `'self'` = your own `relayer` broadcasts
|
|
6374
|
+
* (you pay gas). `'keyless'` = auto-pick a known KEYLESS facilitator for the chain (it sponsors
|
|
6375
|
+
* gas — zero-config; the same resolution as the top-level `exact: true` shorthand). `{ facilitator }`
|
|
6376
|
+
* = a specific facilitator you name — pin this in production rather than relying on the auto-pick. */
|
|
6377
|
+
settle: 'self' | 'keyless' | {
|
|
6365
6378
|
facilitator: string;
|
|
6366
6379
|
authHeaders?: () => Promise<Record<string, string>>;
|
|
6367
6380
|
/** Solana only — the facilitator's fee-payer pubkey, if you'd rather set it than have the
|
|
@@ -6453,9 +6466,17 @@ interface RequirePaymentOptions {
|
|
|
6453
6466
|
/**
|
|
6454
6467
|
* ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
|
|
6455
6468
|
* gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
|
|
6456
|
-
*
|
|
6469
|
+
* Shorthand **`exact: true`** === `{ settle: 'keyless' }`: the gate auto-picks a known KEYLESS
|
|
6470
|
+
* facilitator for each offered chain (from `KNOWN_FACILITATORS`), so neither buyer nor merchant
|
|
6471
|
+
* pays gas, zero-config. It is a SOFT, best-effort flag — a chain with no available keyless
|
|
6472
|
+
* facilitator DEGRADES GRACEFULLY to the always-present `onchain-proof` rail (the buyer pays gas,
|
|
6473
|
+
* the only option left when no facilitator can sponsor) with a LOUD warning; it never bricks the
|
|
6474
|
+
* gate. For guaranteed gasless, pin `settle: { facilitator }` (recommended in production) or
|
|
6475
|
+
* self-settle `settle: 'self'`; an EXPLICIT `settle` that can't carry exact throws loudly (a config
|
|
6476
|
+
* error you should fix). `false`/omitted keeps the gate exactly as today (`onchain-proof` only —
|
|
6477
|
+
* byte-identical).
|
|
6457
6478
|
*/
|
|
6458
|
-
exact?: ExactRailOption;
|
|
6479
|
+
exact?: boolean | ExactRailOption;
|
|
6459
6480
|
/**
|
|
6460
6481
|
* Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
|
|
6461
6482
|
* an input schema or it won't list the resource.** Set `true` for a no-input GET,
|
|
@@ -6611,6 +6632,14 @@ interface FacilitatorSupportedKind {
|
|
|
6611
6632
|
network: string;
|
|
6612
6633
|
/** The fee-payer pubkey when the kind carries one (SVM rails). */
|
|
6613
6634
|
feePayer?: string;
|
|
6635
|
+
/** The kind's x402 envelope version when the facilitator reports it per-kind — e.g.
|
|
6636
|
+
* AEON's `/supported` serves `{ x402Version, scheme, network }`, letting a reader tell
|
|
6637
|
+
* a v1 from a v2 BNB rail. Optional — absent when the facilitator doesn't advertise it. */
|
|
6638
|
+
x402Version?: number;
|
|
6639
|
+
/** The EVM exact transfer method (`eip3009` / `permit2`) when the facilitator advertises
|
|
6640
|
+
* it in the kind's `extra` — so coverage can tell whether a BNB exact kind is gasless
|
|
6641
|
+
* EIP-3009 or Permit2. Optional — most facilitators (AEON included) omit it. */
|
|
6642
|
+
assetTransferMethod?: string;
|
|
6614
6643
|
}
|
|
6615
6644
|
/**
|
|
6616
6645
|
* Parse a facilitator `/supported` body into its advertised (scheme, network) kinds.
|
package/dist/index.d.ts
CHANGED
|
@@ -5562,6 +5562,15 @@ declare class PipRailClient {
|
|
|
5562
5562
|
* `quote()` (read-only) and `fetch()` (which then authorises + pays).
|
|
5563
5563
|
*/
|
|
5564
5564
|
private resolveChallenge;
|
|
5565
|
+
/** Match a foreign-supplied network string against the bound driver, tolerating a
|
|
5566
|
+
* SLUG ('bsc', 'base', '56') the SAME way discovery's `railOnNetwork` already does —
|
|
5567
|
+
* normalize to CAIP-2 first, since a foreign/AEON/community 402 may label the network
|
|
5568
|
+
* with a slug (AEON serves v1 duplicate kinds '56'/'bsc'). ADDITIVE: a value that's
|
|
5569
|
+
* already CAIP-2 passes through `normalizeNetwork` UNCHANGED, so every existing
|
|
5570
|
+
* exact-CAIP-2 match is byte-identical; only slugs resolving to the bound chain become
|
|
5571
|
+
* newly matchable (an unknown slug stays unresolved → still unmatched; a different
|
|
5572
|
+
* chain's slug resolves elsewhere → still unmatched). */
|
|
5573
|
+
private supportsNetwork;
|
|
5565
5574
|
/** The candidate accepts this client could pay, on the bound network. Always the
|
|
5566
5575
|
* backendless `onchain-proof` rails; PLUS standard `exact` rails when `schemes`
|
|
5567
5576
|
* enables them AND the driver can settle them (EVM `payExact` + a recognised
|
|
@@ -6361,7 +6370,11 @@ interface AcceptOption {
|
|
|
6361
6370
|
* relayer key needed. (EVM facilitators are also the path onto Coinbase's Bazaar directory.)
|
|
6362
6371
|
*/
|
|
6363
6372
|
interface ExactRailOption {
|
|
6364
|
-
|
|
6373
|
+
/** How the gate settles an inbound `exact` payment. `'self'` = your own `relayer` broadcasts
|
|
6374
|
+
* (you pay gas). `'keyless'` = auto-pick a known KEYLESS facilitator for the chain (it sponsors
|
|
6375
|
+
* gas — zero-config; the same resolution as the top-level `exact: true` shorthand). `{ facilitator }`
|
|
6376
|
+
* = a specific facilitator you name — pin this in production rather than relying on the auto-pick. */
|
|
6377
|
+
settle: 'self' | 'keyless' | {
|
|
6365
6378
|
facilitator: string;
|
|
6366
6379
|
authHeaders?: () => Promise<Record<string, string>>;
|
|
6367
6380
|
/** Solana only — the facilitator's fee-payer pubkey, if you'd rather set it than have the
|
|
@@ -6453,9 +6466,17 @@ interface RequirePaymentOptions {
|
|
|
6453
6466
|
/**
|
|
6454
6467
|
* ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
|
|
6455
6468
|
* gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
|
|
6456
|
-
*
|
|
6469
|
+
* Shorthand **`exact: true`** === `{ settle: 'keyless' }`: the gate auto-picks a known KEYLESS
|
|
6470
|
+
* facilitator for each offered chain (from `KNOWN_FACILITATORS`), so neither buyer nor merchant
|
|
6471
|
+
* pays gas, zero-config. It is a SOFT, best-effort flag — a chain with no available keyless
|
|
6472
|
+
* facilitator DEGRADES GRACEFULLY to the always-present `onchain-proof` rail (the buyer pays gas,
|
|
6473
|
+
* the only option left when no facilitator can sponsor) with a LOUD warning; it never bricks the
|
|
6474
|
+
* gate. For guaranteed gasless, pin `settle: { facilitator }` (recommended in production) or
|
|
6475
|
+
* self-settle `settle: 'self'`; an EXPLICIT `settle` that can't carry exact throws loudly (a config
|
|
6476
|
+
* error you should fix). `false`/omitted keeps the gate exactly as today (`onchain-proof` only —
|
|
6477
|
+
* byte-identical).
|
|
6457
6478
|
*/
|
|
6458
|
-
exact?: ExactRailOption;
|
|
6479
|
+
exact?: boolean | ExactRailOption;
|
|
6459
6480
|
/**
|
|
6460
6481
|
* Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
|
|
6461
6482
|
* an input schema or it won't list the resource.** Set `true` for a no-input GET,
|
|
@@ -6611,6 +6632,14 @@ interface FacilitatorSupportedKind {
|
|
|
6611
6632
|
network: string;
|
|
6612
6633
|
/** The fee-payer pubkey when the kind carries one (SVM rails). */
|
|
6613
6634
|
feePayer?: string;
|
|
6635
|
+
/** The kind's x402 envelope version when the facilitator reports it per-kind — e.g.
|
|
6636
|
+
* AEON's `/supported` serves `{ x402Version, scheme, network }`, letting a reader tell
|
|
6637
|
+
* a v1 from a v2 BNB rail. Optional — absent when the facilitator doesn't advertise it. */
|
|
6638
|
+
x402Version?: number;
|
|
6639
|
+
/** The EVM exact transfer method (`eip3009` / `permit2`) when the facilitator advertises
|
|
6640
|
+
* it in the kind's `extra` — so coverage can tell whether a BNB exact kind is gasless
|
|
6641
|
+
* EIP-3009 or Permit2. Optional — most facilitators (AEON included) omit it. */
|
|
6642
|
+
assetTransferMethod?: string;
|
|
6614
6643
|
}
|
|
6615
6644
|
/**
|
|
6616
6645
|
* Parse a facilitator `/supported` body into its advertised (scheme, network) kinds.
|
package/dist/index.js
CHANGED
|
@@ -3220,7 +3220,7 @@ var PipRailClient = class {
|
|
|
3220
3220
|
const candidates = this.gatherCandidates(net, challenge, schemes);
|
|
3221
3221
|
if (candidates.length === 0) {
|
|
3222
3222
|
const exactOnNet = challenge.accepts.some(
|
|
3223
|
-
(a) => a.scheme === "exact" &&
|
|
3223
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network)
|
|
3224
3224
|
);
|
|
3225
3225
|
if (schemes.includes("exact") && exactOnNet && typeof net.payExact !== "function") {
|
|
3226
3226
|
throw new UnsupportedSchemeError(
|
|
@@ -3229,7 +3229,7 @@ var PipRailClient = class {
|
|
|
3229
3229
|
}
|
|
3230
3230
|
if (!schemes.includes("exact") && exactOnNet && typeof net.payExact === "function") {
|
|
3231
3231
|
const payable = challenge.accepts.some(
|
|
3232
|
-
(a) => a.scheme === "exact" &&
|
|
3232
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network) && net.describeAsset(a.asset) != null
|
|
3233
3233
|
);
|
|
3234
3234
|
if (payable) {
|
|
3235
3235
|
throw new NoCompatibleAcceptError(
|
|
@@ -3249,6 +3249,17 @@ var PipRailClient = class {
|
|
|
3249
3249
|
const chosen = priced.find((p) => p.quote.withinPolicy) ?? priced[0];
|
|
3250
3250
|
return { net, wallet, accept: chosen.accept, challenge, quote: chosen.quote };
|
|
3251
3251
|
}
|
|
3252
|
+
/** Match a foreign-supplied network string against the bound driver, tolerating a
|
|
3253
|
+
* SLUG ('bsc', 'base', '56') the SAME way discovery's `railOnNetwork` already does —
|
|
3254
|
+
* normalize to CAIP-2 first, since a foreign/AEON/community 402 may label the network
|
|
3255
|
+
* with a slug (AEON serves v1 duplicate kinds '56'/'bsc'). ADDITIVE: a value that's
|
|
3256
|
+
* already CAIP-2 passes through `normalizeNetwork` UNCHANGED, so every existing
|
|
3257
|
+
* exact-CAIP-2 match is byte-identical; only slugs resolving to the bound chain become
|
|
3258
|
+
* newly matchable (an unknown slug stays unresolved → still unmatched; a different
|
|
3259
|
+
* chain's slug resolves elsewhere → still unmatched). */
|
|
3260
|
+
supportsNetwork(net, network) {
|
|
3261
|
+
return net.supports(normalizeNetwork(network));
|
|
3262
|
+
}
|
|
3252
3263
|
/** The candidate accepts this client could pay, on the bound network. Always the
|
|
3253
3264
|
* backendless `onchain-proof` rails; PLUS standard `exact` rails when `schemes`
|
|
3254
3265
|
* enables them AND the driver can settle them (EVM `payExact` + a recognised
|
|
@@ -3259,14 +3270,14 @@ var PipRailClient = class {
|
|
|
3259
3270
|
if (schemes.includes("onchain-proof")) {
|
|
3260
3271
|
out.push(
|
|
3261
3272
|
...challenge.accepts.filter(
|
|
3262
|
-
(a) => a.scheme === "onchain-proof" &&
|
|
3273
|
+
(a) => a.scheme === "onchain-proof" && this.supportsNetwork(net, a.network)
|
|
3263
3274
|
)
|
|
3264
3275
|
);
|
|
3265
3276
|
}
|
|
3266
3277
|
if (schemes.includes("exact")) {
|
|
3267
3278
|
out.push(
|
|
3268
3279
|
...challenge.accepts.filter(
|
|
3269
|
-
(a) => a.scheme === "exact" &&
|
|
3280
|
+
(a) => a.scheme === "exact" && this.supportsNetwork(net, a.network) && typeof net.payExact === "function" && net.describeAsset(a.asset) != null && // a foreign rail's maxTimeoutSeconds must be a usable positive integer, or
|
|
3270
3281
|
// signing it would build a NaN/garbage validBefore — drop it silently
|
|
3271
3282
|
// (symmetric with an unrecognised token) rather than leak a raw SyntaxError.
|
|
3272
3283
|
Number.isInteger(a.maxTimeoutSeconds) && a.maxTimeoutSeconds > 0
|
|
@@ -3508,7 +3519,7 @@ var PipRailClient = class {
|
|
|
3508
3519
|
);
|
|
3509
3520
|
}
|
|
3510
3521
|
async payAndConfirm(net, wallet, accept) {
|
|
3511
|
-
if (!
|
|
3522
|
+
if (!this.supportsNetwork(net, accept.network)) {
|
|
3512
3523
|
throw new WrongChainError(
|
|
3513
3524
|
`Challenge expects ${accept.network} but client is on ${net.network}.`
|
|
3514
3525
|
);
|
|
@@ -4761,7 +4772,15 @@ function parseFacilitatorSupported(body) {
|
|
|
4761
4772
|
const o = k;
|
|
4762
4773
|
if (typeof o.scheme !== "string" || typeof o.network !== "string") continue;
|
|
4763
4774
|
const fp = o.extra?.feePayer;
|
|
4764
|
-
|
|
4775
|
+
const ver = o.x402Version;
|
|
4776
|
+
const method = o.extra?.assetTransferMethod;
|
|
4777
|
+
out.push({
|
|
4778
|
+
scheme: o.scheme,
|
|
4779
|
+
network: o.network,
|
|
4780
|
+
...typeof fp === "string" ? { feePayer: fp } : {},
|
|
4781
|
+
...typeof ver === "number" ? { x402Version: ver } : {},
|
|
4782
|
+
...typeof method === "string" ? { assetTransferMethod: method } : {}
|
|
4783
|
+
});
|
|
4765
4784
|
}
|
|
4766
4785
|
return out;
|
|
4767
4786
|
}
|
|
@@ -4872,6 +4891,63 @@ async function settleViaFacilitator(input) {
|
|
|
4872
4891
|
return { ok: true, receipt };
|
|
4873
4892
|
}
|
|
4874
4893
|
|
|
4894
|
+
// src/facilitators.ts
|
|
4895
|
+
var KNOWN_FACILITATORS = {
|
|
4896
|
+
// Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
|
|
4897
|
+
// exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
|
|
4898
|
+
"eip155:8453": [
|
|
4899
|
+
{
|
|
4900
|
+
url: "https://facilitator.payai.network",
|
|
4901
|
+
keyless: true,
|
|
4902
|
+
schemes: ["exact"],
|
|
4903
|
+
settles: ["eip3009"],
|
|
4904
|
+
note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
|
|
4905
|
+
},
|
|
4906
|
+
{
|
|
4907
|
+
url: "https://facilitator.xpay.sh",
|
|
4908
|
+
keyless: true,
|
|
4909
|
+
schemes: ["exact"],
|
|
4910
|
+
settles: ["eip3009"],
|
|
4911
|
+
note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
|
|
4912
|
+
}
|
|
4913
|
+
],
|
|
4914
|
+
// Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
|
|
4915
|
+
// by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
|
|
4916
|
+
// read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
|
|
4917
|
+
// returns 401 (an API key is required), so they are not keyless for settlement.
|
|
4918
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
|
|
4919
|
+
{
|
|
4920
|
+
url: "https://facilitator.payai.network",
|
|
4921
|
+
keyless: true,
|
|
4922
|
+
schemes: ["exact"],
|
|
4923
|
+
settles: ["svm"],
|
|
4924
|
+
note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
|
|
4925
|
+
},
|
|
4926
|
+
{
|
|
4927
|
+
url: "https://pay.openfacilitator.io",
|
|
4928
|
+
keyless: true,
|
|
4929
|
+
schemes: ["exact"],
|
|
4930
|
+
settles: ["svm"],
|
|
4931
|
+
note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
|
|
4932
|
+
},
|
|
4933
|
+
{
|
|
4934
|
+
url: "https://facilitator.corbits.dev",
|
|
4935
|
+
keyless: true,
|
|
4936
|
+
schemes: ["exact"],
|
|
4937
|
+
settles: ["svm"],
|
|
4938
|
+
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
4939
|
+
}
|
|
4940
|
+
]
|
|
4941
|
+
};
|
|
4942
|
+
function knownFacilitatorsFor(network) {
|
|
4943
|
+
return KNOWN_FACILITATORS[network] ?? [];
|
|
4944
|
+
}
|
|
4945
|
+
function firstKeylessFacilitator(network, method) {
|
|
4946
|
+
return knownFacilitatorsFor(network).find(
|
|
4947
|
+
(f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
|
|
4948
|
+
);
|
|
4949
|
+
}
|
|
4950
|
+
|
|
4875
4951
|
// src/server.ts
|
|
4876
4952
|
function toInvalidBody(result) {
|
|
4877
4953
|
return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
|
|
@@ -4892,10 +4968,16 @@ function normaliseAccepts(options) {
|
|
|
4892
4968
|
"requirePayment: provide either { chain, token, amount } or a non-empty `accept: [{ chain, token, amount }, \u2026]`."
|
|
4893
4969
|
);
|
|
4894
4970
|
}
|
|
4971
|
+
function normaliseExactOption(exact) {
|
|
4972
|
+
if (!exact) return void 0;
|
|
4973
|
+
if (exact === true) return { settle: "keyless" };
|
|
4974
|
+
return exact;
|
|
4975
|
+
}
|
|
4895
4976
|
function createPaymentGate(options) {
|
|
4896
4977
|
const minConfirmations = options.minConfirmations ?? 1;
|
|
4897
4978
|
const maxTimeoutSeconds = options.maxTimeoutSeconds ?? 600;
|
|
4898
4979
|
const genNonce = options.generateNonce ?? (() => globalThis.crypto.randomUUID());
|
|
4980
|
+
const exactOption = normaliseExactOption(options.exact);
|
|
4899
4981
|
let resolved;
|
|
4900
4982
|
function ready() {
|
|
4901
4983
|
if (resolved) return resolved;
|
|
@@ -4915,7 +4997,7 @@ function createPaymentGate(options) {
|
|
|
4915
4997
|
const { asset, decimals, symbol } = net.resolveToken(a.token);
|
|
4916
4998
|
const amountBase = parseUnits(a.amount, decimals);
|
|
4917
4999
|
const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
|
|
4918
|
-
if (
|
|
5000
|
+
if (exactOption) {
|
|
4919
5001
|
const outcome = await resolveExactRail(net, asset);
|
|
4920
5002
|
if (outcome.rail) spec.exact = outcome.rail;
|
|
4921
5003
|
else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
|
|
@@ -4923,10 +5005,17 @@ function createPaymentGate(options) {
|
|
|
4923
5005
|
return spec;
|
|
4924
5006
|
})
|
|
4925
5007
|
);
|
|
4926
|
-
if (
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
5008
|
+
if (exactOption && !specs.some((s) => s.exact)) {
|
|
5009
|
+
const why = 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.";
|
|
5010
|
+
if (exactOption.settle === "keyless") {
|
|
5011
|
+
if (typeof process === "undefined" || !process?.env?.PIPRAIL_NO_HINTS) {
|
|
5012
|
+
console.warn(
|
|
5013
|
+
`[piprail] exact: true \u2014 no offered chain has a gasless \`exact\` rail available, so this gate serves ONCHAIN-PROOF ONLY (buyers PAY GAS \u2014 the fallback when no facilitator can sponsor). ${why} To be gasless: pin \`exact: { settle: { facilitator } }\` or self-settle \`exact: { settle: 'self', relayer }\`. (Suppress with PIPRAIL_NO_HINTS=1.)`
|
|
5014
|
+
);
|
|
5015
|
+
}
|
|
5016
|
+
} else {
|
|
5017
|
+
throw new Error("requirePayment: `exact` was requested but none of the offered rails support it. " + why);
|
|
5018
|
+
}
|
|
4930
5019
|
}
|
|
4931
5020
|
return specs;
|
|
4932
5021
|
})();
|
|
@@ -4937,9 +5026,23 @@ function createPaymentGate(options) {
|
|
|
4937
5026
|
return p;
|
|
4938
5027
|
}
|
|
4939
5028
|
async function resolveExactRail(net, asset) {
|
|
4940
|
-
const cfg =
|
|
4941
|
-
const settle = cfg.settle;
|
|
5029
|
+
const cfg = exactOption;
|
|
4942
5030
|
if (!net.resolveExactRail) return {};
|
|
5031
|
+
let settle = cfg.settle;
|
|
5032
|
+
if (settle === "keyless") {
|
|
5033
|
+
const picked = firstKeylessFacilitator(net.network);
|
|
5034
|
+
if (!picked) {
|
|
5035
|
+
return {
|
|
5036
|
+
skipReason: `${net.network}: \`exact: true\` found no known keyless facilitator for this network. Pass \`exact: { settle: { facilitator } }\`, \`exact: { settle: 'self', relayer }\`, or see the coverage map (KNOWN_FACILITATORS / docs.piprail.com).`
|
|
5037
|
+
};
|
|
5038
|
+
}
|
|
5039
|
+
if (typeof process === "undefined" || process?.env?.NODE_ENV !== "production" && !process?.env?.PIPRAIL_NO_HINTS) {
|
|
5040
|
+
console.warn(
|
|
5041
|
+
`[piprail] exact: keyless rail on ${net.network} auto-settles via ${picked.url} (zero-config; pin \`exact.settle.facilitator\` in production).`
|
|
5042
|
+
);
|
|
5043
|
+
}
|
|
5044
|
+
settle = { facilitator: picked.url };
|
|
5045
|
+
}
|
|
4943
5046
|
let relayer;
|
|
4944
5047
|
let feePayer;
|
|
4945
5048
|
if (settle === "self") {
|
|
@@ -5311,7 +5414,12 @@ function requirePayment(options) {
|
|
|
5311
5414
|
} catch (err) {
|
|
5312
5415
|
if (err instanceof SettlementError) {
|
|
5313
5416
|
res.status(502);
|
|
5314
|
-
res.json({
|
|
5417
|
+
res.json({
|
|
5418
|
+
x402Version: 2,
|
|
5419
|
+
error: "settlement_failed",
|
|
5420
|
+
detail: err.message,
|
|
5421
|
+
fallback: "The gasless `exact` settlement failed. This resource also accepts the `onchain-proof` scheme \u2014 retry by paying that rail yourself (you broadcast the transfer and pay the gas). It is the fallback when no facilitator can sponsor the gas."
|
|
5422
|
+
});
|
|
5315
5423
|
return;
|
|
5316
5424
|
}
|
|
5317
5425
|
next(err);
|
|
@@ -5340,63 +5448,6 @@ function normaliseHeader(value) {
|
|
|
5340
5448
|
return value;
|
|
5341
5449
|
}
|
|
5342
5450
|
|
|
5343
|
-
// src/facilitators.ts
|
|
5344
|
-
var KNOWN_FACILITATORS = {
|
|
5345
|
-
// Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
|
|
5346
|
-
// exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
|
|
5347
|
-
"eip155:8453": [
|
|
5348
|
-
{
|
|
5349
|
-
url: "https://facilitator.payai.network",
|
|
5350
|
-
keyless: true,
|
|
5351
|
-
schemes: ["exact"],
|
|
5352
|
-
settles: ["eip3009"],
|
|
5353
|
-
note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
|
|
5354
|
-
},
|
|
5355
|
-
{
|
|
5356
|
-
url: "https://facilitator.xpay.sh",
|
|
5357
|
-
keyless: true,
|
|
5358
|
-
schemes: ["exact"],
|
|
5359
|
-
settles: ["eip3009"],
|
|
5360
|
-
note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
|
|
5361
|
-
}
|
|
5362
|
-
],
|
|
5363
|
-
// Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
|
|
5364
|
-
// by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
|
|
5365
|
-
// read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
|
|
5366
|
-
// returns 401 (an API key is required), so they are not keyless for settlement.
|
|
5367
|
-
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
|
|
5368
|
-
{
|
|
5369
|
-
url: "https://facilitator.payai.network",
|
|
5370
|
-
keyless: true,
|
|
5371
|
-
schemes: ["exact"],
|
|
5372
|
-
settles: ["svm"],
|
|
5373
|
-
note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
|
|
5374
|
-
},
|
|
5375
|
-
{
|
|
5376
|
-
url: "https://pay.openfacilitator.io",
|
|
5377
|
-
keyless: true,
|
|
5378
|
-
schemes: ["exact"],
|
|
5379
|
-
settles: ["svm"],
|
|
5380
|
-
note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
|
|
5381
|
-
},
|
|
5382
|
-
{
|
|
5383
|
-
url: "https://facilitator.corbits.dev",
|
|
5384
|
-
keyless: true,
|
|
5385
|
-
schemes: ["exact"],
|
|
5386
|
-
settles: ["svm"],
|
|
5387
|
-
note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
|
|
5388
|
-
}
|
|
5389
|
-
]
|
|
5390
|
-
};
|
|
5391
|
-
function knownFacilitatorsFor(network) {
|
|
5392
|
-
return KNOWN_FACILITATORS[network] ?? [];
|
|
5393
|
-
}
|
|
5394
|
-
function firstKeylessFacilitator(network, method) {
|
|
5395
|
-
return knownFacilitatorsFor(network).find(
|
|
5396
|
-
(f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
|
|
5397
|
-
);
|
|
5398
|
-
}
|
|
5399
|
-
|
|
5400
5451
|
// src/receipts.ts
|
|
5401
5452
|
var DEFAULT_RETRIES = 5;
|
|
5402
5453
|
var DEFAULT_TIMEOUT_MS = 1e4;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piprail/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Accept x402 crypto payments across 29 chains — every major EVM chain plus Solana, TON, Tron, NEAR, Sui, Aptos, Algorand, Stellar & XRPL — in a couple of lines. No backend, no database, no fee; payments settle straight to your wallet.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|