@piprail/sdk 1.23.0 → 1.24.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 +43 -0
- package/README.md +28 -1
- package/dist/index.cjs +248 -16
- package/dist/index.d.cts +198 -5
- package/dist/index.d.ts +198 -5
- package/dist/index.js +238 -6
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,48 @@ 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.24.0] — 2026-06-14 — multi-chain buying (one buyer, a wallet per chain)
|
|
8
|
+
|
|
9
|
+
### Added — pay a 402 on whichever chain it asks for
|
|
10
|
+
|
|
11
|
+
- **`MultiChainPayer`** — a `PipRailClient` is bound to ONE chain + ONE wallet (an EVM key can't sign a
|
|
12
|
+
Solana tx). `MultiChainPayer.fromWallets({ wallets: { base: { privateKey }, solana: { secretKey }, … }, policy })`
|
|
13
|
+
carries one wallet per chain and exposes a single `fetch`/`get`/`post`/`planPayment`/`canAfford`/`quote`/
|
|
14
|
+
`discover`/`register`/`spent`/`budget`. On a 402 it surveys every funded chain and pays the FIRST chain
|
|
15
|
+
you listed that can actually settle — through each client's own spend policy, `onBeforePay`, retries, and
|
|
16
|
+
replay-protection. No price oracle, no backend, no custody; across coins the order you list the chains is
|
|
17
|
+
the preference (within a chain, the cheapest-gas rail). Also `new MultiChainPayer([...clients])` for full
|
|
18
|
+
control (e.g. custom-EVM viem `Chain`s). `schemes` (incl. the gasless `exact` rail) propagates to every
|
|
19
|
+
chain's client.
|
|
20
|
+
- **`fetchAcross(clients, url, init?)`** — the EXECUTION counterpart to `planAcross`: plan across an array
|
|
21
|
+
of single-chain clients and pay, on its owning client, the rail `planAcross` reports as `best` (the first
|
|
22
|
+
funded chain that can settle). Throws `PaymentDeclinedError` with a merged, per-chain funding hint when
|
|
23
|
+
no chain can settle.
|
|
24
|
+
- **`PayingClient`** — the shared read-+-pay interface `paymentTools` now accepts; both `PipRailClient` and
|
|
25
|
+
`MultiChainPayer` satisfy it, so the agent toolkit (and the MCP) wrap either unchanged.
|
|
26
|
+
- **`piprail_register` agent tool** gains optional `network` + `asset` params, so a multi-chain agent can
|
|
27
|
+
advertise a listing on a specific chain instead of defaulting to the first wallet's chain.
|
|
28
|
+
|
|
29
|
+
### Changed
|
|
30
|
+
|
|
31
|
+
- **Cross-chain `best` selection is now your PREFERENCE order, not raw gas magnitude.** `planAcross` /
|
|
32
|
+
`fetchAcross` no longer compare gas fees across different native coins (base units aren't comparable —
|
|
33
|
+
e.g. EVM wei vs Solana lamports — and there's no oracle), which previously let a small-base-unit coin
|
|
34
|
+
win regardless of real cost. They now pay the FIRST chain you list that can settle (within a chain, the
|
|
35
|
+
cheapest-gas rail still wins) — matching the documented contract. Single-chain `PipRailClient` ranking is
|
|
36
|
+
unchanged.
|
|
37
|
+
- **`planAcross` now propagates a TOTAL outage.** If EVERY client fails to reach the resource it throws
|
|
38
|
+
(like a single client) instead of returning `null` — so `canAfford`/`quote` can't report a false
|
|
39
|
+
"affordable"/"not-gated". A single chain being down still just drops that chain.
|
|
40
|
+
- **Clearer multi-chain decline message.** When no funded chain can settle, `planAcross`'s `fundingHint`
|
|
41
|
+
(and the `PaymentDeclinedError` `fetchAcross` throws) now names EVERY funded chain's own blocker — "top up
|
|
42
|
+
X USDC on base · add ~Y POL gas on polygon" — instead of only the first. Chains the 402 never offered are
|
|
43
|
+
dropped as noise when another chain is close; if none of your chains are offered, it says where the 402
|
|
44
|
+
IS payable. Per-rail `blockers`/`warnings` stay machine-readable for agents that branch programmatically.
|
|
45
|
+
|
|
46
|
+
Single-chain `PipRailClient` behaviour is byte-identical. Examples: `examples/multi-chain` (routing + a
|
|
47
|
+
live gasless-`exact` BNB Permit2 settlement through `MultiChainPayer`).
|
|
48
|
+
|
|
7
49
|
## [1.23.0] — 2026-06-14 — self-describing endpoints + discovery reach
|
|
8
50
|
|
|
9
51
|
### Added — self-describing, more discoverable endpoints (discoverability plan: Phases 1, 2, 4, 5)
|
|
@@ -1022,6 +1064,7 @@ straight into your wallet. The API is small and self-contained.
|
|
|
1022
1064
|
to your wallet; PipRail never holds funds.
|
|
1023
1065
|
- `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
|
|
1024
1066
|
|
|
1067
|
+
[1.24.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1025
1068
|
[1.15.1]: https://www.npmjs.com/package/@piprail/sdk
|
|
1026
1069
|
[1.15.0]: https://www.npmjs.com/package/@piprail/sdk
|
|
1027
1070
|
[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 = {
|
|
@@ -4851,7 +5081,7 @@ function isRetryableStatus(status) {
|
|
|
4851
5081
|
}
|
|
4852
5082
|
var sleep = (ms) => ms > 0 ? new Promise((resolve) => setTimeout(resolve, ms)) : Promise.resolve();
|
|
4853
5083
|
async function signBody(secret, body) {
|
|
4854
|
-
const subtle = _optionalChain([globalThis, 'access',
|
|
5084
|
+
const subtle = _optionalChain([globalThis, 'access', _77 => _77.crypto, 'optionalAccess', _78 => _78.subtle]);
|
|
4855
5085
|
if (!subtle) return null;
|
|
4856
5086
|
try {
|
|
4857
5087
|
const enc = new TextEncoder();
|
|
@@ -4921,7 +5151,7 @@ async function deliverReceipt(receipt, options) {
|
|
|
4921
5151
|
const retryable = status === void 0 ? true : isRetryableStatus(status);
|
|
4922
5152
|
const willRetry = !ok && retryable && attempt < maxAttempts;
|
|
4923
5153
|
try {
|
|
4924
|
-
_optionalChain([onAttempt, 'optionalCall',
|
|
5154
|
+
_optionalChain([onAttempt, 'optionalCall', _79 => _79({ attempt, ok, ...status !== void 0 ? { status } : {}, ...error ? { error } : {}, willRetry })]);
|
|
4925
5155
|
} catch (e39) {
|
|
4926
5156
|
}
|
|
4927
5157
|
if (ok) return { delivered: true, attempts: attempt, status };
|
|
@@ -5035,4 +5265,6 @@ async function deliverReceipt(receipt, options) {
|
|
|
5035
5265
|
|
|
5036
5266
|
|
|
5037
5267
|
|
|
5038
|
-
|
|
5268
|
+
|
|
5269
|
+
|
|
5270
|
+
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;
|
package/dist/index.d.cts
CHANGED
|
@@ -5204,6 +5204,22 @@ interface RegisterOptions {
|
|
|
5204
5204
|
*/
|
|
5205
5205
|
attribution?: boolean;
|
|
5206
5206
|
}
|
|
5207
|
+
/**
|
|
5208
|
+
* The read-+-pay surface an agent toolkit needs — the methods {@link paymentTools}
|
|
5209
|
+
* calls. BOTH {@link PipRailClient} (one chain) and {@link MultiChainPayer} (many
|
|
5210
|
+
* chains, one per wallet) satisfy it, so `paymentTools` wraps either unchanged:
|
|
5211
|
+
* point an MCP/LLM at one wallet or at a whole bundle without touching the tools.
|
|
5212
|
+
*/
|
|
5213
|
+
interface PayingClient {
|
|
5214
|
+
discover(opts?: DiscoverOptions): Promise<DiscoveredResource[]>;
|
|
5215
|
+
quote(url: string, init?: RequestInit): Promise<PipRailQuote | null>;
|
|
5216
|
+
planPayment(url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5217
|
+
get(url: string, init?: RequestInit): Promise<Response>;
|
|
5218
|
+
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
5219
|
+
register(url: string, opts?: RegisterOptions): Promise<RegisterOutcome[]>;
|
|
5220
|
+
spent(): SpendSummary;
|
|
5221
|
+
budget(): SessionBudget;
|
|
5222
|
+
}
|
|
5207
5223
|
declare class PipRailClient {
|
|
5208
5224
|
private readonly opts;
|
|
5209
5225
|
private readonly maxRetries;
|
|
@@ -5453,11 +5469,188 @@ declare class PipRailClient {
|
|
|
5453
5469
|
* A {@link PipRailClient} is bound to one chain (its wallet); give this one client
|
|
5454
5470
|
* per chain the agent funds and it runs each client's {@link PipRailClient.planPayment}
|
|
5455
5471
|
* in parallel and merges the rails into one plan, ranked payable-first. `best` is a
|
|
5456
|
-
* payable rail
|
|
5457
|
-
*
|
|
5458
|
-
*
|
|
5472
|
+
* payable rail. Across different native coins there's no oracle to compare gas costs,
|
|
5473
|
+
* so it does NOT rank chains by fee against each other: `best` is the FIRST chain you
|
|
5474
|
+
* pass in `clients` that can settle (your preference order); within a single chain it
|
|
5475
|
+
* still prefers the cheapest-gas rail. Returns `null` only if the URL isn't gated for
|
|
5476
|
+
* any client. Throws only if EVERY client fails to reach the resource (a total outage),
|
|
5477
|
+
* mirroring a single client — a single chain being down just drops that chain.
|
|
5459
5478
|
*/
|
|
5460
5479
|
declare function planAcross(clients: PipRailClient[], url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5480
|
+
/**
|
|
5481
|
+
* PAY across several single-chain clients — the EXECUTION counterpart to
|
|
5482
|
+
* {@link planAcross}. Plans the URL on every client in parallel (keeping which
|
|
5483
|
+
* client owns which rail), picks the rail `planAcross` names as `best` (the first
|
|
5484
|
+
* funded chain you listed that can settle RIGHT NOW), and pays it on its owning
|
|
5485
|
+
* client. So an agent that holds one wallet
|
|
5486
|
+
* per chain pays whichever chain/token the merchant's 402 asks for — with no
|
|
5487
|
+
* manual routing — while every payment still goes through that client's own
|
|
5488
|
+
* spend policy, `onBeforePay` hook, retries, and replay-protection (this just
|
|
5489
|
+
* calls the chosen client's {@link PipRailClient.fetch}).
|
|
5490
|
+
*
|
|
5491
|
+
* - A URL that needs no payment (no 402) is returned straight through.
|
|
5492
|
+
* - When NO funded chain can settle it, throws {@link PaymentDeclinedError} with a
|
|
5493
|
+
* merged, per-chain funding hint — BEFORE any on-chain send.
|
|
5494
|
+
*
|
|
5495
|
+
* Selection matches {@link planAcross}: payable-first, and across different native
|
|
5496
|
+
* coins (no price oracle) the FIRST chain you pass in `clients` that can settle wins
|
|
5497
|
+
* (your preference); within a chain, the cheapest-gas rail. It normally pays the rail
|
|
5498
|
+
* `planAcross` reports as `best`, but on a BEST-EFFORT basis — the owning client
|
|
5499
|
+
* re-reads its balances/gas at pay time, so a change between planning and paying (a
|
|
5500
|
+
* concurrent payment, RPC drift, the merchant returning a different 402) can make it
|
|
5501
|
+
* pick another settleable rail ON THE SAME CHAIN, or decline; its spend policy +
|
|
5502
|
+
* `onBeforePay` still gate whatever is actually paid. For the ergonomic object form,
|
|
5503
|
+
* see {@link MultiChainPayer}.
|
|
5504
|
+
*
|
|
5505
|
+
* NOTE: this PROBES the URL with the caller's `init` (method + body) on each client to
|
|
5506
|
+
* read the 402, so prefer it for GET / idempotent requests — a non-idempotent POST is
|
|
5507
|
+
* sent once per client before the pay leg (the x402 gate returns 402 without acting,
|
|
5508
|
+
* but the body is re-sent).
|
|
5509
|
+
*/
|
|
5510
|
+
declare function fetchAcross(clients: PipRailClient[], url: string, init?: RequestInit): Promise<Response>;
|
|
5511
|
+
|
|
5512
|
+
/**
|
|
5513
|
+
* MultiChainPayer — one buyer, many wallets, pay whatever the merchant asks.
|
|
5514
|
+
*
|
|
5515
|
+
* A {@link PipRailClient} is bound to exactly ONE chain and ONE wallet (an EVM key
|
|
5516
|
+
* can't sign a Solana tx, and vice-versa — that's enforced at bind time). So a
|
|
5517
|
+
* buyer who wants to pay a 402 *whatever chain/token it demands* holds one key per
|
|
5518
|
+
* chain. This is the ergonomic object that carries that bundle: give it a
|
|
5519
|
+
* `{ chain → wallet }` map and it builds one client per chain, then exposes a single
|
|
5520
|
+
* `fetch`/`get`/`post`/`plan`/`quote` that auto-routes to the first funded chain that
|
|
5521
|
+
* can settle — no manual "which client owns this rail?" plumbing.
|
|
5522
|
+
*
|
|
5523
|
+
* It is a thin, chain-agnostic composition over the existing primitives — it adds
|
|
5524
|
+
* NO new payment logic:
|
|
5525
|
+
* - `planPayment` → {@link planAcross} (merge every chain's plan, payable-first)
|
|
5526
|
+
* - `fetch`/`get`/`post` → {@link fetchAcross} (pay on the first chain that can settle)
|
|
5527
|
+
* Every payment still runs through its owning client's own spend policy,
|
|
5528
|
+
* `onBeforePay` hook, retries, and replay-protection. There is no cross-chain
|
|
5529
|
+
* custody, no price oracle, and no backend: across chains it pays the FIRST one you
|
|
5530
|
+
* list that can settle (your preference order — gas isn't comparable across coins);
|
|
5531
|
+
* within a chain it picks the cheapest-gas rail.
|
|
5532
|
+
*
|
|
5533
|
+
* Because it implements {@link PayingClient}, the agent toolkit ({@link paymentTools})
|
|
5534
|
+
* and the MCP server wrap it byte-identically to a single client.
|
|
5535
|
+
*/
|
|
5536
|
+
|
|
5537
|
+
/**
|
|
5538
|
+
* One wallet per chain you fund, keyed by chain selector. The KEY is a chain
|
|
5539
|
+
* string (an EVM preset like `'base'`/`'bnb'`, or a non-EVM family
|
|
5540
|
+
* `'solana'|'ton'|'tron'|'near'|'sui'|'aptos'|'algorand'|'stellar'|'xrpl'`); the
|
|
5541
|
+
* VALUE is that family's {@link WalletInput}:
|
|
5542
|
+
*
|
|
5543
|
+
* base/bnb/… → { privateKey } solana → { secretKey } ton/algorand → { mnemonic }
|
|
5544
|
+
* stellar → { secret } xrpl → { seed } near → { accountId, privateKey }
|
|
5545
|
+
*
|
|
5546
|
+
* One key per family — this map is how a single buyer carries the keys for every
|
|
5547
|
+
* chain it's willing to pay on. (For a CUSTOM EVM chain configured by a viem
|
|
5548
|
+
* `Chain` object, build the {@link PipRailClient} yourself and use
|
|
5549
|
+
* `new MultiChainPayer([...clients])`.)
|
|
5550
|
+
*/
|
|
5551
|
+
interface MultiChainPayerOptions {
|
|
5552
|
+
/** `{ chain → wallet }`. Iteration order is your chain PREFERENCE: across chains the
|
|
5553
|
+
* first one that can settle wins (there's no oracle to compare gas across coins). */
|
|
5554
|
+
wallets: Record<string, WalletInput>;
|
|
5555
|
+
/** Spend policy applied to EVERY chain's client. Each client still keeps its own
|
|
5556
|
+
* per-(network,asset) ledger — there is no cross-token sum (no price oracle). */
|
|
5557
|
+
policy?: PaymentPolicy;
|
|
5558
|
+
/** Per-chain RPC overrides, keyed by the same chain selector as `wallets`. */
|
|
5559
|
+
rpcUrls?: Record<string, string>;
|
|
5560
|
+
/** Which schemes every client may settle. Default `['onchain-proof']` (unchanged). */
|
|
5561
|
+
schemes?: PaymentScheme[];
|
|
5562
|
+
/** Final approval hook applied to every chain's client (fires before any send). */
|
|
5563
|
+
onBeforePay?: (quote: PipRailQuote) => boolean | Promise<boolean>;
|
|
5564
|
+
/** Observability hook applied to every chain's client. */
|
|
5565
|
+
onEvent?: (event: PipRailEvent) => void;
|
|
5566
|
+
/** Retry budget for the post-broadcast leg, per client. Default 3. */
|
|
5567
|
+
maxPaymentRetries?: number;
|
|
5568
|
+
/** Timeout (ms) for the retry leg, per client. Default 30_000. */
|
|
5569
|
+
retryTimeoutMs?: number;
|
|
5570
|
+
}
|
|
5571
|
+
declare class MultiChainPayer implements PayingClient {
|
|
5572
|
+
private readonly _clients;
|
|
5573
|
+
/**
|
|
5574
|
+
* Wrap an explicit, ordered set of single-chain clients — use this when a client
|
|
5575
|
+
* needs full control (e.g. a custom EVM chain configured by a viem `Chain`). The
|
|
5576
|
+
* ORDER is your chain preference: across chains the first that can settle wins. Pass
|
|
5577
|
+
* at MOST one client per chain — two clients on the SAME network would double-count in
|
|
5578
|
+
* `spent()`/`budget()` and waste a plan round-trip (`fromWallets` can't produce this).
|
|
5579
|
+
* For the common case, prefer {@link MultiChainPayer.fromWallets}.
|
|
5580
|
+
*/
|
|
5581
|
+
constructor(clients: PipRailClient[]);
|
|
5582
|
+
/**
|
|
5583
|
+
* Build one client per funded chain from a `{ chain → wallet }` map — the
|
|
5584
|
+
* ergonomic path. The shared `policy`/`schemes`/`onBeforePay`/`onEvent` apply to
|
|
5585
|
+
* every client; `rpcUrls` are matched per chain. Iteration order of `wallets` is
|
|
5586
|
+
* the chain preference.
|
|
5587
|
+
*
|
|
5588
|
+
* ```ts
|
|
5589
|
+
* const payer = MultiChainPayer.fromWallets({
|
|
5590
|
+
* wallets: {
|
|
5591
|
+
* base: { privateKey: process.env.EVM_KEY! },
|
|
5592
|
+
* solana: { secretKey: process.env.SOLANA_SECRET! },
|
|
5593
|
+
* xrpl: { seed: process.env.XRPL_SEED! },
|
|
5594
|
+
* },
|
|
5595
|
+
* policy: { maxAmount: '1.00', maxTotal: '20.00', tokens: ['USDC', 'USDT'] },
|
|
5596
|
+
* })
|
|
5597
|
+
* const res = await payer.get('https://api.example.com/paid') // pays on the first funded chain that can settle
|
|
5598
|
+
* ```
|
|
5599
|
+
*/
|
|
5600
|
+
static fromWallets(opts: MultiChainPayerOptions): MultiChainPayer;
|
|
5601
|
+
/** The underlying single-chain clients, in preference order. Reach for one of
|
|
5602
|
+
* these for chain-specific reads (`estimateCost`, `discoverySigner`, per-chain
|
|
5603
|
+
* `budget()`) that don't make sense merged. */
|
|
5604
|
+
get clients(): readonly PipRailClient[];
|
|
5605
|
+
/** Plan a 402 across every funded chain — merged + ranked payable-first. `null`
|
|
5606
|
+
* when the URL needs no payment. (Delegates to {@link planAcross}.) */
|
|
5607
|
+
planPayment(url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5608
|
+
/** Can ANY funded chain settle this URL right now? (A free resource is trivially
|
|
5609
|
+
* "affordable".) No funds move. */
|
|
5610
|
+
canAfford(url: string, init?: RequestInit): Promise<boolean>;
|
|
5611
|
+
/** Price a gated URL across funded chains — the chosen rail's quote (the first
|
|
5612
|
+
* funded chain that can settle), else the first offered rail's. `null` when the URL
|
|
5613
|
+
* needs no payment. When it IS
|
|
5614
|
+
* gated but none of your chains are offered, surfaces the same informative
|
|
5615
|
+
* `NoCompatibleAcceptError` a single client would (it names the chains the 402 is
|
|
5616
|
+
* payable on) rather than a misleading `null`. No funds move. */
|
|
5617
|
+
quote(url: string, init?: RequestInit): Promise<PipRailQuote | null>;
|
|
5618
|
+
/** Pay the first funded chain (in your listed order) that can settle this URL.
|
|
5619
|
+
* Delegates to {@link fetchAcross} — full policy / approval / retry / replay path on
|
|
5620
|
+
* the owning client. The owner re-reads balances at pay time, so the rail paid is the
|
|
5621
|
+
* surfaced `best` on a best-effort basis (it can pick another rail on the SAME chain,
|
|
5622
|
+
* or decline, if balances shift between plan and pay). PROBES the URL with `init`
|
|
5623
|
+
* (method + body) per client — prefer GET / idempotent requests. */
|
|
5624
|
+
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
5625
|
+
/** GET that auto-pays across chains. */
|
|
5626
|
+
get(url: string, init?: RequestInit): Promise<Response>;
|
|
5627
|
+
/**
|
|
5628
|
+
* POST that auto-pays across chains. `body` is a string/FormData/URLSearchParams/
|
|
5629
|
+
* ArrayBuffer/Blob (sent as-is) or a plain object (serialised as JSON) — mirrors
|
|
5630
|
+
* {@link PipRailClient.post}.
|
|
5631
|
+
*/
|
|
5632
|
+
post(url: string, body?: BodyInit | object | undefined, init?: RequestInit): Promise<Response>;
|
|
5633
|
+
/**
|
|
5634
|
+
* Find payable resources across every funded chain. With the default
|
|
5635
|
+
* `network: 'self'`, each chain's own results are merged + deduped by URL (so
|
|
5636
|
+
* "self" means "any chain I can pay"). A network-scoped query (a CAIP-2 id or
|
|
5637
|
+
* `'any'`) is chain-independent, so one client answers it. Never throws for a
|
|
5638
|
+
* read problem; moves no funds.
|
|
5639
|
+
*/
|
|
5640
|
+
discover(opts?: DiscoverOptions): Promise<DiscoveredResource[]>;
|
|
5641
|
+
/** List a resource YOU run on the open indexes. Registration is a merchant action
|
|
5642
|
+
* independent of which chain you pay FROM, so it goes through your first chain's
|
|
5643
|
+
* client; pass `opts.network` to advertise a specific chain. Moves no funds. */
|
|
5644
|
+
register(url: string, opts?: RegisterOptions): Promise<RegisterOutcome[]>;
|
|
5645
|
+
/** Aggregate spend across every chain — counts summed; per-(network,asset) rows
|
|
5646
|
+
* and records concatenated (no cross-chain collisions, never a cross-token sum). */
|
|
5647
|
+
spent(): SpendSummary;
|
|
5648
|
+
/** A merged budget view: every chain's per-(network,asset) remaining rows, plus the
|
|
5649
|
+
* MOST-RESTRICTIVE session time envelope across chains (the soonest deadline wins).
|
|
5650
|
+
* Mirrors {@link PipRailClient.budget}'s shape so the agent toolkit reads it
|
|
5651
|
+
* unchanged; per-chain session detail is on each `clients[i].budget()`. */
|
|
5652
|
+
budget(): SessionBudget;
|
|
5653
|
+
}
|
|
5461
5654
|
|
|
5462
5655
|
/**
|
|
5463
5656
|
* MCP-style tool annotations — optional, advisory hints that let an MCP client or
|
|
@@ -5514,7 +5707,7 @@ interface AgentTool {
|
|
|
5514
5707
|
* declined? }`) — never a thrown error — so the model reasons about it (and never
|
|
5515
5708
|
* re-pays a broadcast-but-unconfirmed payment) instead of crashing.
|
|
5516
5709
|
*/
|
|
5517
|
-
declare function paymentTools(client:
|
|
5710
|
+
declare function paymentTools(client: PayingClient): AgentTool[];
|
|
5518
5711
|
|
|
5519
5712
|
/**
|
|
5520
5713
|
* One line summarising a {@link PaymentPlan} for a model: what's payable, on which
|
|
@@ -7031,4 +7224,4 @@ declare const PERMIT2_WITNESS_TYPES: {
|
|
|
7031
7224
|
*/
|
|
7032
7225
|
declare function renderLandingPage(sd: SelfDescription): string;
|
|
7033
7226
|
|
|
7034
|
-
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 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, 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, 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 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, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
|
7227
|
+
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 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 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, buildBazaarExtension, buildChallengeHeader, 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, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
package/dist/index.d.ts
CHANGED
|
@@ -5204,6 +5204,22 @@ interface RegisterOptions {
|
|
|
5204
5204
|
*/
|
|
5205
5205
|
attribution?: boolean;
|
|
5206
5206
|
}
|
|
5207
|
+
/**
|
|
5208
|
+
* The read-+-pay surface an agent toolkit needs — the methods {@link paymentTools}
|
|
5209
|
+
* calls. BOTH {@link PipRailClient} (one chain) and {@link MultiChainPayer} (many
|
|
5210
|
+
* chains, one per wallet) satisfy it, so `paymentTools` wraps either unchanged:
|
|
5211
|
+
* point an MCP/LLM at one wallet or at a whole bundle without touching the tools.
|
|
5212
|
+
*/
|
|
5213
|
+
interface PayingClient {
|
|
5214
|
+
discover(opts?: DiscoverOptions): Promise<DiscoveredResource[]>;
|
|
5215
|
+
quote(url: string, init?: RequestInit): Promise<PipRailQuote | null>;
|
|
5216
|
+
planPayment(url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5217
|
+
get(url: string, init?: RequestInit): Promise<Response>;
|
|
5218
|
+
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
5219
|
+
register(url: string, opts?: RegisterOptions): Promise<RegisterOutcome[]>;
|
|
5220
|
+
spent(): SpendSummary;
|
|
5221
|
+
budget(): SessionBudget;
|
|
5222
|
+
}
|
|
5207
5223
|
declare class PipRailClient {
|
|
5208
5224
|
private readonly opts;
|
|
5209
5225
|
private readonly maxRetries;
|
|
@@ -5453,11 +5469,188 @@ declare class PipRailClient {
|
|
|
5453
5469
|
* A {@link PipRailClient} is bound to one chain (its wallet); give this one client
|
|
5454
5470
|
* per chain the agent funds and it runs each client's {@link PipRailClient.planPayment}
|
|
5455
5471
|
* in parallel and merges the rails into one plan, ranked payable-first. `best` is a
|
|
5456
|
-
* payable rail
|
|
5457
|
-
*
|
|
5458
|
-
*
|
|
5472
|
+
* payable rail. Across different native coins there's no oracle to compare gas costs,
|
|
5473
|
+
* so it does NOT rank chains by fee against each other: `best` is the FIRST chain you
|
|
5474
|
+
* pass in `clients` that can settle (your preference order); within a single chain it
|
|
5475
|
+
* still prefers the cheapest-gas rail. Returns `null` only if the URL isn't gated for
|
|
5476
|
+
* any client. Throws only if EVERY client fails to reach the resource (a total outage),
|
|
5477
|
+
* mirroring a single client — a single chain being down just drops that chain.
|
|
5459
5478
|
*/
|
|
5460
5479
|
declare function planAcross(clients: PipRailClient[], url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5480
|
+
/**
|
|
5481
|
+
* PAY across several single-chain clients — the EXECUTION counterpart to
|
|
5482
|
+
* {@link planAcross}. Plans the URL on every client in parallel (keeping which
|
|
5483
|
+
* client owns which rail), picks the rail `planAcross` names as `best` (the first
|
|
5484
|
+
* funded chain you listed that can settle RIGHT NOW), and pays it on its owning
|
|
5485
|
+
* client. So an agent that holds one wallet
|
|
5486
|
+
* per chain pays whichever chain/token the merchant's 402 asks for — with no
|
|
5487
|
+
* manual routing — while every payment still goes through that client's own
|
|
5488
|
+
* spend policy, `onBeforePay` hook, retries, and replay-protection (this just
|
|
5489
|
+
* calls the chosen client's {@link PipRailClient.fetch}).
|
|
5490
|
+
*
|
|
5491
|
+
* - A URL that needs no payment (no 402) is returned straight through.
|
|
5492
|
+
* - When NO funded chain can settle it, throws {@link PaymentDeclinedError} with a
|
|
5493
|
+
* merged, per-chain funding hint — BEFORE any on-chain send.
|
|
5494
|
+
*
|
|
5495
|
+
* Selection matches {@link planAcross}: payable-first, and across different native
|
|
5496
|
+
* coins (no price oracle) the FIRST chain you pass in `clients` that can settle wins
|
|
5497
|
+
* (your preference); within a chain, the cheapest-gas rail. It normally pays the rail
|
|
5498
|
+
* `planAcross` reports as `best`, but on a BEST-EFFORT basis — the owning client
|
|
5499
|
+
* re-reads its balances/gas at pay time, so a change between planning and paying (a
|
|
5500
|
+
* concurrent payment, RPC drift, the merchant returning a different 402) can make it
|
|
5501
|
+
* pick another settleable rail ON THE SAME CHAIN, or decline; its spend policy +
|
|
5502
|
+
* `onBeforePay` still gate whatever is actually paid. For the ergonomic object form,
|
|
5503
|
+
* see {@link MultiChainPayer}.
|
|
5504
|
+
*
|
|
5505
|
+
* NOTE: this PROBES the URL with the caller's `init` (method + body) on each client to
|
|
5506
|
+
* read the 402, so prefer it for GET / idempotent requests — a non-idempotent POST is
|
|
5507
|
+
* sent once per client before the pay leg (the x402 gate returns 402 without acting,
|
|
5508
|
+
* but the body is re-sent).
|
|
5509
|
+
*/
|
|
5510
|
+
declare function fetchAcross(clients: PipRailClient[], url: string, init?: RequestInit): Promise<Response>;
|
|
5511
|
+
|
|
5512
|
+
/**
|
|
5513
|
+
* MultiChainPayer — one buyer, many wallets, pay whatever the merchant asks.
|
|
5514
|
+
*
|
|
5515
|
+
* A {@link PipRailClient} is bound to exactly ONE chain and ONE wallet (an EVM key
|
|
5516
|
+
* can't sign a Solana tx, and vice-versa — that's enforced at bind time). So a
|
|
5517
|
+
* buyer who wants to pay a 402 *whatever chain/token it demands* holds one key per
|
|
5518
|
+
* chain. This is the ergonomic object that carries that bundle: give it a
|
|
5519
|
+
* `{ chain → wallet }` map and it builds one client per chain, then exposes a single
|
|
5520
|
+
* `fetch`/`get`/`post`/`plan`/`quote` that auto-routes to the first funded chain that
|
|
5521
|
+
* can settle — no manual "which client owns this rail?" plumbing.
|
|
5522
|
+
*
|
|
5523
|
+
* It is a thin, chain-agnostic composition over the existing primitives — it adds
|
|
5524
|
+
* NO new payment logic:
|
|
5525
|
+
* - `planPayment` → {@link planAcross} (merge every chain's plan, payable-first)
|
|
5526
|
+
* - `fetch`/`get`/`post` → {@link fetchAcross} (pay on the first chain that can settle)
|
|
5527
|
+
* Every payment still runs through its owning client's own spend policy,
|
|
5528
|
+
* `onBeforePay` hook, retries, and replay-protection. There is no cross-chain
|
|
5529
|
+
* custody, no price oracle, and no backend: across chains it pays the FIRST one you
|
|
5530
|
+
* list that can settle (your preference order — gas isn't comparable across coins);
|
|
5531
|
+
* within a chain it picks the cheapest-gas rail.
|
|
5532
|
+
*
|
|
5533
|
+
* Because it implements {@link PayingClient}, the agent toolkit ({@link paymentTools})
|
|
5534
|
+
* and the MCP server wrap it byte-identically to a single client.
|
|
5535
|
+
*/
|
|
5536
|
+
|
|
5537
|
+
/**
|
|
5538
|
+
* One wallet per chain you fund, keyed by chain selector. The KEY is a chain
|
|
5539
|
+
* string (an EVM preset like `'base'`/`'bnb'`, or a non-EVM family
|
|
5540
|
+
* `'solana'|'ton'|'tron'|'near'|'sui'|'aptos'|'algorand'|'stellar'|'xrpl'`); the
|
|
5541
|
+
* VALUE is that family's {@link WalletInput}:
|
|
5542
|
+
*
|
|
5543
|
+
* base/bnb/… → { privateKey } solana → { secretKey } ton/algorand → { mnemonic }
|
|
5544
|
+
* stellar → { secret } xrpl → { seed } near → { accountId, privateKey }
|
|
5545
|
+
*
|
|
5546
|
+
* One key per family — this map is how a single buyer carries the keys for every
|
|
5547
|
+
* chain it's willing to pay on. (For a CUSTOM EVM chain configured by a viem
|
|
5548
|
+
* `Chain` object, build the {@link PipRailClient} yourself and use
|
|
5549
|
+
* `new MultiChainPayer([...clients])`.)
|
|
5550
|
+
*/
|
|
5551
|
+
interface MultiChainPayerOptions {
|
|
5552
|
+
/** `{ chain → wallet }`. Iteration order is your chain PREFERENCE: across chains the
|
|
5553
|
+
* first one that can settle wins (there's no oracle to compare gas across coins). */
|
|
5554
|
+
wallets: Record<string, WalletInput>;
|
|
5555
|
+
/** Spend policy applied to EVERY chain's client. Each client still keeps its own
|
|
5556
|
+
* per-(network,asset) ledger — there is no cross-token sum (no price oracle). */
|
|
5557
|
+
policy?: PaymentPolicy;
|
|
5558
|
+
/** Per-chain RPC overrides, keyed by the same chain selector as `wallets`. */
|
|
5559
|
+
rpcUrls?: Record<string, string>;
|
|
5560
|
+
/** Which schemes every client may settle. Default `['onchain-proof']` (unchanged). */
|
|
5561
|
+
schemes?: PaymentScheme[];
|
|
5562
|
+
/** Final approval hook applied to every chain's client (fires before any send). */
|
|
5563
|
+
onBeforePay?: (quote: PipRailQuote) => boolean | Promise<boolean>;
|
|
5564
|
+
/** Observability hook applied to every chain's client. */
|
|
5565
|
+
onEvent?: (event: PipRailEvent) => void;
|
|
5566
|
+
/** Retry budget for the post-broadcast leg, per client. Default 3. */
|
|
5567
|
+
maxPaymentRetries?: number;
|
|
5568
|
+
/** Timeout (ms) for the retry leg, per client. Default 30_000. */
|
|
5569
|
+
retryTimeoutMs?: number;
|
|
5570
|
+
}
|
|
5571
|
+
declare class MultiChainPayer implements PayingClient {
|
|
5572
|
+
private readonly _clients;
|
|
5573
|
+
/**
|
|
5574
|
+
* Wrap an explicit, ordered set of single-chain clients — use this when a client
|
|
5575
|
+
* needs full control (e.g. a custom EVM chain configured by a viem `Chain`). The
|
|
5576
|
+
* ORDER is your chain preference: across chains the first that can settle wins. Pass
|
|
5577
|
+
* at MOST one client per chain — two clients on the SAME network would double-count in
|
|
5578
|
+
* `spent()`/`budget()` and waste a plan round-trip (`fromWallets` can't produce this).
|
|
5579
|
+
* For the common case, prefer {@link MultiChainPayer.fromWallets}.
|
|
5580
|
+
*/
|
|
5581
|
+
constructor(clients: PipRailClient[]);
|
|
5582
|
+
/**
|
|
5583
|
+
* Build one client per funded chain from a `{ chain → wallet }` map — the
|
|
5584
|
+
* ergonomic path. The shared `policy`/`schemes`/`onBeforePay`/`onEvent` apply to
|
|
5585
|
+
* every client; `rpcUrls` are matched per chain. Iteration order of `wallets` is
|
|
5586
|
+
* the chain preference.
|
|
5587
|
+
*
|
|
5588
|
+
* ```ts
|
|
5589
|
+
* const payer = MultiChainPayer.fromWallets({
|
|
5590
|
+
* wallets: {
|
|
5591
|
+
* base: { privateKey: process.env.EVM_KEY! },
|
|
5592
|
+
* solana: { secretKey: process.env.SOLANA_SECRET! },
|
|
5593
|
+
* xrpl: { seed: process.env.XRPL_SEED! },
|
|
5594
|
+
* },
|
|
5595
|
+
* policy: { maxAmount: '1.00', maxTotal: '20.00', tokens: ['USDC', 'USDT'] },
|
|
5596
|
+
* })
|
|
5597
|
+
* const res = await payer.get('https://api.example.com/paid') // pays on the first funded chain that can settle
|
|
5598
|
+
* ```
|
|
5599
|
+
*/
|
|
5600
|
+
static fromWallets(opts: MultiChainPayerOptions): MultiChainPayer;
|
|
5601
|
+
/** The underlying single-chain clients, in preference order. Reach for one of
|
|
5602
|
+
* these for chain-specific reads (`estimateCost`, `discoverySigner`, per-chain
|
|
5603
|
+
* `budget()`) that don't make sense merged. */
|
|
5604
|
+
get clients(): readonly PipRailClient[];
|
|
5605
|
+
/** Plan a 402 across every funded chain — merged + ranked payable-first. `null`
|
|
5606
|
+
* when the URL needs no payment. (Delegates to {@link planAcross}.) */
|
|
5607
|
+
planPayment(url: string, init?: RequestInit): Promise<PaymentPlan | null>;
|
|
5608
|
+
/** Can ANY funded chain settle this URL right now? (A free resource is trivially
|
|
5609
|
+
* "affordable".) No funds move. */
|
|
5610
|
+
canAfford(url: string, init?: RequestInit): Promise<boolean>;
|
|
5611
|
+
/** Price a gated URL across funded chains — the chosen rail's quote (the first
|
|
5612
|
+
* funded chain that can settle), else the first offered rail's. `null` when the URL
|
|
5613
|
+
* needs no payment. When it IS
|
|
5614
|
+
* gated but none of your chains are offered, surfaces the same informative
|
|
5615
|
+
* `NoCompatibleAcceptError` a single client would (it names the chains the 402 is
|
|
5616
|
+
* payable on) rather than a misleading `null`. No funds move. */
|
|
5617
|
+
quote(url: string, init?: RequestInit): Promise<PipRailQuote | null>;
|
|
5618
|
+
/** Pay the first funded chain (in your listed order) that can settle this URL.
|
|
5619
|
+
* Delegates to {@link fetchAcross} — full policy / approval / retry / replay path on
|
|
5620
|
+
* the owning client. The owner re-reads balances at pay time, so the rail paid is the
|
|
5621
|
+
* surfaced `best` on a best-effort basis (it can pick another rail on the SAME chain,
|
|
5622
|
+
* or decline, if balances shift between plan and pay). PROBES the URL with `init`
|
|
5623
|
+
* (method + body) per client — prefer GET / idempotent requests. */
|
|
5624
|
+
fetch(url: string, init?: RequestInit): Promise<Response>;
|
|
5625
|
+
/** GET that auto-pays across chains. */
|
|
5626
|
+
get(url: string, init?: RequestInit): Promise<Response>;
|
|
5627
|
+
/**
|
|
5628
|
+
* POST that auto-pays across chains. `body` is a string/FormData/URLSearchParams/
|
|
5629
|
+
* ArrayBuffer/Blob (sent as-is) or a plain object (serialised as JSON) — mirrors
|
|
5630
|
+
* {@link PipRailClient.post}.
|
|
5631
|
+
*/
|
|
5632
|
+
post(url: string, body?: BodyInit | object | undefined, init?: RequestInit): Promise<Response>;
|
|
5633
|
+
/**
|
|
5634
|
+
* Find payable resources across every funded chain. With the default
|
|
5635
|
+
* `network: 'self'`, each chain's own results are merged + deduped by URL (so
|
|
5636
|
+
* "self" means "any chain I can pay"). A network-scoped query (a CAIP-2 id or
|
|
5637
|
+
* `'any'`) is chain-independent, so one client answers it. Never throws for a
|
|
5638
|
+
* read problem; moves no funds.
|
|
5639
|
+
*/
|
|
5640
|
+
discover(opts?: DiscoverOptions): Promise<DiscoveredResource[]>;
|
|
5641
|
+
/** List a resource YOU run on the open indexes. Registration is a merchant action
|
|
5642
|
+
* independent of which chain you pay FROM, so it goes through your first chain's
|
|
5643
|
+
* client; pass `opts.network` to advertise a specific chain. Moves no funds. */
|
|
5644
|
+
register(url: string, opts?: RegisterOptions): Promise<RegisterOutcome[]>;
|
|
5645
|
+
/** Aggregate spend across every chain — counts summed; per-(network,asset) rows
|
|
5646
|
+
* and records concatenated (no cross-chain collisions, never a cross-token sum). */
|
|
5647
|
+
spent(): SpendSummary;
|
|
5648
|
+
/** A merged budget view: every chain's per-(network,asset) remaining rows, plus the
|
|
5649
|
+
* MOST-RESTRICTIVE session time envelope across chains (the soonest deadline wins).
|
|
5650
|
+
* Mirrors {@link PipRailClient.budget}'s shape so the agent toolkit reads it
|
|
5651
|
+
* unchanged; per-chain session detail is on each `clients[i].budget()`. */
|
|
5652
|
+
budget(): SessionBudget;
|
|
5653
|
+
}
|
|
5461
5654
|
|
|
5462
5655
|
/**
|
|
5463
5656
|
* MCP-style tool annotations — optional, advisory hints that let an MCP client or
|
|
@@ -5514,7 +5707,7 @@ interface AgentTool {
|
|
|
5514
5707
|
* declined? }`) — never a thrown error — so the model reasons about it (and never
|
|
5515
5708
|
* re-pays a broadcast-but-unconfirmed payment) instead of crashing.
|
|
5516
5709
|
*/
|
|
5517
|
-
declare function paymentTools(client:
|
|
5710
|
+
declare function paymentTools(client: PayingClient): AgentTool[];
|
|
5518
5711
|
|
|
5519
5712
|
/**
|
|
5520
5713
|
* One line summarising a {@link PaymentPlan} for a model: what's payable, on which
|
|
@@ -7031,4 +7224,4 @@ declare const PERMIT2_WITNESS_TYPES: {
|
|
|
7031
7224
|
*/
|
|
7032
7225
|
declare function renderLandingPage(sd: SelfDescription): string;
|
|
7033
7226
|
|
|
7034
|
-
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 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, 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, 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 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, buildBazaarExtension, buildChallengeHeader, buildExactAuthorization, buildExactSignatureHeader, buildOpenApi, buildReceiptHeader, buildSelfDescription, buildSignatureHeader, buildWellKnownX402, buildX402DnsTxt, chainIdForExactNetwork, claim402IndexDomain, classifyChallenge, createPaymentGate, decorateOutcome, deliverReceipt, describeChallenge, discoveryHeaders, eip3009Abi, encodeXPaymentHeader, evaluatePolicy, explainDecline, facilitatorCoverage, firstKeylessFacilitator, formatSpendReport, getDirectoryInfo, isPermit2ProxyChain, knownFacilitatorsFor, normalizeNetwork, parseChallenge, parseExactPaymentHeader, parseExactRequirements, parseFacilitatorSupported, parseReceipt, parseSettleResponse, parseSignatureHeader, paymentTools, pickAccept, planAcross, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
|
7227
|
+
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 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 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, buildBazaarExtension, buildChallengeHeader, 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, readExactDomain, register402Index, registerDriver, registerX402Scan, renderLandingPage, requirePayment, resolveChain, searchOpenIndexes, settleViaFacilitator, summarizePlan, toInsufficientFundsError, toInvalidBody, verify402IndexDomain };
|
package/dist/index.js
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 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 = 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 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, { ...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
|
+
_clients;
|
|
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
|
+
...opts.rpcUrls?.[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 = 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, { ...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(init?.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, { ...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
|
}
|
|
@@ -4960,6 +5190,7 @@ export {
|
|
|
4960
5190
|
KNOWN_FACILITATORS,
|
|
4961
5191
|
MaxRetriesExceededError,
|
|
4962
5192
|
MissingDriverError,
|
|
5193
|
+
MultiChainPayer,
|
|
4963
5194
|
NoCompatibleAcceptError,
|
|
4964
5195
|
NonReplayableBodyError,
|
|
4965
5196
|
PERMIT2_ADDRESS,
|
|
@@ -5006,6 +5237,7 @@ export {
|
|
|
5006
5237
|
evaluatePolicy,
|
|
5007
5238
|
explainDecline,
|
|
5008
5239
|
facilitatorCoverage,
|
|
5240
|
+
fetchAcross,
|
|
5009
5241
|
firstKeylessFacilitator,
|
|
5010
5242
|
formatSpendReport,
|
|
5011
5243
|
getDirectoryInfo,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@piprail/sdk",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.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",
|