@piprail/sdk 2.5.0 → 2.7.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 +53 -0
- package/README.md +1 -1
- package/dist/index.cjs +203 -22
- package/dist/index.d.cts +62 -10
- package/dist/index.d.ts +62 -10
- package/dist/index.js +197 -16
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,58 @@ 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.0] — 2026-06-19 — symmetric payment notifications (`onFailed`: both sides notified on success AND failure)
|
|
8
|
+
|
|
9
|
+
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical; omit the new
|
|
10
|
+
options and behaviour is unchanged.
|
|
11
|
+
|
|
12
|
+
- **New merchant hook `onFailed(failure)`** on `createPaymentGate` / `requirePayment` — the mirror of
|
|
13
|
+
`onPaid`. It fires whenever a SUBMITTED proof is rejected (a `kind:'invalid'` verdict: wrong amount,
|
|
14
|
+
expired, replayed, unknown asset, …), so the merchant is notified of a failure exactly as the buyer's
|
|
15
|
+
client already is. The new `FailedPayment` it receives carries the SAME machine `code` (a
|
|
16
|
+
`VerifyErrorCode`) the buyer gets — one consistent reason on both sides.
|
|
17
|
+
- **`onFailedError(error, failure)`** and **`awaitOnFailed`** mirror `onPaidError` / `awaitOnPaid`: the
|
|
18
|
+
hook is fully isolated (a sync throw or async rejection routes to `onFailedError` — it can never break
|
|
19
|
+
the request or escape as an unhandledRejection), and `awaitOnFailed` runs it before the 402 returns.
|
|
20
|
+
- **Exported `FailedPayment` type** — `{ code, detail, transient }`. The `transient` flag is `true` for
|
|
21
|
+
`tx_not_found` / `insufficient_confirmations` (the proof may still be settling and the buyer's client
|
|
22
|
+
auto-retries — alert on `!transient` to avoid false alarms on RPC lag), `false` for a definitive
|
|
23
|
+
rejection (wrong amount, expired, replayed, bad signature, …). Nothing is hidden — every rejected
|
|
24
|
+
attempt still fires `onFailed`.
|
|
25
|
+
- **Buyer side:** the `payment-failed` client event now also carries structured `code` / `detail` (the
|
|
26
|
+
server's parsed reason) alongside the existing human `reason`. It now ALSO fires on a **pre-send
|
|
27
|
+
decline** (policy / `onBeforePay` / no settleable rail) — previously those only threw — so a consumer
|
|
28
|
+
watching `onEvent` learns of EVERY failure, not just server rejections (the typed throw is unchanged).
|
|
29
|
+
- **Fires only on a real rejection** — not on a normal first-request `challenge` (no proof yet), and not
|
|
30
|
+
on a transient/settlement error that throws (an RPC blip, or a 5xx `SettlementError`): those aren't
|
|
31
|
+
payment verdicts. A failure the merchant never receives a request for (insufficient funds, policy
|
|
32
|
+
decline, abandonment) reaches only the buyer — a backendless gate is passive by design.
|
|
33
|
+
- **Examples + tests:** a complete `examples/payment-system/` reference (merchant + buyer — both sides,
|
|
34
|
+
success + failure → a SQLite ledger with a free `/ledger` dashboard); `onFailed` added to the Express +
|
|
35
|
+
Next.js examples. Coverage spans every `VerifyErrorCode`, both rails, the `requirePayment` middleware,
|
|
36
|
+
the `transient` flag, concurrency, replay-after-success, that a thrown `SettlementError`/transient error
|
|
37
|
+
does NOT fire `onFailed`, buyer declines, and a full client↔gate HTTP loop proving both sides receive
|
|
38
|
+
the same `code`.
|
|
39
|
+
|
|
40
|
+
## [2.6.0] — 2026-06-18 — keyless-gasless `exact` on 7 more EVM mainnets (6 → 13 chains)
|
|
41
|
+
|
|
42
|
+
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical; this only
|
|
43
|
+
grows the `KNOWN_FACILITATORS` seed map, so `exact: true` now resolves a keyless, gas-sponsoring
|
|
44
|
+
facilitator zero-config on **7 more chains**.
|
|
45
|
+
|
|
46
|
+
- **`exact: true` is now zero-config truly-gasless (buyer AND merchant pay zero native) on Ethereum,
|
|
47
|
+
Polygon, Arbitrum, Optimism, Avalanche, Sei, and Unichain** — joining Base, Monad, BNB, HyperEVM,
|
|
48
|
+
Algorand, and Solana (**6 → 13 chains**). Every new (chain, facilitator) pair was LIVE-settled on
|
|
49
|
+
mainnet with the buyer holding **zero native gas** (provably gasless), per the seed-map RULE
|
|
50
|
+
(settle-proven, never a `/supported` read). **17 new pairs**, e.g. Polygon now lists five keyless
|
|
51
|
+
facilitators (PayAI, Polygon Labs, Corbits, Ultravioleta DAO, Dexter).
|
|
52
|
+
- **Base** gains two more keyless facilitators (Cascade, Satoshi/bitcoinsapi).
|
|
53
|
+
- **Not seeded (advertised ≠ settles):** Celo and Scroll — Ultravioleta DAO lists them but its sponsor
|
|
54
|
+
contract reverts there (`contract_call_failed`), so neither was ever live-settled. They stay on
|
|
55
|
+
`onchain-proof` until a facilitator actually settles them.
|
|
56
|
+
- No API surface change. Merchants on an unseeded network still pass an explicit
|
|
57
|
+
`exact: { settle: { facilitator } }`.
|
|
58
|
+
|
|
7
59
|
## [2.5.0] — 2026-06-18 — gasless NEAR `exact` rail (NEP-366 meta-transactions)
|
|
8
60
|
|
|
9
61
|
Additive and backward-compatible — defaults and the zero-config 402 stay byte-identical; pure-EVM
|
|
@@ -1381,6 +1433,7 @@ straight into your wallet. The API is small and self-contained.
|
|
|
1381
1433
|
to your wallet; PipRail never holds funds.
|
|
1382
1434
|
- `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
|
|
1383
1435
|
|
|
1436
|
+
[2.6.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1384
1437
|
[2.5.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1385
1438
|
[2.4.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1386
1439
|
[2.3.0]: https://www.npmjs.com/package/@piprail/sdk
|
package/README.md
CHANGED
|
@@ -24,7 +24,7 @@ app.get('/report',
|
|
|
24
24
|
)
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
That route now costs **0.05 USDC on Base**, paid straight to your wallet. One parameter picks the chain. → [Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)
|
|
27
|
+
That route now costs **0.05 USDC on Base**, paid straight to your wallet. One parameter picks the chain. Add `onPaid` / `onFailed` to be notified the moment a payment settles or is rejected — both carry the same reason, and the buyer's client is notified too. → [Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)
|
|
28
28
|
|
|
29
29
|
## Let an agent pay for it
|
|
30
30
|
|
package/dist/index.cjs
CHANGED
|
@@ -3212,7 +3212,9 @@ var PipRailClient = (_class2 = class {
|
|
|
3212
3212
|
if (autoRoute) {
|
|
3213
3213
|
const plan = await this.planFromChallenge(net, wallet, challenge, url, schemes);
|
|
3214
3214
|
if (!plan.best) {
|
|
3215
|
-
|
|
3215
|
+
const reason = _nullishCoalesce(plan.fundingHint, () => ( "No rail is settleable for this payment."));
|
|
3216
|
+
this.safeEmit({ kind: "payment-failed", reason });
|
|
3217
|
+
throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(reason);
|
|
3216
3218
|
}
|
|
3217
3219
|
accept = plan.best.accept;
|
|
3218
3220
|
quote = plan.best.quote;
|
|
@@ -3502,10 +3504,10 @@ var PipRailClient = (_class2 = class {
|
|
|
3502
3504
|
* TERMINAL expiry/approval decline it must not retry) without parsing prose. */
|
|
3503
3505
|
async authorize(quote) {
|
|
3504
3506
|
if (!quote.withinPolicy) {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
);
|
|
3507
|
+
const reason = `Payment refused by policy: ${_nullishCoalesce(quote.policyReason, () => ( "not allowed"))}`;
|
|
3508
|
+
const reasonCode = reasonCodeForPolicy(quote.policyCode);
|
|
3509
|
+
this.safeEmit({ kind: "payment-failed", reason, code: reasonCode });
|
|
3510
|
+
throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(reason, { reasonCode });
|
|
3509
3511
|
}
|
|
3510
3512
|
const hook = this.opts.onBeforePay;
|
|
3511
3513
|
if (!hook) return;
|
|
@@ -3513,16 +3515,14 @@ var PipRailClient = (_class2 = class {
|
|
|
3513
3515
|
try {
|
|
3514
3516
|
approved = await hook(quote);
|
|
3515
3517
|
} catch (err) {
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
});
|
|
3518
|
+
const reason = "onBeforePay threw \u2014 refusing to pay.";
|
|
3519
|
+
this.safeEmit({ kind: "payment-failed", reason, code: "APPROVAL" });
|
|
3520
|
+
throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(reason, { cause: err, reasonCode: "APPROVAL" });
|
|
3520
3521
|
}
|
|
3521
3522
|
if (!approved) {
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
);
|
|
3523
|
+
const reason = `onBeforePay declined ${quote.amountFormatted} ${_nullishCoalesce(quote.symbol, () => ( ""))}`.trimEnd() + ` on ${quote.network}.`;
|
|
3524
|
+
this.safeEmit({ kind: "payment-failed", reason, code: "APPROVAL" });
|
|
3525
|
+
throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(reason, { reasonCode: "APPROVAL" });
|
|
3526
3526
|
}
|
|
3527
3527
|
}
|
|
3528
3528
|
/** Record a settled payment in the ledger (true decimals for the running total). */
|
|
@@ -3617,7 +3617,8 @@ var PipRailClient = (_class2 = class {
|
|
|
3617
3617
|
const unconfirmedNote = confirmed ? "" : " (broadcast but NOT locally confirmed \u2014 it may still have settled on-chain)";
|
|
3618
3618
|
this.safeEmit({
|
|
3619
3619
|
kind: "payment-failed",
|
|
3620
|
-
reason: `server returned 402 after broadcasting payment ${ref}${unconfirmedNote} (${why})
|
|
3620
|
+
reason: `server returned 402 after broadcasting payment ${ref}${unconfirmedNote} (${why})`,
|
|
3621
|
+
...lastReason ? { code: lastReason.error, detail: lastReason.detail } : {}
|
|
3621
3622
|
});
|
|
3622
3623
|
throw new (0, _chunkJG6KRAW6cjs.MaxRetriesExceededError)(
|
|
3623
3624
|
`Server still returned 402 after ${attempts} attempt(s) with on-chain proof ref=${ref}${unconfirmedNote}. Last server rejection: ${why}. Re-verify or re-submit ref=${ref} before retrying \u2014 never re-pay (it would double-spend).`,
|
|
@@ -3652,7 +3653,7 @@ var PipRailClient = (_class2 = class {
|
|
|
3652
3653
|
const headers = new Headers(_optionalChain([init, 'optionalAccess', _49 => _49.headers]));
|
|
3653
3654
|
headers.set(HEADER_SIGNATURE, buildExactSignatureHeader({ accepted, payload }));
|
|
3654
3655
|
const rejectDefinitive = (why2) => {
|
|
3655
|
-
this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})
|
|
3656
|
+
this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})`, code: why2 });
|
|
3656
3657
|
throw new (0, _chunkJG6KRAW6cjs.MaxRetriesExceededError)(
|
|
3657
3658
|
`exact: the facilitator rejected the payment (${why2}). Fix the cause, then re-present the SAME signed authorization (nonce=${nonce}) \u2014 do NOT re-sign a fresh nonce. ref=${nonce}.`,
|
|
3658
3659
|
{ ref: nonce }
|
|
@@ -3707,7 +3708,8 @@ var PipRailClient = (_class2 = class {
|
|
|
3707
3708
|
const why = lastReason ? `${lastReason.error}${lastReason.detail ? ` \u2014 ${lastReason.detail}` : ""}` : "server gave no reason";
|
|
3708
3709
|
this.safeEmit({
|
|
3709
3710
|
kind: "payment-failed",
|
|
3710
|
-
reason: `exact: 402 after submitting authorization nonce=${nonce} (${why})
|
|
3711
|
+
reason: `exact: 402 after submitting authorization nonce=${nonce} (${why})`,
|
|
3712
|
+
...lastReason ? { code: lastReason.error, detail: lastReason.detail } : {}
|
|
3711
3713
|
});
|
|
3712
3714
|
throw new (0, _chunkJG6KRAW6cjs.MaxRetriesExceededError)(
|
|
3713
3715
|
`exact: server still returned 402 after submitting the signed authorization (nonce=${nonce}). Last rejection: ${why}. Re-present the SAME authorization \u2014 do NOT re-sign a fresh nonce; verify authorizationState(${payerFrom}, ${nonce}) first. ref=${nonce}.`,
|
|
@@ -4962,6 +4964,20 @@ var KNOWN_FACILITATORS = {
|
|
|
4962
4964
|
schemes: ["exact"],
|
|
4963
4965
|
settles: ["eip3009"],
|
|
4964
4966
|
note: "GoPlausible \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-17 (tx 0x9bcbc1f01fe1fd1aed2a79e5582555164cc4185e9800189df378a6b46eb9c59e). 2nd GoPlausible validation chain (after Algorand)."
|
|
4967
|
+
},
|
|
4968
|
+
{
|
|
4969
|
+
url: "https://facilitator.cascade.fyi",
|
|
4970
|
+
keyless: true,
|
|
4971
|
+
schemes: ["exact"],
|
|
4972
|
+
settles: ["eip3009"],
|
|
4973
|
+
note: "Cascade \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-18 (tx 0x2e784725f3c66e170720cc8df43a9248f02ad80914fcbe05dadbdbe411c3b52b)."
|
|
4974
|
+
},
|
|
4975
|
+
{
|
|
4976
|
+
url: "https://facilitator.bitcoinsapi.com",
|
|
4977
|
+
keyless: true,
|
|
4978
|
+
schemes: ["exact"],
|
|
4979
|
+
settles: ["eip3009"],
|
|
4980
|
+
note: "Satoshi (bitcoinsapi) \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-18 (tx 0xbb2b6c354b12c6d07cce91e2b337e38327d4b0833bbd1dfbeda3ad495b67b819)."
|
|
4965
4981
|
}
|
|
4966
4982
|
],
|
|
4967
4983
|
// Monad (eip155:143). Corbits + Ultravioleta DAO each keyless-settle the EVM EIP-3009 exact rail
|
|
@@ -5024,6 +5040,139 @@ var KNOWN_FACILITATORS = {
|
|
|
5024
5040
|
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (HyperEVM native USDC EIP-3009). LIVE-settled on HyperEVM 2026-06-17 (tx 0x56af8148a92a291f0ce362e250919f7742074e5464ac0f315ad68abaec93bd0a)."
|
|
5025
5041
|
}
|
|
5026
5042
|
],
|
|
5043
|
+
// ── gasless-extension (2026-06-18): 7 more EVM mainnets, each LIVE-settled by us — a real EIP-3009
|
|
5044
|
+
// exact payment with the buyer holding ZERO native (so PROVABLY gasless), funded self-service by
|
|
5045
|
+
// bridging USDC in (LI.FI/Eco/Relay), never a /supported read. Dexter enforces a ~$0.004 dynamic
|
|
5046
|
+
// floor on some chains (we cleared it at $0.005); UVD has no floor (100%-sponsors) but its sponsor
|
|
5047
|
+
// contract is NOT deployed on every chain it advertises (Avalanche/Celo/Scroll → contract_call_failed,
|
|
5048
|
+
// left UNSEEDED — the NEAR lesson: advertising ≠ settling).
|
|
5049
|
+
// Ethereum L1 (eip155:1) — UVD (no floor, sponsors L1 gas). 2nd keyless ETH option vs Primev, which
|
|
5050
|
+
// rejected PipRail's exact as unsupported_scheme.
|
|
5051
|
+
"eip155:1": [
|
|
5052
|
+
{
|
|
5053
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5054
|
+
keyless: true,
|
|
5055
|
+
schemes: ["exact"],
|
|
5056
|
+
settles: ["eip3009"],
|
|
5057
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (Ethereum native USDC EIP-3009; no settlement floor). LIVE-settled on Ethereum mainnet 2026-06-18 (tx 0x7fcc3cafdf43c52888f0117cde633eff35371f832dfb8dbd84e6fd7704ae5cfc)."
|
|
5058
|
+
}
|
|
5059
|
+
],
|
|
5060
|
+
// Polygon PoS (eip155:137) — FIVE keyless facilitators (the broadest after Base), all live-settled.
|
|
5061
|
+
"eip155:137": [
|
|
5062
|
+
{
|
|
5063
|
+
url: "https://facilitator.payai.network",
|
|
5064
|
+
keyless: true,
|
|
5065
|
+
schemes: ["exact"],
|
|
5066
|
+
settles: ["eip3009"],
|
|
5067
|
+
note: "PayAI \u2014 keyless, sponsors gas (Polygon native USDC EIP-3009). LIVE-settled on Polygon 2026-06-18 (tx 0xb37630871504618c4db35e8ef0edd3c99deac102b143b7e5eb738c03fb13a619)."
|
|
5068
|
+
},
|
|
5069
|
+
{
|
|
5070
|
+
url: "https://x402.polygon.technology",
|
|
5071
|
+
keyless: true,
|
|
5072
|
+
schemes: ["exact"],
|
|
5073
|
+
settles: ["eip3009"],
|
|
5074
|
+
note: "Polygon Labs (the official Polygon facilitator) \u2014 keyless, sponsors gas. LIVE-settled on Polygon 2026-06-18 (tx 0x6a8ca60ea111959a61a85ad6c4f2ed99e192052ec7b9ffb59baa33691a86f419)."
|
|
5075
|
+
},
|
|
5076
|
+
{
|
|
5077
|
+
url: "https://facilitator.corbits.dev",
|
|
5078
|
+
keyless: true,
|
|
5079
|
+
schemes: ["exact"],
|
|
5080
|
+
settles: ["eip3009"],
|
|
5081
|
+
note: "Corbits (Faremeter) \u2014 keyless, sponsors gas. LIVE-settled on Polygon 2026-06-18 (tx 0x2fadca2ee3cd7fcff32d015e8b06109de6b747d7cd0f80dcd8fb022b2d58d008)."
|
|
5082
|
+
},
|
|
5083
|
+
{
|
|
5084
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5085
|
+
keyless: true,
|
|
5086
|
+
schemes: ["exact"],
|
|
5087
|
+
settles: ["eip3009"],
|
|
5088
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Polygon 2026-06-18 (tx 0x0922cd78f3b7c35cc294cf0c0d9b7609e6596eb337be0d197fa5fc511eacba34)."
|
|
5089
|
+
},
|
|
5090
|
+
{
|
|
5091
|
+
url: "https://x402.dexter.cash",
|
|
5092
|
+
keyless: true,
|
|
5093
|
+
schemes: ["exact"],
|
|
5094
|
+
settles: ["eip3009"],
|
|
5095
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic settlement floor (sub-floor \u2192 amount_too_low). LIVE-settled on Polygon 2026-06-18 at $0.005 (tx 0x4a7cfc96e4e2496652435c77b0021db4a459e7bcd97948c15e6bea09077c164b)."
|
|
5096
|
+
}
|
|
5097
|
+
],
|
|
5098
|
+
// Arbitrum One (eip155:42161) — PayAI + UVD + Dexter.
|
|
5099
|
+
"eip155:42161": [
|
|
5100
|
+
{
|
|
5101
|
+
url: "https://facilitator.payai.network",
|
|
5102
|
+
keyless: true,
|
|
5103
|
+
schemes: ["exact"],
|
|
5104
|
+
settles: ["eip3009"],
|
|
5105
|
+
note: "PayAI \u2014 keyless, sponsors gas (Arbitrum native USDC EIP-3009). LIVE-settled on Arbitrum 2026-06-18 (tx 0x4c7fbfb37ef087bb9bc8872b1b2b0b83ea7ca4cf6b50bc883e51f9b85be61199)."
|
|
5106
|
+
},
|
|
5107
|
+
{
|
|
5108
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5109
|
+
keyless: true,
|
|
5110
|
+
schemes: ["exact"],
|
|
5111
|
+
settles: ["eip3009"],
|
|
5112
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Arbitrum 2026-06-18 (tx 0xd1fa7e6ffbd59502bba5809ce181d7936749b93a45c8c2277b4f6d8638326dec)."
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
url: "https://x402.dexter.cash",
|
|
5116
|
+
keyless: true,
|
|
5117
|
+
schemes: ["exact"],
|
|
5118
|
+
settles: ["eip3009"],
|
|
5119
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic floor. LIVE-settled on Arbitrum 2026-06-18 at $0.005 (tx 0x0993d51b3859dfd7e8d5b2af709ce64f01ff34ed14bfef665dbb340e7373d599)."
|
|
5120
|
+
}
|
|
5121
|
+
],
|
|
5122
|
+
// Optimism (eip155:10) — Dexter + UVD.
|
|
5123
|
+
"eip155:10": [
|
|
5124
|
+
{
|
|
5125
|
+
url: "https://x402.dexter.cash",
|
|
5126
|
+
keyless: true,
|
|
5127
|
+
schemes: ["exact"],
|
|
5128
|
+
settles: ["eip3009"],
|
|
5129
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic floor. LIVE-settled on Optimism 2026-06-18 at $0.005 (tx 0x98b282a5dd698054eff4900ff8547170d4f7605b51140c55147fc5ae485f81da)."
|
|
5130
|
+
},
|
|
5131
|
+
{
|
|
5132
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5133
|
+
keyless: true,
|
|
5134
|
+
schemes: ["exact"],
|
|
5135
|
+
settles: ["eip3009"],
|
|
5136
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Optimism 2026-06-18 (tx 0xdd91dfdf12bed2d196e37fa1dea9a0da71a97e3c39a0ec0ed114be6a9af22837)."
|
|
5137
|
+
}
|
|
5138
|
+
],
|
|
5139
|
+
// Avalanche C-Chain (eip155:43114) — PayAI + Dexter. (UVD advertises it but contract_call_failed → unseeded.)
|
|
5140
|
+
"eip155:43114": [
|
|
5141
|
+
{
|
|
5142
|
+
url: "https://facilitator.payai.network",
|
|
5143
|
+
keyless: true,
|
|
5144
|
+
schemes: ["exact"],
|
|
5145
|
+
settles: ["eip3009"],
|
|
5146
|
+
note: "PayAI \u2014 keyless, sponsors gas (Avalanche native USDC EIP-3009). LIVE-settled on Avalanche 2026-06-18 (tx 0x6a1307bc48a157de236ea03440ea2cb6ad4f27e22dd9c89a4345b4c3edb270c7)."
|
|
5147
|
+
},
|
|
5148
|
+
{
|
|
5149
|
+
url: "https://x402.dexter.cash",
|
|
5150
|
+
keyless: true,
|
|
5151
|
+
schemes: ["exact"],
|
|
5152
|
+
settles: ["eip3009"],
|
|
5153
|
+
note: "Dexter \u2014 keyless, sponsors gas (no floor hit at $0.001 here). LIVE-settled on Avalanche 2026-06-18 (tx 0xb2263e9a4ea3917eee6acabcb454d42a50264fdd69a0781ed8fcaec5590e264b)."
|
|
5154
|
+
}
|
|
5155
|
+
],
|
|
5156
|
+
// Sei (eip155:1329) — PayAI (the only keyless facilitator that lists Sei).
|
|
5157
|
+
"eip155:1329": [
|
|
5158
|
+
{
|
|
5159
|
+
url: "https://facilitator.payai.network",
|
|
5160
|
+
keyless: true,
|
|
5161
|
+
schemes: ["exact"],
|
|
5162
|
+
settles: ["eip3009"],
|
|
5163
|
+
note: "PayAI \u2014 keyless, sponsors gas (Sei native USDC EIP-3009). LIVE-settled on Sei 2026-06-18 (tx 0xde63679b64749cb59625527e5c7682503c851d66b9b8b8ba217ed7b099461312)."
|
|
5164
|
+
}
|
|
5165
|
+
],
|
|
5166
|
+
// Unichain (eip155:130) — UVD (the only keyless facilitator that lists Unichain).
|
|
5167
|
+
"eip155:130": [
|
|
5168
|
+
{
|
|
5169
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5170
|
+
keyless: true,
|
|
5171
|
+
schemes: ["exact"],
|
|
5172
|
+
settles: ["eip3009"],
|
|
5173
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (Unichain native USDC EIP-3009). LIVE-settled on Unichain 2026-06-18 (tx 0xf8bf4a9a200f24159ac67c6315c30b194257999d7da21471b39d5f2cc32b2236)."
|
|
5174
|
+
}
|
|
5175
|
+
],
|
|
5027
5176
|
// Algorand (mainnet, CAIP-2 = full base64 genesis hash). GoPlausible keyless-settles the ratified
|
|
5028
5177
|
// x402 Algorand `exact` rail (atomic-group fee pooling): its sponsor pools the whole group fee and
|
|
5029
5178
|
// submits, so NEITHER the buyer NOR the merchant pays ALGO — both-sides gasless. LIVE-settled by us
|
|
@@ -5113,6 +5262,7 @@ function normaliseExactOption(exact) {
|
|
|
5113
5262
|
if (exact === true) return { settle: "keyless" };
|
|
5114
5263
|
return exact;
|
|
5115
5264
|
}
|
|
5265
|
+
var TRANSIENT_VERIFY_CODES = /* @__PURE__ */ new Set(["tx_not_found", "insufficient_confirmations"]);
|
|
5116
5266
|
function createPaymentGate(options) {
|
|
5117
5267
|
const minConfirmations = _nullishCoalesce(options.minConfirmations, () => ( 1));
|
|
5118
5268
|
const maxTimeoutSeconds = _nullishCoalesce(options.maxTimeoutSeconds, () => ( 600));
|
|
@@ -5397,6 +5547,32 @@ function createPaymentGate(options) {
|
|
|
5397
5547
|
if (options.awaitOnPaid) await fireOnPaid(paid);
|
|
5398
5548
|
else void fireOnPaid(paid);
|
|
5399
5549
|
}
|
|
5550
|
+
function reportOnFailedError(error, failure) {
|
|
5551
|
+
if (!options.onFailedError) return;
|
|
5552
|
+
try {
|
|
5553
|
+
options.onFailedError(error, failure);
|
|
5554
|
+
} catch (e38) {
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
function fireOnFailed(failure) {
|
|
5558
|
+
if (!options.onFailed) return;
|
|
5559
|
+
let outcome;
|
|
5560
|
+
try {
|
|
5561
|
+
outcome = options.onFailed(failure);
|
|
5562
|
+
} catch (err) {
|
|
5563
|
+
reportOnFailedError(err, failure);
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5566
|
+
if (outcome != null && typeof outcome.then === "function") {
|
|
5567
|
+
return Promise.resolve(outcome).catch((err) => reportOnFailedError(err, failure));
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
async function deliverOnFailed(result) {
|
|
5571
|
+
const code = result.error;
|
|
5572
|
+
const failure = { code, detail: result.detail, transient: TRANSIENT_VERIFY_CODES.has(code) };
|
|
5573
|
+
if (options.awaitOnFailed) await fireOnFailed(failure);
|
|
5574
|
+
else void fireOnFailed(failure);
|
|
5575
|
+
}
|
|
5400
5576
|
async function describe(resourceUrl = "") {
|
|
5401
5577
|
const specs = await ready();
|
|
5402
5578
|
const accepts = [];
|
|
@@ -5475,28 +5651,28 @@ function createPaymentGate(options) {
|
|
|
5475
5651
|
nonce = [exact.payload.transaction, exact.payload.senderAuth].map((t) => {
|
|
5476
5652
|
try {
|
|
5477
5653
|
return Buffer.from(t, "base64").toString("base64");
|
|
5478
|
-
} catch (
|
|
5654
|
+
} catch (e39) {
|
|
5479
5655
|
return t;
|
|
5480
5656
|
}
|
|
5481
5657
|
}).join("|");
|
|
5482
5658
|
} else if ("transaction" in exact.payload) {
|
|
5483
5659
|
try {
|
|
5484
5660
|
nonce = Buffer.from(exact.payload.transaction, "base64").toString("base64");
|
|
5485
|
-
} catch (
|
|
5661
|
+
} catch (e40) {
|
|
5486
5662
|
nonce = exact.payload.transaction;
|
|
5487
5663
|
}
|
|
5488
5664
|
} else if ("paymentGroup" in exact.payload) {
|
|
5489
5665
|
nonce = exact.payload.paymentGroup.map((t) => {
|
|
5490
5666
|
try {
|
|
5491
5667
|
return Buffer.from(t, "base64").toString("base64");
|
|
5492
|
-
} catch (
|
|
5668
|
+
} catch (e41) {
|
|
5493
5669
|
return t;
|
|
5494
5670
|
}
|
|
5495
5671
|
}).join("|");
|
|
5496
5672
|
} else if ("signedDelegateAction" in exact.payload) {
|
|
5497
5673
|
try {
|
|
5498
5674
|
nonce = Buffer.from(exact.payload.signedDelegateAction, "base64").toString("base64");
|
|
5499
|
-
} catch (
|
|
5675
|
+
} catch (e42) {
|
|
5500
5676
|
nonce = exact.payload.signedDelegateAction;
|
|
5501
5677
|
}
|
|
5502
5678
|
} else if ("permit2Authorization" in exact.payload) {
|
|
@@ -5560,6 +5736,11 @@ function createPaymentGate(options) {
|
|
|
5560
5736
|
return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
|
|
5561
5737
|
}
|
|
5562
5738
|
async function verify(paymentSignature) {
|
|
5739
|
+
const result = await resolveVerdict(paymentSignature);
|
|
5740
|
+
if (result.kind === "invalid") await deliverOnFailed(result);
|
|
5741
|
+
return result;
|
|
5742
|
+
}
|
|
5743
|
+
async function resolveVerdict(paymentSignature) {
|
|
5563
5744
|
const raw = normaliseHeader(paymentSignature);
|
|
5564
5745
|
if (!raw) return asChallenge();
|
|
5565
5746
|
const sig = parseSignatureHeader(raw);
|
|
@@ -5637,7 +5818,7 @@ async function signBody(secret, body) {
|
|
|
5637
5818
|
const sig = await subtle.sign("HMAC", key, enc.encode(body));
|
|
5638
5819
|
const hex = Array.from(new Uint8Array(sig)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
5639
5820
|
return `sha256=${hex}`;
|
|
5640
|
-
} catch (
|
|
5821
|
+
} catch (e43) {
|
|
5641
5822
|
return null;
|
|
5642
5823
|
}
|
|
5643
5824
|
}
|
|
@@ -5698,7 +5879,7 @@ async function deliverReceipt(receipt, options) {
|
|
|
5698
5879
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
5699
5880
|
try {
|
|
5700
5881
|
_optionalChain([onAttempt, 'optionalCall', _91 => _91({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
5701
|
-
} catch (
|
|
5882
|
+
} catch (e44) {
|
|
5702
5883
|
}
|
|
5703
5884
|
if (ok) return { delivered: true, attempts: attempt, status };
|
|
5704
5885
|
if (!willRetry) {
|
package/dist/index.d.cts
CHANGED
|
@@ -5044,6 +5044,14 @@ type PipRailEvent = {
|
|
|
5044
5044
|
} | {
|
|
5045
5045
|
kind: 'payment-failed';
|
|
5046
5046
|
reason: string;
|
|
5047
|
+
/** A machine-readable failure code when one is known. For a SERVER rejection it's the SAME
|
|
5048
|
+
* code the merchant's `onFailed` hook receives (a canonical {@link VerifyErrorCode} from a
|
|
5049
|
+
* PipRail gate, or a foreign facilitator's reason string); for a pre-send client DECLINE
|
|
5050
|
+
* (policy / budget / approval) it's that decline reason (e.g. `'BUDGET'`, `'APPROVAL'`).
|
|
5051
|
+
* Absent when no structured code was given. */
|
|
5052
|
+
code?: string;
|
|
5053
|
+
/** Human-readable detail, when present (e.g. `"Paid 40000, required 500000."`). */
|
|
5054
|
+
detail?: string;
|
|
5047
5055
|
};
|
|
5048
5056
|
/**
|
|
5049
5057
|
* Wallet for the chosen chain family. **One field, every chain: `{ key }`** — the
|
|
@@ -6463,6 +6471,28 @@ interface ExactRailOption {
|
|
|
6463
6471
|
* "just works". Force `'eip3009'` or `'permit2'` to pin one. Ignored on Solana (always SVM). */
|
|
6464
6472
|
method?: 'eip3009' | 'permit2' | 'auto';
|
|
6465
6473
|
}
|
|
6474
|
+
/**
|
|
6475
|
+
* The merchant-side mirror of {@link PaidReceipt}: what an {@link RequirePaymentOptions.onFailed}
|
|
6476
|
+
* hook receives when a SUBMITTED payment proof is REJECTED. It carries the SAME machine-readable
|
|
6477
|
+
* `code` the buyer's client is given for that rejection, so both sides are notified of one
|
|
6478
|
+
* consistent reason. (A rejection has no settlement, so — unlike a receipt — there is no tx hash
|
|
6479
|
+
* or settled amount to report.)
|
|
6480
|
+
*/
|
|
6481
|
+
interface FailedPayment {
|
|
6482
|
+
/** The canonical rejection reason — the same {@link VerifyErrorCode} surfaced to the buyer
|
|
6483
|
+
* (e.g. `amount_too_low`, `payment_expired`, `tx_already_used`, `transfer_not_found`). */
|
|
6484
|
+
code: VerifyErrorCode;
|
|
6485
|
+
/** Human-readable detail, e.g. `"Paid 40000, required 500000."`. */
|
|
6486
|
+
detail: string;
|
|
6487
|
+
/**
|
|
6488
|
+
* `true` for a **transient** rejection (`tx_not_found` / `insufficient_confirmations`): the proof
|
|
6489
|
+
* may still be settling and the buyer's client **retries automatically** — you'll get `onPaid` if
|
|
6490
|
+
* it then succeeds. `false` for a **definitive** rejection the buyer must fix (wrong amount,
|
|
6491
|
+
* expired, replayed, bad signature, wrong recipient). Alert on `!transient` to avoid false alarms
|
|
6492
|
+
* on normal RPC lag; nothing is hidden — every rejected attempt still fires `onFailed`.
|
|
6493
|
+
*/
|
|
6494
|
+
transient: boolean;
|
|
6495
|
+
}
|
|
6466
6496
|
interface RequirePaymentOptions {
|
|
6467
6497
|
/**
|
|
6468
6498
|
* Single-chain form: which chain to accept payment on. EVM ('bnb'|'base'|…),
|
|
@@ -6535,6 +6565,37 @@ interface RequirePaymentOptions {
|
|
|
6535
6565
|
* via `onPaidError`; it never turns a settled payment into a 402.
|
|
6536
6566
|
*/
|
|
6537
6567
|
awaitOnPaid?: boolean;
|
|
6568
|
+
/**
|
|
6569
|
+
* The merchant-side mirror of `onPaid`: fired when a SUBMITTED payment proof is REJECTED — a
|
|
6570
|
+
* `kind:'invalid'` verdict (wrong amount, expired, replayed, unknown asset, …). Receives a
|
|
6571
|
+
* {@link FailedPayment} carrying the SAME machine `code` the buyer's client is given, so the
|
|
6572
|
+
* merchant and the buyer are notified of the same failure with the same reason.
|
|
6573
|
+
*
|
|
6574
|
+
* Fires ONLY on a rejected attempt — NOT on a normal first-request 402 `challenge` (no proof
|
|
6575
|
+
* yet), and NOT on a transient/settlement error that throws (an RPC blip, or a 5xx
|
|
6576
|
+
* `SettlementError`): those aren't payment verdicts. Like `onPaid`, it may be **sync or async**
|
|
6577
|
+
* and is fully isolated — a throw OR a rejected promise is caught and routed to `onFailedError`,
|
|
6578
|
+
* so it can never break the request or crash the process. Fire-and-forget by default; set
|
|
6579
|
+
* `awaitOnFailed` to run it before the 402 is returned.
|
|
6580
|
+
*
|
|
6581
|
+
* NOTE: a failure the merchant never receives a request for — the buyer can't afford it, an
|
|
6582
|
+
* `onBeforePay`/`policy` declines it, or the buyer abandons before paying — cannot reach a
|
|
6583
|
+
* backendless gate (only the buyer's client sees it). `onFailed` covers every rejection that
|
|
6584
|
+
* DOES reach the gate.
|
|
6585
|
+
*/
|
|
6586
|
+
onFailed?: (failure: FailedPayment) => void | Promise<void>;
|
|
6587
|
+
/**
|
|
6588
|
+
* Observe a failure inside `onFailed` (sync throw or async rejection) — the mirror of
|
|
6589
|
+
* `onPaidError`. Without it, a throwing `onFailed` is swallowed silently. Its own throws are
|
|
6590
|
+
* also swallowed (it can never break a request).
|
|
6591
|
+
*/
|
|
6592
|
+
onFailedError?: (error: unknown, failure: FailedPayment) => void;
|
|
6593
|
+
/**
|
|
6594
|
+
* Await `onFailed` before the 402 rejection is returned (mirror of `awaitOnPaid`), so
|
|
6595
|
+
* "failure recorded" is guaranteed before the caller is told. Default `false` (fire-and-forget).
|
|
6596
|
+
* A rejection inside the hook is still isolated via `onFailedError`.
|
|
6597
|
+
*/
|
|
6598
|
+
awaitOnFailed?: boolean;
|
|
6538
6599
|
/**
|
|
6539
6600
|
* ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
|
|
6540
6601
|
* gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
|
|
@@ -6639,15 +6700,6 @@ interface PaymentGate {
|
|
|
6639
6700
|
*/
|
|
6640
6701
|
landingPage(challenge: X402Challenge): string;
|
|
6641
6702
|
}
|
|
6642
|
-
/**
|
|
6643
|
-
* Framework-agnostic core. Build one gate per gated resource and reuse it
|
|
6644
|
-
* — its in-memory used-tx set is what stops the same proof being redeemed
|
|
6645
|
-
* twice. Wrap it for Express with `requirePayment`, or call it directly
|
|
6646
|
-
* from Hono / Fastify / Adonis / Workers / etc.
|
|
6647
|
-
*
|
|
6648
|
-
* The chain's driver is resolved lazily on first `challenge()`/`verify()`,
|
|
6649
|
-
* which is what lets Solana (and future families) auto-mount with no setup.
|
|
6650
|
-
*/
|
|
6651
6703
|
declare function createPaymentGate(options: RequirePaymentOptions): PaymentGate;
|
|
6652
6704
|
interface ExpressLikeRequest {
|
|
6653
6705
|
headers: Record<string, string | string[] | undefined>;
|
|
@@ -7527,4 +7579,4 @@ declare const PERMIT2_WITNESS_TYPES: {
|
|
|
7527
7579
|
*/
|
|
7528
7580
|
declare function renderLandingPage(sd: SelfDescription): string;
|
|
7529
7581
|
|
|
7530
|
-
export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, BRAND, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySort, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, type FacilitatorSupportedKind, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, KNOWN_FACILITATORS, type KnownFacilitator, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, MultiChainPayer, type MultiChainPayerOptions, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, POWERED_BY, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, type PayingClient, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SelfDescribeEndpoint, type SelfDescribeRail, type SelfDescription, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, appendKeywords, buildBazaarExtension, buildChallengeHeader, buildEndpointInfo, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, fetchAcross, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, rankResources, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, scoreResource, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
|
7582
|
+
export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, BRAND, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySort, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, type FacilitatorSupportedKind, type FailedPayment, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, KNOWN_FACILITATORS, type KnownFacilitator, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, MultiChainPayer, type MultiChainPayerOptions, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, POWERED_BY, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, type PayingClient, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SelfDescribeEndpoint, type SelfDescribeRail, type SelfDescription, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, appendKeywords, buildBazaarExtension, buildChallengeHeader, buildEndpointInfo, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, fetchAcross, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, rankResources, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, scoreResource, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
package/dist/index.d.ts
CHANGED
|
@@ -5044,6 +5044,14 @@ type PipRailEvent = {
|
|
|
5044
5044
|
} | {
|
|
5045
5045
|
kind: 'payment-failed';
|
|
5046
5046
|
reason: string;
|
|
5047
|
+
/** A machine-readable failure code when one is known. For a SERVER rejection it's the SAME
|
|
5048
|
+
* code the merchant's `onFailed` hook receives (a canonical {@link VerifyErrorCode} from a
|
|
5049
|
+
* PipRail gate, or a foreign facilitator's reason string); for a pre-send client DECLINE
|
|
5050
|
+
* (policy / budget / approval) it's that decline reason (e.g. `'BUDGET'`, `'APPROVAL'`).
|
|
5051
|
+
* Absent when no structured code was given. */
|
|
5052
|
+
code?: string;
|
|
5053
|
+
/** Human-readable detail, when present (e.g. `"Paid 40000, required 500000."`). */
|
|
5054
|
+
detail?: string;
|
|
5047
5055
|
};
|
|
5048
5056
|
/**
|
|
5049
5057
|
* Wallet for the chosen chain family. **One field, every chain: `{ key }`** — the
|
|
@@ -6463,6 +6471,28 @@ interface ExactRailOption {
|
|
|
6463
6471
|
* "just works". Force `'eip3009'` or `'permit2'` to pin one. Ignored on Solana (always SVM). */
|
|
6464
6472
|
method?: 'eip3009' | 'permit2' | 'auto';
|
|
6465
6473
|
}
|
|
6474
|
+
/**
|
|
6475
|
+
* The merchant-side mirror of {@link PaidReceipt}: what an {@link RequirePaymentOptions.onFailed}
|
|
6476
|
+
* hook receives when a SUBMITTED payment proof is REJECTED. It carries the SAME machine-readable
|
|
6477
|
+
* `code` the buyer's client is given for that rejection, so both sides are notified of one
|
|
6478
|
+
* consistent reason. (A rejection has no settlement, so — unlike a receipt — there is no tx hash
|
|
6479
|
+
* or settled amount to report.)
|
|
6480
|
+
*/
|
|
6481
|
+
interface FailedPayment {
|
|
6482
|
+
/** The canonical rejection reason — the same {@link VerifyErrorCode} surfaced to the buyer
|
|
6483
|
+
* (e.g. `amount_too_low`, `payment_expired`, `tx_already_used`, `transfer_not_found`). */
|
|
6484
|
+
code: VerifyErrorCode;
|
|
6485
|
+
/** Human-readable detail, e.g. `"Paid 40000, required 500000."`. */
|
|
6486
|
+
detail: string;
|
|
6487
|
+
/**
|
|
6488
|
+
* `true` for a **transient** rejection (`tx_not_found` / `insufficient_confirmations`): the proof
|
|
6489
|
+
* may still be settling and the buyer's client **retries automatically** — you'll get `onPaid` if
|
|
6490
|
+
* it then succeeds. `false` for a **definitive** rejection the buyer must fix (wrong amount,
|
|
6491
|
+
* expired, replayed, bad signature, wrong recipient). Alert on `!transient` to avoid false alarms
|
|
6492
|
+
* on normal RPC lag; nothing is hidden — every rejected attempt still fires `onFailed`.
|
|
6493
|
+
*/
|
|
6494
|
+
transient: boolean;
|
|
6495
|
+
}
|
|
6466
6496
|
interface RequirePaymentOptions {
|
|
6467
6497
|
/**
|
|
6468
6498
|
* Single-chain form: which chain to accept payment on. EVM ('bnb'|'base'|…),
|
|
@@ -6535,6 +6565,37 @@ interface RequirePaymentOptions {
|
|
|
6535
6565
|
* via `onPaidError`; it never turns a settled payment into a 402.
|
|
6536
6566
|
*/
|
|
6537
6567
|
awaitOnPaid?: boolean;
|
|
6568
|
+
/**
|
|
6569
|
+
* The merchant-side mirror of `onPaid`: fired when a SUBMITTED payment proof is REJECTED — a
|
|
6570
|
+
* `kind:'invalid'` verdict (wrong amount, expired, replayed, unknown asset, …). Receives a
|
|
6571
|
+
* {@link FailedPayment} carrying the SAME machine `code` the buyer's client is given, so the
|
|
6572
|
+
* merchant and the buyer are notified of the same failure with the same reason.
|
|
6573
|
+
*
|
|
6574
|
+
* Fires ONLY on a rejected attempt — NOT on a normal first-request 402 `challenge` (no proof
|
|
6575
|
+
* yet), and NOT on a transient/settlement error that throws (an RPC blip, or a 5xx
|
|
6576
|
+
* `SettlementError`): those aren't payment verdicts. Like `onPaid`, it may be **sync or async**
|
|
6577
|
+
* and is fully isolated — a throw OR a rejected promise is caught and routed to `onFailedError`,
|
|
6578
|
+
* so it can never break the request or crash the process. Fire-and-forget by default; set
|
|
6579
|
+
* `awaitOnFailed` to run it before the 402 is returned.
|
|
6580
|
+
*
|
|
6581
|
+
* NOTE: a failure the merchant never receives a request for — the buyer can't afford it, an
|
|
6582
|
+
* `onBeforePay`/`policy` declines it, or the buyer abandons before paying — cannot reach a
|
|
6583
|
+
* backendless gate (only the buyer's client sees it). `onFailed` covers every rejection that
|
|
6584
|
+
* DOES reach the gate.
|
|
6585
|
+
*/
|
|
6586
|
+
onFailed?: (failure: FailedPayment) => void | Promise<void>;
|
|
6587
|
+
/**
|
|
6588
|
+
* Observe a failure inside `onFailed` (sync throw or async rejection) — the mirror of
|
|
6589
|
+
* `onPaidError`. Without it, a throwing `onFailed` is swallowed silently. Its own throws are
|
|
6590
|
+
* also swallowed (it can never break a request).
|
|
6591
|
+
*/
|
|
6592
|
+
onFailedError?: (error: unknown, failure: FailedPayment) => void;
|
|
6593
|
+
/**
|
|
6594
|
+
* Await `onFailed` before the 402 rejection is returned (mirror of `awaitOnPaid`), so
|
|
6595
|
+
* "failure recorded" is guaranteed before the caller is told. Default `false` (fire-and-forget).
|
|
6596
|
+
* A rejection inside the hook is still isolated via `onFailedError`.
|
|
6597
|
+
*/
|
|
6598
|
+
awaitOnFailed?: boolean;
|
|
6538
6599
|
/**
|
|
6539
6600
|
* ALSO advertise a standard x402 `exact` rail so any standard x402 client can pay this
|
|
6540
6601
|
* gate — opt-in, EVM (EIP-3009/Permit2) + Solana (SVM). See {@link ExactRailOption}.
|
|
@@ -6639,15 +6700,6 @@ interface PaymentGate {
|
|
|
6639
6700
|
*/
|
|
6640
6701
|
landingPage(challenge: X402Challenge): string;
|
|
6641
6702
|
}
|
|
6642
|
-
/**
|
|
6643
|
-
* Framework-agnostic core. Build one gate per gated resource and reuse it
|
|
6644
|
-
* — its in-memory used-tx set is what stops the same proof being redeemed
|
|
6645
|
-
* twice. Wrap it for Express with `requirePayment`, or call it directly
|
|
6646
|
-
* from Hono / Fastify / Adonis / Workers / etc.
|
|
6647
|
-
*
|
|
6648
|
-
* The chain's driver is resolved lazily on first `challenge()`/`verify()`,
|
|
6649
|
-
* which is what lets Solana (and future families) auto-mount with no setup.
|
|
6650
|
-
*/
|
|
6651
6703
|
declare function createPaymentGate(options: RequirePaymentOptions): PaymentGate;
|
|
6652
6704
|
interface ExpressLikeRequest {
|
|
6653
6705
|
headers: Record<string, string | string[] | undefined>;
|
|
@@ -7527,4 +7579,4 @@ declare const PERMIT2_WITNESS_TYPES: {
|
|
|
7527
7579
|
*/
|
|
7528
7580
|
declare function renderLandingPage(sd: SelfDescription): string;
|
|
7529
7581
|
|
|
7530
|
-
export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, BRAND, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySort, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, type FacilitatorSupportedKind, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, KNOWN_FACILITATORS, type KnownFacilitator, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, MultiChainPayer, type MultiChainPayerOptions, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, POWERED_BY, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, type PayingClient, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SelfDescribeEndpoint, type SelfDescribeRail, type SelfDescription, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, appendKeywords, buildBazaarExtension, buildChallengeHeader, buildEndpointInfo, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, fetchAcross, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, rankResources, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, scoreResource, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
|
7582
|
+
export { type AcceptOption, type AddressId, type AgentTool, type AlgorandToken, type AptosToken, type AssetId, BRAND, type BazaarExtension, type BuildExactParams, CHAINS, type Caip2, type ChainFamily, type ChainInput, type ChainName, type ChainPreset, type ChainSelector, type ChallengeTriage, type ChallengeVerdict, type ConfirmInfo, ConfirmationTimeoutError, type CostEstimate, DIRECTORY_INFO, type DeclineReasonCode, type DeliverAttempt, type DeliverReceiptOptions, type DeliverResult, type DirectoryInfo, type DiscoverOptions, type DiscoveredRail, type DiscoveredResource, type DiscoveryDescriptor, type DiscoverySigner, type DiscoverySort, type DiscoverySource, type DomainClaim, type DomainVerification, EIP3009_TYPES, EXACT_NETWORK_SLUGS, type EvmToken, type ExactAccept, type ExactAuthorization, type ExactAuthorizationWire, type ExactPaymentPayload, type ExactPaymentPayloadAny, type ExactRailOption, type ExpressLikeMiddleware, type ExpressLikeNext, type ExpressLikeRequest, type ExpressLikeResponse, type FacilitatorConfig, type FacilitatorPaymentRequirements, type FacilitatorSupportedKind, type FailedPayment, GENERATOR, HEADER_REQUIRED, HEADER_RESPONSE, HEADER_RESPONSE_V1, HEADER_SIGNATURE, HEADER_SIGNATURE_V1, InsufficientFundsError, InvalidEnvelopeError, KNOWN_FACILITATORS, type KnownFacilitator, type ListingVisibility, type ManifestInput, MaxRetriesExceededError, MissingDriverError, MultiChainPayer, type MultiChainPayerOptions, type NearToken, NoCompatibleAcceptError, NonReplayableBodyError, type OpenApiDocument, type OpenApiOperation, PERMIT2_ADDRESS, PERMIT2_PROXY_CHAIN_IDS, PERMIT2_WITNESS_TYPES, PIPRAIL_AGENT_GUIDE, POWERED_BY, type PaidReceipt, type ParsedExactPayment, type PayBlocker, type PayOption, type PayWarning, type PayingClient, PaymentDeclinedError, type PaymentDriver, type PaymentGate, type PaymentIntent, type PaymentPlan, type PaymentPolicy, type PaymentRail, type PaymentScheme, PaymentTimeoutError, type Permit2Authorization, type Permit2PaymentPayload, PipRailClient, type PipRailClientOptions, type PipRailCostQuote, PipRailError, type PipRailEvent, type PipRailQuote, type PolicyDecision, type PolicyDenyCode, REGISTER_ATTRIBUTION, RecipientNotReadyError, type RecipientReason, type RegisterInput, type RegisterOptions, type RegisterOutcome, type RequirePaymentOptions, type ResolveOptions, type ResolvedChain, type ResolvedNetwork, type ResolvedToken, type ResourceDescription, type SearchOpenIndexesOptions, type SelfDescribeEndpoint, type SelfDescribeRail, type SelfDescription, type SessionBudget, type SettleOutcome, type SettleViaFacilitatorInput, SettlementError, type SolanaToken, type SpendAssetTotal, type SpendRecord, type SpendRemaining, type SpendSummary, type StellarToken, type SuiToken, type TokenInfo, type TokenInput, type TonToken, type ToolAnnotations, type TronToken, UnknownTokenError, UnsupportedNetworkError, UnsupportedSchemeError, type VerifyErrorCode, type VerifyPaymentResult, type VerifyResult, type WalletBalance, type WalletHandle, type WalletInput, WalletRequiredError, type WellKnownX402, WrongChainError, WrongFamilyError, type X402AcceptEntry, type X402AnyAccept, type X402Challenge, type X402DnsRecord, type X402ExactAcceptEntry, type X402InvalidBody, type X402PaymentSignature, type X402Receipt, type X402ResourceObject, X402_EXACT_PERMIT2_PROXY, type XrplToken, agentGuide, appendAttribution, appendKeywords, buildBazaarExtension, buildChallengeHeader, buildEndpointInfo, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, fetchAcross, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, rankResources, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, scoreResource, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
package/dist/index.js
CHANGED
|
@@ -3212,7 +3212,9 @@ var PipRailClient = class {
|
|
|
3212
3212
|
if (autoRoute) {
|
|
3213
3213
|
const plan = await this.planFromChallenge(net, wallet, challenge, url, schemes);
|
|
3214
3214
|
if (!plan.best) {
|
|
3215
|
-
|
|
3215
|
+
const reason = plan.fundingHint ?? "No rail is settleable for this payment.";
|
|
3216
|
+
this.safeEmit({ kind: "payment-failed", reason });
|
|
3217
|
+
throw new PaymentDeclinedError(reason);
|
|
3216
3218
|
}
|
|
3217
3219
|
accept = plan.best.accept;
|
|
3218
3220
|
quote = plan.best.quote;
|
|
@@ -3502,10 +3504,10 @@ var PipRailClient = class {
|
|
|
3502
3504
|
* TERMINAL expiry/approval decline it must not retry) without parsing prose. */
|
|
3503
3505
|
async authorize(quote) {
|
|
3504
3506
|
if (!quote.withinPolicy) {
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
);
|
|
3507
|
+
const reason = `Payment refused by policy: ${quote.policyReason ?? "not allowed"}`;
|
|
3508
|
+
const reasonCode = reasonCodeForPolicy(quote.policyCode);
|
|
3509
|
+
this.safeEmit({ kind: "payment-failed", reason, code: reasonCode });
|
|
3510
|
+
throw new PaymentDeclinedError(reason, { reasonCode });
|
|
3509
3511
|
}
|
|
3510
3512
|
const hook = this.opts.onBeforePay;
|
|
3511
3513
|
if (!hook) return;
|
|
@@ -3513,16 +3515,14 @@ var PipRailClient = class {
|
|
|
3513
3515
|
try {
|
|
3514
3516
|
approved = await hook(quote);
|
|
3515
3517
|
} catch (err) {
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
});
|
|
3518
|
+
const reason = "onBeforePay threw \u2014 refusing to pay.";
|
|
3519
|
+
this.safeEmit({ kind: "payment-failed", reason, code: "APPROVAL" });
|
|
3520
|
+
throw new PaymentDeclinedError(reason, { cause: err, reasonCode: "APPROVAL" });
|
|
3520
3521
|
}
|
|
3521
3522
|
if (!approved) {
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
);
|
|
3523
|
+
const reason = `onBeforePay declined ${quote.amountFormatted} ${quote.symbol ?? ""}`.trimEnd() + ` on ${quote.network}.`;
|
|
3524
|
+
this.safeEmit({ kind: "payment-failed", reason, code: "APPROVAL" });
|
|
3525
|
+
throw new PaymentDeclinedError(reason, { reasonCode: "APPROVAL" });
|
|
3526
3526
|
}
|
|
3527
3527
|
}
|
|
3528
3528
|
/** Record a settled payment in the ledger (true decimals for the running total). */
|
|
@@ -3617,7 +3617,8 @@ var PipRailClient = class {
|
|
|
3617
3617
|
const unconfirmedNote = confirmed ? "" : " (broadcast but NOT locally confirmed \u2014 it may still have settled on-chain)";
|
|
3618
3618
|
this.safeEmit({
|
|
3619
3619
|
kind: "payment-failed",
|
|
3620
|
-
reason: `server returned 402 after broadcasting payment ${ref}${unconfirmedNote} (${why})
|
|
3620
|
+
reason: `server returned 402 after broadcasting payment ${ref}${unconfirmedNote} (${why})`,
|
|
3621
|
+
...lastReason ? { code: lastReason.error, detail: lastReason.detail } : {}
|
|
3621
3622
|
});
|
|
3622
3623
|
throw new MaxRetriesExceededError(
|
|
3623
3624
|
`Server still returned 402 after ${attempts} attempt(s) with on-chain proof ref=${ref}${unconfirmedNote}. Last server rejection: ${why}. Re-verify or re-submit ref=${ref} before retrying \u2014 never re-pay (it would double-spend).`,
|
|
@@ -3652,7 +3653,7 @@ var PipRailClient = class {
|
|
|
3652
3653
|
const headers = new Headers(init?.headers);
|
|
3653
3654
|
headers.set(HEADER_SIGNATURE, buildExactSignatureHeader({ accepted, payload }));
|
|
3654
3655
|
const rejectDefinitive = (why2) => {
|
|
3655
|
-
this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})
|
|
3656
|
+
this.safeEmit({ kind: "payment-failed", reason: `exact: facilitator rejected nonce=${nonce} (${why2})`, code: why2 });
|
|
3656
3657
|
throw new MaxRetriesExceededError(
|
|
3657
3658
|
`exact: the facilitator rejected the payment (${why2}). Fix the cause, then re-present the SAME signed authorization (nonce=${nonce}) \u2014 do NOT re-sign a fresh nonce. ref=${nonce}.`,
|
|
3658
3659
|
{ ref: nonce }
|
|
@@ -3707,7 +3708,8 @@ var PipRailClient = class {
|
|
|
3707
3708
|
const why = lastReason ? `${lastReason.error}${lastReason.detail ? ` \u2014 ${lastReason.detail}` : ""}` : "server gave no reason";
|
|
3708
3709
|
this.safeEmit({
|
|
3709
3710
|
kind: "payment-failed",
|
|
3710
|
-
reason: `exact: 402 after submitting authorization nonce=${nonce} (${why})
|
|
3711
|
+
reason: `exact: 402 after submitting authorization nonce=${nonce} (${why})`,
|
|
3712
|
+
...lastReason ? { code: lastReason.error, detail: lastReason.detail } : {}
|
|
3711
3713
|
});
|
|
3712
3714
|
throw new MaxRetriesExceededError(
|
|
3713
3715
|
`exact: server still returned 402 after submitting the signed authorization (nonce=${nonce}). Last rejection: ${why}. Re-present the SAME authorization \u2014 do NOT re-sign a fresh nonce; verify authorizationState(${payerFrom}, ${nonce}) first. ref=${nonce}.`,
|
|
@@ -4962,6 +4964,20 @@ var KNOWN_FACILITATORS = {
|
|
|
4962
4964
|
schemes: ["exact"],
|
|
4963
4965
|
settles: ["eip3009"],
|
|
4964
4966
|
note: "GoPlausible \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-17 (tx 0x9bcbc1f01fe1fd1aed2a79e5582555164cc4185e9800189df378a6b46eb9c59e). 2nd GoPlausible validation chain (after Algorand)."
|
|
4967
|
+
},
|
|
4968
|
+
{
|
|
4969
|
+
url: "https://facilitator.cascade.fyi",
|
|
4970
|
+
keyless: true,
|
|
4971
|
+
schemes: ["exact"],
|
|
4972
|
+
settles: ["eip3009"],
|
|
4973
|
+
note: "Cascade \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-18 (tx 0x2e784725f3c66e170720cc8df43a9248f02ad80914fcbe05dadbdbe411c3b52b)."
|
|
4974
|
+
},
|
|
4975
|
+
{
|
|
4976
|
+
url: "https://facilitator.bitcoinsapi.com",
|
|
4977
|
+
keyless: true,
|
|
4978
|
+
schemes: ["exact"],
|
|
4979
|
+
settles: ["eip3009"],
|
|
4980
|
+
note: "Satoshi (bitcoinsapi) \u2014 keyless, sponsors gas (Base USDC EIP-3009). LIVE-settled on Base 2026-06-18 (tx 0xbb2b6c354b12c6d07cce91e2b337e38327d4b0833bbd1dfbeda3ad495b67b819)."
|
|
4965
4981
|
}
|
|
4966
4982
|
],
|
|
4967
4983
|
// Monad (eip155:143). Corbits + Ultravioleta DAO each keyless-settle the EVM EIP-3009 exact rail
|
|
@@ -5024,6 +5040,139 @@ var KNOWN_FACILITATORS = {
|
|
|
5024
5040
|
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (HyperEVM native USDC EIP-3009). LIVE-settled on HyperEVM 2026-06-17 (tx 0x56af8148a92a291f0ce362e250919f7742074e5464ac0f315ad68abaec93bd0a)."
|
|
5025
5041
|
}
|
|
5026
5042
|
],
|
|
5043
|
+
// ── gasless-extension (2026-06-18): 7 more EVM mainnets, each LIVE-settled by us — a real EIP-3009
|
|
5044
|
+
// exact payment with the buyer holding ZERO native (so PROVABLY gasless), funded self-service by
|
|
5045
|
+
// bridging USDC in (LI.FI/Eco/Relay), never a /supported read. Dexter enforces a ~$0.004 dynamic
|
|
5046
|
+
// floor on some chains (we cleared it at $0.005); UVD has no floor (100%-sponsors) but its sponsor
|
|
5047
|
+
// contract is NOT deployed on every chain it advertises (Avalanche/Celo/Scroll → contract_call_failed,
|
|
5048
|
+
// left UNSEEDED — the NEAR lesson: advertising ≠ settling).
|
|
5049
|
+
// Ethereum L1 (eip155:1) — UVD (no floor, sponsors L1 gas). 2nd keyless ETH option vs Primev, which
|
|
5050
|
+
// rejected PipRail's exact as unsupported_scheme.
|
|
5051
|
+
"eip155:1": [
|
|
5052
|
+
{
|
|
5053
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5054
|
+
keyless: true,
|
|
5055
|
+
schemes: ["exact"],
|
|
5056
|
+
settles: ["eip3009"],
|
|
5057
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (Ethereum native USDC EIP-3009; no settlement floor). LIVE-settled on Ethereum mainnet 2026-06-18 (tx 0x7fcc3cafdf43c52888f0117cde633eff35371f832dfb8dbd84e6fd7704ae5cfc)."
|
|
5058
|
+
}
|
|
5059
|
+
],
|
|
5060
|
+
// Polygon PoS (eip155:137) — FIVE keyless facilitators (the broadest after Base), all live-settled.
|
|
5061
|
+
"eip155:137": [
|
|
5062
|
+
{
|
|
5063
|
+
url: "https://facilitator.payai.network",
|
|
5064
|
+
keyless: true,
|
|
5065
|
+
schemes: ["exact"],
|
|
5066
|
+
settles: ["eip3009"],
|
|
5067
|
+
note: "PayAI \u2014 keyless, sponsors gas (Polygon native USDC EIP-3009). LIVE-settled on Polygon 2026-06-18 (tx 0xb37630871504618c4db35e8ef0edd3c99deac102b143b7e5eb738c03fb13a619)."
|
|
5068
|
+
},
|
|
5069
|
+
{
|
|
5070
|
+
url: "https://x402.polygon.technology",
|
|
5071
|
+
keyless: true,
|
|
5072
|
+
schemes: ["exact"],
|
|
5073
|
+
settles: ["eip3009"],
|
|
5074
|
+
note: "Polygon Labs (the official Polygon facilitator) \u2014 keyless, sponsors gas. LIVE-settled on Polygon 2026-06-18 (tx 0x6a8ca60ea111959a61a85ad6c4f2ed99e192052ec7b9ffb59baa33691a86f419)."
|
|
5075
|
+
},
|
|
5076
|
+
{
|
|
5077
|
+
url: "https://facilitator.corbits.dev",
|
|
5078
|
+
keyless: true,
|
|
5079
|
+
schemes: ["exact"],
|
|
5080
|
+
settles: ["eip3009"],
|
|
5081
|
+
note: "Corbits (Faremeter) \u2014 keyless, sponsors gas. LIVE-settled on Polygon 2026-06-18 (tx 0x2fadca2ee3cd7fcff32d015e8b06109de6b747d7cd0f80dcd8fb022b2d58d008)."
|
|
5082
|
+
},
|
|
5083
|
+
{
|
|
5084
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5085
|
+
keyless: true,
|
|
5086
|
+
schemes: ["exact"],
|
|
5087
|
+
settles: ["eip3009"],
|
|
5088
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Polygon 2026-06-18 (tx 0x0922cd78f3b7c35cc294cf0c0d9b7609e6596eb337be0d197fa5fc511eacba34)."
|
|
5089
|
+
},
|
|
5090
|
+
{
|
|
5091
|
+
url: "https://x402.dexter.cash",
|
|
5092
|
+
keyless: true,
|
|
5093
|
+
schemes: ["exact"],
|
|
5094
|
+
settles: ["eip3009"],
|
|
5095
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic settlement floor (sub-floor \u2192 amount_too_low). LIVE-settled on Polygon 2026-06-18 at $0.005 (tx 0x4a7cfc96e4e2496652435c77b0021db4a459e7bcd97948c15e6bea09077c164b)."
|
|
5096
|
+
}
|
|
5097
|
+
],
|
|
5098
|
+
// Arbitrum One (eip155:42161) — PayAI + UVD + Dexter.
|
|
5099
|
+
"eip155:42161": [
|
|
5100
|
+
{
|
|
5101
|
+
url: "https://facilitator.payai.network",
|
|
5102
|
+
keyless: true,
|
|
5103
|
+
schemes: ["exact"],
|
|
5104
|
+
settles: ["eip3009"],
|
|
5105
|
+
note: "PayAI \u2014 keyless, sponsors gas (Arbitrum native USDC EIP-3009). LIVE-settled on Arbitrum 2026-06-18 (tx 0x4c7fbfb37ef087bb9bc8872b1b2b0b83ea7ca4cf6b50bc883e51f9b85be61199)."
|
|
5106
|
+
},
|
|
5107
|
+
{
|
|
5108
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5109
|
+
keyless: true,
|
|
5110
|
+
schemes: ["exact"],
|
|
5111
|
+
settles: ["eip3009"],
|
|
5112
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Arbitrum 2026-06-18 (tx 0xd1fa7e6ffbd59502bba5809ce181d7936749b93a45c8c2277b4f6d8638326dec)."
|
|
5113
|
+
},
|
|
5114
|
+
{
|
|
5115
|
+
url: "https://x402.dexter.cash",
|
|
5116
|
+
keyless: true,
|
|
5117
|
+
schemes: ["exact"],
|
|
5118
|
+
settles: ["eip3009"],
|
|
5119
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic floor. LIVE-settled on Arbitrum 2026-06-18 at $0.005 (tx 0x0993d51b3859dfd7e8d5b2af709ce64f01ff34ed14bfef665dbb340e7373d599)."
|
|
5120
|
+
}
|
|
5121
|
+
],
|
|
5122
|
+
// Optimism (eip155:10) — Dexter + UVD.
|
|
5123
|
+
"eip155:10": [
|
|
5124
|
+
{
|
|
5125
|
+
url: "https://x402.dexter.cash",
|
|
5126
|
+
keyless: true,
|
|
5127
|
+
schemes: ["exact"],
|
|
5128
|
+
settles: ["eip3009"],
|
|
5129
|
+
note: "Dexter \u2014 keyless, sponsors gas; ~$0.004 dynamic floor. LIVE-settled on Optimism 2026-06-18 at $0.005 (tx 0x98b282a5dd698054eff4900ff8547170d4f7605b51140c55147fc5ae485f81da)."
|
|
5130
|
+
},
|
|
5131
|
+
{
|
|
5132
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5133
|
+
keyless: true,
|
|
5134
|
+
schemes: ["exact"],
|
|
5135
|
+
settles: ["eip3009"],
|
|
5136
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored. LIVE-settled on Optimism 2026-06-18 (tx 0xdd91dfdf12bed2d196e37fa1dea9a0da71a97e3c39a0ec0ed114be6a9af22837)."
|
|
5137
|
+
}
|
|
5138
|
+
],
|
|
5139
|
+
// Avalanche C-Chain (eip155:43114) — PayAI + Dexter. (UVD advertises it but contract_call_failed → unseeded.)
|
|
5140
|
+
"eip155:43114": [
|
|
5141
|
+
{
|
|
5142
|
+
url: "https://facilitator.payai.network",
|
|
5143
|
+
keyless: true,
|
|
5144
|
+
schemes: ["exact"],
|
|
5145
|
+
settles: ["eip3009"],
|
|
5146
|
+
note: "PayAI \u2014 keyless, sponsors gas (Avalanche native USDC EIP-3009). LIVE-settled on Avalanche 2026-06-18 (tx 0x6a1307bc48a157de236ea03440ea2cb6ad4f27e22dd9c89a4345b4c3edb270c7)."
|
|
5147
|
+
},
|
|
5148
|
+
{
|
|
5149
|
+
url: "https://x402.dexter.cash",
|
|
5150
|
+
keyless: true,
|
|
5151
|
+
schemes: ["exact"],
|
|
5152
|
+
settles: ["eip3009"],
|
|
5153
|
+
note: "Dexter \u2014 keyless, sponsors gas (no floor hit at $0.001 here). LIVE-settled on Avalanche 2026-06-18 (tx 0xb2263e9a4ea3917eee6acabcb454d42a50264fdd69a0781ed8fcaec5590e264b)."
|
|
5154
|
+
}
|
|
5155
|
+
],
|
|
5156
|
+
// Sei (eip155:1329) — PayAI (the only keyless facilitator that lists Sei).
|
|
5157
|
+
"eip155:1329": [
|
|
5158
|
+
{
|
|
5159
|
+
url: "https://facilitator.payai.network",
|
|
5160
|
+
keyless: true,
|
|
5161
|
+
schemes: ["exact"],
|
|
5162
|
+
settles: ["eip3009"],
|
|
5163
|
+
note: "PayAI \u2014 keyless, sponsors gas (Sei native USDC EIP-3009). LIVE-settled on Sei 2026-06-18 (tx 0xde63679b64749cb59625527e5c7682503c851d66b9b8b8ba217ed7b099461312)."
|
|
5164
|
+
}
|
|
5165
|
+
],
|
|
5166
|
+
// Unichain (eip155:130) — UVD (the only keyless facilitator that lists Unichain).
|
|
5167
|
+
"eip155:130": [
|
|
5168
|
+
{
|
|
5169
|
+
url: "https://facilitator.ultravioletadao.xyz",
|
|
5170
|
+
keyless: true,
|
|
5171
|
+
schemes: ["exact"],
|
|
5172
|
+
settles: ["eip3009"],
|
|
5173
|
+
note: "Ultravioleta DAO \u2014 keyless, 100% gas-sponsored (Unichain native USDC EIP-3009). LIVE-settled on Unichain 2026-06-18 (tx 0xf8bf4a9a200f24159ac67c6315c30b194257999d7da21471b39d5f2cc32b2236)."
|
|
5174
|
+
}
|
|
5175
|
+
],
|
|
5027
5176
|
// Algorand (mainnet, CAIP-2 = full base64 genesis hash). GoPlausible keyless-settles the ratified
|
|
5028
5177
|
// x402 Algorand `exact` rail (atomic-group fee pooling): its sponsor pools the whole group fee and
|
|
5029
5178
|
// submits, so NEITHER the buyer NOR the merchant pays ALGO — both-sides gasless. LIVE-settled by us
|
|
@@ -5113,6 +5262,7 @@ function normaliseExactOption(exact) {
|
|
|
5113
5262
|
if (exact === true) return { settle: "keyless" };
|
|
5114
5263
|
return exact;
|
|
5115
5264
|
}
|
|
5265
|
+
var TRANSIENT_VERIFY_CODES = /* @__PURE__ */ new Set(["tx_not_found", "insufficient_confirmations"]);
|
|
5116
5266
|
function createPaymentGate(options) {
|
|
5117
5267
|
const minConfirmations = options.minConfirmations ?? 1;
|
|
5118
5268
|
const maxTimeoutSeconds = options.maxTimeoutSeconds ?? 600;
|
|
@@ -5397,6 +5547,32 @@ function createPaymentGate(options) {
|
|
|
5397
5547
|
if (options.awaitOnPaid) await fireOnPaid(paid);
|
|
5398
5548
|
else void fireOnPaid(paid);
|
|
5399
5549
|
}
|
|
5550
|
+
function reportOnFailedError(error, failure) {
|
|
5551
|
+
if (!options.onFailedError) return;
|
|
5552
|
+
try {
|
|
5553
|
+
options.onFailedError(error, failure);
|
|
5554
|
+
} catch {
|
|
5555
|
+
}
|
|
5556
|
+
}
|
|
5557
|
+
function fireOnFailed(failure) {
|
|
5558
|
+
if (!options.onFailed) return;
|
|
5559
|
+
let outcome;
|
|
5560
|
+
try {
|
|
5561
|
+
outcome = options.onFailed(failure);
|
|
5562
|
+
} catch (err) {
|
|
5563
|
+
reportOnFailedError(err, failure);
|
|
5564
|
+
return;
|
|
5565
|
+
}
|
|
5566
|
+
if (outcome != null && typeof outcome.then === "function") {
|
|
5567
|
+
return Promise.resolve(outcome).catch((err) => reportOnFailedError(err, failure));
|
|
5568
|
+
}
|
|
5569
|
+
}
|
|
5570
|
+
async function deliverOnFailed(result) {
|
|
5571
|
+
const code = result.error;
|
|
5572
|
+
const failure = { code, detail: result.detail, transient: TRANSIENT_VERIFY_CODES.has(code) };
|
|
5573
|
+
if (options.awaitOnFailed) await fireOnFailed(failure);
|
|
5574
|
+
else void fireOnFailed(failure);
|
|
5575
|
+
}
|
|
5400
5576
|
async function describe(resourceUrl = "") {
|
|
5401
5577
|
const specs = await ready();
|
|
5402
5578
|
const accepts = [];
|
|
@@ -5560,6 +5736,11 @@ function createPaymentGate(options) {
|
|
|
5560
5736
|
return { kind: "paid", receipt: result.receipt, receiptHeader: buildReceiptHeader(result.receipt) };
|
|
5561
5737
|
}
|
|
5562
5738
|
async function verify(paymentSignature) {
|
|
5739
|
+
const result = await resolveVerdict(paymentSignature);
|
|
5740
|
+
if (result.kind === "invalid") await deliverOnFailed(result);
|
|
5741
|
+
return result;
|
|
5742
|
+
}
|
|
5743
|
+
async function resolveVerdict(paymentSignature) {
|
|
5563
5744
|
const raw = normaliseHeader(paymentSignature);
|
|
5564
5745
|
if (!raw) return asChallenge();
|
|
5565
5746
|
const sig = parseSignatureHeader(raw);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piprail/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.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",
|