@piprail/sdk 1.22.1 → 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 CHANGED
@@ -4,6 +4,88 @@ 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
+
49
+ ## [1.23.0] — 2026-06-14 — self-describing endpoints + discovery reach
50
+
51
+ ### Added — self-describing, more discoverable endpoints (discoverability plan: Phases 1, 2, 4, 5)
52
+
53
+ - **Self-describing 402s, ON by default.** Every challenge a gate builds now carries an
54
+ `extensions.piprail` block — identity, per-rail how-to-pay, `npm i @piprail/sdk` + a paste-ready
55
+ snippet, the MCP command, and docs + discovery pointers — so any human, AI agent, or crawler that
56
+ lands on a gated endpoint (even the `onchain-proof` scheme a stock x402 client can't pay) knows what
57
+ it is and how to pay it. **Purely-additive** metadata in the spec-opaque `extensions` bag, and it
58
+ rides in the response **body** only — the base64 `payment-required` **header stays slim** (just
59
+ `accepts[]` + a small `bazaar`/rejection block), so the header, pay path, `accepts[]`, and status are
60
+ byte-identical (a rejection's `{code,detail}` are deep-merged as siblings and still win). Opt out with
61
+ `requirePayment({ selfDescribe: false })`. New exports: `buildSelfDescription`, `describeChallenge`, `BRAND`.
62
+ - **Human landing page + HTTP pointers.** `gate.landingPage(challenge)` / `renderLandingPage()` render a
63
+ tiny, HTML-escaped 402 page for a browser (serve it on `Accept: text/html`; agents/crawlers keep the
64
+ JSON 402). It leads with **how to pay** and a clear **caution** that a manual send to the address won't
65
+ unlock the resource (pay via an x402 client — no custody, no manual-payment desk). `discoveryHeaders()`
66
+ + `POWERED_BY` emit a `Link` (rel `service-desc` / `x402-discovery`) + `x-powered-by` header bag to
67
+ spread on every response. The SDK serves nothing — the merchant does.
68
+ - **Facilitator-coverage map.** `KNOWN_FACILITATORS` (+ `knownFacilitatorsFor` /
69
+ `firstKeylessFacilitator`) — shipped data of which keyless facilitators settle `exact` on which
70
+ networks (seeded: PayAI on Base + Solana, dated-verified). `facilitatorCoverage(url)` /
71
+ `parseFacilitatorSupported(body)` read a facilitator's live `GET /supported` (best-effort, never throw).
72
+ - **Agent guide** gained a "landing cold — read the self-description" section (surfaced over the MCP).
73
+
74
+ All of the above is additive + opt-in; the zero-config pay path is byte-identical to before. New pure
75
+ modules (`selfdescribe.ts`, `landing.ts`, `facilitators.ts`) join the viem-free protocol-layer grep.
76
+
77
+ ### Removed
78
+ - **`base-sepolia` (84532) testnet entry** from `EXACT_NETWORK_SLUGS` and the Permit2 proxy chain-id set
79
+ — mainnets only, no testnet presets (it was dead reference data; nothing settles on a testnet).
80
+
81
+ ### Fixed
82
+ - **`x-powered-by` is now ASCII** (`PipRail x402 | https://piprail.com`) — the previous non-ASCII middot
83
+ mangled to `·` over Node's latin1 header transport. `GENERATOR` keeps its `·` (JSON body only).
84
+ - **`renderLandingPage` never throws** on a rail missing `amount`/`asset` (matches the module's
85
+ never-throw contract).
86
+ - **`fetchFacilitatorFeePayer` matches the network normalized** (CAIP-2 *or* slug) so a slug-reporting
87
+ facilitator's Solana exact fee-payer is found instead of silently dropped.
88
+
7
89
  ## [1.22.1] — 2026-06-13 — docs: community links + integrations signposting
8
90
 
9
91
  Docs-only patch — **no code change** (the SDK is byte-identical to 1.22.0). Refreshes the README:
@@ -982,6 +1064,7 @@ straight into your wallet. The API is small and self-contained.
982
1064
  to your wallet; PipRail never holds funds.
983
1065
  - `viem ^2.21` is a peer dependency. Node 20+ or a modern browser.
984
1066
 
1067
+ [1.24.0]: https://www.npmjs.com/package/@piprail/sdk
985
1068
  [1.15.1]: https://www.npmjs.com/package/@piprail/sdk
986
1069
  [1.15.0]: https://www.npmjs.com/package/@piprail/sdk
987
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) |