@blockrun/llm 3.22.1 → 3.23.1
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 +959 -1319
- package/dist/index.cjs +61 -4
- package/dist/index.d.cts +433 -4
- package/dist/index.d.ts +433 -4
- package/dist/index.js +60 -4
- package/package.json +1 -1
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
|
|
8
|
+
paid with an API key or per-request USDC on Solana, Base or Arc. No vendor lock-in.
|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/@blockrun/llm)
|
|
11
11
|
[](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
|
|
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'
|
|
38
|
-
console.log(r.routing.savings); // 0.
|
|
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
|
|
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
|
|
47
|
-
- 🆓 **<!-- br:models.free -->7<!-- /br:models.free --> genuinely free models
|
|
48
|
-
- 🔐 **Two ways to connect
|
|
49
|
-
- 💸 **Pay per request in USDC
|
|
50
|
-
- 🛡️ **Automatic failover
|
|
51
|
-
- ⚡ **Streaming, OpenAI & Anthropic compat
|
|
52
|
-
- 🎨 **Beyond chat
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
114
|
-
|
|
115
|
-
|
|
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(); //
|
|
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(); //
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
//
|
|
167
|
-
//
|
|
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
|
-
|
|
181
|
+
const routed = await client.smartChat('Explain photosynthesis.');
|
|
182
|
+
console.log(routed.model, routed.response);
|
|
170
183
|
|
|
171
|
-
//
|
|
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();
|
|
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
|
|
201
|
+
const reply = await client.chat('openai/gpt-5.4', 'Hello!');
|
|
187
202
|
```
|
|
188
203
|
|
|
189
|
-
That's it. The SDK handles x402 payment
|
|
190
|
-
|
|
191
|
-
|
|
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?
|
|
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
|
-
|
|
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
|
|
206
|
-
const result = await client.smartChat('What is
|
|
207
|
-
console.log(result.model);
|
|
208
|
-
console.log(result.response);
|
|
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
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
|
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
|
|
257
|
-
locally with
|
|
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
|
-
|
|
263
|
-
|
|
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
|
-
//
|
|
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
|
|
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 `
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
|
364
|
-
| `premium` |
|
|
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
|
-
|
|
367
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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); // '
|
|
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["
|
|
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
|
|
399
|
-
|
|
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
|
|
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
|
-
|
|
414
|
-
on
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
|
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
|
-
|
|
455
|
-
`
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
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
|
-
//
|
|
483
|
-
const
|
|
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
|
-
//
|
|
486
|
-
const response = await client.chat('
|
|
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
|
|
490
|
-
const
|
|
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', [
|
|
@@ -513,17 +641,21 @@ nothing is added to `getSpending()`.
|
|
|
513
641
|
|
|
514
642
|
**Setup:**
|
|
515
643
|
1. Export your Solana wallet key: `export SOLANA_WALLET_KEY="your-bs58-key"`
|
|
516
|
-
2. Fund with USDC on Solana mainnet
|
|
644
|
+
2. Fund it with USDC on Solana mainnet
|
|
517
645
|
3. That's it — payments are automatic via x402
|
|
518
646
|
|
|
519
|
-
**
|
|
520
|
-
**Payment:** Solana USDC (SPL, mainnet)
|
|
647
|
+
**Endpoint:** `https://sol.blockrun.ai/api` · **Payment:** Solana USDC (SPL, mainnet)
|
|
521
648
|
|
|
522
649
|
**Images, music and video on Solana:**
|
|
523
650
|
|
|
524
651
|
```typescript
|
|
652
|
+
import { SolanaLLMClient } from '@blockrun/llm';
|
|
653
|
+
|
|
654
|
+
const client = new SolanaLLMClient();
|
|
655
|
+
|
|
525
656
|
const image = await client.image('a lighthouse at dusk', { model: 'openai/gpt-image-2' });
|
|
526
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.
|
|
527
659
|
const video = await client.video('a red apple slowly spinning', { duration_seconds: 5 });
|
|
528
660
|
```
|
|
529
661
|
|
|
@@ -531,7 +663,7 @@ Slow jobs answer `202 { id, poll_url }` and the client polls for you. Each poll
|
|
|
531
663
|
proves the job is yours with a wallet signature (`x-poll-wallet`,
|
|
532
664
|
`x-poll-timestamp`, `x-poll-signature`: ed25519 over
|
|
533
665
|
`blockrun-poll:v1:<kind>:<job id>:<unix seconds>`), not a payment, so a
|
|
534
|
-
multi-minute job
|
|
666
|
+
multi-minute job does not sign a Solana transaction every few seconds. Image
|
|
535
667
|
and music are paid when submitted. A video is charged only once it is finished:
|
|
536
668
|
the client signs one payment for the completed job, and a failed job costs
|
|
537
669
|
nothing. If the gateway does not accept the poll signature for a job, the client
|
|
@@ -540,26 +672,18 @@ unchanged.
|
|
|
540
672
|
|
|
541
673
|
### Batch settlement (optional metered billing)
|
|
542
674
|
|
|
543
|
-
By default
|
|
544
|
-
priced at the request ceiling
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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.
|
|
548
680
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
> 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`.
|
|
554
685
|
|
|
555
|
-
|
|
556
|
-
If batch is unavailable or unsafe for a request, the SDK falls back to `exact`
|
|
557
|
-
before it sends a batch payment. Once one is sent, a call whose payment may have
|
|
558
|
-
been charged is raised as `BatchPaymentUnresolvedError`, never paid again (see
|
|
559
|
-
[Runtime behavior](#runtime-behavior)).
|
|
560
|
-
|
|
561
|
-
In addition to the Solana wallet dependencies from [Installation](#installation),
|
|
562
|
-
batch mode needs three optional peer dependencies:
|
|
686
|
+
Batch mode needs three more optional peers on top of the Solana ones:
|
|
563
687
|
|
|
564
688
|
```bash
|
|
565
689
|
npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit
|
|
@@ -571,394 +695,252 @@ import { SolanaLLMClient, BLOCKRUN_SOL_OPERATOR } from '@blockrun/llm';
|
|
|
571
695
|
const client = new SolanaLLMClient({
|
|
572
696
|
privateKey: process.env.SOLANA_WALLET_KEY,
|
|
573
697
|
batch: {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// Most escrow at stake in the channel: deposited and not yet settled. Default: "$1".
|
|
577
|
-
maxDeposit: '$5',
|
|
698
|
+
operators: [BLOCKRUN_SOL_OPERATOR], // explicit trust; there is no default
|
|
699
|
+
maxDeposit: '$5', // most escrow at stake at once (default "$1")
|
|
578
700
|
},
|
|
579
701
|
});
|
|
580
702
|
|
|
581
|
-
// The first call opens the channel
|
|
582
|
-
|
|
583
|
-
// ceiling when the 402 names none, never more than the room maxDeposit leaves.
|
|
584
|
-
// The call that opens or tops up the channel is charged its quoted price, as
|
|
585
|
-
// it would be with exact.
|
|
586
|
-
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');
|
|
587
705
|
|
|
588
|
-
//
|
|
706
|
+
// Later calls are metered: you pay for the tokens the call actually used.
|
|
589
707
|
const reply = await client.chat('anthropic/claude-sonnet-4.6', 'Summarize x402 in one line', {
|
|
590
|
-
maxTokens: 2048, // a ceiling, not a price
|
|
708
|
+
maxTokens: 2048, // a ceiling, not a price
|
|
591
709
|
});
|
|
592
710
|
|
|
593
|
-
console.log(client.getSpending()); // { totalUsd: <actual charges>, calls: 2 }
|
|
594
|
-
|
|
595
711
|
// Done for good? Close the channel to get the unused escrow back.
|
|
596
|
-
// BlockRun closes it cooperatively when it can; otherwise this starts a
|
|
597
|
-
// payer-forced close and the escrow returns after the channel's grace period.
|
|
598
712
|
await client.closeBatchChannel();
|
|
599
713
|
```
|
|
600
714
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
- **
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
`maxDeposit` the most the selected operator could claim beyond what has
|
|
613
|
-
already been settled, without another authorization from the payer. Before a
|
|
614
|
-
top-up the SDK reads the channel's settled amount from the chain; if the
|
|
615
|
-
RPC cannot answer, it assumes nothing was settled. If the account there
|
|
616
|
-
cannot be read as a payment channel (another owner, too short, an
|
|
617
|
-
unsupported encoding), or the installed `@x402/svm` lays out channel
|
|
618
|
-
accounts differently from what the SDK decodes, it signs no top-up and the
|
|
619
|
-
call pays `exact` (`channel_unreadable`), keeping the record.
|
|
620
|
-
- **Key rotation.** `operators` accepts a list so an old and new BlockRun key can
|
|
621
|
-
overlap during a controlled rotation.
|
|
622
|
-
- **Server-signed channels only.** Batch pays only into server-signed
|
|
623
|
-
(operator) channels, the ones `closeBatchChannel()` can refund. A batch
|
|
624
|
-
accept that is client-signed (`extra.voucherSigner` omitted or `"client"`),
|
|
625
|
-
which a custom gateway could offer, is never paid: the SDK ignores it, and a
|
|
626
|
-
402 whose only batch accepts are client-signed pays `exact`
|
|
627
|
-
(`client_signed_not_supported`). A client-signed channel an earlier SDK
|
|
628
|
-
version opened can still be closed: `closeBatchChannel()` refunds a stored
|
|
629
|
-
one through `@x402/svm`'s client-signed refund when the gateway's challenge
|
|
630
|
-
offers its client-signed accept (after the trusted server-signed channel,
|
|
631
|
-
when both are stored).
|
|
632
|
-
|
|
633
|
-
#### Runtime behavior
|
|
634
|
-
|
|
635
|
-
Batch never pays twice for one call. Before a batch payment is sent, any
|
|
636
|
-
problem falls back to `exact`. Once it is sent, the SDK decides what the
|
|
637
|
-
gateway's answer proves, in one place:
|
|
638
|
-
|
|
639
|
-
| The gateway's answer to the payment | What the SDK does |
|
|
640
|
-
| --- | --- |
|
|
641
|
-
| 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) |
|
|
642
|
-
| 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 |
|
|
643
|
-
| 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 |
|
|
644
|
-
| 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 |
|
|
645
|
-
| 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 |
|
|
646
|
-
| 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) |
|
|
647
|
-
| 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 |
|
|
648
|
-
|
|
649
|
-
- **Payment identifier.** When the gateway's 402 declares the x402
|
|
650
|
-
`payment-identifier` extension, every batch payment carries a new id in
|
|
651
|
-
its `extensions` (`pay_` and 32 hex digits). A gateway that stores each
|
|
652
|
-
paid response under its id answers a replay of a completed request with
|
|
653
|
-
the original response and receipt, and the call is served and booked
|
|
654
|
-
once; while the original is still running, it answers 409 with a
|
|
655
|
-
`Retry-After`. Every replay repeats the payment byte for byte: the same
|
|
656
|
-
`PAYMENT-SIGNATURE` header (same id, same request id, same signed
|
|
657
|
-
authorization and deposit transaction) and the same request body, which
|
|
658
|
-
the SDK serializes once per call. This also recovers a generation that
|
|
659
|
-
outlasts the client's `timeout` (60 s by default): the request is aborted,
|
|
660
|
-
and the replays pick up its stored response once the gateway has it. The
|
|
661
|
-
replay resolves the call only on a 2xx whose receipt the SDK can verify
|
|
662
|
-
(`success: true`, and an `extra` with the operator's `voucher` for this
|
|
663
|
-
request's charge and a `commitmentId`; a `channelState`, when present,
|
|
664
|
-
must match). Every error answer, and a replay's 2xx without a success
|
|
665
|
-
receipt, is held back from the channel's bookkeeping until no replay
|
|
666
|
-
follows it; the bookkeeping then takes the last answer, unless an earlier
|
|
667
|
-
one carried a success receipt and the later ones do not, so a charge the
|
|
668
|
-
gateway reported on an error status is never rolled back by a replay's
|
|
669
|
-
refusal. These replays do not count against `rateLimit`. An `exact`
|
|
670
|
-
payment carries its own new id when the extension is declared, so a
|
|
671
|
-
gateway that declares it `required` takes the exact fallback too. Without
|
|
672
|
-
the extension nothing changes.
|
|
673
|
-
- **Trust model.** The gateway is already trusted with the escrow up to
|
|
674
|
-
`maxDeposit`, so its explicit answer to a payment's first send is taken as
|
|
675
|
-
proof that nothing was charged. Nothing else is: not an error's type or
|
|
676
|
-
`cause.code` (a request can reach the gateway before the connection fails),
|
|
677
|
-
not a missing receipt, and not chain state. An answer to a replay only
|
|
678
|
-
describes the replay: a 402 or `duplicate_settlement` on a replay means the
|
|
679
|
-
original reached the gateway, so it stays in doubt.
|
|
680
|
-
- **In doubt means raised, never paid again.** A call in doubt throws
|
|
681
|
-
`BatchPaymentUnresolvedError` and is never paid again: no new
|
|
682
|
-
authorization, no new deposit, no `exact`, and no `fallbackModels` or
|
|
683
|
-
`smartChat()` fallback model (it is a `PaymentError` whose retry
|
|
684
|
-
disposition is `'paid-or-in-doubt'`). It carries `wallet`, `requestId` (the
|
|
685
|
-
payment that may have been charged), `channelId`, `payloadKind` (`'open'`,
|
|
686
|
-
`'top-up'` or `'authorization'`), `depositInDoubt`, `status` (the gateway's
|
|
687
|
-
last answer, when there was one), `cause` (that answer as an `APIError`, or
|
|
688
|
-
the transport error) and `reason`:
|
|
689
|
-
- `'replay_unresolved'`: a 429 without a receipt whose one replay got no
|
|
690
|
-
success receipt (or `rateLimit` left no room to replay it), or a payment
|
|
691
|
-
carrying a payment identifier whose replays got none: the last answer
|
|
692
|
-
was one no later replay can change, or `inDoubt.maxWaitMs` ran out (the
|
|
693
|
-
message says how many replays were sent);
|
|
694
|
-
- `'ambiguous_rate_limit'`: a 429 whose receipt does not prove nothing was
|
|
695
|
-
broadcast;
|
|
696
|
-
- `'no_response'`: sending it threw (timeout, abort, network error);
|
|
697
|
-
- `'duplicate_settlement'`: the first answer was a 402 naming
|
|
698
|
-
`duplicate_settlement`, meaning another copy of this payment had already
|
|
699
|
-
reached the gateway;
|
|
700
|
-
- `'outcome_unknown'`: any other answer that neither serves the call nor
|
|
701
|
-
proves nothing was charged.
|
|
702
|
-
|
|
703
|
-
A retry is up to you, and it is a new payment:
|
|
704
|
-
|
|
705
|
-
```typescript
|
|
706
|
-
import type { BatchPaymentUnresolvedError } from '@blockrun/llm';
|
|
707
|
-
|
|
708
|
-
try {
|
|
709
|
-
await client.smartChat('Summarize x402 in one line');
|
|
710
|
-
} catch (err) {
|
|
711
|
-
// Match by name: `instanceof` fails when the error comes from another
|
|
712
|
-
// loaded copy of the SDK (the CJS and ESM builds side by side).
|
|
713
|
-
if (err instanceof Error && err.name === 'BatchPaymentUnresolvedError') {
|
|
714
|
-
const unresolved = err as BatchPaymentUnresolvedError;
|
|
715
|
-
console.warn(`payment ${unresolved.requestId} from ${unresolved.wallet} may have been charged`);
|
|
716
|
-
}
|
|
717
|
-
throw err;
|
|
718
|
-
}
|
|
719
|
-
```
|
|
720
|
-
|
|
721
|
-
Raising is deliberately conservative. Resolving a payment in doubt
|
|
722
|
-
automatically needs the gateway's help: a receipt (`PAYMENT-RESPONSE`) on
|
|
723
|
-
every response to a paid request, and a request-status lookup that answers
|
|
724
|
-
for a request id and refuses (fences) one it never saw, in the spirit of
|
|
725
|
-
[idempotency keys](https://datatracker.ietf.org/doc/draft-ietf-httpapi-idempotency-key-header/).
|
|
726
|
-
The SDK will use them once the gateway offers them.
|
|
727
|
-
- **Safe fallback.** These pay `exact` instead, before anything is sent or
|
|
728
|
-
after an answer proving nothing was charged:
|
|
729
|
-
- the 402 has no batch accept, offers only client-signed ones (`client_signed_not_supported`), or names an operator outside `operators`;
|
|
730
|
-
- the deposit the call needs does not fit in the room `maxDeposit` leaves;
|
|
731
|
-
- another batch call for the same wallet is in flight;
|
|
732
|
-
- a deposit is in doubt, or a channel record is being re-read from the chain (below);
|
|
733
|
-
- the gateway refuses batch, or a 402 answers the first send (see the table);
|
|
734
|
-
- the gateway still answers 429 after the retries `rateLimit` allows, when nothing was charged.
|
|
735
|
-
|
|
736
|
-
Parallel calls never queue behind the channel, except during a 429 cooldown (below).
|
|
737
|
-
- **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`).
|
|
738
|
-
- **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).
|
|
739
|
-
- **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.
|
|
740
|
-
- **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:
|
|
741
|
-
|
|
742
|
-
```text
|
|
743
|
-
[@blockrun/llm] batch-settlement event=backoff reason=rate_limited status=429 errorReason=batch_deposit_rate_limited retryAfterMs=30000 attempt=1 wallet=<address> next=retry
|
|
744
|
-
[@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"
|
|
745
|
-
```
|
|
746
|
-
|
|
747
|
-
`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:
|
|
748
|
-
|
|
749
|
-
```typescript
|
|
750
|
-
const client = new SolanaLLMClient({
|
|
751
|
-
batch: {
|
|
752
|
-
operators: [BLOCKRUN_SOL_OPERATOR],
|
|
753
|
-
rateLimit: { maxAttempts: 3, maxWaitMs: 60_000 }, // the defaults
|
|
754
|
-
inDoubt: { maxWaitMs: 300_000 }, // the default
|
|
755
|
-
onEvent: (event) => metrics.increment(`batch.${event.type}`, { reason: event.reason }),
|
|
756
|
-
},
|
|
757
|
-
});
|
|
758
|
-
|
|
759
|
-
client.getBatchStats();
|
|
760
|
-
// { fallbacks: 1, fallbacksByReason: { rate_limited: 1 }, backoffs: 2, retries: 2, recoveries: 0,
|
|
761
|
-
// resyncs: 0, unresolved: 0, unresolvedByReason: {} }
|
|
762
|
-
```
|
|
763
|
-
- **Scope.** Batch covers non-streaming chat: `chat`, `chatCompletion`, `smartChat`, and `smartChatCompletion`. `stream()` and the image and media jobs still pay with `exact`.
|
|
764
|
-
- **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.
|
|
765
|
-
- **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.
|
|
766
|
-
- **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`).
|
|
767
|
-
- **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`.
|
|
768
|
-
- **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.
|
|
769
|
-
- **Rollout.** `sol.blockrun.ai` lists `exact` first and advertises
|
|
770
|
-
`batch-settlement` only when it is enabled. A configured client remains fully
|
|
771
|
-
compatible while rollout is paused or restricted because it falls back to
|
|
772
|
-
`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)**.
|
|
773
726
|
|
|
774
727
|
## Arc Support
|
|
775
728
|
|
|
776
|
-
The same `LLMClient` pays on [Circle's Arc](https://www.arc.network) via [arc.blockrun.ai](https://arc.blockrun.ai)
|
|
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.
|
|
777
730
|
|
|
778
731
|
```typescript
|
|
779
732
|
import { LLMClient } from '@blockrun/llm';
|
|
780
733
|
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
apiUrl: 'https://arc.blockrun.ai/api',
|
|
784
|
-
});
|
|
734
|
+
// BASE_CHAIN_WALLET_KEY: the same EVM key you use on Base
|
|
735
|
+
const client = new LLMClient({ apiUrl: 'https://arc.blockrun.ai/api' });
|
|
785
736
|
|
|
786
|
-
const
|
|
737
|
+
const routed = await client.smartChat('gm Arc');
|
|
738
|
+
const response = await client.chat('openai/gpt-5.4', 'gm Arc');
|
|
787
739
|
```
|
|
788
740
|
|
|
789
|
-
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)
|
|
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.
|
|
790
742
|
|
|
791
743
|
**Setup:**
|
|
792
|
-
1. Same wallet key as Base: `export
|
|
744
|
+
1. Same wallet key as Base: `export BASE_CHAIN_WALLET_KEY="0x..."`
|
|
793
745
|
2. Fund it with USDC on Arc (Arc's native token, shown as the ERC-20 at `0x3600…0000`)
|
|
794
746
|
3. `apiUrl: 'https://arc.blockrun.ai/api'` — payments are automatic via x402
|
|
795
747
|
|
|
796
|
-
**
|
|
797
|
-
**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
|
|
798
749
|
|
|
799
|
-
##
|
|
750
|
+
## Chat, Tools and Streaming
|
|
800
751
|
|
|
801
|
-
|
|
752
|
+
### Simple chat
|
|
802
753
|
|
|
803
|
-
|
|
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);
|
|
804
762
|
|
|
805
|
-
|
|
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
|
+
```
|
|
806
768
|
|
|
807
|
-
|
|
769
|
+
### Full chat completion
|
|
808
770
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
console.log(`Fund your wallet: ${url}`); // single-use, expires ~5 min — mint at click time
|
|
812
|
-
```
|
|
771
|
+
```typescript
|
|
772
|
+
import { LLMClient, type ChatMessage } from '@blockrun/llm';
|
|
813
773
|
|
|
814
|
-
|
|
774
|
+
const client = new LLMClient();
|
|
815
775
|
|
|
816
|
-
|
|
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
|
+
];
|
|
817
780
|
|
|
818
|
-
|
|
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);
|
|
819
784
|
|
|
820
|
-
|
|
821
|
-
const
|
|
822
|
-
console.log(
|
|
785
|
+
// Or pinned
|
|
786
|
+
const result = await client.chatCompletion('anthropic/claude-sonnet-5', messages);
|
|
787
|
+
console.log(result.choices[0].message.content);
|
|
823
788
|
```
|
|
824
789
|
|
|
825
|
-
###
|
|
790
|
+
### Streaming
|
|
826
791
|
|
|
827
|
-
|
|
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).
|
|
828
796
|
|
|
829
|
-
|
|
830
|
-
2. The gateway returns **402 Payment Required** with the price.
|
|
831
|
-
3. The SDK signs a USDC payment **locally** (EIP-712) — on **Base** for `LLMClient`, on **Solana** for `SolanaLLMClient` — using your wallet key.
|
|
832
|
-
4. The request is retried automatically with the payment proof.
|
|
833
|
-
5. The gateway settles on-chain and returns the AI response.
|
|
797
|
+
#### OpenAI-compatible (recommended)
|
|
834
798
|
|
|
835
|
-
|
|
799
|
+
```typescript
|
|
800
|
+
import { OpenAI } from '@blockrun/llm';
|
|
836
801
|
|
|
837
|
-
|
|
838
|
-
roughly 60 seconds. If one expires between signing and verification,
|
|
839
|
-
`SolanaLLMClient` re-signs against a fresh blockhash and retries — up to twice,
|
|
840
|
-
with a short backoff — rather than surfacing a payment error you would only
|
|
841
|
-
have to retry by hand.
|
|
802
|
+
const client = new OpenAI({ walletKey: process.env.BASE_CHAIN_WALLET_KEY }); // or { apiKey }
|
|
842
803
|
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
+
});
|
|
848
809
|
|
|
849
|
-
|
|
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:
|
|
850
819
|
|
|
851
820
|
```typescript
|
|
852
|
-
import {
|
|
821
|
+
import { LLMClient, type ChatMessage } from '@blockrun/llm';
|
|
853
822
|
|
|
854
|
-
const
|
|
855
|
-
console.log(`Spent $${spent.totalUsd.toFixed(4)} across ${spent.calls} calls`);
|
|
823
|
+
const client = new LLMClient();
|
|
856
824
|
|
|
857
|
-
const
|
|
858
|
-
|
|
859
|
-
|
|
825
|
+
const messages: ChatMessage[] = [
|
|
826
|
+
{ role: 'user', content: 'Explain quantum computing in simple terms' },
|
|
827
|
+
];
|
|
860
828
|
|
|
861
|
-
|
|
829
|
+
const response = await client.chatCompletionStream('google/gemini-2.5-flash', messages);
|
|
862
830
|
|
|
863
|
-
|
|
831
|
+
const reader = response.body!.getReader();
|
|
832
|
+
const decoder = new TextDecoder();
|
|
833
|
+
let buffer = '';
|
|
864
834
|
|
|
865
|
-
|
|
835
|
+
while (true) {
|
|
836
|
+
const { done, value } = await reader.read();
|
|
837
|
+
if (done) break;
|
|
866
838
|
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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
|
+
```
|
|
871
849
|
|
|
872
|
-
|
|
873
|
-
import { BlockrunClient } from '@blockrun/llm';
|
|
850
|
+
#### Solana
|
|
874
851
|
|
|
875
|
-
|
|
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.
|
|
876
860
|
|
|
877
|
-
|
|
878
|
-
|
|
861
|
+
```typescript
|
|
862
|
+
import { SolanaLLMClient } from '@blockrun/llm';
|
|
879
863
|
|
|
880
|
-
|
|
881
|
-
const rows = await br.post('/v1/surf/onchain/sql', {
|
|
882
|
-
query: 'SELECT block_number FROM ethereum.blocks ORDER BY block_number DESC LIMIT 1',
|
|
883
|
-
});
|
|
864
|
+
type ChatChunk = { choices?: { delta?: { content?: string } }[] };
|
|
884
865
|
|
|
885
|
-
|
|
886
|
-
const video = await br.poll('/v1/videos/generations', {
|
|
887
|
-
model: 'xai/grok-imagine-video',
|
|
888
|
-
prompt: 'a red apple spinning',
|
|
889
|
-
});
|
|
866
|
+
const client = new SolanaLLMClient(); // SOLANA_WALLET_KEY, or { apiKey: 'brk_live_…' }
|
|
890
867
|
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
model: 'anthropic/claude-sonnet-5',
|
|
868
|
+
for await (const chunk of client.stream<ChatChunk>('/v1/chat/completions', {
|
|
869
|
+
model: 'deepseek/deepseek-chat',
|
|
894
870
|
messages: [{ role: 'user', content: 'Hi' }],
|
|
871
|
+
max_tokens: 64,
|
|
895
872
|
stream: true,
|
|
896
873
|
})) {
|
|
897
|
-
process.stdout.write(chunk
|
|
874
|
+
process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
|
|
898
875
|
}
|
|
899
876
|
```
|
|
900
877
|
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
- `poll<T>(path, body?, { budgetMs, intervalMs })` — submit + poll (image, video, music, voice)
|
|
905
|
-
- `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.
|
|
906
881
|
|
|
907
|
-
|
|
908
|
-
`PortraitClient`, `VoiceClient`, `MusicClient`, `SearchClient`, `RpcClient`,
|
|
909
|
-
`PriceClient`, `SurfClient`) all remain — they will be soft-deprecated in 2.6 (rewritten as
|
|
910
|
-
shims over `BlockrunClient`) and removed in 3.0.
|
|
882
|
+
## Models and Service APIs
|
|
911
883
|
|
|
912
|
-
|
|
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:
|
|
913
888
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
[Models & Pricing](https://blockrun.ai/models). The reference below documents
|
|
917
|
-
the SDK's model families and non-chat client surfaces; it is intentionally
|
|
918
|
-
secondary to the runtime catalog.
|
|
889
|
+
```typescript
|
|
890
|
+
import { LLMClient } from '@blockrun/llm';
|
|
919
891
|
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
for live rates, or read them from the catalog at runtime — `client.listModels()`
|
|
923
|
-
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
|
|
924
894
|
|
|
925
|
-
|
|
926
|
-
|
|
895
|
+
for (const model of models) {
|
|
896
|
+
console.log(`${model.id}: $${model.inputPrice}/M input`);
|
|
897
|
+
}
|
|
927
898
|
|
|
928
|
-
|
|
929
|
-
|
|
899
|
+
const imageModels = await client.listImageModels();
|
|
900
|
+
```
|
|
930
901
|
|
|
931
|
-
###
|
|
902
|
+
### Free Tier
|
|
932
903
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
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.
|
|
936
907
|
|
|
937
|
-
| Model | Context |
|
|
938
|
-
|
|
939
|
-
| `
|
|
940
|
-
| `
|
|
941
|
-
| `
|
|
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 |
|
|
942
917
|
|
|
943
|
-
|
|
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.
|
|
944
920
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
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>
|
|
948
923
|
|
|
949
|
-
|
|
950
|
-
|---|---|
|
|
951
|
-
| `openai/gpt-5.6-sol` | 1.05M |
|
|
952
|
-
| `openai/gpt-5.6-sol-pro` | 1.05M |
|
|
953
|
-
| `openai/gpt-5.6-terra` | 1.05M |
|
|
954
|
-
| `openai/gpt-5.6-terra-pro` | 1.05M |
|
|
955
|
-
| `openai/gpt-5.6-luna` | 1.05M |
|
|
956
|
-
| `openai/gpt-5.6-luna-pro` | 1.05M |
|
|
924
|
+
### OpenAI
|
|
957
925
|
|
|
958
|
-
|
|
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.
|
|
959
931
|
|
|
960
932
|
| Model | Context | Notes |
|
|
961
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 | |
|
|
962
944
|
| `openai/gpt-5.5` | 1.05M | |
|
|
963
945
|
| `openai/gpt-5.5-pro` | 1.05M | |
|
|
964
946
|
| `openai/chat-latest` | 128K | ChatGPT Instant — the model behind chatgpt.com |
|
|
@@ -966,32 +948,22 @@ longer at the same token price.
|
|
|
966
948
|
| `openai/gpt-5.4-pro` | 1.05M | |
|
|
967
949
|
| `openai/gpt-5.4-mini` | 400K | |
|
|
968
950
|
| `openai/gpt-5.4-nano` | 1.05M | |
|
|
951
|
+
| `openai/gpt-5.3-codex` | 400K | Coding/agentic SKU |
|
|
969
952
|
| `openai/gpt-5.2` | 400K | |
|
|
970
953
|
| `openai/gpt-5.2-pro` | 400K | |
|
|
971
954
|
| `openai/gpt-5.1` | 400K | Configurable reasoning effort |
|
|
972
|
-
| `openai/gpt-5.3-codex` | 400K | Coding/agentic SKU |
|
|
973
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 |
|
|
974
965
|
|
|
975
|
-
###
|
|
976
|
-
|
|
977
|
-
| Model | Context |
|
|
978
|
-
|---|---|
|
|
979
|
-
| `openai/gpt-4.1` | 128K |
|
|
980
|
-
| `openai/gpt-4.1-mini` | 128K |
|
|
981
|
-
| `openai/gpt-4.1-nano` | 128K |
|
|
982
|
-
| `openai/gpt-4o` | 128K |
|
|
983
|
-
| `openai/gpt-4o-mini` | 128K |
|
|
984
|
-
|
|
985
|
-
### OpenAI O-Series (Reasoning)
|
|
986
|
-
|
|
987
|
-
| Model | Context |
|
|
988
|
-
|---|---|
|
|
989
|
-
| `openai/o1` | 200K |
|
|
990
|
-
| `openai/o3` | 200K |
|
|
991
|
-
| `openai/o3-mini` | 128K |
|
|
992
|
-
| `openai/o4-mini` | 128K |
|
|
993
|
-
|
|
994
|
-
### Anthropic Claude
|
|
966
|
+
### Anthropic Claude
|
|
995
967
|
|
|
996
968
|
| Model | Context | Notes |
|
|
997
969
|
|---|---|---|
|
|
@@ -1000,7 +972,7 @@ longer at the same token price.
|
|
|
1000
972
|
| `anthropic/claude-opus-5.5` | 1M | Newest Opus — Opus-class reasoning at a lower price than Opus 5 |
|
|
1001
973
|
| `anthropic/claude-opus-5` | 1M | Flagship — the baseline the routing savings claim is measured against |
|
|
1002
974
|
| `anthropic/claude-opus-4.8` | 1M | Agentic coding + adaptive thinking, 128K output |
|
|
1003
|
-
| `anthropic/claude-opus-4.7` | 1M |
|
|
975
|
+
| `anthropic/claude-opus-4.7` | 1M | Router-core's per-request savings baseline (priced like Opus 5) |
|
|
1004
976
|
| `anthropic/claude-opus-4.5` | 200K | |
|
|
1005
977
|
| `anthropic/claude-sonnet-5.5` | 1M | Newest Sonnet — everyday coding and agent work, 128K output |
|
|
1006
978
|
| `anthropic/claude-sonnet-5` | 1M | Best cost/quality balance for long-context agent turns |
|
|
@@ -1040,8 +1012,7 @@ paid SKU; the vision SKU is an experimental preview.
|
|
|
1040
1012
|
|
|
1041
1013
|
The older Grok chat SKUs (grok-3/3-mini, the grok-4 fast families,
|
|
1042
1014
|
grok-code-fast-1, grok-2-vision) have left the catalog. Retired ids stay
|
|
1043
|
-
callable — the gateway redirects them to a healthy model
|
|
1044
|
-
only ranks what `/v1/models` lists.
|
|
1015
|
+
callable — the gateway redirects them to a healthy model.
|
|
1045
1016
|
|
|
1046
1017
|
| Model | Context | Notes |
|
|
1047
1018
|
|---|---|---|
|
|
@@ -1059,16 +1030,25 @@ only ranks what `/v1/models` lists.
|
|
|
1059
1030
|
| `minimax/minimax-m3` | 1M | |
|
|
1060
1031
|
| `minimax/minimax-m2.7` | 200K | |
|
|
1061
1032
|
| `zai/glm-5.3` | 1M | |
|
|
1062
|
-
| `zai/glm-5.3-flash` | 1M |
|
|
1033
|
+
| `zai/glm-5.3-flash` | 1M | Low-cost vision-capable GLM |
|
|
1063
1034
|
| `zai/glm-5.2` | 1M | |
|
|
1064
1035
|
| `zai/glm-5.1` | 200K | |
|
|
1065
1036
|
| `zai/glm-5` | 200K | |
|
|
1066
1037
|
| `zai/glm-5-turbo` | 200K | |
|
|
1038
|
+
| `qwen/qwen3.8-max` | 1M | |
|
|
1067
1039
|
| `qwen/qwen3.7-max` | 1M | |
|
|
1068
1040
|
| `qwen/qwen3.7-plus` | 1M | |
|
|
1069
1041
|
| `qwen/qwen3.8-flash` | 1M | |
|
|
1070
1042
|
| `qwen/qwen3.7-flash` | 1M | Cheapest paid chat model in the catalog |
|
|
1071
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
|
+
|
|
1072
1052
|
### Xiaomi
|
|
1073
1053
|
|
|
1074
1054
|
| Model | Context |
|
|
@@ -1076,158 +1056,187 @@ only ranks what `/v1/models` lists.
|
|
|
1076
1056
|
| `xiaomi/mimo-v2.5` | 1M |
|
|
1077
1057
|
| `xiaomi/mimo-v2.5-pro` | 1M |
|
|
1078
1058
|
|
|
1079
|
-
###
|
|
1059
|
+
### Testnet Models (Base Sepolia)
|
|
1080
1060
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1061
|
+
| Model |
|
|
1062
|
+
|-------|
|
|
1063
|
+
| `openai/gpt-oss-20b` |
|
|
1064
|
+
| `openai/gpt-oss-120b` |
|
|
1084
1065
|
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
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
|
|
1093
1116
|
|
|
1094
|
-
### Image Generation
|
|
1095
1117
|
| Model | Notes |
|
|
1096
1118
|
|---|---|
|
|
1097
|
-
| `openai/gpt-image-1` | Native GPT-4o image generation |
|
|
1098
|
-
| `openai/gpt-image-2` | Reasoning-driven — multilingual text rendering, character consistency |
|
|
1099
|
-
| `openai/gpt-image-2.5-flare` | GPT Image 2.5 |
|
|
1100
|
-
| `openai/gpt-image-2.5-sunburst` | GPT Image 2.5 |
|
|
1101
|
-
| `google/nano-banana` | Gemini 2.5 Flash image generation — fast and efficient |
|
|
1102
|
-
| `google/nano-banana-2` | Gemini 3.1 Flash — pro-level quality at Flash speed |
|
|
1103
|
-
| `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 |
|
|
1104
1126
|
| `xai/grok-imagine-image` | Fast, 300 RPM |
|
|
1127
|
+
| `xai/grok-imagine-image-2.0` | Between Grok Imagine and Pro |
|
|
1105
1128
|
| `xai/grok-imagine-image-pro` | Quality tier, 30 RPM |
|
|
1106
|
-
| `xai/grok-imagine-image-2.0` | Grok Imagine 2.0 |
|
|
1107
1129
|
| `bytedance/seedream-5-pro` | Flagship generation + editing, up to 4K-class, reference images |
|
|
1108
1130
|
| `zai/cogview-4` | Up to 1440x1440 |
|
|
1109
1131
|
|
|
1110
|
-
|
|
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.
|
|
1111
1139
|
|
|
1112
|
-
```
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
+
});
|
|
1119
1163
|
console.log(fused.data[0].url);
|
|
1120
1164
|
```
|
|
1121
1165
|
|
|
1122
1166
|
### Video Generation
|
|
1167
|
+
|
|
1123
1168
|
| Model | Default | Max | Notes |
|
|
1124
1169
|
|---|---|---|---|
|
|
1125
1170
|
| `xai/grok-imagine-video` | 8s | 15s | 480p default, 720p available; text or image to video |
|
|
1126
|
-
| `xai/grok-imagine-video-1.5` | 8s | 15s |
|
|
1127
|
-
| `bytedance/seedance-1.5-pro` | 5s | 12s | Budget
|
|
1128
|
-
| `bytedance/seedance-2.0-fast` | 5s | 15s | 720p, ~60-80s to generate. RealFace
|
|
1129
|
-
| `bytedance/seedance-2.0-mini` | 5s | 15s | 480p/720p
|
|
1130
|
-
| `bytedance/seedance-2.0` | 5s | 15s | Premium
|
|
1131
|
-
| `bytedance/seedance-2.5` | 5s | 30s | Long-form — up to 30s, multilingual,
|
|
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 |
|
|
1132
1177
|
| `azure/sora-2` | 4s | 12s | Sora 2 via Azure AI Foundry — 720p with synced audio; 4, 8 or 12s |
|
|
1133
1178
|
|
|
1134
|
-
```
|
|
1179
|
+
```typescript
|
|
1135
1180
|
import { VideoClient } from '@blockrun/llm';
|
|
1136
1181
|
|
|
1137
1182
|
const client = new VideoClient();
|
|
1138
1183
|
const result = await client.generate('a red apple slowly spinning on a wooden table');
|
|
1139
|
-
console.log(result.data[0].url);
|
|
1184
|
+
console.log(result.data[0].url); // permanent MP4 URL
|
|
1140
1185
|
console.log(result.data[0].duration_seconds); // 8
|
|
1141
1186
|
|
|
1142
1187
|
// Image-to-video
|
|
1143
|
-
const
|
|
1188
|
+
const fromImage = await client.generate('the subject turns and smiles', {
|
|
1144
1189
|
imageUrl: 'https://example.com/portrait.jpg',
|
|
1145
1190
|
});
|
|
1146
1191
|
|
|
1147
|
-
//
|
|
1148
|
-
const
|
|
1149
|
-
model: 'bytedance/seedance-2.0
|
|
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',
|
|
1150
1195
|
aspectRatio: '21:9',
|
|
1151
1196
|
resolution: '1080p',
|
|
1152
1197
|
generateAudio: true, // omit to use the model's default
|
|
1153
1198
|
seed: 42,
|
|
1154
|
-
watermark: false,
|
|
1155
1199
|
returnLastFrame: true, // useful for clip chaining
|
|
1156
1200
|
});
|
|
1201
|
+
console.log(seedance.data[0].last_frame_url); // present when the upstream returns it
|
|
1202
|
+
```
|
|
1157
1203
|
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
});
|
|
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.
|
|
1166
1211
|
|
|
1167
|
-
|
|
1168
|
-
// unsupported one is a 400 before payment, never silently dropped.
|
|
1169
|
-
const r5 = await client.generate('a paper boat drifting down a rain gutter', {
|
|
1170
|
-
model: 'bytedance/seedance-2.5',
|
|
1171
|
-
bitrateMode: 'high', // Seedance 2.x
|
|
1172
|
-
outputFormat: 'mov', // Seedance 2.5 only
|
|
1173
|
-
returnLastFrame: true,
|
|
1174
|
-
});
|
|
1175
|
-
console.log(r5.data[0].last_frame_url); // present when the upstream returns it
|
|
1176
|
-
// cameraFixed: true is Seedance 1.5-pro only.
|
|
1177
|
-
```
|
|
1212
|
+
### Music Generation
|
|
1178
1213
|
|
|
1179
|
-
|
|
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.
|
|
1180
1217
|
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
`referenceImageUrls` / `referenceVideos` / `referenceAudios` with a 400 before
|
|
1184
|
-
any payment.
|
|
1218
|
+
```typescript
|
|
1219
|
+
import { MusicClient } from '@blockrun/llm';
|
|
1185
1220
|
|
|
1186
|
-
|
|
1187
|
-
|---|---|---|
|
|
1188
|
-
| `bytedance/seedance-2.0` / `-fast` / `-mini` | 1–9 | 1–3 clips of each; audio needs an image or video alongside |
|
|
1189
|
-
| `bytedance/seedance-2.5` | 1–30 | — |
|
|
1190
|
-
|
|
1191
|
-
Reference mode is its own mode: it cannot be mixed with `imageUrl`,
|
|
1192
|
-
`lastFrameUrl` or `realFaceAssetId`. Cite images as "image 1", "image 2" and
|
|
1193
|
-
clips as "video 1" in the prompt. Reference clips add a per-clip surcharge;
|
|
1194
|
-
reference audio must be ≤15.2s.
|
|
1195
|
-
|
|
1196
|
-
```ts
|
|
1197
|
-
const client = new VideoClient({ apiKey: process.env.BLOCKRUN_API_KEY });
|
|
1198
|
-
|
|
1199
|
-
// Omni / multi-reference: character/style consistency from images
|
|
1200
|
-
const r6 = await client.generate(
|
|
1201
|
-
'the character from image 1 walks through the city from image 2',
|
|
1202
|
-
{
|
|
1203
|
-
model: 'bytedance/seedance-2.0',
|
|
1204
|
-
referenceImageUrls: [
|
|
1205
|
-
'https://example.com/character.jpg',
|
|
1206
|
-
'https://example.com/city.jpg',
|
|
1207
|
-
],
|
|
1208
|
-
}
|
|
1209
|
-
);
|
|
1221
|
+
const client = new MusicClient();
|
|
1210
1222
|
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
inputType: 'reference', // optional: 400 if the fields say otherwise
|
|
1220
|
-
}
|
|
1221
|
-
);
|
|
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
|
+
});
|
|
1222
1231
|
```
|
|
1223
1232
|
|
|
1224
1233
|
### Text-to-Speech & Sound Effects
|
|
1225
1234
|
|
|
1226
|
-
`SpeechClient` wraps BlockRun Voice
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
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).
|
|
1231
1240
|
|
|
1232
1241
|
| Model | Max Input | Notes |
|
|
1233
1242
|
|---|---|---|
|
|
@@ -1235,10 +1244,10 @@ synchronous (<1s for Flash).
|
|
|
1235
1244
|
| `elevenlabs/turbo-v2.5` | 40k chars | ~250ms latency, balanced quality |
|
|
1236
1245
|
| `elevenlabs/multilingual-v2` | 10k chars | Long-form narration, audiobooks — 29 languages |
|
|
1237
1246
|
| `elevenlabs/v3` | 5k chars | Max expressiveness, 70+ languages |
|
|
1238
|
-
| `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) |
|
|
1239
1248
|
| `elevenlabs/sound-effects` | 1k chars | Sound effects up to 22s |
|
|
1240
1249
|
|
|
1241
|
-
```
|
|
1250
|
+
```typescript
|
|
1242
1251
|
import { SpeechClient } from '@blockrun/llm';
|
|
1243
1252
|
|
|
1244
1253
|
const client = new SpeechClient();
|
|
@@ -1273,7 +1282,7 @@ enrollment, so a failed enrollment never charges your wallet. The returned
|
|
|
1273
1282
|
`source_image_url`). (Real-person likeness is not supported on BlockRun —
|
|
1274
1283
|
enrolled portraits are AI characters.)
|
|
1275
1284
|
|
|
1276
|
-
```
|
|
1285
|
+
```typescript
|
|
1277
1286
|
import { PortraitClient, VideoClient } from '@blockrun/llm';
|
|
1278
1287
|
|
|
1279
1288
|
const portraits = new PortraitClient();
|
|
@@ -1291,14 +1300,14 @@ const clip = await video.generate('she waves and smiles', {
|
|
|
1291
1300
|
console.log(clip.data[0].url);
|
|
1292
1301
|
```
|
|
1293
1302
|
|
|
1294
|
-
### Voice Calls
|
|
1303
|
+
### Voice Calls and Phone Numbers
|
|
1295
1304
|
|
|
1296
1305
|
`VoiceClient` wraps `POST /v1/voice/call` (paid, flat per call) and
|
|
1297
1306
|
`GET /v1/voice/call/{callId}` (free polling) — AI-powered outbound phone
|
|
1298
1307
|
calls powered by Bland.ai. The agent dials the recipient and runs a real-time
|
|
1299
1308
|
conversation based on your `task` instructions. US + Canada destinations.
|
|
1300
1309
|
|
|
1301
|
-
```
|
|
1310
|
+
```typescript
|
|
1302
1311
|
import { VoiceClient } from '@blockrun/llm';
|
|
1303
1312
|
|
|
1304
1313
|
const client = new VoiceClient();
|
|
@@ -1317,92 +1326,58 @@ const status = await client.getStatus(result.call_id);
|
|
|
1317
1326
|
console.log(status.status, status.recording_url);
|
|
1318
1327
|
```
|
|
1319
1328
|
|
|
1320
|
-
|
|
1321
|
-
|
|
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
|
+
```
|
|
1322
1342
|
|
|
1323
1343
|
### Standalone Search
|
|
1324
1344
|
|
|
1325
|
-
`SearchClient` wraps `POST /v1/search` —
|
|
1326
|
-
|
|
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).
|
|
1327
1349
|
|
|
1328
|
-
```
|
|
1350
|
+
```typescript
|
|
1329
1351
|
import { SearchClient } from '@blockrun/llm';
|
|
1330
1352
|
|
|
1331
1353
|
const client = new SearchClient();
|
|
1332
1354
|
const result = await client.search('Latest news on x402 adoption', {
|
|
1333
|
-
sources: ['
|
|
1355
|
+
sources: ['web', 'news'],
|
|
1334
1356
|
maxResults: 10,
|
|
1335
1357
|
});
|
|
1336
1358
|
console.log(result.summary);
|
|
1337
|
-
for (const
|
|
1338
|
-
```
|
|
1339
|
-
|
|
1340
|
-
The same endpoint is available as `client.search()` on `LLMClient` and
|
|
1341
|
-
`SolanaLLMClient` when the selected gateway supports it.
|
|
1342
|
-
|
|
1343
|
-
### Surf Crypto Data
|
|
1344
|
-
|
|
1345
|
-
`SurfClient` exposes the full `/v1/surf/*` catalog — 84+ pay-per-call
|
|
1346
|
-
endpoints across CEX/DEX market data, on-chain SQL, wallet intelligence,
|
|
1347
|
-
prediction markets (Polymarket + Kalshi), social analytics, news, VC fund
|
|
1348
|
-
data, and an OpenAI-compatible chat surface. Flat pricing per call:
|
|
1349
|
-
|
|
1350
|
-
| Tier | Examples |
|
|
1351
|
-
|---|---|
|
|
1352
|
-
| 1 | `/market/price`, `/market/ranking`, `/news/feed`, prediction-market reads, social tweets |
|
|
1353
|
-
| 2 | `/exchange/depth`, `/exchange/klines`, `/wallet/detail`, `/search/*`, `/social/ranking` |
|
|
1354
|
-
| 3 | `/onchain/sql`, `/onchain/query`, `/onchain/schema`, `/chat/completions` |
|
|
1355
|
-
|
|
1356
|
-
Because the catalog is broad and evolving, the client deliberately ships a
|
|
1357
|
-
generic `get` / `post` pair instead of 84 typed wrappers. Pass the path
|
|
1358
|
-
(with or without the `/v1/surf` prefix), query params, or a JSON body —
|
|
1359
|
-
type the response via a generic if you want.
|
|
1360
|
-
|
|
1361
|
-
```ts
|
|
1362
|
-
import { SurfClient } from '@blockrun/llm';
|
|
1363
|
-
|
|
1364
|
-
const surf = new SurfClient();
|
|
1365
|
-
|
|
1366
|
-
// Tier 1 — token price
|
|
1367
|
-
const btc = await surf.get('/market/price', { symbol: 'BTC' });
|
|
1368
|
-
|
|
1369
|
-
// Tier 2 — order book depth
|
|
1370
|
-
const book = await surf.get('/exchange/depth', {
|
|
1371
|
-
exchange: 'binance',
|
|
1372
|
-
symbol: 'BTC-USDT',
|
|
1373
|
-
});
|
|
1374
|
-
|
|
1375
|
-
// Tier 3 — raw on-chain SQL against 80+ ClickHouse tables
|
|
1376
|
-
const rows = await surf.post('/onchain/sql', {
|
|
1377
|
-
query: 'SELECT block_number FROM ethereum.blocks ORDER BY block_number DESC LIMIT 5',
|
|
1378
|
-
});
|
|
1379
|
-
|
|
1380
|
-
// Typed response via generic
|
|
1381
|
-
type Price = { symbol: string; price: number; timestamp: string };
|
|
1382
|
-
const eth = await surf.get<Price>('/market/price', { symbol: 'ETH' });
|
|
1359
|
+
for (const c of result.citations ?? []) console.log(c.url);
|
|
1383
1360
|
```
|
|
1384
1361
|
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
Methods: `userLookup`, `userInfo`, `followers`, `following`, `followings`,
|
|
1388
|
-
`verifiedFollowers`, `userTweets`, `mentions`, `tweetLookup`, `tweetReplies`,
|
|
1389
|
-
`tweetThread`, `search`, `trending`, `articlesRising`.
|
|
1362
|
+
The same endpoint is available as `search()` on `LLMClient` and
|
|
1363
|
+
`SolanaLLMClient`.
|
|
1390
1364
|
|
|
1391
1365
|
### Market Data (Pyth)
|
|
1392
1366
|
|
|
1393
1367
|
`PriceClient` wraps the Pyth-backed market-data endpoints. Crypto, FX and
|
|
1394
|
-
commodity are
|
|
1395
|
-
|
|
1396
|
-
|
|
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.
|
|
1397
1371
|
|
|
1398
|
-
```
|
|
1372
|
+
```typescript
|
|
1399
1373
|
import { PriceClient } from '@blockrun/llm';
|
|
1400
1374
|
|
|
1401
1375
|
const p = new PriceClient({ requireWallet: false });
|
|
1402
1376
|
const btc = await p.price('crypto', 'BTC-USD');
|
|
1403
1377
|
const eur = await p.price('fx', 'EUR-USD');
|
|
1378
|
+
const symbols = await p.listSymbols('crypto', { query: 'sol', limit: 20 });
|
|
1404
1379
|
|
|
1405
|
-
// Paid
|
|
1380
|
+
// Paid: requires an API key or a wallet
|
|
1406
1381
|
const p2 = new PriceClient();
|
|
1407
1382
|
const aapl = await p2.price('stocks', 'AAPL', { market: 'us' });
|
|
1408
1383
|
const bars = await p2.history('stocks', 'AAPL', {
|
|
@@ -1411,50 +1386,115 @@ const bars = await p2.history('stocks', 'AAPL', {
|
|
|
1411
1386
|
from: 1700000000,
|
|
1412
1387
|
to: 1710000000,
|
|
1413
1388
|
});
|
|
1414
|
-
const symbols = await p.listSymbols('crypto', { query: 'sol', limit: 20 });
|
|
1415
1389
|
```
|
|
1416
1390
|
|
|
1417
1391
|
Supported `StockMarket` values: `us, hk, jp, kr, gb, de, fr, nl, ie, lu, cn, ca`.
|
|
1418
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
|
+
|
|
1419
1462
|
### DeFi Data, DEX Swaps & Cloud Compute
|
|
1420
1463
|
|
|
1421
|
-
Three passthrough families live directly on `LLMClient`
|
|
1464
|
+
Three passthrough families live directly on `LLMClient` and `SolanaLLMClient`:
|
|
1465
|
+
|
|
1466
|
+
```typescript
|
|
1467
|
+
import { LLMClient } from '@blockrun/llm';
|
|
1422
1468
|
|
|
1423
|
-
```ts
|
|
1424
1469
|
const client = new LLMClient();
|
|
1425
1470
|
|
|
1426
|
-
// DefiLlama
|
|
1427
|
-
const protocols = await client.defiProtocols();
|
|
1471
|
+
// DefiLlama: protocols, TVL, yields, prices
|
|
1428
1472
|
const aave = await client.defiProtocol('aave');
|
|
1429
|
-
const prices = await client.defiPrices(['coingecko:bitcoin', 'base:0x833589...']);
|
|
1430
1473
|
|
|
1431
|
-
// 0x DEX
|
|
1432
|
-
// fee on executed swaps instead of x402)
|
|
1474
|
+
// 0x DEX quotes (free: BlockRun takes an on-chain affiliate fee on executed swaps instead)
|
|
1433
1475
|
const quote = await client.dexQuote({
|
|
1434
1476
|
chainId: '8453', sellToken: '0x...', buyToken: '0x...',
|
|
1435
1477
|
sellAmount: '1000000', taker: '0xYourWallet',
|
|
1436
1478
|
});
|
|
1437
|
-
const gq = await client.dexGaslessQuote({ /* ... */ });
|
|
1438
|
-
const res = await client.dexGaslessSubmit({ trade: { /* signed eip712 */ } });
|
|
1439
|
-
const status = await client.dexGaslessStatus(res.tradeHash as string);
|
|
1440
1479
|
|
|
1441
|
-
// Modal
|
|
1480
|
+
// Modal: sandboxed CPU / GPU compute
|
|
1442
1481
|
const sb = await client.modalSandboxCreate({ image: 'python:3.11' });
|
|
1443
|
-
const out = await client.modalSandboxExec(sb.sandbox_id as string, ['python', '-c', 'print(42)']);
|
|
1444
|
-
await client.modalSandboxTerminate(sb.sandbox_id as string);
|
|
1445
1482
|
```
|
|
1446
1483
|
|
|
1447
|
-
|
|
1448
|
-
`
|
|
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)`.
|
|
1449
1488
|
|
|
1450
1489
|
### Multi-chain RPC
|
|
1451
1490
|
|
|
1452
1491
|
`RpcClient` wraps `POST /v1/rpc/{network}` — standard JSON-RPC 2.0 access to
|
|
1453
1492
|
<!-- br:chains.rpc -->40<!-- /br:chains.rpc --> chains through one endpoint (Ethereum, Base, Solana, Polygon, BSC,
|
|
1454
1493
|
Arbitrum, Optimism, Avalanche, Bitcoin, Sui, and more; powered by Tatum's RPC
|
|
1455
|
-
gateway). Use account
|
|
1494
|
+
gateway). Use account credit or an EVM wallet; no separate Tatum key is needed.
|
|
1495
|
+
A JSON-RPC batch is priced per element.
|
|
1456
1496
|
|
|
1457
|
-
```
|
|
1497
|
+
```typescript
|
|
1458
1498
|
import { RpcClient } from '@blockrun/llm';
|
|
1459
1499
|
|
|
1460
1500
|
const client = new RpcClient();
|
|
@@ -1463,14 +1503,8 @@ const client = new RpcClient();
|
|
|
1463
1503
|
const block = await client.call('ethereum', 'eth_blockNumber');
|
|
1464
1504
|
console.log(parseInt(block.result as string, 16));
|
|
1465
1505
|
|
|
1466
|
-
const balance = await client.call('base', 'eth_getBalance', [
|
|
1467
|
-
'0x4200000000000000000000000000000000000006',
|
|
1468
|
-
'latest',
|
|
1469
|
-
]);
|
|
1470
|
-
|
|
1471
1506
|
// Non-EVM chains speak their native JSON-RPC
|
|
1472
1507
|
const slot = await client.call('solana', 'getSlot');
|
|
1473
|
-
const tip = await client.call('bitcoin', 'getblockcount');
|
|
1474
1508
|
|
|
1475
1509
|
// Batch: one payment, per-element pricing (rate x N)
|
|
1476
1510
|
const out = await client.batch('polygon', [
|
|
@@ -1483,703 +1517,313 @@ console.log(block.cacheHit); // true if served from the gateway's hot cache
|
|
|
1483
1517
|
console.log(block.txHash); // x402 settlement tx
|
|
1484
1518
|
```
|
|
1485
1519
|
|
|
1486
|
-
|
|
1487
|
-
(`eth`, `arb`, `
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
without an SDK update. Hot, low-volatility reads (`eth_chainId`, mined
|
|
1491
|
-
blocks/receipts, `getTransaction`, ...) are served from a method-aware
|
|
1492
|
-
gateway cache — same price, lower latency.
|
|
1493
|
-
|
|
1494
|
-
### Testnet Models (Base Sepolia)
|
|
1495
|
-
| Model |
|
|
1496
|
-
|-------|
|
|
1497
|
-
| `openai/gpt-oss-20b` |
|
|
1498
|
-
| `openai/gpt-oss-120b` |
|
|
1499
|
-
|
|
1500
|
-
*Testnet models use flat pricing (no token counting) for simplicity.*
|
|
1501
|
-
|
|
1502
|
-
</details>
|
|
1503
|
-
|
|
1504
|
-
## 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.
|
|
1505
1524
|
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
```typescript
|
|
1509
|
-
import { LLMClient } from '@blockrun/llm';
|
|
1525
|
+
## OpenAI and Anthropic Compatibility
|
|
1510
1526
|
|
|
1511
|
-
|
|
1527
|
+
### OpenAI
|
|
1512
1528
|
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
{ model: 'openai/gpt-image-1' }
|
|
1517
|
-
);
|
|
1518
|
-
console.log(result.data[0].url);
|
|
1519
|
-
```
|
|
1520
|
-
|
|
1521
|
-
## Usage Examples
|
|
1522
|
-
|
|
1523
|
-
### 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).
|
|
1524
1532
|
|
|
1525
1533
|
```typescript
|
|
1526
|
-
import {
|
|
1527
|
-
|
|
1528
|
-
const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
|
|
1534
|
+
import { OpenAI } from '@blockrun/llm';
|
|
1529
1535
|
|
|
1530
|
-
const
|
|
1531
|
-
console.log(response);
|
|
1536
|
+
const client = new OpenAI(); // BLOCKRUN_API_KEY, or { walletKey: '0x...' }, or { apiKey }
|
|
1532
1537
|
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1538
|
+
const completion = await client.chat.completions.create({
|
|
1539
|
+
model: 'blockrun/auto',
|
|
1540
|
+
messages: [{ role: 'user', content: 'Hello!' }],
|
|
1536
1541
|
});
|
|
1542
|
+
console.log(completion.choices[0].message.content);
|
|
1537
1543
|
```
|
|
1538
1544
|
|
|
1539
|
-
###
|
|
1545
|
+
### Anthropic
|
|
1540
1546
|
|
|
1541
|
-
|
|
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.
|
|
1542
1551
|
|
|
1543
1552
|
```typescript
|
|
1544
|
-
import {
|
|
1545
|
-
|
|
1546
|
-
const client = new LLMClient();
|
|
1547
|
-
|
|
1548
|
-
// Auto-route to cheapest capable model
|
|
1549
|
-
const result = await client.smartChat('What is 2+2?');
|
|
1550
|
-
console.log(result.response); // '4'
|
|
1551
|
-
console.log(result.model); // 'google/gemini-2.5-flash'
|
|
1552
|
-
console.log(result.routing.tier); // 'SIMPLE'
|
|
1553
|
-
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 88%'
|
|
1554
|
-
|
|
1555
|
-
// Routing profiles ('eco' | 'auto' | 'premium')
|
|
1556
|
-
const eco = await client.smartChat('Explain AI', { routingProfile: 'eco' }); // Free tier first, then cheapest paid
|
|
1557
|
-
const auto = await client.smartChat('Code review', { routingProfile: 'auto' }); // Balanced (default)
|
|
1558
|
-
const premium = await client.smartChat('Write a legal brief', { routingProfile: 'premium' }); // Best quality
|
|
1559
|
-
|
|
1560
|
-
// Guaranteed $0: call a free model directly
|
|
1561
|
-
const free = await client.chat('nvidia/nemotron-3.5-lightning', 'Hello!');
|
|
1562
|
-
```
|
|
1563
|
-
|
|
1564
|
-
**Routing Profiles:**
|
|
1565
|
-
|
|
1566
|
-
| Profile | Description | Best For |
|
|
1567
|
-
|---------|-------------|----------|
|
|
1568
|
-
| `eco` | Budget-optimized — ranks the <!-- br:models.free -->7<!-- /br:models.free -->-model free tier first | Cost-sensitive workloads, zero-cost testing |
|
|
1569
|
-
| `auto` | Intelligent routing (default) | General use |
|
|
1570
|
-
| `premium` | Best quality models | Critical tasks |
|
|
1571
|
-
|
|
1572
|
-
**Tiers:**
|
|
1573
|
-
|
|
1574
|
-
| Tier | Example Tasks | Typical Models |
|
|
1575
|
-
|------|---------------|----------------|
|
|
1576
|
-
| SIMPLE | Greetings, math, lookups | Gemini Flash, GPT-4o-mini |
|
|
1577
|
-
| MEDIUM | Explanations, summaries | GPT-4o, Claude Sonnet |
|
|
1578
|
-
| COMPLEX | Analysis, code generation | GPT-5.2, Claude Opus |
|
|
1579
|
-
| REASONING | Multi-step logic, planning | o3, DeepSeek Reasoner |
|
|
1580
|
-
|
|
1581
|
-
### Full Chat Completion
|
|
1582
|
-
|
|
1583
|
-
```typescript
|
|
1584
|
-
import { LLMClient, type ChatMessage } from '@blockrun/llm';
|
|
1585
|
-
|
|
1586
|
-
const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
|
|
1587
|
-
|
|
1588
|
-
const messages: ChatMessage[] = [
|
|
1589
|
-
{ role: 'system', content: 'You are a helpful assistant.' },
|
|
1590
|
-
{ role: 'user', content: 'How do I read a file in Node.js?' },
|
|
1591
|
-
];
|
|
1592
|
-
|
|
1593
|
-
const result = await client.chatCompletion('openai/gpt-4o', messages);
|
|
1594
|
-
console.log(result.choices[0].message.content);
|
|
1595
|
-
```
|
|
1596
|
-
|
|
1597
|
-
### Streaming
|
|
1598
|
-
|
|
1599
|
-
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).
|
|
1600
|
-
|
|
1601
|
-
#### OpenAI-compatible (recommended)
|
|
1553
|
+
import { AnthropicClient } from '@blockrun/llm';
|
|
1602
1554
|
|
|
1603
|
-
|
|
1604
|
-
import { OpenAI } from '@blockrun/llm';
|
|
1555
|
+
const client = new AnthropicClient();
|
|
1605
1556
|
|
|
1606
|
-
const
|
|
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]);
|
|
1607
1563
|
|
|
1608
|
-
|
|
1564
|
+
// Any model works in Anthropic format
|
|
1565
|
+
const gptResponse = await client.messages.create({
|
|
1609
1566
|
model: 'openai/gpt-5.4',
|
|
1610
|
-
|
|
1611
|
-
|
|
1567
|
+
max_tokens: 1024,
|
|
1568
|
+
messages: [{ role: 'user', content: 'Hello from GPT!' }],
|
|
1612
1569
|
});
|
|
1613
|
-
|
|
1614
|
-
for await (const chunk of stream) {
|
|
1615
|
-
process.stdout.write(chunk.choices[0]?.delta?.content || '');
|
|
1616
|
-
}
|
|
1617
1570
|
```
|
|
1618
1571
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
const client = new LLMClient();
|
|
1625
|
-
|
|
1626
|
-
const messages: ChatMessage[] = [
|
|
1627
|
-
{ role: 'user', content: 'Explain quantum computing in simple terms' },
|
|
1628
|
-
];
|
|
1629
|
-
|
|
1630
|
-
// Returns a raw fetch Response with SSE body
|
|
1631
|
-
const response = await client.chatCompletionStream('google/gemini-2.5-flash', messages);
|
|
1632
|
-
|
|
1633
|
-
const reader = response.body!.getReader();
|
|
1634
|
-
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).
|
|
1635
1576
|
|
|
1636
|
-
|
|
1637
|
-
const { done, value } = await reader.read();
|
|
1638
|
-
if (done) break;
|
|
1577
|
+
## Wallets
|
|
1639
1578
|
|
|
1640
|
-
|
|
1641
|
-
for (const line of chunk.split('\n')) {
|
|
1642
|
-
if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;
|
|
1643
|
-
const data = JSON.parse(line.slice(6));
|
|
1644
|
-
process.stdout.write(data.choices?.[0]?.delta?.content || '');
|
|
1645
|
-
}
|
|
1646
|
-
}
|
|
1647
|
-
```
|
|
1579
|
+
### Agent setup
|
|
1648
1580
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
`SolanaLLMClient.stream()` is the Solana counterpart, with the same shape as
|
|
1652
|
-
`BlockrunClient.stream()`: it yields each `data:` frame already parsed as JSON
|
|
1653
|
-
and stops at `[DONE]`. The x402 handshake happens before the first frame — 402,
|
|
1654
|
-
sign an SPL TransferChecked authorization locally, replay with
|
|
1655
|
-
`PAYMENT-SIGNATURE` — including the fresh-blockhash re-sign on a
|
|
1656
|
-
verification-phase rejection. A free model, or an account key, is answered `200`
|
|
1657
|
-
with no handshake at all and settles nothing.
|
|
1581
|
+
One call for agent runtimes (Claude Code skills, MCP servers, …):
|
|
1658
1582
|
|
|
1659
1583
|
```typescript
|
|
1660
|
-
import {
|
|
1584
|
+
import { setupAgentClient, setupAgentWallet, setupAgentSolanaWallet, status } from '@blockrun/llm';
|
|
1661
1585
|
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
messages: [{ role: 'user', content: 'Hi' }],
|
|
1667
|
-
max_tokens: 64,
|
|
1668
|
-
stream: true,
|
|
1669
|
-
})) {
|
|
1670
|
-
process.stdout.write(chunk?.choices?.[0]?.delta?.content ?? '');
|
|
1671
|
-
}
|
|
1672
|
-
```
|
|
1673
|
-
|
|
1674
|
-
Set `stream: true` yourself — the method does not inject it, because the gateway
|
|
1675
|
-
prices a streaming and a non-streaming request the same and rewriting a caller's
|
|
1676
|
-
body silently is how you end up debugging a request you did not send.
|
|
1677
|
-
|
|
1678
|
-
#### Payment + streaming flow
|
|
1679
|
-
|
|
1680
|
-
```
|
|
1681
|
-
First call (cache miss):
|
|
1682
|
-
1. Send request → 402 response (BlockRun returns price)
|
|
1683
|
-
2. Sign USDC payment locally (key never leaves machine)
|
|
1684
|
-
3. Retry with PAYMENT-SIGNATURE header + stream: true
|
|
1685
|
-
4. Cache payment requirements for this model (1h TTL)
|
|
1686
|
-
5. Stream tokens as they arrive
|
|
1687
|
-
|
|
1688
|
-
Subsequent calls (cache hit):
|
|
1689
|
-
1. Pre-sign payment from cache — skip 402 round-trip
|
|
1690
|
-
2. Send request with PAYMENT-SIGNATURE upfront
|
|
1691
|
-
3. Stream tokens immediately (~200ms faster)
|
|
1692
|
-
```
|
|
1693
|
-
|
|
1694
|
-
### List Available Models
|
|
1695
|
-
|
|
1696
|
-
```typescript
|
|
1697
|
-
import { LLMClient } from '@blockrun/llm';
|
|
1698
|
-
|
|
1699
|
-
const client = new LLMClient(); // Uses BASE_CHAIN_WALLET_KEY (never sent to server)
|
|
1700
|
-
const models = await client.listModels();
|
|
1701
|
-
|
|
1702
|
-
for (const model of models) {
|
|
1703
|
-
console.log(`${model.id}: $${model.inputPrice}/M input`);
|
|
1704
|
-
}
|
|
1705
|
-
```
|
|
1706
|
-
|
|
1707
|
-
### Multiple Requests
|
|
1708
|
-
|
|
1709
|
-
```typescript
|
|
1710
|
-
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!');
|
|
1711
1590
|
|
|
1712
|
-
|
|
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
|
|
1713
1594
|
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
]);
|
|
1595
|
+
// Base wallet address and balance
|
|
1596
|
+
await status();
|
|
1597
|
+
// Wallet: 0xCC8c...5EF8
|
|
1598
|
+
// Balance: $5.30 USDC
|
|
1719
1599
|
```
|
|
1720
1600
|
|
|
1721
|
-
|
|
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>`.
|
|
1722
1606
|
|
|
1723
|
-
|
|
1607
|
+
### Wallet discovery and migration
|
|
1724
1608
|
|
|
1725
|
-
|
|
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.
|
|
1726
1612
|
|
|
1727
1613
|
```typescript
|
|
1728
|
-
import {
|
|
1614
|
+
import { listDiscoveredWallets, importWallet, listDiscoveredSolanaWallets } from '@blockrun/llm';
|
|
1729
1615
|
|
|
1730
|
-
const
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
const markets = await client.pm("polymarket/markets");
|
|
1734
|
-
const filtered = await client.pm("polymarket/markets", { status: "active", limit: 10 });
|
|
1735
|
-
const searched = await client.pm("polymarket/markets", { search: "bitcoin" });
|
|
1736
|
-
|
|
1737
|
-
// List events
|
|
1738
|
-
const events = await client.pm("polymarket/events");
|
|
1739
|
-
|
|
1740
|
-
// Historical trades
|
|
1741
|
-
const trades = await client.pm("polymarket/trades");
|
|
1742
|
-
|
|
1743
|
-
// OHLCV candlestick data for a specific condition
|
|
1744
|
-
const candles = await client.pm("polymarket/candlesticks/0x1234abcd...");
|
|
1745
|
-
|
|
1746
|
-
// Wallet profile (tier 2)
|
|
1747
|
-
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
|
+
}
|
|
1748
1619
|
|
|
1749
|
-
|
|
1750
|
-
const pnl = await client.pm("polymarket/wallet/pnl/0xABC123...");
|
|
1620
|
+
importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');
|
|
1751
1621
|
|
|
1752
|
-
//
|
|
1753
|
-
const
|
|
1622
|
+
// Solana: the same pair, async
|
|
1623
|
+
const solWallets = await listDiscoveredSolanaWallets();
|
|
1754
1624
|
```
|
|
1755
1625
|
|
|
1756
|
-
|
|
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.
|
|
1757
1629
|
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
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.
|
|
1761
1636
|
|
|
1762
|
-
|
|
1763
|
-
|
|
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:
|
|
1764
1641
|
|
|
1765
|
-
// Binance candles for supported pairs
|
|
1766
|
-
const btcCandles = await client.pm("binance/candles/BTCUSDT");
|
|
1767
|
-
const ethCandles = await client.pm("binance/candles/ETHUSDT");
|
|
1768
|
-
// Also: SOLUSDT, XRPUSDT
|
|
1769
1642
|
```
|
|
1643
|
+
NOTICE: BlockRun created a new wallet, but also found existing wallet(s)
|
|
1644
|
+
belonging to other applications on this system:
|
|
1770
1645
|
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
```typescript
|
|
1774
|
-
// Cross-platform matching pairs
|
|
1775
|
-
const pairs = await client.pm("matching-markets/pairs");
|
|
1646
|
+
0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f
|
|
1647
|
+
...
|
|
1776
1648
|
```
|
|
1777
1649
|
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
Works on both `LLMClient` (Base) and `SolanaLLMClient`.
|
|
1650
|
+
### One wallet across every BlockRun product
|
|
1781
1651
|
|
|
1782
|
-
|
|
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.
|
|
1783
1657
|
|
|
1784
|
-
|
|
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`.
|
|
1785
1666
|
|
|
1786
|
-
|
|
1787
|
-
|---|---|
|
|
1788
|
-
| `exaSearch(query, options?)` | Neural/keyword web search |
|
|
1789
|
-
| `exaFindSimilar(url, options?)` | Find semantically similar pages |
|
|
1790
|
-
| `exaContents(urls, options?)` | Extract full text from URLs |
|
|
1791
|
-
| `exaAnswer(query, options?)` | AI answer grounded in web search |
|
|
1792
|
-
| `exa(path, body)` | Generic proxy for any Exa endpoint |
|
|
1667
|
+
## Configuration and Environment Variables
|
|
1793
1668
|
|
|
1794
1669
|
```typescript
|
|
1795
1670
|
import { LLMClient } from '@blockrun/llm';
|
|
1796
1671
|
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
// Neural web search
|
|
1800
|
-
const results = await client.exaSearch("latest AI safety research", { numResults: 5 });
|
|
1801
|
-
const news = await client.exaSearch("bitcoin ETF news", { category: "news", numResults: 10 });
|
|
1802
|
-
|
|
1803
|
-
// Find similar pages
|
|
1804
|
-
const similar = await client.exaFindSimilar("https://openai.com/research/gpt-4", { numResults: 5 });
|
|
1805
|
-
|
|
1806
|
-
// Extract content from URLs
|
|
1807
|
-
const content = await client.exaContents(["https://arxiv.org/abs/2303.08774"]);
|
|
1808
|
-
|
|
1809
|
-
// AI-generated answer from live web
|
|
1810
|
-
const answer = await client.exaAnswer("What is the current state of AI safety research?");
|
|
1811
|
-
|
|
1812
|
-
// Generic proxy for any Exa endpoint
|
|
1813
|
-
const custom = await client.exa("search", { query: "transformer architecture", numResults: 5 });
|
|
1814
|
-
```
|
|
1815
|
-
|
|
1816
|
-
Same surface on `SolanaLLMClient` once Solana-side `EXA_API_KEY` is provisioned.
|
|
1817
|
-
|
|
1818
|
-
## Configuration
|
|
1819
|
-
|
|
1820
|
-
```typescript
|
|
1821
|
-
// Default: reads BASE_CHAIN_WALLET_KEY from environment
|
|
1672
|
+
// Default: BLOCKRUN_API_KEY, else BASE_CHAIN_WALLET_KEY, from the environment
|
|
1822
1673
|
const client = new LLMClient();
|
|
1823
1674
|
|
|
1824
1675
|
// Or pass options explicitly
|
|
1825
|
-
const
|
|
1826
|
-
privateKey: '0x...',
|
|
1827
|
-
|
|
1828
|
-
|
|
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)
|
|
1829
1681
|
});
|
|
1830
1682
|
```
|
|
1831
1683
|
|
|
1832
|
-
## Environment Variables
|
|
1833
|
-
|
|
1834
1684
|
| Variable | Description |
|
|
1835
1685
|
|----------|-------------|
|
|
1836
|
-
| `BLOCKRUN_API_KEY` |
|
|
1837
|
-
| `BLOCKRUN_API_BASE_URL` | Account API endpoint (
|
|
1838
|
-
| `BASE_CHAIN_WALLET_KEY` |
|
|
1839
|
-
| `
|
|
1840
|
-
| `
|
|
1841
|
-
|
|
1842
|
-
`
|
|
1843
|
-
|
|
1844
|
-
`
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
[
|
|
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`.
|
|
1849
1733
|
|
|
1850
1734
|
## Error Handling
|
|
1851
1735
|
|
|
1852
1736
|
```typescript
|
|
1853
|
-
import { LLMClient, APIError, PaymentError } from '@blockrun/llm';
|
|
1737
|
+
import { LLMClient, APIError, PaymentError, retryDisposition } from '@blockrun/llm';
|
|
1854
1738
|
|
|
1855
1739
|
const client = new LLMClient();
|
|
1856
1740
|
|
|
1857
1741
|
try {
|
|
1858
|
-
const response = await client.
|
|
1742
|
+
const response = await client.smartChat('Hello!');
|
|
1859
1743
|
} catch (error) {
|
|
1860
1744
|
if (error instanceof PaymentError) {
|
|
1861
|
-
|
|
1745
|
+
// Wallet mode: signing or settlement failed. Check the USDC balance.
|
|
1746
|
+
console.error(`Payment failed: ${error.message}`);
|
|
1862
1747
|
} else if (error instanceof APIError) {
|
|
1863
|
-
|
|
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
|
|
1864
1758
|
}
|
|
1865
1759
|
}
|
|
1866
1760
|
```
|
|
1867
1761
|
|
|
1868
|
-
|
|
1869
|
-
|
|
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)):
|
|
1870
1767
|
anything but `'unpaid'` may already have been charged.
|
|
1871
1768
|
|
|
1872
1769
|
## Testing
|
|
1873
1770
|
|
|
1874
|
-
### Running Unit Tests
|
|
1875
|
-
|
|
1876
|
-
Unit tests do not require API access or funded wallets:
|
|
1877
|
-
|
|
1878
1771
|
```bash
|
|
1879
|
-
npm test
|
|
1880
|
-
|
|
1881
|
-
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
|
|
1882
1774
|
```
|
|
1883
1775
|
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
- 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:
|
|
1890
1781
|
|
|
1891
1782
|
```bash
|
|
1892
1783
|
export BASE_CHAIN_WALLET_KEY=0x...
|
|
1893
|
-
|
|
1894
|
-
```
|
|
1895
|
-
|
|
1896
|
-
Integration tests are automatically skipped if `BASE_CHAIN_WALLET_KEY` is not set.
|
|
1897
|
-
|
|
1898
|
-
## Setting Up Your Wallet
|
|
1899
|
-
|
|
1900
|
-
### Solana
|
|
1901
|
-
1. Create a Solana wallet (Phantom, Backpack, Solflare, etc.)
|
|
1902
|
-
2. Get USDC on Solana for API payments
|
|
1903
|
-
3. Export your secret key and set as `SOLANA_WALLET_KEY`
|
|
1904
|
-
|
|
1905
|
-
```bash
|
|
1906
|
-
# .env
|
|
1907
|
-
SOLANA_WALLET_KEY=...your_bs58_secret_key
|
|
1784
|
+
npx vitest run test/integration
|
|
1908
1785
|
```
|
|
1909
1786
|
|
|
1910
|
-
### Base (EVM)
|
|
1911
|
-
1. Create a wallet on Base (Coinbase Wallet, MetaMask, etc.)
|
|
1912
|
-
2. Get USDC on Base for API payments
|
|
1913
|
-
3. Export your private key and set as `BASE_CHAIN_WALLET_KEY`
|
|
1914
|
-
|
|
1915
|
-
```bash
|
|
1916
|
-
# .env
|
|
1917
|
-
BASE_CHAIN_WALLET_KEY=0x...
|
|
1918
|
-
```
|
|
1919
|
-
|
|
1920
|
-
Note: Solana transactions are gasless for the user - the CDP facilitator pays for transaction fees.
|
|
1921
|
-
|
|
1922
1787
|
## Security
|
|
1923
1788
|
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
- **
|
|
1927
|
-
- **
|
|
1928
|
-
- **Verify
|
|
1929
|
-
|
|
1930
|
-
### Best Practices
|
|
1931
|
-
|
|
1932
|
-
**Private Key Management:**
|
|
1933
|
-
- Use environment variables, never hard-code keys
|
|
1934
|
-
- Use dedicated wallets for API payments (separate from main holdings)
|
|
1935
|
-
- Set spending limits by only funding payment wallets with small amounts
|
|
1936
|
-
- Never commit `.env` files to version control
|
|
1937
|
-
- Rotate keys periodically
|
|
1938
|
-
|
|
1939
|
-
**Input Validation:**
|
|
1940
|
-
The SDK validates all inputs before API requests:
|
|
1941
|
-
- Private keys (format, length, valid hex)
|
|
1942
|
-
- API URLs (HTTPS required for production, HTTP allowed for localhost)
|
|
1943
|
-
- 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:
|
|
1944
1794
|
|
|
1945
|
-
**Error Sanitization:**
|
|
1946
|
-
API errors are automatically sanitized to prevent sensitive information leaks.
|
|
1947
|
-
|
|
1948
|
-
**Monitoring:**
|
|
1949
1795
|
```typescript
|
|
1950
|
-
|
|
1951
|
-
console.log(`View transactions: https://basescan.org/address/${address}`);
|
|
1952
|
-
```
|
|
1953
|
-
|
|
1954
|
-
**Keep Updated:**
|
|
1955
|
-
```bash
|
|
1956
|
-
npm update @blockrun/llm # Get security patches
|
|
1957
|
-
```
|
|
1958
|
-
|
|
1959
|
-
## TypeScript Support
|
|
1960
|
-
|
|
1961
|
-
Full TypeScript support with exported types:
|
|
1962
|
-
|
|
1963
|
-
```typescript
|
|
1964
|
-
import {
|
|
1965
|
-
LLMClient,
|
|
1966
|
-
OpenAI,
|
|
1967
|
-
type ChatMessage,
|
|
1968
|
-
type ChatResponse,
|
|
1969
|
-
type ChatOptions,
|
|
1970
|
-
type ChatCompletionOptions,
|
|
1971
|
-
type Model,
|
|
1972
|
-
// Smart routing types
|
|
1973
|
-
type SmartChatOptions,
|
|
1974
|
-
type SmartChatResponse,
|
|
1975
|
-
type RoutingDecision,
|
|
1976
|
-
type RoutingProfile,
|
|
1977
|
-
type RoutingTier,
|
|
1978
|
-
APIError,
|
|
1979
|
-
PaymentError,
|
|
1980
|
-
} from '@blockrun/llm';
|
|
1981
|
-
|
|
1982
|
-
// chatCompletionStream returns a standard fetch Response with SSE body
|
|
1983
|
-
const streamResponse: Response = await client.chatCompletionStream(model, messages, options);
|
|
1984
|
-
|
|
1985
|
-
// OpenAI-compat stream returns AsyncIterable
|
|
1986
|
-
const stream: AsyncIterable<OpenAIChatCompletionChunk> = await openaiClient.chat.completions.create({
|
|
1987
|
-
model, messages, stream: true
|
|
1988
|
-
});
|
|
1989
|
-
```
|
|
1990
|
-
|
|
1991
|
-
## Agent Wallet Setup
|
|
1992
|
-
|
|
1993
|
-
One-line setup for agent runtimes (Claude Code skills, MCP servers, etc.):
|
|
1994
|
-
|
|
1995
|
-
```typescript
|
|
1996
|
-
import { setupAgentWallet } from '@blockrun/llm';
|
|
1997
|
-
|
|
1998
|
-
// Auto-creates wallet if none exists, returns ready client
|
|
1999
|
-
const client = setupAgentWallet();
|
|
2000
|
-
const response = await client.chat('openai/gpt-5.4', 'Hello!');
|
|
2001
|
-
```
|
|
2002
|
-
|
|
2003
|
-
For Solana:
|
|
2004
|
-
|
|
2005
|
-
```typescript
|
|
2006
|
-
import { setupAgentSolanaWallet } from '@blockrun/llm';
|
|
2007
|
-
|
|
2008
|
-
const client = await setupAgentSolanaWallet();
|
|
2009
|
-
const response = await client.chat('anthropic/claude-sonnet-4.6', 'Hello!');
|
|
2010
|
-
```
|
|
2011
|
-
|
|
2012
|
-
Check wallet status:
|
|
2013
|
-
|
|
2014
|
-
```typescript
|
|
2015
|
-
import { status } from '@blockrun/llm';
|
|
2016
|
-
|
|
2017
|
-
await status();
|
|
2018
|
-
// Wallet: 0xCC8c...5EF8
|
|
2019
|
-
// Balance: $5.30 USDC
|
|
2020
|
-
```
|
|
2021
|
-
|
|
2022
|
-
## Wallet Discovery and Migration
|
|
2023
|
-
|
|
2024
|
-
The SDK can discover compatible wallets for an explicit, user-confirmed
|
|
2025
|
-
migration. It never automatically makes a discovered provider wallet active:
|
|
2026
|
-
|
|
2027
|
-
```typescript
|
|
2028
|
-
import { scanWallets, scanSolanaWallets } from '@blockrun/llm';
|
|
2029
|
-
|
|
2030
|
-
// Scans ~/.<dir>/wallet.json for Base wallets
|
|
2031
|
-
const baseWallets = scanWallets();
|
|
2032
|
-
|
|
2033
|
-
// Scans ~/.<dir>/solana-wallet.json and ~/.brcc/wallet.json
|
|
2034
|
-
const solWallets = scanSolanaWallets();
|
|
2035
|
-
```
|
|
2036
|
-
|
|
2037
|
-
`getOrCreateWallet()` always uses `~/.blockrun/.session` (or an explicit
|
|
2038
|
-
wallet environment variable, or the legacy `~/.blockrun/wallet.key`). Review
|
|
2039
|
-
the discovered addresses and import one explicitly if you intend to switch
|
|
2040
|
-
wallets.
|
|
2041
|
-
|
|
2042
|
-
### Upgrading from a provider wallet
|
|
2043
|
-
|
|
2044
|
-
Earlier versions adopted the most recently written provider wallet
|
|
2045
|
-
automatically. If you relied on that, the first run after upgrading creates a
|
|
2046
|
-
fresh BlockRun wallet and prints the addresses it found, so you can import the
|
|
2047
|
-
one you actually own:
|
|
2048
|
-
|
|
2049
|
-
```
|
|
2050
|
-
NOTICE: BlockRun created a new wallet, but also found existing wallet(s)
|
|
2051
|
-
belonging to other applications on this system:
|
|
2052
|
-
|
|
2053
|
-
0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f
|
|
2054
|
-
...
|
|
2055
|
-
```
|
|
2056
|
-
|
|
2057
|
-
Adopt one deliberately:
|
|
2058
|
-
|
|
2059
|
-
```typescript
|
|
2060
|
-
import { listDiscoveredWallets, importWallet } from '@blockrun/llm';
|
|
2061
|
-
|
|
2062
|
-
for (const w of listDiscoveredWallets()) {
|
|
2063
|
-
console.log(w.address, 'from', w.source);
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');
|
|
2067
|
-
```
|
|
2068
|
-
|
|
2069
|
-
`importWallet()` writes your current wallet to
|
|
2070
|
-
`~/.blockrun/.session.backup-<timestamp>` before switching, so adopting a wallet
|
|
2071
|
-
never strands funds in the old one. Solana: `listDiscoveredSolanaWallets()` and
|
|
2072
|
-
`importSolanaWallet()`.
|
|
2073
|
-
|
|
2074
|
-
Addresses shown are derived from the discovered key itself, and `importWallet()`
|
|
2075
|
-
matches on that derived address — so a wallet file cannot claim an address it
|
|
2076
|
-
cannot sign for, nor be adopted by one. `listDiscoveredWallets()` never returns
|
|
2077
|
-
private keys.
|
|
2078
|
-
|
|
2079
|
-
### One wallet across every BlockRun product
|
|
2080
|
-
|
|
2081
|
-
Base wallet resolution, discovery, and adoption are implemented in
|
|
2082
|
-
[`@blockrun/core`](https://www.npmjs.com/package/@blockrun/core), the shared kernel
|
|
2083
|
-
this SDK, the `blockrun` CLI, and clawrouter-codex all read. Defining the canonical
|
|
2084
|
-
order in one place is what keeps them in agreement — when each product carried its
|
|
2085
|
-
own copy, they drifted, and a fix made here did not reach the CLI.
|
|
2086
|
-
|
|
2087
|
-
The kernel is bundled into the SDK at build time (frozen, reviewed bytes — no
|
|
2088
|
-
floating dependency), so there is nothing extra to install. Set `BLOCKRUN_HOME`
|
|
2089
|
-
to override the base directory (`~` by default) for test isolation; unset,
|
|
2090
|
-
behaviour is unchanged. **Treat `BLOCKRUN_HOME` as security-sensitive**: it
|
|
2091
|
-
redirects where the signing key is read from and written to, so an environment
|
|
2092
|
-
that can set it controls the wallet as surely as one that can set
|
|
2093
|
-
`BLOCKRUN_WALLET_KEY`. Set it before importing the SDK — the exported
|
|
2094
|
-
`WALLET_FILE_PATH`/`WALLET_DIR_PATH` constants snapshot at import (all internal
|
|
2095
|
-
reads and writes resolve per call). Solana resolution is still SDK-local and
|
|
2096
|
-
does not honor `BLOCKRUN_HOME`.
|
|
2097
|
-
|
|
2098
|
-
For a single run without changing anything, use
|
|
2099
|
-
`export BLOCKRUN_WALLET_KEY=<private-key>`.
|
|
2100
|
-
|
|
2101
|
-
## Response Caching
|
|
2102
|
-
|
|
2103
|
-
The SDK caches responses to avoid duplicate payments:
|
|
2104
|
-
|
|
2105
|
-
```typescript
|
|
2106
|
-
import { getCachedByRequest, saveToCache, clearCache } from '@blockrun/llm';
|
|
2107
|
-
|
|
2108
|
-
// Automatic TTLs by endpoint:
|
|
2109
|
-
// - Search: 15 minutes
|
|
2110
|
-
// - Models: 24 hours
|
|
2111
|
-
// - Chat/Image: no cache (every call is unique)
|
|
2112
|
-
|
|
2113
|
-
// Manual cache management
|
|
2114
|
-
clearCache(); // Remove all cached responses
|
|
2115
|
-
```
|
|
2116
|
-
|
|
2117
|
-
## Cost Logging
|
|
2118
|
-
|
|
2119
|
-
Track spending across sessions:
|
|
2120
|
-
|
|
2121
|
-
```typescript
|
|
2122
|
-
import { logCost, getCostSummary } from '@blockrun/llm';
|
|
2123
|
-
|
|
2124
|
-
// Costs are logged to ~/.blockrun/data/costs.jsonl
|
|
2125
|
-
const summary = getCostSummary();
|
|
2126
|
-
console.log(`Total: $${summary.totalUsd.toFixed(2)}`);
|
|
2127
|
-
console.log(`Calls: ${summary.calls}`);
|
|
2128
|
-
console.log(`By model:`, summary.byModel);
|
|
2129
|
-
```
|
|
2130
|
-
|
|
2131
|
-
## Anthropic SDK Compatibility
|
|
2132
|
-
|
|
2133
|
-
Use the official Anthropic SDK interface with BlockRun's pay-per-request backend:
|
|
2134
|
-
|
|
2135
|
-
```typescript
|
|
2136
|
-
import { AnthropicClient } from '@blockrun/llm';
|
|
2137
|
-
|
|
2138
|
-
const client = new AnthropicClient(); // Auto-detects wallet, auto-pays
|
|
2139
|
-
|
|
2140
|
-
const response = await client.messages.create({
|
|
2141
|
-
model: 'claude-sonnet-4-6',
|
|
2142
|
-
max_tokens: 1024,
|
|
2143
|
-
messages: [{ role: 'user', content: 'Hello!' }],
|
|
2144
|
-
});
|
|
2145
|
-
console.log(response.content[0].text);
|
|
1796
|
+
import { LLMClient } from '@blockrun/llm';
|
|
2146
1797
|
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
model: 'openai/gpt-5.4',
|
|
2150
|
-
max_tokens: 1024,
|
|
2151
|
-
messages: [{ role: 'user', content: 'Hello from GPT!' }],
|
|
2152
|
-
});
|
|
1798
|
+
const client = new LLMClient();
|
|
1799
|
+
console.log(`View transactions: https://basescan.org/address/${client.getWalletAddress()}`);
|
|
2153
1800
|
```
|
|
2154
1801
|
|
|
2155
|
-
The
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
- [Website](https://blockrun.ai)
|
|
2160
|
-
- [Documentation](https://github.com/BlockRunAI/awesome-blockrun/tree/main/docs)
|
|
2161
|
-
- [GitHub](https://github.com/blockrunai/blockrun-llm-ts)
|
|
2162
|
-
- [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.
|
|
2163
1806
|
|
|
2164
1807
|
## Frequently Asked Questions
|
|
2165
1808
|
|
|
2166
1809
|
### What is @blockrun/llm?
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
### How does payment work?
|
|
2170
|
-
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.
|
|
2171
1811
|
|
|
2172
1812
|
### What is smart routing?
|
|
2173
|
-
Router Core V3
|
|
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).
|
|
2174
1814
|
|
|
2175
1815
|
### Does it support streaming?
|
|
2176
|
-
Yes
|
|
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.
|
|
2177
1817
|
|
|
2178
1818
|
### How much does it cost?
|
|
2179
|
-
Pay only for what you use
|
|
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`).
|
|
2180
1823
|
|
|
2181
|
-
|
|
2182
|
-
|
|
1824
|
+
## License
|
|
1825
|
+
|
|
1826
|
+
MIT
|
|
2183
1827
|
|
|
2184
1828
|
---
|
|
2185
1829
|
|
|
@@ -2190,7 +1834,3 @@ Yes. Use `SolanaLLMClient` for Solana payments (recommended) and `LLMClient` for
|
|
|
2190
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)
|
|
2191
1835
|
|
|
2192
1836
|
</div>
|
|
2193
|
-
|
|
2194
|
-
## License
|
|
2195
|
-
|
|
2196
|
-
MIT
|