Skip to content

About

TypeScript SDK for BlockRun — every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. Zero rate limits.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

@blockrun/llm

Cut your LLM bill by 84%. One line of TypeScript.

The smart-routing SDK for 88 models — every request goes to the cheapest model that can handle it, paid with an API key or per-request USDC on Solana, Base or Arc. No vendor lock-in.

npm npm downloads CI License: MIT Node TypeScript

API Key Solana Base Network x402 Telegram

Sign up / Get an API key · Website · Models & Pricing · ClawRouter · Python SDK · Telegram


import { LLMClient } from '@blockrun/llm';

// Reads BLOCKRUN_API_KEY (sign up at https://user.blockrun.ai),
// or BASE_CHAIN_WALLET_KEY if you'd rather pay per request in USDC.
const client = new LLMClient();

const r = await client.smartChat('Prove step by step that the sum of two odd integers is even.');
console.log(r.model);            // 'deepseek/deepseek-v4-pro': a strong reasoner, not the frontier flagship
console.log(r.routing.savings);  // e.g. 0.83: this request cost 83% less than the baseline
console.log(r.response);         // the proof

84% cheaper than pinning Claude Opus 5 across a realistic workload on the default auto profile, 98% on eco, which tries the free tier first. How that is measured: Smart Routing.

Why This SDK

  • 🧠 Smart routing that pays for itself. The bundled Router Core V3 engine (shared with ClawRouter) classifies every request locally in <1ms across 15 dimensions and routes to the cheapest capable model.
  • 🆓 7 genuinely free models. $0 in and out, including two 1M-context Nemotrons, two that accept images, and free coding models from Cohere and Poolside.
  • 🔐 Two ways to connect. A BlockRun API key billed against account credit (sign up, create a key, add credit), or a wallet signature with x402 micropayments and no account at all. Same code either way.
  • 💸 Pay per request in USDC. x402 micropayments on Solana, Base or Arc. Agents can fund and pay their own way.
  • 🛡️ 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.
  • ⚡ Streaming, OpenAI & Anthropic compat. Drop-in chat.completions / messages layers, SSE streaming, strict TypeScript.
  • 🎨 Beyond chat. Image, video, music, speech, live search, prediction markets, market data, and 40-chain RPC through the same API key or wallet.

How It Compares

OpenAI SDK OpenRouter LiteLLM @blockrun/llm
Cost routing ✗ one vendor Manual selection Manual selection Automatic — 84% cheaper
Models GPT only 200+ 100+ (BYO keys) 88, one credential
Free tier ✗ Rate-limited ✗ 7 models, no signup
Auth API key Account + API key Your API keys API key or wallet signature
Payment Card + invoice Credit card BYO keys Account credit or USDC per-request
Agent-ready ✗ ✗ ✗ ✓ — one key, or agents fund their own wallet

Installation

npm install @blockrun/llm   # Account API keys, Base and Arc wallets; smart routing included
Solana payments — two more optional peers
npm install @blockrun/llm @solana/web3.js @solana/spl-token

Why they are not automatic: @solana/spl-token pulls in bigint-buffer, whose native toBigIntLE() has an unpatched buffer overflow (GHSA-3gc7-fjrx-p6mg) with no fixed release anywhere. As an optional dependency it landed in the lockfile of every consumer, including projects that only ever pay on Base. As an optional peer it reaches only the projects that ask for Solana. Calling a Solana path without them throws an error naming the exact install command.

Supported chains — Solana (recommended), Base, Arc, Base Sepolia

Only relevant in wallet mode; an account API key is chain-independent.

Chain Network Payment Notes
Solana Solana Mainnet USDC (SPL) Recommended for new wallets; SolanaLLMClient
Base Base Mainnet (eip155:8453) USDC LLMClient; settled by the Coinbase CDP facilitator
Arc Circle Arc (eip155:5042) USDC LLMClient with apiUrl: 'https://arc.blockrun.ai/api'; settled by Circle's facilitator
Base Sepolia Base Sepolia (eip155:84532) Testnet USDC Development

Protocol: x402 v2.

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.

Documentation map

If you want to… Start here
Let the SDK choose the cheapest capable model Smart Routing
Try it for $0 Try It Free and Free Tier
Understand wallet payments and settlement How Payment Works
Pay from a Solana wallet Solana Support
Enable metered batch settlement Batch settlement and the full reference
Stream tokens, use tools Chat, Tools and Streaming
Generate media or query data services Models and Service APIs
Use the OpenAI or Anthropic SDK surface OpenAI and Anthropic Compatibility
Create, find or import a wallet Wallets
Configure production credentials safely Configuration and Environment Variables and Security

Quick Start

Choose one billing mode. The chat and routing APIs are the same in every mode.

Mode Credential Settlement Best for
Account BLOCKRUN_API_KEY BlockRun account credit Applications and teams that want the simplest setup
Solana wallet SOLANA_WALLET_KEY Per-request USDC over x402 Agents and non-custodial workloads; recommended wallet chain
Base wallet BASE_CHAIN_WALLET_KEY Per-request USDC over x402 Existing EVM wallets and Base-native applications (the same key pays on Arc)

Credential precedence is deterministic. An explicit apiKey wins over the environment, and an explicit privateKey selects wallet mode even when BLOCKRUN_API_KEY is set; passing both explicitly is an error. With no explicit credential, BLOCKRUN_API_KEY beats the wallet key env vars, so a process holding both runs in account mode. An invalid or exhausted API key never falls back to a wallet payment.

Account API key

  1. Sign up or sign in.
  2. Create a key on API Keys and add credit on Credits.
  3. Set the key and use any SDK client. No wallet or payment chain is required.
export BLOCKRUN_API_KEY=brk_live_...
import { LLMClient, ImageClient, VideoClient, BlockrunClient } from '@blockrun/llm';

const llm = new LLMClient(); // reads BLOCKRUN_API_KEY

// Let the router pick the cheapest capable model...
const routed = await llm.smartChat('Explain x402 in one paragraph.');
console.log(routed.model, routed.response);

// ...or pin one yourself
console.log(await llm.chat('openai/gpt-5.2', 'Hello!'));

const images = new ImageClient({ apiKey: process.env.BLOCKRUN_API_KEY });
const videos = new VideoClient(); // same account; async jobs are polled for you
const api = new BlockrunClient();  // generic access, e.g. the Responses API
const response = await api.post('/v1/responses', { model: 'openai/gpt-5.2', input: 'Hello!' });

Every client accepts apiKey: the service clients, SolanaLLMClient, and the OpenAI/Anthropic compatibility wrappers. Usage and charges are on Activity. Keep API keys in server or local environment variables, never in browser code or logs. Base URL overrides, retry and polling rules, and switching back to a wallet are under Account mode details.

Solana wallet

import { setupAgentClient, SolanaLLMClient } from '@blockrun/llm';

// Uses BLOCKRUN_API_KEY when set. Otherwise it honours a saved chain preference,
// keeps an existing Base-only wallet on Base, and creates new wallets on Solana.
const client = await setupAgentClient();
const routed = await client.smartChat('Explain photosynthesis.');
console.log(routed.model, routed.response);

// Or construct the Solana client explicitly (needs the Solana peers from Installation above).
const solana = new SolanaLLMClient({ privateKey: process.env.SOLANA_WALLET_KEY });
console.log(await solana.chat('deepseek/deepseek-chat', 'gm')); // pin a model

Base wallet

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient(); // BASE_CHAIN_WALLET_KEY; the key signs locally and is never sent

// Recommended: let the router pick the cheapest capable model
const result = await client.smartChat('Hello!');
console.log(result.model, result.response);

// Or pin a model yourself
const reply = await client.chat('openai/gpt-5.4', 'Hello!');

That's it. The SDK handles the x402 payment, and smartChat() keeps the bill down on every request. The router is bundled: no extra package to install. To pay on Arc instead, point the same client at Arc's gateway; see Arc Support.

Try It Free (No USDC Required)

Want to kick the tires before funding a wallet? Use BlockRun's free tier:

import { LLMClient } from '@blockrun/llm';

// The client still needs a wallet key or an API key to construct, but free
// models are answered without a payment: nothing is signed and nothing is charged.
const client = new LLMClient();

// Option 1: call a free model directly (guaranteed $0)
const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'Explain x402 in 1 sentence');

// Option 2: let the router pick: 'eco' ranks the free tier first
const result = await client.smartChat('What is the capital of France?', { routingProfile: 'eco' });
console.log(result.model);           // a free model, e.g. 'nvidia/nemotron-3.5-lightning'
console.log(result.response);        // 'The capital of France is Paris.'
console.log(result.routing.savings); // 1 (100%)

The seven free models are listed under Free Tier. eco lands on a free model when one can handle the request and moves to the cheapest paid model when none can; pin a free id when the call must cost $0.

