@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 +63 -0
- package/README.md +28 -1
- package/dist/index.cjs +277 -21
- package/dist/index.d.cts +198 -5
- package/dist/index.d.ts +198 -5
- package/dist/index.js +267 -11
- package/package.json +1 -1
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
|
-
|
|
3523
|
-
const live =
|
|
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 =
|
|
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
|
-
//
|
|
3536
|
-
|
|
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',
|
|
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',
|
|
4221
|
-
const fp = _optionalChain([kind, 'optionalAccess',
|
|
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',
|
|
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',
|
|
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',
|
|
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',
|
|
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
|
-
//
|
|
4812
|
-
//
|
|
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
|
-
//
|
|
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',
|
|
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',
|
|
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
|
-
|
|
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;
|