@piprail/sdk 2.2.0 → 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,7 +4,36 @@ 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.2.0] — 2026-06-16broader x402 ecosystem interoperability
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
+
8
37
 
9
38
  Both changes are additive and backward-compatible — defaults and the zero-config 402 stay
10
39
  byte-identical; only previously-skipped cases become newly handled.
@@ -1220,6 +1249,7 @@ straight into your wallet. The API is small and self-contained.
1220
1249
  to your wallet; PipRail never holds funds.
1221
1250
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
1222
1251
 
1252
+ [2.3.0]: https://www.npmjs.com/package/@piprail/sdk
1223
1253
  [2.2.0]: https://www.npmjs.com/package/@piprail/sdk
1224
1254
  [2.1.1]: https://www.npmjs.com/package/@piprail/sdk
1225
1255
  [2.1.0]: https://www.npmjs.com/package/@piprail/sdk
package/dist/index.cjs CHANGED
@@ -4891,6 +4891,63 @@ async function settleViaFacilitator(input) {
4891
4891
  return { ok: true, receipt };
4892
4892
  }
4893
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
+
4894
4951
  // src/server.ts
4895
4952
  function toInvalidBody(result) {
4896
4953
  return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
@@ -4911,10 +4968,16 @@ function normaliseAccepts(options) {
4911
4968
  "requirePayment: provide either { chain, token, amount } or a non-empty `accept: [{ chain, token, amount }, \u2026]`."
4912
4969
  );
4913
4970
  }
4971
+ function normaliseExactOption(exact) {
4972
+ if (!exact) return void 0;
4973
+ if (exact === true) return { settle: "keyless" };
4974
+ return exact;
4975
+ }
4914
4976
  function createPaymentGate(options) {
4915
4977
  const minConfirmations = _nullishCoalesce(options.minConfirmations, () => ( 1));
4916
4978
  const maxTimeoutSeconds = _nullishCoalesce(options.maxTimeoutSeconds, () => ( 600));
4917
4979
  const genNonce = _nullishCoalesce(options.generateNonce, () => ( (() => globalThis.crypto.randomUUID())));
4980
+ const exactOption = normaliseExactOption(options.exact);
4918
4981
  let resolved;
4919
4982
  function ready() {
4920
4983
  if (resolved) return resolved;
@@ -4934,7 +4997,7 @@ function createPaymentGate(options) {
4934
4997
  const { asset, decimals, symbol } = net.resolveToken(a.token);
4935
4998
  const amountBase = _chunkJG6KRAW6cjs.parseUnits.call(void 0, a.amount, decimals);
4936
4999
  const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
4937
- if (options.exact) {
5000
+ if (exactOption) {
4938
5001
  const outcome = await resolveExactRail(net, asset);
4939
5002
  if (outcome.rail) spec.exact = outcome.rail;
4940
5003
  else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
@@ -4942,10 +5005,17 @@ function createPaymentGate(options) {
4942
5005
  return spec;
4943
5006
  })
4944
5007
  );
4945
- if (options.exact && !specs.some((s) => s.exact)) {
4946
- throw new Error(
4947
- "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`.")
4948
- );
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
+ }
4949
5019
  }
4950
5020
  return specs;
4951
5021
  })();
@@ -4956,9 +5026,23 @@ function createPaymentGate(options) {
4956
5026
  return p;
4957
5027
  }
4958
5028
  async function resolveExactRail(net, asset) {
4959
- const cfg = options.exact;
4960
- const settle = cfg.settle;
5029
+ const cfg = exactOption;
4961
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
+ }
4962
5046
  let relayer;
4963
5047
  let feePayer;
4964
5048
  if (settle === "self") {
@@ -5086,7 +5170,7 @@ function createPaymentGate(options) {
5086
5170
  instruction: describeChallenge({ x402Version: 2, resource: { url: resourceUrl }, accepts }),
5087
5171
  ...endpointInfo ? { endpoint: endpointInfo } : {}
5088
5172
  });