Smart Routing (Router Core V3)

Let the SDK pick the cheapest capable model for each request: 84% cheaper than pinning Claude Opus 5 for the same traffic on auto, 98% on eco.

Not an "up to" figure. The baseline, the workload mix and the token ratio are published in savings-mix.json, priced against the live catalog, so anyone can recompute the claim and get the same answer. The per-request routing.savings on each response is measured against router-core's baseline model, anthropic/claude-opus-4.7, which is priced the same as Claude Opus 5 ($5 input / $25 output per million tokens).

Smart routing is powered by the product-neutral @blockrun/router-core V3 engine, the same deterministic portfolio router that drives ClawRouter. It is bundled into this SDK: no separate router package to install, and the routing decision runs locally with no extra model call.

Three ways to use it:

import { LLMClient, type ChatMessage, type Tool } from '@blockrun/llm';

const client = new LLMClient();
const messages: ChatMessage[] = [{ role: 'user', content: 'What is the weather in Paris?' }];
const tools: Tool[] = [{
  type: 'function',
  function: {
    name: 'get_weather',
    description: 'Current weather for a city',
    parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
  },
}];

// 1. smartChat(): one-line routed chat
const result = await client.smartChat('What is the capital of France?');
console.log(result.model); // 'google/gemini-2.5-flash'
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 90%': this request vs the baseline

// 2. smartChatCompletion(): full agent / tool conversations, routed
const agent = await client.smartChatCompletion(messages, { tools, toolChoice: 'auto' });

// 3. Model aliases: blockrun/auto | blockrun/eco | blockrun/premium
const reply = await client.chatCompletion('blockrun/auto', messages);

// Inspect a decision without paying for anything
const decision = await client.route('Prove the Riemann hypothesis step by step');
console.log(decision.model);       // 'deepseek/deepseek-v4-pro'
console.log(decision.method);      // 'portfolio'
console.log(decision.taskType);    // 'reasoning'
console.log(decision.candidates);  // ranked, capability-eligible models
console.log(decision.fallbacks);   // ['google/gemini-3.5-flash', 'deepseek/deepseek-reasoner', 'xai/grok-4.3', ...]

