@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 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" && net.supports(a.network)
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" && net.supports(a.network) && net.describeAsset(a.asset) != null
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" && net.supports(a.network)
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" && net.supports(a.network) && typeof net.payExact === "function" && net.describeAsset(a.asset) != null && // a foreign rail's maxTimeoutSeconds must be a usable positive integer, or
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 (!net.supports(accept.network)) {
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
- out.push({ scheme: o.scheme, network: o.network, ...typeof fp === "string" ? { feePayer: fp } : {} });
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 (options.exact) {
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 (options.exact && !specs.some((s) => s.exact)) {
4927
- throw new Error(
4928
- "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`.")
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 = options.exact;
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', _79 => _79.extensions]), () => ( {}));
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', _80 => _80.error]) ? { error: opts.error } : {},
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({ x402Version: 2, error: "settlement_failed", detail: err.message });
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', _81 => _81.crypto, 'optionalAccess', _82 => _82.subtle]);
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', _83 => _83({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
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
- settle: 'self' | {
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
- * Omit to keep the gate exactly as today (`onchain-proof` only).
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
- settle: 'self' | {
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
- * Omit to keep the gate exactly as today (`onchain-proof` only).
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" && net.supports(a.network)
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" && net.supports(a.network) && net.describeAsset(a.asset) != null
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" && net.supports(a.network)
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" && net.supports(a.network) && typeof net.payExact === "function" && net.describeAsset(a.asset) != null && // a foreign rail's maxTimeoutSeconds must be a usable positive integer, or
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 (!net.supports(accept.network)) {
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
- out.push({ scheme: o.scheme, network: o.network, ...typeof fp === "string" ? { feePayer: fp } : {} });
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 (options.exact) {
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 (options.exact && !specs.some((s) => s.exact)) {
4927
- throw new Error(
4928
- "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`.")
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 = options.exact;
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({ x402Version: 2, error: "settlement_failed", detail: err.message });
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.1.1",
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",