@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 +31 -1
- package/dist/index.cjs +101 -69
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +97 -65
- package/package.json +1 -1
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.
|
|
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 (
|
|
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 (
|
|
4946
|
-
|
|
4947
|
-
|
|
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 =
|
|
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',
|
|
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',
|
|
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({
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
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 (
|
|
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 (
|
|
4946
|
-
|
|
4947
|
-
|
|
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 =
|
|
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({
|
|
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.
|
|
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",
|