The aliases are resolved locally by chat() and chatCompletion() on both LLMClient and SolanaLLMClient, by LLMClient.chatCompletionStream(), and by the OpenAI-compatible wrapper. SolanaLLMClient.stream() and the Anthropic-compatible wrapper (which proxies straight to the gateway's /v1/messages) do not resolve them; pass a concrete model id there.

Example picks in this README reflect the live catalog when it was written. The router re-ranks as models and prices move, so your output can differ.

Automatic Fallback on Transient Errors

smartChat() populates a fallback chain from the portfolio ranking and chat() / chatCompletion() walk it automatically when the primary model returns a transient error — timeouts, network failures, 429 rate limits, or 5xx responses (502/503/504/522/524) — before any payment was sent. Other 4xx errors and PaymentError propagate immediately so wallet / auth issues surface fast. (Solana's internal stale-blockhash re-sign is a separate, lower-level retry inside the payment step — see How Payment Works.)

The next model is a new paid request, so the chain is only walked when the failed request cannot have been charged. Every error from these calls carries a retry disposition, which retryDisposition(err) returns:

  • 'unpaid': nothing chargeable was sent — the unpaid first request and its 402 challenge, or signing the payment. A 429, 5xx, timeout or network error here moves on to the next model. On Solana, a 503 PAYMENT_VERIFICATION_UNAVAILABLE in answer to the payment is 'unpaid' too: the gateway sends it only when verification could not run, so nothing was settled or broadcast (the batch path already treats it as a refusal). This holds on every exact-paid Solana endpoint, not only chat.
  • 'paid-or-in-doubt': the signed payment (exact or batch) was sent and may have been charged — a timeout, abort or network error after sending it, any error status in answer to it, or a 2xx whose body could not be read. The error propagates; no other model is bought for the call. With an API key the request itself is billed, so only the account API's explicit answers are 'unpaid': a 4xx (such as a 429), or a 5xx carrying its JSON error envelope (api.blockrun.ai debits only an accepted 2xx). An HTML or empty 5xx from a proxy, or a timeout, is not.

An error without a disposition counts as 'paid-or-in-doubt'. Use the same check in your own retry wrapper, or pass your own fallback chain:

import { LLMClient, retryDisposition } from '@blockrun/llm';

const client = new LLMClient();

try {
  await client.chat('openai/gpt-5.2', 'hello');
} catch (err) {
  if (retryDisposition(err) !== 'unpaid') throw err; // may have been charged: never resend
  // safe to retry: nothing was paid
}

// Manually pass a fallback chain to chat() / chatCompletion()
const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'hello', {
  fallbackModels: ['nvidia/nemotron-3-ultra-550b', 'cohere/north-mini-code'],
});
// If nemotron-3.5-lightning times out, the SDK retries against the next model
// and logs each hop to stderr: "[@blockrun/llm] <from> -> <to> (...)".

Routing Profiles

Profile Strategy Savings vs Opus 5 Best For
eco Cheapest capable model — ranks the 7-model free tier first 98% Cost-sensitive production, zero-cost testing
auto Best balance of cost and quality (default) 84% General use
premium Highest-quality model per tier 0% Quality-critical tasks

There is no free routing profile: routingProfile accepts 'eco' | 'auto' | 'premium'. (ClawRouter's /model free belongs to its own proxy, not to this SDK's router options.) For guaranteed $0, call a free model directly with chat(); for smart-routed $0-first, use eco.

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();

// Use premium models for quality-critical tasks
const result = await client.smartChat(
  'Write production-grade async TypeScript code',
  { routingProfile: 'premium' }
);
console.log(result.model);  // 'openai/gpt-5.3-codex'

How the Router Works

flowchart LR
    A["prompt"] --> B["classify locally<br/>15 dimensions, &lt;1ms"]
    B --> C["hard filters<br/>tools · vision · context ·<br/>structured output"]
    C --> D["rank portfolio<br/>quality · cost · speed ·<br/>reliability"]
    D --> E["cheapest capable model<br/>+ ranked fallback chain"]
    E --> F["one request, paid by<br/>API key or x402 USDC"]
    F --> G["response<br/>+ full routing metadata"]
Loading

Auto uses the deterministic Router Core V3.5 portfolio strategy (DEFAULT_ROUTING_CONFIG.version in the pinned engine): it classifies the task shape locally across

15 dimensions

(token count, code presence, reasoning markers, technical/creative terms, agentic patterns, …), enforces tool / vision / structured-output / context constraints as hard filters, then ranks an ordered candidate portfolio. The winner becomes routing.model; the rest surface as routing.candidates and feed the transient-error fallback chain. Classification is local and deterministic: <1ms, no extra model call. (The SDK reads /v1/models once to price candidates; the catalog is cached.)

Classification still maps to one of four tiers (routing.tier). Each tier × profile has a designated primary (what the rules strategy — routing.method: 'rules', the rollback lever — routes to directly, and what anchors the portfolio's candidate pool):

Tier Example Tasks ECO AUTO PREMIUM
SIMPLE "What is the capital of France?", definitions nemotron-3.5-lightning (FREE) gemini-2.5-flash gemini-2.5-flash
MEDIUM Code snippets, explanations glm-5.3-flash gemini-3.5-flash gpt-5.3-codex
COMPLEX Architecture, long documents glm-5.3-flash gemini-3.1-pro claude-fable-5
REASONING Proofs, multi-step reasoning deepseek-reasoner deepseek-reasoner claude-sonnet-5

This table mirrors the pinned router-core DEFAULT_ROUTING_CONFIG; routing.tierConfigs on any decision returns the map that decision used.

The router's ranking is used as-is. It can pick a model the gateway serves but does not list on /v1/models (for example nvidia/nemotron-3-nano-30b, which appears in eco fallback chains); the gateway serves those ids directly. The only rewrite is ClawRouter's proxy namespace: free/<model> ids map to their nvidia/<model> gateway ids, and are dropped when there is no mapping.

Routing Metadata Reference

Every smartChat() result carries the full decision on result.routing (type RoutingDecision) — enough to log, audit, or replay why a model was picked:

Field Description
model Selected model id (same as result.model)
method 'portfolio' (the Auto default), 'rules' (rollback strategy), or 'llm'
tier Task tier: 'SIMPLE', 'MEDIUM', 'COMPLEX', or 'REASONING'
taskType Portfolio task classification: 'chat', 'extraction', 'code_edit', 'code_agent', 'tool_agent', 'debug', 'reasoning', 'reasoning_math', 'long_context', 'vision', …
candidates Ordered, capability-eligible models ranked by the portfolio router; the first entry is model
candidateScores Per-candidate score breakdown (quality / cost / speed / reliability), ordered with candidates
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)
savings 0–1 fraction saved vs the baseline model (anthropic/claude-opus-4.7, priced like Opus 5)
costEstimate / baselineCost Estimated cost of the pick vs that baseline, in USD
confidence Sigmoid-calibrated classifier confidence, 0–1
routerVersion 'v3-portfolio' or 'v2-rules'
profile Routing profile applied: 'auto', 'eco', 'premium', or 'agentic'
reasoning Human-readable explanation of the decision
tierConfigs The tier → primary/fallback map the decision was made against

Search and routing. xAI search (x_search / web_search tools, or search / searchParameters not set to mode: 'off') runs on xai/* models only. When a routed call asks for search, the candidate chain is narrowed to its xai/* models in the router's order. The router seldom ranks xAI, so when the route has none, the call falls back to the catalog's search-capable xAI models (those /v1/models tags search): cheapest first on eco / auto, most capable first on premium. routing.reasoning says when that happened. Only a catalog with no xai/* chat model at all makes route() / smartChat() / smartChatCompletion() throw, before anything is sent or paid. Pin an xai/* model yourself when you want a specific one.

TypeScript Types

Everything is typed. The routing types are derived from @blockrun/router-core, pinned to a reviewed immutable commit, and shipped inlined in this SDK's declaration files and runtime bundle, so you install nothing extra to route or to typecheck.

import {
  LLMClient,
  OpenAI,
  APIError,
  PaymentError,
  type ChatMessage,
  type ChatResponse,
  type ChatOptions,
  type ChatCompletionOptions,
  type Model,
  // Smart routing
  type SmartChatOptions,
  type SmartChatResponse,
  type SmartChatCompletionOptions,
  type SmartChatCompletionResponse,
  type RoutingDecision,
  type RoutingProfile,
  type RoutingTier,
  type RoutingTaskType,
  type RoutingTierConfig,
  // OpenAI-compatible layer
  type OpenAIChatCompletionResponse,
  type OpenAIChatCompletionChunk, // what the stream: true iterator yields
} from '@blockrun/llm';

Going Deeper

How Payment Works

In wallet mode no account or API key is needed. You hold USDC in your own wallet, and every request pays for itself with an on-chain micropayment. Two phases:

Phase 1 — Fund your wallet once

First, have a wallet. setupAgentClient() creates one for you (see Wallets), or bring your own:

  • Solana: create a wallet (Phantom, Backpack, Solflare, …) and set its bs58 secret key as SOLANA_WALLET_KEY.
  • Base / Arc: create an EVM wallet (Coinbase Wallet, MetaMask, …) and set its private key as BASE_CHAIN_WALLET_KEY.
# .env (never commit it)
SOLANA_WALLET_KEY=...your_bs58_secret_key
BASE_CHAIN_WALLET_KEY=0x...

Then get USDC into it. You only do this when the balance runs low:

  • (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.
  • (b) Transfer existing USDC to your wallet address: Base USDC for LLMClient on Base, USDC on Arc for Arc, Solana SPL USDC for SolanaLLMClient.
  • (c) Skip funding entirely. Use the free models: every call is $0, no balance required.
import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();

const { url } = await client.onramp(client.getWalletAddress());
console.log(`Fund your wallet: ${url}`);  // single-use, expires in ~5 min: mint it at click time

const balance = await client.getBalance(); // USDC on Base
console.log(`Balance: $${balance.toFixed(2)} USDC`);

$5 of USDC covers thousands of paid requests. SolanaLLMClient has its own getBalance() (USDC on Solana) and an async getWalletAddress().

Phase 2 — Every request pays for itself (automatic x402)

You just call client.smartChat(...) or client.chat(...); the payment is invisible:

  1. The SDK sends the request to BlockRun's gateway.
  2. The gateway answers 402 Payment Required with the price.
  3. The SDK signs the payment locally with your wallet key: an EIP-3009 USDC authorization (EIP-712 typed data) on Base or Arc for LLMClient, an SPL TransferChecked transaction on Solana for SolanaLLMClient.
  4. The request is retried automatically with the payment proof.
  5. The gateway's facilitator settles on-chain and the AI response comes back.

One call, no separate pay step. Free-tier models settle at $0 (no payment signed). You pay no gas on any chain: the facilitator submits the transfer and pays the network fee.

On Solana, step 3 pins the payment to a recent blockhash that is valid for roughly 60 seconds. If one expires between signing and verification, SolanaLLMClient re-signs against a fresh blockhash and retries — up to twice, with a short backoff — rather than surfacing a payment error you would only have to retry by hand.

The retry is deliberately narrow. It fires only when the gateway explicitly reports a verification-phase stale blockhash. Settlement failures, ambiguous rejections, insufficient funds and malformed responses all fail immediately. Verification runs strictly before settlement, so a retryable rejection means no transaction was broadcast and you cannot be charged twice.

Track spend and verify settlements

import { LLMClient, getCostSummary } from '@blockrun/llm';

const client = new LLMClient();

const spent = client.getSpending(); // this client, wallet mode only (throws with an API key)
console.log(`Spent $${spent.totalUsd.toFixed(4)} across ${spent.calls} calls`);

const summary = getCostSummary(); // across sessions, from ~/.blockrun/cost_log.jsonl
console.log(`Lifetime: $${summary.totalUsd.toFixed(2)} over ${summary.calls} calls`);
console.log('By model:', summary.byModel);

Only LLMClient wallet settlements (Base / Arc chat) are appended to ~/.blockrun/cost_log.jsonl automatically. Other clients count their payments in their own getSpending(). Account usage lives on the Activity page, not in-process.

In wallet mode, every paid request is a real on-chain USDC transfer — look up your wallet address on Basescan (or a Solana or Arc explorer) to verify each settlement independently. The exception is Solana batch settlement: there the on-chain records are the channel deposit and BlockRun's batched redemptions, and each call is a signed voucher.

Your private key never leaves your machine: it is only used for local signing. With per-request (exact) wallet payments, USDC moves from your wallet for each request and BlockRun holds none of your funds. Two exceptions to keep in mind: account credit is held by BlockRun on your account, and a Solana batch channel escrows a deposit the operator can claim from, up to maxDeposit.

Solana Support

Pay for AI calls with Solana USDC via sol.blockrun.ai:

import { SolanaLLMClient } from '@blockrun/llm';

// SOLANA_WALLET_KEY env var (bs58-encoded Solana secret key),
// or pass { privateKey: 'your-bs58-solana-key' } / { apiKey: 'brk_live_…' }
const client = new SolanaLLMClient();

// Same API as LLMClient: route first...
const routed = await client.smartChat('gm Solana: what is x402?');
console.log(routed.model, routed.response);

// ...or pin a model
const response = await client.chat('deepseek/deepseek-chat', 'gm Solana');

// Live Search with Grok
const trending = await client.chat('xai/grok-4.5', 'What is trending on X?', { search: true });

// xAI x_search on chosen accounts, structured output, real cost per call
const res = await client.chatCompletion('xai/grok-4.3', [
  { role: 'user', content: 'Which new models did these labs announce today? Return JSON.' },
], {
  tools: [{ type: 'x_search', allowedXHandles: ['OpenAI', 'AnthropicAI', 'xai'], fromDate: '2026-10-04' }],
  searchParameters: { maxTurns: 2 },          // bounds xAI's per-post search bill
  responseFormat: { type: 'json_object' },
  timeout: 180_000,
});
console.log(res.choices[0].message.annotations); // url_citation with positions
console.log(res.usage?.server_side_tool_usage);  // { x_posts_fetched, ... }
console.log(res.costUsd, res.settlement);        // what you paid, and the tx

costUsd, settlement and the per-call timeout work the same on LLMClient (Base / Arc). costUsd is what the call added to getSpending(): the signed price under exact, the metered extra.chargedAmount under batch settlement, 0 for a free model; it is undefined in API-key mode (billed to the account). When the gateway answers a paid request from a free fallback model it skips settlement: costUsd is 0, settlement.skipped is "free-fallback", and nothing is added to getSpending().

Setup:

  1. Export your Solana wallet key: export SOLANA_WALLET_KEY="your-bs58-key"
  2. Fund it with USDC on Solana mainnet
  3. That's it — payments are automatic via x402

Endpoint: https://sol.blockrun.ai/api · Payment: Solana USDC (SPL, mainnet)

Images, music and video on Solana:

import { SolanaLLMClient } from '@blockrun/llm';

const client = new SolanaLLMClient();

const image = await client.image('a lighthouse at dusk', { model: 'openai/gpt-image-2' });
const track = await client.music('chill lo-fi beats with piano');
// Extra video options are sent verbatim, so use the gateway's snake_case names.
const video = await client.video('a red apple slowly spinning', { duration_seconds: 5 });

Slow jobs answer 202 { id, poll_url } and the client polls for you. Each poll proves the job is yours with a wallet signature (x-poll-wallet, x-poll-timestamp, x-poll-signature: ed25519 over blockrun-poll:v1:<kind>:<job id>:<unix seconds>), not a payment, so a multi-minute job does not sign a Solana transaction every few seconds. Image and music are paid when submitted. A video is charged only once it is finished: the client signs one payment for the completed job, and a failed job costs nothing. If the gateway does not accept the poll signature for a job, the client falls back to paying each poll the old way (logged to stderr). Base polling is unchanged.

Batch settlement (optional metered billing)

By default every Solana call uses x402 exact: one SPL transfer per call, priced at the request ceiling. Batch settlement replaces those per-call transfers with a bounded payment channel: the client locks a small deposit once, each request authorizes no more than its quoted ceiling, and the gateway charges the metered cost after the response.

It is off unless you opt in. BLOCKRUN_SOL_OPERATOR is a public constant exported by this SDK, not a secret or an environment variable; listing it in batch.operators is the explicit decision that this wallet trusts BlockRun's operator to sign vouchers against the channel, up to maxDeposit.

Batch mode needs three more optional peers on top of the Solana ones:

npm install @x402/core@~2.28.0 @x402/svm@~2.28.0 @solana/kit
import { SolanaLLMClient, BLOCKRUN_SOL_OPERATOR } from '@blockrun/llm';

const client = new SolanaLLMClient({
  privateKey: process.env.SOLANA_WALLET_KEY,
  batch: {
    operators: [BLOCKRUN_SOL_OPERATOR], // explicit trust; there is no default
    maxDeposit: '$5',                   // most escrow at stake at once (default "$1")
  },
});

// The first call opens the channel and is charged its quoted price, as with exact.
await client.smartChat('gm');

// Later calls are metered: you pay for the tokens the call actually used.
const reply = await client.chat('anthropic/claude-sonnet-4.6', 'Summarize x402 in one line', {
  maxTokens: 2048, // a ceiling, not a price
});

// Done for good? Close the channel to get the unused escrow back.
await client.closeBatchChannel();
  • Explicit trust. operators has no default, and the SDK never learns a trusted key from a 402.
  • 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.
  • 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).
  • Non-streaming chat only. chat, chatCompletion, smartChat and smartChatCompletion use batch; stream() and media jobs pay exact.
  • 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.
  • Observable. Every fallback, backoff, recovery and re-read writes one stderr line; batch.onEvent and client.getBatchStats() expose the same events and counters.

The full runtime contract (the answer-by-answer table, payment-identifier replays, 429 backoff, deposit-in-doubt handling, crash safety, closing rules, RPC requirements, and every event reason) is in docs/solana-batch.md.

Arc Support

The same LLMClient pays on Circle's Arc via arc.blockrun.ai: point apiUrl at it and hold USDC on Arc in the same EVM wallet.

import { LLMClient } from '@blockrun/llm';

// BASE_CHAIN_WALLET_KEY: the same EVM key you use on Base
const client = new LLMClient({ apiUrl: 'https://arc.blockrun.ai/api' });

const routed = await client.smartChat('gm Arc');
const response = await client.chat('openai/gpt-5.4', 'gm Arc');

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.

Setup:

  1. Same wallet key as Base: export BASE_CHAIN_WALLET_KEY="0x..."
  2. Fund it with USDC on Arc (Arc's native token, shown as the ERC-20 at 0x3600…0000)
  3. apiUrl: 'https://arc.blockrun.ai/api' — payments are automatic via x402

Endpoint: https://arc.blockrun.ai/api · Payment: USDC on Arc (chain 5042), settled by Circle

Chat, Tools and Streaming

Simple chat

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient(); // BLOCKRUN_API_KEY or BASE_CHAIN_WALLET_KEY

// Routed: the cheapest model that can handle it
const routed = await client.smartChat('Explain quantum computing in two paragraphs');
console.log(routed.response);

// Pinned, with a system prompt
const haiku = await client.chat('anthropic/claude-sonnet-5', 'Write a haiku', {
  system: 'You are a creative poet.',
});

Full chat completion

import { LLMClient, type ChatMessage } from '@blockrun/llm';

const client = new LLMClient();

const messages: ChatMessage[] = [
  { role: 'system', content: 'You are a helpful assistant.' },
  { role: 'user', content: 'How do I read a file in Node.js?' },
];

// Routed: the full OpenAI-style response plus the routing decision
const routed = await client.smartChatCompletion(messages);
console.log(routed.response.choices[0].message.content, routed.model);

// Or pinned
const result = await client.chatCompletion('anthropic/claude-sonnet-5', messages);
console.log(result.choices[0].message.content);

Streaming

Every client streams with automatic payment. On LLMClient in Base / Arc wallet mode, chatCompletionStream() also keeps a pre-auth cache: after the first call to a model it signs the payment up front and skips the 402 round-trip (about 200ms saved per request).

OpenAI-compatible (recommended)

import { OpenAI } from '@blockrun/llm';

const client = new OpenAI({ walletKey: process.env.BASE_CHAIN_WALLET_KEY }); // or { apiKey }

const stream = await client.chat.completions.create({
  model: 'blockrun/auto', // routing aliases work here too
  messages: [{ role: 'user', content: 'Write a short story about AI agents' }],
  stream: true,
});

for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content || '');
}

Native client

chatCompletionStream() returns the raw fetch Response. SSE events can span network reads, so buffer partial lines:

import { LLMClient, type ChatMessage } from '@blockrun/llm';

const client = new LLMClient();

const messages: ChatMessage[] = [
  { role: 'user', content: 'Explain quantum computing in simple terms' },
];

const response = await client.chatCompletionStream('google/gemini-2.5-flash', messages);

const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;

  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop() ?? ''; // keep the incomplete last line for the next read
  for (const line of lines) {
    if (!line.startsWith('data: ') || line === 'data: [DONE]') continue;
    const data = JSON.parse(line.slice(6));
    process.stdout.write(data.choices?.[0]?.delta?.content || '');
  }
}

Solana

SolanaLLMClient.stream() has the same shape as BlockrunClient.stream(): it yields each data: frame already parsed as JSON and stops at [DONE]. The x402 handshake happens before the first frame — 402, sign an SPL TransferChecked authorization locally, replay with PAYMENT-SIGNATURE — including the fresh-blockhash re-sign on a verification-phase rejection. A free model, or an account key, is answered 200 with no handshake at all and settles nothing. Pass a concrete model id: stream() does not resolve the blockrun/* aliases.

import { SolanaLLMClient } from '@blockrun/llm';

type ChatChunk = { choices?: { delta?: { content?: string } }[] };

const client = new SolanaLLMClient(); // SOLANA_WALLET_KEY, or { apiKey: 'brk_live_…' }

for await (const chunk of client.stream<ChatChunk>('/v1/chat/completions', {
  model: 'deepseek/deepseek-chat',
  messages: [{ role: 'user', content: 'Hi' }],
  max_tokens: 64,
  stream: true,
})) {
  process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
}

Set stream: true yourself — the method does not inject it, because the gateway prices a streaming and a non-streaming request the same and rewriting a caller's body silently is how you end up debugging a request you did not send.

Models and Service APIs

The live catalog is the source of truth for availability, context windows and pricing. Prices are not listed in this README: they change often. See blockrun.ai/models for live rates, or read them at runtime:

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();
const models = await client.listModels(); // chat models with pricing

for (const model of models) {
  console.log(`${model.id}: $${model.inputPrice}/M input`);
}

const imageModels = await client.listImageModels();

Free Tier

Input and output both $0 — no promo, no rate-limit gimmick. The free tier is not NVIDIA-only, so pin these by full model id rather than by an nvidia/* prefix, or let routingProfile: 'eco' rank them first.

Model Context Notes
nvidia/nemotron-3.5-lightning 1M Thinking-mode reasoning at 1M context
nvidia/nemotron-3-ultra-550b 1M Largest free model — 550B, 1M context
nvidia/nemotron-3-nano-omni-30b-a3b-reasoning 256K Multimodal reasoning — text + images
nvidia/llama-3.2-11b-vision 128K Vision-language — accepts images
nvidia/muse-glimmer-30b 128K Chat, reasoning and coding
cohere/north-mini-code 256K Compact coding model, sub-second responses
poolside/laguna-xs-2.1 128K Coding model

A free model can be briefly marked unavailable in the live catalog while the gateway's health check holds it back; it stays on the free tier.

Paid chat models — OpenAI, Anthropic, Google, DeepSeek, xAI, Moonshot, MiniMax, Z.ai, Qwen, Mistral, Tencent, Poolside, Xiaomi

OpenAI

GPT-6: Astra is the flagship for long-horizon agentic work and computer use, Sol the cost-efficient tier for complex coding, Luna the fast low-cost tier. GPT-5.6: three tiers on one base (Sol deepest reasoning, Terra balanced, Luna cheap and fast), each with a -pro sibling that thinks longer at the same token price.

Model Context Notes
openai/gpt-6.1-sol 1.05M
openai/gpt-6-astra 1.05M GPT-6 flagship
openai/gpt-6-sol 1.05M
openai/gpt-6-luna 1.05M
openai/gpt-5.6-sol 1.05M
openai/gpt-5.6-sol-pro 1.05M
openai/gpt-5.6-terra 1.05M
openai/gpt-5.6-terra-pro 1.05M
openai/gpt-5.6-luna 1.05M
openai/gpt-5.6-luna-pro 1.05M
openai/gpt-5.5 1.05M
openai/gpt-5.5-pro 1.05M
openai/chat-latest 128K ChatGPT Instant — the model behind chatgpt.com
openai/gpt-5.4 1.05M
openai/gpt-5.4-pro 1.05M
openai/gpt-5.4-mini 400K
openai/gpt-5.4-nano 1.05M
openai/gpt-5.3-codex 400K Coding/agentic SKU
openai/gpt-5.2 400K
openai/gpt-5.2-pro 400K
openai/gpt-5.1 400K Configurable reasoning effort
openai/gpt-5-mini 200K
openai/gpt-4.1 128K
openai/gpt-4.1-mini 128K
openai/gpt-4.1-nano 128K
openai/gpt-4o 128K
openai/gpt-4o-mini 128K
openai/o1 200K Reasoning
openai/o3 200K Reasoning
openai/o3-mini 128K Reasoning
openai/o4-mini 128K Reasoning

Anthropic Claude

Model Context Notes
anthropic/claude-fable-5.1 1M Most capable — successor to Fable 5 at the same tier and price
anthropic/claude-fable-5 1M Mythos-class flagship above Opus — always-on thinking, 128K output
anthropic/claude-opus-5.5 1M Newest Opus — Opus-class reasoning at a lower price than Opus 5
anthropic/claude-opus-5 1M Flagship — the baseline the routing savings claim is measured against
anthropic/claude-opus-4.8 1M Agentic coding + adaptive thinking, 128K output
anthropic/claude-opus-4.7 1M Router-core's per-request savings baseline (priced like Opus 5)
anthropic/claude-opus-4.5 200K
anthropic/claude-sonnet-5.5 1M Newest Sonnet — everyday coding and agent work, 128K output
anthropic/claude-sonnet-5 1M Best cost/quality balance for long-context agent turns
anthropic/claude-sonnet-4.6 1M
anthropic/claude-sonnet-4.5 200K
anthropic/claude-haiku-4.5 200K

Google Gemini

Model Context
google/gemini-3.1-pro 1M
google/gemini-3.8-flash 1M
google/gemini-3.6-flash 1M
google/gemini-3.5-flash 1M
google/gemini-3-flash-preview 1M
google/gemini-3.5-flash-lite 1M
google/gemini-3.1-flash-lite 1M
google/gemini-2.5-pro 1M
google/gemini-2.5-flash 1M
google/gemini-2.5-flash-lite 1M

DeepSeek

DeepSeek upstream serves the legacy deepseek-chat / deepseek-reasoner aliases as V4 Flash non-thinking / thinking modes. V4 Pro is the flagship paid SKU; the vision SKU is an experimental preview.

Model Context Notes
deepseek/deepseek-v4-pro 1M V4 flagship — strongest open-weight reasoner
deepseek/deepseek-v4-flash-vision-exp 1M Experimental vision preview
deepseek/deepseek-chat 1M V4 Flash non-thinking
deepseek/deepseek-reasoner 1M V4 Flash thinking (same upstream, thinking on by default)

xAI Grok

The older Grok chat SKUs (grok-3/3-mini, the grok-4 fast families, grok-code-fast-1, grok-2-vision) have left the catalog. Retired ids stay callable — the gateway redirects them to a healthy model.

Model Context Notes
xai/grok-4.7 500K Flagship — reasoning + vision, selectable effort (low → xhigh), native Live Search (search: true)
xai/grok-4.6 500K Reasoning with selectable effort, native Live Search
xai/grok-4.5 500K Reasoning + vision, native Live Search
xai/grok-4.3 1M Reasoning + vision, tuned for agentic workflows
xai/grok-build-0.1 256K Fast agentic coding model

Moonshot, MiniMax, Z.ai, Qwen

Model Context Notes
moonshot/kimi-k3 1M Replaces the retired kimi-k2.5 / k2.6 SKUs
minimax/minimax-m3 1M
minimax/minimax-m2.7 200K
zai/glm-5.3 1M
zai/glm-5.3-flash 1M Low-cost vision-capable GLM
zai/glm-5.2 1M
zai/glm-5.1 200K
zai/glm-5 200K
zai/glm-5-turbo 200K
qwen/qwen3.8-max 1M
qwen/qwen3.7-max 1M
qwen/qwen3.7-plus 1M
qwen/qwen3.8-flash 1M
qwen/qwen3.7-flash 1M Cheapest paid chat model in the catalog

Mistral, Tencent, Poolside

Model Context Notes
mistral/mistral-large-4 512K Reasoning + vision, 256K output
tencent/hy4-preview 1M Preview
poolside/laguna-s-2.1 1M Paid coding model (sibling of the free XS)

Xiaomi

Model Context
xiaomi/mimo-v2.5 1M
xiaomi/mimo-v2.5-pro 1M

Testnet Models (Base Sepolia)

Model
openai/gpt-oss-20b
openai/gpt-oss-120b

Testnet models use flat pricing (no token counting) for simplicity.

BlockrunClient — the universal primitive

BlockrunClient speaks to any BlockRun endpoint, so new endpoints work without an SDK release. It takes an account apiKey or an EVM wallet key (BLOCKRUN_WALLET_KEY or BASE_CHAIN_WALLET_KEY); its wallet mode pays on Base / Arc only and refuses a solana: 402, so Solana wallets use SolanaLLMClient.

import { BlockrunClient } from '@blockrun/llm';

const br = new BlockrunClient();

// Sync GET: crypto price
const btc = await br.get('/v1/crypto/price/BTC-USD');

// Sync POST: live web search
const res = await br.post('/v1/search', { query: 'x402 adoption', sources: ['web'] });

// Submit + poll: long-running video generation (settled only on completion)
const video = await br.poll('/v1/videos/generations', {
  model: 'xai/grok-imagine-video',
  prompt: 'a red apple spinning',
});

// Streaming SSE: chat completions
type ChatChunk = { choices?: { delta?: { content?: string } }[] };
for await (const chunk of br.stream<ChatChunk>('/v1/chat/completions', {
  model: 'anthropic/claude-sonnet-5',
  messages: [{ role: 'user', content: 'Hi' }],
  stream: true,
})) {
  process.stdout.write(chunk.choices?.[0]?.delta?.content ?? '');
}

Four call shapes cover every endpoint type:

  • get<T>(path, params?) — synchronous GET (price, list, history)
  • post<T>(path, body?) — synchronous POST (search, RPC)
  • poll<T>(path, body?, { budgetMs, intervalMs }) — submit + poll (image, video, music)
  • stream<T>(path, body?) — async iterator over SSE chunks (chat)

The per-API clients below remain fully supported; BlockrunClient is the generic escape hatch for endpoints without a typed wrapper.

Image Generation and Editing

Model Notes
openai/gpt-image-1 Native GPT-4o image generation; also edits
openai/gpt-image-2 Reasoning-driven — multilingual text rendering, character consistency; also edits (the default)
openai/gpt-image-2.5-flare GPT Image 2.5 — fast, everyday quality
openai/gpt-image-2.5-sunburst GPT Image 2.5 — most capable; also edits (incl. mask)
google/nano-banana Gemini 2.5 Flash image generation — fast and efficient; also edits
google/nano-banana-2 Gemini 3.1 Flash — pro-level quality at Flash speed; also edits
google/nano-banana-pro Gemini 3 Pro — highest quality, up to 4K; also edits
xai/grok-imagine-image Fast, 300 RPM
xai/grok-imagine-image-2.0 Between Grok Imagine and Pro
xai/grok-imagine-image-pro Quality tier, 30 RPM
bytedance/seedream-5-pro Flagship generation + editing, up to 4K-class, reference images
zai/cogview-4 Up to 1440x1440

ImageClient.edit() calls /v1/images/image2image with the edit-capable models marked above. Source images must be base64 data:image/... URIs (not URLs). Pass one to edit it, or an array of 2–4 to fuse them (e.g. a subject and a brand logo): openai/* takes up to 4 sources, google/* up to 3. A mask is OpenAI-only and cannot be combined with multiple sources. LLMClient.imageEdit() and SolanaLLMClient.imageEdit() take the same arguments.

import { ImageClient } from '@blockrun/llm';

const client = new ImageClient();

const generated = await client.generate('a lighthouse at dusk, oil painting', {
  model: 'google/nano-banana',
});
console.log(generated.data[0].url);

const photoDataUri = 'data:image/png;base64,...';
const subjectDataUri = 'data:image/png;base64,...';
const logoDataUri = 'data:image/png;base64,...';

// Edit one image
const edited = await client.edit('Make the sky purple and add northern lights', photoDataUri, {
  model: 'openai/gpt-image-2',
});

// Fuse several images
const fused = await client.edit('Place the logo on the t-shirt', [subjectDataUri, logoDataUri], {
  model: 'google/nano-banana',
});
console.log(fused.data[0].url);

Video Generation

Model Default Max Notes
xai/grok-imagine-video 8s 15s 480p default, 720p available; text or image to video
xai/grok-imagine-video-1.5 8s 15s Native synced audio; 480p default, 720p / 1080p available
bytedance/seedance-1.5-pro 5s 12s Budget, up to 1080p, synced audio. No RealFace
bytedance/seedance-2.0-fast 5s 15s 480p/720p, ~60-80s to generate. RealFace supported
bytedance/seedance-2.0-mini 5s 15s 480p/720p, lowest-cost Seedance 2.0. RealFace supported
bytedance/seedance-2.0 5s 15s Premium, up to 4K, synced audio. RealFace supported
bytedance/seedance-2.5 5s 30s Long-form — up to 30s, 480p/720p, multilingual, up to 30 reference images
azure/sora-2 4s 12s Sora 2 via Azure AI Foundry — 720p with synced audio; 4, 8 or 12s
import { VideoClient } from '@blockrun/llm';

const client = new VideoClient();
const result = await client.generate('a red apple slowly spinning on a wooden table');
console.log(result.data[0].url);              // permanent MP4 URL
console.log(result.data[0].duration_seconds); // 8

// Image-to-video
const fromImage = await client.generate('the subject turns and smiles', {
  imageUrl: 'https://example.com/portrait.jpg',
});

// Seedance options (each is model-gated: an unsupported one is a 400 before payment)
const seedance = await client.generate('aerial drone shot over a snowy mountain', {
  model: 'bytedance/seedance-2.0',
  aspectRatio: '21:9',
  resolution: '1080p',
  generateAudio: true,    // omit to use the model's default
  seed: 42,
  returnLastFrame: true,  // useful for clip chaining
});
console.log(seedance.data[0].last_frame_url); // present when the upstream returns it

Seedance also supports first-and-last-frame interpolation (lastFrameUrl), output controls (bitrateMode, outputFormat, cameraFixed), and reference images, video and audio (referenceImageUrls, referenceVideos, referenceAudios). Reference media is served by api.blockrun.ai, so it needs an account API key; the wallet gateways refuse it with a 400 before any payment. Each option's model support and limits are documented on VideoGenerateOptions in the TSDoc.

Music Generation

MusicClient wraps POST /v1/audio/generations with minimax/music-2.5+ (the default). A track takes 1–3 minutes; the returned URL is valid for about 24 hours, so download it.

import { MusicClient } from '@blockrun/llm';

const client = new MusicClient();

const instrumental = await client.generate('chill lo-fi beats with piano');
console.log(instrumental.data[0].url);

// With vocals: pass lyrics and turn instrumental off
const song = await client.generate('upbeat pop song', {
  instrumental: false,
  lyrics: 'Hello world, this is my song...',
});

Text-to-Speech & Sound Effects

SpeechClient wraps BlockRun Voice: POST /v1/audio/speech (OpenAI-compatible TTS), POST /v1/audio/sound-effects, and the free GET /v1/audio/voices. The ElevenLabs TTS models are priced by character count ((chars / 1000) × model rate, with a per-request minimum); Seed Audio is priced by output duration. Synthesis is synchronous (<1s for Flash).

Model Max Input Notes
elevenlabs/flash-v2.5 40k chars ~75ms latency, 32 languages (default)
elevenlabs/turbo-v2.5 40k chars ~250ms latency, balanced quality
elevenlabs/multilingual-v2 10k chars Long-form narration, audiobooks — 29 languages
elevenlabs/v3 5k chars Max expressiveness, 70+ languages
bytedance/seed-audio-1.0 3k chars Prompt-directed — describe voice, emotion and staging in words; billed by output duration (up to 120s)
elevenlabs/sound-effects 1k chars Sound effects up to 22s
import { SpeechClient } from '@blockrun/llm';

const client = new SpeechClient();

// Text-to-speech (voice aliases: sarah, george, laura, charlie,
// river, roger, callum, harry — or any raw ElevenLabs voice_id)
const result = await client.generate('Welcome to BlockRun.', { voice: 'george' });
console.log(result.data[0].url);  // audio URL (mp3 by default)

// Other formats / speed
const wav = await client.generate('Breaking news from the world of micropayments.', {
  model: 'elevenlabs/v3',
  responseFormat: 'wav',
  speed: 1.1,
});

// Sound effects (flat per generation)
const fx = await client.soundEffect('rain on a tin roof, distant thunder');

// List voices (free, rate-limited)
const voices = await client.listVoices();

Virtual Portraits

PortraitClient wraps POST /v1/portrait/enroll (paid, flat promo rate, no KYC). Enroll a face image by URL and get back a Token360 asset id (ta_xxxxxx). Pass that id as realFaceAssetId on a Seedance 2.0 video generation to keep the same AI character across clips. Payment settles only after Token360 confirms the enrollment, so a failed enrollment never charges your wallet. The returned image_url is a gateway-mirrored copy of your source image (see mirrored / source_image_url). (Real-person likeness is not supported on BlockRun — enrolled portraits are AI characters.)

import { PortraitClient, VideoClient } from '@blockrun/llm';

const portraits = new PortraitClient();
const { asset_id } = await portraits.enroll({
  name: 'Spokesperson',
  imageUrl: 'https://example.com/face.jpg',  // public https JPG/PNG/WEBP, ≤10 MB
});

// Reuse the same character across Seedance 2.0 clips
const video = new VideoClient();
const clip = await video.generate('she waves and smiles', {
  model: 'bytedance/seedance-2.0-fast',
  realFaceAssetId: asset_id,
});
console.log(clip.data[0].url);

Voice Calls and Phone Numbers

VoiceClient wraps POST /v1/voice/call (paid, flat per call) and GET /v1/voice/call/{callId} (free polling) — AI-powered outbound phone calls powered by Bland.ai. The agent dials the recipient and runs a real-time conversation based on your task instructions. US + Canada destinations.

import { VoiceClient } from '@blockrun/llm';

const client = new VoiceClient();

// Initiate (paid)
const result = await client.call({
  to: '+14155552671',
  task: 'You are a friendly assistant calling to confirm a 3pm dentist appointment.',
  voice: 'maya',     // 'nat' | 'josh' | 'maya' | 'june' | 'paige' | 'derek' | 'florian'
  max_duration: 5,   // minutes (1–30)
});
console.log(result.call_id);

// Poll for transcript + recording (free)
const status = await client.getStatus(result.call_id);
console.log(status.status, status.recording_url);

PhoneClient covers phone lookups and numbers: lookup() (carrier and line type) and lookupFraud() (adds SIM-swap and call-forwarding signals), plus buyNumber({ country, areaCode }) for a 30-day US/CA number, renewNumber(), listNumbers() and releaseNumber(). A number you own can be the caller ID of a voice call: pass it as from.

import { PhoneClient } from '@blockrun/llm';

const phone = new PhoneClient();
const info = await phone.lookup('+14155552671');
const { phone_number } = await phone.buyNumber({ country: 'US', areaCode: '415' });

Standalone Search

SearchClient wraps POST /v1/search — Grok Live Search over web and news, at a flat price per call. For X/Twitter search, use chat on an xai/* model with an x_search tool or searchParameters: { sources: [{ type: 'x' }] } (see Solana Support for an example).

import { SearchClient } from '@blockrun/llm';

const client = new SearchClient();
const result = await client.search('Latest news on x402 adoption', {
  sources: ['web', 'news'],
  maxResults: 10,
});
console.log(result.summary);
for (const c of result.citations ?? []) console.log(c.url);

The same endpoint is available as search() on LLMClient and SolanaLLMClient.

Market Data (Pyth)

PriceClient wraps the Pyth-backed market-data endpoints. Crypto, FX and commodity are free (price + history + list); 12 global stock markets and the usstock legacy alias are billed per call for price + history (list is always free). Pass requireWallet: false to construct a free-only client.

import { PriceClient } from '@blockrun/llm';

const p = new PriceClient({ requireWallet: false });
const btc = await p.price('crypto', 'BTC-USD');
const eur = await p.price('fx', 'EUR-USD');
const symbols = await p.listSymbols('crypto', { query: 'sol', limit: 20 });

// Paid: requires an API key or a wallet
const p2 = new PriceClient();
const aapl = await p2.price('stocks', 'AAPL', { market: 'us' });
const bars = await p2.history('stocks', 'AAPL', {
  market: 'us',
  resolution: 'D',
  from: 1700000000,
  to: 1710000000,
});

Supported StockMarket values: us, hk, jp, kr, gb, de, fr, nl, ie, lu, cn, ca.

Prediction Markets (Powered by Predexon)

Prediction-market data from Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, Binance Futures, UMA oracle resolutions and sports markets via Predexon. Every endpoint costs the same flat price per call ($0.0085 at the time of writing). Use an account API key or x402 wallet payments; no separate Predexon key is needed. pm() works on both LLMClient and SolanaLLMClient; query parameters are strings.

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();

// Polymarket
const markets = await client.pm('polymarket/markets', { status: 'active', limit: '10' });
const events = await client.pm('polymarket/events');
const candles = await client.pm('polymarket/candlesticks/0x1234abcd...');
const profile = await client.pm('polymarket/wallet/0xABC123...');
const pnl = await client.pm('polymarket/wallet/pnl/0xABC123...');
const leaderboard = await client.pm('polymarket/leaderboard');

// Kalshi and Binance Futures
const kalshiMarkets = await client.pm('kalshi/markets');
const btcCandles = await client.pm('binance/candles/BTCUSDT');

// Cross-venue search (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun)
const found = await client.pm('markets/search', { q: 'bitcoin' });

// POST endpoints take a JSON body
const identities = await client.pmQuery('polymarket/wallet/identities', {
  addresses: ['0xabc...', '0xdef...'],
});

Exa Web Search (Powered by Exa)

Exa's neural web search, paid with account credit or x402; no separate Exa key is needed. Use LLMClient for a Base wallet or SolanaLLMClient for a Solana wallet.

Method Description
exaSearch(query, options?) Neural/keyword web search
exaFindSimilar(url, options?) Find semantically similar pages
exaContents(urls) Extract full text from URLs (SolanaLLMClient also accepts an options object)
exaAnswer(query) AI answer grounded in web search (SolanaLLMClient also accepts an options object)
exa(path, body) Generic proxy for any Exa endpoint

On LLMClient the typed helpers return the result payload (the response's data) with typed fields; exa() returns the raw response envelope.

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();

const results = await client.exaSearch('latest AI safety research', { numResults: 5 });
for (const r of results.results) console.log(r.title, r.url);

const news = await client.exaSearch('bitcoin ETF news', { category: 'news', numResults: 10 });
const similar = await client.exaFindSimilar('https://openai.com/research/gpt-4', { numResults: 5 });
const content = await client.exaContents(['https://arxiv.org/abs/2303.08774']);
const answer = await client.exaAnswer('What is the current state of AI safety research?');

// Generic proxy for any Exa endpoint
const custom = await client.exa('search', { query: 'transformer architecture', numResults: 5 });

DeFi Data, DEX Swaps & Cloud Compute

Three passthrough families live directly on LLMClient and SolanaLLMClient:

import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();

// DefiLlama: protocols, TVL, yields, prices
const aave = await client.defiProtocol('aave');

// 0x DEX quotes (free: BlockRun takes an on-chain affiliate fee on executed swaps instead)
const quote = await client.dexQuote({
  chainId: '8453', sellToken: '0x...', buyToken: '0x...',
  sellAmount: '1000000', taker: '0xYourWallet',
});

// Modal: sandboxed CPU / GPU compute
const sb = await client.modalSandboxCreate({ image: 'python:3.11' });

More helpers: defiProtocols(), defiChains(), defiYields(), defiPrices(); dexPrice(), dexGaslessQuote(), dexGaslessSubmit(), dexGaslessStatus(); modalSandboxExec(), modalSandboxStatus(), modalSandboxTerminate(). Generic escape hatches: defi(path, params), dex(path, params, body?), modal(path, body).

Multi-chain RPC

RpcClient wraps POST /v1/rpc/{network} — standard JSON-RPC 2.0 access to

40 chains through one endpoint (Ethereum, Base, Solana, Polygon, BSC,

Arbitrum, Optimism, Avalanche, Bitcoin, Sui, and more; powered by Tatum's RPC gateway). Use account credit or an EVM wallet; no separate Tatum key is needed. A JSON-RPC batch is priced per element.

import { RpcClient } from '@blockrun/llm';

const client = new RpcClient();

// EVM chains speak eth_* JSON-RPC
const block = await client.call('ethereum', 'eth_blockNumber');
console.log(parseInt(block.result as string, 16));

// Non-EVM chains speak their native JSON-RPC
const slot = await client.call('solana', 'getSlot');

// Batch: one payment, per-element pricing (rate x N)
const out = await client.batch('polygon', [
  { method: 'eth_blockNumber' },
  { method: 'eth_gasPrice' },
]);

console.log(block.network);   // 'ethereum' (canonical key from X-Network)
console.log(block.cacheHit);  // true if served from the gateway's hot cache
console.log(block.txHash);    // x402 settlement tx

The curated chains are exported as SUPPORTED_NETWORKS and common aliases (eth, arb, sol, btc, …) as NETWORK_ALIASES; unknown but well-formed slugs fall through to a generic {slug}-mainnet attempt, so new chains work without an SDK update.

OpenAI and Anthropic Compatibility

OpenAI

OpenAI mirrors the openai SDK's chat.completions.create() on top of LLMClient, so the blockrun/auto|eco|premium aliases work. Streaming is shown under Streaming.

import { OpenAI } from '@blockrun/llm';

const client = new OpenAI(); // BLOCKRUN_API_KEY, or { walletKey: '0x...' }, or { apiKey }

const completion = await client.chat.completions.create({
  model: 'blockrun/auto',
  messages: [{ role: 'user', content: 'Hello!' }],
});
console.log(completion.choices[0].message.content);

Anthropic

AnthropicClient wraps the official @anthropic-ai/sdk with a custom fetch that handles payment. It uses BLOCKRUN_API_KEY when set; otherwise it signs with your wallet, creating one if none exists (the same getOrCreateWallet() resolution as Wallets). Your private key never leaves your machine.

import { AnthropicClient } from '@blockrun/llm';

const client = new AnthropicClient();

const response = await client.messages.create({
  model: 'claude-sonnet-4-6',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello!' }],
});
console.log(response.content[0]);

// Any model works in Anthropic format
const gptResponse = await client.messages.create({
  model: 'openai/gpt-5.4',
  max_tokens: 1024,
  messages: [{ role: 'user', content: 'Hello from GPT!' }],
});

This wrapper proxies straight to the gateway's /v1/messages and does not resolve blockrun/* aliases; pass a concrete model id. Anthropic's own dashed ids resolve, including the Mythos-class claude-fable-5 and claude-fable-5-1 / claude-fable-5.1 (1M context, always-on thinking).

Wallets

Agent setup

One call for agent runtimes (Claude Code skills, MCP servers, …):

import { setupAgentClient, setupAgentWallet, setupAgentSolanaWallet, status } from '@blockrun/llm';

// API key when configured; otherwise a saved ~/.blockrun/payment-chain preference,
// an existing Base-only wallet stays on Base, and new wallets are created on Solana.
const client = await setupAgentClient();
const r = await client.smartChat('Hello!');

// Chain-specific: create the wallet if none exists and return a ready client
const base = setupAgentWallet();                  // LLMClient (sync)
const solana = await setupAgentSolanaWallet();    // SolanaLLMClient

// Base wallet address and balance
await status();
// Wallet: 0xCC8c...5EF8
// Balance: $5.30 USDC

getOrCreateWallet() resolves the Base wallet in a fixed order: BLOCKRUN_WALLET_KEY or BASE_CHAIN_WALLET_KEY, then ~/.blockrun/.session, then the legacy ~/.blockrun/wallet.key. LLMClient reads an explicit privateKey, then BASE_CHAIN_WALLET_KEY, then BLOCKRUN_WALLET_KEY. For a single run without changing anything, use export BLOCKRUN_WALLET_KEY=<private-key>.

Wallet discovery and migration

The SDK can discover compatible wallets other applications wrote, for an explicit, user-confirmed migration. It never automatically makes a discovered wallet active.

import { listDiscoveredWallets, importWallet, listDiscoveredSolanaWallets } from '@blockrun/llm';

for (const w of listDiscoveredWallets()) {
  console.log(w.address, 'from', w.source); // addresses only, never private keys
}

importWallet('0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f');

// Solana: the same pair, async
const solWallets = await listDiscoveredSolanaWallets();

scanWallets() and scanSolanaWallets() also exist, but they return the discovered private keys; prefer the listDiscovered* functions unless you are writing a migration tool.

importWallet() writes your current wallet to ~/.blockrun/.session.backup-<timestamp> before switching, so adopting a wallet never strands funds in the old one. For Solana, use await importSolanaWallet(address). Addresses shown are derived from the discovered key itself, and importWallet() matches on that derived address, so a wallet file cannot claim an address it cannot sign for, nor be adopted by one.

Earlier versions adopted the most recently written provider wallet automatically. If you relied on that, the first run after upgrading creates a fresh BlockRun wallet and prints the addresses it found, so you can import the one you actually own:

NOTICE: BlockRun created a new wallet, but also found existing wallet(s)
belonging to other applications on this system:

  0x88f9B82462f6C4bf4a0Fb15e5c3971559a316e7f
...

One wallet across every BlockRun product

Base wallet resolution, discovery, and adoption are implemented in @blockrun/core, the shared kernel this SDK, the blockrun CLI, and clawrouter-codex all read, so they agree on which wallet is yours. The kernel is bundled into the SDK at build time (frozen, reviewed bytes, no floating dependency), so there is nothing extra to install.

Set BLOCKRUN_HOME to override the base directory (~ by default) for test isolation; unset, behaviour is unchanged. Treat BLOCKRUN_HOME as security-sensitive: it redirects where the signing key is read from and written to, so an environment that can set it controls the wallet as surely as one that can set BLOCKRUN_WALLET_KEY. Set it before importing the SDK — the exported WALLET_FILE_PATH / WALLET_DIR_PATH constants snapshot at import (all internal reads and writes resolve per call). Solana wallet resolution, the response cache and the cost log do not honour BLOCKRUN_HOME.

Configuration and Environment Variables

import { LLMClient } from '@blockrun/llm';

// Default: BLOCKRUN_API_KEY, else BASE_CHAIN_WALLET_KEY, from the environment
const client = new LLMClient();

// Or pass options explicitly
const configured = new LLMClient({
  privateKey: '0x...',                // wallet key, used only for local signing
  // apiKey: 'brk_live_...',          // or account billing (not both)
  apiUrl: 'https://blockrun.ai/api',  // optional; https://arc.blockrun.ai/api for Arc
  timeout: 600_000,                   // optional, ms (default 600000)
});
Variable Description
BLOCKRUN_API_KEY Account key (brk_...): account billing, no wallet needed. Create one at user.blockrun.ai/dashboard/keys
BLOCKRUN_API_BASE_URL Account API endpoint (default https://api.blockrun.ai)
BASE_CHAIN_WALLET_KEY EVM wallet private key for LLMClient (Base / Arc) and every EVM client
BLOCKRUN_WALLET_KEY EVM wallet key read by every EVM client. The service clients, BlockrunClient, setupAgentWallet() and AnthropicClient check it before BASE_CHAIN_WALLET_KEY; LLMClient checks it after, so a process holding both keeps paying from the same wallet
SOLANA_WALLET_KEY Solana secret key, bs58-encoded, for SolanaLLMClient
SOLANA_RPC_URL Solana RPC for payments and balance (also rpcUrl)
SOLANA_RPC_API_KEY / SOLANA_RPC_HEADERS Header auth for that RPC (also rpcHeaders)
BASE_RPC_URL Base RPC tried first by LLMClient.getBalance()
BLOCKRUN_CHAT_TIMEOUT LLMClient default timeout, in seconds (default 600)
BLOCKRUN_HOME Base directory for the Base wallet files (security-sensitive, see Wallets)

BLOCKRUN_SOL_OPERATOR is not an environment variable or a secret. It is an SDK export used for the explicit batch-settlement trust configuration.

Account mode details

  • Base URL. Account clients use https://api.blockrun.ai; an OpenAI-style https://api.blockrun.ai/v1 base is also accepted. Override with apiUrl (baseURL on OpenAI) or BLOCKRUN_API_BASE_URL.
  • Origin pinning. The key is only ever sent to the configured origin, including when following a job's poll_url; redirects are refused.
  • Retries. Account POSTs are never replayed automatically, including through the Anthropic wrapper: the first POST is the billed one. GET/HEAD requests retry temporary gateway errors (502/503/504/522/524). A 429 returns retryAfter to the caller.
  • Async jobs. Accepted jobs keep polling the same job within the original timeout. Keep the complete returned poll_url, including its query parameters; if polling times out, check the job and Activity before submitting again.
  • Wallet-only helpers. getWalletAddress(), getBalance() and getSpending() throw in account mode; account usage is on Activity. Account credit does not sign trades or transfer wallet funds.
  • Switching back to a wallet. Unset BLOCKRUN_API_KEY and create a new client, or pass an explicit privateKey. Existing clients keep the credentials they were built with.

Local cache helpers

No client caches responses automatically. cache.ts exports manual helpers for your own dedup: getCachedByRequest(endpoint, body), saveToCache(endpoint, body, response, costUsd?), getCached(key), setCache(key, data, ttlMs) and clearCache(). TTLs by endpoint: /v1/search 15 minutes, /v1/pm/ 30 minutes, /v1/models 24 hours, /v1/partner/ 1 hour, chat and image never, anything else 1 hour. saveToCache() also archives a readable copy under ~/.blockrun/data/ and appends to ~/.blockrun/cost_log.jsonl.

Error Handling

import { LLMClient, APIError, PaymentError, retryDisposition } from '@blockrun/llm';

const client = new LLMClient();

try {
  const response = await client.smartChat('Hello!');
} catch (error) {
  if (error instanceof PaymentError) {
    // Wallet mode: signing or settlement failed. Check the USDC balance.
    console.error(`Payment failed: ${error.message}`);
  } else if (error instanceof APIError) {
    if (error.statusCode === 402) {
      console.error('Account credit exhausted: top up at https://user.blockrun.ai');
    } else if (error.statusCode === 429) {
      console.error(`Rate limited; retry after ${error.retryAfter ?? 'a moment'}`);
    } else {
      console.error(`API error ${error.statusCode}: ${error.message}`);
    }
  }
  if (retryDisposition(error) === 'unpaid') {
    // nothing was charged: safe to retry
  }
}

In account mode an exhausted or missing credit balance is an APIError with statusCode 402, never a PaymentError. Errors keep the account error's response.code, and the key is redacted from error bodies. Before retrying a failed call yourself, check retryDisposition(error) (see Automatic Fallback on Transient Errors): anything but 'unpaid' may already have been charged.

Testing

npm test                                           # vitest in watch mode
npx vitest run --exclude 'test/integration/**'     # unit tests once, no network

Unit tests do not need API access or a funded wallet. Plain npm test also runs test/integration, which makes real paid calls against production when BASE_CHAIN_WALLET_KEY or SOLANA_WALLET_KEY is set in your environment (and is skipped when it is not). To run the integration suite on purpose, use a funded test wallet ($1+ of USDC) and:

export BASE_CHAIN_WALLET_KEY=0x...
npx vitest run test/integration

Security

  • Keys stay local. A wallet key only signs on your machine; only the signature is sent.
  • Use a dedicated payment wallet funded with small amounts, separate from your main holdings.
  • 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.
  • Rotate API keys and wallet keys periodically.
  • Verify on-chain. Wallet payments are on-chain transfers you can check on an explorer:
import { LLMClient } from '@blockrun/llm';

const client = new LLMClient();
console.log(`View transactions: https://basescan.org/address/${client.getWalletAddress()}`);

The SDK validates max_tokens ranges, EVM private-key format, and API URLs (HTTPS required, except for localhost) before sending a request. Error bodies are sanitized, and API keys are redacted from them. Run npm update @blockrun/llm to pick up security fixes.

Frequently Asked Questions

What is @blockrun/llm?

A TypeScript SDK that cuts LLM costs by 84% on a realistic workload with built-in smart routing: every request goes to the cheapest of 88 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.

What is smart routing?

Router Core V3, bundled into the SDK: the same deterministic engine that powers ClawRouter. It analyzes each request across 15 dimensions locally, in under 1ms, and picks the cheapest capable model. Use smartChat(), smartChatCompletion(), or the blockrun/auto model alias. See Smart Routing.

Does it support streaming?

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.

How much does it cost?

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.

Do I need a crypto wallet?

No. An account API key from user.blockrun.ai works without any wallet or chain. Wallets are for x402 pay-per-request on Solana (SolanaLLMClient, recommended), Base or Arc (LLMClient).

License

MIT


If the router just cut your bill, give it a star ⭐ — it helps more agents pay less.

Website · Models & Pricing · ClawRouter · Python SDK · Telegram

About

TypeScript SDK for BlockRun — every frontier model behind one OpenAI-compatible client. Pay with a BlockRun API key or per-call in USDC. Smart routing picks the cheapest capable model. Zero rate limits.

Topics

Resources

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages