@blockrun/llm 3.22.0 → 3.23.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
@@ -5,7 +5,7 @@
5
5
  ### Cut your LLM bill by <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%. One line of TypeScript.
6
6
 
7
7
  The smart-routing SDK for <!-- br:models.chatVisible -->88<!-- /br:models.chatVisible --> models — every request goes to the cheapest model that can handle it,
8
- paid with an API key or per-request USDC on Solana or Base. No vendor lock-in.
8
+ paid with an API key or per-request USDC on Solana, Base or Arc. No vendor lock-in.
9
9
 
10
10
  [![npm](https://img.shields.io/npm/v/@blockrun/llm.svg?style=flat-square)](https://www.npmjs.com/package/@blockrun/llm)
11
11
  [![npm downloads](https://img.shields.io/npm/dm/@blockrun/llm.svg?style=flat-square)](https://www.npmjs.com/package/@blockrun/llm)
@@ -30,26 +30,26 @@ paid with an API key or per-request USDC on Solana or Base. No vendor lock-in.
30
30
  import { LLMClient } from '@blockrun/llm';
31
31
 
32
32
  // Reads BLOCKRUN_API_KEY (sign up at https://user.blockrun.ai),
33
- // or a wallet key if you'd rather pay per request in USDC.
33
+ // or BASE_CHAIN_WALLET_KEY if you'd rather pay per request in USDC.
34
34
  const client = new LLMClient();
35
35
 
36
36
  const r = await client.smartChat('Prove step by step that the sum of two odd integers is even.');
37
- console.log(r.model); // 'deepseek/deepseek-v4-pro' — the right model, not the frontier flagship
38
- console.log(r.routing.savings); // 0.96 — this exact request cost 96% less than pinning the baseline
37
+ console.log(r.model); // 'deepseek/deepseek-v4-pro': a strong reasoner, not the frontier flagship
38
+ console.log(r.routing.savings); // e.g. 0.83: this request cost 83% less than the baseline
39
39
  console.log(r.response); // the proof
40
40
  ```
41
41
 
42
- **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper than pinning Claude Opus 5** across a realistic workload on the default `auto` profile, **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** on `eco` — and eco's first stop is the free tier, so simple requests cost $0.00 outright. Not an "up to" figure: the baseline, workload mix, and token ratio are published in [`savings-mix.json`](https://github.com/BlockRunAI/blockrun/blob/main/src/brand/savings-mix.json) so anyone can recompute the claim. Details in [Smart Routing](#smart-routing-router-core-v3).
42
+ **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper than pinning Claude Opus 5** across a realistic workload on the default `auto` profile, **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** on `eco`, which tries the free tier first. How that is measured: [Smart Routing](#smart-routing-router-core-v3).
43
43
 
44
44
  ## Why This SDK
45
45
 
46
- - 🧠 **Smart routing that pays for itself** — the bundled [Router Core V3](https://github.com/BlockRunAI/router-core) engine (shared with [ClawRouter](https://github.com/BlockRunAI/ClawRouter)) classifies every request locally in <1ms across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions and routes to the cheapest capable model. The main event.
47
- - 🆓 **<!-- br:models.free -->7<!-- /br:models.free --> genuinely free models** — $0 in and out, incl. two 1M-context Nemotrons, a multimodal one, and free coding models from Cohere and Poolside. No rate-limit gimmicks.
48
- - 🔐 **Two ways to connect** — a **BlockRun API key** billed against account credit ([sign up at user.blockrun.ai](https://user.blockrun.ai), [create a key](https://user.blockrun.ai/dashboard/keys), [add credit](https://user.blockrun.ai/dashboard/credits)), or a wallet signature with x402 micropayments and no account at all. Same code either way.
49
- - 💸 **Pay per request in USDC** — x402 micropayments on Solana or Base. $5 covers thousands of requests; agents can pay their own way.
50
- - 🛡️ **Automatic failover** — transient errors (timeouts, 429, 5xx) before payment walk the router's ranked fallback chain instead of failing your request; a failure after a payment was sent never buys a second model.
51
- - ⚡ **Streaming, OpenAI & Anthropic compat** — drop-in `chat.completions` / `messages` layers, SSE streaming, strict TypeScript.
52
- - 🎨 **Beyond chat** — image, video, music, speech, live search, prediction markets, crypto data, and 40-chain RPC through the same API key or wallet.
46
+ - 🧠 **Smart routing that pays for itself.** The bundled [Router Core V3](https://github.com/BlockRunAI/router-core) engine (shared with [ClawRouter](https://github.com/BlockRunAI/ClawRouter)) classifies every request locally in <1ms across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions and routes to the cheapest capable model.
47
+ - 🆓 **<!-- br:models.free -->7<!-- /br:models.free --> genuinely free models.** $0 in and out, including two 1M-context Nemotrons, two that accept images, and free coding models from Cohere and Poolside.
48
+ - 🔐 **Two ways to connect.** A **BlockRun API key** billed against account credit ([sign up](https://user.blockrun.ai), [create a key](https://user.blockrun.ai/dashboard/keys), [add credit](https://user.blockrun.ai/dashboard/credits)), or a wallet signature with x402 micropayments and no account at all. Same code either way.
49
+ - 💸 **Pay per request in USDC.** x402 micropayments on Solana, Base or Arc. Agents can fund and pay their own way.
50
+ - 🛡️ **Automatic failover.** Transient errors (timeouts, 429, 5xx) before payment walk the router's ranked fallback chain instead of failing your request; a failure after a payment was sent never buys a second model.
51
+ - ⚡ **Streaming, OpenAI & Anthropic compat.** Drop-in `chat.completions` / `messages` layers, SSE streaming, strict TypeScript.
52
+ - 🎨 **Beyond chat.** Image, video, music, speech, live search, prediction markets, market data, and <!-- br:chains.rpc -->40<!-- /br:chains.rpc -->-chain RPC through the same API key or wallet.
53
53
 
54
54
  ## How It Compares
55
55
 
@@ -65,7 +65,7 @@ console.log(r.response); // the proof
65
65
  ## Installation
66
66
 
67
67
  ```bash
68
- npm install @blockrun/llm # Account API keys or Base wallets; smart routing included
68
+ npm install @blockrun/llm # Account API keys, Base and Arc wallets; smart routing included
69
69
  ```
70
70
 
71
71
  <details>
@@ -86,20 +86,38 @@ without them throws an error naming the exact install command.
86
86
  </details>
87
87
 
88
88
  <details>
89
- <summary><strong>Supported chains</strong> — Solana (recommended), Base, Base Sepolia</summary>
89
+ <summary><strong>Supported chains</strong> — Solana (recommended), Base, Arc, Base Sepolia</summary>
90
90
 
91
- | Chain | Network | Payment | Status |
92
- |-------|---------|---------|--------|
93
- | **Solana** | Solana Mainnet | USDC (SPL) | Recommended for new wallets |
94
- | **Base** | Base Mainnet (Chain ID: 8453) | USDC | Supported |
95
- | **Base Testnet** | Base Sepolia (Chain ID: 84532) | Testnet USDC | Development |
91
+ Only relevant in wallet mode; an account API key is chain-independent.
96
92
 
97
- **Protocol:** x402 v2 (CDP Facilitator)
93
+ | Chain | Network | Payment | Notes |
94
+ |-------|---------|---------|-------|
95
+ | **Solana** | Solana Mainnet | USDC (SPL) | Recommended for new wallets; `SolanaLLMClient` |
96
+ | **Base** | Base Mainnet (`eip155:8453`) | USDC | `LLMClient`; settled by the Coinbase CDP facilitator |
97
+ | **Arc** | Circle Arc (`eip155:5042`) | USDC | `LLMClient` with `apiUrl: 'https://arc.blockrun.ai/api'`; settled by Circle's facilitator |
98
+ | **Base Sepolia** | Base Sepolia (`eip155:84532`) | Testnet USDC | Development |
99
+
100
+ **Protocol:** x402 v2.
98
101
 
99
102
  </details>
100
103
 
101
104
  The Anthropic SDK is a runtime dependency because the public compatibility wrapper exposes its types. Solana signing dependencies remain optional and are unnecessary for account billing.
102
105
 
106
+ ## Documentation map
107
+
108
+ | If you want to… | Start here |
109
+ |-----------------|------------|
110
+ | Let the SDK choose the cheapest capable model | [Smart Routing](#smart-routing-router-core-v3) |
111
+ | Try it for $0 | [Try It Free](#try-it-free-no-usdc-required) and [Free Tier](#free-tier) |
112
+ | Understand wallet payments and settlement | [How Payment Works](#how-payment-works) |
113
+ | Pay from a Solana wallet | [Solana Support](#solana-support) |
114
+ | Enable metered batch settlement | [Batch settlement](#batch-settlement-optional-metered-billing) and the [full reference](https://github.com/BlockRunAI/blockrun-llm-ts/blob/main/docs/solana-batch.md) |
115
+ | Stream tokens, use tools | [Chat, Tools and Streaming](#chat-tools-and-streaming) |
116
+ | Generate media or query data services | [Models and Service APIs](#models-and-service-apis) |
117
+ | Use the OpenAI or Anthropic SDK surface | [OpenAI and Anthropic Compatibility](#openai-and-anthropic-compatibility) |
118
+ | Create, find or import a wallet | [Wallets](#wallets) |
119
+ | Configure production credentials safely | [Configuration and Environment Variables](#configuration-and-environment-variables) and [Security](#security) |
120
+
103
121
  ## Quick Start
104
122
 
105
123
  Choose one billing mode. The chat and routing APIs are the same in every mode.
@@ -108,11 +126,14 @@ Choose one billing mode. The chat and routing APIs are the same in every mode.
108
126
  |------|------------|------------|----------|
109
127
  | **Account** | `BLOCKRUN_API_KEY` | BlockRun account credit | Applications and teams that want the simplest setup |
110
128
  | **Solana wallet** | `SOLANA_WALLET_KEY` | Per-request USDC over x402 | Agents and non-custodial workloads; recommended wallet chain |
111
- | **Base wallet** | `BASE_CHAIN_WALLET_KEY` | Per-request USDC over x402 | Existing EVM wallets and Base-native applications |
129
+ | **Base wallet** | `BASE_CHAIN_WALLET_KEY` | Per-request USDC over x402 | Existing EVM wallets and Base-native applications (the same key pays on Arc) |
112
130
 
113
- Credential precedence is deterministic: an explicit `apiKey` or `privateKey`
114
- wins; otherwise `BLOCKRUN_API_KEY` wins over wallet environment variables.
115
- Passing both explicit credentials is an error.
131
+ Credential precedence is deterministic. An explicit `apiKey` wins over the
132
+ environment, and an explicit `privateKey` selects wallet mode even when
133
+ `BLOCKRUN_API_KEY` is set; passing both explicitly is an error. With no explicit
134
+ credential, `BLOCKRUN_API_KEY` beats the wallet key env vars, so a process holding
135
+ both runs in account mode. An invalid or exhausted API key never falls back to a
136
+ wallet payment.
116
137
 
117
138
  ### Account API key
118
139
 
@@ -127,49 +148,42 @@ export BLOCKRUN_API_KEY=brk_live_...
127
148
  ```typescript
128
149
  import { LLMClient, ImageClient, VideoClient, BlockrunClient } from '@blockrun/llm';
129
150
 
130
- const llm = new LLMClient(); // Reads BLOCKRUN_API_KEY
151
+ const llm = new LLMClient(); // reads BLOCKRUN_API_KEY
152
+
153
+ // Let the router pick the cheapest capable model...
154
+ const routed = await llm.smartChat('Explain x402 in one paragraph.');
155
+ console.log(routed.model, routed.response);
156
+
157
+ // ...or pin one yourself
131
158
  console.log(await llm.chat('openai/gpt-5.2', 'Hello!'));
159
+
132
160
  const images = new ImageClient({ apiKey: process.env.BLOCKRUN_API_KEY });
133
- const videos = new VideoClient(); // Same account; async jobs are polled automatically
134
- const api = new BlockrunClient();
135
- // Generic access to Responses and other service endpoints:
161
+ const videos = new VideoClient(); // same account; async jobs are polled for you
162
+ const api = new BlockrunClient(); // generic access, e.g. the Responses API
136
163
  const response = await api.post('/v1/responses', { model: 'openai/gpt-5.2', input: 'Hello!' });
137
164
  ```
138
165
 
139
- All named service clients, `SolanaLLMClient`, and the OpenAI/Anthropic compatibility
140
- wrappers accept `apiKey`. They use `https://api.blockrun.ai`; an OpenAI-style
141
- `https://api.blockrun.ai/v1` base is also accepted. Override with `apiUrl`
142
- (`baseURL` for `OpenAI`) or `BLOCKRUN_API_BASE_URL`.
143
-
144
- An explicit `apiKey` wins over the environment. An explicit `privateKey` selects
145
- wallet mode even when `BLOCKRUN_API_KEY` is set; passing both explicit credentials
146
- is an error. With no explicit credential, `BLOCKRUN_API_KEY` beats the wallet key
147
- env vars — a process holding both runs in account mode. Invalid or exhausted API keys never fall back to wallet payments.
148
- Errors preserve `statusCode`, account error `response.code`, and `retryAfter`.
149
- Account credentials are restricted to the configured origin, including polling. Account POSTs are not automatically replayed, including through the Anthropic wrapper. GET/HEAD requests can retry temporary gateway errors; accepted jobs keep polling the same job within the original timeout. A 429 returns `retryAfter` to the caller.
150
-
151
- Check [Activity](https://user.blockrun.ai/dashboard/activity) for account usage and charges. Chat uses token usage; media and data can use duration, image, or per-request units. When adding credit, the checkout shows the credit amount and total card charge, including any processing fee. Keep API keys in server or local environment variables, never in browser code or logs.
152
-
153
- For asynchronous jobs, retain the complete returned `poll_url`, including its query parameters. If polling times out, check the original job and Activity before submitting again.
154
-
155
- To switch back to a wallet, unset `BLOCKRUN_API_KEY` and create a new wallet client, or pass an explicit `privateKey` to the appropriate client. Existing clients retain their original credentials; changing a wallet chain does not change account billing.
156
-
157
- `getSpending()` reports x402 settlements only and throws in account mode; wallet
158
- address/balance helpers require a wallet. Account credit does not sign trades or
159
- transfer wallet funds. Service availability depends on the account gateway and model.
166
+ Every client accepts `apiKey`: the service clients, `SolanaLLMClient`, and the
167
+ OpenAI/Anthropic compatibility wrappers. Usage and charges are on
168
+ [Activity](https://user.blockrun.ai/dashboard/activity). Keep API keys in server
169
+ or local environment variables, never in browser code or logs. Base URL
170
+ overrides, retry and polling rules, and switching back to a wallet are under
171
+ [Account mode details](#account-mode-details).
160
172
 
161
173
  ### Solana wallet
162
174
 
163
175
  ```typescript
164
176
  import { setupAgentClient, SolanaLLMClient } from '@blockrun/llm';
165
177
 
166
- // API key when configured; otherwise new wallets use Solana.
167
- // Existing chain preferences and Base-only wallets are preserved.
178
+ // Uses BLOCKRUN_API_KEY when set. Otherwise it honours a saved chain preference,
179
+ // keeps an existing Base-only wallet on Base, and creates new wallets on Solana.
168
180
  const client = await setupAgentClient();
169
- console.log(await client.smartChat('Explain photosynthesis.'));
181
+ const routed = await client.smartChat('Explain photosynthesis.');
182
+ console.log(routed.model, routed.response);
170
183
 
171
- // Explicit Solana wallet; requires the optional Solana dependencies below.
184
+ // Or construct the Solana client explicitly (needs the Solana peers from Installation above).
172
185
  const solana = new SolanaLLMClient({ privateKey: process.env.SOLANA_WALLET_KEY });
186
+ console.log(await solana.chat('deepseek/deepseek-chat', 'gm')); // pin a model
173
187
  ```
174
188
 
175
189
  ### Base wallet
@@ -177,130 +191,108 @@ const solana = new SolanaLLMClient({ privateKey: process.env.SOLANA_WALLET_KEY }
177
191
  ```typescript
178
192
  import { LLMClient } from '@blockrun/llm';
179
193
 
180
- const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
194
+ const client = new LLMClient(); // BASE_CHAIN_WALLET_KEY; the key signs locally and is never sent
181
195
 
182
196
  // Recommended: let the router pick the cheapest capable model
183
197
  const result = await client.smartChat('Hello!');
198
+ console.log(result.model, result.response);
184
199
 
185
200
  // Or pin a model yourself
186
- const response = await client.chat('openai/gpt-4o', 'Hello!');
201
+ const reply = await client.chat('openai/gpt-5.4', 'Hello!');
187
202
  ```
188
203
 
189
- That's it. The SDK handles x402 payment automatically — and `smartChat()`
190
- keeps the bill down on every request. The router is bundled: no extra
191
- package to install.
204
+ That's it. The SDK handles the x402 payment, and `smartChat()` keeps the bill
205
+ down on every request. The router is bundled: no extra package to install.
206
+ To pay on Arc instead, point the same client at Arc's gateway; see
207
+ [Arc Support](#arc-support).
192
208
 
193
209
  ### Try It Free (No USDC Required)
194
210
 
195
- Want to kick the tires before funding a wallet? Route to BlockRun's free tier:
211
+ Want to kick the tires before funding a wallet? Use BlockRun's free tier:
196
212
 
197
213
  ```typescript
198
214
  import { LLMClient } from '@blockrun/llm';
199
215
 
200
- const client = new LLMClient(); // Wallet still required for signing, but $0 charged
216
+ // The client still needs a wallet key or an API key to construct, but free
217
+ // models are answered without a payment: nothing is signed and nothing is charged.
218
+ const client = new LLMClient();
201
219
 
202
- // Option 1: call a free model directly
220
+ // Option 1: call a free model directly (guaranteed $0)
203
221
  const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'Explain x402 in 1 sentence');
204
222
 
205
- // Option 2: let the smart router pick — 'eco' ranks the free tier first
206
- const result = await client.smartChat('What is 2+2?', { routingProfile: 'eco' });
207
- console.log(result.model); // a free-tier model — $0 in and out
208
- console.log(result.response); // '4'
223
+ // Option 2: let the router pick: 'eco' ranks the free tier first
224
+ const result = await client.smartChat('What is the capital of France?', { routingProfile: 'eco' });
225
+ console.log(result.model); // a free model, e.g. 'nvidia/nemotron-3.5-lightning'
226
+ console.log(result.response); // 'The capital of France is Paris.'
209
227
  console.log(result.routing.savings); // 1 (100%)
210
228
  ```
211
229
 
212
- There is no `free` routing profile in `smartChat()` — `routingProfile` accepts
213
- `'eco' | 'auto' | 'premium'`. (ClawRouter's `/model free` is a feature of its
214
- own proxy, not of this SDK's router options.) For guaranteed $0, pin one of
215
- the free model ids below; for smart-routed $0-first, use `eco`.
216
-
217
- **Available free models** — input and output both $0. The free tier is **no
218
- longer NVIDIA-only**, so pin these by full model id rather than by an
219
- `nvidia/*` prefix. Full contexts and notes in [Free Tier](#free-tier);
220
- `client.listModels()` returns the live catalog at runtime.
221
-
222
- | Model ID | Context | Best For |
223
- |----------|---------|----------|
224
- | `nvidia/nemotron-3.5-lightning` | 1M | Thinking-mode reasoning at 1M context |
225
- | `nvidia/nemotron-3-ultra-550b` | 1M | Largest free model — 550B |
226
- | `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` | 256K | Multimodal reasoning — text + images |
227
- | `nvidia/llama-3.2-11b-vision` | 128K | Vision-language — accepts images |
228
- | `cohere/north-mini-code` | 256K | Compact coding model, sub-second responses |
229
- | `poolside/laguna-xs-2.1` | 128K | Coding model |
230
-
231
- ## Documentation map
232
-
233
- | If you want to… | Start here |
234
- |-----------------|------------|
235
- | Let the SDK choose the cheapest capable model | [Smart Routing](#smart-routing-router-core-v3) |
236
- | Pay from a Solana wallet | [Solana Support](#solana-support) |
237
- | Enable metered batch settlement | [Batch settlement](#batch-settlement-optional-metered-billing) |
238
- | Understand wallet payments and settlement | [How Payment Works](#how-payment-works) |
239
- | Use the OpenAI or Anthropic SDK surface | [Streaming](#streaming) and [Anthropic SDK Compatibility](#anthropic-sdk-compatibility) |
240
- | Generate media or query data services | [Models and service APIs](#models-and-service-apis) |
241
- | Configure production credentials safely | [Configuration](#configuration) and [Security](#security) |
230
+ The seven free models are listed under [Free Tier](#free-tier). `eco` lands on
231
+ a free model when one can handle the request and moves to the cheapest paid
232
+ model when none can; pin a free id when the call must cost $0.
242
233
 
243
234
  ## Smart Routing (Router Core V3)
244
235
 
245
- Let the SDK automatically pick the cheapest capable model for each request — **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper than pinning Claude Opus 5** for the same traffic on `auto`, **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** on `eco`.
236
+ Let the SDK pick the cheapest capable model for each request: **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper than pinning Claude Opus 5** for the same traffic on `auto`, **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** on `eco`.
246
237
 
247
238
  Not an "up to" figure. The baseline, the workload mix and the token ratio are
248
239
  published in [`savings-mix.json`](https://github.com/BlockRunAI/blockrun/blob/main/src/brand/savings-mix.json),
249
240
  priced against the live catalog, so anyone can recompute the claim and get the
250
- same answer.
241
+ same answer. The per-request `routing.savings` on each response is measured
242
+ against router-core's baseline model, `anthropic/claude-opus-4.7`, which is
243
+ priced the same as Claude Opus 5 ($5 input / $25 output per million tokens).
251
244
 
252
245
  Smart routing is powered by the product-neutral
253
- [`@blockrun/router-core`](https://github.com/BlockRunAI/router-core) V3 engine —
246
+ [`@blockrun/router-core`](https://github.com/BlockRunAI/router-core) V3 engine,
254
247
  the same deterministic portfolio router that drives
255
248
  [ClawRouter](https://github.com/BlockRunAI/ClawRouter). It is **bundled into
256
- this SDK**: no separate router package to install, and routing runs 100%
257
- locally with zero external calls.
249
+ this SDK**: no separate router package to install, and the routing decision
250
+ runs locally with no extra model call.
258
251
 
259
252
  Three ways to use it:
260
253
 
261
254
  ```typescript
262
- // 1. smartChat() — one-line routed chat
263
- const result = await client.smartChat('What is 2+2?');
255
+ import { LLMClient, type ChatMessage, type Tool } from '@blockrun/llm';
256
+
257
+ const client = new LLMClient();
258
+ const messages: ChatMessage[] = [{ role: 'user', content: 'What is the weather in Paris?' }];
259
+ const tools: Tool[] = [{
260
+ type: 'function',
261
+ function: {
262
+ name: 'get_weather',
263
+ description: 'Current weather for a city',
264
+ parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
265
+ },
266
+ }];
264
267
 
265
- // 2. smartChatCompletion() — full agent/tool conversations, routed
268
+ // 1. smartChat(): one-line routed chat
269
+ const result = await client.smartChat('What is the capital of France?');
270
+ console.log(result.model); // 'google/gemini-2.5-flash'
271
+ console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 90%': this request vs the baseline
272
+
273
+ // 2. smartChatCompletion(): full agent / tool conversations, routed
266
274
  const agent = await client.smartChatCompletion(messages, { tools, toolChoice: 'auto' });
267
275
 
268
- // 3. blockrun/auto | blockrun/eco | blockrun/premium — model aliases accepted
269
- // by chat(), chatCompletion(), and chatCompletionStream() on both chains
276
+ // 3. Model aliases: blockrun/auto | blockrun/eco | blockrun/premium
270
277
  const reply = await client.chatCompletion('blockrun/auto', messages);
271
278
 
272
279
  // Inspect a decision without paying for anything
273
- const decision = await client.route('Prove the Riemann hypothesis');
280
+ const decision = await client.route('Prove the Riemann hypothesis step by step');
281
+ console.log(decision.model); // 'deepseek/deepseek-v4-pro'
282
+ console.log(decision.method); // 'portfolio'
283
+ console.log(decision.taskType); // 'reasoning'
284
+ console.log(decision.candidates); // ranked, capability-eligible models
285
+ console.log(decision.fallbacks); // ['google/gemini-3.5-flash', 'deepseek/deepseek-reasoner', 'xai/grok-4.3', ...]
274
286
  ```
275
287
 
276
- The aliases are resolved locally by `LLMClient`, `SolanaLLMClient`, and the
277
- OpenAI-compat layer. The Anthropic-compat layer proxies straight to the
278
- gateway's `/v1/messages` and does **not** resolve them — pass a concrete
279
- model id there.
280
-
281
- ```typescript
282
- import { LLMClient } from '@blockrun/llm';
283
-
284
- const client = new LLMClient();
285
-
286
- // Auto-routes to cheapest capable model
287
- const result = await client.smartChat('What is 2+2?');
288
- console.log(result.response); // '4'
289
- console.log(result.model); // 'qwen/qwen3.7-flash' (cheap, fast)
290
- console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // this request, vs the Opus 5 baseline
291
-
292
- // Complex reasoning task -> routes to reasoning model
293
- const complex = await client.smartChat('Prove the Riemann hypothesis step by step');
294
- console.log(complex.model); // 'xai/grok-4.3'
288
+ The aliases are resolved locally by `chat()` and `chatCompletion()` on both
289
+ `LLMClient` and `SolanaLLMClient`, by `LLMClient.chatCompletionStream()`, and by
290
+ the OpenAI-compatible wrapper. `SolanaLLMClient.stream()` and the
291
+ Anthropic-compatible wrapper (which proxies straight to the gateway's
292
+ `/v1/messages`) do **not** resolve them; pass a concrete model id there.
295
293
 
296
- // Inspect how the request was classified and ranked (Router Core V3.5 portfolio).
297
- console.log(complex.routing.method); // 'portfolio'
298
- console.log(complex.routing.taskType); // 'reasoning'
299
- console.log(complex.routing.candidates); // ranked, capability-eligible models
300
-
301
- // Inspect the fallback chain SmartChat will walk on transient errors.
302
- console.log(complex.routing.fallbacks); // ['anthropic/claude-opus-4.7', ...]
303
- ```
294
+ Example picks in this README reflect the live catalog when it was written. The
295
+ router re-ranks as models and prices move, so your output can differ.
304
296
 
305
297
  ### Automatic Fallback on Transient Errors
306
298
 
@@ -310,7 +302,7 @@ returns a transient error — timeouts, network failures, 429 rate limits, or
310
302
  5xx responses (502/503/504/522/524) — **before any payment was sent**. Other
311
303
  4xx errors and `PaymentError` propagate immediately so wallet / auth issues
312
304
  surface fast. (Solana's internal stale-blockhash re-sign is a separate,
313
- lower-level retry inside the payment step — see [How Payment Works](#phase-2--every-request-pays-itself-automatic-x402).)
305
+ lower-level retry inside the payment step — see [How Payment Works](#phase-2--every-request-pays-for-itself-automatic-x402).)
314
306
 
315
307
  The next model is a new paid request, so the chain is only walked when the
316
308
  failed request cannot have been charged. Every error from these calls carries
@@ -333,10 +325,12 @@ a retry disposition, which `retryDisposition(err)` returns:
333
325
  5xx from a proxy, or a timeout, is not.
334
326
 
335
327
  An error without a disposition counts as `'paid-or-in-doubt'`. Use the same
336
- check in your own retry wrapper:
328
+ check in your own retry wrapper, or pass your own fallback chain:
337
329
 
338
330
  ```typescript
339
- import { retryDisposition } from '@blockrun/llm';
331
+ import { LLMClient, retryDisposition } from '@blockrun/llm';
332
+
333
+ const client = new LLMClient();
340
334
 
341
335
  try {
342
336
  await client.chat('openai/gpt-5.2', 'hello');
@@ -344,9 +338,7 @@ try {
344
338
  if (retryDisposition(err) !== 'unpaid') throw err; // may have been charged: never resend
345
339
  // safe to retry: nothing was paid
346
340
  }
347
- ```
348
341
 
349
- ```typescript
350
342
  // Manually pass a fallback chain to chat() / chatCompletion()
351
343
  const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'hello', {
352
344
  fallbackModels: ['nvidia/nemotron-3-ultra-550b', 'cohere/north-mini-code'],
@@ -360,20 +352,26 @@ const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'hello', {
360
352
  | Profile | Strategy | Savings vs Opus 5 | Best For |
361
353
  |---------|----------|-------------------|----------|
362
354
  | `eco` | Cheapest capable model — ranks the <!-- br:models.free -->7<!-- /br:models.free -->-model free tier first | **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** | Cost-sensitive production, zero-cost testing |
363
- | `auto` | Best balance of cost/quality (default) | **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%** | General use |
364
- | `premium` | Top-tier models (OpenAI, Anthropic) | 0% | Quality-critical tasks |
355
+ | `auto` | Best balance of cost and quality (default) | **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%** | General use |
356
+ | `premium` | Highest-quality model per tier | 0% | Quality-critical tasks |
365
357
 
366
- For guaranteed $0, call a free model directly with `chat()` — see
367
- [Try It Free](#try-it-free-no-usdc-required). ClawRouter's `/model free`
368
- profile belongs to its own proxy; `smartChat()`'s options are the three above.
358
+ There is no `free` routing profile: `routingProfile` accepts
359
+ `'eco' | 'auto' | 'premium'`. (ClawRouter's `/model free` belongs to its own
360
+ proxy, not to this SDK's router options.) For guaranteed $0, call a
361
+ [free model](#free-tier) directly with `chat()`; for smart-routed $0-first,
362
+ use `eco`.
369
363
 
370
364
  ```typescript
371
- // Use premium models for complex tasks
365
+ import { LLMClient } from '@blockrun/llm';
366
+
367
+ const client = new LLMClient();
368
+
369
+ // Use premium models for quality-critical tasks
372
370
  const result = await client.smartChat(
373
371
  'Write production-grade async TypeScript code',
374
372
  { routingProfile: 'premium' }
375
373
  );
376
- console.log(result.model); // 'anthropic/claude-opus-4.7'
374
+ console.log(result.model); // 'openai/gpt-5.3-codex'
377
375
  ```
378
376
 
379
377
  ### How the Router Works
@@ -384,7 +382,7 @@ flowchart LR
384
382
  B --> C["hard filters<br/>tools · vision · context ·<br/>structured output"]
385
383
  C --> D["rank portfolio<br/>quality · cost · speed ·<br/>reliability"]
386
384
  D --> E["cheapest capable model<br/>+ ranked fallback chain"]
387
- E --> F["x402 USDC payment<br/>for this request only"]
385
+ E --> F["one request, paid by<br/>API key or x402 USDC"]
388
386
  F --> G["response<br/>+ full routing metadata"]
389
387
  ```
390
388
 
@@ -395,8 +393,9 @@ Auto uses the deterministic **Router Core V3.5 portfolio strategy**
395
393
  agentic patterns, …), enforces tool / vision / structured-output / context
396
394
  constraints as **hard filters**, then ranks an ordered candidate portfolio.
397
395
  The winner becomes `routing.model`; the rest surface as `routing.candidates`
398
- and feed SmartChat's transient-error fallback chain. Routing stays 100% local
399
- and deterministic — <1ms, no extra model call, no network hop.
396
+ and feed the transient-error fallback chain. Classification is local and
397
+ deterministic: <1ms, no extra model call. (The SDK reads `/v1/models` once to
398
+ price candidates; the catalog is cached.)
400
399
 
401
400
  Classification still maps to one of four tiers (`routing.tier`). Each
402
401
  tier × profile has a designated primary (what the rules strategy —
@@ -405,18 +404,19 @@ anchors the portfolio's candidate pool):
405
404
 
406
405
  | Tier | Example Tasks | ECO | AUTO | PREMIUM |
407
406
  |------|---------------|-----|------|---------|
408
- | SIMPLE | "What is 2+2?", definitions | nemotron-3.5-lightning (**FREE**) | gemini-2.5-flash | gemini-3.5-flash |
407
+ | SIMPLE | "What is the capital of France?", definitions | nemotron-3.5-lightning (**FREE**) | gemini-2.5-flash | gemini-2.5-flash |
409
408
  | MEDIUM | Code snippets, explanations | glm-5.3-flash | gemini-3.5-flash | gpt-5.3-codex |
410
409
  | COMPLEX | Architecture, long documents | glm-5.3-flash | gemini-3.1-pro | claude-fable-5 |
411
410
  | REASONING | Proofs, multi-step reasoning | deepseek-reasoner | deepseek-reasoner | claude-sonnet-5 |
412
411
 
413
- Since Router Core V3.5 every primary and every fallback rung is a model listed
414
- on `/v1/models` — nothing the router picks is withheld from the public pricing
415
- page, and the published savings claim is priced on those same visible models.
412
+ This table mirrors the pinned router-core `DEFAULT_ROUTING_CONFIG`;
413
+ `routing.tierConfigs` on any decision returns the map that decision used.
416
414
 
417
- This table mirrors ClawRouter's tier configs at the version this SDK pins;
418
- the [ClawRouter README](https://github.com/BlockRunAI/ClawRouter#how-it-works)
419
- is the live source of truth as models and prices move.
415
+ The router's ranking is used as-is. It can pick a model the gateway serves but
416
+ does not list on `/v1/models` (for example `nvidia/nemotron-3-nano-30b`, which
417
+ appears in `eco` fallback chains); the gateway serves those ids directly. The
418
+ only rewrite is ClawRouter's proxy namespace: `free/<model>` ids map to their
419
+ `nvidia/<model>` gateway ids, and are dropped when there is no mapping.
420
420
 
421
421
  ### Routing Metadata Reference
422
422
 
@@ -433,7 +433,7 @@ picked:
433
433
  | `candidates` | Ordered, capability-eligible models ranked by the portfolio router; the first entry is `model` |
434
434
  | `candidateScores` | Per-candidate score breakdown (`quality` / `cost` / `speed` / `reliability`), ordered with `candidates` |
435
435
  | `fallbacks` | The chain `chat()` walks on transient errors before payment (timeout / network / 429 / 5xx) — `candidates` minus the primary, with ClawRouter's proxy-namespace `free/*` ids mapped to their `nvidia/*` gateway ids (SDK-computed) |
436
- | `savings` | 0–1 fraction saved vs the premium baseline |
436
+ | `savings` | 0–1 fraction saved vs the baseline model (`anthropic/claude-opus-4.7`, priced like Opus 5) |
437
437
  | `costEstimate` / `baselineCost` | Estimated cost of the pick vs that baseline, in USD |
438
438
  | `confidence` | Sigmoid-calibrated classifier confidence, 0–1 |
439
439
  | `routerVersion` | `'v3-portfolio'` or `'v2-rules'` |
@@ -451,14 +451,38 @@ dependable path.
451
451
 
452
452
  ### TypeScript Types
453
453
 
454
- `RoutingDecision`, `RoutingProfile`, `RoutingTier`, `RoutingTaskType`,
455
- `RoutingTierConfig`, `SmartChatCompletionOptions`, and
456
- `SmartChatCompletionResponse` are exported from `@blockrun/llm`. They are
457
- derived from
458
- [`@blockrun/router-core`](https://github.com/BlockRunAI/router-core), pinned
459
- to a reviewed immutable commit, and shipped **inlined in this SDK's
460
- declaration files and runtime bundle** — you install nothing extra to route
461
- or to typecheck.
454
+ Everything is typed. The routing types are derived from
455
+ [`@blockrun/router-core`](https://github.com/BlockRunAI/router-core), pinned to
456
+ a reviewed immutable commit, and shipped **inlined in this SDK's declaration
457
+ files and runtime bundle**, so you install nothing extra to route or to
458
+ typecheck.
459
+
460
+ ```typescript
461
+ import {
462
+ LLMClient,
463
+ OpenAI,
464
+ APIError,
465
+ PaymentError,
466
+ type ChatMessage,
467
+ type ChatResponse,
468
+ type ChatOptions,
469
+ type ChatCompletionOptions,
470
+ type Model,
471
+ // Smart routing
472
+ type SmartChatOptions,
473
+ type SmartChatResponse,
474
+ type SmartChatCompletionOptions,
475
+ type SmartChatCompletionResponse,
476
+ type RoutingDecision,
477
+ type RoutingProfile,
478
+ type RoutingTier,
479
+ type RoutingTaskType,
480
+ type RoutingTierConfig,
481
+ // OpenAI-compatible layer
482
+ type OpenAIChatCompletionResponse,
483
+ type OpenAIChatCompletionChunk, // what the stream: true iterator yields
484
+ } from '@blockrun/llm';
485
+ ```
462
486
 
463
487
  ### Going Deeper
464
488
 
@@ -469,6 +493,109 @@ or to typecheck.
469
493
  - [ClawRouter vs OpenRouter](https://github.com/BlockRunAI/ClawRouter/blob/main/docs/clawrouter-vs-openrouter-llm-routing-comparison.md) — head-to-head comparison
470
494
  - [`@blockrun/router-core`](https://github.com/BlockRunAI/router-core) — the deterministic routing engine both share
471
495
 
496
+ ## How Payment Works
497
+
498
+ In wallet mode no account or API key is needed. You hold USDC in your own
499
+ wallet, and **every request pays for itself** with an on-chain micropayment.
500
+ Two phases:
501
+
502
+ ### Phase 1 — Fund your wallet once
503
+
504
+ First, have a wallet. `setupAgentClient()` creates one for you (see
505
+ [Wallets](#wallets)), or bring your own:
506
+
507
+ - **Solana:** create a wallet (Phantom, Backpack, Solflare, …) and set its bs58 secret key as `SOLANA_WALLET_KEY`.
508
+ - **Base / Arc:** create an EVM wallet (Coinbase Wallet, MetaMask, …) and set its private key as `BASE_CHAIN_WALLET_KEY`.
509
+
510
+ ```bash
511
+ # .env (never commit it)
512
+ SOLANA_WALLET_KEY=...your_bs58_secret_key
513
+ BASE_CHAIN_WALLET_KEY=0x...
514
+ ```
515
+
516
+ Then get USDC into it. You only do this when the balance runs low:
517
+
518
+ - **(a) Buy with a card (Base USDC).** Call `onramp()` to mint a one-time Coinbase Onramp link, then open the returned `pay.coinbase.com` URL: pay by card or bank in 60+ fiat currencies and the USDC lands in your wallet. The call itself is **free**. Onramp is **Base-only** (`LLMClient`), and the funding address must equal your signing wallet.
519
+ - **(b) Transfer existing USDC** to your wallet address: Base USDC for `LLMClient` on Base, USDC on Arc for Arc, Solana SPL USDC for `SolanaLLMClient`.
520
+ - **(c) Skip funding entirely.** Use the [free models](#free-tier): every call is **$0**, no balance required.
521
+
522
+ ```typescript
523
+ import { LLMClient } from '@blockrun/llm';
524
+
525
+ const client = new LLMClient();
526
+
527
+ const { url } = await client.onramp(client.getWalletAddress());
528
+ console.log(`Fund your wallet: ${url}`); // single-use, expires in ~5 min: mint it at click time
529
+
530
+ const balance = await client.getBalance(); // USDC on Base
531
+ console.log(`Balance: $${balance.toFixed(2)} USDC`);
532
+ ```
533
+
534
+ $5 of USDC covers thousands of paid requests. `SolanaLLMClient` has its own
535
+ `getBalance()` (USDC on Solana) and an async `getWalletAddress()`.
536
+
537
+ ### Phase 2 — Every request pays for itself (automatic x402)
538
+
539
+ You just call `client.smartChat(...)` or `client.chat(...)`; the payment is invisible:
540
+
541
+ 1. The SDK sends the request to BlockRun's gateway.
542
+ 2. The gateway answers **402 Payment Required** with the price.
543
+ 3. The SDK signs the payment **locally** with your wallet key: an EIP-3009
544
+ USDC authorization (EIP-712 typed data) on Base or Arc for `LLMClient`, an
545
+ SPL `TransferChecked` transaction on Solana for `SolanaLLMClient`.
546
+ 4. The request is retried automatically with the payment proof.
547
+ 5. The gateway's facilitator settles on-chain and the AI response comes back.
548
+
549
+ One call, no separate pay step. Free-tier models settle at **$0** (no payment
550
+ signed). You pay no gas on any chain: the facilitator submits the transfer and
551
+ pays the network fee.
552
+
553
+ On **Solana**, step 3 pins the payment to a recent blockhash that is valid for
554
+ roughly 60 seconds. If one expires between signing and verification,
555
+ `SolanaLLMClient` re-signs against a fresh blockhash and retries — up to twice,
556
+ with a short backoff — rather than surfacing a payment error you would only
557
+ have to retry by hand.
558
+
559
+ The retry is deliberately narrow. It fires only when the gateway explicitly
560
+ reports a *verification-phase* stale blockhash. Settlement failures, ambiguous
561
+ rejections, insufficient funds and malformed responses all fail immediately.
562
+ Verification runs strictly before settlement, so a retryable rejection means no
563
+ transaction was broadcast and you cannot be charged twice.
564
+
565
+ ### Track spend and verify settlements
566
+
567
+ ```typescript
568
+ import { LLMClient, getCostSummary } from '@blockrun/llm';
569
+
570
+ const client = new LLMClient();
571
+
572
+ const spent = client.getSpending(); // this client, wallet mode only (throws with an API key)
573
+ console.log(`Spent $${spent.totalUsd.toFixed(4)} across ${spent.calls} calls`);
574
+
575
+ const summary = getCostSummary(); // across sessions, from ~/.blockrun/cost_log.jsonl
576
+ console.log(`Lifetime: $${summary.totalUsd.toFixed(2)} over ${summary.calls} calls`);
577
+ console.log('By model:', summary.byModel);
578
+ ```
579
+
580
+ Only `LLMClient` wallet settlements (Base / Arc chat) are appended to
581
+ `~/.blockrun/cost_log.jsonl` automatically. Other clients count their payments
582
+ in their own `getSpending()`. Account usage lives on the
583
+ [Activity](https://user.blockrun.ai/dashboard/activity) page, not in-process.
584
+
585
+ In wallet mode, every paid request is a real on-chain USDC transfer — look up
586
+ your wallet address on [Basescan](https://basescan.org) (or a Solana or Arc
587
+ explorer) to verify each settlement independently. The exception is Solana
588
+ [batch settlement](#batch-settlement-optional-metered-billing): there the
589
+ on-chain records are the channel deposit and BlockRun's batched redemptions,
590
+ and each call is a signed voucher.
591
+
592
+ **Your private key never leaves your machine:** it is only used for local
593
+ signing. With per-request (`exact`) wallet payments, USDC moves from your
594
+ wallet for each request and BlockRun holds none of your funds. Two exceptions
595
+ to keep in mind: account credit is held by BlockRun on your account, and a
596
+ Solana batch channel escrows a deposit the operator can claim from, up to
597
+ `maxDeposit`.
598
+
472
599
  ## Solana Support
473
600
 
474
601
  Pay for AI calls with Solana USDC via [sol.blockrun.ai](https://sol.blockrun.ai):
@@ -476,18 +603,19 @@ Pay for AI calls with Solana USDC via [sol.blockrun.ai](https://sol.blockrun.ai)
476
603
  ```typescript
477
604
  import { SolanaLLMClient } from '@blockrun/llm';
478
605
 
479
- // SOLANA_WALLET_KEY env var (bs58-encoded Solana secret key)
606
+ // SOLANA_WALLET_KEY env var (bs58-encoded Solana secret key),
607
+ // or pass { privateKey: 'your-bs58-solana-key' } / { apiKey: 'brk_live_…' }
480
608
  const client = new SolanaLLMClient();
481
609
 
482
- // Or pass key directly
483
- const client2 = new SolanaLLMClient({ privateKey: 'your-bs58-solana-key' });
610
+ // Same API as LLMClient: route first...
611
+ const routed = await client.smartChat('gm Solana: what is x402?');
612
+ console.log(routed.model, routed.response);
484
613
 
485
- // Same API as LLMClient
486
- const response = await client.chat('openai/gpt-4o', 'gm Solana');
487
- console.log(response);
614
+ // ...or pin a model
615
+ const response = await client.chat('deepseek/deepseek-chat', 'gm Solana');
488
616
 
489
- // Live Search with Grok (Solana payment)
490
- const tweet = await client.chat('xai/grok-4.5', 'What is trending on X?', { search: true });
617
+ // Live Search with Grok
618
+ const trending = await client.chat('xai/grok-4.5', 'What is trending on X?', { search: true });
491
619
 
492
620
  // xAI x_search on chosen accounts, structured output, real cost per call
493
621
  const res = await client.chatCompletion('xai/grok-4.3', [
@@ -507,20 +635,27 @@ console.log(res.costUsd, res.settlement); // what you paid, and the tx
507
635
  (Base / Arc). `costUsd` is what the call added to `getSpending()`: the signed
508
636
  price under `exact`, the metered `extra.chargedAmount` under batch settlement,
509
637
  `0` for a free model; it is undefined in API-key mode (billed to the account).
638
+ When the gateway answers a paid request from a free fallback model it skips
639
+ settlement: `costUsd` is `0`, `settlement.skipped` is `"free-fallback"`, and
640
+ nothing is added to `getSpending()`.
510
641
 
511
642
  **Setup:**
512
643
  1. Export your Solana wallet key: `export SOLANA_WALLET_KEY="your-bs58-key"`
513
- 2. Fund with USDC on Solana mainnet
644
+ 2. Fund it with USDC on Solana mainnet
514
645
  3. That's it — payments are automatic via x402
515
646
 
516
- **Supported endpoint:** `https://sol.blockrun.ai/api`
517
- **Payment:** Solana USDC (SPL, mainnet)
647
+ **Endpoint:** `https://sol.blockrun.ai/api` · **Payment:** Solana USDC (SPL, mainnet)
518
648
 
519
649
  **Images, music and video on Solana:**
520
650
 
521
651
  ```typescript
652
+ import { SolanaLLMClient } from '@blockrun/llm';
653
+
654
+ const client = new SolanaLLMClient();
655
+
522
656
  const image = await client.image('a lighthouse at dusk', { model: 'openai/gpt-image-2' });
523
657
  const track = await client.music('chill lo-fi beats with piano');
658
+ // Extra video options are sent verbatim, so use the gateway's snake_case names.
524
659
  const video = await client.video('a red apple slowly spinning', { duration_seconds: 5 });
525
660
  ```
526
661
 
@@ -528,7 +663,7 @@ Slow jobs answer `202 { id, poll_url }` and the client polls for you. Each poll
528
663
  proves the job is yours with a wallet signature (`x-poll-wallet`,
529
664
  `x-poll-timestamp`, `x-poll-signature`: ed25519 over
530
665
  `blockrun-poll:v1:<kind>:<job id>:<unix seconds>`), not a payment, so a
531
- multi-minute job no longer signs a Solana transaction every few seconds. Image
666
+ multi-minute job does not sign a Solana transaction every few seconds. Image
532
667
  and music are paid when submitted. A video is charged only once it is finished:
533
668
  the client signs one payment for the completed job, and a failed job costs
534
669
  nothing. If the gateway does not accept the poll signature for a job, the client
@@ -537,26 +672,18 @@ unchanged.
537
672
 
538
673
  ### Batch settlement (optional metered billing)
539
674
 
540
- By default, every Solana call uses x402 `exact`: one SPL transfer per call,
541
- priced at the request ceiling and settled before the model answers. Batch
542
- settlement replaces those per-call transfers with a bounded payment channel.
543
- The client locks a small deposit once, each request authorizes no more than its
544
- quoted ceiling, and the gateway charges the metered cost after the response.
675
+ By default every Solana call uses x402 `exact`: one SPL transfer per call,
676
+ priced at the request ceiling. Batch settlement replaces those per-call
677
+ transfers with a bounded payment channel: the client locks a small deposit
678
+ once, each request authorizes no more than its quoted ceiling, and the gateway
679
+ charges the **metered** cost after the response.
545
680
 
546
- > **No operator key needs to be created, copied, or stored in an environment
547
- > variable.** `BLOCKRUN_SOL_OPERATOR` is a public constant exported by this SDK.
548
- > Adding it to `batch.operators` is the explicit opt-in: it tells the client that
549
- > this wallet trusts BlockRun's operator to sign vouchers against the channel,
550
- > up to `maxDeposit`.
681
+ It is off unless you opt in. `BLOCKRUN_SOL_OPERATOR` is a public constant
682
+ exported by this SDK, not a secret or an environment variable; listing it in
683
+ `batch.operators` is the explicit decision that this wallet trusts BlockRun's
684
+ operator to sign vouchers against the channel, up to `maxDeposit`.
551
685
 
552
- Without a `batch` option, behavior is unchanged and every request uses `exact`.
553
- If batch is unavailable or unsafe for a request, the SDK falls back to `exact`
554
- before it sends a batch payment. Once one is sent, a call whose payment may have
555
- been charged is raised as `BatchPaymentUnresolvedError`, never paid again (see
556
- [Runtime behavior](#runtime-behavior)).
557
-
558
- In addition to the Solana wallet dependencies from [Installation](#installation),
559
- batch mode needs three optional peer dependencies:
686
+ Batch mode needs three more optional peers on top of the Solana ones:
560
687
 
561
688
  ```bash
562
689
  npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit
@@ -568,394 +695,252 @@ import { SolanaLLMClient, BLOCKRUN_SOL_OPERATOR } from '@blockrun/llm';
568
695
  const client = new SolanaLLMClient({
569
696
  privateKey: process.env.SOLANA_WALLET_KEY,
570
697
  batch: {
571
- // Provided by @blockrun/llm. This explicit trust decision enables batch.
572
- operators: [BLOCKRUN_SOL_OPERATOR],
573
- // Most escrow at stake in the channel: deposited and not yet settled. Default: "$1".
574
- maxDeposit: '$5',
698
+ operators: [BLOCKRUN_SOL_OPERATOR], // explicit trust; there is no default
699
+ maxDeposit: '$5', // most escrow at stake at once (default "$1")
575
700
  },
576
701
  });
577
702
 
578
- // The first call opens the channel. The deposit is the 402's extra.minDeposit
579
- // (the @x402/svm server defaults it to 3x this call's ceiling), or 5x the
580
- // ceiling when the 402 names none, never more than the room maxDeposit leaves.
581
- // The call that opens or tops up the channel is charged its quoted price, as
582
- // it would be with exact.
583
- await client.chat('openai/gpt-4o-mini', 'gm');
703
+ // The first call opens the channel and is charged its quoted price, as with exact.
704
+ await client.smartChat('gm');
584
705
 
585
- // Every later call is metered: you pay for the tokens the call actually used.
706
+ // Later calls are metered: you pay for the tokens the call actually used.
586
707
  const reply = await client.chat('anthropic/claude-sonnet-4.6', 'Summarize x402 in one line', {
587
- maxTokens: 2048, // a ceiling, not a price: a short answer is not billed for 2,048 tokens
708
+ maxTokens: 2048, // a ceiling, not a price
588
709
  });
589
710
 
590
- console.log(client.getSpending()); // { totalUsd: <actual charges>, calls: 2 }
591
-
592
711
  // Done for good? Close the channel to get the unused escrow back.
593
- // BlockRun closes it cooperatively when it can; otherwise this starts a
594
- // payer-forced close and the escrow returns after the channel's grace period.
595
712
  await client.closeBatchChannel();
596
713
  ```
597
714
 
598
- #### Trust and spending boundary
599
-
600
- - **Explicit trust.** `operators` is required and has no implicit default. The
601
- package exports BlockRun's production public key as `BLOCKRUN_SOL_OPERATOR`
602
- (`5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3`) so applications do not
603
- duplicate it. The SDK never learns a trusted key from an untrusted 402.
604
- - **Bounded exposure.** `maxDeposit` caps the escrow at stake: the channel's
605
- deposit minus what has already been settled on-chain. A top-up is allowed
606
- only while `(deposit - settled) + topUp <= maxDeposit`, so a long-lived
607
- channel keeps topping up as BlockRun settles what it charged, and its
608
- unsettled balance never exceeds the cap. In server-signed mode that makes
609
- `maxDeposit` the most the selected operator could claim beyond what has
610
- already been settled, without another authorization from the payer. Before a
611
- top-up the SDK reads the channel's settled amount from the chain; if the
612
- RPC cannot answer, it assumes nothing was settled. If the account there
613
- cannot be read as a payment channel (another owner, too short, an
614
- unsupported encoding), or the installed `@x402/svm` lays out channel
615
- accounts differently from what the SDK decodes, it signs no top-up and the
616
- call pays `exact` (`channel_unreadable`), keeping the record.
617
- - **Key rotation.** `operators` accepts a list so an old and new BlockRun key can
618
- overlap during a controlled rotation.
619
- - **Server-signed channels only.** Batch pays only into server-signed
620
- (operator) channels, the ones `closeBatchChannel()` can refund. A batch
621
- accept that is client-signed (`extra.voucherSigner` omitted or `"client"`),
622
- which a custom gateway could offer, is never paid: the SDK ignores it, and a
623
- 402 whose only batch accepts are client-signed pays `exact`
624
- (`client_signed_not_supported`). A client-signed channel an earlier SDK
625
- version opened can still be closed: `closeBatchChannel()` refunds a stored
626
- one through `@x402/svm`'s client-signed refund when the gateway's challenge
627
- offers its client-signed accept (after the trusted server-signed channel,
628
- when both are stored).
629
-
630
- #### Runtime behavior
631
-
632
- Batch never pays twice for one call. Before a batch payment is sent, any
633
- problem falls back to `exact`. Once it is sent, the SDK decides what the
634
- gateway's answer proves, in one place:
635
-
636
- | The gateway's answer to the payment | What the SDK does |
637
- | --- | --- |
638
- | a 2xx | the call is served and booked (a missing or unreconciled receipt only makes the SDK re-read the channel before the next payment) |
639
- | first send: a 402, or a recognised refusal (`batch_payer_not_allowed`, `batch_payer_not_admitted`, `batch_admission_paused`, `batch_server_signed_only`, `batch_unavailable`, `batch_channel_limit`, `PAYMENT_VERIFICATION_UNAVAILABLE`), with no receipt or a failed one with no transaction | nothing was charged: pays `exact`. A deposit refused with a 402 or `PAYMENT_VERIFICATION_UNAVAILABLE` may still have been broadcast, so its channel is re-read from the chain before anything pays into it again, unless the 402's failed receipt names one of PayAI's `batch_*` admission refusals (capacity, rate, new channels disabled, account paused, fee-payer lane, deposit terms): that proves nothing was broadcast, so the deposit is forgotten, and the wallet's calls pay `exact` at once, without trying batch (`fallback` / `admission_refused`), for `Retry-After` (at most 15 min), else 60s, or 10 min when the deposit's own amount, asset or withdraw delay was refused |
640
- | first send: a 429 whose failed receipt proves nothing was broadcast (`batch_account_channel_capacity_exhausted`, `batch_channel_capacity_exhausted`, `batch_deposit_rate_limited`) | nothing was charged: backs off, then pays again with a new batch payment from a fresh 402; `exact` once `rateLimit` runs out |
641
- | first send: a 429 with no receipt | **in doubt**: after the backoff, replays the identical payment **once**; a 2xx with a success receipt ends it, anything else raises. A payment carrying a payment identifier is replayed as in the last row instead |
642
- | first send of an authorization: an error status with a cancelled receipt (`success: false`, `errorReason: "batch_cancelled"`, no transaction) | nothing was charged: the error is raised with retry disposition `'unpaid'`, so `fallbackModels` / `smartChat()` move on to the next model |
643
- | anything else: a timeout, an abort or a network error after sending; a 5xx or other error status without a recognised refusal; a receipt naming a transaction, saying `settlement_pending` or saying it succeeded on an error status; a 429 with any other receipt | **in doubt**: raises `BatchPaymentUnresolvedError`, unless the payment carries a payment identifier (below) |
644
- | a payment carrying a payment identifier, in doubt: no answer, a 5xx, a 429, or a 409 with `Retry-After` | **in doubt**: waits `Retry-After` (at least 1 s, at most 30 s; without one about 1s, 2s, 4s... with jitter), then replays the identical payment, and again for as long as the answer is one of these, within `inDoubt.maxWaitMs` (default 5 minutes). A 2xx with a success receipt ends it. Any other answer (a 409 without `Retry-After`, a 402, a 2xx without a receipt the SDK can verify), or running out of time, raises |
645
-
646
- - **Payment identifier.** When the gateway's 402 declares the x402
647
- `payment-identifier` extension, every batch payment carries a new id in
648
- its `extensions` (`pay_` and 32 hex digits). A gateway that stores each
649
- paid response under its id answers a replay of a completed request with
650
- the original response and receipt, and the call is served and booked
651
- once; while the original is still running, it answers 409 with a
652
- `Retry-After`. Every replay repeats the payment byte for byte: the same
653
- `PAYMENT-SIGNATURE` header (same id, same request id, same signed
654
- authorization and deposit transaction) and the same request body, which
655
- the SDK serializes once per call. This also recovers a generation that
656
- outlasts the client's `timeout` (60 s by default): the request is aborted,
657
- and the replays pick up its stored response once the gateway has it. The
658
- replay resolves the call only on a 2xx whose receipt the SDK can verify
659
- (`success: true`, and an `extra` with the operator's `voucher` for this
660
- request's charge and a `commitmentId`; a `channelState`, when present,
661
- must match). Every error answer, and a replay's 2xx without a success
662
- receipt, is held back from the channel's bookkeeping until no replay
663
- follows it; the bookkeeping then takes the last answer, unless an earlier
664
- one carried a success receipt and the later ones do not, so a charge the
665
- gateway reported on an error status is never rolled back by a replay's
666
- refusal. These replays do not count against `rateLimit`. An `exact`
667
- payment carries its own new id when the extension is declared, so a
668
- gateway that declares it `required` takes the exact fallback too. Without
669
- the extension nothing changes.
670
- - **Trust model.** The gateway is already trusted with the escrow up to
671
- `maxDeposit`, so its explicit answer to a payment's first send is taken as
672
- proof that nothing was charged. Nothing else is: not an error's type or
673
- `cause.code` (a request can reach the gateway before the connection fails),
674
- not a missing receipt, and not chain state. An answer to a replay only
675
- describes the replay: a 402 or `duplicate_settlement` on a replay means the
676
- original reached the gateway, so it stays in doubt.
677
- - **In doubt means raised, never paid again.** A call in doubt throws
678
- `BatchPaymentUnresolvedError` and is never paid again: no new
679
- authorization, no new deposit, no `exact`, and no `fallbackModels` or
680
- `smartChat()` fallback model (it is a `PaymentError` whose retry
681
- disposition is `'paid-or-in-doubt'`). It carries `wallet`, `requestId` (the
682
- payment that may have been charged), `channelId`, `payloadKind` (`'open'`,
683
- `'top-up'` or `'authorization'`), `depositInDoubt`, `status` (the gateway's
684
- last answer, when there was one), `cause` (that answer as an `APIError`, or
685
- the transport error) and `reason`:
686
- - `'replay_unresolved'`: a 429 without a receipt whose one replay got no
687
- success receipt (or `rateLimit` left no room to replay it), or a payment
688
- carrying a payment identifier whose replays got none: the last answer
689
- was one no later replay can change, or `inDoubt.maxWaitMs` ran out (the
690
- message says how many replays were sent);
691
- - `'ambiguous_rate_limit'`: a 429 whose receipt does not prove nothing was
692
- broadcast;
693
- - `'no_response'`: sending it threw (timeout, abort, network error);
694
- - `'duplicate_settlement'`: the first answer was a 402 naming
695
- `duplicate_settlement`, meaning another copy of this payment had already
696
- reached the gateway;
697
- - `'outcome_unknown'`: any other answer that neither serves the call nor
698
- proves nothing was charged.
699
-
700
- A retry is up to you, and it is a new payment:
701
-
702
- ```typescript
703
- import type { BatchPaymentUnresolvedError } from '@blockrun/llm';
704
-
705
- try {
706
- await client.smartChat('Summarize x402 in one line');
707
- } catch (err) {
708
- // Match by name: `instanceof` fails when the error comes from another
709
- // loaded copy of the SDK (the CJS and ESM builds side by side).
710
- if (err instanceof Error && err.name === 'BatchPaymentUnresolvedError') {
711
- const unresolved = err as BatchPaymentUnresolvedError;
712
- console.warn(`payment ${unresolved.requestId} from ${unresolved.wallet} may have been charged`);
713
- }
714
- throw err;
715
- }
716
- ```
717
-
718
- Raising is deliberately conservative. Resolving a payment in doubt
719
- automatically needs the gateway's help: a receipt (`PAYMENT-RESPONSE`) on
720
- every response to a paid request, and a request-status lookup that answers
721
- for a request id and refuses (fences) one it never saw, in the spirit of
722
- [idempotency keys](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/).
723
- The SDK will use them once the gateway offers them.
724
- - **Safe fallback.** These pay `exact` instead, before anything is sent or
725
- after an answer proving nothing was charged:
726
- - the 402 has no batch accept, offers only client-signed ones (`client_signed_not_supported`), or names an operator outside `operators`;
727
- - the deposit the call needs does not fit in the room `maxDeposit` leaves;
728
- - another batch call for the same wallet is in flight;
729
- - a deposit is in doubt, or a channel record is being re-read from the chain (below);
730
- - the gateway refuses batch, or a 402 answers the first send (see the table);
731
- - the gateway still answers 429 after the retries `rateLimit` allows, when nothing was charged.
732
-
733
- Parallel calls never queue behind the channel, except during a 429 cooldown (below).
734
- - **Deposit size.** An open or top-up deposits the 402's `extra.minDeposit` when it is at least the call's ceiling (the `@x402/svm` server defaults it to 3x the ceiling), otherwise 5x the ceiling. It is never less than the call needs, and never more than the room `maxDeposit` leaves: `maxDeposit - (deposit - settled)`, which for an open is all of `maxDeposit`. A call whose shortfall does not fit pays `exact` (`deposit_over_cap`).
735
- - **429 means wait, not give up.** After a 429 the call waits for `Retry-After` (seconds or an HTTP date; without one, about 1s, 2s, 4s with jitter). A `Retry-After` that is not a finite delay of at most a day is ignored, and the default backoff applies. While the wallet cools down, its other calls wait as well instead of sending new channel opens. `batch.rateLimit` bounds this per call: `maxAttempts` (default `3` batch payments sent, the first and the one replay included) and `maxWaitMs` (default `60000` ms spent waiting). The replays of a payment carrying a payment identifier are bound by `batch.inDoubt.maxWaitMs` instead (default `300000` ms, counted from its first answer in doubt, waits and replays included; no replay starts once its wait would end past it, and one already sent runs until its answer or the client's `timeout`; `0` never replays). A call that cannot wait out `Retry-After` within what is left of `maxWaitMs` pays `exact` at once, and the wallet's shared cooldown never lasts longer than `maxWaitMs`, so no single header keeps batch off for longer than that. A 429 that proves nothing was broadcast is paid again with a new payment, rebuilt from a fresh 402 challenge (one unpaid request) because the original's blockhash and slot hints may have gone stale. That unpaid request is the call itself: if the gateway serves it (a 2xx), that response is the call's result and nothing more is paid (a `recovered` event, `served_unpaid_on_rechallenge`); if it gets any other answer or none, the call throws that error with retry disposition `'unpaid'` (nothing was paid for it, so `fallbackModels` may take over). It is never paid `exact` against the stale challenge. A 429 without a receipt is never rebuilt: it gets its one replay, byte for byte (same request id, same signed authorization, same signed deposit transaction).
736
- - **A deposit in doubt blocks new deposits.** When an open or top-up is in doubt, or a deposit-bearing call was served without a receipt the SDK could reconcile, the wallet signs no batch payment at all (so no second deposit can land next to it) and its calls pay `exact` (`channel_resync_pending`) until the chain settles the deposit. The SDK reads channels at `finalized` commitment only. A deposit has landed once the finalized channel holds it. It has provably never landed once the finalized chain is 300 blocks past a block height the SDK read itself after the payment was built (a transaction expires 150 blocks after its blockhash), and a read at least that recent still does not show it: then an open's record is dropped and a top-up's channel is rewritten with the deposit it really has. Wall-clock time, and any `lastValidBlockHeight` a 402 names, are never used. This settles only the deposit, never whether the gateway charged the payment that carried it.
737
- - **No silent fallbacks.** Every fallback, every 429 backoff, every recovery, every channel re-read and every payment raised in doubt writes one line to stderr, each time it happens:
738
-
739
- ```text
740
- [@blockrun/llm] batch-settlement event=backoff reason=rate_limited status=429 errorReason=batch_deposit_rate_limited retryAfterMs=30000 attempt=1 wallet=<address> next=retry
741
- [@blockrun/llm] batch-settlement event=unresolved reason=no_response attempt=1 wallet=<address> next=raise detail="the payment got no answer (AbortError: This operation was aborted); it may have been charged, so it is not paid again"
742
- ```
743
-
744
- `batch.onEvent` receives each one as a `SolanaBatchEvent`: `{ type: 'fallback' | 'backoff' | 'recovered' | 'resync' | 'unresolved', reason, status?, errorReason?, retryAfterMs?, attempt?, detail?, wallet, at }`. `reason` is a stable code: `rate_limited`, `cooldown` or `in_doubt` (backoffs, and the `recovered` event after one, or `served_unpaid_on_rechallenge`); for fallbacks `not_offered`, `client_signed_not_supported`, `channel_busy`, `untrusted_operator`, `deposit_over_cap`, `channel_pending`, `closed_during_call`, `peer_dependency_missing`, `payment_creation_failed`, `wallet_config_conflict`, `channel_store_locked`, `deposit_journal_unreadable`, `deposit_journal_failed`, `channel_resync_pending`, `channel_resync_failed`, `channel_unreadable`, `payment_required`, `rate_limited` or one of the recognised refusal codes above; for `unresolved`, the `BatchPaymentUnresolvedError` reasons; for `resync`, the re-read reasons below. The callback may be async; one that throws or rejects is logged and never affects the payment. `client.getBatchStats()` returns this client's counters:
745
-
746
- ```typescript
747
- const client = new SolanaLLMClient({
748
- batch: {
749
- operators: [BLOCKRUN_SOL_OPERATOR],
750
- rateLimit: { maxAttempts: 3, maxWaitMs: 60_000 }, // the defaults
751
- inDoubt: { maxWaitMs: 300_000 }, // the default
752
- onEvent: (event) => metrics.increment(`batch.${event.type}`, { reason: event.reason }),
753
- },
754
- });
755
-
756
- client.getBatchStats();
757
- // { fallbacks: 1, fallbacksByReason: { rate_limited: 1 }, backoffs: 2, retries: 2, recoveries: 0,
758
- // resyncs: 0, unresolved: 0, unresolvedByReason: {} }
759
- ```
760
- - **Scope.** Batch covers non-streaming chat: `chat`, `chatCompletion`, `smartChat`, and `smartChatCompletion`. `stream()` and the image and media jobs still pay with `exact`.
761
- - **One channel per wallet.** Every `SolanaLLMClient` for a wallet in one process shares a single channel, including clients built from different loaded copies of this package (its CJS and ESM builds, or two installs). A second client with different `batch` options pays `exact`. On disk, one live process owns a wallet's channel file through a lock file holding its pid, in the same format earlier releases write and read, so a process still on an older version that shares the channel store (say, during a rolling upgrade) sees the lock as live too; a random ownership token sits beside it in `<lock>.owner`. Other processes pay `exact` until that process exits. A lock that names the current process but was not taken by this SDK version is never treated as stale; if no process uses it (say, a crashed process whose pid was reused), delete the `.lock` and `.lock.owner` files.
762
- - **State and crash safety.** The open channel is saved to `~/.blockrun/solana-batch/<wallet>.json` (mode `0600`), so a restart reuses it; a custom `channelStore` path must also be per wallet. Before an open or top-up is sent, its intent is written to `<wallet>.json.deposit-intents` beside it (the store's full path plus `.deposit-intents`, so every store has its own; mode `0600`; the file and its directory are fsynced where the platform allows), and removed once the deposit is reconciled. After a crash, every intent left there makes the new process treat that deposit as in doubt (above): it signs no deposit until the chain settles it, and it never re-sends the old payment or pays for its request again. A deposit that cannot be journaled is not sent (`deposit_journal_failed`, paid `exact`). Intents an earlier version journaled under its old name (`<wallet>.deposit-intents.json`, which a store named `<wallet>` without `.json` shared) are moved over on start, only the ones for this wallet; an old journal that cannot be read keeps batch off as below. Only a missing journal counts as empty: one that cannot be read, or is not a well-formed journal this version knows (a missing `intents`, an unknown `version`, a malformed entry), keeps batch off for the wallet and its calls pay `exact` (`deposit_journal_unreadable`); the file is left as it is. **With `channelStore: false` this crash recovery is not available:** the channel is kept in memory and found again on-chain, and a deposit that was in flight when the process died is not known to be in doubt. Nothing coordinates processes in that mode either, so use it for a single process per wallet.
763
- - **Closing.** `closeBatchChannel()` closes the channel the gateway's current 402 names: the one for its trusted operator, receiver authorizer, fee payer, asset and receiver. It first re-reads that record if it is in doubt (for example after a process died mid-deposit) and loads that channel alone, so the escrow can be recovered without another paid call; after the close the SDK forgets that channel only. During a key rotation, a channel opened with the other operator key keeps its record and can be closed once the gateway names that operator. A client-signed channel left by an earlier SDK version is closed the same way once no trusted server-signed record is left for that challenge, through the challenge's client-signed accept. It closes nothing and throws `BatchCloseDeferredError` while a chat call for the wallet is in flight (`reason: 'call_in_flight'`, including one waiting out a 429 or between the replays of a payment in doubt, so that retry can never open a channel you believe is closed) or while any of its deposits is in doubt (`reason: 'deposit_in_doubt'`, with `channelIds`); call it again once the call returns or the deposit is settled (usually a few minutes: it waits for finality). A chat that was still waiting for its first answer when a close completed pays `exact` instead of opening a new channel (`closed_during_call`).
764
- - **RPC.** Batch uses the same Solana RPC as `exact`: `rpcUrl` / `SOLANA_RPC_URL`, with `rpcHeaders` / `SOLANA_RPC_HEADERS` / `SOLANA_RPC_API_KEY` for header-auth endpoints. `@x402/svm` builds its RPC client from a URL alone, so when headers are configured the SDK adds them through a thin wrapper around the global `fetch` that only touches requests to exactly that RPC URL made during a batch payment. The RPC must support `finalized` commitment and `minContextSlot` (standard on current Solana RPCs); one that does not keeps calls on `exact`.
765
- - **A funded channel is never forgotten.** A refused or rate-limited open or top-up that charged nothing leaves the saved channel exactly as it was confirmed. When the SDK cannot trust its record — a deposit in doubt; a response whose receipt is missing or was rebuilt without a voucher; a pending deposit or journaled intent left by a process that died; a chain that shows more deposit than the record before a top-up — it re-reads that one channel by its address (`getAccountInfo` at `finalized`, no program scan) before the next payment, and rewrites the record with the on-chain deposit and the highest of its confirmed cumulative charge, the one the saved record already holds, and the on-chain settled amount. The saved cumulative never moves backwards, even when a journaled intent outlived the receipt that reconciled its deposit (vouchers are redeemed on-chain later, so the settled amount can lag). Each re-read emits a `resync` event (`reason`: `deposit_unanswered`, `deposit_failed`, `deposit_rate_limited`, `receipt_missing`, `receipt_unreconciled`, `orphaned_deposit`, `deposit_unrecorded`, `channel_unusable`; `detail`: what the chain showed). A top-up never goes into a channel the chain shows closing or closed, or that is not your wallet's for its operator and mint: that record is re-read at once (`channel_unusable`), a closed channel's record is dropped and the call carries on as with no channel (a fresh open when it fits in `maxDeposit`), and anything else keeps the record and pays `exact` (`channel_unreadable`). Until it succeeds, calls pay `exact` (`channel_resync_pending`, or `channel_resync_failed` when the chain cannot be read). Only an absent account counts as "no channel": an account at that address that cannot be read as your channel (another owner, layout, encoding, payer, operator or mint) keeps the record and pays `exact` (`channel_unreadable`), and is never a reason to sign a new deposit. Every channel read (these re-reads and the settled read before a top-up) first checks that the installed `@x402/svm` still uses the channel layout the SDK decodes; when it does not, nothing is decoded and those calls pay `exact` (`channel_unreadable`) with the record kept.
766
- - **Rollout.** `sol.blockrun.ai` lists `exact` first and advertises
767
- `batch-settlement` only when it is enabled. A configured client remains fully
768
- compatible while rollout is paused or restricted because it falls back to
769
- `exact`.
715
+ - **Explicit trust.** `operators` has no default, and the SDK never learns a trusted key from a 402.
716
+ - **Bounded exposure.** `maxDeposit` caps the escrow deposited and not yet settled on-chain; it is the most the operator could claim without another authorization from you.
717
+ - **Never pays twice.** Anything that goes wrong before a batch payment is sent falls back to `exact`. Once one is sent, a call whose payment may have been charged throws `BatchPaymentUnresolvedError` and is never paid again (no new payment, no `exact`, no fallback model).
718
+ - **Non-streaming chat only.** `chat`, `chatCompletion`, `smartChat` and `smartChatCompletion` use batch; `stream()` and media jobs pay `exact`.
719
+ - **One channel per wallet.** All clients for a wallet share one channel, saved to `~/.blockrun/solana-batch/<wallet>.json` (mode `0600`) so a restart reuses it.
720
+ - **Observable.** Every fallback, backoff, recovery and re-read writes one stderr line; `batch.onEvent` and `client.getBatchStats()` expose the same events and counters.
721
+
722
+ The full runtime contract (the answer-by-answer table, payment-identifier
723
+ replays, 429 backoff, deposit-in-doubt handling, crash safety, closing rules,
724
+ RPC requirements, and every event reason) is in
725
+ **[docs/solana-batch.md](https://github.com/BlockRunAI/blockrun-llm-ts/blob/main/docs/solana-batch.md)**.
770
726
 
771
727
  ## Arc Support
772
728
 
773
- The same `LLMClient` pays on [Circle's Arc](https://www.arc.network) via [arc.blockrun.ai](https://arc.blockrun.ai) — point `apiUrl` at it and hold USDC on Arc in the same EVM wallet:
729
+ The same `LLMClient` pays on [Circle's Arc](https://www.arc.network) via [arc.blockrun.ai](https://arc.blockrun.ai): point `apiUrl` at it and hold USDC on Arc in the same EVM wallet.
774
730
 
775
731
  ```typescript
776
732
  import { LLMClient } from '@blockrun/llm';
777
733
 
778
- const client = new LLMClient({
779
- privateKey: process.env.BLOCKRUN_WALLET_KEY,
780
- apiUrl: 'https://arc.blockrun.ai/api',
781
- });
734
+ // BASE_CHAIN_WALLET_KEY: the same EVM key you use on Base
735
+ const client = new LLMClient({ apiUrl: 'https://arc.blockrun.ai/api' });
782
736
 
783
- const response = await client.chat('openai/gpt-4o', 'gm Arc');
737
+ const routed = await client.smartChat('gm Arc');
738
+ const response = await client.chat('openai/gpt-5.4', 'gm Arc');
784
739
  ```
785
740
 
786
- The 402 from that host names `eip155:5042`, and the SDK signs the EIP-3009 authorization against Arc's USDC (`0x3600…0000`, EIP-712 domain `USDC` v2) — never Base's. Circle's facilitator verifies and settles it on Arc; you pay no gas. Which networks the SDK can sign for is the exported `EVM_NETWORKS` table (Base, Arc, Base Sepolia); a 402 naming any other network, or a non-USDC asset, is refused before anything is signed.
741
+ The 402 from that host names `eip155:5042`, and the SDK signs the EIP-3009 authorization against Arc's USDC (`0x3600…0000`, EIP-712 domain `USDC` v2), never Base's. Circle's facilitator verifies and settles it on Arc; you pay no gas. Which networks the SDK can sign for is the exported `EVM_NETWORKS` table (Base, Arc, Base Sepolia); a 402 naming any other network, or a non-USDC asset, is refused before anything is signed.
787
742
 
788
743
  **Setup:**
789
- 1. Same wallet key as Base: `export BLOCKRUN_WALLET_KEY="0x..."`
744
+ 1. Same wallet key as Base: `export BASE_CHAIN_WALLET_KEY="0x..."`
790
745
  2. Fund it with USDC on Arc (Arc's native token, shown as the ERC-20 at `0x3600…0000`)
791
746
  3. `apiUrl: 'https://arc.blockrun.ai/api'` — payments are automatic via x402
792
747
 
793
- **Supported endpoint:** `https://arc.blockrun.ai/api`
794
- **Payment:** USDC on Arc (chain 5042), settled by Circle
748
+ **Endpoint:** `https://arc.blockrun.ai/api` · **Payment:** USDC on Arc (chain 5042), settled by Circle
795
749
 
796
- ## How Payment Works
750
+ ## Chat, Tools and Streaming
797
751
 
798
- In wallet mode, no API key is required. You hold USDC in your own wallet, and **every request pays for itself** with an on-chain micropayment. Two phases:
752
+ ### Simple chat
799
753
 
800
- ### Phase 1 — Fund your wallet once
754
+ ```typescript
755
+ import { LLMClient } from '@blockrun/llm';
756
+
757
+ const client = new LLMClient(); // BLOCKRUN_API_KEY or BASE_CHAIN_WALLET_KEY
758
+
759
+ // Routed: the cheapest model that can handle it
760
+ const routed = await client.smartChat('Explain quantum computing in two paragraphs');
761
+ console.log(routed.response);
801
762
 
802
- You only do this when your balance runs low. Three ways to get USDC into your wallet:
763
+ // Pinned, with a system prompt
764
+ const haiku = await client.chat('anthropic/claude-sonnet-5', 'Write a haiku', {
765
+ system: 'You are a creative poet.',
766
+ });
767
+ ```
803
768
 
804
- - **(a) Buy with a card (Base USDC).** Call the new `onramp()` method to mint a one-time Coinbase Onramp link, then open the returned `pay.coinbase.com` URL — pay by card/bank in 60+ fiat currencies and the USDC lands in your wallet. The call itself is **free**. Onramp is **Base-only** (buying USDC with a card always lands Base USDC), and the funding address must equal your signing wallet:
769
+ ### Full chat completion
805
770
 
806
- ```typescript
807
- const { url } = await client.onramp(client.getWalletAddress());
808
- console.log(`Fund your wallet: ${url}`); // single-use, expires ~5 min — mint at click time
809
- ```
771
+ ```typescript
772
+ import { LLMClient, type ChatMessage } from '@blockrun/llm';
810
773
 
811
- - **(b) Transfer existing USDC.** Send USDC you already hold to your wallet address (`client.getWalletAddress()`). On Base, send Base USDC; on Solana (`SolanaLLMClient`), send Solana SPL USDC.
774
+ const client = new LLMClient();
812
775
 
813
- - **(c) Skip funding entirely.** Use the free models (e.g. `nvidia/nemotron-3.5-lightning`) — every call is **$0**, no balance required.
776
+ const messages: ChatMessage[] = [
777
+ { role: 'system', content: 'You are a helpful assistant.' },
778
+ { role: 'user', content: 'How do I read a file in Node.js?' },
779
+ ];
814
780
 
815
- $5 of USDC covers thousands of paid requests. Check your balance any time:
781
+ // Routed: the full OpenAI-style response plus the routing decision
782
+ const routed = await client.smartChatCompletion(messages);
783
+ console.log(routed.response.choices[0].message.content, routed.model);
816
784
 
817
- ```typescript
818
- const balance = await client.getBalance(); // USDC on Base
819
- console.log(`Balance: $${balance.toFixed(2)} USDC`);
785
+ // Or pinned
786
+ const result = await client.chatCompletion('anthropic/claude-sonnet-5', messages);
787
+ console.log(result.choices[0].message.content);
820
788
  ```
821
789
 
822
- ### Phase 2 — Every request pays itself (automatic x402)
790
+ ### Streaming
823
791
 
824
- You just call e.g. `client.chat(...)` — the payment is invisible:
792
+ Every client streams with automatic payment. On `LLMClient` in Base / Arc
793
+ wallet mode, `chatCompletionStream()` also keeps a **pre-auth cache**: after the
794
+ first call to a model it signs the payment up front and skips the 402 round-trip
795
+ (about 200ms saved per request).
825
796
 
826
- 1. You send a request to BlockRun's API.
827
- 2. The gateway returns **402 Payment Required** with the price.
828
- 3. The SDK signs a USDC payment **locally** (EIP-712) — on **Base** for `LLMClient`, on **Solana** for `SolanaLLMClient` — using your wallet key.
829
- 4. The request is retried automatically with the payment proof.
830
- 5. The gateway settles on-chain and returns the AI response.
797
+ #### OpenAI-compatible (recommended)
831
798
 
832
- One call, no separate pay step. Free-tier models settle at **$0** (no payment signed).
799
+ ```typescript
800
+ import { OpenAI } from '@blockrun/llm';
833
801
 
834
- On **Solana**, step 3 pins the payment to a recent blockhash that is valid for
835
- roughly 60 seconds. If one expires between signing and verification,
836
- `SolanaLLMClient` re-signs against a fresh blockhash and retries — up to twice,
837
- with a short backoff — rather than surfacing a payment error you would only
838
- have to retry by hand.
802
+ const client = new OpenAI({ walletKey: process.env.BASE_CHAIN_WALLET_KEY }); // or { apiKey }
839
803
 
840
- The retry is deliberately narrow. It fires only when the gateway explicitly
841
- reports a *verification-phase* stale blockhash. Settlement failures, ambiguous
842
- rejections, insufficient funds and malformed responses all fail immediately.
843
- Verification runs strictly before settlement, so a retryable rejection means no
844
- transaction was broadcast and you cannot be charged twice.
804
+ const stream = await client.chat.completions.create({
805
+ model: 'blockrun/auto', // routing aliases work here too
806
+ messages: [{ role: 'user', content: 'Write a short story about AI agents' }],
807
+ stream: true,
808
+ });
845
809
 
846
- ### Track spend and verify settlements
810
+ for await (const chunk of stream) {
811
+ process.stdout.write(chunk.choices[0]?.delta?.content || '');
812
+ }
813
+ ```
814
+
815
+ #### Native client
816
+
817
+ `chatCompletionStream()` returns the raw `fetch` `Response`. SSE events can span
818
+ network reads, so buffer partial lines:
847
819
 
848
820
  ```typescript
849
- import { getCostSummary } from '@blockrun/llm';
821
+ import { LLMClient, type ChatMessage } from '@blockrun/llm';
850
822
 
851
- const spent = client.getSpending(); // this session
852
- console.log(`Spent $${spent.totalUsd.toFixed(4)} across ${spent.calls} calls`);
823
+ const client = new LLMClient();
853
824
 
854
- const summary = getCostSummary(); // across sessions (~/.blockrun/data/costs.jsonl)
855
- console.log(`Lifetime: $${summary.totalUsd.toFixed(2)} over ${summary.calls} calls`);
856
- ```
825
+ const messages: ChatMessage[] = [
826
+ { role: 'user', content: 'Explain quantum computing in simple terms' },
827
+ ];
857
828
 
858
- In wallet mode, every paid request is a real on-chain USDC transfer — look up your wallet address on [Basescan](https://basescan.org) (or a Solana explorer) to verify each settlement independently. The exception is Solana [batch settlement](#batch-settlement-optional-metered-billing): there the on-chain records are the channel deposit and BlockRun's batched redemptions, and each call is a signed voucher.
829
+ const response = await client.chatCompletionStream('google/gemini-2.5-flash', messages);
859
830
 
860
- **Non-custodial by design: your private key never leaves your machine** — it is only used for local signing, and no funds are ever held by BlockRun.
831
+ const reader = response.body!.getReader();
832
+ const decoder = new TextDecoder();
833
+ let buffer = '';
861
834
 
862
- ## `BlockrunClient` — the universal primitive (recommended for new code)
835
+ while (true) {
836
+ const { done, value } = await reader.read();
837
+ if (done) break;
863
838
 
864
- Starting in `2.5.0`, the SDK ships a single `BlockrunClient` that speaks to
865
- **every** BlockRun endpoint over x402. New API surfaces are intended to be
866
- distributed as [Claude Code skills](https://github.com/anthropics/skills)
867
- that drive this primitive — no SDK release required to add an endpoint.
839
+ buffer += decoder.decode(value, { stream: true });
840
+ const lines = buffer.split('\n');
841
+ buffer = lines.pop() ?? ''; // keep the incomplete last line for the next read
842
+ for (const line of lines) {
843
+ if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;
844
+ const data = JSON.parse(line.slice(6));
845
+ process.stdout.write(data.choices?.[0]?.delta?.content || '');
846
+ }
847
+ }
848
+ ```
868
849
 
869
- ```typescript
870
- import { BlockrunClient } from '@blockrun/llm';
850
+ #### Solana
871
851
 
872
- const br = new BlockrunClient();
852
+ `SolanaLLMClient.stream()` has the same shape as `BlockrunClient.stream()`: it
853
+ yields each `data:` frame already parsed as JSON and stops at `[DONE]`. The
854
+ x402 handshake happens before the first frame — 402, sign an SPL
855
+ TransferChecked authorization locally, replay with `PAYMENT-SIGNATURE` —
856
+ including the fresh-blockhash re-sign on a verification-phase rejection. A free
857
+ model, or an account key, is answered `200` with no handshake at all and
858
+ settles nothing. Pass a concrete model id: `stream()` does not resolve the
859
+ `blockrun/*` aliases.
873
860
 
874
- // Sync GET — Surf market price (Tier 1)
875
- const btc = await br.get('/v1/surf/market/price', { symbol: 'BTC' });
861
+ ```typescript
862
+ import { SolanaLLMClient } from '@blockrun/llm';
876
863
 
877
- // Sync POST — raw on-chain SQL (Tier 3)
878
- const rows = await br.post('/v1/surf/onchain/sql', {
879
- query: 'SELECT block_number FROM ethereum.blocks ORDER BY block_number DESC LIMIT 1',
880
- });
864
+ type ChatChunk = { choices?: { delta?: { content?: string } }[] };
881
865
 
882
- // Submit + poll — long-running video gen (settled only on completion)
883
- const video = await br.poll('/v1/videos/generations', {
884
- model: 'xai/grok-imagine-video',
885
- prompt: 'a red apple spinning',
886
- });
866
+ const client = new SolanaLLMClient(); // SOLANA_WALLET_KEY, or { apiKey: 'brk_live_…' }
887
867
 
888
- // Streaming SSE — chat completions
889
- for await (const chunk of br.stream('/v1/chat/completions', {
890
- model: 'anthropic/claude-sonnet-5',
868
+ for await (const chunk of client.stream<ChatChunk>('/v1/chat/completions', {
869
+ model: 'deepseek/deepseek-chat',
891
870
  messages: [{ role: 'user', content: 'Hi' }],
871
+ max_tokens: 64,
892
872
  stream: true,
893
873
  })) {
894
- process.stdout.write(chunk?.choices?.[0]?.delta?.content ?? '');
874
+ process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
895
875
  }
896
876
  ```
897
877
 
898
- Four call shapes cover every endpoint type:
899
- - `get<T>(path, params?)` — synchronous GET (price, ranking, list, news)
900
- - `post<T>(path, body?)` — synchronous POST (on-chain SQL, search)
901
- - `poll<T>(path, body?, { budgetMs, intervalMs })` — submit + poll (image, video, music, voice)
902
- - `stream<T>(path, body?)` — async iterator over SSE chunks (chat)
878
+ Set `stream: true` yourself — the method does not inject it, because the gateway
879
+ prices a streaming and a non-streaming request the same and rewriting a caller's
880
+ body silently is how you end up debugging a request you did not send.
903
881
 
904
- The per-API client classes (`LLMClient`, `ImageClient`, `VideoClient`,
905
- `PortraitClient`, `VoiceClient`, `MusicClient`, `SearchClient`, `RpcClient`,
906
- `PriceClient`, `SurfClient`) all remain — they will be soft-deprecated in 2.6 (rewritten as
907
- shims over `BlockrunClient`) and removed in 3.0.
882
+ ## Models and Service APIs
908
883
 
909
- ## Models and service APIs
884
+ The live catalog is the source of truth for availability, context windows and
885
+ pricing. **Prices are not listed in this README**: they change often. See
886
+ **[blockrun.ai/models](https://blockrun.ai/models)** for live rates, or read
887
+ them at runtime:
910
888
 
911
- The live catalog is the source of truth for availability, context windows, and
912
- pricing: call `client.listModels()` or visit
913
- [Models & Pricing](https://blockrun.ai/models). The reference below documents
914
- the SDK's model families and non-chat client surfaces; it is intentionally
915
- secondary to the runtime catalog.
889
+ ```typescript
890
+ import { LLMClient } from '@blockrun/llm';
916
891
 
917
- **Prices are not listed here.** They change often, and a number copied into a
918
- README is wrong the day after it lands. See **[blockrun.ai/models](https://blockrun.ai/models)**
919
- for live rates, or read them from the catalog at runtime — `client.listModels()`
920
- and `client.listImageModels()` return exactly what the gateway is charging.
892
+ const client = new LLMClient();
893
+ const models = await client.listModels(); // chat models with pricing
921
894
 
922
- <details>
923
- <summary><strong>Model and service catalog</strong> — chat families, media, search, market data, DeFi, and RPC</summary>
895
+ for (const model of models) {
896
+ console.log(`${model.id}: $${model.inputPrice}/M input`);
897
+ }
924
898
 
925
- The tables and examples in this section are an SDK surface reference. Use the
926
- live catalog for runtime availability and prices.
899
+ const imageModels = await client.listImageModels();
900
+ ```
927
901
 
928
- ### OpenAI GPT-6 Family
902
+ ### Free Tier
929
903
 
930
- The GPT-6 generation: Astra is the flagship for long-horizon agentic work and
931
- computer use, Sol the cost-efficient tier for complex coding, Luna the fast
932
- low-cost tier.
904
+ Input and output both $0 — no promo, no rate-limit gimmick. The free tier is
905
+ **not NVIDIA-only**, so pin these by full model id rather than by an `nvidia/*`
906
+ prefix, or let `routingProfile: 'eco'` rank them first.
933
907
 
934
- | Model | Context |
935
- |---|---|
936
- | `openai/gpt-6-astra` | 1.05M |
937
- | `openai/gpt-6-sol` | 1.05M |
938
- | `openai/gpt-6-luna` | 1.05M |
908
+ | Model | Context | Notes |
909
+ |---|---|---|
910
+ | `nvidia/nemotron-3.5-lightning` | 1M | Thinking-mode reasoning at 1M context |
911
+ | `nvidia/nemotron-3-ultra-550b` | 1M | Largest free model — 550B, 1M context |
912
+ | `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` | 256K | Multimodal reasoning — text + images |
913
+ | `nvidia/llama-3.2-11b-vision` | 128K | Vision-language — accepts images |
914
+ | `nvidia/muse-glimmer-30b` | 128K | Chat, reasoning and coding |
915
+ | `cohere/north-mini-code` | 256K | Compact coding model, sub-second responses |
916
+ | `poolside/laguna-xs-2.1` | 128K | Coding model |
939
917
 
940
- ### OpenAI GPT-5.6 Family
918
+ A free model can be briefly marked unavailable in the live catalog while the
919
+ gateway's health check holds it back; it stays on the free tier.
941
920
 
942
- Three tiers on one 1.05M-context base — Sol (deepest reasoning), Terra
943
- (balanced), Luna (cheap and fast). Each has a `-pro` sibling that thinks
944
- longer at the same token price.
921
+ <details>
922
+ <summary><strong>Paid chat models</strong> — OpenAI, Anthropic, Google, DeepSeek, xAI, Moonshot, MiniMax, Z.ai, Qwen, Mistral, Tencent, Poolside, Xiaomi</summary>
945
923
 
946
- | Model | Context |
947
- |---|---|
948
- | `openai/gpt-5.6-sol` | 1.05M |
949
- | `openai/gpt-5.6-sol-pro` | 1.05M |
950
- | `openai/gpt-5.6-terra` | 1.05M |
951
- | `openai/gpt-5.6-terra-pro` | 1.05M |
952
- | `openai/gpt-5.6-luna` | 1.05M |
953
- | `openai/gpt-5.6-luna-pro` | 1.05M |
924
+ ### OpenAI
954
925
 
955
- ### OpenAI GPT-5.5 / 5.4 / 5.2 / 5.1 Families
926
+ GPT-6: Astra is the flagship for long-horizon agentic work and computer use,
927
+ Sol the cost-efficient tier for complex coding, Luna the fast low-cost tier.
928
+ GPT-5.6: three tiers on one base (Sol deepest reasoning, Terra balanced, Luna
929
+ cheap and fast), each with a `-pro` sibling that thinks longer at the same
930
+ token price.
956
931
 
957
932
  | Model | Context | Notes |
958
933
  |---|---|---|
934
+ | `openai/gpt-6.1-sol` | 1.05M | |
935
+ | `openai/gpt-6-astra` | 1.05M | GPT-6 flagship |
936
+ | `openai/gpt-6-sol` | 1.05M | |
937
+ | `openai/gpt-6-luna` | 1.05M | |
938
+ | `openai/gpt-5.6-sol` | 1.05M | |
939
+ | `openai/gpt-5.6-sol-pro` | 1.05M | |
940
+ | `openai/gpt-5.6-terra` | 1.05M | |
941
+ | `openai/gpt-5.6-terra-pro` | 1.05M | |
942
+ | `openai/gpt-5.6-luna` | 1.05M | |
943
+ | `openai/gpt-5.6-luna-pro` | 1.05M | |
959
944
  | `openai/gpt-5.5` | 1.05M | |
960
945
  | `openai/gpt-5.5-pro` | 1.05M | |
961
946
  | `openai/chat-latest` | 128K | ChatGPT Instant — the model behind chatgpt.com |
@@ -963,32 +948,22 @@ longer at the same token price.
963
948
  | `openai/gpt-5.4-pro` | 1.05M | |
964
949
  | `openai/gpt-5.4-mini` | 400K | |
965
950
  | `openai/gpt-5.4-nano` | 1.05M | |
951
+ | `openai/gpt-5.3-codex` | 400K | Coding/agentic SKU |
966
952
  | `openai/gpt-5.2` | 400K | |
967
953
  | `openai/gpt-5.2-pro` | 400K | |
968
954
  | `openai/gpt-5.1` | 400K | Configurable reasoning effort |
969
- | `openai/gpt-5.3-codex` | 400K | Coding/agentic SKU |
970
955
  | `openai/gpt-5-mini` | 200K | |
956
+ | `openai/gpt-4.1` | 128K | |
957
+ | `openai/gpt-4.1-mini` | 128K | |
958
+ | `openai/gpt-4.1-nano` | 128K | |
959
+ | `openai/gpt-4o` | 128K | |
960
+ | `openai/gpt-4o-mini` | 128K | |
961
+ | `openai/o1` | 200K | Reasoning |
962
+ | `openai/o3` | 200K | Reasoning |
963
+ | `openai/o3-mini` | 128K | Reasoning |
964
+ | `openai/o4-mini` | 128K | Reasoning |
971
965
 
972
- ### OpenAI GPT-4 Family
973
-
974
- | Model | Context |
975
- |---|---|
976
- | `openai/gpt-4.1` | 128K |
977
- | `openai/gpt-4.1-mini` | 128K |
978
- | `openai/gpt-4.1-nano` | 128K |
979
- | `openai/gpt-4o` | 128K |
980
- | `openai/gpt-4o-mini` | 128K |
981
-
982
- ### OpenAI O-Series (Reasoning)
983
-
984
- | Model | Context |
985
- |---|---|
986
- | `openai/o1` | 200K |
987
- | `openai/o3` | 200K |
988
- | `openai/o3-mini` | 128K |
989
- | `openai/o4-mini` | 128K |
990
-
991
- ### Anthropic Claude
966
+ ### Anthropic Claude
992
967
 
993
968
  | Model | Context | Notes |
994
969
  |---|---|---|
@@ -997,7 +972,7 @@ longer at the same token price.
997
972
  | `anthropic/claude-opus-5.5` | 1M | Newest Opus — Opus-class reasoning at a lower price than Opus 5 |
998
973
  | `anthropic/claude-opus-5` | 1M | Flagship — the baseline the routing savings claim is measured against |
999
974
  | `anthropic/claude-opus-4.8` | 1M | Agentic coding + adaptive thinking, 128K output |
1000
- | `anthropic/claude-opus-4.7` | 1M | |
975
+ | `anthropic/claude-opus-4.7` | 1M | Router-core's per-request savings baseline (priced like Opus 5) |
1001
976
  | `anthropic/claude-opus-4.5` | 200K | |
1002
977
  | `anthropic/claude-sonnet-5.5` | 1M | Newest Sonnet — everyday coding and agent work, 128K output |
1003
978
  | `anthropic/claude-sonnet-5` | 1M | Best cost/quality balance for long-context agent turns |
@@ -1037,8 +1012,7 @@ paid SKU; the vision SKU is an experimental preview.
1037
1012
 
1038
1013
  The older Grok chat SKUs (grok-3/3-mini, the grok-4 fast families,
1039
1014
  grok-code-fast-1, grok-2-vision) have left the catalog. Retired ids stay
1040
- callable — the gateway redirects them to a healthy model — but SmartChat
1041
- only ranks what `/v1/models` lists.
1015
+ callable — the gateway redirects them to a healthy model.
1042
1016
 
1043
1017
  | Model | Context | Notes |
1044
1018
  |---|---|---|
@@ -1056,16 +1030,25 @@ only ranks what `/v1/models` lists.
1056
1030
  | `minimax/minimax-m3` | 1M | |
1057
1031
  | `minimax/minimax-m2.7` | 200K | |
1058
1032
  | `zai/glm-5.3` | 1M | |
1059
- | `zai/glm-5.3-flash` | 1M | Cheapest vision-capable paid SKU |
1033
+ | `zai/glm-5.3-flash` | 1M | Low-cost vision-capable GLM |
1060
1034
  | `zai/glm-5.2` | 1M | |
1061
1035
  | `zai/glm-5.1` | 200K | |
1062
1036
  | `zai/glm-5` | 200K | |
1063
1037
  | `zai/glm-5-turbo` | 200K | |
1038
+ | `qwen/qwen3.8-max` | 1M | |
1064
1039
  | `qwen/qwen3.7-max` | 1M | |
1065
1040
  | `qwen/qwen3.7-plus` | 1M | |
1066
1041
  | `qwen/qwen3.8-flash` | 1M | |
1067
1042
  | `qwen/qwen3.7-flash` | 1M | Cheapest paid chat model in the catalog |
1068
1043
 
1044
+ ### Mistral, Tencent, Poolside
1045
+
1046
+ | Model | Context | Notes |
1047
+ |---|---|---|
1048
+ | `mistral/mistral-large-4` | 512K | Reasoning + vision, 256K output |
1049
+ | `tencent/hy4-preview` | 1M | Preview |
1050
+ | `poolside/laguna-s-2.1` | 1M | Paid coding model (sibling of the free XS) |
1051
+
1069
1052
  ### Xiaomi
1070
1053
 
1071
1054
  | Model | Context |
@@ -1073,158 +1056,187 @@ only ranks what `/v1/models` lists.
1073
1056
  | `xiaomi/mimo-v2.5` | 1M |
1074
1057
  | `xiaomi/mimo-v2.5-pro` | 1M |
1075
1058
 
1076
- ### Free Tier
1059
+ ### Testnet Models (Base Sepolia)
1077
1060
 
1078
- Input and output both $0 — no promo, no rate-limit gimmick. The free tier is
1079
- **no longer NVIDIA-only**, so pin these by full model id rather than by an
1080
- `nvidia/*` prefix, or let `routingProfile: 'eco'` rank them first.
1061
+ | Model |
1062
+ |-------|
1063
+ | `openai/gpt-oss-20b` |
1064
+ | `openai/gpt-oss-120b` |
1081
1065
 
1082
- | Model | Context | Notes |
1083
- |---|---|---|
1084
- | `nvidia/nemotron-3-nano-omni-30b-a3b-reasoning` | 256K | Multimodal reasoning — text + images |
1085
- | `nvidia/nemotron-3.5-lightning` | 1M | Thinking-mode reasoning at 1M context |
1086
- | `nvidia/llama-3.2-11b-vision` | 128K | Vision-language — accepts images |
1087
- | `nvidia/nemotron-3-ultra-550b` | 1M | Largest free model — 550B, 1M context |
1088
- | `cohere/north-mini-code` | 256K | Compact coding model, sub-second responses |
1089
- | `poolside/laguna-xs-2.1` | 128K | Coding model |
1066
+ *Testnet models use flat pricing (no token counting) for simplicity.*
1067
+
1068
+ </details>
1069
+
1070
+ ### `BlockrunClient` — the universal primitive
1071
+
1072
+ `BlockrunClient` speaks to **any** BlockRun endpoint, so new endpoints work
1073
+ without an SDK release. It takes an account `apiKey` or an EVM wallet key
1074
+ (`BLOCKRUN_WALLET_KEY` or `BASE_CHAIN_WALLET_KEY`); its wallet mode pays on
1075
+ Base / Arc only and refuses a `solana:` 402, so Solana wallets use
1076
+ `SolanaLLMClient`.
1077
+
1078
+ ```typescript
1079
+ import { BlockrunClient } from '@blockrun/llm';
1080
+
1081
+ const br = new BlockrunClient();
1082
+
1083
+ // Sync GET: crypto price
1084
+ const btc = await br.get('/v1/crypto/price/BTC-USD');
1085
+
1086
+ // Sync POST: live web search
1087
+ const res = await br.post('/v1/search', { query: 'x402 adoption', sources: ['web'] });
1088
+
1089
+ // Submit + poll: long-running video generation (settled only on completion)
1090
+ const video = await br.poll('/v1/videos/generations', {
1091
+ model: 'xai/grok-imagine-video',
1092
+ prompt: 'a red apple spinning',
1093
+ });
1094
+
1095
+ // Streaming SSE: chat completions
1096
+ type ChatChunk = { choices?: { delta?: { content?: string } }[] };
1097
+ for await (const chunk of br.stream<ChatChunk>('/v1/chat/completions', {
1098
+ model: 'anthropic/claude-sonnet-5',
1099
+ messages: [{ role: 'user', content: 'Hi' }],
1100
+ stream: true,
1101
+ })) {
1102
+ process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
1103
+ }
1104
+ ```
1105
+
1106
+ Four call shapes cover every endpoint type:
1107
+ - `get<T>(path, params?)` — synchronous GET (price, list, history)
1108
+ - `post<T>(path, body?)` — synchronous POST (search, RPC)
1109
+ - `poll<T>(path, body?, { budgetMs, intervalMs })` — submit + poll (image, video, music)
1110
+ - `stream<T>(path, body?)` — async iterator over SSE chunks (chat)
1111
+
1112
+ The per-API clients below remain fully supported; `BlockrunClient` is the
1113
+ generic escape hatch for endpoints without a typed wrapper.
1114
+
1115
+ ### Image Generation and Editing
1090
1116
 
1091
- ### Image Generation
1092
1117
  | Model | Notes |
1093
1118
  |---|---|
1094
- | `openai/gpt-image-1` | Native GPT-4o image generation |
1095
- | `openai/gpt-image-2` | Reasoning-driven — multilingual text rendering, character consistency |
1096
- | `openai/gpt-image-2.5-flare` | GPT Image 2.5 |
1097
- | `openai/gpt-image-2.5-sunburst` | GPT Image 2.5 |
1098
- | `google/nano-banana` | Gemini 2.5 Flash image generation — fast and efficient |
1099
- | `google/nano-banana-2` | Gemini 3.1 Flash — pro-level quality at Flash speed |
1100
- | `google/nano-banana-pro` | Gemini 3 Pro — highest quality, up to 4K |
1119
+ | `openai/gpt-image-1` | Native GPT-4o image generation; also edits |
1120
+ | `openai/gpt-image-2` | Reasoning-driven — multilingual text rendering, character consistency; also edits (the default) |
1121
+ | `openai/gpt-image-2.5-flare` | GPT Image 2.5 — fast, everyday quality |
1122
+ | `openai/gpt-image-2.5-sunburst` | GPT Image 2.5 — most capable; also edits (incl. mask) |
1123
+ | `google/nano-banana` | Gemini 2.5 Flash image generation — fast and efficient; also edits |
1124
+ | `google/nano-banana-2` | Gemini 3.1 Flash — pro-level quality at Flash speed; also edits |
1125
+ | `google/nano-banana-pro` | Gemini 3 Pro — highest quality, up to 4K; also edits |
1101
1126
  | `xai/grok-imagine-image` | Fast, 300 RPM |
1127
+ | `xai/grok-imagine-image-2.0` | Between Grok Imagine and Pro |
1102
1128
  | `xai/grok-imagine-image-pro` | Quality tier, 30 RPM |
1103
- | `xai/grok-imagine-image-2.0` | Grok Imagine 2.0 |
1104
1129
  | `bytedance/seedream-5-pro` | Flagship generation + editing, up to 4K-class, reference images |
1105
1130
  | `zai/cogview-4` | Up to 1440x1440 |
1106
1131
 
1107
- Image editing (`client.edit`) via `/v1/images/image2image`: `openai/gpt-image-1`, `openai/gpt-image-2`, `google/nano-banana`, and `google/nano-banana-pro`. Pass a single base64 `data:image/...` URI to edit one image, or an array of 2–4 URIs to **fuse** them (e.g. a subject + a brand logo). Fusion caps: `openai/*` up to 4 source images, `google/*` up to 3. A `mask` cannot be combined with multiple source images.
1132
+ `ImageClient.edit()` calls `/v1/images/image2image` with the edit-capable
1133
+ models marked above. Source images must be base64 `data:image/...` URIs (not
1134
+ URLs). Pass one to edit it, or an array of 2–4 to **fuse** them (e.g. a subject
1135
+ and a brand logo): `openai/*` takes up to 4 sources, `google/*` up to 3. A
1136
+ `mask` is OpenAI-only and cannot be combined with multiple sources.
1137
+ `LLMClient.imageEdit()` and `SolanaLLMClient.imageEdit()` take the same
1138
+ arguments.
1108
1139
 
1109
- ```ts
1110
- // Multi-image fusion with Nano Banana
1111
- const fused = await client.edit(
1112
- "Place the logo on the t-shirt",
1113
- [subjectDataUri, logoDataUri],
1114
- { model: "google/nano-banana" }
1115
- );
1140
+ ```typescript
1141
+ import { ImageClient } from '@blockrun/llm';
1142
+
1143
+ const client = new ImageClient();
1144
+
1145
+ const generated = await client.generate('a lighthouse at dusk, oil painting', {
1146
+ model: 'google/nano-banana',
1147
+ });
1148
+ console.log(generated.data[0].url);
1149
+
1150
+ const photoDataUri = 'data:image/png;base64,...';
1151
+ const subjectDataUri = 'data:image/png;base64,...';
1152
+ const logoDataUri = 'data:image/png;base64,...';
1153
+
1154
+ // Edit one image
1155
+ const edited = await client.edit('Make the sky purple and add northern lights', photoDataUri, {
1156
+ model: 'openai/gpt-image-2',
1157
+ });
1158
+
1159
+ // Fuse several images
1160
+ const fused = await client.edit('Place the logo on the t-shirt', [subjectDataUri, logoDataUri], {
1161
+ model: 'google/nano-banana',
1162
+ });
1116
1163
  console.log(fused.data[0].url);
1117
1164
  ```
1118
1165
 
1119
1166
  ### Video Generation
1167
+
1120
1168
  | Model | Default | Max | Notes |
1121
1169
  |---|---|---|---|
1122
1170
  | `xai/grok-imagine-video` | 8s | 15s | 480p default, 720p available; text or image to video |
1123
- | `xai/grok-imagine-video-1.5` | 8s | 15s | Flagship — native synced audio; 480p default, 720p available |
1124
- | `bytedance/seedance-1.5-pro` | 5s | 12s | Budget 720p with synced audio. No RealFace assets |
1125
- | `bytedance/seedance-2.0-fast` | 5s | 15s | 720p, ~60-80s to generate. RealFace assets supported |
1126
- | `bytedance/seedance-2.0-mini` | 5s | 15s | 480p/720p at half the flagship rate. RealFace supported |
1127
- | `bytedance/seedance-2.0` | 5s | 15s | Premium 720p with synced audio. RealFace supported |
1128
- | `bytedance/seedance-2.5` | 5s | 30s | Long-form — up to 30s, multilingual, multi-asset |
1171
+ | `xai/grok-imagine-video-1.5` | 8s | 15s | Native synced audio; 480p default, 720p / 1080p available |
1172
+ | `bytedance/seedance-1.5-pro` | 5s | 12s | Budget, up to 1080p, synced audio. No RealFace |
1173
+ | `bytedance/seedance-2.0-fast` | 5s | 15s | 480p/720p, ~60-80s to generate. RealFace supported |
1174
+ | `bytedance/seedance-2.0-mini` | 5s | 15s | 480p/720p, lowest-cost Seedance 2.0. RealFace supported |
1175
+ | `bytedance/seedance-2.0` | 5s | 15s | Premium, up to 4K, synced audio. RealFace supported |
1176
+ | `bytedance/seedance-2.5` | 5s | 30s | Long-form — up to 30s, 480p/720p, multilingual, up to 30 reference images |
1129
1177
  | `azure/sora-2` | 4s | 12s | Sora 2 via Azure AI Foundry — 720p with synced audio; 4, 8 or 12s |
1130
1178
 
1131
- ```ts
1179
+ ```typescript
1132
1180
  import { VideoClient } from '@blockrun/llm';
1133
1181
 
1134
1182
  const client = new VideoClient();
1135
1183
  const result = await client.generate('a red apple slowly spinning on a wooden table');
1136
- console.log(result.data[0].url); // permanent MP4 URL
1184
+ console.log(result.data[0].url); // permanent MP4 URL
1137
1185
  console.log(result.data[0].duration_seconds); // 8
1138
1186
 
1139
1187
  // Image-to-video
1140
- const r2 = await client.generate('the subject turns and smiles', {
1188
+ const fromImage = await client.generate('the subject turns and smiles', {
1141
1189
  imageUrl: 'https://example.com/portrait.jpg',
1142
1190
  });
1143
1191
 
1144
- // Token360 / Seedance options (silently ignored by xAI Grok video)
1145
- const r3 = await client.generate('aerial drone shot over a snowy mountain', {
1146
- model: 'bytedance/seedance-2.0-fast',
1192
+ // Seedance options (each is model-gated: an unsupported one is a 400 before payment)
1193
+ const seedance = await client.generate('aerial drone shot over a snowy mountain', {
1194
+ model: 'bytedance/seedance-2.0',
1147
1195
  aspectRatio: '21:9',
1148
1196
  resolution: '1080p',
1149
1197
  generateAudio: true, // omit to use the model's default
1150
1198
  seed: 42,
1151
- watermark: false,
1152
1199
  returnLastFrame: true, // useful for clip chaining
1153
1200
  });
1201
+ console.log(seedance.data[0].last_frame_url); // present when the upstream returns it
1202
+ ```
1154
1203
 
1155
- // First-and-last-frame interpolation (Seedance only): the model tweens
1156
- // from imageUrl (first frame) to lastFrameUrl (final frame).
1157
- // Priced identically to image-to-video.
1158
- const r4 = await client.generate('the flower blooms in golden morning light', {
1159
- model: 'bytedance/seedance-1.5-pro',
1160
- imageUrl: 'https://example.com/bud.jpg',
1161
- lastFrameUrl: 'https://example.com/bloom.jpg',
1162
- });
1204
+ Seedance also supports first-and-last-frame interpolation (`lastFrameUrl`),
1205
+ output controls (`bitrateMode`, `outputFormat`, `cameraFixed`), and
1206
+ reference images, video and audio (`referenceImageUrls`, `referenceVideos`,
1207
+ `referenceAudios`). Reference media is served by `api.blockrun.ai`, so it needs
1208
+ an account API key; the wallet gateways refuse it with a 400 before any
1209
+ payment. Each option's model support and limits are documented on
1210
+ `VideoGenerateOptions` in the TSDoc.
1163
1211
 
1164
- // Seedance output controls. Each is model-gated at the gateway: an
1165
- // unsupported one is a 400 before payment, never silently dropped.
1166
- const r5 = await client.generate('a paper boat drifting down a rain gutter', {
1167
- model: 'bytedance/seedance-2.5',
1168
- bitrateMode: 'high', // Seedance 2.x
1169
- outputFormat: 'mov', // Seedance 2.5 only
1170
- returnLastFrame: true,
1171
- });
1172
- console.log(r5.data[0].last_frame_url); // present when the upstream returns it
1173
- // cameraFixed: true is Seedance 1.5-pro only.
1174
- ```
1212
+ ### Music Generation
1175
1213
 
1176
- #### Reference images, video and audio (account API key only)
1214
+ `MusicClient` wraps `POST /v1/audio/generations` with `minimax/music-2.5+` (the
1215
+ default). A track takes 1–3 minutes; the returned URL is valid for about 24 hours,
1216
+ so download it.
1177
1217
 
1178
- Seedance reference media is served by `api.blockrun.ai`, so it needs an
1179
- account API key. The wallet gateways (blockrun.ai, sol.blockrun.ai) refuse
1180
- `referenceImageUrls` / `referenceVideos` / `referenceAudios` with a 400 before
1181
- any payment.
1218
+ ```typescript
1219
+ import { MusicClient } from '@blockrun/llm';
1182
1220
 
1183
- | Model | Reference images | Reference video / audio |
1184
- |---|---|---|
1185
- | `bytedance/seedance-2.0` / `-fast` / `-mini` | 1–9 | 1–3 clips of each; audio needs an image or video alongside |
1186
- | `bytedance/seedance-2.5` | 1–30 | — |
1187
-
1188
- Reference mode is its own mode: it cannot be mixed with `imageUrl`,
1189
- `lastFrameUrl` or `realFaceAssetId`. Cite images as "image 1", "image 2" and
1190
- clips as "video 1" in the prompt. Reference clips add a per-clip surcharge;
1191
- reference audio must be ≤15.2s.
1192
-
1193
- ```ts
1194
- const client = new VideoClient({ apiKey: process.env.BLOCKRUN_API_KEY });
1195
-
1196
- // Omni / multi-reference: character/style consistency from images
1197
- const r6 = await client.generate(
1198
- 'the character from image 1 walks through the city from image 2',
1199
- {
1200
- model: 'bytedance/seedance-2.0',
1201
- referenceImageUrls: [
1202
- 'https://example.com/character.jpg',
1203
- 'https://example.com/city.jpg',
1204
- ],
1205
- }
1206
- );
1221
+ const client = new MusicClient();
1207
1222
 
1208
- // Reference-to-video: image 1 for the character, video 1 for the motion
1209
- const r7 = await client.generate(
1210
- 'use image 1 for the character and video 1 for the motion',
1211
- {
1212
- model: 'bytedance/seedance-2.0-fast',
1213
- durationSeconds: 5,
1214
- referenceImageUrls: ['https://example.com/character.png'],
1215
- referenceVideos: [{ url: 'https://example.com/motion.mp4' }],
1216
- inputType: 'reference', // optional: 400 if the fields say otherwise
1217
- }
1218
- );
1223
+ const instrumental = await client.generate('chill lo-fi beats with piano');
1224
+ console.log(instrumental.data[0].url);
1225
+
1226
+ // With vocals: pass lyrics and turn instrumental off
1227
+ const song = await client.generate('upbeat pop song', {
1228
+ instrumental: false,
1229
+ lyrics: 'Hello world, this is my song...',
1230
+ });
1219
1231
  ```
1220
1232
 
1221
1233
  ### Text-to-Speech & Sound Effects
1222
1234
 
1223
- `SpeechClient` wraps BlockRun Voice (ElevenLabs): `POST /v1/audio/speech`
1224
- (OpenAI-compatible TTS), `POST /v1/audio/sound-effects`, and the free
1225
- `GET /v1/audio/voices`. TTS price scales with character count:
1226
- `(chars / 1000) × model rate`, with a per-request minimum. Synthesis is
1227
- synchronous (<1s for Flash).
1235
+ `SpeechClient` wraps BlockRun Voice: `POST /v1/audio/speech` (OpenAI-compatible
1236
+ TTS), `POST /v1/audio/sound-effects`, and the free `GET /v1/audio/voices`. The
1237
+ ElevenLabs TTS models are priced by character count (`(chars / 1000) × model
1238
+ rate`, with a per-request minimum); Seed Audio is priced by output duration.
1239
+ Synthesis is synchronous (<1s for Flash).
1228
1240
 
1229
1241
  | Model | Max Input | Notes |
1230
1242
  |---|---|---|
@@ -1232,10 +1244,10 @@ synchronous (<1s for Flash).
1232
1244
  | `elevenlabs/turbo-v2.5` | 40k chars | ~250ms latency, balanced quality |
1233
1245
  | `elevenlabs/multilingual-v2` | 10k chars | Long-form narration, audiobooks — 29 languages |
1234
1246
  | `elevenlabs/v3` | 5k chars | Max expressiveness, 70+ languages |
1235
- | `bytedance/seed-audio-1.0` | 3k chars | Prompt-directed — describe voice, emotion and staging in words |
1247
+ | `bytedance/seed-audio-1.0` | 3k chars | Prompt-directed — describe voice, emotion and staging in words; billed by output duration (up to 120s) |
1236
1248
  | `elevenlabs/sound-effects` | 1k chars | Sound effects up to 22s |
1237
1249
 
1238
- ```ts
1250
+ ```typescript
1239
1251
  import { SpeechClient } from '@blockrun/llm';
1240
1252
 
1241
1253
  const client = new SpeechClient();
@@ -1270,7 +1282,7 @@ enrollment, so a failed enrollment never charges your wallet. The returned
1270
1282
  `source_image_url`). (Real-person likeness is not supported on BlockRun —
1271
1283
  enrolled portraits are AI characters.)
1272
1284
 
1273
- ```ts
1285
+ ```typescript
1274
1286
  import { PortraitClient, VideoClient } from '@blockrun/llm';
1275
1287
 
1276
1288
  const portraits = new PortraitClient();
@@ -1288,14 +1300,14 @@ const clip = await video.generate('she waves and smiles', {
1288
1300
  console.log(clip.data[0].url);
1289
1301
  ```
1290
1302
 
1291
- ### Voice Calls
1303
+ ### Voice Calls and Phone Numbers
1292
1304
 
1293
1305
  `VoiceClient` wraps `POST /v1/voice/call` (paid, flat per call) and
1294
1306
  `GET /v1/voice/call/{callId}` (free polling) — AI-powered outbound phone
1295
1307
  calls powered by Bland.ai. The agent dials the recipient and runs a real-time
1296
1308
  conversation based on your `task` instructions. US + Canada destinations.
1297
1309
 
1298
- ```ts
1310
+ ```typescript
1299
1311
  import { VoiceClient } from '@blockrun/llm';
1300
1312
 
1301
1313
  const client = new VoiceClient();
@@ -1314,92 +1326,58 @@ const status = await client.getStatus(result.call_id);
1314
1326
  console.log(status.status, status.recording_url);
1315
1327
  ```
1316
1328
 
1317
- Bring your own caller-ID: pass `from: '+14155552671'` (must be a BlockRun
1318
- phone number you own; buy via `/v1/phone/numbers/buy`).
1329
+ `PhoneClient` covers phone lookups and numbers: `lookup()` (carrier and line
1330
+ type) and `lookupFraud()` (adds SIM-swap and call-forwarding signals), plus
1331
+ `buyNumber({ country, areaCode })` for a 30-day US/CA number, `renewNumber()`,
1332
+ `listNumbers()` and `releaseNumber()`. A number you own can be the caller ID
1333
+ of a voice call: pass it as `from`.
1334
+
1335
+ ```typescript
1336
+ import { PhoneClient } from '@blockrun/llm';
1337
+
1338
+ const phone = new PhoneClient();
1339
+ const info = await phone.lookup('+14155552671');
1340
+ const { phone_number } = await phone.buyNumber({ country: 'US', areaCode: '415' });
1341
+ ```
1319
1342
 
1320
1343
  ### Standalone Search
1321
1344
 
1322
- `SearchClient` wraps `POST /v1/search` — standalone Grok Live Search.
1323
- Pricing is per source plus margin — see [blockrun.ai/models](https://blockrun.ai/models).
1345
+ `SearchClient` wraps `POST /v1/search` — Grok Live Search over `web` and
1346
+ `news`, at a flat price per call. For X/Twitter search, use chat on an `xai/*`
1347
+ model with an `x_search` tool or `searchParameters: { sources: [{ type: 'x' }] }`
1348
+ (see [Solana Support](#solana-support) for an example).
1324
1349
 
1325
- ```ts
1350
+ ```typescript
1326
1351
  import { SearchClient } from '@blockrun/llm';
1327
1352
 
1328
1353
  const client = new SearchClient();
1329
1354
  const result = await client.search('Latest news on x402 adoption', {
1330
- sources: ['x', 'web'],
1355
+ sources: ['web', 'news'],
1331
1356
  maxResults: 10,
1332
1357
  });
1333
1358
  console.log(result.summary);
1334
- for (const url of result.citations ?? []) console.log(url);
1335
- ```
1336
-
1337
- The same endpoint is available as `client.search()` on `LLMClient` and
1338
- `SolanaLLMClient` when the selected gateway supports it.
1339
-
1340
- ### Surf Crypto Data
1341
-
1342
- `SurfClient` exposes the full `/v1/surf/*` catalog — 84+ pay-per-call
1343
- endpoints across CEX/DEX market data, on-chain SQL, wallet intelligence,
1344
- prediction markets (Polymarket + Kalshi), social analytics, news, VC fund
1345
- data, and an OpenAI-compatible chat surface. Flat pricing per call:
1346
-
1347
- | Tier | Examples |
1348
- |---|---|
1349
- | 1 | `/market/price`, `/market/ranking`, `/news/feed`, prediction-market reads, social tweets |
1350
- | 2 | `/exchange/depth`, `/exchange/klines`, `/wallet/detail`, `/search/*`, `/social/ranking` |
1351
- | 3 | `/onchain/sql`, `/onchain/query`, `/onchain/schema`, `/chat/completions` |
1352
-
1353
- Because the catalog is broad and evolving, the client deliberately ships a
1354
- generic `get` / `post` pair instead of 84 typed wrappers. Pass the path
1355
- (with or without the `/v1/surf` prefix), query params, or a JSON body —
1356
- type the response via a generic if you want.
1357
-
1358
- ```ts
1359
- import { SurfClient } from '@blockrun/llm';
1360
-
1361
- const surf = new SurfClient();
1362
-
1363
- // Tier 1 — token price
1364
- const btc = await surf.get('/market/price', { symbol: 'BTC' });
1365
-
1366
- // Tier 2 — order book depth
1367
- const book = await surf.get('/exchange/depth', {
1368
- exchange: 'binance',
1369
- symbol: 'BTC-USDT',
1370
- });
1371
-
1372
- // Tier 3 — raw on-chain SQL against 80+ ClickHouse tables
1373
- const rows = await surf.post('/onchain/sql', {
1374
- query: 'SELECT block_number FROM ethereum.blocks ORDER BY block_number DESC LIMIT 5',
1375
- });
1376
-
1377
- // Typed response via generic
1378
- type Price = { symbol: string; price: number; timestamp: string };
1379
- const eth = await surf.get<Price>('/market/price', { symbol: 'ETH' });
1359
+ for (const c of result.citations ?? []) console.log(c.url);
1380
1360
  ```
1381
1361
 
1382
- Full endpoint inventory: <https://blockrun.ai/marketplace/surf>.
1383
-
1384
- Methods: `userLookup`, `userInfo`, `followers`, `following`, `followings`,
1385
- `verifiedFollowers`, `userTweets`, `mentions`, `tweetLookup`, `tweetReplies`,
1386
- `tweetThread`, `search`, `trending`, `articlesRising`.
1362
+ The same endpoint is available as `search()` on `LLMClient` and
1363
+ `SolanaLLMClient`.
1387
1364
 
1388
1365
  ### Market Data (Pyth)
1389
1366
 
1390
1367
  `PriceClient` wraps the Pyth-backed market-data endpoints. Crypto, FX and
1391
- commodity are fully free (price + history + list); 12 global stock markets
1392
- and the `usstock` legacy alias are billed per call for price + history (list is
1393
- always free). Pass `requireWallet: false` to construct a free-only client.
1368
+ commodity are free (price + history + list); 12 global stock markets and the
1369
+ `usstock` legacy alias are billed per call for price + history (list is always
1370
+ free). Pass `requireWallet: false` to construct a free-only client.
1394
1371
 
1395
- ```ts
1372
+ ```typescript
1396
1373
  import { PriceClient } from '@blockrun/llm';
1397
1374
 
1398
1375
  const p = new PriceClient({ requireWallet: false });
1399
1376
  const btc = await p.price('crypto', 'BTC-USD');
1400
1377
  const eur = await p.price('fx', 'EUR-USD');
1378
+ const symbols = await p.listSymbols('crypto', { query: 'sol', limit: 20 });
1401
1379
 
1402
- // Paid — requires a wallet
1380
+ // Paid: requires an API key or a wallet
1403
1381
  const p2 = new PriceClient();
1404
1382
  const aapl = await p2.price('stocks', 'AAPL', { market: 'us' });
1405
1383
  const bars = await p2.history('stocks', 'AAPL', {
@@ -1408,50 +1386,115 @@ const bars = await p2.history('stocks', 'AAPL', {
1408
1386
  from: 1700000000,
1409
1387
  to: 1710000000,
1410
1388
  });
1411
- const symbols = await p.listSymbols('crypto', { query: 'sol', limit: 20 });
1412
1389
  ```
1413
1390
 
1414
1391
  Supported `StockMarket` values: `us, hk, jp, kr, gb, de, fr, nl, ie, lu, cn, ca`.
1415
1392
 
1393
+ ### Prediction Markets (Powered by Predexon)
1394
+
1395
+ Prediction-market data from Polymarket, Kalshi, Limitless, Opinion,
1396
+ Predict.Fun, Binance Futures, UMA oracle resolutions and sports markets via
1397
+ [Predexon](https://predexon.com). Every endpoint costs the same flat price per
1398
+ call ($0.0085 at the time of writing). Use an account API key or x402 wallet
1399
+ payments; no separate Predexon key is needed. `pm()` works on both `LLMClient`
1400
+ and `SolanaLLMClient`; query parameters are strings.
1401
+
1402
+ ```typescript
1403
+ import { LLMClient } from '@blockrun/llm';
1404
+
1405
+ const client = new LLMClient();
1406
+
1407
+ // Polymarket
1408
+ const markets = await client.pm('polymarket/markets', { status: 'active', limit: '10' });
1409
+ const events = await client.pm('polymarket/events');
1410
+ const candles = await client.pm('polymarket/candlesticks/0x1234abcd...');
1411
+ const profile = await client.pm('polymarket/wallet/0xABC123...');
1412
+ const pnl = await client.pm('polymarket/wallet/pnl/0xABC123...');
1413
+ const leaderboard = await client.pm('polymarket/leaderboard');
1414
+
1415
+ // Kalshi and Binance Futures
1416
+ const kalshiMarkets = await client.pm('kalshi/markets');
1417
+ const btcCandles = await client.pm('binance/candles/BTCUSDT');
1418
+
1419
+ // Cross-venue search (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun)
1420
+ const found = await client.pm('markets/search', { q: 'bitcoin' });
1421
+
1422
+ // POST endpoints take a JSON body
1423
+ const identities = await client.pmQuery('polymarket/wallet/identities', {
1424
+ addresses: ['0xabc...', '0xdef...'],
1425
+ });
1426
+ ```
1427
+
1428
+ ### Exa Web Search (Powered by Exa)
1429
+
1430
+ [Exa](https://exa.ai)'s neural web search, paid with account credit or x402;
1431
+ no separate Exa key is needed. Use `LLMClient` for a Base wallet or
1432
+ `SolanaLLMClient` for a Solana wallet.
1433
+
1434
+ | Method | Description |
1435
+ |---|---|
1436
+ | `exaSearch(query, options?)` | Neural/keyword web search |
1437
+ | `exaFindSimilar(url, options?)` | Find semantically similar pages |
1438
+ | `exaContents(urls)` | Extract full text from URLs (`SolanaLLMClient` also accepts an options object) |
1439
+ | `exaAnswer(query)` | AI answer grounded in web search (`SolanaLLMClient` also accepts an options object) |
1440
+ | `exa(path, body)` | Generic proxy for any Exa endpoint |
1441
+
1442
+ On `LLMClient` the typed helpers return the result payload (the response's
1443
+ `data`) with typed fields; `exa()` returns the raw response envelope.
1444
+
1445
+ ```typescript
1446
+ import { LLMClient } from '@blockrun/llm';
1447
+
1448
+ const client = new LLMClient();
1449
+
1450
+ const results = await client.exaSearch('latest AI safety research', { numResults: 5 });
1451
+ for (const r of results.results) console.log(r.title, r.url);
1452
+
1453
+ const news = await client.exaSearch('bitcoin ETF news', { category: 'news', numResults: 10 });
1454
+ const similar = await client.exaFindSimilar('https://openai.com/research/gpt-4', { numResults: 5 });
1455
+ const content = await client.exaContents(['https://arxiv.org/abs/2303.08774']);
1456
+ const answer = await client.exaAnswer('What is the current state of AI safety research?');
1457
+
1458
+ // Generic proxy for any Exa endpoint
1459
+ const custom = await client.exa('search', { query: 'transformer architecture', numResults: 5 });
1460
+ ```
1461
+
1416
1462
  ### DeFi Data, DEX Swaps & Cloud Compute
1417
1463
 
1418
- Three passthrough families live directly on `LLMClient` / `SolanaLLMClient`:
1464
+ Three passthrough families live directly on `LLMClient` and `SolanaLLMClient`:
1465
+
1466
+ ```typescript
1467
+ import { LLMClient } from '@blockrun/llm';
1419
1468
 
1420
- ```ts
1421
1469
  const client = new LLMClient();
1422
1470
 
1423
- // DefiLlama — protocols / TVL / yields / prices
1424
- const protocols = await client.defiProtocols();
1471
+ // DefiLlama: protocols, TVL, yields, prices
1425
1472
  const aave = await client.defiProtocol('aave');
1426
- const prices = await client.defiPrices(['coingecko:bitcoin', 'base:0x833589...']);
1427
1473
 
1428
- // 0x DEX — swap + gasless quotes (FREE; BlockRun takes an on-chain affiliate
1429
- // fee on executed swaps instead of x402)
1474
+ // 0x DEX quotes (free: BlockRun takes an on-chain affiliate fee on executed swaps instead)
1430
1475
  const quote = await client.dexQuote({
1431
1476
  chainId: '8453', sellToken: '0x...', buyToken: '0x...',
1432
1477
  sellAmount: '1000000', taker: '0xYourWallet',
1433
1478
  });
1434
- const gq = await client.dexGaslessQuote({ /* ... */ });
1435
- const res = await client.dexGaslessSubmit({ trade: { /* signed eip712 */ } });
1436
- const status = await client.dexGaslessStatus(res.tradeHash as string);
1437
1479
 
1438
- // Modal — sandboxed compute (create CPU / GPU, exec)
1480
+ // Modal: sandboxed CPU / GPU compute
1439
1481
  const sb = await client.modalSandboxCreate({ image: 'python:3.11' });
1440
- const out = await client.modalSandboxExec(sb.sandbox_id as string, ['python', '-c', 'print(42)']);
1441
- await client.modalSandboxTerminate(sb.sandbox_id as string);
1442
1482
  ```
1443
1483
 
1444
- Generic escape hatches: `client.defi(path, params)`, `client.dex(path, params, body?)`,
1445
- `client.modal(path, body)`.
1484
+ More helpers: `defiProtocols()`, `defiChains()`, `defiYields()`, `defiPrices()`;
1485
+ `dexPrice()`, `dexGaslessQuote()`, `dexGaslessSubmit()`, `dexGaslessStatus()`;
1486
+ `modalSandboxExec()`, `modalSandboxStatus()`, `modalSandboxTerminate()`. Generic
1487
+ escape hatches: `defi(path, params)`, `dex(path, params, body?)`, `modal(path, body)`.
1446
1488
 
1447
1489
  ### Multi-chain RPC
1448
1490
 
1449
1491
  `RpcClient` wraps `POST /v1/rpc/{network}` — standard JSON-RPC 2.0 access to
1450
1492
  <!-- br:chains.rpc -->40<!-- /br:chains.rpc --> chains through one endpoint (Ethereum, Base, Solana, Polygon, BSC,
1451
1493
  Arbitrum, Optimism, Avalanche, Bitcoin, Sui, and more; powered by Tatum's RPC
1452
- gateway). Use account credits or the selected x402 wallet; no separate Tatum key is needed. A JSON-RPC batch is priced per element.
1494
+ gateway). Use account credit or an EVM wallet; no separate Tatum key is needed.
1495
+ A JSON-RPC batch is priced per element.
1453
1496
 
1454
- ```ts
1497
+ ```typescript
1455
1498
  import { RpcClient } from '@blockrun/llm';
1456
1499
 
1457
1500
  const client = new RpcClient();
@@ -1460,14 +1503,8 @@ const client = new RpcClient();
1460
1503
  const block = await client.call('ethereum', 'eth_blockNumber');
1461
1504
  console.log(parseInt(block.result as string, 16));
1462
1505
 
1463
- const balance = await client.call('base', 'eth_getBalance', [
1464
- '0x4200000000000000000000000000000000000006',
1465
- 'latest',
1466
- ]);
1467
-
1468
1506
  // Non-EVM chains speak their native JSON-RPC
1469
1507
  const slot = await client.call('solana', 'getSlot');
1470
- const tip = await client.call('bitcoin', 'getblockcount');
1471
1508
 
1472
1509
  // Batch: one payment, per-element pricing (rate x N)
1473
1510
  const out = await client.batch('polygon', [
@@ -1480,703 +1517,313 @@ console.log(block.cacheHit); // true if served from the gateway's hot cache
1480
1517
  console.log(block.txHash); // x402 settlement tx
1481
1518
  ```
1482
1519
 
1483
- 40 curated chains are exported as `SUPPORTED_NETWORKS`; common aliases
1484
- (`eth`, `arb`, `op`, `matic`, `bnb`, `avax`, `sol`, `btc`, `xrp`, `dot`, ...)
1485
- resolve server-side (`NETWORK_ALIASES`). Unknown but well-formed slugs fall
1486
- through to a generic `{slug}-mainnet` gateway attempt, so new chains work
1487
- without an SDK update. Hot, low-volatility reads (`eth_chainId`, mined
1488
- blocks/receipts, `getTransaction`, ...) are served from a method-aware
1489
- gateway cache — same price, lower latency.
1490
-
1491
- ### Testnet Models (Base Sepolia)
1492
- | Model |
1493
- |-------|
1494
- | `openai/gpt-oss-20b` |
1495
- | `openai/gpt-oss-120b` |
1496
-
1497
- *Testnet models use flat pricing (no token counting) for simplicity.*
1498
-
1499
- </details>
1500
-
1501
- ## Image Editing (img2img)
1520
+ The curated chains are exported as `SUPPORTED_NETWORKS` and common aliases
1521
+ (`eth`, `arb`, `sol`, `btc`, …) as `NETWORK_ALIASES`; unknown but well-formed
1522
+ slugs fall through to a generic `{slug}-mainnet` attempt, so new chains work
1523
+ without an SDK update.
1502
1524
 
1503
- Edit existing images with text prompts:
1504
-
1505
- ```typescript
1506
- import { LLMClient } from '@blockrun/llm';
1525
+ ## OpenAI and Anthropic Compatibility
1507
1526
 
1508
- const client = new LLMClient();
1527
+ ### OpenAI
1509
1528
 
1510
- const result = await client.imageEdit(
1511
- 'Make the sky purple and add northern lights',
1512
- 'data:image/png;base64,...', // base64 or URL
1513
- { model: 'openai/gpt-image-1' }
1514
- );
1515
- console.log(result.data[0].url);
1516
- ```
1517
-
1518
- ## Usage Examples
1519
-
1520
- ### Simple Chat
1529
+ `OpenAI` mirrors the `openai` SDK's `chat.completions.create()` on top of
1530
+ `LLMClient`, so the `blockrun/auto|eco|premium` aliases work. Streaming is shown
1531
+ under [Streaming](#streaming).
1521
1532
 
1522
1533
  ```typescript
1523
- import { LLMClient } from '@blockrun/llm';
1524
-
1525
- const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
1534
+ import { OpenAI } from '@blockrun/llm';
1526
1535
 
1527
- const response = await client.chat('openai/gpt-4o', 'Explain quantum computing');
1528
- console.log(response);
1536
+ const client = new OpenAI(); // BLOCKRUN_API_KEY, or { walletKey: '0x...' }, or { apiKey }
1529
1537
 
1530
- // With system prompt
1531
- const response2 = await client.chat('anthropic/claude-sonnet-5', 'Write a haiku', {
1532
- system: 'You are a creative poet.',
1538
+ const completion = await client.chat.completions.create({
1539
+ model: 'blockrun/auto',
1540
+ messages: [{ role: 'user', content: 'Hello!' }],
1533
1541
  });
1542
+ console.log(completion.choices[0].message.content);
1534
1543
  ```
1535
1544
 
1536
- ### Smart Routing (Router Core V3)
1545
+ ### Anthropic
1537
1546
 
1538
- Save up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on inference costs with intelligent model routing. The bundled Router Core V3 engine classifies each request across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions, applies hard capability filters, and ranks the cheapest capable models (<1ms, 100% local). Bundled — nothing extra to install.
1547
+ `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch
1548
+ that handles payment. It uses `BLOCKRUN_API_KEY` when set; otherwise it signs
1549
+ with your wallet, creating one if none exists (the same `getOrCreateWallet()`
1550
+ resolution as [Wallets](#wallets)). Your private key never leaves your machine.
1539
1551
 
1540
1552
  ```typescript
1541
- import { LLMClient } from '@blockrun/llm';
1542
-
1543
- const client = new LLMClient();
1544
-
1545
- // Auto-route to cheapest capable model
1546
- const result = await client.smartChat('What is 2+2?');
1547
- console.log(result.response); // '4'
1548
- console.log(result.model); // 'google/gemini-2.5-flash'
1549
- console.log(result.routing.tier); // 'SIMPLE'
1550
- console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 88%'
1551
-
1552
- // Routing profiles ('eco' | 'auto' | 'premium')
1553
- const eco = await client.smartChat('Explain AI', { routingProfile: 'eco' }); // Free tier first, then cheapest paid
1554
- const auto = await client.smartChat('Code review', { routingProfile: 'auto' }); // Balanced (default)
1555
- const premium = await client.smartChat('Write a legal brief', { routingProfile: 'premium' }); // Best quality
1556
-
1557
- // Guaranteed $0: call a free model directly
1558
- const free = await client.chat('nvidia/nemotron-3.5-lightning', 'Hello!');
1559
- ```
1560
-
1561
- **Routing Profiles:**
1562
-
1563
- | Profile | Description | Best For |
1564
- |---------|-------------|----------|
1565
- | `eco` | Budget-optimized — ranks the <!-- br:models.free -->7<!-- /br:models.free -->-model free tier first | Cost-sensitive workloads, zero-cost testing |
1566
- | `auto` | Intelligent routing (default) | General use |
1567
- | `premium` | Best quality models | Critical tasks |
1568
-
1569
- **Tiers:**
1570
-
1571
- | Tier | Example Tasks | Typical Models |
1572
- |------|---------------|----------------|
1573
- | SIMPLE | Greetings, math, lookups | Gemini Flash, GPT-4o-mini |
1574
- | MEDIUM | Explanations, summaries | GPT-4o, Claude Sonnet |
1575
- | COMPLEX | Analysis, code generation | GPT-5.2, Claude Opus |
1576
- | REASONING | Multi-step logic, planning | o3, DeepSeek Reasoner |
1577
-
1578
- ### Full Chat Completion
1579
-
1580
- ```typescript
1581
- import { LLMClient, type ChatMessage } from '@blockrun/llm';
1582
-
1583
- const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
1584
-
1585
- const messages: ChatMessage[] = [
1586
- { role: 'system', content: 'You are a helpful assistant.' },
1587
- { role: 'user', content: 'How do I read a file in Node.js?' },
1588
- ];
1589
-
1590
- const result = await client.chatCompletion('openai/gpt-4o', messages);
1591
- console.log(result.choices[0].message.content);
1592
- ```
1593
-
1594
- ### Streaming
1595
-
1596
- Stream responses token-by-token with automatic x402 payment. Uses a **pre-auth cache** to skip the 402 round-trip on repeat calls to the same model (~200ms saved per request after the first).
1597
-
1598
- #### OpenAI-compatible (recommended)
1553
+ import { AnthropicClient } from '@blockrun/llm';
1599
1554
 
1600
- ```typescript
1601
- import { OpenAI } from '@blockrun/llm';
1555
+ const client = new AnthropicClient();
1602
1556
 
1603
- const client = new OpenAI({ walletKey: process.env.BASE_CHAIN_WALLET_KEY });
1557
+ const response = await client.messages.create({
1558
+ model: 'claude-sonnet-4-6',
1559
+ max_tokens: 1024,
1560
+ messages: [{ role: 'user', content: 'Hello!' }],
1561
+ });
1562
+ console.log(response.content[0]);
1604
1563
 
1605
- const stream = await client.chat.completions.create({
1564
+ // Any model works in Anthropic format
1565
+ const gptResponse = await client.messages.create({
1606
1566
  model: 'openai/gpt-5.4',
1607
- messages: [{ role: 'user', content: 'Write a short story about AI agents' }],
1608
- stream: true,
1567
+ max_tokens: 1024,
1568
+ messages: [{ role: 'user', content: 'Hello from GPT!' }],
1609
1569
  });
1610
-
1611
- for await (const chunk of stream) {
1612
- process.stdout.write(chunk.choices[0]?.delta?.content || '');
1613
- }
1614
1570
  ```
1615
1571
 
1616
- #### Native client
1617
-
1618
- ```typescript
1619
- import { LLMClient, type ChatMessage } from '@blockrun/llm';
1620
-
1621
- const client = new LLMClient();
1622
-
1623
- const messages: ChatMessage[] = [
1624
- { role: 'user', content: 'Explain quantum computing in simple terms' },
1625
- ];
1626
-
1627
- // Returns a raw fetch Response with SSE body
1628
- const response = await client.chatCompletionStream('google/gemini-2.5-flash', messages);
1629
-
1630
- const reader = response.body!.getReader();
1631
- const decoder = new TextDecoder();
1572
+ This wrapper proxies straight to the gateway's `/v1/messages` and does not
1573
+ resolve `blockrun/*` aliases; pass a concrete model id. Anthropic's own dashed
1574
+ ids resolve, including the Mythos-class `claude-fable-5` and
1575
+ `claude-fable-5-1` / `claude-fable-5.1` (1M context, always-on thinking).
1632
1576
 
1633
- while (true) {
1634
- const { done, value } = await reader.read();
1635
- if (done) break;
1577
+ ## Wallets
1636
1578
 
1637
- const chunk = decoder.decode(value, { stream: true });
1638
- for (const line of chunk.split('\n')) {
1639
- if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;
1640
- const data = JSON.parse(line.slice(6));
1641
- process.stdout.write(data.choices?.[0]?.delta?.content || '');
1642
- }
1643
- }
1644
- ```
1579
+ ### Agent setup
1645
1580
 
1646
- #### Solana
1647
-
1648
- `SolanaLLMClient.stream()` is the Solana counterpart, with the same shape as
1649
- `BlockrunClient.stream()`: it yields each `data:` frame already parsed as JSON
1650
- and stops at `[DONE]`. The x402 handshake happens before the first frame — 402,
1651
- sign an SPL TransferChecked authorization locally, replay with
1652
- `PAYMENT-SIGNATURE` — including the fresh-blockhash re-sign on a
1653
- verification-phase rejection. A free model, or an account key, is answered `200`
1654
- with no handshake at all and settles nothing.
1581
+ One call for agent runtimes (Claude Code skills, MCP servers, …):
1655
1582
 
1656
1583
  ```typescript
1657
- import { SolanaLLMClient } from '@blockrun/llm';
1584
+ import { setupAgentClient, setupAgentWallet, setupAgentSolanaWallet, status } from '@blockrun/llm';
1658
1585
 
1659
- const client = new SolanaLLMClient(); // SOLANA_WALLET_KEY, or { apiKey: 'brk_live_…' }
1660
-
1661
- for await (const chunk of client.stream('/v1/chat/completions', {
1662
- model: 'deepseek/deepseek-chat',
1663
- messages: [{ role: 'user', content: 'Hi' }],
1664
- max_tokens: 64,
1665
- stream: true,
1666
- })) {
1667
- process.stdout.write(chunk?.choices?.[0]?.delta?.content ?? '');
1668
- }
1669
- ```
1670
-
1671
- Set `stream: true` yourself — the method does not inject it, because the gateway
1672
- prices a streaming and a non-streaming request the same and rewriting a caller's
1673
- body silently is how you end up debugging a request you did not send.
1674
-
1675
- #### Payment + streaming flow
1676
-
1677
- ```
1678
- First call (cache miss):
1679
- 1. Send request → 402 response (BlockRun returns price)
1680
- 2. Sign USDC payment locally (key never leaves machine)
1681
- 3. Retry with PAYMENT-SIGNATURE header + stream: true
1682
- 4. Cache payment requirements for this model (1h TTL)
1683
- 5. Stream tokens as they arrive
1684
-
1685
- Subsequent calls (cache hit):
1686
- 1. Pre-sign payment from cache — skip 402 round-trip
1687
- 2. Send request with PAYMENT-SIGNATURE upfront
1688
- 3. Stream tokens immediately (~200ms faster)
1689
- ```
1690
-
1691
- ### List Available Models
1692
-
1693
- ```typescript
1694
- import { LLMClient } from '@blockrun/llm';
1695
-
1696
- const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
1697
- const models = await client.listModels();
1698
-
1699
- for (const model of models) {
1700
- console.log(`${model.id}: $${model.inputPrice}/M input`);
1701
- }
1702
- ```
1703
-
1704
- ### Multiple Requests
1705
-
1706
- ```typescript
1707
- import { LLMClient } from '@blockrun/llm';
1586
+ // API key when configured; otherwise a saved ~/.blockrun/payment-chain preference,
1587
+ // an existing Base-only wallet stays on Base, and new wallets are created on Solana.
1588
+ const client = await setupAgentClient();
1589
+ const r = await client.smartChat('Hello!');
1708
1590
 
1709
- const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
1591
+ // Chain-specific: create the wallet if none exists and return a ready client
1592
+ const base = setupAgentWallet(); // LLMClient (sync)
1593
+ const solana = await setupAgentSolanaWallet(); // SolanaLLMClient
1710
1594
 
1711
- const [gpt, claude, gemini] = await Promise.all([
1712
- client.chat('openai/gpt-4o', 'What is 2+2?'),
1713
- client.chat('anthropic/claude-sonnet-5', 'What is 3+3?'),
1714
- client.chat('google/gemini-2.5-flash', 'What is 4+4?'),
1715
- ]);
1595
+ // Base wallet address and balance
1596
+ await status();
1597
+ // Wallet: 0xCC8c...5EF8
1598
+ // Balance: $5.30 USDC
1716
1599
  ```
1717
1600
 
1718
- ## Prediction Markets (Powered by Predexon)
1601
+ `getOrCreateWallet()` resolves the Base wallet in a fixed order:
1602
+ `BLOCKRUN_WALLET_KEY` or `BASE_CHAIN_WALLET_KEY`, then `~/.blockrun/.session`,
1603
+ then the legacy `~/.blockrun/wallet.key`. `LLMClient` itself reads only
1604
+ `BASE_CHAIN_WALLET_KEY` (or an explicit `privateKey`). For a single run of the
1605
+ helpers without changing anything, use `export BLOCKRUN_WALLET_KEY=<private-key>`.
1719
1606
 
1720
- Access real-time prediction market data from Polymarket, Kalshi, and Binance Futures via [Predexon](https://predexon.com). Use a BlockRun account API key or x402 wallet payments; no separate Predexon key is needed.
1607
+ ### Wallet discovery and migration
1721
1608
 
1722
- ### Polymarket
1609
+ The SDK can discover compatible wallets other applications wrote, for an
1610
+ explicit, user-confirmed migration. It never automatically makes a discovered
1611
+ wallet active.
1723
1612
 
1724
1613
  ```typescript
1725
- import { LLMClient } from '@blockrun/llm';
1614
+ import { listDiscoveredWallets, importWallet, listDiscoveredSolanaWallets } from '@blockrun/llm';
1726
1615
 
1727
- const client = new LLMClient();
1728
-
1729
- // List markets with optional filters
1730
- const markets = await client.pm("polymarket/markets");
1731
- const filtered = await client.pm("polymarket/markets", { status: "active", limit: 10 });
1732
- const searched = await client.pm("polymarket/markets", { search: "bitcoin" });
1733
-
1734
- // List events
1735
- const events = await client.pm("polymarket/events");
1736
-
1737
- // Historical trades
1738
- const trades = await client.pm("polymarket/trades");
1739
-
1740
- // OHLCV candlestick data for a specific condition
1741
- const candles = await client.pm("polymarket/candlesticks/0x1234abcd...");
1742
-
1743
- // Wallet profile (tier 2)
1744
- const profile = await client.pm("polymarket/wallet/0xABC123...");
1616
+ for (const w of listDiscoveredWallets()) {
1617
+ console.log(w.address, 'from', w.source); // addresses only, never private keys
1618
+ }
1745
1619
 
1746
- // Wallet P&L (tier 2)
1747
- const pnl = await client.pm("polymarket/wallet/pnl/0xABC123...");
1620
+ importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');
1748
1621
 
1749
- // Global leaderboard
1750
- const leaderboard = await client.pm("polymarket/leaderboard");
1622
+ // Solana: the same pair, async
1623
+ const solWallets = await listDiscoveredSolanaWallets();
1751
1624
  ```
1752
1625
 
1753
- ### Kalshi & Binance
1626
+ `scanWallets()` and `scanSolanaWallets()` also exist, but they return the
1627
+ discovered **private keys**; prefer the `listDiscovered*` functions unless you
1628
+ are writing a migration tool.
1754
1629
 
1755
- ```typescript
1756
- // Kalshi markets
1757
- const kalshiMarkets = await client.pm("kalshi/markets");
1630
+ `importWallet()` writes your current wallet to
1631
+ `~/.blockrun/.session.backup-<timestamp>` before switching, so adopting a wallet
1632
+ never strands funds in the old one. For Solana, use
1633
+ `await importSolanaWallet(address)`. Addresses shown are derived from the
1634
+ discovered key itself, and `importWallet()` matches on that derived address, so
1635
+ a wallet file cannot claim an address it cannot sign for, nor be adopted by one.
1758
1636
 
1759
- // Kalshi trades
1760
- const kalshiTrades = await client.pm("kalshi/trades");
1637
+ Earlier versions adopted the most recently written provider wallet
1638
+ automatically. If you relied on that, the first run after upgrading creates a
1639
+ fresh BlockRun wallet and prints the addresses it found, so you can import the
1640
+ one you actually own:
1761
1641
 
1762
- // Binance candles for supported pairs
1763
- const btcCandles = await client.pm("binance/candles/BTCUSDT");
1764
- const ethCandles = await client.pm("binance/candles/ETHUSDT");
1765
- // Also: SOLUSDT, XRPUSDT
1766
1642
  ```
1643
+ NOTICE: BlockRun created a new wallet, but also found existing wallet(s)
1644
+ belonging to other applications on this system:
1767
1645
 
1768
- ### Cross-Platform
1769
-
1770
- ```typescript
1771
- // Cross-platform matching pairs
1772
- const pairs = await client.pm("matching-markets/pairs");
1646
+ 0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f
1647
+ ...
1773
1648
  ```
1774
1649
 
1775
- All current endpoints are GET. The `pmQuery()` method is available for future POST endpoints.
1776
-
1777
- Works on both `LLMClient` (Base) and `SolanaLLMClient`.
1650
+ ### One wallet across every BlockRun product
1778
1651
 
1779
- ## Exa Web Search (Powered by Exa)
1652
+ Base wallet resolution, discovery, and adoption are implemented in
1653
+ [`@blockrun/core`](https://www.npmjs.com/package/@blockrun/core), the shared kernel
1654
+ this SDK, the `blockrun` CLI, and clawrouter-codex all read, so they agree on
1655
+ which wallet is yours. The kernel is bundled into the SDK at build time (frozen,
1656
+ reviewed bytes, no floating dependency), so there is nothing extra to install.
1780
1657
 
1781
- Access [Exa](https://exa.ai)'s neural web search using account credits or x402 wallet payments; no separate Exa key is needed. Use `SolanaLLMClient` for a Solana wallet or `LLMClient` for a Base wallet. Availability depends on the selected gateway.
1658
+ Set `BLOCKRUN_HOME` to override the base directory (`~` by default) for test
1659
+ isolation; unset, behaviour is unchanged. **Treat `BLOCKRUN_HOME` as
1660
+ security-sensitive**: it redirects where the signing key is read from and
1661
+ written to, so an environment that can set it controls the wallet as surely as
1662
+ one that can set `BLOCKRUN_WALLET_KEY`. Set it before importing the SDK — the
1663
+ exported `WALLET_FILE_PATH` / `WALLET_DIR_PATH` constants snapshot at import
1664
+ (all internal reads and writes resolve per call). Solana wallet resolution, the
1665
+ response cache and the cost log do not honour `BLOCKRUN_HOME`.
1782
1666
 
1783
- | Method | Description |
1784
- |---|---|
1785
- | `exaSearch(query, options?)` | Neural/keyword web search |
1786
- | `exaFindSimilar(url, options?)` | Find semantically similar pages |
1787
- | `exaContents(urls, options?)` | Extract full text from URLs |
1788
- | `exaAnswer(query, options?)` | AI answer grounded in web search |
1789
- | `exa(path, body)` | Generic proxy for any Exa endpoint |
1667
+ ## Configuration and Environment Variables
1790
1668
 
1791
1669
  ```typescript
1792
1670
  import { LLMClient } from '@blockrun/llm';
1793
1671
 
1794
- const client = new LLMClient();
1795
-
1796
- // Neural web search
1797
- const results = await client.exaSearch("latest AI safety research", { numResults: 5 });
1798
- const news = await client.exaSearch("bitcoin ETF news", { category: "news", numResults: 10 });
1799
-
1800
- // Find similar pages
1801
- const similar = await client.exaFindSimilar("https://openai.com/research/gpt-4", { numResults: 5 });
1802
-
1803
- // Extract content from URLs
1804
- const content = await client.exaContents(["https://arxiv.org/abs/2303.08774"]);
1805
-
1806
- // AI-generated answer from live web
1807
- const answer = await client.exaAnswer("What is the current state of AI safety research?");
1808
-
1809
- // Generic proxy for any Exa endpoint
1810
- const custom = await client.exa("search", { query: "transformer architecture", numResults: 5 });
1811
- ```
1812
-
1813
- Same surface on `SolanaLLMClient` once Solana-side `EXA_API_KEY` is provisioned.
1814
-
1815
- ## Configuration
1816
-
1817
- ```typescript
1818
- // Default: reads BASE_CHAIN_WALLET_KEY from environment
1672
+ // Default: BLOCKRUN_API_KEY, else BASE_CHAIN_WALLET_KEY, from the environment
1819
1673
  const client = new LLMClient();
1820
1674
 
1821
1675
  // Or pass options explicitly
1822
- const client = new LLMClient({
1823
- privateKey: '0x...', // Your wallet key (never sent to server)
1824
- apiUrl: 'https://blockrun.ai/api', // Optional
1825
- timeout: 60000, // Optional (ms)
1676
+ const configured = new LLMClient({
1677
+ privateKey: '0x...', // wallet key, used only for local signing
1678
+ // apiKey: 'brk_live_...', // or account billing (not both)
1679
+ apiUrl: 'https://blockrun.ai/api', // optional; https://arc.blockrun.ai/api for Arc
1680
+ timeout: 600_000, // optional, ms (default 600000)
1826
1681
  });
1827
1682
  ```
1828
1683
 
1829
- ## Environment Variables
1830
-
1831
1684
  | Variable | Description |
1832
1685
  |----------|-------------|
1833
- | `BLOCKRUN_API_KEY` | Your BlockRun account key (`brk_...`) — account billing, no wallet needed. Create one at [user.blockrun.ai/dashboard/keys](https://user.blockrun.ai/dashboard/keys) |
1834
- | `BLOCKRUN_API_BASE_URL` | Account API endpoint (optional, default: https://api.blockrun.ai) |
1835
- | `BASE_CHAIN_WALLET_KEY` | Your Base chain wallet private key (for Base / `LLMClient`) |
1836
- | `SOLANA_WALLET_KEY` | Your Solana wallet secret key - bs58 encoded (for `SolanaLLMClient`) |
1837
- | `BLOCKRUN_API_URL` | x402 gateway endpoint (optional, default: https://blockrun.ai/api) |
1838
-
1839
- `BLOCKRUN_API_KEY` takes precedence: if it is set alongside a wallet key env var and
1840
- you pass no explicit credential, the client runs in account mode. Pass an explicit
1841
- `privateKey` to force wallet mode.
1842
-
1843
- `BLOCKRUN_SOL_OPERATOR` is **not** an environment variable or a secret. It is an
1844
- SDK export used for the explicit batch-settlement trust configuration shown in
1845
- [Solana Support](#batch-settlement-optional-metered-billing).
1686
+ | `BLOCKRUN_API_KEY` | Account key (`brk_...`): account billing, no wallet needed. Create one at [user.blockrun.ai/dashboard/keys](https://user.blockrun.ai/dashboard/keys) |
1687
+ | `BLOCKRUN_API_BASE_URL` | Account API endpoint (default `https://api.blockrun.ai`) |
1688
+ | `BASE_CHAIN_WALLET_KEY` | EVM wallet private key for `LLMClient` (Base / Arc) and every EVM client |
1689
+ | `BLOCKRUN_WALLET_KEY` | EVM wallet key honoured by the service clients, `BlockrunClient`, `setupAgentWallet()` and `AnthropicClient` (checked before `BASE_CHAIN_WALLET_KEY`); **not** read by `LLMClient` |
1690
+ | `SOLANA_WALLET_KEY` | Solana secret key, bs58-encoded, for `SolanaLLMClient` |
1691
+ | `SOLANA_RPC_URL` | Solana RPC for payments and balance (also `rpcUrl`) |
1692
+ | `SOLANA_RPC_API_KEY` / `SOLANA_RPC_HEADERS` | Header auth for that RPC (also `rpcHeaders`) |
1693
+ | `BASE_RPC_URL` | Base RPC tried first by `LLMClient.getBalance()` |
1694
+ | `BLOCKRUN_CHAT_TIMEOUT` | `LLMClient` default timeout, in **seconds** (default 600) |
1695
+ | `BLOCKRUN_HOME` | Base directory for the Base wallet files (security-sensitive, see [Wallets](#one-wallet-across-every-blockrun-product)) |
1696
+
1697
+ `BLOCKRUN_SOL_OPERATOR` is **not** an environment variable or a secret. It is
1698
+ an SDK export used for the explicit [batch-settlement](#batch-settlement-optional-metered-billing)
1699
+ trust configuration.
1700
+
1701
+ ### Account mode details
1702
+
1703
+ - **Base URL.** Account clients use `https://api.blockrun.ai`; an OpenAI-style
1704
+ `https://api.blockrun.ai/v1` base is also accepted. Override with `apiUrl`
1705
+ (`baseURL` on `OpenAI`) or `BLOCKRUN_API_BASE_URL`.
1706
+ - **Origin pinning.** The key is only ever sent to the configured origin,
1707
+ including when following a job's `poll_url`; redirects are refused.
1708
+ - **Retries.** Account POSTs are never replayed automatically, including
1709
+ through the Anthropic wrapper: the first POST is the billed one. GET/HEAD
1710
+ requests retry temporary gateway errors (502/503/504/522/524). A 429 returns
1711
+ `retryAfter` to the caller.
1712
+ - **Async jobs.** Accepted jobs keep polling the same job within the original
1713
+ timeout. Keep the complete returned `poll_url`, including its query
1714
+ parameters; if polling times out, check the job and Activity before
1715
+ submitting again.
1716
+ - **Wallet-only helpers.** `getWalletAddress()`, `getBalance()` and
1717
+ `getSpending()` throw in account mode; account usage is on
1718
+ [Activity](https://user.blockrun.ai/dashboard/activity). Account credit does
1719
+ not sign trades or transfer wallet funds.
1720
+ - **Switching back to a wallet.** Unset `BLOCKRUN_API_KEY` and create a new
1721
+ client, or pass an explicit `privateKey`. Existing clients keep the
1722
+ credentials they were built with.
1723
+
1724
+ ### Local cache helpers
1725
+
1726
+ No client caches responses automatically. `cache.ts` exports manual helpers
1727
+ for your own dedup: `getCachedByRequest(endpoint, body)`, `saveToCache(endpoint,
1728
+ body, response, costUsd?)`, `getCached(key)`, `setCache(key, data, ttlMs)` and
1729
+ `clearCache()`. TTLs by endpoint: `/v1/search` 15 minutes, `/v1/pm/` 30
1730
+ minutes, `/v1/models` 24 hours, `/v1/partner/` 1 hour, chat and image never,
1731
+ anything else 1 hour. `saveToCache()` also archives a readable copy under
1732
+ `~/.blockrun/data/` and appends to `~/.blockrun/cost_log.jsonl`.
1846
1733
 
1847
1734
  ## Error Handling
1848
1735
 
1849
1736
  ```typescript
1850
- import { LLMClient, APIError, PaymentError } from '@blockrun/llm';
1737
+ import { LLMClient, APIError, PaymentError, retryDisposition } from '@blockrun/llm';
1851
1738
 
1852
1739
  const client = new LLMClient();
1853
1740
 
1854
1741
  try {
1855
- const response = await client.chat('openai/gpt-4o', 'Hello!');
1742
+ const response = await client.smartChat('Hello!');
1856
1743
  } catch (error) {
1857
1744
  if (error instanceof PaymentError) {
1858
- console.error('Payment failed - check USDC balance');
1745
+ // Wallet mode: signing or settlement failed. Check the USDC balance.
1746
+ console.error(`Payment failed: ${error.message}`);
1859
1747
  } else if (error instanceof APIError) {
1860
- console.error(`API error: ${error.message}`);
1748
+ if (error.statusCode === 402) {
1749
+ console.error('Account credit exhausted: top up at https://user.blockrun.ai');
1750
+ } else if (error.statusCode === 429) {
1751
+ console.error(`Rate limited; retry after ${error.retryAfter ?? 'a moment'}`);
1752
+ } else {
1753
+ console.error(`API error ${error.statusCode}: ${error.message}`);
1754
+ }
1755
+ }
1756
+ if (retryDisposition(error) === 'unpaid') {
1757
+ // nothing was charged: safe to retry
1861
1758
  }
1862
1759
  }
1863
1760
  ```
1864
1761
 
1865
- Before retrying a failed call yourself, check `retryDisposition(error)`
1866
- (see [Automatic Fallback on Transient Errors](#automatic-fallback-on-transient-errors)):
1762
+ In account mode an exhausted or missing credit balance is an `APIError` with
1763
+ `statusCode` 402, never a `PaymentError`. Errors keep the account error's
1764
+ `response.code`, and the key is redacted from error bodies. Before retrying a
1765
+ failed call yourself, check `retryDisposition(error)` (see
1766
+ [Automatic Fallback on Transient Errors](#automatic-fallback-on-transient-errors)):
1867
1767
  anything but `'unpaid'` may already have been charged.
1868
1768
 
1869
1769
  ## Testing
1870
1770
 
1871
- ### Running Unit Tests
1872
-
1873
- Unit tests do not require API access or funded wallets:
1874
-
1875
1771
  ```bash
1876
- npm test # Run tests in watch mode
1877
- npm test run # Run tests once
1878
- npm test -- --coverage # Run with coverage report
1772
+ npm test # vitest in watch mode
1773
+ npx vitest run --exclude 'test/integration/**' # unit tests once, no network
1879
1774
  ```
1880
1775
 
1881
- ### Running Integration Tests
1882
-
1883
- Integration tests call the production API and require:
1884
- - A funded Base wallet with USDC ($1+ recommended)
1885
- - `BASE_CHAIN_WALLET_KEY` environment variable set
1886
- - Integration tests make real paid calls; cost depends on the models exercised
1776
+ Unit tests do not need API access or a funded wallet. **Plain `npm test` also
1777
+ runs `test/integration`**, which makes **real paid calls** against production
1778
+ when `BASE_CHAIN_WALLET_KEY` or `SOLANA_WALLET_KEY` is set in your environment
1779
+ (and is skipped when it is not). To run the integration suite on purpose, use a
1780
+ funded test wallet ($1+ of USDC) and:
1887
1781
 
1888
1782
  ```bash
1889
1783
  export BASE_CHAIN_WALLET_KEY=0x...
1890
- npm test -- test/integration # Run integration tests only
1891
- ```
1892
-
1893
- Integration tests are automatically skipped if `BASE_CHAIN_WALLET_KEY` is not set.
1894
-
1895
- ## Setting Up Your Wallet
1896
-
1897
- ### Solana
1898
- 1. Create a Solana wallet (Phantom, Backpack, Solflare, etc.)
1899
- 2. Get USDC on Solana for API payments
1900
- 3. Export your secret key and set as `SOLANA_WALLET_KEY`
1901
-
1902
- ```bash
1903
- # .env
1904
- SOLANA_WALLET_KEY=...your_bs58_secret_key
1784
+ npx vitest run test/integration
1905
1785
  ```
1906
1786
 
1907
- ### Base (EVM)
1908
- 1. Create a wallet on Base (Coinbase Wallet, MetaMask, etc.)
1909
- 2. Get USDC on Base for API payments
1910
- 3. Export your private key and set as `BASE_CHAIN_WALLET_KEY`
1911
-
1912
- ```bash
1913
- # .env
1914
- BASE_CHAIN_WALLET_KEY=0x...
1915
- ```
1916
-
1917
- Note: Solana transactions are gasless for the user - the CDP facilitator pays for transaction fees.
1918
-
1919
1787
  ## Security
1920
1788
 
1921
- ### Private Key Safety
1922
-
1923
- - **Private key stays local**: Your key is only used for signing on your machine
1924
- - **No custody**: BlockRun never holds your funds
1925
- - **Verify transactions**: All payments are on-chain and verifiable
1926
-
1927
- ### Best Practices
1928
-
1929
- **Private Key Management:**
1930
- - Use environment variables, never hard-code keys
1931
- - Use dedicated wallets for API payments (separate from main holdings)
1932
- - Set spending limits by only funding payment wallets with small amounts
1933
- - Never commit `.env` files to version control
1934
- - Rotate keys periodically
1935
-
1936
- **Input Validation:**
1937
- The SDK validates all inputs before API requests:
1938
- - Private keys (format, length, valid hex)
1939
- - API URLs (HTTPS required for production, HTTP allowed for localhost)
1940
- - Model names and parameters (ranges for max\_tokens, temperature, top\_p)
1789
+ - **Keys stay local.** A wallet key only signs on your machine; only the signature is sent.
1790
+ - **Use a dedicated payment wallet** funded with small amounts, separate from your main holdings.
1791
+ - **Keep secrets in the environment.** Never hard-code keys or API keys, never commit `.env`, and keep API keys out of browser code and logs.
1792
+ - **Rotate** API keys and wallet keys periodically.
1793
+ - **Verify on-chain.** Wallet payments are on-chain transfers you can check on an explorer:
1941
1794
 
1942
- **Error Sanitization:**
1943
- API errors are automatically sanitized to prevent sensitive information leaks.
1944
-
1945
- **Monitoring:**
1946
1795
  ```typescript
1947
- const address = client.getWalletAddress();
1948
- console.log(`View transactions: https://basescan.org/address/${address}`);
1949
- ```
1950
-
1951
- **Keep Updated:**
1952
- ```bash
1953
- npm update @blockrun/llm # Get security patches
1954
- ```
1955
-
1956
- ## TypeScript Support
1957
-
1958
- Full TypeScript support with exported types:
1959
-
1960
- ```typescript
1961
- import {
1962
- LLMClient,
1963
- OpenAI,
1964
- type ChatMessage,
1965
- type ChatResponse,
1966
- type ChatOptions,
1967
- type ChatCompletionOptions,
1968
- type Model,
1969
- // Smart routing types
1970
- type SmartChatOptions,
1971
- type SmartChatResponse,
1972
- type RoutingDecision,
1973
- type RoutingProfile,
1974
- type RoutingTier,
1975
- APIError,
1976
- PaymentError,
1977
- } from '@blockrun/llm';
1978
-
1979
- // chatCompletionStream returns a standard fetch Response with SSE body
1980
- const streamResponse: Response = await client.chatCompletionStream(model, messages, options);
1981
-
1982
- // OpenAI-compat stream returns AsyncIterable
1983
- const stream: AsyncIterable<OpenAIChatCompletionChunk> = await openaiClient.chat.completions.create({
1984
- model, messages, stream: true
1985
- });
1986
- ```
1987
-
1988
- ## Agent Wallet Setup
1989
-
1990
- One-line setup for agent runtimes (Claude Code skills, MCP servers, etc.):
1991
-
1992
- ```typescript
1993
- import { setupAgentWallet } from '@blockrun/llm';
1994
-
1995
- // Auto-creates wallet if none exists, returns ready client
1996
- const client = setupAgentWallet();
1997
- const response = await client.chat('openai/gpt-5.4', 'Hello!');
1998
- ```
1999
-
2000
- For Solana:
2001
-
2002
- ```typescript
2003
- import { setupAgentSolanaWallet } from '@blockrun/llm';
2004
-
2005
- const client = await setupAgentSolanaWallet();
2006
- const response = await client.chat('anthropic/claude-sonnet-4.6', 'Hello!');
2007
- ```
2008
-
2009
- Check wallet status:
2010
-
2011
- ```typescript
2012
- import { status } from '@blockrun/llm';
2013
-
2014
- await status();
2015
- // Wallet: 0xCC8c...5EF8
2016
- // Balance: $5.30 USDC
2017
- ```
2018
-
2019
- ## Wallet Discovery and Migration
2020
-
2021
- The SDK can discover compatible wallets for an explicit, user-confirmed
2022
- migration. It never automatically makes a discovered provider wallet active:
2023
-
2024
- ```typescript
2025
- import { scanWallets, scanSolanaWallets } from '@blockrun/llm';
2026
-
2027
- // Scans ~/.<dir>/wallet.json for Base wallets
2028
- const baseWallets = scanWallets();
2029
-
2030
- // Scans ~/.<dir>/solana-wallet.json and ~/.brcc/wallet.json
2031
- const solWallets = scanSolanaWallets();
2032
- ```
2033
-
2034
- `getOrCreateWallet()` always uses `~/.blockrun/.session` (or an explicit
2035
- wallet environment variable, or the legacy `~/.blockrun/wallet.key`). Review
2036
- the discovered addresses and import one explicitly if you intend to switch
2037
- wallets.
2038
-
2039
- ### Upgrading from a provider wallet
2040
-
2041
- Earlier versions adopted the most recently written provider wallet
2042
- automatically. If you relied on that, the first run after upgrading creates a
2043
- fresh BlockRun wallet and prints the addresses it found, so you can import the
2044
- one you actually own:
2045
-
2046
- ```
2047
- NOTICE: BlockRun created a new wallet, but also found existing wallet(s)
2048
- belonging to other applications on this system:
2049
-
2050
- 0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f
2051
- ...
2052
- ```
2053
-
2054
- Adopt one deliberately:
2055
-
2056
- ```typescript
2057
- import { listDiscoveredWallets, importWallet } from '@blockrun/llm';
2058
-
2059
- for (const w of listDiscoveredWallets()) {
2060
- console.log(w.address, 'from', w.source);
2061
- }
2062
-
2063
- importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');
2064
- ```
2065
-
2066
- `importWallet()` writes your current wallet to
2067
- `~/.blockrun/.session.backup-<timestamp>` before switching, so adopting a wallet
2068
- never strands funds in the old one. Solana: `listDiscoveredSolanaWallets()` and
2069
- `importSolanaWallet()`.
2070
-
2071
- Addresses shown are derived from the discovered key itself, and `importWallet()`
2072
- matches on that derived address — so a wallet file cannot claim an address it
2073
- cannot sign for, nor be adopted by one. `listDiscoveredWallets()` never returns
2074
- private keys.
2075
-
2076
- ### One wallet across every BlockRun product
2077
-
2078
- Base wallet resolution, discovery, and adoption are implemented in
2079
- [`@blockrun/core`](https://www.npmjs.com/package/@blockrun/core), the shared kernel
2080
- this SDK, the `blockrun` CLI, and clawrouter-codex all read. Defining the canonical
2081
- order in one place is what keeps them in agreement — when each product carried its
2082
- own copy, they drifted, and a fix made here did not reach the CLI.
2083
-
2084
- The kernel is bundled into the SDK at build time (frozen, reviewed bytes — no
2085
- floating dependency), so there is nothing extra to install. Set `BLOCKRUN_HOME`
2086
- to override the base directory (`~` by default) for test isolation; unset,
2087
- behaviour is unchanged. **Treat `BLOCKRUN_HOME` as security-sensitive**: it
2088
- redirects where the signing key is read from and written to, so an environment
2089
- that can set it controls the wallet as surely as one that can set
2090
- `BLOCKRUN_WALLET_KEY`. Set it before importing the SDK — the exported
2091
- `WALLET_FILE_PATH`/`WALLET_DIR_PATH` constants snapshot at import (all internal
2092
- reads and writes resolve per call). Solana resolution is still SDK-local and
2093
- does not honor `BLOCKRUN_HOME`.
2094
-
2095
- For a single run without changing anything, use
2096
- `export BLOCKRUN_WALLET_KEY=<private-key>`.
2097
-
2098
- ## Response Caching
2099
-
2100
- The SDK caches responses to avoid duplicate payments:
2101
-
2102
- ```typescript
2103
- import { getCachedByRequest, saveToCache, clearCache } from '@blockrun/llm';
2104
-
2105
- // Automatic TTLs by endpoint:
2106
- // - Search: 15 minutes
2107
- // - Models: 24 hours
2108
- // - Chat/Image: no cache (every call is unique)
2109
-
2110
- // Manual cache management
2111
- clearCache(); // Remove all cached responses
2112
- ```
2113
-
2114
- ## Cost Logging
2115
-
2116
- Track spending across sessions:
2117
-
2118
- ```typescript
2119
- import { logCost, getCostSummary } from '@blockrun/llm';
2120
-
2121
- // Costs are logged to ~/.blockrun/data/costs.jsonl
2122
- const summary = getCostSummary();
2123
- console.log(`Total: $${summary.totalUsd.toFixed(2)}`);
2124
- console.log(`Calls: ${summary.calls}`);
2125
- console.log(`By model:`, summary.byModel);
2126
- ```
2127
-
2128
- ## Anthropic SDK Compatibility
2129
-
2130
- Use the official Anthropic SDK interface with BlockRun's pay-per-request backend:
2131
-
2132
- ```typescript
2133
- import { AnthropicClient } from '@blockrun/llm';
2134
-
2135
- const client = new AnthropicClient(); // Auto-detects wallet, auto-pays
2136
-
2137
- const response = await client.messages.create({
2138
- model: 'claude-sonnet-4-6',
2139
- max_tokens: 1024,
2140
- messages: [{ role: 'user', content: 'Hello!' }],
2141
- });
2142
- console.log(response.content[0].text);
1796
+ import { LLMClient } from '@blockrun/llm';
2143
1797
 
2144
- // Any model works in Anthropic format
2145
- const gptResponse = await client.messages.create({
2146
- model: 'openai/gpt-5.4',
2147
- max_tokens: 1024,
2148
- messages: [{ role: 'user', content: 'Hello from GPT!' }],
2149
- });
1798
+ const client = new LLMClient();
1799
+ console.log(`View transactions: https://basescan.org/address/${client.getWalletAddress()}`);
2150
1800
  ```
2151
1801
 
2152
- The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch that handles x402 payment automatically. Your private key never leaves your machine. The Mythos-class `claude-fable-5` alias is available here too (1M context, always-on thinking).
2153
-
2154
- ## Links
2155
-
2156
- - [Website](https://blockrun.ai)
2157
- - [Documentation](https://github.com/BlockRunAI/awesome-blockrun/tree/main/docs)
2158
- - [GitHub](https://github.com/blockrunai/blockrun-llm-ts)
2159
- - [Telegram](https://t.me/+mroQv4-4hGgzOGUx)
1802
+ The SDK validates `max_tokens` ranges, EVM private-key format, and API URLs
1803
+ (HTTPS required, except for localhost) before sending a request. Error bodies
1804
+ are sanitized, and API keys are redacted from them. Run `npm update @blockrun/llm`
1805
+ to pick up security fixes.
2160
1806
 
2161
1807
  ## Frequently Asked Questions
2162
1808
 
2163
1809
  ### What is @blockrun/llm?
2164
- @blockrun/llm is a TypeScript SDK that cuts LLM costs by up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% with built-in smart routing: every request is routed to the cheapest of <!-- br:models.chatVisible -->88<!-- /br:models.chatVisible --> models (OpenAI, Anthropic, Google, xAI, DeepSeek, Moonshot, and more) that can handle it, then paid per-request in USDC via the x402 protocol — with API key account billing or x402 wallet payments on Solana or Base.
2165
-
2166
- ### How does payment work?
2167
- When you make an API call, the SDK automatically handles x402 payment. It signs a USDC transaction locally using your wallet private key (which never leaves your machine), and includes the payment proof in the request header. Settlement is non-custodial and instant on Base or Solana.
1810
+ A TypeScript SDK that cuts LLM costs by <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on a realistic workload with built-in smart routing: every request goes to the cheapest of <!-- br:models.chatVisible -->88<!-- /br:models.chatVisible --> models (OpenAI, Anthropic, Google, xAI, DeepSeek, Moonshot, and more) that can handle it, paid with an account API key or per-request USDC via x402 on Solana, Base or Arc.
2168
1811
 
2169
1812
  ### What is smart routing?
2170
- Router Core V3 is bundled into the SDK — the same deterministic routing engine that powers ClawRouter, with nothing extra to install. It analyzes your request across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions and automatically picks the cheapest model capable of handling it. Routing happens locally in under 1ms. Use `smartChat()`, `smartChatCompletion()`, or the `blockrun/auto` model alias. It can save up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on LLM costs compared to using premium models for every request.
1813
+ Router Core V3, bundled into the SDK: the same deterministic engine that powers ClawRouter. It analyzes each request across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions locally, in under 1ms, and picks the cheapest capable model. Use `smartChat()`, `smartChatCompletion()`, or the `blockrun/auto` model alias. See [Smart Routing](#smart-routing-router-core-v3).
2171
1814
 
2172
1815
  ### Does it support streaming?
2173
- Yes — as of v1.6.1. Use `client.chatCompletionStream()` for native streaming or `stream: true` in the OpenAI-compatible client. Payment is handled automatically: the SDK signs USDC payment before streaming begins, and caches payment requirements per model so subsequent calls skip the 402 round-trip (~200ms faster).
1816
+ Yes: `LLMClient.chatCompletionStream()`, `stream: true` in the OpenAI-compatible client, and `SolanaLLMClient.stream()`. Payment happens before the first token, and with an API key the stream is billed to your account.
2174
1817
 
2175
1818
  ### How much does it cost?
2176
- Pay only for what you use. There are no minimums, subscriptions, or monthly fees, and $5 in USDC gets you thousands of requests. Live per-model rates are at [blockrun.ai/models](https://blockrun.ai/models).
1819
+ Pay only for what you use: no minimums, subscriptions, or monthly fees, and seven models are free. Live per-model rates are at [blockrun.ai/models](https://blockrun.ai/models).
1820
+
1821
+ ### Do I need a crypto wallet?
1822
+ No. An account API key from [user.blockrun.ai](https://user.blockrun.ai) works without any wallet or chain. Wallets are for x402 pay-per-request on Solana (`SolanaLLMClient`, recommended), Base or Arc (`LLMClient`).
2177
1823
 
2178
- ### Does it support both Solana and Base?
2179
- Yes. Use `SolanaLLMClient` for Solana payments (recommended) and `LLMClient` for Base payments. Use `apiKey` for account billing without selecting a chain.
1824
+ ## License
1825
+
1826
+ MIT
2180
1827
 
2181
1828
  ---
2182
1829
 
@@ -2187,7 +1834,3 @@ Yes. Use `SolanaLLMClient` for Solana payments (recommended) and `LLMClient` for
2187
1834
  [Website](https://blockrun.ai) · [Models & Pricing](https://blockrun.ai/models) · [ClawRouter](https://github.com/BlockRunAI/ClawRouter) · [Python SDK](https://github.com/BlockRunAI/blockrun-llm) · [Telegram](https://t.me/blockrunAI)
2188
1835
 
2189
1836
  </div>
2190
-
2191
- ## License
2192
-
2193
- MIT