@piprail/sdk 1.23.0 → 1.25.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,67 @@ 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
+ ## [1.25.0] — 2026-06-15 — more keyless facilitators (live-verified)
8
+
9
+ ### Added — `KNOWN_FACILITATORS` grows beyond PayAI
10
+
11
+ - **`KNOWN_FACILITATORS`** now seeds three more keyless facilitators, each **live-settled** on mainnet
12
+ through PipRail (a real `exact` payment, buyer paid zero gas), not just read from `/supported`:
13
+ **xpay** on Base (EIP-3009), and **OpenFacilitator** + **Corbits** on Solana (SVM). So
14
+ `firstKeylessFacilitator()` / the `exact: true` shorthand can resolve a keyless sponsor on more
15
+ networks, with redundancy beyond PayAI.
16
+ - **Daydreams** and **Questflow** are deliberately **not** seeded: their `/supported` is public but
17
+ `/verify` returns `401` (an API key is required), so they aren't keyless for *settlement*. A public
18
+ `/supported` is not proof of keyless settlement — every seeded entry carries a dated, live-verified note.
19
+
20
+ ### Notes
21
+
22
+ - Pure data + comments; **no behaviour change** to existing rails or defaults. The full, current provider
23
+ matrix (incl. key-required and self-host options, with on-chain tx proofs) lives in the docs:
24
+ [Facilitator coverage](https://docs.piprail.com/accepting-payments/facilitator-coverage/).
25
+
26
+ ## [1.24.0] — 2026-06-14 — multi-chain buying (one buyer, a wallet per chain)
27
+
28
+ ### Added — pay a 402 on whichever chain it asks for
29
+
30
+ - **`MultiChainPayer`** — a `PipRailClient` is bound to ONE chain + ONE wallet (an EVM key can't sign a
31
+ Solana tx). `MultiChainPayer.fromWallets({ wallets: { base: { privateKey }, solana: { secretKey }, … }, policy })`
32
+ carries one wallet per chain and exposes a single `fetch`/`get`/`post`/`planPayment`/`canAfford`/`quote`/
33
+ `discover`/`register`/`spent`/`budget`. On a 402 it surveys every funded chain and pays the FIRST chain
34
+ you listed that can actually settle — through each client's own spend policy, `onBeforePay`, retries, and
35
+ replay-protection. No price oracle, no backend, no custody; across coins the order you list the chains is
36
+ the preference (within a chain, the cheapest-gas rail). Also `new MultiChainPayer([...clients])` for full
37
+ control (e.g. custom-EVM viem `Chain`s). `schemes` (incl. the gasless `exact` rail) propagates to every
38
+ chain's client.
39
+ - **`fetchAcross(clients, url, init?)`** — the EXECUTION counterpart to `planAcross`: plan across an array
40
+ of single-chain clients and pay, on its owning client, the rail `planAcross` reports as `best` (the first
41
+ funded chain that can settle). Throws `PaymentDeclinedError` with a merged, per-chain funding hint when
42
+ no chain can settle.
43
+ - **`PayingClient`** — the shared read-+-pay interface `paymentTools` now accepts; both `PipRailClient` and
44
+ `MultiChainPayer` satisfy it, so the agent toolkit (and the MCP) wrap either unchanged.
45
+ - **`piprail_register` agent tool** gains optional `network` + `asset` params, so a multi-chain agent can
46
+ advertise a listing on a specific chain instead of defaulting to the first wallet's chain.
47
+
48
+ ### Changed
49
+
50
+ - **Cross-chain `best` selection is now your PREFERENCE order, not raw gas magnitude.** `planAcross` /
51
+ `fetchAcross` no longer compare gas fees across different native coins (base units aren't comparable —
52
+ e.g. EVM wei vs Solana lamports — and there's no oracle), which previously let a small-base-unit coin
53
+ win regardless of real cost. They now pay the FIRST chain you list that can settle (within a chain, the
54
+ cheapest-gas rail still wins) — matching the documented contract. Single-chain `PipRailClient` ranking is
55
+ unchanged.
56
+ - **`planAcross` now propagates a TOTAL outage.** If EVERY client fails to reach the resource it throws
57
+ (like a single client) instead of returning `null` — so `canAfford`/`quote` can't report a false
58
+ "affordable"/"not-gated". A single chain being down still just drops that chain.
59
+ - **Clearer multi-chain decline message.** When no funded chain can settle, `planAcross`'s `fundingHint`
60
+ (and the `PaymentDeclinedError` `fetchAcross` throws) now names EVERY funded chain's own blocker — "top up
61
+ X USDC on base · add ~Y POL gas on polygon" — instead of only the first. Chains the 402 never offered are
62
+ dropped as noise when another chain is close; if none of your chains are offered, it says where the 402
63
+ IS payable. Per-rail `blockers`/`warnings` stay machine-readable for agents that branch programmatically.
64
+
65
+ Single-chain `PipRailClient` behaviour is byte-identical. Examples: `examples/multi-chain` (routing + a
66
+ live gasless-`exact` BNB Permit2 settlement through `MultiChainPayer`).
67
+
7
68
  ## [1.23.0] — 2026-06-14 — self-describing endpoints + discovery reach
8
69
 
9
70
  ### Added — self-describing, more discoverable endpoints (discoverability plan: Phases 1, 2, 4, 5)
@@ -1022,6 +1083,8 @@ straight into your wallet. The API is small and self-contained.
1022
1083
  to your wallet; PipRail never holds funds.
1023
1084
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
1024
1085
 
1086
+ [1.25.0]: https://www.npmjs.com/package/@piprail/sdk
1087
+ [1.24.0]: https://www.npmjs.com/package/@piprail/sdk
1025
1088
  [1.15.1]: https://www.npmjs.com/package/@piprail/sdk
1026
1089
  [1.15.0]: https://www.npmjs.com/package/@piprail/sdk
1027
1090
  [1.14.0]: https://www.npmjs.com/package/@piprail/sdk
package/README.md CHANGED
@@ -36,6 +36,33 @@ const client = new PipRailClient({ chain: 'base', wallet: { privateKey: process.
36
36
  const res = await client.fetch('https://api.example.com/report') // hits the 402, pays it, retries with proof
37
37
  ```
38
38
 
39
+ ## Pay across chains — one buyer, a wallet per chain
40
+
41
+ A client is bound to one chain (an EVM key can't sign a Solana tx). To pay a 402
42
+ on **whatever chain it asks for**, give a `MultiChainPayer` one wallet per chain —
43
+ it surveys every chain you hold and pays the **first one you listed** that can settle
44
+ (your preference; within a chain, the cheapest-gas rail — there's no oracle to compare
45
+ gas across coins):
46
+
47
+ ```ts
48
+ import { MultiChainPayer } from '@piprail/sdk'
49
+
50
+ const payer = MultiChainPayer.fromWallets({
51
+ wallets: {
52
+ base: { privateKey: process.env.EVM_KEY }, // one EVM key works on every EVM chain
53
+ solana: { secretKey: process.env.SOLANA_KEY },
54
+ xrpl: { seed: process.env.XRPL_SEED },
55
+ },
56
+ policy: { maxAmount: '1.00', maxTotal: '10.00', tokens: ['USDC', 'USDT'] }, // one budget, every chain
57
+ })
58
+
59
+ await payer.planPayment(url) // read-only: every chain ranked, payable-first in your listed order
60
+ const res = await payer.get(url) // pays on the first chain that can settle — same spend policy, no manual routing
61
+ ```
62
+
63
+ Built on `planAcross` / `fetchAcross` (the same composable primitives, for when you
64
+ already hold an array of clients). See [`examples/multi-chain`](../examples/multi-chain).
65
+
39
66
  The same app can **take** payments and **make** them. → [Making payments](https://docs.piprail.com/making-payments/piprail-client/)
40
67
 
41
68
  ---
@@ -46,7 +73,7 @@ The same app can **take** payments and **make** them. → [Making payments](http
46
73
  |---|---|
47
74
  | **[Getting started](https://docs.piprail.com/getting-started/introduction/)** | Install · quickstart · how it works |
48
75
  | **[Accepting payments](https://docs.piprail.com/accepting-payments/require-payment-and-gate/)** | `requirePayment` · `createPaymentGate` · the `exact` rail |
49
- | **[Making payments](https://docs.piprail.com/making-payments/piprail-client/)** | `PipRailClient` · `quote` · `estimateCost` · `planPayment` · auto-route |
76
+ | **[Making payments](https://docs.piprail.com/making-payments/piprail-client/)** | `PipRailClient` · `quote` · `estimateCost` · `planPayment` · auto-route · `MultiChainPayer` |
50
77
  | **[Spend controls](https://docs.piprail.com/spend-controls/payment-policy/)** | Budgets · time envelope · the spend ledger |
51
78
  | **[Agent toolkit](https://docs.piprail.com/agent-toolkit/payment-tools/)** | `paymentTools` · the agent guide · NL renderers |
52
79
  | **[Discovery](https://docs.piprail.com/discovery/discover-and-register/)** | Find & be found on the open x402 indexes ($0, no backend) |
package/dist/index.cjs CHANGED
@@ -3493,6 +3493,33 @@ function rankOptions(options) {
3493
3493
  return 0;
3494
3494
  });
3495
3495
  }
3496
+ function rankAcross(plans) {
3497
+ const rank = { payable: 0, unknown: 1, blocked: 2 };
3498
+ return plans.flatMap((p) => p.options).sort((a, b) => rank[a.state] - rank[b.state]);
3499
+ }
3500
+ async function planEachClient(clients, url, init) {
3501
+ const settled = await Promise.allSettled(clients.map((c) => c.planPayment(url, init)));
3502
+ const live = [];
3503
+ let anyReached = false;
3504
+ let firstError;
3505
+ settled.forEach((s, i) => {
3506
+ if (s.status === "fulfilled") {
3507
+ anyReached = true;
3508
+ if (s.value != null) live.push({ client: clients[i], plan: s.value });
3509
+ } else if (firstError === void 0) {
3510
+ firstError = s.reason;
3511
+ }
3512
+ });
3513
+ if (live.length === 0 && !anyReached) {
3514
+ throw _nullishCoalesce(firstError, () => ( new Error("planAcross: every client failed to reach the resource.")));
3515
+ }
3516
+ return live;
3517
+ }
3518
+ function mergeDeclineHint(plans) {
3519
+ const actionable = plans.filter((p) => p.options.length > 0 && p.fundingHint).map((p) => p.fundingHint);
3520
+ const chosen = actionable.length ? actionable : plans.map((p) => p.fundingHint).filter(Boolean);
3521
+ return chosen.length ? [...new Set(chosen)].join(" \xB7 ") : null;
3522
+ }
3496
3523
  function buildFundingHint(options, chainLabel) {
3497
3524
  if (options.length === 0) return null;
3498
3525
  const target = [...options].sort((a, b) => a.blockers.length - b.blockers.length)[0];
@@ -3519,10 +3546,10 @@ function buildFundingHint(options, chainLabel) {
3519
3546
  return parts.length ? `Can't settle on ${chainLabel}: ${parts.join(" and ")} (to pay ${target.quote.amountFormatted} ${sym}).` : `Can't settle on ${chainLabel} for ${target.quote.amountFormatted} ${sym}.`;
3520
3547
  }
3521
3548
  async function planAcross(clients, url, init) {
3522
- const plans = await Promise.all(clients.map((c) => c.planPayment(url, init).catch(() => null)));
3523
- const live = plans.filter((p) => p != null);
3549
+ if (clients.length === 0) return null;
3550
+ const live = (await planEachClient(clients, url, init)).map((p) => p.plan);
3524
3551
  if (live.length === 0) return null;
3525
- const options = rankOptions(live.flatMap((p) => p.options));
3552
+ const options = rankAcross(live);
3526
3553
  const best = _nullishCoalesce(options.find((o) => o.state === "payable"), () => ( null));
3527
3554
  const status = best ? "ready" : options.some((o) => o.state === "unknown") ? "unknown" : "blocked";
3528
3555
  return {
@@ -3532,10 +3559,25 @@ async function planAcross(clients, url, init) {
3532
3559
  payable: best !== null,
3533
3560
  best,
3534
3561
  options,
3535
- // First non-null hint across clients each already names its chain.
3536
- fundingHint: best ? null : _nullishCoalesce(live.map((p) => p.fundingHint).find(Boolean), () => ( null))
3562
+ // Merge EVERY funded chain's blocker into one clear sentence (not just the first) —
3563
+ // see mergeDeclineHint. `null` when a rail is payable.
3564
+ fundingHint: best ? null : mergeDeclineHint(live)
3537
3565
  };
3538
3566
  }
3567
+ async function fetchAcross(clients, url, init) {
3568
+ if (clients.length === 0) {
3569
+ throw new TypeError("fetchAcross needs at least one PipRailClient.");
3570
+ }
3571
+ const live = await planEachClient(clients, url, init);
3572
+ if (live.length === 0) return clients[0].fetch(url, init);
3573
+ const best = rankAcross(live.map((p) => p.plan)).find((o) => o.state === "payable");
3574
+ if (!best) {
3575
+ const hint = mergeDeclineHint(live.map((p) => p.plan));
3576
+ throw new (0, _chunkU35MG4TFcjs.PaymentDeclinedError)(hint || "No funded chain can settle this payment right now.");
3577
+ }
3578
+ const owner = live.find((p) => p.plan.options.includes(best)).client;
3579
+ return owner.fetch(url, { ..._nullishCoalesce(init, () => ( {})), autoRoute: true });
3580
+ }
3539
3581
  function railOnNetwork(rail, matches) {
3540
3582
  const n = normalizeNetwork(rail.network);
3541
3583
  return !n.includes(":") || matches(n);
@@ -3601,6 +3643,187 @@ async function readInvalidReason(response) {
3601
3643
  return null;
3602
3644
  }
3603
3645
 
3646
+ // src/payer.ts
3647
+ var MultiChainPayer = class _MultiChainPayer {
3648
+
3649
+ /**
3650
+ * Wrap an explicit, ordered set of single-chain clients — use this when a client
3651
+ * needs full control (e.g. a custom EVM chain configured by a viem `Chain`). The
3652
+ * ORDER is your chain preference: across chains the first that can settle wins. Pass
3653
+ * at MOST one client per chain — two clients on the SAME network would double-count in
3654
+ * `spent()`/`budget()` and waste a plan round-trip (`fromWallets` can't produce this).
3655
+ * For the common case, prefer {@link MultiChainPayer.fromWallets}.
3656
+ */
3657
+ constructor(clients) {
3658
+ if (clients.length === 0) {
3659
+ throw new TypeError("MultiChainPayer needs at least one PipRailClient.");
3660
+ }
3661
+ this._clients = [...clients];
3662
+ }
3663
+ /**
3664
+ * Build one client per funded chain from a `{ chain → wallet }` map — the
3665
+ * ergonomic path. The shared `policy`/`schemes`/`onBeforePay`/`onEvent` apply to
3666
+ * every client; `rpcUrls` are matched per chain. Iteration order of `wallets` is
3667
+ * the chain preference.
3668
+ *
3669
+ * ```ts
3670
+ * const payer = MultiChainPayer.fromWallets({
3671
+ * wallets: {
3672
+ * base: { privateKey: process.env.EVM_KEY! },
3673
+ * solana: { secretKey: process.env.SOLANA_SECRET! },
3674
+ * xrpl: { seed: process.env.XRPL_SEED! },
3675
+ * },
3676
+ * policy: { maxAmount: '1.00', maxTotal: '20.00', tokens: ['USDC', 'USDT'] },
3677
+ * })
3678
+ * const res = await payer.get('https://api.example.com/paid') // pays on the first funded chain that can settle
3679
+ * ```
3680
+ */
3681
+ static fromWallets(opts) {
3682
+ const entries = Object.entries(opts.wallets);
3683
+ if (entries.length === 0) {
3684
+ throw new TypeError("MultiChainPayer.fromWallets needs at least one wallet.");
3685
+ }
3686
+ const clients = entries.map(
3687
+ ([chain, wallet]) => new PipRailClient({
3688
+ chain,
3689
+ wallet,
3690
+ ...opts.policy ? { policy: opts.policy } : {},
3691
+ ...opts.schemes ? { schemes: opts.schemes } : {},
3692
+ ..._optionalChain([opts, 'access', _64 => _64.rpcUrls, 'optionalAccess', _65 => _65[chain]]) ? { rpcUrl: opts.rpcUrls[chain] } : {},
3693
+ ...opts.onBeforePay ? { onBeforePay: opts.onBeforePay } : {},
3694
+ ...opts.onEvent ? { onEvent: opts.onEvent } : {},
3695
+ ...opts.maxPaymentRetries != null ? { maxPaymentRetries: opts.maxPaymentRetries } : {},
3696
+ ...opts.retryTimeoutMs != null ? { retryTimeoutMs: opts.retryTimeoutMs } : {}
3697
+ })
3698
+ );
3699
+ return new _MultiChainPayer(clients);
3700
+ }
3701
+ /** The underlying single-chain clients, in preference order. Reach for one of
3702
+ * these for chain-specific reads (`estimateCost`, `discoverySigner`, per-chain
3703
+ * `budget()`) that don't make sense merged. */
3704
+ get clients() {
3705
+ return this._clients;
3706
+ }
3707
+ /** Plan a 402 across every funded chain — merged + ranked payable-first. `null`
3708
+ * when the URL needs no payment. (Delegates to {@link planAcross}.) */
3709
+ planPayment(url, init) {
3710
+ return planAcross(this._clients, url, init);
3711
+ }
3712
+ /** Can ANY funded chain settle this URL right now? (A free resource is trivially
3713
+ * "affordable".) No funds move. */
3714
+ async canAfford(url, init) {
3715
+ const plan = await this.planPayment(url, init);
3716
+ return plan == null ? true : plan.payable;
3717
+ }
3718
+ /** Price a gated URL across funded chains — the chosen rail's quote (the first
3719
+ * funded chain that can settle), else the first offered rail's. `null` when the URL
3720
+ * needs no payment. When it IS
3721
+ * gated but none of your chains are offered, surfaces the same informative
3722
+ * `NoCompatibleAcceptError` a single client would (it names the chains the 402 is
3723
+ * payable on) rather than a misleading `null`. No funds move. */
3724
+ async quote(url, init) {
3725
+ const plan = await this.planPayment(url, init);
3726
+ if (plan == null) return null;
3727
+ const opt = _nullishCoalesce(plan.best, () => ( plan.options[0]));
3728
+ if (opt) return opt.quote;
3729
+ return this._clients[0].quote(url, init);
3730
+ }
3731
+ /** Pay the first funded chain (in your listed order) that can settle this URL.
3732
+ * Delegates to {@link fetchAcross} — full policy / approval / retry / replay path on
3733
+ * the owning client. The owner re-reads balances at pay time, so the rail paid is the
3734
+ * surfaced `best` on a best-effort basis (it can pick another rail on the SAME chain,
3735
+ * or decline, if balances shift between plan and pay). PROBES the URL with `init`
3736
+ * (method + body) per client — prefer GET / idempotent requests. */
3737
+ fetch(url, init) {
3738
+ return fetchAcross(this._clients, url, init);
3739
+ }
3740
+ /** GET that auto-pays across chains. */
3741
+ get(url, init) {
3742
+ return this.fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: "GET" });
3743
+ }
3744
+ /**
3745
+ * POST that auto-pays across chains. `body` is a string/FormData/URLSearchParams/
3746
+ * ArrayBuffer/Blob (sent as-is) or a plain object (serialised as JSON) — mirrors
3747
+ * {@link PipRailClient.post}.
3748
+ */
3749
+ post(url, body, init) {
3750
+ const headers = new Headers(_optionalChain([init, 'optionalAccess', _66 => _66.headers]));
3751
+ let payload;
3752
+ if (body === void 0 || body === null) {
3753
+ payload = void 0;
3754
+ } else if (isBodyInit(body)) {
3755
+ payload = body;
3756
+ } else if (typeof body === "object") {
3757
+ payload = JSON.stringify(body);
3758
+ if (!headers.has("content-type")) headers.set("content-type", "application/json");
3759
+ } else {
3760
+ payload = String(body);
3761
+ }
3762
+ return this.fetch(url, { ..._nullishCoalesce(init, () => ( {})), method: "POST", headers, body: payload });
3763
+ }
3764
+ /**
3765
+ * Find payable resources across every funded chain. With the default
3766
+ * `network: 'self'`, each chain's own results are merged + deduped by URL (so
3767
+ * "self" means "any chain I can pay"). A network-scoped query (a CAIP-2 id or
3768
+ * `'any'`) is chain-independent, so one client answers it. Never throws for a
3769
+ * read problem; moves no funds.
3770
+ */
3771
+ async discover(opts = {}) {
3772
+ if (opts.network && opts.network !== "self") {
3773
+ return this._clients[0].discover(opts);
3774
+ }
3775
+ const perChain = await Promise.all(
3776
+ this._clients.map((c) => c.discover({ ...opts, network: "self" }).catch(() => []))
3777
+ );
3778
+ const seen = /* @__PURE__ */ new Set();
3779
+ const merged = [];
3780
+ for (const r of perChain.flat()) {
3781
+ if (seen.has(r.resource)) continue;
3782
+ seen.add(r.resource);
3783
+ merged.push(r);
3784
+ }
3785
+ return merged;
3786
+ }
3787
+ /** List a resource YOU run on the open indexes. Registration is a merchant action
3788
+ * independent of which chain you pay FROM, so it goes through your first chain's
3789
+ * client; pass `opts.network` to advertise a specific chain. Moves no funds. */
3790
+ register(url, opts = {}) {
3791
+ return this._clients[0].register(url, opts);
3792
+ }
3793
+ /** Aggregate spend across every chain — counts summed; per-(network,asset) rows
3794
+ * and records concatenated (no cross-chain collisions, never a cross-token sum). */
3795
+ spent() {
3796
+ const summaries = this._clients.map((c) => c.spent());
3797
+ return {
3798
+ count: summaries.reduce((n, s) => n + s.count, 0),
3799
+ byAsset: summaries.flatMap((s) => s.byAsset),
3800
+ records: summaries.flatMap((s) => s.records)
3801
+ };
3802
+ }
3803
+ /** A merged budget view: every chain's per-(network,asset) remaining rows, plus the
3804
+ * MOST-RESTRICTIVE session time envelope across chains (the soonest deadline wins).
3805
+ * Mirrors {@link PipRailClient.budget}'s shape so the agent toolkit reads it
3806
+ * unchanged; per-chain session detail is on each `clients[i].budget()`. */
3807
+ budget() {
3808
+ const budgets = this._clients.map((c) => c.budget());
3809
+ const session = budgets.map((b) => b.session).reduce((soonest, s) => {
3810
+ if (soonest.secondsRemaining == null) return s;
3811
+ if (s.secondsRemaining == null) return soonest;
3812
+ return s.secondsRemaining < soonest.secondsRemaining ? s : soonest;
3813
+ });
3814
+ return { session, byAsset: budgets.flatMap((b) => b.byAsset) };
3815
+ }
3816
+ };
3817
+ function isBodyInit(value) {
3818
+ if (typeof value === "string") return true;
3819
+ if (value instanceof ArrayBuffer) return true;
3820
+ if (ArrayBuffer.isView(value)) return true;
3821
+ if (typeof URLSearchParams !== "undefined" && value instanceof URLSearchParams) return true;
3822
+ if (typeof FormData !== "undefined" && value instanceof FormData) return true;
3823
+ if (typeof Blob !== "undefined" && value instanceof Blob) return true;
3824
+ return false;
3825
+ }
3826
+
3604
3827
  // src/selfdescribe.ts
3605
3828
  var BRAND = {
3606
3829
  name: "PipRail",
@@ -4003,7 +4226,12 @@ function paymentTools(client) {
4003
4226
  url: { type: "string", description: "Full URL of the resource to list." },
4004
4227
  name: { type: "string", description: "Display name (defaults to the host)." },
4005
4228
  description: { type: "string", description: "What the resource offers." },
4006
- priceUsd: { type: "number", description: "Advertised price in USD (metadata)." }
4229
+ priceUsd: { type: "number", description: "Advertised price in USD (metadata)." },
4230
+ network: {
4231
+ type: "string",
4232
+ description: "Network slug to advertise, e.g. 'base' (defaults to the paying chain). Set it when registering from a multi-chain wallet so the listing names the right chain."
4233
+ },
4234
+ asset: { type: "string", description: "Payment asset symbol, e.g. 'USDC' (metadata)." }
4007
4235
  },
4008
4236
  required: ["url"],
4009
4237
  additionalProperties: false
@@ -4013,6 +4241,8 @@ function paymentTools(client) {
4013
4241
  if (typeof args.name === "string") opts.name = args.name;
4014
4242
  if (typeof args.description === "string") opts.description = args.description;
4015
4243
  if (typeof args.priceUsd === "number") opts.priceUsd = args.priceUsd;
4244
+ if (typeof args.network === "string") opts.network = args.network;
4245
+ if (typeof args.asset === "string") opts.asset = args.asset;
4016
4246
  const outcomes = await client.register(String(args.url), opts);
4017
4247
  return { outcomes };
4018
4248
  }
@@ -4215,10 +4445,10 @@ async function fetchFacilitatorFeePayer(url, network, timeoutMs = 8e3) {
4215
4445
  const res = await fetch(`${base2}/supported`, { signal: ctrl.signal });
4216
4446
  if (!res.ok) return void 0;
4217
4447
  const body = await res.json();
4218
- const kinds = Array.isArray(_optionalChain([body, 'optionalAccess', _64 => _64.kinds])) ? body.kinds : [];
4448
+ const kinds = Array.isArray(_optionalChain([body, 'optionalAccess', _67 => _67.kinds])) ? body.kinds : [];
4219
4449
  const want = normalizeNetwork(network);
4220
- const kind = kinds.find((k) => _optionalChain([k, 'optionalAccess', _65 => _65.scheme]) === "exact" && normalizeNetwork(String(_nullishCoalesce(_optionalChain([k, 'optionalAccess', _66 => _66.network]), () => ( "")))) === want);
4221
- const fp = _optionalChain([kind, 'optionalAccess', _67 => _67.extra, 'optionalAccess', _68 => _68.feePayer]);
4450
+ const kind = kinds.find((k) => _optionalChain([k, 'optionalAccess', _68 => _68.scheme]) === "exact" && normalizeNetwork(String(_nullishCoalesce(_optionalChain([k, 'optionalAccess', _69 => _69.network]), () => ( "")))) === want);
4451
+ const fp = _optionalChain([kind, 'optionalAccess', _70 => _70.extra, 'optionalAccess', _71 => _71.feePayer]);
4222
4452
  return typeof fp === "string" ? fp : void 0;
4223
4453
  } catch (e32) {
4224
4454
  return void 0;
@@ -4227,14 +4457,14 @@ async function fetchFacilitatorFeePayer(url, network, timeoutMs = 8e3) {
4227
4457
  }
4228
4458
  }
4229
4459
  function parseFacilitatorSupported(body) {
4230
- const kinds = _optionalChain([body, 'optionalAccess', _69 => _69.kinds]);
4460
+ const kinds = _optionalChain([body, 'optionalAccess', _72 => _72.kinds]);
4231
4461
  if (!Array.isArray(kinds)) return [];
4232
4462
  const out = [];
4233
4463
  for (const k of kinds) {
4234
4464
  if (!k || typeof k !== "object") continue;
4235
4465
  const o = k;
4236
4466
  if (typeof o.scheme !== "string" || typeof o.network !== "string") continue;
4237
- const fp = _optionalChain([o, 'access', _70 => _70.extra, 'optionalAccess', _71 => _71.feePayer]);
4467
+ const fp = _optionalChain([o, 'access', _73 => _73.extra, 'optionalAccess', _74 => _74.feePayer]);
4238
4468
  out.push({ scheme: o.scheme, network: o.network, ...typeof fp === "string" ? { feePayer: fp } : {} });
4239
4469
  }
4240
4470
  return out;
@@ -4535,7 +4765,7 @@ function createPaymentGate(options) {
4535
4765
  accepts,
4536
4766
  instruction: describeChallenge({ x402Version: 2, resource: { url: resourceUrl }, accepts })
4537
4767
  });
4538
- const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _72 => _72.extensions]), () => ( {}));
4768
+ const rejectionExt = _nullishCoalesce(_optionalChain([opts, 'optionalAccess', _75 => _75.extensions]), () => ( {}));
4539
4769
  const rejectionPiprail = _nullishCoalesce(rejectionExt.piprail, () => ( {}));
4540
4770
  const bodyPiprail = { ..._nullishCoalesce(selfDescribe, () => ( {})), ...rejectionPiprail };
4541
4771
  const bodyExtensions = {
@@ -4554,7 +4784,7 @@ function createPaymentGate(options) {
4554
4784
  ...options.description ? { description: options.description } : {}
4555
4785
  },
4556
4786
  accepts,
4557
- ..._optionalChain([opts, 'optionalAccess', _73 => _73.error]) ? { error: opts.error } : {},
4787
+ ..._optionalChain([opts, 'optionalAccess', _76 => _76.error]) ? { error: opts.error } : {},
4558
4788
  ...Object.keys(bodyExtensions).length > 0 ? { extensions: bodyExtensions } : {}
4559
4789
  };
4560
4790
  const headerChallenge = {
@@ -4808,25 +5038,49 @@ function normaliseHeader(value) {
4808
5038
 
4809
5039
  // src/facilitators.ts
4810
5040
  var KNOWN_FACILITATORS = {
4811
- // PayAI keyless (no API key), sponsors the gas. Verified 2026-06-14 against
4812
- // https://facilitator.payai.network/supported (exact · eip155:8453) + the live demo.
5041
+ // Base (eip155:8453). Every entry is keyless and LIVE-settled by us (a real EIP-3009
5042
+ // exact payment, buyer paid zero ETH) — not just a /supported read on the dated day.
4813
5043
  "eip155:8453": [
4814
5044
  {
4815
5045
  url: "https://facilitator.payai.network",
4816
5046
  keyless: true,
4817
5047
  schemes: ["exact"],
4818
5048
  settles: ["eip3009"],
4819
- note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009)"
5049
+ note: "PayAI \u2014 keyless, sponsors gas (Base USDC EIP-3009). Verified 2026-06-14 (/supported + live demo)."
5050
+ },
5051
+ {
5052
+ url: "https://facilitator.xpay.sh",
5053
+ keyless: true,
5054
+ schemes: ["exact"],
5055
+ settles: ["eip3009"],
5056
+ note: "xpay \u2014 keyless, zero-fee, sponsors gas. LIVE-settled on Base 2026-06-15 (tx 0x2273d5\u2026)."
4820
5057
  }
4821
5058
  ],
4822
- // PayAI on Solana keyless fee-payer sponsor for the SVM exact rail. Verified 2026-06-14.
5059
+ // Solana (mainnet-beta). Keyless fee-payer sponsors for the SVM exact rail, each LIVE-settled
5060
+ // by us (a real SPL TransferChecked, buyer paid zero SOL) on the dated day — beyond a /supported
5061
+ // read. Daydreams + Questflow are intentionally ABSENT: their /supported is public but /verify
5062
+ // returns 401 (an API key is required), so they are not keyless for settlement.
4823
5063
  "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": [
4824
5064
  {
4825
5065
  url: "https://facilitator.payai.network",
4826
5066
  keyless: true,
4827
5067
  schemes: ["exact"],
4828
5068
  settles: ["svm"],
4829
- note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM)"
5069
+ note: "PayAI \u2014 keyless fee-payer sponsor (Solana SPL SVM). LIVE-settled 2026-06-14 (tx 4dL8jRKH\u2026)."
5070
+ },
5071
+ {
5072
+ url: "https://pay.openfacilitator.io",
5073
+ keyless: true,
5074
+ schemes: ["exact"],
5075
+ settles: ["svm"],
5076
+ note: "OpenFacilitator \u2014 keyless (no signup), fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx 5BabDtX\u2026)."
5077
+ },
5078
+ {
5079
+ url: "https://facilitator.corbits.dev",
5080
+ keyless: true,
5081
+ schemes: ["exact"],
5082
+ settles: ["svm"],
5083
+ note: "Corbits \u2014 keyless, Solana-first fee-payer sponsor. LIVE-settled on Solana 2026-06-15 (tx BCreYer\u2026)."
4830
5084
  }
4831
5085
  ]
4832
5086
  };
@@ -4851,7 +5105,7 @@ function isRetryableStatus(status) {
4851
5105
  }
4852
5106
  var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
4853
5107
  async function signBody(secret, body) {
4854
- const subtle = _optionalChain([globalThis, 'access', _74 => _74.crypto, 'optionalAccess', _75 => _75.subtle]);
5108
+ const subtle = _optionalChain([globalThis, 'access', _77 => _77.crypto, 'optionalAccess', _78 => _78.subtle]);
4855
5109
  if (!subtle) return null;
4856
5110
  try {
4857
5111
  const enc = new TextEncoder();
@@ -4921,7 +5175,7 @@ async function deliverReceipt(receipt, options) {
4921
5175
  const retryable = status === void 0 ? true : isRetryableStatus(status);
4922
5176
  const willRetry = !ok && retryable && attempt < maxAttempts;
4923
5177
  try {
4924
- _optionalChain([onAttempt, 'optionalCall', _76 => _76({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
5178
+ _optionalChain([onAttempt, 'optionalCall', _79 => _79({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
4925
5179
  } catch (e39) {
4926
5180
  }
4927
5181
  if (ok) return { delivered: true, attempts: attempt, status };
@@ -5035,4 +5289,6 @@ async function deliverReceipt(receipt, options) {
5035
5289
 
5036
5290
 
5037
5291
 
5038
- exports.BRAND = BRAND; exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkU35MG4TFcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkU35MG4TFcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkU35MG4TFcjs.InvalidEnvelopeError; exports.KNOWN_FACILITATORS = KNOWN_FACILITATORS; exports.MaxRetriesExceededError = _chunkU35MG4TFcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkU35MG4TFcjs.MissingDriverError; exports.NoCompatibleAcceptError = _chunkU35MG4TFcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkU35MG4TFcjs.NonReplayableBodyError; exports.PERMIT2_ADDRESS = PERMIT2_ADDRESS; exports.PERMIT2_PROXY_CHAIN_IDS = PERMIT2_PROXY_CHAIN_IDS; exports.PERMIT2_WITNESS_TYPES = PERMIT2_WITNESS_TYPES; exports.PIPRAIL_AGENT_GUIDE = PIPRAIL_AGENT_GUIDE; exports.POWERED_BY = POWERED_BY; exports.PaymentDeclinedError = _chunkU35MG4TFcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkU35MG4TFcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkU35MG4TFcjs.PipRailError; exports.REGISTER_ATTRIBUTION = REGISTER_ATTRIBUTION; exports.RecipientNotReadyError = _chunkU35MG4TFcjs.RecipientNotReadyError; exports.SettlementError = _chunkU35MG4TFcjs.SettlementError; exports.UnknownTokenError = _chunkU35MG4TFcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkU35MG4TFcjs.UnsupportedNetworkError; exports.UnsupportedSchemeError = _chunkU35MG4TFcjs.UnsupportedSchemeError; exports.WalletRequiredError = _chunkU35MG4TFcjs.WalletRequiredError; exports.WrongChainError = _chunkU35MG4TFcjs.WrongChainError; exports.WrongFamilyError = _chunkU35MG4TFcjs.WrongFamilyError; exports.X402_EXACT_PERMIT2_PROXY = X402_EXACT_PERMIT2_PROXY; exports.agentGuide = agentGuide; exports.appendAttribution = appendAttribution; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildExactSignatureHeader = buildExactSignatureHeader; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSelfDescription = buildSelfDescription; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.classifyChallenge = classifyChallenge; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.deliverReceipt = deliverReceipt; exports.describeChallenge = describeChallenge; exports.discoveryHeaders = discoveryHeaders; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.explainDecline = explainDecline; exports.facilitatorCoverage = facilitatorCoverage; exports.firstKeylessFacilitator = firstKeylessFacilitator; exports.formatSpendReport = formatSpendReport; exports.getDirectoryInfo = getDirectoryInfo; exports.isPermit2ProxyChain = isPermit2ProxyChain; exports.knownFacilitatorsFor = knownFacilitatorsFor; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseFacilitatorSupported = parseFacilitatorSupported; exports.parseReceipt = parseReceipt; exports.parseSettleResponse = parseSettleResponse; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.renderLandingPage = renderLandingPage; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.summarizePlan = summarizePlan; exports.toInsufficientFundsError = _chunkU35MG4TFcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;
5292
+
5293
+
5294
+ exports.BRAND = BRAND; exports.CHAINS = CHAINS; exports.ConfirmationTimeoutError = _chunkU35MG4TFcjs.ConfirmationTimeoutError; exports.DIRECTORY_INFO = DIRECTORY_INFO; exports.EIP3009_TYPES = EIP3009_TYPES; exports.EXACT_NETWORK_SLUGS = EXACT_NETWORK_SLUGS; exports.GENERATOR = GENERATOR; exports.HEADER_REQUIRED = HEADER_REQUIRED; exports.HEADER_RESPONSE = HEADER_RESPONSE; exports.HEADER_RESPONSE_V1 = HEADER_RESPONSE_V1; exports.HEADER_SIGNATURE = HEADER_SIGNATURE; exports.HEADER_SIGNATURE_V1 = HEADER_SIGNATURE_V1; exports.InsufficientFundsError = _chunkU35MG4TFcjs.InsufficientFundsError; exports.InvalidEnvelopeError = _chunkU35MG4TFcjs.InvalidEnvelopeError; exports.KNOWN_FACILITATORS = KNOWN_FACILITATORS; exports.MaxRetriesExceededError = _chunkU35MG4TFcjs.MaxRetriesExceededError; exports.MissingDriverError = _chunkU35MG4TFcjs.MissingDriverError; exports.MultiChainPayer = MultiChainPayer; exports.NoCompatibleAcceptError = _chunkU35MG4TFcjs.NoCompatibleAcceptError; exports.NonReplayableBodyError = _chunkU35MG4TFcjs.NonReplayableBodyError; exports.PERMIT2_ADDRESS = PERMIT2_ADDRESS; exports.PERMIT2_PROXY_CHAIN_IDS = PERMIT2_PROXY_CHAIN_IDS; exports.PERMIT2_WITNESS_TYPES = PERMIT2_WITNESS_TYPES; exports.PIPRAIL_AGENT_GUIDE = PIPRAIL_AGENT_GUIDE; exports.POWERED_BY = POWERED_BY; exports.PaymentDeclinedError = _chunkU35MG4TFcjs.PaymentDeclinedError; exports.PaymentTimeoutError = _chunkU35MG4TFcjs.PaymentTimeoutError; exports.PipRailClient = PipRailClient; exports.PipRailError = _chunkU35MG4TFcjs.PipRailError; exports.REGISTER_ATTRIBUTION = REGISTER_ATTRIBUTION; exports.RecipientNotReadyError = _chunkU35MG4TFcjs.RecipientNotReadyError; exports.SettlementError = _chunkU35MG4TFcjs.SettlementError; exports.UnknownTokenError = _chunkU35MG4TFcjs.UnknownTokenError; exports.UnsupportedNetworkError = _chunkU35MG4TFcjs.UnsupportedNetworkError; exports.UnsupportedSchemeError = _chunkU35MG4TFcjs.UnsupportedSchemeError; exports.WalletRequiredError = _chunkU35MG4TFcjs.WalletRequiredError; exports.WrongChainError = _chunkU35MG4TFcjs.WrongChainError; exports.WrongFamilyError = _chunkU35MG4TFcjs.WrongFamilyError; exports.X402_EXACT_PERMIT2_PROXY = X402_EXACT_PERMIT2_PROXY; exports.agentGuide = agentGuide; exports.appendAttribution = appendAttribution; exports.buildBazaarExtension = buildBazaarExtension; exports.buildChallengeHeader = buildChallengeHeader; exports.buildExactAuthorization = buildExactAuthorization; exports.buildExactSignatureHeader = buildExactSignatureHeader; exports.buildOpenApi = buildOpenApi; exports.buildReceiptHeader = buildReceiptHeader; exports.buildSelfDescription = buildSelfDescription; exports.buildSignatureHeader = buildSignatureHeader; exports.buildWellKnownX402 = buildWellKnownX402; exports.buildX402DnsTxt = buildX402DnsTxt; exports.chainIdForExactNetwork = chainIdForExactNetwork; exports.claim402IndexDomain = claim402IndexDomain; exports.classifyChallenge = classifyChallenge; exports.createPaymentGate = createPaymentGate; exports.decorateOutcome = decorateOutcome; exports.deliverReceipt = deliverReceipt; exports.describeChallenge = describeChallenge; exports.discoveryHeaders = discoveryHeaders; exports.eip3009Abi = eip3009Abi; exports.encodeXPaymentHeader = encodeXPaymentHeader; exports.evaluatePolicy = evaluatePolicy; exports.explainDecline = explainDecline; exports.facilitatorCoverage = facilitatorCoverage; exports.fetchAcross = fetchAcross; exports.firstKeylessFacilitator = firstKeylessFacilitator; exports.formatSpendReport = formatSpendReport; exports.getDirectoryInfo = getDirectoryInfo; exports.isPermit2ProxyChain = isPermit2ProxyChain; exports.knownFacilitatorsFor = knownFacilitatorsFor; exports.normalizeNetwork = normalizeNetwork; exports.parseChallenge = parseChallenge; exports.parseExactPaymentHeader = parseExactPaymentHeader; exports.parseExactRequirements = parseExactRequirements; exports.parseFacilitatorSupported = parseFacilitatorSupported; exports.parseReceipt = parseReceipt; exports.parseSettleResponse = parseSettleResponse; exports.parseSignatureHeader = parseSignatureHeader; exports.paymentTools = paymentTools; exports.pickAccept = pickAccept; exports.planAcross = planAcross; exports.readExactDomain = readExactDomain; exports.register402Index = register402Index; exports.registerDriver = registerDriver; exports.registerX402Scan = registerX402Scan; exports.renderLandingPage = renderLandingPage; exports.requirePayment = requirePayment; exports.resolveChain = resolveChain; exports.searchOpenIndexes = searchOpenIndexes; exports.settleViaFacilitator = settleViaFacilitator; exports.summarizePlan = summarizePlan; exports.toInsufficientFundsError = _chunkU35MG4TFcjs.toInsufficientFundsError; exports.toInvalidBody = toInvalidBody; exports.verify402IndexDomain = verify402IndexDomain;