5089
- const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _81 => _81.extensions]), () => ( {}));
5173
+ const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _87 => _87.extensions]), () => ( {}));
5090
5174
  const rejectionPiprail = _nullishCoalesce(rejectionExt.piprail, () => ( {}));
5091
5175
  const bodyPiprail = { ..._nullishCoalesce(selfDescribe, () => ( {})), ...rejectionPiprail };
5092
5176
  const bodyExtensions = {
@@ -5106,7 +5190,7 @@ function createPaymentGate(options) {
5106
5190
  ...options.mimeType ? { mimeType: options.mimeType } : {}
5107
5191
  },
5108
5192
  accepts,
5109
- ..._optionalChain([opts, 'optionalAccess', _82 => _82.error]) ? { error: opts.error } : {},
5193
+ ..._optionalChain([opts, 'optionalAccess', _88 => _88.error]) ? { error: opts.error } : {},
5110
5194
  ...Object.keys(bodyExtensions).length > 0 ? { extensions: bodyExtensions } : {}
5111
5195
  };
5112
5196
  const headerChallenge = {
@@ -5330,7 +5414,12 @@ function requirePayment(options) {
5330
5414
  } catch (err) {
5331
5415
  if (err instanceof _chunkJG6KRAW6cjs.SettlementError) {
5332
5416
  res.status(502);
5333
- 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
+ });
5334
5423
  return;
5335
5424
  }
5336
5425
  next(err);
@@ -5359,63 +5448,6 @@ function normaliseHeader(value) {
5359
5448
  return value;
5360
5449
  }
5361
5450
 
