@piprail/sdk 1.6.0 → 1.8.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/README.md CHANGED
@@ -113,11 +113,91 @@ For **every rail the 402 offers on your chain**, the plan reads **token balance
113
113
 
114
114
  ```ts
115
115
  import { paymentTools } from '@piprail/sdk'
116
- const tools = paymentTools(client) // → [piprail_quote_payment, piprail_plan_payment, piprail_pay_request]
116
+ const tools = paymentTools(client) // → [piprail_discover, piprail_quote_payment, piprail_plan_payment, piprail_pay_request, piprail_register]
117
117
  ```
118
118
 
119
+ Each descriptor also carries advisory **`annotations`** (MCP-style `ToolAnnotations` — `title`,
120
+ `readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`): the three reads are flagged
121
+ **read-only**, `piprail_pay_request` is flagged **value-moving** (the one tool that spends), and
122
+ `piprail_register` is non-destructive — so an MCP client can render the right consent. They're hints,
123
+ not the boundary; the spend policy is. `@piprail/mcp` advertises them on the wire.
124
+
119
125
  See [`examples/agent-tools.mjs`](../examples/agent-tools.mjs) for MCP / AI-SDK wiring.
120
126
 
127
+ ## Be discoverable — find and be found ($0, no backend)
128
+
129
+ A 402 endpoint is payable, but nobody can *find* it. PipRail closes that gap by building on the
130
+ **open** x402 indexes that already exist (402 Index, the CDP Bazaar read API, x402scan) — **nothing
131
+ PipRail-hosted, no registry, no database.** All opt-in; the pay path is untouched. There's **no PipRail
132
+ account and no x402 sign-up anywhere** — the only thing ever registered is a merchant's own URL, by one
133
+ call. The complete reference — including a **step-by-step walkthrough of exactly what each step needs**
134
+ (wallet? signing? sign-up? — spoiler: 402 Index needs none) — is **[DISCOVERY.md](./DISCOVERY.md)** (§7).
135
+
136
+ > **Experimental.** Discovery integrates with third-party open indexes whose conventions are young
137
+ > and moving — treat this layer as experimental. The read path + 402 Index register are live-verified;
138
+ > x402scan SIWX isn't yet. Note **402 Index probes your URL and only lists endpoints that actually
139
+ > return a `402`** — so register a *deployed* gate, not a marketing page. (DISCOVERY.md §10 has the log.)
140
+
141
+ **1) List a resource you run** — one call, no auth, no signature:
142
+
143
+ ```ts
144
+ const client = new PipRailClient({ wallet: { privateKey: KEY }, chain: 'base' })
145
+
146
+ await client.register('https://api.example.com/report', { name: 'Market Report', priceUsd: 0.05 })
147
+ // → [{ source: '402index', ok: true, detail: 'Listed on 402 Index (searchable at 402index.io).' }]
148
+ // Add targets: ['402index', 'x402scan'] to also register on x402scan via one wallet signature
149
+ // (SIWX — Base/Solana only). It moves no funds; you're listing on third-party open directories.
150
+ ```
151
+
152
+ **Works on every chain.** 402 Index needs no signature and has no chain allowlist, so *any* chain —
153
+ a preset, a non-EVM family, or a custom `{ id, rpcUrl }` chain — can be listed and found; `discover()`
154
+ never silently hides a resource whose chain it can't resolve. (x402scan is the one Base/Solana-only
155
+ *bonus* target.)
156
+
157
+ **Built-with attribution (tasteful, honest).** Your emitted `/openapi.json` carries an
158
+ `x-generator: "@piprail/sdk"` stamp by default (opt out with `attribution: false`), and every index
159
+ request sends a `User-Agent: @piprail/sdk` — so the tech spreads through the files indexes crawl and
160
+ the logs operators read, never by spamming listings. An opt-in `register(url, { attribution: true })`
161
+ adds a best-effort `via` tag; it's off by default (it's your listing).
162
+
163
+ **2) Find resources to pay** — read the open indexes (free), filtered to your chain by default:
164
+
165
+ ```ts
166
+ const hits = await client.discover({ query: 'weather', maxPrice: 0.01 })
167
+ // → [{ resource, name, source, priceUsd, rails: [...] }, …]
168
+ const res = await client.fetch(hits[0].resource) // then quote → plan → pay as usual
169
+ ```
170
+
171
+ `network` defaults to `'self'` (your chain only); pass `'any'` to search every chain, or a CAIP-2 id
172
+ (`'eip155:8453'`) for a specific one. Chain slugs map to CAIP-2 through the `SLUG_TO_CAIP2` table —
173
+ adding a chain adds one entry there (see [DISCOVERY.md](./DISCOVERY.md) §2.5), and an unresolved
174
+ network is kept, never hidden.
175
+
176
+ **3) Emit a discovery file** — turn your gate's config into the artifacts a crawler reads (pure, no
177
+ I/O); serve the result as a static file on **your own** origin:
178
+
179
+ ```ts
180
+ import { createPaymentGate, buildOpenApi, buildX402DnsTxt } from '@piprail/sdk'
181
+
182
+ const gate = createPaymentGate({ chain: 'base', token: 'USDC', amount: '0.05', payTo })
183
+ const openapi = buildOpenApi({
184
+ origin: 'https://api.example.com',
185
+ resources: [await gate.describe('https://api.example.com/report')],
186
+ })
187
+ // serve `openapi` at https://api.example.com/openapi.json — each priced op carries an `x-payment-info`
188
+ // block (the field indexes crawl) plus a default root `x-generator` attribution stamp.
189
+ // buildWellKnownX402(...) emits the legacy /.well-known/x402 file; buildX402DnsTxt(...) the _x402 DNS line
190
+ ```
191
+
192
+ For an LLM/MCP these are two more tools — **`piprail_discover`** (find) and **`piprail_register`**
193
+ (be found) — on top of the three payment tools, so `paymentTools(client)` / `@piprail/mcp` expose **five**.
194
+
195
+ > **Two honest caveats.** The open indexes assume the mainstream `exact` scheme, so to be *usefully*
196
+ > listed also offer a standard `exact` USDC rail on Base/Solana (`discover()` results are
197
+ > cross-scheme; `fetch()` pays only PipRail `onchain-proof` rails directly). And **x402scan indexes
198
+ > Base/Solana only** — 402 Index has no such limit, so it's the default register target. There is no
199
+ > single ratified discovery standard yet; OpenAPI-first is an emerging multi-vendor convention.
200
+
121
201
  ### Accept several chains at once
