@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 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
- throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(_nullishCoalesce(plan.fundingHint, () => ( "No rail is settleable for this payment.")));
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
- throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(
3506
- `Payment refused by policy: ${_nullishCoalesce(quote.policyReason, () => ( "not allowed"))}`,
3507
- { reasonCode: reasonCodeForPolicy(quote.policyCode) }
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
- throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)("onBeforePay threw \u2014 refusing to pay.", {
3517
- cause: err,
3518
- reasonCode: "APPROVAL"
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
- throw new (0, _chunkJG6KRAW6cjs.PaymentDeclinedError)(
3523
- `onBeforePay declined ${quote.amountFormatted} ${_nullishCoalesce(quote.symbol, () => ( ""))}`.trimEnd() + ` on ${quote.network}.`,
3524
- { reasonCode: "APPROVAL" }
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 (e38) {
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 (e39) {
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 (e40) {
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 (e41) {
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 (e42) {
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 (e43) {
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
- throw new PaymentDeclinedError(plan.fundingHint ?? "No rail is settleable for this payment.");
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
- throw new PaymentDeclinedError(
3506
- `Payment refused by policy: ${quote.policyReason ?? "not allowed"}`,
3507
- { reasonCode: reasonCodeForPolicy(quote.policyCode) }
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
- throw new PaymentDeclinedError("onBeforePay threw \u2014 refusing to pay.", {
3517
- cause: err,
3518
- reasonCode: "APPROVAL"
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
- throw new PaymentDeclinedError(
3523
- `onBeforePay declined ${quote.amountFormatted} ${quote.symbol ?? ""}`.trimEnd() + ` on ${quote.network}.`,
3524
- { reasonCode: "APPROVAL" }
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.5.0",
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",