5362
- // src/facilitators.ts
5363
- var KNOWN_FACILITATORS = {
5364
- // Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
5365
- // exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
5366
- "eip155:8453": [
5367
- {
5368
- url: "https://facilitator.payai.network",
5369
- keyless: true,
5370
- schemes: ["exact"],
5371
- settles: ["eip3009"],
5372
- note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
5373
- },
5374
- {
5375
- url: "https://facilitator.xpay.sh",
5376
- keyless: true,
5377
- schemes: ["exact"],
5378
- settles: ["eip3009"],
5379
- note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
5380
- }
5381
- ],
5382
- // Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
5383
- // by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
5384
- // read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
5385
- // returns 401 (an API key is required), so they are not keyless for settlement.
5386
- "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
5387
- {
5388
- url: "https://facilitator.payai.network",
5389
- keyless: true,
5390
- schemes: ["exact"],
5391
- settles: ["svm"],
5392
- note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
5393
- },
5394
- {
5395
- url: "https://pay.openfacilitator.io",
5396
- keyless: true,
5397
- schemes: ["exact"],
5398
- settles: ["svm"],
5399
- note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
5400
- },
5401
- {
5402
- url: "https://facilitator.corbits.dev",
5403
- keyless: true,
5404
- schemes: ["exact"],
5405
- settles: ["svm"],
5406
- note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
5407
- }
5408
- ]
5409
- };
5410
- function knownFacilitatorsFor(network) {
5411
- return _nullishCoalesce(KNOWN_FACILITATORS[network], () => ( []));
5412
- }
5413
- function firstKeylessFacilitator(network, method) {
5414
- return knownFacilitatorsFor(network).find(
5415
- (f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
5416
- );
5417
- }
5418
-
5419
5451
  // src/receipts.ts
5420
5452
  var DEFAULT_RETRIES = 5;
5421
5453
  var DEFAULT_TIMEOUT_MS = 1e4;
@@ -5428,7 +5460,7 @@ function isRetryableStatus(status) {
5428
5460
  }
5429
5461
  var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
5430
5462
  async function signBody(secret, body) {
5431
- const subtle = _optionalChain([globalThis, 'access', _83 => _83.crypto, 'optionalAccess', _84 => _84.subtle]);
5463
+ const subtle = _optionalChain([globalThis, 'access', _89 => _89.crypto, 'optionalAccess', _90 => _90.subtle]);
5432
5464
  if (!subtle) return null;
5433
5465
  try {
5434
5466
  const enc = new TextEncoder();
@@ -5498,7 +5530,7 @@ async function deliverReceipt(receipt, options) {
5498
5530
  const retryable = status === void 0 ? true : isRetryableStatus(status);
5499
5531
  const willRetry = !ok && retryable && attempt < maxAttempts;
5500
5532
  try {
5501
- _optionalChain([onAttempt, 'optionalCall', _85 => _85({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
5533
+ _optionalChain([onAttempt, 'optionalCall', _91 => _91({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
5502
5534
  } catch (e40) {
5503
5535
  }
5504
5536
  if (ok) return { delivered: true, attempts: attempt, status };
package/dist/index.d.cts CHANGED
@@ -6370,7 +6370,11 @@ interface AcceptOption {
6370
6370
  * relayer key needed. (EVM facilitators are also the path onto Coinbase's Bazaar directory.)
6371
6371
  */
6372
6372
  interface ExactRailOption {
6373
- 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' | {
6374
6378
  facilitator: string;
6375
6379
  authHeaders?: () => Promise<Record<string, string>>;
6376
6380
  /** Solana only — the facilitator's fee-payer pubkey, if you'd rather set it than have the
@@ -6462,9 +6466,17 @@ interface RequirePaymentOptions {
6462
6466
  /**
6463
6467
  * ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
6464
6468
  * gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
6465
- * 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).
6466
6478
  */
6467
- exact?: ExactRailOption;
6479
+ exact?: boolean | ExactRailOption;
6468
6480
  /**
6469
6481
  * Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
6470
6482
  * an input schema or it won't list the resource.** Set `true` for a no-input GET,
package/dist/index.d.ts CHANGED
@@ -6370,7 +6370,11 @@ interface AcceptOption {
6370
6370
  * relayer key needed. (EVM facilitators are also the path onto Coinbase's Bazaar directory.)
6371
6371
  */
6372
6372
  interface ExactRailOption {
6373
- 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' | {
6374
6378
  facilitator: string;
6375
6379
  authHeaders?: () => Promise<Record<string, string>>;
6376
6380
  /** Solana only — the facilitator's fee-payer pubkey, if you'd rather set it than have the
@@ -6462,9 +6466,17 @@ interface RequirePaymentOptions {
6462
6466
  /**
6463
6467
  * ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
6464
6468
  * gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
6465
- * 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).
6466
6478
  */
6467
- exact?: ExactRailOption;
6479
+ exact?: boolean | ExactRailOption;
6468
6480
  /**
6469
6481
  * Make this gate's 402 self-describing for the open indexes — **x402scan REQUIRES
6470
6482
  * an input schema or it won't list the resource.** Set `true` for a no-input GET,
package/dist/index.js CHANGED
@@ -4891,6 +4891,63 @@ async function settleViaFacilitator(input) {
4891
4891
  return { ok: true, receipt };
4892
4892
  }
4893
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
+
4894
4951
  // src/server.ts
4895
4952
  function toInvalidBody(result) {
4896
4953
  return { x402Version: 2, status: "invalid", error: result.error, detail: result.detail };
@@ -4911,10 +4968,16 @@ function normaliseAccepts(options) {
4911
4968
  "requirePayment: provide either { chain, token, amount } or a non-empty `accept: [{ chain, token, amount }, \u2026]`."
4912
4969
  );
4913
4970
  }
4971
+ function normaliseExactOption(exact) {
4972
+ if (!exact) return void 0;
4973
+ if (exact === true) return { settle: "keyless" };
4974
+ return exact;
4975
+ }
4914
4976
  function createPaymentGate(options) {
4915
4977
  const minConfirmations = options.minConfirmations ?? 1;
4916
4978
  const maxTimeoutSeconds = options.maxTimeoutSeconds ?? 600;
4917
4979
  const genNonce = options.generateNonce ?? (() => globalThis.crypto.randomUUID());
4980
+ const exactOption = normaliseExactOption(options.exact);
4918
4981
  let resolved;
4919
4982
  function ready() {
4920
4983
  if (resolved) return resolved;
@@ -4934,7 +4997,7 @@ function createPaymentGate(options) {
4934
4997
  const { asset, decimals, symbol } = net.resolveToken(a.token);
4935
4998
  const amountBase = parseUnits(a.amount, decimals);
4936
4999
  const spec = { net, asset, decimals, symbol, amountBase, amountFormatted: a.amount, payTo };
4937
- if (options.exact) {
5000
+ if (exactOption) {
4938
5001
  const outcome = await resolveExactRail(net, asset);
4939
5002
  if (outcome.rail) spec.exact = outcome.rail;
4940
5003
  else if (outcome.skipReason) exactSkips.push(outcome.skipReason);
@@ -4942,10 +5005,17 @@ function createPaymentGate(options) {
4942
5005
  return spec;
4943
5006
  })
4944
5007
  );
4945
- if (options.exact && !specs.some((s) => s.exact)) {
4946
- throw new Error(
4947
- "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`.")
4948
- );
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
+ }
4949
5019
  }
4950
5020
  return specs;
4951
5021
  })();
@@ -4956,9 +5026,23 @@ function createPaymentGate(options) {
4956
5026
  return p;
4957
5027
  }
4958
5028
  async function resolveExactRail(net, asset) {
4959
- const cfg = options.exact;
4960
- const settle = cfg.settle;
5029
+ const cfg = exactOption;
4961
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
+ }
4962
5046
  let relayer;
4963
5047
  let feePayer;
4964
5048
  if (settle === "self") {
@@ -5330,7 +5414,12 @@ function requirePayment(options) {
5330
5414
  } catch (err) {
5331
5415
  if (err instanceof SettlementError) {
5332
5416
  res.status(502);
5333
- 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
+ });
5334
5423
  return;
5335
5424
  }
5336
5425
  next(err);
@@ -5359,63 +5448,6 @@ function normaliseHeader(value) {
5359
5448
  return value;
5360
5449
  }
5361
5450
 
5362
- // src/facilitators.ts
5363
- var KNOWN_FACILITATORS = {
5364
- // Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
5365
- // exact payment, buyer paid zero ETH) — not just a /supported read — on the dated day.
5366
- "eip155:8453": [
5367
- {
5368
- url: "https://facilitator.payai.network",
5369
- keyless: true,
5370
- schemes: ["exact"],
5371
- settles: ["eip3009"],
5372
- note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
5373
- },
5374
- {
5375
- url: "https://facilitator.xpay.sh",
5376
- keyless: true,
5377
- schemes: ["exact"],
5378
- settles: ["eip3009"],
5379
- note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
5380
- }
5381
- ],
5382
- // Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
5383
- // by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
5384
- // read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
5385
- // returns 401 (an API key is required), so they are not keyless for settlement.
5386
- "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
5387
- {
5388
- url: "https://facilitator.payai.network",
5389
- keyless: true,
5390
- schemes: ["exact"],
5391
- settles: ["svm"],
5392
- note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
5393
- },
5394
- {
5395
- url: "https://pay.openfacilitator.io",
5396
- keyless: true,
5397
- schemes: ["exact"],
5398
- settles: ["svm"],
5399
- note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
5400
- },
5401
- {
5402
- url: "https://facilitator.corbits.dev",
5403
- keyless: true,
5404
- schemes: ["exact"],
5405
- settles: ["svm"],
5406
- note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
5407
- }
5408
- ]
5409
- };
5410
- function knownFacilitatorsFor(network) {
5411
- return KNOWN_FACILITATORS[network] ?? [];
5412
- }
5413
- function firstKeylessFacilitator(network, method) {
5414
- return knownFacilitatorsFor(network).find(
5415
- (f) => f.keyless && f.schemes.includes("exact") && (method === void 0 || f.settles.includes(method))
5416
- );
5417
- }
5418
-
5419
5451
  // src/receipts.ts
5420
5452
  var DEFAULT_RETRIES = 5;
5421
5453
  var DEFAULT_TIMEOUT_MS = 1e4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@piprail/sdk",
3
- "version": "2.2.0",
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",