122
202
 
123
203
  `requirePayment` (and `createPaymentGate`) take an **`accept: [...]`** array — one challenge that's payable on **any** of several chains/tokens, across **all ten families** (EVM, Solana, TON, Tron, Stellar, XRPL, NEAR, Sui, Aptos, Algorand). The agent pays with whatever it holds:
@@ -560,7 +640,7 @@ The full standard every module follows is **[ERRORS.md](./ERRORS.md)**.
560
640
 
561
641
  ## API
562
642
 
563
- **`requirePayment(options)`** → Express middleware  ·  **`createPaymentGate(options)`** → `{ challenge, verify }`
643
+ **`requirePayment(options)`** → Express middleware  ·  **`createPaymentGate(options)`** → `{ challenge, verify, describe }` (`describe()` → static discovery metadata for the emitters)
564
644
 
565
645
  | Option | Default | Notes |
566
646
  |---|---|---|
@@ -592,7 +672,9 @@ Provide **either** `chain` + `token` + `amount` (single) **or** a non-empty `acc
592
672
  | `retryTimeoutMs` | `30000` | Timeout for the retry leg after broadcast |
593
673
  | `onEvent` | — | `(event) => void` observability: `payment-required` · `payment-broadcast` · `payment-confirmed` · `payment-unconfirmed` (broadcast OK, local confirm timed out → deferring to server) · `payment-settled` · `payment-failed` |
594
674
 
595
- Methods: `fetch` · `get` · `post` (return the gated `Response` after settlement) · **`quote(url)`** (price without paying → `PipRailQuote \| null`) · **`estimateCost(url)`** (price **+** native-coin gas estimate → `PipRailCostQuote \| null`) · **`planPayment(url)`** (affordability + recipient-readiness across the offered rails → `PaymentPlan \| null`) · **`canAfford(url)`** (→ `boolean`) · **`spent()`** (per-asset ledger snapshot). Pass `{ autoRoute: true }` to `fetch` (or set it on the client) to pay the cheapest *settleable* rail. Module-level **`planAcross(clients, url)`** plans across chains.
675
+ Methods: `fetch` · `get` · `post` (return the gated `Response` after settlement) · **`quote(url)`** (price without paying → `PipRailQuote \| null`) · **`estimateCost(url)`** (price **+** native-coin gas estimate → `PipRailCostQuote \| null`) · **`planPayment(url)`** (affordability + recipient-readiness across the offered rails → `PaymentPlan \| null`) · **`canAfford(url)`** (→ `boolean`) · **`spent()`** (per-asset ledger snapshot) · **`discover(opts?)`** (find resources on the open indexes → `DiscoveredResource[]`) · **`register(url, opts?)`** (list a resource on the open indexes → `RegisterOutcome[]`) · **`discoverySigner()`** (the wallet's discovery signer, EVM today, or `null`). Pass `{ autoRoute: true }` to `fetch` (or set it on the client) to pay the cheapest *settleable* rail. Module-level **`planAcross(clients, url)`** plans across chains.
676
+
677
+ **Discovery (opt-in, $0, nothing hosted):** `client.discover()` / `client.register()`, the standalone `searchOpenIndexes` / `register402Index` / `registerX402Scan`, and the pure emitters `buildOpenApi` / `buildWellKnownX402` / `buildX402DnsTxt` (fed by `gate.describe()`). See [Be discoverable](#be-discoverable--find-and-be-found-0-no-backend).
596
678
 
597
679
  **Wallets by family** — the `chain` selector routes; each driver validates its own key format (a mismatch throws `WrongFamilyError`):
598
680
 
@@ -609,7 +691,7 @@ Methods: `fetch` · `get` · `post` (return the gated `Response` after settlemen
609
691
  | Aptos | `{ privateKey }` (ed25519-priv-0x… AIP-80) or `{ account }` |
610
692
  | Algorand | `{ mnemonic }` (25 words) or `{ account }` (algosdk `{ addr, sk }`) |
611
693
 
612
- **Hand an LLM a wallet:** `paymentTools(client)` → framework-agnostic tool descriptors (MCP / AI SDK / function-calling), budget enforced by the client.
694
+ **Hand an LLM a wallet:** `paymentTools(client)` → five framework-agnostic tool descriptors (`piprail_discover` · `piprail_quote_payment` · `piprail_plan_payment` · `piprail_pay_request` · `piprail_register`) for MCP / AI SDK / function-calling, budget enforced by the client.
613
695
 
614
696
  **Bring your own chain family:** the SDK is built on a tiny `PaymentDriver` contract — `resolve(chain)` returns a bound network with `resolveToken` / `describeAsset` / `assertValidPayTo` / `bindWallet` / `send` / `confirm` / `estimateCost` / `balanceOf` / `recipientReady` / `verify`. Register your own with `registerDriver(...)`; the protocol layer never changes (see [Architecture](#architecture-under-the-hood)).
615
697
 
package/STANDARDS.md CHANGED
@@ -25,7 +25,7 @@ zero-config path still read in one line? If a feature can't be opt-in, reconside
25
25
  ## 1. The layering (never violate)
26
26
 
27
27
  ```
28
- protocol layer index · server · client · x402 · policy · ledger · agent · errors · util/*
28
+ protocol layer index · server · client · x402 · policy · ledger · agent · discovery · indexes · errors · util/*
29
29
  (chain-agnostic — ZERO viem / @solana / @ton / @stellar imports)
30
30
  │ depends only on …
31
31
 
@@ -36,9 +36,9 @@ chain drivers drivers/<family>/ chains · wallet · pay · verify · index
36
36
  registry.ts (routes a chain → family) index.ts (eager EVM + lazy auto-mount of the rest)
37
37
  ```
38
38
 
39
- - **The protocol layer is chain-agnostic.** `server`/`client`/`x402`/`policy`/`ledger`/`agent`
40
- import only `drivers/types.ts` + pure utils — never a chain library. Verified by the
41
- lazy-chunk invariant (below).
39
+ - **The protocol layer is chain-agnostic.** `server`/`client`/`x402`/`policy`/`ledger`/`agent`/
40
+ `discovery`/`indexes` import only `drivers/types.ts` + pure utils — never a chain library.
41
+ Verified by the lazy-chunk invariant (below).
42
42
  - **Drivers mirror each other** file-for-file (`chains`/`wallet`/`pay`/`verify`/`index`),
43
43
  functions family-suffixed (`payEvm`/`verifyStellar`). A new contract method is implemented in
44
44
  **all** families.