@blockrun/llm
Version:
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. No rate limits.
4,550 lines • 195 kB
text/typescript
import * as _anthropic_ai_sdk from '@anthropic-ai/sdk';
/**
* Smart Router Types
*
* Four classification tiers — REASONING is distinct from COMPLEX because
* reasoning tasks need different models (o3, gemini-pro) than general
* complex tasks (gpt-4o, sonnet-4).
*
* Scoring uses weighted float dimensions with sigmoid confidence calibration.
*/
type Tier = "SIMPLE" | "MEDIUM" | "COMPLEX" | "REASONING";
type RoutingDecision$1 = {
model: string;
tier: Tier;
confidence: number;
method: "rules" | "llm" | "portfolio";
reasoning: string;
costEstimate: number;
baselineCost: number;
savings: number;
agenticScore?: number;
/** Which tier configs were used (auto/eco/premium/agentic) — avoids re-derivation in the host */
tierConfigs?: Record<Tier, TierConfig>;
/** Which routing profile was applied */
profile?: "auto" | "eco" | "premium" | "agentic";
/** Ordered, capability-eligible candidates. The first entry is `model`. */
candidates?: string[];
/** Explainable request classification used by the portfolio router. */
taskType?: TaskType;
/** Router implementation that made the selection. */
routerVersion?: "v2-rules" | "v3-portfolio";
/** Explainable local portfolio score breakdown, ordered with `candidates`. */
candidateScores?: Array<{
model: string;
score: number;
quality: number;
cost: number;
speed: number;
reliability: number;
}>;
};
type TaskType = "chat" | "extraction" | "code_edit" | "code_agent" | "tool_agent" | "tool_agent_parallel" | "debug" | "reasoning" | "reasoning_mcq" | "reasoning_math" | "long_context" | "vision";
type TierConfig = {
primary: string;
fallback: string[];
};
type ScoringConfig = {
tokenCountThresholds: {
simple: number;
complex: number;
};
codeKeywords: string[];
reasoningKeywords: string[];
simpleKeywords: string[];
technicalKeywords: string[];
creativeKeywords: string[];
imperativeVerbs: string[];
constraintIndicators: string[];
outputFormatKeywords: string[];
referenceKeywords: string[];
negationKeywords: string[];
domainSpecificKeywords: string[];
agenticTaskKeywords: string[];
dimensionWeights: Record<string, number>;
tierBoundaries: {
simpleMedium: number;
mediumComplex: number;
complexReasoning: number;
};
confidenceSteepness: number;
confidenceThreshold: number;
};
type ClassifierConfig = {
llmModel: string;
llmMaxTokens: number;
llmTemperature: number;
promptTruncationChars: number;
cacheTtlMs: number;
};
type OverridesConfig = {
maxTokensForceComplex: number;
structuredOutputMinTier: Tier;
ambiguousDefaultTier: Tier;
/**
* When enabled, prefer models optimized for agentic workflows.
* Agentic models continue autonomously with multi-step tasks
* instead of stopping and waiting for user input.
*/
agenticMode?: boolean;
};
/**
* Time-windowed promotion that temporarily overrides tier routing.
* Active promotions are auto-applied; expired ones are ignored at runtime.
*/
type Promotion = {
/** Human-readable label (e.g. "GLM-5 Launch Promo") */
name: string;
/** ISO date string, promotion starts (inclusive). e.g. "2026-04-01" */
startDate: string;
/** ISO date string, promotion ends (exclusive). e.g. "2026-04-15" */
endDate: string;
/** Partial tier overrides — merged into the active tier configs (primary/fallback) */
tierOverrides: Partial<Record<Tier, Partial<TierConfig>>>;
/** Which profiles this applies to. Default: all profiles. */
profiles?: Array<"auto" | "eco" | "premium" | "agentic">;
};
type RoutingConfig = {
version: string;
/** Enables a one-line rollback to the established V2 rules selector. */
strategy?: "rules" | "portfolio";
/**
* Locally recompute a comparison strategy without changing the model that
* actually serves the request. The host emits only decision metadata via
* `onShadowRouted`; it never persists prompt content or makes a second call.
*/
shadow?: {
strategy: "rules" | "portfolio";
sampleRate?: number;
};
/** Calibratable local portfolio scoring weights; values are relative, not probabilities. */
portfolio?: {
auto: {
quality: number;
capability: number;
cost: number;
speed: number;
reliability: number;
legacy: number;
};
eco: {
quality: number;
capability: number;
cost: number;
speed: number;
reliability: number;
legacy: number;
};
premium: {
quality: number;
capability: number;
cost: number;
speed: number;
reliability: number;
legacy: number;
};
highStakesBoost: {
quality: number;
reliability: number;
};
latencySensitiveSpeedBoost: number;
/** A candidate materially below the best task affinity cannot win on cost alone. */
affinityFloorGap: {
auto: number;
eco: number;
premium: number;
};
};
classifier: ClassifierConfig;
scoring: ScoringConfig;
tiers: Record<Tier, TierConfig>;
/**
* Tier configs for agentic mode — models that excel at multi-step tasks.
* Set to `null` to disable agentic tier selection entirely (forces all
* requests through `tiers`, even when tools are present in the request).
*/
agenticTiers?: Record<Tier, TierConfig> | null;
/** Tier configs for eco profile — ultra cost-optimized (blockrun/eco). `null` falls back to `tiers`. */
ecoTiers?: Record<Tier, TierConfig> | null;
/** Tier configs for premium profile — best quality (blockrun/premium). `null` falls back to `tiers`. */
premiumTiers?: Record<Tier, TierConfig> | null;
/** Time-windowed promotions that temporarily override tier routing */
promotions?: Promotion[];
overrides: OverridesConfig;
};
interface FunctionDefinition {
name: string;
description?: string;
parameters?: Record<string, unknown>;
strict?: boolean;
}
interface Tool {
type: "function";
function: FunctionDefinition;
}
/**
* xAI's server-side X search, for xai/* models. The gateway runs it on xAI's
* Responses API; billed per X post fetched. https://docs.x.ai/developers/tools/x-search
*/
interface XSearchTool {
type: "x_search";
/** Only these handles (≤20, without "@"). Not together with excludedXHandles. */
allowedXHandles?: string[];
/** Never these handles (≤20). Not together with allowedXHandles. */
excludedXHandles?: string[];
/** YYYY-MM-DD, inclusive. */
fromDate?: string;
/** YYYY-MM-DD, inclusive. */
toDate?: string;
enableImageUnderstanding?: boolean;
enableVideoUnderstanding?: boolean;
}
/** xAI's server-side web search, for xai/* models. */
interface WebSearchTool {
type: "web_search";
/** ≤5 domains. */
allowedDomains?: string[];
excludedDomains?: string[];
enableImageUnderstanding?: boolean;
}
/** Anything `tools` accepts: function tools, plus xAI's search tools on xai/* models. */
type AnyTool = Tool | XSearchTool | WebSearchTool;
/** A URL citation with its position in the answer text (xAI search). */
interface UrlCitation {
type: "url_citation";
url: string;
title?: string;
start_index?: number;
end_index?: number;
}
/** How a paid call settled — see `ChatResponse.settlement`. */
interface Settlement {
/** `exact`: one transfer at the quote. `batch`: metered from a payment channel. */
scheme: "exact" | "batch" | "free" | "api-key";
/** What was signed or authorised for this call, in USD. */
quotedUsd: number;
/** On-chain transaction (signature / hash), when the gateway reported one. */
transaction?: string;
network?: string;
/** Present and false only when the gateway reported a failed settlement. */
success?: false;
/**
* Why the gateway served this paid request without settling it, from
* `X-Settlement-Skipped` (else `X-Payment-Waived`): `"free-fallback"` when a
* free fallback model answered, or another reason the gateway names. Nothing
* moved on chain, so `costUsd` is 0 and nothing was added to `getSpending()`.
* `quotedUsd` still states what was signed.
*/
skipped?: "free-fallback" | (string & {});
}
interface FunctionCall {
name: string;
arguments: string;
}
interface ToolCall {
id: string;
type: "function";
function: FunctionCall;
}
type ToolChoice = "none" | "auto" | "required" | {
type: "function";
function: {
name: string;
};
};
interface ChatMessage {
role: "system" | "user" | "assistant" | "tool";
content?: string | null;
name?: string;
tool_call_id?: string;
tool_calls?: ToolCall[];
reasoning_content?: string;
thinking?: string;
/** URL citations with positions (xAI search answers). */
annotations?: UrlCitation[];
}
interface ChatChoice {
index: number;
message: ChatMessage;
finish_reason?: "stop" | "length" | "content_filter" | "tool_calls";
}
interface ChatUsage {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
num_sources_used?: number;
cache_read_input_tokens?: number;
cache_creation_input_tokens?: number;
/**
* xAI search usage counts: x_search_calls, x_posts_fetched, x_users_fetched,
* web_search_calls … (what xAI bills the search by).
*/
server_side_tool_usage?: Record<string, number | undefined>;
}
interface ChatResponse {
id: string;
object: string;
created: number;
model: string;
choices: ChatChoice[];
usage?: ChatUsage;
citations?: string[];
/** xAI search: the searches the model ran (`{type, query, status}`). */
search_calls?: Array<{
type: string;
query?: string;
status?: string;
}>;
/**
* What this call cost you, in USD: the metered charge under batch
* settlement, the signed price under exact, 0 for a free call. Undefined in
* API-key mode (billed to the account, see the dashboard).
*/
costUsd?: number;
/** How the call was paid, with the on-chain transaction when reported. */
settlement?: Settlement;
/**
* Populated when the gateway transparently substituted a different
* model for the one the caller asked for — typically because the
* requested model errored and the gateway routed to a free fallback
* to fulfil the request. When `used` is true:
* - `model` is the model that actually answered (vs `ChatResponse.model`
* which historically reflected the requested model id).
* - `settlementSkipped` is `true` when the gateway also skipped the
* on-chain settle — i.e. the user was not charged for this call
* because a free fallback served it.
* Surfaced from the gateway's `X-Fallback-Used / X-Fallback-Model /
* X-Settlement-Skipped` response headers. Absent when the headers
* aren't present (most calls).
*/
fallback?: {
used: true;
model?: string;
settlementSkipped?: boolean;
};
/** Present when a `blockrun/auto|eco|premium` alias selected the model locally. */
routing?: RoutingDecision;
}
interface Model {
id: string;
name: string;
provider: string;
description: string;
/** Per 1M tokens. 0 when billingMode !== "paid". */
inputPrice: number;
/** Per 1M tokens. 0 when billingMode !== "paid". */
outputPrice: number;
contextWindow: number;
maxOutput: number;
categories: string[];
available: boolean;
type?: "llm" | "image";
/** One of "paid" (per-token), "flat" (flatPrice per request) or "free". */
billingMode?: "paid" | "flat" | "free";
/** Flat per-request price when billingMode === "flat". */
flatPrice?: number;
/** True for deprecated/superseded models that remain routable. */
hidden?: boolean;
}
interface ImageData {
url: string;
/** Original upstream URL (e.g. imgen.x.ai). Omitted for data URIs. */
source_url?: string;
/** True when the gateway mirrored the image to its GCS bucket. Omitted for data URIs. */
backed_up?: boolean;
revised_prompt?: string;
b64_json?: string;
}
interface ImageResponse {
created: number;
data: ImageData[];
}
interface ImageModel {
id: string;
name: string;
provider: string;
description: string;
pricePerImage: number;
supportedSizes?: string[];
maxPromptLength?: number;
available: boolean;
type?: "llm" | "image";
}
interface ImageClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 120000 for images) */
timeout?: number;
}
interface ImageGenerateOptions {
/** Model ID (default: "google/nano-banana") */
model?: string;
/** Image size (default: "1024x1024") */
size?: string;
/** Number of images to generate (default: 1) */
n?: number;
/** Image quality (for supported models) */
quality?: "standard" | "hd";
}
interface WebSearchSource {
type: "web";
country?: string;
excludedWebsites?: string[];
allowedWebsites?: string[];
safeSearch?: boolean;
enableImageUnderstanding?: boolean;
}
interface XSearchSource {
type: "x";
/** ≤20 handles. Not together with excludedXHandles. */
includedXHandles?: string[];
excludedXHandles?: string[];
/** Accepted for compatibility; xAI's x_search has no such filter. */
postFavoriteCount?: number;
/** Accepted for compatibility; xAI's x_search has no such filter. */
postViewCount?: number;
enableImageUnderstanding?: boolean;
enableVideoUnderstanding?: boolean;
}
interface NewsSearchSource {
type: "news";
country?: string;
excludedWebsites?: string[];
allowedWebsites?: string[];
safeSearch?: boolean;
}
interface RssSearchSource {
type: "rss";
links: string[];
}
type SearchSource = WebSearchSource | XSearchSource | NewsSearchSource | RssSearchSource;
interface SearchParameters {
mode?: "off" | "auto" | "on";
sources?: SearchSource[];
returnCitations?: boolean;
fromDate?: string;
toDate?: string;
maxSearchResults?: number;
/** Agentic search rounds xAI may run (1–10; gateway default 4). Bounds the per-post bill. */
maxTurns?: number;
}
/** Usage info for Live Search sources */
interface SearchUsage {
/** Number of search sources used in the response */
numSourcesUsed?: number;
}
interface Spending {
totalUsd: number;
calls: number;
}
/** Pre-request cost estimate for a chat call */
interface CostEstimate {
/** Model ID used for the estimate */
model: string;
/** Estimated input token count */
estimatedInputTokens: number;
/** Estimated output token count */
estimatedOutputTokens: number;
/** Estimated cost in USD */
estimatedCostUsd: number;
}
/** Per-call spending report with running session totals */
interface SpendingReport {
/** Model ID used */
model: string;
/** Input tokens consumed */
inputTokens: number;
/** Output tokens consumed */
outputTokens: number;
/** Cost of this call in USD */
costUsd: number;
/** Cumulative session spend in USD */
sessionTotalUsd: number;
/** Total number of calls in this session */
sessionCalls: number;
}
/** Chat response bundled with its spending report */
interface ChatResponseWithCost {
/** The chat completion response */
response: ChatResponse;
/** Spending report for this call */
spendingReport: SpendingReport;
}
interface ResourceInfo {
url: string;
description?: string;
mimeType?: string;
}
interface PaymentRequirement {
scheme: string;
network: string;
asset: string;
amount?: string;
maxAmountRequired?: string;
payTo: string;
maxTimeoutSeconds?: number;
extra?: {
name?: string;
version?: string;
};
}
interface PaymentRequired {
x402Version: number;
accepts: PaymentRequirement[];
resource?: ResourceInfo;
/** x402 v2 extensions the gateway declares, keyed by extension name. */
extensions?: Record<string, unknown>;
}
interface LLMClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x). Optional if BASE_CHAIN_WALLET_KEY (or BLOCKRUN_WALLET_KEY) is set. */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
timeout?: number;
}
/**
* OpenAI-compatible response format. `{ type: "json_object" }` enables JSON
* mode — honored across all providers by the gateway (native for OpenAI/Azure,
* emulated via a raw-JSON system instruction for Anthropic/Bedrock).
*/
type ResponseFormat = {
type: "text";
} | {
type: "json_object";
} | {
type: "json_schema";
json_schema: Record<string, unknown>;
};
interface ChatOptions {
/** System prompt */
system?: string;
/** Max tokens to generate */
maxTokens?: number;
/** Sampling temperature */
temperature?: number;
/** Nucleus sampling parameter */
topP?: number;
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
search?: boolean;
/** Full Live Search configuration (for search-enabled models) */
searchParameters?: SearchParameters;
/** Response format, e.g. { type: "json_object" } for JSON mode */
responseFormat?: ResponseFormat;
/** Up to 4 stop sequences (forwarded natively / mapped to stop_sequences) */
stop?: string | string[];
/**
* Models to try in order if the primary returns a transient error
* (timeout, network, 5xx). 4xx and PaymentError still propagate
* immediately. `smartChat` populates this from the routing tier's
* fallback chain automatically.
*/
fallbackModels?: string[];
}
interface ChatCompletionOptions {
/** Max tokens to generate */
maxTokens?: number;
/** Sampling temperature */
temperature?: number;
/** Nucleus sampling parameter */
topP?: number;
/** Enable Live Search (shortcut for searchParameters.mode = "on") */
search?: boolean;
/** Full Live Search configuration (for search-enabled models) */
searchParameters?: SearchParameters;
/** Tool definitions: function tools, plus x_search / web_search on xai/* models */
tools?: AnyTool[];
/** Tool selection strategy */
toolChoice?: ToolChoice;
/** Response format, e.g. { type: "json_object" } for JSON mode */
responseFormat?: ResponseFormat;
/** Up to 4 stop sequences (forwarded natively / mapped to stop_sequences) */
stop?: string | string[];
/**
* Models to try in order if the primary returns a transient error
* (timeout, network, 5xx). 4xx and PaymentError still propagate
* immediately.
*/
fallbackModels?: string[];
/**
* Per-call timeout in ms, overriding the client's `timeout` for every request
* of this call (both `LLMClient` and `SolanaLLMClient`). Search and long
* reasoning calls can need more than the 60 s default.
*/
timeout?: number;
}
type RoutingProfile = "eco" | "auto" | "premium";
type RoutingTier = Tier;
/**
* Request classification produced by the bundled Router Core V3 portfolio
* router. Present on decisions with `method: "portfolio"`.
*/
type RoutingTaskType = TaskType;
/** Primary model + ordered fallbacks for one routing tier. */
type RoutingTierConfig = RoutingConfig["tiers"][Tier];
interface RoutingDecision extends RoutingDecision$1 {
/**
* Remaining gateway-callable models, in the router's ranked order.
* `chat()` walks this list when the primary model hits a transient error
* (timeout, network, 429, 5xx). Excludes the primary itself. The router's
* proxy-namespace `free/*` ids appear here as their `nvidia/*` gateway
* ids (or are dropped when the mapping is proxy-only).
*/
fallbacks?: string[];
}
interface SmartChatOptions extends ChatOptions {
/** Routing profile: eco (budget), auto (balanced), premium (best quality) */
routingProfile?: RoutingProfile;
/** Maximum output tokens (used for cost estimation) */
maxOutputTokens?: number;
}
interface SmartChatCompletionOptions extends ChatCompletionOptions {
/** Routing profile: eco (budget), auto (balanced), premium (best quality) */
routingProfile?: RoutingProfile;
/** Maximum output tokens used for route cost/capacity estimation. */
maxOutputTokens?: number;
}
interface SmartChatResponse {
/** The AI response text */
response: string;
/** Which model was selected by smart routing */
model: string;
/** Routing decision metadata */
routing: RoutingDecision;
}
interface SmartChatCompletionResponse {
/** Full OpenAI-compatible response, including tool calls. */
response: ChatResponse;
/** Model selected before any transient runtime fallback. */
model: string;
/** Local routing decision and ordered fallback candidates. */
routing: RoutingDecision;
}
interface SearchResult {
query: string;
summary: string;
citations?: Array<Record<string, string>>;
sources_used?: number;
model?: string;
}
interface ImageEditOptions {
/** Model ID (default: "openai/gpt-image-2") */
model?: string;
/** Optional base64-encoded mask image */
mask?: string;
/** Image size (default: "1024x1024") */
size?: string;
/** Number of images to generate (default: 1) */
n?: number;
}
interface AudioTrack {
url: string;
duration_seconds?: number;
lyrics?: string;
}
interface MusicResponse {
created: number;
model: string;
data: AudioTrack[];
txHash?: string;
}
interface AudioModel {
id: string;
name: string;
provider: string;
description: string;
pricePerTrack: number;
maxDurationSeconds: number;
supportsLyrics: boolean;
supportsInstrumental: boolean;
available: boolean;
type: "audio";
}
interface MusicClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 210000 — music gen takes 1-3 min) */
timeout?: number;
}
interface MusicGenerateOptions {
/** Model ID (default: "minimax/music-2.5+") */
model?: "minimax/music-2.5+" | "minimax/music-2.5";
/** Generate without vocals (default: true) */
instrumental?: boolean;
/** Custom lyrics — cannot be used with instrumental: true */
lyrics?: string;
}
/** A single synthesized audio clip. */
interface SpeechAudio {
url: string;
format?: string;
characters?: number;
credits?: number;
}
/** Response from speech synthesis or sound-effect generation. */
interface SpeechResponse {
created: number;
model: string;
data: SpeechAudio[];
txHash?: string;
}
interface SpeechClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 120000 — synthesis is synchronous) */
timeout?: number;
}
/** TTS model IDs. Billed per 1k characters; rates at https://blockrun.ai/models. */
type SpeechModel = "elevenlabs/flash-v2.5" | "elevenlabs/turbo-v2.5" | "elevenlabs/multilingual-v2" | "elevenlabs/v3";
/** Friendly voice aliases (raw ElevenLabs voice_ids also accepted). */
type SpeechVoice = "sarah" | "george" | "laura" | "charlie" | "river" | "roger" | "callum" | "harry" | (string & {});
interface SpeechGenerateOptions {
/** Speech model ID (default: "elevenlabs/flash-v2.5") */
model?: SpeechModel | (string & {});
/** Voice alias or raw ElevenLabs voice_id (default: "sarah") */
voice?: SpeechVoice;
/** Audio format (default: "mp3") */
responseFormat?: "mp3" | "opus" | "pcm" | "wav";
/** Playback speed 0.7-1.2 */
speed?: number;
}
interface SoundEffectOptions {
/** Model ID (default: "elevenlabs/sound-effects") */
model?: string;
/** Target duration 0.5-22s (auto if unset) */
durationSeconds?: number;
/** 0-1, higher follows the prompt more literally */
promptInfluence?: number;
/** Audio format (default: "mp3") */
responseFormat?: "mp3" | "opus" | "pcm" | "wav";
}
/** A voice entry returned by GET /v1/audio/voices. */
interface VoiceInfo {
voice_id: string;
name?: string;
alias?: string;
category?: string;
labels?: Record<string, string>;
preview_url?: string;
[key: string]: unknown;
}
interface RpcClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 60000 — upstream gateway timeout is 20s) */
timeout?: number;
}
/** A JSON-RPC 2.0 error object. */
interface RpcError {
code?: number;
message?: string;
data?: unknown;
}
/**
* Response from a multi-chain JSON-RPC call.
*
* Standard JSON-RPC 2.0 envelope plus BlockRun gateway metadata pulled
* from response headers (X-Network / X-Cache / X-Payment-Receipt).
*/
interface RpcResponse<T = unknown> {
jsonrpc?: string;
id?: string | number | null;
result?: T;
error?: RpcError;
/** Canonical network key, e.g. "ethereum" (from X-Network) */
network?: string;
/** Served from the gateway's method-aware cache (from X-Cache) */
cacheHit?: boolean;
/** x402 settlement tx hash (single calls) */
txHash?: string;
}
/** A single request in a JSON-RPC batch (jsonrpc/id auto-filled if omitted). */
interface RpcBatchRequest {
method: string;
params?: unknown[];
id?: string | number;
jsonrpc?: string;
}
/** Built-in Bland.ai voice presets. Any string is accepted (custom voice IDs work too). */
type VoicePreset = "nat" | "josh" | "maya" | "june" | "paige" | "derek" | "florian";
/** Bland.ai conversation model tier. */
type CallModel = "base" | "enhanced" | "turbo";
interface VoiceClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 60000 — initiation only) */
timeout?: number;
}
interface CallOptions {
/** Destination phone number in E.164 format (e.g. "+14155552671"). US + Canada. */
to: string;
/** What the AI agent should do on the call. 10–4000 chars. */
task: string;
/** Your provisioned BlockRun caller-ID number (E.164). Must be wallet-owned. */
from?: string;
/** Voice preset or any Bland.ai voice ID. */
voice?: VoicePreset | string;
/** Maximum call length in minutes (1–30, default 5). */
max_duration?: number;
/** BCP-47 language code for STT/TTS (default "en-US"). */
language?: string;
/** Optional opening line spoken by the agent. */
first_sentence?: string;
/** If true, wait for the recipient to speak first. */
wait_for_greeting?: boolean;
/** Sensitivity for detecting recipient interruption (50–500 ms). */
interruption_threshold?: number;
/** Conversation model. */
model?: CallModel;
}
interface CallInitiatedResponse {
call_id: string;
status: string;
poll_url: string;
message?: string;
/** On-chain payment receipt (Base tx hash). */
txHash?: string;
}
/**
* Bland.ai call status payload. Returned from getStatus.
* Most fields are populated only after the call ends.
*/
interface CallStatusResponse {
call_id?: string;
status?: string;
to?: string;
from?: string;
/** ISO timestamp call started, or null while queued. */
started_at?: string | null;
/** ISO timestamp call ended, or null while in progress. */
ended_at?: string | null;
/** Total call length in seconds, once completed. */
call_length?: number;
/** URL to the call recording (mp3/wav). */
recording_url?: string | null;
/** Full text transcript of the call. */
concatenated_transcript?: string | null;
/** Per-turn transcript array. Shape comes from Bland.ai. */
transcripts?: Array<Record<string, unknown>>;
/** Why the call ended ("user_hangup", "agent_hangup", "timeout", …). */
ended_reason?: string | null;
/** Pass-through for anything Bland.ai adds. */
[key: string]: unknown;
}
interface VideoClip {
/** Permanent blockrun-hosted URL (falls back to upstream if backup fails) */
url: string;
/** Original upstream URL (e.g. vidgen.x.ai) */
source_url?: string;
/** Duration of the generated video */
duration_seconds?: number;
/** Upstream provider's request id (xAI) */
request_id?: string;
/** True when the gateway mirrored the video to its GCS bucket */
backed_up?: boolean;
/** Final frame image when returnLastFrame is requested and the provider returns it. */
last_frame_url?: string;
/** Whether the final frame was mirrored to BlockRun storage. */
last_frame_backed_up?: boolean;
}
interface VideoResponse {
created: number;
model: string;
data: VideoClip[];
txHash?: string;
}
interface VideoModel {
id: string;
name: string;
provider: string;
description: string;
pricePerSecond: number;
defaultDurationSeconds: number;
maxDurationSeconds: number;
supportsImageInput: boolean;
available: boolean;
type: "video";
}
interface VideoClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 300000 — video gen + polling up to 3 min) */
timeout?: number;
}
interface VideoGenerateOptions {
/** Model ID (default: "xai/grok-imagine-video") */
model?: "xai/grok-imagine-video" | string;
/** Optional seed image URL for image-to-video */
imageUrl?: string;
/**
* Virtual Portrait asset ID (`ta_xxxxxx`) — keeps the same AI character
* across multiple Seedance videos. Enroll via `POST /v1/portrait/enroll`
* (promotional rate, no KYC). **Seedance 2.0 fast/pro only.** Mutually exclusive
* with `imageUrl`. Real-person likeness is not supported on BlockRun.
*/
realFaceAssetId?: string;
/**
* First-and-last-frame interpolation: a second image that seeds the FINAL
* frame so the model tweens from `imageUrl` → `lastFrameUrl`. Requires
* `imageUrl` (the first frame) and a Seedance model
* (1.5-pro, 2.0 / Fast / Mini, or 2.5).
* Priced identically to image-to-video. Mutually exclusive with
* `realFaceAssetId`.
*/
lastFrameUrl?: string;
/**
* Omni / multi-reference: up to 9 (Seedance 2.0 / Fast / Mini) or 30
* (Seedance 2.5) reference image URLs for character/style consistency. Cite
* them as "image 1", "image 2" in the prompt. Mutually exclusive with
* `imageUrl` / `lastFrameUrl` / `realFaceAssetId`.
*
* **Account API key only.** Reference media is served by `api.blockrun.ai`;
* the wallet gateways (blockrun.ai, sol.blockrun.ai) refuse it with a 400
* before any payment.
*/
referenceImageUrls?: string[];
/**
* Reference-to-video: 1–3 http(s) motion/style video clips (Seedance 2.0 /
* Fast / Mini). May be combined with `referenceImageUrls`; mutually exclusive
* with frame seeds. `role` is optional and, when set, must be `"reference"`.
* Adds a per-clip surcharge. **Account API key only** (see `referenceImageUrls`).
*/
referenceVideos?: Array<{
url: string;
role?: "reference";
}>;
/**
* Reference audio: 1–3 http(s) clips, each ≤15.2s (Seedance 2.0 / Fast /
* Mini). Requires `referenceImageUrls` or `referenceVideos` alongside it.
* Adds a per-clip surcharge. **Account API key only** (see `referenceImageUrls`).
*/
referenceAudios?: Array<{
url: string;
role?: "reference";
}>;
/** Output bitrate mode. Seedance 2.0 / Fast / Mini / 2.5 only — other models get a 400. */
bitrateMode?: "standard" | "high";
/** Output container. Seedance 2.5 only — other models get a 400. */
outputFormat?: "mp4" | "mov";
/** Lock the camera. Seedance 1.5-pro only — other models get a 400. */
cameraFixed?: boolean;
/** End-user identifier forwarded for upstream abuse monitoring. Seedance only. */
safetyIdentifier?: string;
/**
* Declare the intended mode. The gateway infers it from the media fields
* anyway; when this is set and disagrees, it answers 400 (with the inferred
* mode) before payment instead of rendering the wrong thing.
*/
inputType?: "text" | "image" | "first_last_frame" | "reference";
/** Duration to bill for (defaults to model's default duration) */
durationSeconds?: number;
/** Output aspect ratio. Token360 / Seedance only — silently ignored by xAI Grok. */
aspectRatio?: "adaptive" | "16:9" | "9:16" | "1:1" | "4:3" | "3:4" | "21:9" | "9:21";
/** Output resolution. Token360 / Seedance only. */
resolution?: "360p" | "480p" | "540p" | "720p" | "1080p" | "1K" | "2K" | "4K";
/**
* Whether the model should produce a synced audio track. Token360 / Seedance
* only — silently ignored by xAI Grok. When omitted, the gateway defaults to
* `true` for text-to-video and `false` for image- or face-conditioned
* generation (the t2v / i2v split that matches Token360's quality profile).
* Pass an explicit boolean to override. Audio generation may be a paid
* surcharge upstream, so callers should expose this as a visible toggle.
*/
generateAudio?: boolean;
/** Reproducibility seed when supported by the model. */
seed?: number;
/** Embed the upstream watermark on the output. Defaults to false at the gateway. */
watermark?: boolean;
/**
* Return the last frame as an image alongside the clip — useful for chaining.
* Read it from `data[0].last_frame_url` (absent if the upstream omits it).
*/
returnLastFrame?: boolean;
}
interface SearchOptions {
/**
* Source types to search: `"web"` and/or `"news"` (default `["web"]`). `"x"` is
* rejected by the gateway; X search runs in chat on `xai/*` models (`x_search`).
*/
sources?: string[];
/** Maximum number of results (default: 10) */
maxResults?: number;
/** Start date filter (YYYY-MM-DD) */
fromDate?: string;
/** End date filter (YYYY-MM-DD) */
toDate?: string;
}
interface ExaSearchOptions {
/** Number of results to return (default: 10, max: 100) */
numResults?: number;
/** Restrict to a content category */
category?: "github" | "news" | "research paper" | "linkedin profile" | "personal site" | "tweet" | "financial report" | "pdf" | "company";
/** Only include pages published after this date (ISO 8601) */
startPublishedDate?: string;
/** Only include pages published before this date (ISO 8601) */
endPublishedDate?: string;
/** Only search within these domains */
includeDomains?: string[];
/** Exclude these domains from results */
excludeDomains?: string[];
}
interface ExaSearchItem {
id: string;
url: string;
title: string;
publishedDate?: string;
author?: string;
score?: number;
}
interface ExaSearchResponse {
requestId: string;
resolvedSearchType: string;
results: ExaSearchItem[];
searchTime: number;
costDollars: {
total: number;
};
}
interface ExaAnswerCitation {
id: string;
title: string;
url: string;
publishedDate?: string;
favicon?: string;
}
interface ExaAnswerResponse {
requestId: string;
answer: string;
citations: ExaAnswerCitation[];
}
interface ExaContentItem {
id: string;
url: string;
title: string;
text: string;
author?: string | null;
}
interface ExaContentsResponse {
results: ExaContentItem[];
costDollars: {
total: number;
};
}
interface ExaFindSimilarOptions {
/** Number of results to return (default: 10, max: 100) */
numResults?: number;
/** Exclude pages from the same domain as the reference URL */
excludeSourceDomain?: boolean;
}
interface OnrampResult {
/** One-time https://pay.coinbase.com/... URL prefilled for this wallet. */
url: string;
}
type PriceCategory = "crypto" | "fx" | "commodity" | "usstock" | "stocks";
type StockMarket = "us" | "hk" | "jp" | "kr" | "gb" | "de" | "fr" | "nl" | "ie" | "lu" | "cn" | "ca";
type BarResolution = "1" | "5" | "15" | "60" | "240" | "D" | "W" | "M";
type MarketSession = "pre" | "post" | "on";
interface PricePoint {
symbol: string;
price: number;
publishTime?: number;
confidence?: number;
feedId?: string;
timestamp?: string;
assetType?: string;
category?: string;
source?: string;
free?: boolean;
}
interface PriceBar {
t?: number;
o?: number;
h?: number;
l?: number;
c?: number;
v?: number;
}
interface PriceHistoryResponse {
symbol: string;
resolution?: string;
from?: number;
to?: number;
bars: PriceBar[];
source?: string;
category?: string;
}
interface SymbolListResponse {
symbols: Array<Record<string, unknown>>;
count?: number;
}
interface PriceOptions {
/** Required when category === "stocks". */
market?: StockMarket;
/** Optional US-equity session hint; ignored for non-equity. */
session?: MarketSession;
}
interface HistoryOptions extends PriceOptions {
/** TradingView-style bar resolution. Defaults to "D". */
resolution?: BarResolution;
/** Window start, unix seconds (required). */
from: number;
/** Window end, unix seconds. Defaults to now on the backend. */
to?: number;
}
interface ListOptions extends PriceOptions {
/** Free-text filter (maps to ?q=). */
query?: string;
/** Page size, capped at 2000. Defaults to 100. */
limit?: number;
}
interface SearchClientOptions extends ApiKeyOptions {
privateKey?: `0x${string}` | string;
apiUrl?: string;
timeout?: number;
}
interface PriceClientOptions extends ApiKeyOptions {
privateKey?: `0x${string}` | string;
apiUrl?: string;
timeout?: number;
/** If false, construction succeeds without a wallet (free endpoints only). */
requireWallet?: boolean;
}
/** @deprecated Surf is retired; see {@link SurfClient}. */
interface SurfClientOptions extends ApiKeyOptions {
privateKey?: `0x${string}` | string;
apiUrl?: string;
timeout?: number;
}
interface PhoneClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
timeout?: number;
}
/**
* Twilio Lookup passthrough — shape comes from upstream and varies by feature
* (basic vs `line_type_intelligence`, `sim_swap`, `call_forwarding`).
* Typed as a permissive record so future Twilio fields don't require an SDK release.
*/
interface PhoneLookupResponse {
phone_number?: string;
country_code?: string;
national_format?: string;
caller_name?: Record<string, unknown> | null;
carrier?: Record<string, unknown> | null;
line_type_intelligence?: Record<string, unknown> | null;
sim_swap?: Record<string, unknown> | null;
call_forwarding?: Record<string, unknown> | null;
/** Pass-through for anything Twilio adds. */
[key: string]: unknown;
}
interface PhoneBuyOptions {
/** ISO country code, "US" or "CA" (default "US"). */
country?: "US" | "CA";
/**
* Optional 3-digit area-code hint. Availability is not guaranteed — the
* backend falls back to any number in the country if the area code
* can't be matched.
*/
areaCode?: string;
}
interface PhoneBuyResponse {
/** The E.164 number now bound to your wallet. */
phone_number: string;
/** ISO-8601 expiry (30 days out from purchase). */
expires_at: string;
/** "base" | "solana" — settlement chain. */
chain: string;
/** Human-readable note. */
message?: string;
/** On-chain payment receipt (tx hash). */
txHash?: string;
}
interface PhoneRenewResponse {
phone_number: string;
/** Extended expiry (30 days from previous expiry). */
expires_at: string;
txHash?: string;
}
interface PhoneNumberRecord {
phone_number: string;
chain: string;
expires_at: string;
active: boolean;
}
interface PhoneListResponse {
numbers: PhoneNumberRecord[];
count: number;
txHash?: string;
}
interface PhoneReleaseResponse {
released: true;
phone_number: string;
txHash?: string;
}
interface PortraitClientOptions extends ApiKeyOptions {
/** EVM wallet private key (hex string starting with 0x) */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
apiUrl?: string;
/** Request timeout in milliseconds (default: 600000 / 600s; override via BLOCKRUN_CHAT_TIMEOUT env, in seconds) */
timeout?: number;
}
interface PortraitEnrollOptions {
/** Display name for the portrait (1–64 chars). */
name: string;
/**
* Public HTTPS URL to a JPG/PNG/WEBP face image (≤10 MB). The gateway fetches
* it server-side and forwards it to Token360 as multipart — only the URL is sent.
*/
imageUrl: string;
}
/**
* Response from `POST /v1/portrait/enroll`. The `asset_id` (`ta_xxxxxx`) is what
* you pass as `realFaceAssetId` on a Seedance 2.0 video generation to keep the
* same AI character across clips.
*/
interface PortraitEnrollResponse {
object: "virtual_portrait";
/** Token360 asset id (`ta_xxxxxx`) — pass as `realFaceAssetId` in VideoGenerateOptions. */
asset_id: string;
/** Token360 asset-group id (internal/debugging). */
group_id: string;
name: string;
/**
* Persisted thumbnail URL. The gateway mirrors your source image to its own
* storage when possible (so the listing thumbnail survives a dead source
* host); falls back to the original URL if mirroring fails. See `mirrored`.
*/
image_url: string;
/** The original URL you supplied (before any mirroring). */
source_image_url?: string;
/** Whether `image_url` is the gateway-mirrored copy (true) or your original (false). */
mirrored?: boolean;
/** ISO-8601 creation timestamp. */
created_at: string;
usage: {
compatible_models: string[];
how_to_use: string;
};
price: {
amount: string;
currency: string;
};
settlement: {
success: boolean;
tx_hash: string | null;
network: string;
};
/** On-chain payment receipt (tx hash), surfaced from the response header. */
txHash?: string;
}
interface BlockrunClientOptions extends ApiKeyOptions {
privateKey?: `0x${string}` | string;
apiUrl?: string;
/** Per-HTTP-call timeout in ms (default 60000). For long-running poll() jobs,
* set budgetMs in PollOptions instead — this only bounds individual fetches. */
timeout?: number;
}
interface PollOptions {
/** Total wall-clock budget for the entire submit + poll loop (default 300000 = 5 min). */
budgetMs?: number;
/** Sleep between poll attempts (default 5000 = 5 s). */
intervalMs?: number;
}
/**
* Whether a failed request may be retried, or sent to another model, without
* risking a second charge. The request layer that knows whether a payment was
* sent attaches it to the error it throws; read it with
* {@link retryDisposition}.
*
* - `"unpaid"`: nothing that could be charged was sent for this request: the
* unpaid first request of an x402 exchange (and its 402 challenge), the
* signing step, or, with an API key, the account API's explicit 4xx
* refusal. A retry or a fallback model pays at most once.
* - `"paid-or-in-doubt"`: a payment was sent (with an API key, the billed
* request itself) and may have been charged: a timeout, an abort or a
* network error after it was sent, any non-2xx answer to it, or a 2xx whose
* body could not be read. A retry or a fallback model could pay twice, so
* the SDK does neither.
*
* An error that carries no disposition is treated as `"paid-or-in-doubt"`.
*/
type RetryDisposition = "unpaid" | "paid-or-in-doubt";
/**
* The {@link RetryDisposition} the SDK attached to an error, or undefined
* when it attached none (then treat the error as `"paid-or-in-doubt"`).
*
* @example
* if (retryDisposition(err) === "unpaid") await retryLater();
*/
declare function retryDisposition(err: unknown): RetryDisposition | undefined;
declare class BlockrunError extends Error {
constructor(message: string);
}
/**
* Thrown by a helper whose upstream endpoint no longer exists.
*
* Kept as a throwing method rather than deleted so upgrading does not break
* imports or property access — the failure is explicit and immediate instead of
* a round trip that returns 410/404.
*/
declare class RetiredEndpointError extends BlockrunError {
constructor(message: string);
}
declare class PaymentError extends BlockrunError {
constructor(message: string);
}
declare class APIError extends BlockrunError {
statusCode: number;
/** Retry-After header returned by the account API. */
retryAfter?: string;
response?: unknown;
constructor(message: string, statusCode: number, response?: unknown);
}
/** Account billing is independent of the wallet payment chain. */
interface ApiKeyOptions {
/** BlockRun account key. Defaults to BLOCKRUN_API_KEY unless a privateKey is explicit. */
apiKey?: string;
}
declare const API_KEY_URL = "https://api.blockrun.ai";
declare const PORTAL_URL = "https://user.blockrun.ai";
/**
* BlockRun LLM Gateway Client.
*
* Provides access to multiple LLM providers (OpenAI, Anthropic, Google, etc.)
* with automatic x402 micropayments in USDC on Base (chain 8453) or, with
* `apiUrl` pointed at arc.blockrun.ai, on Circle's Arc (chain 5042) — the
* 402's `network` selects the chain (see EVM_NETWORKS in x402.ts).
* API base: https://blockrun.ai/api
*/
declare class LLMClient {
static readonly DEFAULT_API_URL = "https://blockrun.ai/api";
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
private modelPricingCache;
private modelPricingPromise;
private preAuthCache;
private static readonly PRE_AUTH_TTL_MS;
/**
* Initialize the BlockRun LLM client.
*
* @param options - Client configuration options (optional if BLOCKRUN_API_KEY, BASE_CHAIN_WALLET_KEY or BLOCKRUN_WALLET_KEY is set)
*/
constructor(options?: LLMClientOptions);
/**
* Simple 1-line chat interface.
*
* @param model - Model ID (e.g., 'openai/gpt-5.2', 'anthropic/claude-sonnet-4.6')
* @param prompt - User message
* @param options - Optional chat parameters
* @returns Assistant's response text
*
* @example
* const response = await client.chat('gpt-5.2', 'What is the capital of France?');
* console.log(response); // 'The capital of France is Paris.'
*/
chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
private makeRoutingDecision;
/**
* Inspect a local routing decision without making or paying for a model call.
* The first invocation may fetch the public model catalog for current prices.
*/
route(prompt: string, options?: SmartChatOptions): Promise<RoutingDecision>;
/**
* Smart chat with automatic model routing.
*
* Uses BlockRun's product-neutral Router Core portfolio strategy: it
* classifies the task shape locally (no
* extra model call), enforces capability constraints as hard filters, and
* ranks an ordered candidate portfolio — the cheapest model that can handle
* the request wins, and the rest become the transient-error fallback chain.
*
* @param prompt - User message
* @param options - Optional chat and routing parameters
* @returns SmartChatResponse with response text, selected model, and routing metadata
*
* @example Simple usage (auto profile)
* ```ts
* const result = await client.smartChat('What is 2+2?');
* console.log(result.response); // '4'
* console.log(result.model); // 'google/gemini-3.5-flash'
* console.log(result.routing.method); // 'portfolio'
* console.log(result.routing.savings); // 0.78 (78% savings)
* ```
*
* @example With routing profile
* ```ts
* // Eco mode (budget optimized)
* const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
*
* // Premium mode (best quality)
* const result = await client.smartChat('Write a business plan', { routingProfile: 'premium' });
* ```
*/
smartChat(prompt: string, options?: SmartChatOptions): Promise<SmartChatResponse>;
/** Route a full Agent/tool conversation while preserving its complete response. */
smartChatCompletion(messages: ChatMessage[], options?: SmartChatCompletionOptions): Promise<SmartChatCompletionResponse>;
/**
* Get model pricing map (cached).
* Fetches from API on first call, then returns cached result.
*/
private getModelPricing;
/** Fetch model pricing from the live catalog, preserving flat billing. */
private fetchModelPricing;
/**
* Full chat completion interface (OpenAI-compatible).
*
* When `fallbackModels` is set, transient failures (timeouts, network
* errors, 429, 5xx) on the primary model trigger a retry against the next
* model in the list before raising, but only while no payment has been
* sent for it (the error's {@link RetryDisposition} is `"unpaid"`). Once a
* payment was sent, any failure propagates: the next model would be a
* second charge. 4xx errors and PaymentError also propagate immediately —
* those aren't "swap upstream and retry" situations. Each fallback hop
* logs one stderr line.
*
* @param model - Primary model ID
* @param messages - Array of messages with role and content
* @param options - Optional completion parameters
* @returns ChatResponse object with choices and usage
*/
chatCompletion(model: string, messages: ChatMessage[], options?: ChatCompletionOptions): Promise<ChatResponse>;
/**
* Write a canonical cost_log entry after a settled x402 payment.
* Best-effort: failures here must never break a successful API call.
* Mirrors what Franklin's AgentClient writes via src/agent/llm.ts so
* cost_log.jsonl is a single source of truth regardless of caller.
*/
private recordCost;
/**
* Parse the chat response JSON and attach `fallback` metadata when the
* gateway signalled a transparent free-fallback substitution. The
* gateway sets X-Fallback-Used / X-Fallback-Model / X-Settlement-Skipped
* on the response when it served a paid request from a free model
* (route.ts createPaymentResponseHeader path). Without surfacing these
* to the caller, the user gets a different model than requested with
* no visibility — silent quality drop and no clue why the on-chain
* balance didn't change.
*/
private parseChatResponse;
/**
* Make a request with automatic x402 payment handling.
*
* Every error it throws carries a {@link RetryDisposition}, which is what
* {@link LLMClient.chatCompletion}'s fallback walk honours: errors of the
* unpaid first request (and its one 502/503 retry), of the 402 challenge
* and of signing are `"unpaid"`; anything after the signed payment was sent
* is `"paid-or-in-doubt"`. With an API key the request itself is billed, so
* only the account API's explicit 4xx refusal is `"unpaid"`.
*/
private requestWithPayment;
/**
* Send a request that carries no payment, marking what it throws with its
* {@link RetryDisposition}: `"unpaid"` with a wallet; with an API key, see
* {@link accountErrorDisposition} (the request itself is billed).
*/
private sendUnpaid;
/**
* Parse a response to a request that carried no payment: the free tier,
* or, with an API key, the billed request itself.
*
* With a wallet nothing was paid, so `costUsd` is 0 (a free model). With an
* API key the call is billed to the account and `costUsd` stays undefined.
*/
private parseUnpaidChatResponse;
/**
* Handle 402 response: parse requirements, sign payment, retry.
*
* Anything thrown before the signed payment is sent is `"unpaid"`; anything
* after it, `"paid-or-in-doubt"` (see {@link RetryDisposition}).
*/
private handlePaymentAndRetry;
/** Read a 402's requirements and sign the exact payment for them. Sends nothing. */
private signFrom402;
/** Send the signed payment and read the answer. The payment may settle from here on. */
private sendPaid;
/**
* Book a served, paid chat call and attach its cost. A response whose
* settlement the gateway skipped ({@link settlementSkipped}: e.g.
* `X-Settlement-Skipped: free-fallback`) moved nothing on chain, so it is
* not added to `getSpending()` or the cost log, and reports `costUsd: 0`.
*/
private bookPaid;
/**
* Book a served, paid raw (non-chat) call: the signed amount goes to
* `getSpending()` and the cost log, unless the gateway skipped settlement
* ({@link settlementSkipped}: e.g. `X-Settlement-Skipped: empty-result` on an
* empty prediction-market search, `unexecuted` on a JSON-RPC call that never
* ran, or `X-Payment-Waived`), in which case nothing moved and nothing is
* booked. `X-Payment-Settled: false` alone is still booked: it is owed.
*/
private bookPaidRaw;
/**
* Sign a payment header and return the PAYMENT-SIGNATURE value.
* Extracted to share logic between streaming and non-streaming flows.
*/
private signPayment;
/**
* Streaming chat completion with automatic x402 payment.
*
* Uses a pre-auth cache so repeat calls to the same model skip the 402
* round-trip (~200ms savings). Falls back to the normal 402 flow on cache
* miss or if the pre-signed payment is rejected.
*
* @returns Raw fetch Response with a streaming SSE body.
*/
chatCompletionStream(model: string, messages: ChatMessage[], options?: ChatCompletionOptions): Promise<Response>;
/**
* Make a request with automatic x402 payment handling, returning raw JSON.
* Used for non-ChatResponse endpoints (search, image edit, etc.).
*/
private requestWithPaymentRaw;
/**
* Handle 402 response for raw endpoints: parse requirements, sign payment, retry.
*/
private handlePaymentAndRetryRaw;
/**
* GET with automatic x402 payment handling, returning raw JSON.
* Used for Predexon prediction market endpoints that use GET + query params.
*/
private getWithPaymentRaw;
/**
* Handle 402 response for GET endpoints: parse requirements, sign payment, retry with GET.
*/
private handleGetPaymentAndRetryRaw;
/**
* Fetch with timeout: `timeoutMs` (a per-call override), else the client's `timeout`.
*/
private fetchWithTimeout;
/**
* List available models with pricing.
*
* Returns the full `/v1/models` unified catalog (chat + image + music).
* The shape preserves backwards compatibility — image/music rows have
* `inputPrice = outputPrice = 0` since those fields don't apply, and
* their per-call price surfaces via `flatPrice`.
*/
listModels(): Promise<Model[]>;
/**
* List available image generation models with pricing.
*
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
* image models live in the unified `/v1/models` catalog under
* `categories: ["image", ...]`. This method filters that catalog so
* existing callers keep working.
*/
listImageModels(): Promise<ImageModel[]>;
/**
* List all available models (chat, image, music, etc.) with pricing.
*
* @returns Array of all models with `type` field set from category
* (`llm` for chat, `image` / `music` for media). Backwards-compat:
* chat models always report `type: "llm"`.
*/
listAllModels(): Promise<(Model | ImageModel)[]>;
/**
* Internal: fetch the raw `/v1/models` catalog without normalising shape.
* Used by listImageModels / listAllModels so each can pick category-
* specific fields.
*/
private fetchRawModels;
/**
* Edit an image using img2img, optionally fusing multiple source images.
*
* @param prompt - Text description of the desired edit
* @param image - A base64 `data:image/...` URI, or an array of 2–4 such URIs
* to fuse (image fusion). Caps: `openai/*` up to 4, `google/*` up to 3.
* A `mask` cannot be combined with multiple source images.
* @param options - Optional edit parameters
* @returns ImageResponse with edited image URLs
*/
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
/**
* Standalone search over web and news. For X/Twitter, use chat on an
* `xai/*` model with an `x_search` tool.
*
* @param query - Search query
* @param options - Optional search parameters
* @returns SearchResult with summary and citations
*/
search(query: string, options?: SearchOptions): Promise<SearchResult>;
/**
* Generic Exa endpoint proxy (POST). Useful when you need an Exa API
* surface that the typed wrappers below don't expose.
*
* @param path - Exa endpoint segment: "search" | "find-similar" | "contents" | "answer"
* @param body - Request body (see Exa API docs)
*
* @example
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
*/
exa(path: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Neural web search via Exa. Returns semantically relevant URLs and metadata.
* Understands meaning, not just keywords.
*
* @param query - Natural language search query
* @param options - Optional filters (numResults, category, date range, domains)
*/
exaSearch(query: string, options?: ExaSearchOptions): Promise<ExaSearchResponse>;
/**
* Ask a question and get a cited, synthesized answer grounded in real web sources.
* No hallucinations — every claim is backed by a citation.
*
* @param query - The question to answer
*/
exaAnswer(query: string): Promise<ExaAnswerResponse>;
/**
* Fetch full Markdown text content from a list of URLs. Billed per URL.
* Returns clean text ready to feed into an LLM context window.
*
* @param urls - Array of URLs to fetch (up to 100)
*/
exaContents(urls: string[]): Promise<ExaContentsResponse>;
/**
* Find pages semantically similar to a given URL.
* Useful for discovering competitors, alternatives, and related resources.
*
* @param url - Reference URL
* @param options - Optional filters (numResults, excludeSourceDomain)
*/
exaFindSimilar(url: string, options?: ExaFindSimilarOptions): Promise<ExaSearchResponse>;
/**
* Mint a one-time Coinbase Onramp link to fund this wallet with USDC on Base.
*
* FREE — the x402 signature only authenticates the wallet (no payment is
* charged). Because the funding address must equal the signing wallet, this
* mints a `pay.coinbase.com` link prefilled for `address` so a card/bank can
* top it up (60+ fiat currencies → Base USDC). Base / USDC only.
*
* The returned URL is single-use and expires in ~5 minutes — mint it at click
* time and never cache it. The funding `address` must match the signing wallet.
*
* @param address - Destination Base wallet address (0x + 40 hex chars). Must
* equal the signing wallet that funds it.
* @returns `{ url }` — a one-time https://pay.coinbase.com/... link.
*
* @example
* const { url } = await client.onramp(client.getWalletAddress());
* console.log(`Fund your wallet: ${url}`);
*/
onramp(address: string): Promise<OnrampResult>;
/**
* Get USDC balance on Base mainnet.
*
* @returns USDC balance as a float (6 decimal places normalized)
*
* @example
* const balance = await client.getBalance();
* console.log(`Balance: $${balance.toFixed(2)} USDC`);
*/
getBalance(): Promise<number>;
/**
* Query Predexon prediction market data (GET endpoints).
*
* Access real-time data from Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, Binance Futures, UMA and sports markets.
* Powered by Predexon. Billed per request.
*
* @param path - Endpoint path, e.g. "polymarket/events", "kalshi/markets/12345"
* @param params - Query parameters passed to the endpoint
*
* @example
* const events = await client.pm("polymarket/events");
* const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
* const results = await client.pm("polymarket/search", { q: "bitcoin" });
*/
pm(path: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
/**
* Structured query for Predexon prediction market data (POST endpoints).
*
* For endpoints that require a JSON body, e.g. bulk wallet identity lookup.
* Tier 1 =, Tier 2 =
*
* @param path - Endpoint path, e.g. "polymarket/wallet/identities"
* @param query - JSON body for the structured query
*
* @example
* const batch = await client.pmQuery("polymarket/wallet/identities", {
* addresses: ["0xabc...", "0xdef..."],
* });
*/
pmQuery(path: string, query: Record<string, unknown>): Promise<Record<string, unknown>>;
/** RETIRED — `/v1/pm/markets` no longer exists.
*
* Predexon sunset market matching on 2026-07-20 and the whole canonical layer
* went with it, so this path returns 410 upstream. Use
* `pm("markets/search", { q })` for cross-venue lookups.
*
* Kept as a throwing stub rather than deleted so upgrading does not break
* property access; it throws before any network I/O.
*
* @throws {RetiredEndpointError} always. */
pmMarkets(_params?: Record<string, string>): Promise<Record<string, unknown>>;
/** RETIRED — `/v1/pm/markets/listings` no longer exists (410, sunset
* 2026-07-20 with market matching). Use `pm("markets/search", { q })`.
*
* @throws {RetiredEndpointError} always. */
pmListings(_params?: Record<string, string>): Promise<Record<string, unknown>>;
/** RETIRED — `/v1/pm/outcomes/{predexonId}` no longer exists (410, sunset
* 2026-07-20 with market matching). Use `pm("markets/search", { q })`.
*
* @throws {RetiredEndpointError} always. */
pmOutcome(_predexonId: string): Promise<Record<string, unknown>>;
/** Polymarket markets with cursor-based keyset pagination (use pagination_key).
* Tier 1. */
pmPolymarketMarketsKeyset(params?: Record<string, string>): Promise<Record<string, unknown>>;
/** Polymarket events with cursor-based keyset pagination (use pagination_key).
* Tier 1. */
pmPolymarketEventsKeyset(params?: Record<string, string>): Promise<Record<string, unknown>>;
/** List available sports categories. Tier 1.
*
* NOTE: upstream returns 500 for every `sports/*` path as of 2026-08-04.
* The route still resolves, so this works again the moment Predexon restores
* it, but do not build on it yet. */
pmSportsCategories(): Promise<Record<string, unknown>>;
/** List sports markets grouped by game. Filter with league, sport_type,
* status, venue. Tier 1.
*
* NOTE: upstream returns 500 for every `sports/*` path as of 2026-08-04. */
pmSportsMarkets(params?: Record<string, string>): Promise<Record<string, unknown>>;
/** Fetch identity + profile metadata for one wallet (ENS, Twitter, portfolio,
* etc.). Tier 2. */
pmWalletIdentity(wallet: string): Promise<Record<string, unknown>>;
/** Bulk identity lookup for up to 200 wallet addresses (POST). Tier 2. */
pmWalletIdentities(addresses: string[]): Promise<Record<string, unknown>>;
/** Discover wallets connected to a seed address via on-chain transfers and
* identity proofs. Tier 2. */
pmWalletCluster(address: string): Promise<Record<string, unknown>>;
/**
* Query DefiLlama DeFi data (GET passthrough). Powered by DefiLlama.
* for protocols / protocol/{slug} / chains / yields;
* for prices/{coins}.
*
* @param path - e.g. "protocols", "protocol/aave", "chains", "yields",
* "prices/coingecko:bitcoin,base:0x..."
* @param params - Query parameters passed through to DefiLlama
*/
defi(path: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
/** All DeFi protocols with TVL. */
defiProtocols(): Promise<Record<string, unknown>>;
/** Single protocol details + historical TVL. */
defiProtocol(slug: string): Promise<Record<string, unknown>>;
/** Current TVL of every chain. */
defiChains(): Promise<Record<string, unknown>>;
/** Yield pools with APY/TVL. */
defiYields(params?: Record<string, string>): Promise<Record<string, unknown>>;
/** Token price lookup. Coins like "coingecko:bitcoin" or "{chain}:{address}". */
defiPrices(coins: string | string[]): Promise<Record<string, unknown>>;
/**
* Query the 0x Swap / Gasless APIs (free — no x402 payment; BlockRun
* takes an on-chain affiliate fee on executed swaps instead).
*
* @param path - "price", "quote", "gasless/price", "gasless/quote",
* "gasless/submit" (POST), "gasless/status/{hash}",
* "gasless/approval-tokens", "gasless/chains", "swap/chains"
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
* @param body - JSON body — pass to switch to POST (gasless/submit only)
*/
dex(path: string, params?: Record<string, string>, body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Indicative Permit2 swap price — no commitment (free). */
dexPrice(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Firm Permit2 swap quote with permit2.eip712 + tx data (free). */
dexQuote(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Gasless indicative price quote (free). */
dexGaslessPrice(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Gasless firm quote — returns trade.eip712 to sign (free). */
dexGaslessQuote(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Submit a signed gasless trade; the 0x relayer pays gas (free). */
dexGaslessSubmit(body: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Poll a gasless trade's status by tradeHash (free). */
dexGaslessStatus(tradeHash: string): Promise<Record<string, unknown>>;
/** Chains where the Swap API is supported (free). */
dexChains(): Promise<Record<string, unknown>>;
/** Chains where the Gasless API is supported (free). */
dexGaslessChains(): Promise<Record<string, unknown>>;
/**
* Call the Modal sandbox compute API (POST passthrough).
*
* @param path - "sandbox/create" (CPU / GPU tiers), "sandbox/exec",
* "sandbox/status", "sandbox/terminate"
* @param body - JSON body for the endpoint
*/
modal(path: string, body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Create a sandboxed compute environment (CPU / GPU tiers). */
modalSandboxCreate(body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Execute a command in a sandbox; returns stdout/stderr. */
modalSandboxExec(sandboxId: string, command: string[], extra?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Check a sandbox's status. */
modalSandboxStatus(sandboxId: string): Promise<Record<string, unknown>>;
/** Terminate a sandbox. */
modalSandboxTerminate(sandboxId: string): Promise<Record<string, unknown>>;
/**
* Get current session spending.
*
* @returns Object with totalUsd and calls count
*
* @example
* const spending = client.getSpending();
* console.log(`Spent $${spending.totalUsd.toFixed(4)} across ${spending.calls} calls`);
*/
getSpending(): Spending;
/**
* Get the wallet address being used for payments.
*/
getWalletAddress(): string;
}
/**
* BlockRun Image Generation Client.
*
* Generate images using Nano Banana (Google Gemini), DALL-E 3, GPT Image,
* or CogView-4 (Zhipu AI) with automatic x402 micropayments on Base chain.
*/
declare class ImageClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
/**
* Initialize the BlockRun Image client.
*
* @param options - Client configuration options
*/
constructor(options?: ImageClientOptions);
/**
* Generate an image from a text prompt.
*
* @param prompt - Text description of the image to generate
* @param options - Optional generation parameters
* @returns ImageResponse with generated image URLs
*
* @example
* const result = await client.generate('A sunset over mountains');
* console.log(result.data[0].url);
*/
generate(prompt: string, options?: ImageGenerateOptions): Promise<ImageResponse>;
/**
* Edit an image using img2img, optionally fusing multiple source images.
*
* Pass a single `data:image/...` base64 data URI to edit one image, or an
* array of 2–4 data URIs to fuse them (image fusion) — e.g. a reference photo
* plus a brand logo. The server caps fusion per provider: `openai/*` accepts
* up to 4 source images, `google/*` (Nano Banana) up to 3. A `mask` cannot be
* combined with multiple source images.
*
* Edit-capable models: `openai/gpt-image-1`, `openai/gpt-image-2`,
* `openai/gpt-image-2.5-sunburst`, `google/nano-banana`, `google/nano-banana-2`,
* `google/nano-banana-pro`. `mask` is OpenAI-only.
*
* @param prompt - Text description of the desired edit
* @param image - A base64 `data:image/...` URI, or an array of 2–4 such URIs to fuse
* @param options - Optional edit parameters
* @returns ImageResponse with edited image URLs
*
* @example
* // Single-image edit
* const result = await client.edit('Make it a painting', imageDataUri);
*
* @example
* // Multi-image fusion (Nano Banana): blend a subject with a brand logo
* const fused = await client.edit(
* 'Place the logo on the t-shirt',
* [subjectDataUri, logoDataUri],
* { model: 'google/nano-banana' }
* );
*/
edit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
/**
* List available image generation models with pricing.
*
* The dedicated `/v1/images/models` endpoint was deprecated server-side;
* image models live in the unified `/v1/models` catalog under
* `categories: ["image", ...]`. This method filters that catalog so
* existing callers keep working.
*/
listImageModels(): Promise<ImageModel[]>;
/**
* Make a request with automatic x402 payment handling.
*/
private requestWithPayment;
/**
* Handle 402 response: parse requirements, sign payment, retry.
*/
private handlePaymentAndRetry;
/**
* Poll the async image generation endpoint until the job reaches a terminal
* state (completed/failed). Used when the POST returns 202 + poll_url for
* slow models that exceeded the server's inline window.
*
* Sends the SAME payment header on every poll — the server binds the payer
* to the job id and re-verifies on each call. Settlement happens server-side
* on the first poll where status=completed.
*/
private pollImageJob;
/**
* Fetch with timeout.
*/
private fetchWithTimeout;
/**
* Get the wallet address being used for payments.
*/
getWalletAddress(): string;
/**
* Get session spending information.
*/
getSpending(): Spending;
}
/**
* BlockRun Music Generation Client.
*
* Generate full-length ~3 minute music tracks using MiniMax Music 2.5+
* with automatic x402 micropayments on Base chain.
*
* Pricing: per track — see https://blockrun.ai/models
* Note: Generated URLs expire in ~24h — download immediately if needed.
*/
declare class MusicClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: MusicClientOptions);
/**
* Generate a music track from a text prompt.
*
* Takes 1-3 minutes. Returns a CDN URL valid for ~24h.
*
* @param prompt - Music style, mood, or description
* @param options - Optional generation parameters
* @returns MusicResponse with track URL and metadata
*
* @example
* const result = await client.generate('chill lo-fi beats with piano');
* console.log(result.data[0].url); // Download this URL — expires in 24h
*
* @example With lyrics
* const result = await client.generate('upbeat pop song', {
* instrumental: false,
* lyrics: 'Hello world, this is my song...'
* });
*/
generate(prompt: string, options?: MusicGenerateOptions): Promise<MusicResponse>;
private requestWithPayment;
private handlePaymentAndRetry;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
/**
* BlockRun Speech Client (BlockRun Voice).
*
* Text-to-speech and sound-effect generation using ElevenLabs models
* with automatic x402 micropayments on Base chain.
*
* TTS pricing scales with input characters; sound effects are flat
*
*/
declare class SpeechClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: SpeechClientOptions);
/**
* Synthesize speech from text (OpenAI-compatible TTS).
*
* Price scales with character count: (chars / 1000) x model rate,
* minimum Synthesis is synchronous.
*
* @param input - Text to synthesize. Per-model character caps apply
* (flash/turbo 40k, multilingual-v2 10k, v3 5k).
* @param options - Optional model / voice / format / speed
* @returns SpeechResponse with audio URL, format, and character count
*
* @example
* const result = await client.generate('Welcome to BlockRun.', { voice: 'george' });
* console.log(result.data[0].url); // audio URL (mp3 by default)
*/
generate(input: string, options?: SpeechGenerateOptions): Promise<SpeechResponse>;
/** OpenAI-style alias for {@link generate}. */
speak(input: string, options?: SpeechGenerateOptions): Promise<SpeechResponse>;
/**
* Generate a cinematic sound effect from a text prompt.
*
*, up to 22 seconds of audio.
*
* @param text - Sound effect description (max 1000 chars).
* E.g. 'rain on a tin roof', 'sci-fi door whoosh'
* @param options - Optional duration / prompt influence / format
* @returns SpeechResponse with audio URL and format
*
* @example
* const fx = await client.soundEffect('crackling campfire at night');
* console.log(fx.data[0].url);
*/
soundEffect(text: string, options?: SoundEffectOptions): Promise<SpeechResponse>;
/**
* List available voices for TTS (free, rate-limited 60 req/min/IP).
*
* Pass a voice's `alias` (if present) or `voice_id` as the `voice`
* option to {@link generate}.
*/
listVoices(): Promise<VoiceInfo[]>;
private requestWithPayment;
private handlePaymentAndRetry;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
/**
* BlockRun Video Generation Client.
*
* Supports xAI Grok Imagine Video and ByteDance Seedance (1.5 Pro /
* 2.0 Fast / 2.0 Pro) with automatic x402 micropayments on Base.
*/
declare class VideoClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: VideoClientOptions);
/**
* Generate a short video clip from a text prompt (or text + image).
*
* Submits an async job, then polls until the video is ready. Typical total
* wall-time is 60-180s, but upstream status can lag several minutes behind
* actual completion. If upstream runs past the budget (default 15min),
* throws with the existing poll URL; check billing before submitting again.
*
* @param prompt - Text description of the video
* @param options - Optional generation parameters
*/
generate(prompt: string, options?: VideoGenerateOptions & {
budgetMs?: number;
}): Promise<VideoResponse>;
/**
* Generate a video from a standard Seedance `content[]` body.
*
* Targets the gateway's `POST /v1/videos` endpoint, which accepts the
* mainstream multimodal `content` array used by other Seedance APIs. Items
* may carry a `role` — `first_frame`, `last_frame`, `reference_image`,
* `reference_video`, `reference_audio` — which the gateway maps to the same
* validated fields as {@link generate}; a role-less single image keeps its
* first-frame meaning — so callers already holding a
* `content[]`-shaped request can submit it unchanged. The gateway validates
* unsupported inputs *before* charging, then delegates to the same x402
* submit+poll pipeline as {@link generate}.
*
* Most SDK users should prefer {@link generate} (structured options like
* `imageUrl` / `lastFrameUrl`) — this exists for migrating existing
* `content[]` payloads with no reshaping.
*
* @param content - The Seedance `content` array, e.g.
* `[{ type: "text", text: "a red apple spinning" }]` or a text item plus
* `{ type: "image_url", image_url: { url: "https://..." } }`.
* @param options - `model`, `budgetMs`, plus the same camelCase render options
* as {@link generate} (`durationSeconds`, `aspectRatio`, `resolution`,
* `generateAudio`, `seed`, `watermark`, `returnLastFrame`, `bitrateMode`,
* `outputFormat`, `cameraFixed`, `safetyIdentifier`, `inputType`). These are mapped
* to the gateway's snake_case fields for you. Any other keys you pass are
* forwarded verbatim (use snake_case for those, since the gateway reads
* snake_case only).
*/
generateFromContent(content: Array<Record<string, unknown>>, options?: {
model?: string;
budgetMs?: number;
durationSeconds?: number;
aspectRatio?: string;
resolution?: string;
generateAudio?: boolean;
seed?: number;
watermark?: boolean;
returnLastFrame?: boolean;
bitrateMode?: "standard" | "high";
outputFormat?: "mp4" | "mov";
cameraFixed?: boolean;
safetyIdentifier?: string;
inputType?: "text" | "image" | "first_last_frame" | "reference";
} & Record<string, unknown>): Promise<VideoResponse>;
private submitAndPoll;
/**
* Parse an x402 challenge response and sign a payment payload for it.
* Used for the initial submit AND for mid-poll re-signing after the 600s
* authorization window lapses on long polls.
*/
private signFromChallenge;
private absolute;
private extractPaymentRequired;
private throwApiError;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
/**
* BlockRun Voice Call Client.
*
* Initiates AI-powered outbound phone calls with automatic x402 micropayments
* on Base chain.
*
* Pricing: per call (regardless of duration up to max_duration).
* Status polling is free.
*/
declare class VoiceClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: VoiceClientOptions);
/**
* Initiate an AI-powered outbound phone call.
*
* Pricing: per call. Returns immediately once the call is queued —
* poll getStatus() for transcript and recording.
*
* @example
* const r = await client.call({
* to: '+14155552671',
* task: 'Confirm the user wants to reschedule to Tuesday 2pm.',
* voice: 'maya',
* max_duration: 3,
* });
*/
call(options: CallOptions): Promise<CallInitiatedResponse>;
/**
* Poll the status of an in-progress or completed call. Free — no payment.
*
* Returns Bland.ai's full call record: status, transcript, recording URL, etc.
* Most fields populate only once the call ends.
*/
getStatus(callId: string): Promise<CallStatusResponse>;
private requestWithPayment;
private handlePaymentAndRetry;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
/** Per-endpoint price in USD — mirrors the backend's `PHONE_PRICES`. */
declare const PHONE_PRICES: Readonly<Record<string, number>>;
/**
* BlockRun Phone Client.
*
* Wraps the `/v1/phone/*` x402 endpoints — phone-number lookup (carrier + fraud)
* and provisioning of the caller-ID numbers required by VoiceClient.call().
*/
declare class PhoneClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: PhoneClientOptions);
/** EVM wallet address used for payments. */
getWalletAddress(): string;
/** Session-cumulative USD spend + call count across this client instance. */
getSpending(): Spending;
/**
* Carrier + line-type lookup.
*
* @param phoneNumber E.164 number (e.g. "+14155552671").
*/
lookup(phoneNumber: string): Promise<PhoneLookupResponse>;
/**
* Lookup + fraud signals (SIM swap, call forwarding).
*
* @param phoneNumber E.164 number.
*/
lookupFraud(phoneNumber: string): Promise<PhoneLookupResponse>;
/**
* Provision a dedicated phone number for 30 days.
*
* Payment is settled only after Twilio confirms the purchase, so failed
* buys never charge your wallet.
*
* @param options.country ISO country code, "US" or "CA" (default "US").
* @param options.areaCode Optional 3-digit area-code hint. Availability is
* not guaranteed — the backend falls back to any number in the country if
* the area code can't be matched.
*/
buyNumber(options?: PhoneBuyOptions): Promise<PhoneBuyResponse>;
/**
* Extend an existing provisioned number by 30 days.
*
* @throws {APIError} 403 when the wallet doesn't own the number or it has expired.
*/
renewNumber(phoneNumber: string): Promise<PhoneRenewResponse>;
/** List the wallet's active phone numbers. */
listNumbers(): Promise<PhoneListResponse>;
/**
* Release a provisioned number back to the Twilio pool. Free, but still
* flows through x402 so the backend can verify ownership.
*/
releaseNumber(phoneNumber: string): Promise<PhoneReleaseResponse>;
private request;
private handlePaymentAndRetry;
private unwrap;
private fetchWithTimeout;
}
/**
* Flat enrollment price in USD — mirrors the backend's `ENROLLMENT_PRICE_USD`.
* Currently (promotional, parity with RealFace). The authoritative price
* is whatever the gateway quotes in the 402; this constant is informational.
*/
declare const PORTRAIT_ENROLLMENT_PRICE_USD = 0.01;
/**
* BlockRun Portrait Client.
*
* Enrolls a Virtual Portrait from a face image URL and returns the `ta_xxxxxx`
* asset id consumed by Seedance 2.0 video generation. Real-person likeness is
* not supported on BlockRun — enrolled portraits are AI characters.
*/
declare class PortraitClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: PortraitClientOptions);
/** EVM wallet address used for payments. */
getWalletAddress(): string;
/** Session-cumulative USD spend + call count across this client instance. */
getSpending(): Spending;
/**
* Enroll a Virtual Portrait from a face image URL. (promo).
*
* Payment is settled only after Token360 confirms the enrollment, so failed
* enrollments never charge your wallet.
*
* @param options.name Display name (1–64 chars).
* @param options.imageUrl Public HTTPS URL to a JPG/PNG/WEBP image (≤10 MB).
* @returns The enrollment record, including `asset_id` (`ta_xxxxxx`).
*/
enroll(options: PortraitEnrollOptions): Promise<PortraitEnrollResponse>;
private request;
private handlePaymentAndRetry;
private unwrap;
private fetchWithTimeout;
}
declare class SearchClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
constructor(options?: SearchClientOptions);
search(query: string, options?: SearchOptions): Promise<SearchResult>;
private requestWithPayment;
private handlePaymentAndRetry;
private fetchWithTimeout;
getWalletAddress(): string;
}
declare class PriceClient {
private apiAuth?;
get authMode(): "api-key" | "wallet";
private account;
private privateKey;
private apiUrl;
private timeout;
constructor(options?: PriceClientOptions);
price(category: PriceCategory, symbol: string, options?: PriceOptions): Promise<PricePoint>;
history(category: PriceCategory, symbol: string, options: HistoryOptions): Promise<PriceHistoryResponse>;
listSymbols(category: PriceCategory, options?: ListOptions): Promise<SymbolListResponse>;
getWalletAddress(): string | null;
private getWithPayment;
private payAndRetry;
private fetchWithTimeout;
}
type QueryValue$1 = string | number | boolean | null | undefined;
type QueryParams$1 = Record<string, QueryValue$1 | QueryValue$1[]>;
/** @deprecated Surf is retired (`410 endpoint_retired`); see the module docs for replacements. */
declare class SurfClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
constructor(options?: SurfClientOptions);
/**
* GET a Surf endpoint. `path` is everything after `/v1/surf` (a leading
* `/v1/surf` is tolerated and stripped). Query params are URL-encoded;
* arrays become repeated keys (`?a=1&a=2`).
*/
get<T = unknown>(path: string, params?: QueryParams$1): Promise<T>;
/**
* POST a Surf endpoint with a JSON body. Same path normalization as `get`.
*/
post<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T>;
private buildUrl;
private requestWithPayment;
private handlePaymentAndRetry;
private throwApiError;
private fetchWithTimeout;
getWalletAddress(): string;
}
/**
* Flat price per JSON-RPC call (batch = N x this). Informational only —
* the actual quote always comes from the 402 challenge.
*/
declare const RPC_PRICE_USD = 0.002;
/**
* Curated chains accepted by /v1/rpc/{network}. Mirrors the backend chain
* registry (verified live 2026-06-07). EVM chains use eth_* JSON-RPC;
* non-EVM (Solana / UTXO / NEAR / Sui / XRP Ledger / Polkadot) speak their
* own JSON-RPC dialect.
*/
declare const SUPPORTED_NETWORKS: readonly ["ethereum", "base", "arbitrum", "arbitrum-nova", "optimism", "polygon", "bsc", "avalanche", "fantom", "cronos", "celo", "gnosis", "zksync", "berachain", "unichain", "monad", "chiliz", "moonbeam", "aurora", "flare", "oasis", "kaia", "sonic", "xdc", "abstract", "hyperevm", "plume", "ronin", "rootstock", "solana", "bitcoin", "litecoin", "dogecoin", "bitcoin-cash", "near", "sui", "ripple", "polkadot", "kusama", "zcash"];
type RpcNetwork = (typeof SUPPORTED_NETWORKS)[number] | (string & {});
/** Common short names the gateway also accepts (resolved server-side). */
declare const NETWORK_ALIASES: Record<string, string>;
/**
* BlockRun Multi-chain RPC Client.
*
* Standard JSON-RPC 2.0 access to 40+ chains through BlockRun's Tatum
* gateway with automatic x402 micropayments on Base chain.
*
* Flat per call; a JSON-RPC batch charges per element.
*/
declare class RpcClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: RpcClientOptions);
/**
* Make a single JSON-RPC 2.0 call. Flat per-call rate.
*
* @param network - Chain name (e.g. "ethereum", "base", "solana") or a
* common alias ("eth", "sol", "matic", ...). See
* SUPPORTED_NETWORKS / NETWORK_ALIASES.
* @param method - Chain RPC method, e.g. "eth_blockNumber", "eth_call",
* "eth_getBalance" (EVM) or "getSlot", "getAccountInfo"
* (Solana).
* @param params - Method-specific params array (optional).
* @returns RpcResponse with `result` (or JSON-RPC `error`), plus
* `network`, `cacheHit` and `txHash` metadata.
*
* @example
* const block = await client.call('ethereum', 'eth_blockNumber');
* console.log(parseInt(block.result as string, 16));
*/
call<T = unknown>(network: RpcNetwork, method: string, params?: unknown[]): Promise<RpcResponse<T>>;
/**
* Make a JSON-RPC 2.0 batch call. Priced per element (rate x N).
*
* @param network - Chain name or alias (see {@link call}).
* @param requests - Requests, each with a `method` and optional
* `params` / `id`. `jsonrpc` and missing ids are
* filled in automatically.
* @returns Array of RpcResponse, in upstream order.
*
* @example
* const out = await client.batch('base', [
* { method: 'eth_blockNumber' },
* { method: 'eth_gasPrice' },
* ]);
*/
batch(network: RpcNetwork, requests: RpcBatchRequest[]): Promise<RpcResponse[]>;
private toResponse;
private requestWithPayment;
private handlePaymentAndRetry;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
type QueryValue = string | number | boolean | null | undefined;
type QueryParams = Record<string, QueryValue | QueryValue[]>;
/**
* The x402-paying HTTP primitive for the BlockRun gateway.
*
* One instance, one wallet, all endpoints. The four call shapes (get, post,
* poll, stream) cover every endpoint type the gateway exposes.
*/
declare class BlockrunClient {
private _account?;
private get account();
private apiAuth?;
/** Active billing mode. */
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private timeout;
private sessionTotalUsd;
private sessionCalls;
constructor(options?: BlockrunClientOptions);
/**
* GET a BlockRun endpoint. `path` is everything after `/api` (a leading
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
*/
get<T = unknown>(path: string, params?: QueryParams): Promise<T>;
/**
* POST a BlockRun endpoint with a JSON body.
*/
post<T = unknown>(path: string, body?: Record<string, unknown>): Promise<T>;
/**
* Submit a long-running job and poll until it completes.
*
* Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
* the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
* deadline exceeded). Settlement happens only when upstream returns 200 +
* completed — upstream failure or caller giving up = no charge.
*
* **EVM only, and enforced upstream.** Replaying one signature works because
* an EIP-3009 authorization stays valid for as long as it was signed for. A
* Solana payment is a transaction pinned to a recent blockhash and expires in
* ~150 blocks (~60s), so replaying one could not cover a long render. No
* guard is needed here: `signFrom402` already refuses a `solana:` network
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
* whose poll loop signs afresh each time; the gateway binds a job to the
* payer address rather than to the signature, and still settles exactly once.
*
* If the gateway returns 200 directly on submit (no async surface), this
* short-circuits and returns the body. Most long-running endpoints (image,
* video, music, voice) return 202 with a poll_url.
*/
poll<T = unknown>(path: string, body?: Record<string, unknown>, options?: PollOptions): Promise<T>;
/**
* Stream a Server-Sent Events endpoint.
*
* Yields each `data: …` line parsed as JSON. Stops when the upstream emits
* `data: [DONE]` or closes the connection. Caller is responsible for typing
* the chunk shape; pass a generic for typed yields.
*
* Example — streaming chat:
* for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
* model: "anthropic/claude-sonnet-4-6",
* messages: [{ role: "user", content: "Hi" }],
* stream: true,
* })) {
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
* }
*/
stream<T = unknown>(path: string, body?: Record<string, unknown>): AsyncGenerator<T, void, undefined>;
private buildUrl;
private absolute;
private requestWithPayment;
private handlePaymentAndRetry;
/**
* Read a 402 response's payment requirements (header or body), then sign and
* return the base64 PAYMENT-SIGNATURE payload with what it pays, in USD.
* The cost stays with the call that signed it (booked by `recordSpending`):
* concurrent calls on one client never share it.
*/
private signFrom402;
/**
* Book `costUsd`, what the call that `response` answered signed. A response
* whose settlement the gateway skipped ({@link settlementSkipped}:
* `X-Settlement-Skipped: <reason>` or `X-Payment-Waived`) moved nothing, so
* nothing is booked. `X-Payment-Settled: false` alone is still booked: the
* payment is owed.
*/
private recordSpending;
private throwApiError;
private fetchWithTimeout;
getWalletAddress(): string;
getSpending(): Spending;
}
/**
* x402 Payment Protocol v2 Implementation for BlockRun.
*
* This module handles creating signed payment payloads for the x402 v2 protocol.
* The private key is used ONLY for local signing and NEVER leaves the client.
*/
declare const BASE_CHAIN_ID = 8453;
declare const USDC_BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
declare const ARC_CHAIN_ID = 5042;
declare const USDC_ARC: "0x3600000000000000000000000000000000000000";
/**
* The EVM networks a BlockRun gateway settles on, keyed by the CAIP-2
* `network` a 402 carries, each with the SDK's OWN values for that network's
* USDC and its EIP-712 domain.
*
* Until 3.16.0 there was one domain — Base's — signed whatever the 402 said,
* so against arc.blockrun.ai (eip155:5042) or testnet.blockrun.ai every
* payment was a signature over the wrong domain: the facilitator recovered a
* different signer and answered 401 after the SDK had reported a payment.
*
* The 402 SELECTS a network from this table; it never supplies the domain.
* That keeps the rule the single constant enforced — a hostile 402's `extra`
* cannot steer a signature onto another contract — while letting the same
* SDK pay on every host. Arc's USDC is the chain's native token exposed as an
* ERC-20 at 0x3600…0000, and its domain name is "USDC", not Base's "USD Coin".
*/
interface EvmNetwork {
name: string;
chainId: number;
usdc: `0x${string}`;
domain: {
name: string;
version: string;
chainId: number;
verifyingContract: `0x${string}`;
};
}
declare const EVM_NETWORKS: Readonly<Record<string, EvmNetwork>>;
/** The network table entry for a 402's `network`, or a refusal that names what IS supported. */
declare function evmNetwork(network: string): EvmNetwork;
declare const SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
declare const USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
interface CreatePaymentOptions {
resourceUrl?: string;
resourceDescription?: string;
maxTimeoutSeconds?: number;
/** The 402's `extra`. Echoed nowhere: the domain comes from EVM_NETWORKS. */
extra?: {
name?: string;
version?: string;
};
/** The 402's `asset`. Checked against the network's USDC; a mismatch is refused. */
asset?: string;
extensions?: Record<string, unknown>;
}
/**
* Create a signed x402 v2 payment payload.
*
* @param privateKey - Hex-encoded private key
* @param fromAddress - Sender wallet address
* @param recipient - Payment recipient address
* @param amount - Amount in micro USDC (6 decimals)
* @param network - CAIP-2 network from the 402 (default: eip155:8453); must be in EVM_NETWORKS
* @param options - Additional options for resource info
* @returns Base64-encoded signed payment payload
*/
declare function createPaymentPayload(privateKey: `0x${string}`, fromAddress: string, recipient: string, amount: string, network?: string, options?: CreatePaymentOptions): Promise<string>;
interface CreateSolanaPaymentOptions {
resourceUrl?: string;
resourceDescription?: string;
maxTimeoutSeconds?: number;
extra?: Record<string, unknown>;
extensions?: Record<string, unknown>;
rpcUrl?: string;
/**
* Optional HTTP headers forwarded to the Solana RPC endpoint
* (e.g. `{ "x-api-key": "..." }` for Tatum / header-auth gateways).
*/
rpcHeaders?: Record<string, string>;
/**
* Skip the client blockhash cache and ask the RPC for a current one.
*
* Set this ONLY when the previous attempt was rejected for a stale
* blockhash. The cache TTL (10s) is longer than any sane retry backoff, so a
* re-sign that leaves this false gets the SAME expired hash back, and the
* duplicate guard then nudges the priority fee — producing fresh transaction
* bytes pinned to the very blockhash the server just rejected. The retry
* looks like it did something and is guaranteed to fail identically.
*
* Not a guarantee of a different hash: the default RPC caches
* `getLatestBlockhash` for 30s server-side and may hand back the same value.
* It is the strongest thing a client can do, and the guard below still
* ensures the bytes differ.
*/
forceFreshBlockhash?: boolean;
}
/**
* Create a signed Solana x402 v2 payment payload.
*
* This creates an SPL TransferChecked transaction for USDC payment
* that the CDP facilitator can verify and settle.
*
* Requires @solana/web3.js and @solana/spl-token dependencies.
*
* @param secretKey - Solana secret key (Uint8Array, 64 bytes)
* @param fromAddress - Sender wallet address (base58)
* @param recipient - Payment recipient address (base58)
* @param amount - Amount in micro USDC (6 decimals)
* @param feePayer - CDP facilitator fee payer address (base58)
* @param options - Additional options
* @returns Base64-encoded signed payment payload
*/
declare function createSolanaPaymentPayload(secretKey: Uint8Array, fromAddress: string, recipient: string, amount: string, feePayer: string, options?: CreateSolanaPaymentOptions): Promise<string>;
/**
* Parse the X-Payment-Required header from a 402 response.
*
* @param headerValue - Base64-encoded payment required header
* @returns Parsed payment required object
* @throws {Error} If the header cannot be parsed or has invalid structure
*/
declare function parsePaymentRequired(headerValue: string): PaymentRequired;
/**
* Extract payment details from parsed payment required response.
* Supports both v1 and v2 formats, with optional network preference.
*
* @param paymentRequired - Parsed payment required object
* @param preferredNetwork - Optional network preference. If specified, will try
* to use matching network option. Defaults to first option (Base).
* Examples:
* - "eip155:8453" for Base
* - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" for Solana
*/
declare function extractPaymentDetails(paymentRequired: PaymentRequired, preferredNetwork?: string): {
amount: string;
recipient: string;
network: string;
asset: string;
scheme: string;
maxTimeoutSeconds: number;
extra?: {
name?: string;
version?: string;
feePayer?: string;
};
resource?: ResourceInfo;
};
/**
* BlockRun Wallet Management - Auto-create and manage wallets.
*
* Provides frictionless wallet setup for new users:
* - Auto-creates wallet if none exists
* - Stores key securely at ~/.blockrun/.session
* - Generates EIP-681 URIs for easy MetaMask funding
*
* Key resolution, discovery, and adoption are delegated to `@blockrun/core`, the
* shared kernel every BlockRun product reads. That is what guarantees this SDK,
* the `blockrun` CLI, and clawrouter-codex all resolve the SAME wallet — the
* behaviour is defined in one place instead of being re-implemented per product
* and drifting. Kept SDK-local: the funding/messaging surface (SDK-specific),
* plus createWallet()/saveWallet() as thin persistence wrappers for API
* compatibility — they write to the same core-resolved path, per call.
*
* Because path resolution now comes from core, `BLOCKRUN_HOME` overrides the base
* directory (previously this module always used the OS home directory). Treat
* that variable 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.
*/
declare const USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
interface WalletInfo {
privateKey: string;
address: string;
isNew: boolean;
}
interface PaymentLinks {
basescan: string;
walletLink: string;
ethereum: string;
blockrun: string;
}
/**
* Create a new Ethereum wallet.
*
* @returns Object with address and privateKey
*/
declare function createWallet(): {
address: string;
privateKey: string;
};
/**
* Save wallet private key to ~/.blockrun/.session
*
* @param privateKey - Private key string (with 0x prefix)
* @returns Path to saved wallet file
*/
declare function saveWallet(privateKey: string): string;
/**
* Discover ~/.<dir>/wallet.json files from other providers.
*
* Each file should contain JSON with a "privateKey" field; the returned
* address is DERIVED from that key (any "address" field in the file is
* ignored, so a file cannot claim an address it holds no key for), and
* entries whose key does not parse are dropped. Results are sorted by
* modification time (most recent first). Discovery is intentionally opt-in:
* a provider wallet must never replace the canonical BlockRun wallet merely
* because it was written more recently.
*
* @returns Array of wallet objects with privateKey and derived address
*/
declare function scanWallets(): Array<{
privateKey: string;
address: string;
source: string;
}>;
/**
* List wallets from other applications, safe to show to a user.
*
* Unlike `scanWallets()`, no private key is returned and the address is derived
* from the key rather than read from the file, so a wallet file cannot claim an
* address it has no key for.
*
* Nothing here is active. Adopt one deliberately with `importWallet()`.
*
* @returns Discovered wallets as `{ address, source }`, most recent first
*/
declare function listDiscoveredWallets(): Array<{
address: string;
source: string;
}>;
/**
* Adopt a discovered wallet by address, making it the active BlockRun wallet.
*
* This is the deliberate migration path: automatic selection never adopts a
* discovered wallet, but you can choose one whose funds you want to spend.
* Matching is done against the address *derived from each discovered key*, so a
* wallet file claiming someone else's address can never be selected by it.
*
* The current `~/.blockrun/.session` is backed up beside itself before being
* overwritten, so adopting a wallet can't strand the funds in the old one.
*
* NOTE: this is NOT `@blockrun/core`'s `importWallet(rawPrivateKey)` — that
* one persists a raw key and refuses to overwrite without `force`. This SDK
* function adopts a DISCOVERED wallet by address and maps onto core's
* `adoptWallet()`. Same name, different operation; don't mix them up when
* importing from core directly.
*
* @param address Address to adopt, as shown by `listDiscoveredWallets()`
* @returns The adopted address
* @throws If no discovered wallet derives to that address
*/
declare function importWallet(address: string): string;
/**
* Load wallet private key from file.
*
* Priority:
* 1. ~/.blockrun/.session
* 2. ~/.blockrun/wallet.key (legacy)
*
* @returns Private key string or null if not found
*/
declare function loadWallet(): string | null;
/**
* Warn when a new wallet was created while other provider wallets exist.
*
* Automatic selection deliberately ignores wallets discovered in other
* applications' directories, so a user who previously relied on that discovery
* would otherwise land on an empty wallet with no explanation of where their
* funds went. This notice names the discovered addresses and tells them how to
* import one on purpose.
*
* Addresses are derived from the discovered private key rather than read from
* the file's "address" field, so a file claiming an address it cannot sign for
* cannot trick the user into importing it.
*
* @param newAddress Address of the wallet that was just created
* @returns Formatted notice, or null if nothing was discovered
*/
declare function formatWalletMigrationNotice(newAddress: string): string | null;
/**
* Get existing wallet or create new one.
*
* Priority:
* 1. BLOCKRUN_WALLET_KEY environment variable
* 2. ~/.blockrun/.session file
* 3. ~/.blockrun/wallet.key file - legacy
* 4. Create new wallet
*
* @returns WalletInfo with address, privateKey, and isNew flag
*/
declare function getOrCreateWallet(): WalletInfo;
/**
* Get wallet address without exposing private key.
*
* @returns Wallet address or null if no wallet configured
*/
declare function getWalletAddress(): string | null;
/**
* Generate EIP-681 URI for USDC transfer on Base.
*
* @param address - Recipient Ethereum address
* @param amountUsdc - Amount in USDC (default 1.0)
* @returns EIP-681 URI string for MetaMask/wallet scanning
*/
declare function getEip681Uri(address: string, amountUsdc?: number): string;
/**
* Generate payment links for the wallet address.
*
* @param address - Ethereum address
* @returns Object with various payment links
*/
declare function getPaymentLinks(address: string): PaymentLinks;
/**
* Format the message shown when a new wallet is created.
*
* @param address - New wallet address
* @returns Formatted message string
*/
declare function formatWalletCreatedMessage(address: string): string;
/**
* Format the message shown when wallet needs more funds.
*
* @param address - Wallet address
* @returns Formatted message string
*/
declare function formatNeedsFundingMessage(address: string): string;
/**
* Compact funding message (no QR) for repeated displays.
*
* @param address - Wallet address
* @returns Short formatted message string
*/
declare function formatFundingMessageCompact(address: string): string;
declare const WALLET_FILE_PATH: string;
declare const WALLET_DIR_PATH: string;
/**
* BlockRun's batch-settlement operator public key on sol.blockrun.ai.
*
* The production key that signs vouchers on BlockRun's Solana channels (it is
* also the channels' receiver authorizer). Pin it in
* `batch.operators` to let this client open channels with BlockRun:
*
* new SolanaLLMClient({ batch: { operators: [BLOCKRUN_SOL_OPERATOR] } })
*
* It is a constant on purpose: a client must never trust the operator a 402
* names. If the key is ever rotated, both keys are valid for an overlap
* period, and `operators` takes a list for exactly that case.
*/
declare const BLOCKRUN_SOL_OPERATOR = "5YKPQUFjw5WQqhSUkEGKNNfYYVqnRRNbpYyL71qQ1vm3";
/** Opt-in configuration for Solana batch-settlement. */
interface SolanaBatchOptions {
/**
* Base58 operator public keys you trust to sign vouchers against your
* channel. A batch accept naming any other operator is ignored, and the
* call pays with `exact`. Required: there is no built-in default key.
*/
operators: string[];
/**
* Most escrow, in USD, this client keeps at stake in one channel: the
* deposit not yet settled on-chain. A top-up is allowed while
* `(deposit - settled) + topUp <= maxDeposit`, so a long-lived channel keeps
* topping up as the gateway settles what it charged. This is the most an
* operator could claim beyond what has already been settled without
* another signature from you. Accepts `"$5"`, `"5"` or `5`. Default `"$1"`
* (the `@x402/svm` default).
*/
maxDeposit?: string | number;
/**
* Where open-channel state is persisted between processes.
* - default: `~/.blockrun/solana-batch/<wallet address>.json` (mode 0600)
* - a path: that file
* - `false`: in memory only. On restart the client finds its channel again
* by scanning the chain for it.
*/
channelStore?: string | false;
/**
* What a call does when another live process owns this wallet's channel
* store (`channel_store_locked`). One process per wallet pays with batch:
* it holds the store's lock until it exits, so worker pools, PM2/cluster
* workers or parallel runs on one wallet must each use their own wallet
* (or route their calls through one process).
* - `"exact"` (default): the call pays with `exact`, as before.
* - `"wait"`: the call waits up to {@link lockWaitMs} for the lock (the
* owner has to exit), then pays with batch, or raises
* {@link BatchChannelLockedError} once the wait runs out.
* - `"throw"`: the call raises {@link BatchChannelLockedError} at once.
*
* The error is raised before anything is signed, so it is `"unpaid"`:
* `fallbackModels` (and `smartChat()`'s chain) may move on, and will meet
* the same lock. A process that loses its lock to another owner raises it
* too unless `onLocked` is `"exact"` (waiting cannot give it back).
* Locks between clients of one process do not apply: they share a channel.
*/
onLocked?: "exact" | "wait" | "throw";
/** Most time, in ms, one call waits for the lock under `onLocked: "wait"`. Default 60000. */
lockWaitMs?: number;
/**
* How a call rides out an HTTP 429 (rate limited, or channel capacity
* exhausted) on a batch payment before it pays with `exact`. The call waits
* for `Retry-After` (seconds or an HTTP date), or 1s, 2s, 4s... with jitter
* when there is none (or when it is not a finite delay of at most a day),
* then tries again: a 429 without a receipt by replaying the identical
* payment, one that proves nothing was broadcast with a new payment. Other
* calls for the same wallet wait out the same cooldown instead of sending
* new channel opens; that shared cooldown never lasts longer than
* `maxWaitMs`. A payment carrying a payment identifier is replayed within
* {@link inDoubt} instead.
*/
rateLimit?: {
/** Batch payment attempts per call, the first one included. Default 3. */
maxAttempts?: number;
/** Most time, in ms, one call spends waiting on 429s before it pays `exact`. Default 60000. */
maxWaitMs?: number;
};
/**
* How long a call keeps replaying a batch payment in doubt that carries a
* payment identifier (the gateway's 402 declares the x402
* `payment-identifier` extension) before it raises
* {@link BatchPaymentUnresolvedError}. The identical payment is replayed
* while the gateway's answer says a later replay can succeed: no answer
* (a timeout, an abort, a network error), a 5xx, a 429, or a 409 with a
* `Retry-After` (the request is still running). Each wait is
* `Retry-After`, else 1s, 2s, 4s... with jitter, and never under 1 s nor
* over 30 s. Replays do not count against {@link rateLimit}.
*/
inDoubt?: {
/**
* Most time, in ms, one call spends replaying a payment in doubt,
* counted from the first answer that left it in doubt: every wait and
* every replay's own round trip. It decides whether another replay
* starts: a wait that would end past it is not started, and the call
* raises. A replay already sent runs until its answer or the client's
* `timeout`, so a call can end up to one `timeout` past it. `0` never
* replays. Default 300000 (5 min).
*/
maxWaitMs?: number;
};
/**
* Called for every batch fallback, 429 backoff, recovery and channel
* re-read, in addition to the one-line log the SDK writes to stderr for each.
* A callback that throws, or returns a promise that rejects, is logged and
* ignored; it never affects the payment, and its promise is not awaited.
* Any other return value is ignored, so `(e) => events.push(e)` is fine.
*/
onEvent?: (event: SolanaBatchEvent) => unknown;
}
/** Default {@link SolanaBatchOptions.rateLimit}. */
declare const DEFAULT_BATCH_RATE_LIMIT: {
readonly maxAttempts: 3;
readonly maxWaitMs: 60000;
};
/** Default {@link SolanaBatchOptions.inDoubt}. */
declare const DEFAULT_BATCH_IN_DOUBT: {
readonly maxWaitMs: 300000;
};
/** Default {@link SolanaBatchOptions.lockWaitMs}. */
declare const DEFAULT_BATCH_LOCK_WAIT_MS = 60000;
/**
* One observable batch-settlement event.
*
* - `fallback`: this call pays with `exact`. `reason` says why. Only ever
* when no batch payment of this call can have been charged. After the
* facilitator refuses a deposit before broadcasting it (`payment_required`
* with one of PayAI's `batch_*` admission codes as `errorReason`), the
* wallet's calls skip batch for `retryAfterMs` (`Retry-After`, else 60s,
* or 10 min for a refusal of the deposit's own terms) and report
* `admission_refused`, naming the same code, without sending a payment.
* - `backoff`: this call is waiting before it retries batch, after a 429
* (`reason: "rate_limited"`) or because another call for the wallet hit one
* (`reason: "cooldown"`), or before each replay of a payment in doubt that
* carries a payment identifier (`reason: "in_doubt"`, or `rate_limited`
* after a 429), with the answer's `status` and the wait in `retryAfterMs`;
* or, once, when a call starts waiting up to `lockWaitMs` for another
* process's channel store lock (`reason: "channel_store_locked"`,
* `onLocked: "wait"`).
* - `recovered`: a call that backed off was then paid with batch (`reason:
* "in_doubt"` when the replay of a payment in doubt resolved it), or its
* fresh challenge after the wait was served without a payment (`reason:
* "served_unpaid_on_rechallenge"`: that response is the call's result, and
* nothing was paid).
* - `unresolved`: this call is raised as {@link BatchPaymentUnresolvedError},
* not paid with `exact` or any fallback model, because a batch payment it
* sent may have been charged and got no definitive answer. `reason` is a
* {@link BatchUnresolvedReason}.
* - `resync`: the channel was re-read from the chain because the SDK could
* not trust its record (a deposit with no clean answer, including a 429
* without a receipt, a receipt that was missing or rebuilt, or a pending
* deposit left by a process that died).
* `reason` says which; `detail` says what the chain showed.
*/
interface SolanaBatchEvent {
type: "fallback" | "backoff" | "recovered" | "resync" | "unresolved";
/**
* A short, stable code: `rate_limited`, `cooldown`, `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`, `payment_required`, `admission_refused`,
* `channel_resync_pending`, `channel_resync_failed`, `channel_unreadable`,
* `deposit_journal_unreadable`, `deposit_journal_failed`,
* `served_unpaid_on_rechallenge` or `batch_cancelled` (`recovered` events), `in_doubt` (`backoff` and `recovered`
* events), or one of the gateway's refusal codes the
* SDK recognises (`batch_payer_not_allowed`, `batch_payer_not_admitted`,
* `batch_admission_paused`, `batch_server_signed_only`, `batch_unavailable`,
* `batch_channel_limit`, `PAYMENT_VERIFICATION_UNAVAILABLE`). An `unresolved` event's reason is a
* {@link BatchUnresolvedReason}. A `resync` event's reason is
* `deposit_unanswered`, `deposit_failed`, `deposit_rate_limited`,
* `deposit_refused`, `receipt_missing`, `receipt_unreconciled`,
* `orphaned_deposit`, `deposit_unrecorded` or `channel_unusable`.
*/
reason: string;
/** HTTP status of the gateway answer behind the event, when there was one. */
status?: number;
/** The gateway's or facilitator's error code, when it named one. */
errorReason?: string;
/** How long this call waits (backoff), or would have had to wait (fallback). */
retryAfterMs?: number;
/** Which batch payment attempt of this call the event belongs to (1-based). */
attempt?: number;
/** Free-text detail, such as the underlying error message. */
detail?: string;
/** The paying wallet's address. */
wallet: string;
/** When it happened, in ms since the epoch. */
at: number;
}
/** Batch-settlement counters for one client, from {@link SolanaLLMClient.getBatchStats}. */
interface SolanaBatchStats {
/** Calls that paid with `exact` instead of batch. */
fallbacks: number;
/** {@link fallbacks}, split by {@link SolanaBatchEvent.reason}. */
fallbacksByReason: Record<string, number>;
/** Waits before a batch retry (own 429s and shared cooldowns). */
backoffs: number;
/** Batch payments sent again: a new payment after a 429, or each replay of a payment in doubt. */
retries: number;
/**
* Calls paid with batch after at least one backoff, or served without a
* payment by their fresh challenge after one (`served_unpaid_on_rechallenge`).
*/
recoveries: number;
/** Times the channel record was re-read from the chain. */
resyncs: number;
/**
* Calls raised as {@link BatchPaymentUnresolvedError}: a batch payment they
* sent may have been charged, so nothing paid for them again.
*/
unresolved: number;
/** {@link unresolved}, split by {@link BatchUnresolvedReason}. */
unresolvedByReason: Record<string, number>;
}
/**
* Why a batch payment is left in doubt; see {@link BatchPaymentUnresolvedError}.
*
* - `replay_unresolved`: a 429 without a receipt, whose one byte-identical
* replay got no definitive success receipt (a 402,
* `duplicate_settlement`, another 429, a 5xx, a timeout...), or a payment
* carrying a payment identifier whose replays got none: the last one was
* answered with something no later replay can change (a 409 without
* `Retry-After`, a 402, a 2xx without a receipt the SDK can verify...),
* or `batch.inDoubt.maxWaitMs` ran out. A 429 without a receipt keeps
* this reason when there is no room to replay it; any other doubt keeps
* its own reason.
* - `ambiguous_rate_limit`: a 429 whose receipt does not prove nothing was
* broadcast (one that says it was charged, `settlement_pending`, or one
* naming a transaction).
* - `no_response`: sending it threw (a timeout, an abort, a network error),
* whatever the error's code: the request may have reached the gateway.
* - `outcome_unknown`: any other answer that neither serves the call nor
* proves nothing was charged: a 5xx without a recognised refusal code, an
* unrecognised 4xx, or a non-2xx whose receipt names a transaction, says
* `settlement_pending`, or says it succeeded.
* - `duplicate_settlement`: the first answer was a 402 naming
* `duplicate_settlement`: the gateway had already reserved this request id,
* so another copy of the payment reached it and may have been charged.
*/
type BatchUnresolvedReason = "replay_unresolved" | "ambiguous_rate_limit" | "no_response" | "outcome_unknown" | "duplicate_settlement";
/** What a batch payment carried: a channel open, a top-up of the channel, or an authorization alone. */
type BatchPayloadKind = "open" | "top-up" | "authorization";
/**
* A batch payment this call sent may have been charged, and never got a
* definitive answer. Reported as an `unresolved` event with the same
* `reason` (see {@link BatchUnresolvedReason}).
*
* A {@link PaymentError} whose {@link RetryDisposition} is
* `"paid-or-in-doubt"`, never transient: the SDK does not pay for the call
* again, not with `exact`, not with a new batch payment, and not by moving
* on to `fallbackModels` (including the chain `smartChat()` fills in). Any
* retry is yours to make, and it is a new payment.
*
* When it carried a deposit (`payloadKind` `open` or `top-up`), the deposit
* is in doubt too: the SDK signs no other deposit for that wallet's channels
* until a chain read at `finalized` commitment settles it.
*/
declare class BatchPaymentUnresolvedError extends PaymentError {
/** The {@link SolanaBatchEvent.reason} of the `unresolved` event reported with it. */
readonly reason: BatchUnresolvedReason;
/** The paying wallet's address. */
readonly wallet: string;
/** The request id of the payment that may have been charged. */
readonly requestId?: string;
/** The channel it paid into. */
readonly channelId?: string;
/** What it carried. */
readonly payloadKind?: BatchPayloadKind;
/** HTTP status of the gateway's last answer to it, when there was one. */
readonly status?: number;
/** Whether it carried a deposit, which is now in doubt as well. */
readonly depositInDoubt: boolean;
constructor(init: {
reason: BatchUnresolvedReason;
wallet: string;
requestId?: string;
channelId?: string;
payloadKind?: BatchPayloadKind;
status?: number;
detail?: string;
cause?: unknown;
});
}
/**
* `closeBatchChannel()` did not close anything, and should be called again
* later:
* - `call_in_flight`: a batch call for the wallet is still running (it may
* be waiting out a 429 before its retry or replay, or between the replays
* of a payment in doubt);
* - `deposit_in_doubt`: a deposit the wallet sent (an open or top-up) has
* not been settled yet. It settles once a `finalized` chain read shows it
* landed, or once the finalized chain is past its last valid block,
* usually a few minutes after it was sent.
*
* Not a payment error: nothing was sent.
*/
declare class BatchCloseDeferredError extends BlockrunError {
readonly reason: "call_in_flight" | "deposit_in_doubt";
/** The paying wallet's address. */
readonly wallet: string;
/** The channels whose deposit is in doubt (`deposit_in_doubt`). */
readonly channelIds: string[];
constructor(init: {
reason: "call_in_flight" | "deposit_in_doubt";
wallet: string;
channelIds?: string[];
detail: string;
});
}
/**
* Batch is on, another owner holds this wallet's channel store, and the
* client's `batch.onLocked` is `"wait"` (the wait ran out) or `"throw"`.
* Raised before anything is signed: nothing was paid for the call, and its
* {@link RetryDisposition} is `"unpaid"`.
*/
declare class BatchChannelLockedError extends BlockrunError {
/** The paying wallet's address. */
readonly wallet: string;
/** The live owner's pid, when the SDK knows it. */
readonly ownerPid?: number;
/** How long this call waited for the lock, in ms (0 unless `onLocked` is `"wait"`). */
readonly waitedMs: number;
constructor(init: {
wallet: string;
detail: string;
ownerPid?: number;
waitedMs: number;
});
}
/**
* The outcome of one batch attempt.
*
* - `paid`: served, and paid with batch.
* - `fallback`: pay `exact`. One after a 429 wait carries the fresh 402 it
* fetched, which the `exact` payment must be signed against.
* - `served`: the fresh, unpaid challenge sent after a wait was answered with
* a 2xx. That response is the call's result; nothing was paid, and nothing
* must be.
* - `failed`: that fresh challenge got neither a 402 nor a 2xx (or no answer,
* or a 402 that cannot be read). Raise `error`, which carries the `"unpaid"`
* retry disposition: the 429 waited out proved nothing was charged, so
* nothing was paid for the call. Never pay `exact` against the stale
* challenge instead.
*/
type BatchAttempt = {
kind: "paid";
response: Response;
chargedUsd: number;
} | {
kind: "fallback";
reason: string;
paymentRequired?: PaymentRequired;
} | {
kind: "served";
response: Response;
} | {
kind: "failed";
error: unknown;
};
/**
* The answer to a fresh, unpaid challenge request (see
* {@link SolanaBatchPayer.pay}): a 402 to pay, or a 2xx that served the
* request without a payment. Anything else is thrown.
*/
type Rechallenge = {
kind: "challenge";
paymentRequired: PaymentRequired;
} | {
kind: "served";
response: Response;
};
interface SolanaBatchPayerInit {
options: SolanaBatchOptions;
secretKey: () => Promise<Uint8Array>;
address: () => Promise<string>;
rpcUrl: string;
/** Headers the exact path sends to `rpcUrl` (`rpcHeaders`, `SOLANA_RPC_HEADERS`, `SOLANA_RPC_API_KEY`). */
rpcHeaders?: Record<string, string>;
/**
* Called once, when the payer is constructed, with a registrar for x402
* payment-creation hooks. An embedder that enforces its own spend rules
* registers its hooks here, so they see every batch payment THIS payer signs
* (each one's `selectedRequirements.amount` is the per-call ceiling, not the
* channel deposit) exactly as they see the embedder's own `exact` payments.
*
* The hooks are this payer's own: payers that share a wallet (and so its
* channel and scheme client) never run each other's hooks. A before-hook
* that throws refuses the payment: nothing is signed or sent, and `pay()`
* reports a fallback. An after-hook that throws does the same: the payment
* was signed but is rolled back unsent, so the channel is not left pending,
* and the failure hooks are told. Failure hooks are notifications only: a
* payload one returns as `recovered` is ignored, since a batch payment is
* never replaced by a payment built outside the batch scheme.
*/
configureClient?: (client: X402ClientLike) => void;
}
/**
* The slice of `@x402/core`'s x402Client handed to
* {@link SolanaBatchPayerInit.configureClient}: its payment-creation hooks.
* Structural, so this package's types never require the optional
* `@x402/core` peer to be installed.
*/
interface X402ClientLike {
onBeforePaymentCreation(hook: (context: X402HookContext) => Promise<unknown> | unknown): unknown;
onAfterPaymentCreation(hook: (context: X402HookContext) => Promise<unknown> | unknown): unknown;
onPaymentCreationFailure(hook: (context: X402HookContext) => Promise<unknown> | unknown): unknown;
}
/** What a payment-creation hook receives: at least the requirement being paid. */
interface X402HookContext {
selectedRequirements: PaymentRequirement & {
scheme: string;
network: string;
amount?: string;
};
[key: string]: unknown;
}
/**
* One wallet's batch-settlement payer. Owned by a SolanaLLMClient.
*
* At most one batch request is in flight per wallet: the server-signed scheme
* allows a single pending authorization per channel. Concurrent calls do not
* queue behind it, they pay with `exact`, so batch never makes a parallel
* workload slower than it is today. The exception is a 429 cooldown: then
* every call for the wallet waits it out (within its own `rateLimit` budget)
* rather than send the gateway another channel open it would refuse.
*/
declare class SolanaBatchPayer {
private readonly init;
private state?;
private readonly maxAttempts;
private readonly maxWaitMs;
private readonly inDoubtMaxWaitMs;
private readonly onLocked;
private readonly lockWaitMs;
private readonly counters;
private readonly hooks;
constructor(init: SolanaBatchPayerInit);
/** A copy of this client's batch counters. */
stats(): SolanaBatchStats;
private storeFile;
/**
* This wallet's shared batch state, or why this client cannot use batch:
* another client in this process holds the wallet with a different trust
* configuration, or another live process owns its channel file.
*/
private wallet;
private build;
/** Run a scheme call so its RPC requests carry this client's `rpcHeaders`. */
private withRpcHeaders;
/**
* Drop everything this wallet knows about one channel, on disk and in
* memory, leaving its other channels' records alone. The scheme is rebuilt
* on the next call, without the closed channel in its memory.
*/
private forget;
/**
* Stop trusting the record of the channel a payload paid into: the next
* payment re-reads that channel from the chain first. The scheme has
* already restored (or, for an open, dropped) its record; the book still
* knows the channel and the cumulative it last confirmed.
*/
private distrust;
/**
* The channel's total deposit if a deposit payload landed: the confirmed
* deposit plus this one (for an open, nothing plus this one). Undefined for
* a payment without a deposit, or a channel the SDK has no record of.
*/
private depositExpectation;
/**
* Re-read every distrusted channel from the chain, at `finalized`
* commitment, and rewrite its record: the on-chain deposit, and the highest
* of the cumulative the SDK can vouch for, the cumulative the stored record
* already confirmed, and the on-chain settled amount. A closing or closed
* channel is dropped. The scheme is rebuilt to load the repaired record.
*
* The stored cumulative is never moved backwards. A deposit's intent can
* outlive the receipt that reconciled it (the process died, or the journal
* could not be cleared, after the scheme saved the new state), so its
* cumulative may predate vouchers the scheme has since confirmed; and
* vouchers are redeemed on-chain asynchronously, so `settled` can lag too.
* Rewriting the record with either would leave every later voucher
* unreconcilable.
*
* A deposit in doubt is settled by the chain alone:
* - landed: the finalized channel holds at least the deposit it would
* bring (and at least what the record already claimed);
* - never landed: the finalized chain is {@link LANDING_MARGIN_BLOCKS}
* past the target's anchor height, and a read at least that recent still
* does not show it. An open's record is then dropped; a top-up's channel
* is rewritten with the deposit it really has.
* Until one of those holds, it is pending. This settles only the deposit:
* whether the gateway charged the payment that carried it is never read
* from the chain.
*
* @throws ResyncError when the chain cannot be read, or a deposit may still
* land: the caller pays exact and the re-read is tried again next call.
* Nothing opens or tops up a channel while any re-read is pending.
*/
private resync;
/**
* A distrusted channel's account at `finalized` commitment, checked to be
* this wallet's channel for the record's operator and mint.
*
* @throws ResyncError (`channel_unreadable`) for any other payment channel.
*/
private readOwnChannel;
/**
* Make one event observable three ways: a stderr line (every time, never
* deduplicated), this client's counters, and the caller's `onEvent`.
*/
private report;
/** Report a fallback and tell the caller to pay exact. */
private fallback;
/**
* Pay one 402 with batch-settlement, or say why the caller should use exact.
*
* `send` replays the original request with the payment headers. Every send
* goes through {@link sendOnce}, the one place an answer is classified as
* charged, not charged, or in doubt:
*
* - charged: the 2xx is returned (`paid`);
* - not charged (the gateway's explicit answer to a first send): a 429
* whose receipt proves nothing was broadcast is backed off
* (`Retry-After`, else 1s, 2s, 4s with jitter) and paid again with a new
* payment built from a fresh 402 (`rechallenge`: the original's
* `recentBlockhash` / `recentSlot` can be stale by then), within
* `rateLimit.maxAttempts` / `maxWaitMs`, then with exact; any other is
* paid with exact (`fallback`). A fresh challenge served with a 2xx is
* the call's result (`served`, nothing paid); one that fails otherwise
* is the call's error (`failed`, `"unpaid"`), never an exact payment
* against the stale challenge;
* - in doubt: ends only with a definitive success receipt from a replay
* of the identical payment ({@link resolveInDoubt}: the one replay a
* receipt-less 429 is allowed, or the replays of a payment carrying a
* payment identifier while the gateway says one can succeed, within
* `inDoubt.maxWaitMs`), or a thrown {@link BatchPaymentUnresolvedError}.
* No new authorization, no new deposit, no exact payment and no fallback
* model is ever paid for a call in doubt.
*
* The wallet stays in cooldown after a 429, so its other calls wait
* instead of sending opens of their own. After a deposit the facilitator
* refused before broadcasting it (a 402 with a failed `batch_*` admission
* receipt), its calls do not wait: they pay exact at once
* (`admission_refused`) until the refusal cooldown ends.
*
* @param closesAtStart - {@link closeFence}, taken before the call's first
* (unpaid) request. If a close completed since, the call pays exact
* (`closed_during_call`): it started before the close, so it must not
* open a channel the caller believes is closed.
*/
pay(paymentRequired: PaymentRequired, send: (headers: Record<string, string>) => Promise<Response>, rechallenge: () => Promise<Rechallenge>, closesAtStart?: number): Promise<BatchAttempt>;
/**
* `onLocked: "wait"`: try the wallet's channel store again every
* {@link LOCK_POLL_MS} until its owner lets go of it or `lockWaitMs` runs
* out. Reported once as a `backoff` event (`channel_store_locked`).
*/
private waitForLock;
/** `onLocked` other than `"exact"`: the call's `"unpaid"` error, nothing signed. */
private lockedOut;
/**
* The close fence for a call about to send its first request: how many
* closes this wallet has completed so far, to hand to {@link pay}.
* `close()` refuses while a call is inside `pay()`, but a call still
* waiting for its 402 is not there yet; the fence covers that window.
* Undefined when the wallet cannot be named (no fence then; `pay()`
* reports why batch is unavailable).
*/
closeFence(): Promise<number | undefined>;
/** {@link pay}, for a wallet this client may use. */
private payWith;
/**
* Put the wallet in a 429 cooldown for `delay` ms (`Retry-After`, or the
* default backoff), and return how long this call would have to wait.
*
* The shared cooldown is capped at `rateLimit.maxWaitMs`, so one header,
* however long, never keeps the wallet's calls off batch for longer than
* the retry budget. This call still weighs the full delay: one it cannot
* wait out within its budget is not waited at all, and the caller pays
* exact (or raises a payment in doubt) at once.
*/
private coolDown;
/**
* Try to end a payment in doubt with the gateway's own word.
*
* The one source of evidence this SDK accepts is a definitive success
* receipt on a byte-identical replay of the payment. Any other answer to a
* replay (a 402 or `duplicate_settlement`, which mean the original reached
* the gateway; a 409; another 429; a 5xx; a timeout) leaves it in doubt.
*
* - A payment carrying a payment identifier is replayed for as long as
* the gateway says a later replay can succeed ({@link replayInDoubt}).
* - Any other payment gets a replay only after a 429 without a receipt:
* one, after its backoff (the owner's bounded safety attempt), within
* `rateLimit`. Every other in-doubt outcome is never replayed.
*
* Chain state is not evidence here: it can settle a deposit (see
* `resync`), never whether the gateway charged.
*
* @returns the charged replay, or the doubt to raise.
*/
private resolveInDoubt;
/**
* Replay a payment in doubt that carries a payment identifier until the
* gateway's answer settles it.
*
* A gateway that declares `payment-identifier` stores each paid response
* under its identifier and answers a byte-identical replay of a completed
* request with it, the original receipt included; while the original is
* still running, it answers 409 with a `Retry-After`. So the identical
* payment (the same `PAYMENT-SIGNATURE` header and the same body) is
* replayed for as long as the last answer says a later one can succeed
* ({@link replayMayResolve}), after {@link inDoubtWait} each time, as long
* as the wait fits in `inDoubt.maxWaitMs`, counted from the first doubt:
* every wait (as long as it really took) and every replay's round trip. A
* replay already sent is not cut short. These replays do not count
* against `rateLimit`; a 429 among them still puts the wallet in cooldown.
*
* Every answer that does not settle it is held back from the scheme
* ({@link InDoubt.held}) and handed over only once no replay follows, so
* the scheme rolls the payment back once, or takes the receipt that answer
* carries. That is the last answer, unless an earlier one carried a
* success receipt and the later ones do not: a success receipt is the
* gateway's word that it charged, and handing the scheme a receipt-less
* refusal of a replay instead would roll a real charge back and desync the
* channel's cumulative.
*
* @returns the charged replay, or the doubt to raise.
*/
private replayInDoubt;
/**
* Raise a payment in doubt: release the scheme's hold on it (the channel
* goes back to its confirmed state), put a deposit's channel in doubt so
* it is re-read from the chain before anything pays into it again, report
* the `unresolved` event, and throw {@link BatchPaymentUnresolvedError}.
*/
private raiseUnresolved;
/** Build a new batch payment for a 402: the payload, its headers and its kind. */
private buildPayment;
/**
* Write a deposit's intent to the wallet's journal before it is sent
* (with the file store: the file and its directory fsynced).
*
* @throws when it cannot be recorded; the deposit must then not be sent.
*/
private journalDeposit;
/**
* Remove a deposit's intent once it is reconciled. A failure is logged,
* not thrown: the intent then outlives its deposit, and after a restart
* the chain re-read settles it again.
*/
private forgetIntent;
/**
* THE choke point: send a batch payment once and classify the answer.
*
* The only caller of `send` for a batch payment, and the only place its
* outcome is decided (see {@link SendOutcome}). Every exception once the
* send has started is `in_doubt`, whatever its type or `cause.code`: the
* request may have reached the gateway. "Not charged" is only ever minted
* here, and only for a first send.
*
* @param retry - count this send in `retries` (it follows this call's own 429).
* @param replay - this is a replay of a payment in doubt: only a 2xx with
* a success receipt counts, everything else stays in doubt.
*/
private sendOnce;
/** {@link sendOnce}'s classifier, for an HTTP answer. */
private classify;
/**
* {@link classify}, past a receipt-less 429. With `hold`, the answer is not
* handed to the scheme: its receipt is only read.
*/
private classifyAnswer;
/**
* Build the payment payload, re-reading any distrusted channel from the
* chain first. A stored record the book refuses to hand over (a deposit a
* dead process never heard back about), or one a top-up's read found
* unusable ({@link prepareTopUp}), is re-read and the payload rebuilt
* once.
*/
private createPayload;
/**
* The 402 to build this payment from: with a finalized blockhash
* ({@link withDepositBlockhash}) when the payment will likely carry a
* deposit (no usable record for the trusted accept's channel, so an open,
* or a top-up {@link prepareTopUp} just sized). An authorization signs no
* transaction and costs no RPC read. Best effort: a failed read leaves the
* 402 as it is, and `@x402/svm` fetches its own blockhash.
*/
private withDepositBlockhash;
/**
* Queue a re-read of a stored channel whose account is gone at finalized
* and at confirmed commitment (closed, its account reclaimed), anchored at
* the SDK's own `getBlockHeight` at confirmed. The anchor is saved in the
* record (`reclaimAnchorHeight`), so a short-lived process finishes the
* wait instead of starting it over; a failed read leaves resync to take it,
* and a failed save only costs that. resync pays exact until finality is
* LANDING_MARGIN_BLOCKS past the anchor, then drops the record if the
* finalized chain still has no account (a lagging read finds the channel
* and rewrites the record, without the anchor).
*/
private queueReclaimed;
/**
* After the gateway refused an authorization with a plain 402 (no receipt,
* not an admission refusal), check the channel it paid into. That is how
* the gateway answers an authorization into a channel the facilitator
* closed (its verify rejects it), and a channel with deposit to spare is
* otherwise never read again: every call would sign another authorization
* into it and pay exact. A channel the finalized chain shows closing,
* closed or not this wallet's is re-read through resync (record dropped,
* or kept and paid exact); one gone at every commitment is queued as
* {@link queueReclaimed}; an open one of this wallet's, or one only at
* confirmed (not finalized yet), is left as it is. A queued re-read is run
* at once. Best effort: a failed read changes nothing, and the next
* refusal checks again.
*/
private recheckRefusedChannel;
/**
* When this payment will top up the wallet's channel, read how much of its
* deposit is already settled on-chain (at `finalized` commitment, so never
* more than will stay settled), so `maxDeposit` caps the escrow still at
* stake rather than every deposit the channel ever took.
*
* Only a top-up costs this read (a channel's balance decides it, from the
* stored record). If the read fails, nothing settled is assumed, which is
* the stricter, lifetime cap. If the chain shows a larger deposit than the
* record, the record is behind (a deposit it never heard about landed):
* sizing a top-up from it could exceed `maxDeposit`, so this call pays
* exact and the channel is re-read before the next payment. If
* `@x402/svm`'s channel layout is not the one this SDK decodes, or the
* account at the channel's address cannot be read as a channel, the call
* pays exact (`channel_unreadable`), record kept. If
* the finalized channel is closing or closed, or is not this wallet's for
* the record's operator and mint, no top-up is sized from the record: it
* is re-read at once (`channel_unusable`), see {@link createPayload}. So is
* a channel with no account at finalized or at confirmed commitment (closed
* and reclaimed); resync drops its record only once finality is past its
* own anchor. One that shows only at confirmed is not finalized yet: the
* lifetime cap applies.
*/
private prepareTopUp;
/**
* Hand the gateway's answer to the scheme, which verifies the operator's
* voucher and advances (or rolls back) the channel's local state.
*
* A malformed receipt must not lose a response the caller has been served,
* so a scheme error is logged rather than thrown.
*/
private settle;
/**
* Close the channel the gateway's current challenge names and return its
* unused escrow to the wallet.
*
* The scheme keeps a closed channel in its records, and would go on paying
* into it, so a successful close forgets that channel. The next batch call
* finds no open channel on-chain and opens a new one. Any other channel
* (say, one opened with an operator key that is being rotated out) keeps
* its record, so it can still be closed without an on-chain scan. A
* client-signed channel an earlier SDK version opened is closed too, once
* the trusted server-signed one has no record (see {@link refundTarget}).
*
* @param url - any batch-enabled route on the gateway the channel was opened with.
* @throws BatchCloseDeferredError while a batch call for the wallet is in
* flight, or while any of its deposits is in doubt (until a finalized
* chain read settles it). Nothing is refunded or forgotten then.
*/
close(url: string): Promise<unknown>;
/**
* Re-read every distrusted channel before a close. A deposit still in
* doubt defers the close: refunding (or forgetting) a channel a deposit
* may yet land on could strand that deposit, and after a restart nothing
* would remember it. A record re-read that only failed for another reason
* (an RPC error on a channel with no deposit in doubt) does not.
*/
private settleDoubtsForClose;
/**
* The channel a close targets: the one the gateway's refund challenge
* names, by the scheme's storage key for its trusted server-signed accept
* (network, asset, receiver, fee payer, withdraw delay, receiver
* authorizer, operator). Probed with an unpaid GET, as `@x402/svm`'s own
* refund probes; that accept is then handed to the refund, so upstream
* does not probe again and pick another accept.
*
* A legacy client-signed channel (opened by an earlier SDK version, which
* could pay a client-signed accept; this one never opens one) must stay
* refundable. When the trusted server-signed accept has no stored record,
* or there is none, a client-signed accept in the challenge whose storage
* key names a stored client-signed record is the target instead, and the
* refund runs through `@x402/svm`'s client-signed path (a refund voucher
* signed by the payer at its confirmed cumulative). The signer modes match:
* a server-signed record is never refunded under a client-signed accept.
*/
private refundTarget;
/**
* Get the stored channel `key` ready to refund.
*
* `@x402/svm` refunds a server-signed channel only from the scheme's memory,
* or after an on-chain scan that many RPCs refuse; it never reads the
* stored record. So the target channel is loaded into a freshly built
* scheme, and only that one: upstream's refund falls back to the first
* channel in memory for the same receiver and asset, whatever its operator.
* A record in doubt (a deposit this process never confirmed, or a pending
* deposit left by a process that died) is re-read from the chain before it
* is loaded. If a re-read cannot settle a record, the refund is still
* attempted and may find the channel by its scan.
*/
private prepareClose;
}
interface SolanaLLMClientOptions extends ApiKeyOptions {
/** bs58-encoded Solana secret key (64 bytes). Optional if SOLANA_WALLET_KEY env var is set. */
privateKey?: string;
/** API endpoint URL (default: https://sol.blockrun.ai/api) */
apiUrl?: string;
/**
* Solana JSON-RPC URL. Defaults to BlockRun's own Tatum-backed proxy
* (`https://sol.blockrun.ai/api/v1/solana/rpc`), free for SDK users.
* Override to point at your own Helius / Tatum / QuickNode account, or
* fall back to the env vars `SOLANA_RPC_URL` /
* `SOLANA_RPC_API_KEY` / `SOLANA_RPC_HEADERS`.
*/
rpcUrl?: string;
/**
* Optional headers forwarded to the Solana RPC endpoint. Use this for
* header-auth gateways (Tatum's `x-api-key`, some Triton tiers). Falls
* back to `SOLANA_RPC_HEADERS` (JSON dict) or `SOLANA_RPC_API_KEY`
* (shortcut for `{ "x-api-key": "..." }`) env vars when omitted.
*/
rpcHeaders?: Record<string, string>;
/**
* Request timeout in milliseconds. Default: `BLOCKRUN_CHAT_TIMEOUT` (in
* seconds) when set, else 600000, the same as `LLMClient`: a reasoning
* model's long turn can take minutes, and a batch payment whose call is
* aborted after it was sent ends unresolved.
*/
timeout?: number;
/**
* Opt in to x402 batch-settlement (metered billing) for chat completions.
* Each call is charged what it actually cost, at most its quoted ceiling,
* from a deposit-backed payment channel instead of a per-call transfer.
* Needs the optional peers `@x402/core`, `@x402/svm` and `@solana/kit`.
* Ignored in API-key mode. See {@link SolanaBatchOptions}.
*/
batch?: SolanaBatchOptions;
}
declare class SolanaLLMClient {
static readonly SOLANA_API_URL = "https://sol.blockrun.ai/api";
private apiAuth?;
get authMode(): "api-key" | "wallet";
private _privateKey?;
private get privateKey();
private apiUrl;
private rpcUrl;
private rpcHeaders?;
private timeout;
private sessionTotalUsd;
private sessionCalls;
private addressCache;
private modelPricingCache;
private modelPricingPromise;
private batchPayer?;
constructor(options?: SolanaLLMClientOptions);
/**
* Close this wallet's batch-settlement channel and return its unused escrow.
*
* Closes the channel for the operator the gateway's current 402 names; a
* channel opened with another trusted operator key (during a key rotation)
* keeps its record. The gateway closes it cooperatively when it can;
* otherwise this starts a payer-forced close and the escrow comes back
* after the channel's grace period. Only meaningful with the `batch`
* option.
*
* @throws BatchCloseDeferredError, closing nothing, while a chat call for
* this wallet is still in flight (including one waiting out a 429, so a
* retry can never reopen a channel you just closed), or while one of its
* deposits is in doubt (until a `finalized` chain read shows it landed or
* that it no longer can, usually a few minutes after it was sent). Call
* it again later.
*/
closeBatchChannel(): Promise<unknown>;
/**
* This client's batch-settlement counters: calls that fell back to `exact`
* (in total and by reason), 429 backoffs, batch retries, and recoveries.
* Every one of those is also logged to stderr and passed to `batch.onEvent`.
* Only meaningful with the `batch` option.
*/
getBatchStats(): SolanaBatchStats;
/** Get Solana wallet address (public key in base58). */
getWalletAddress(): Promise<string>;
/** Simple 1-line chat. */
chat(model: string, prompt: string, options?: ChatOptions): Promise<string>;
/** Full chat completion (OpenAI-compatible). */
chatCompletion(model: string, messages: ChatMessage[], options?: ChatCompletionOptions): Promise<ChatResponse>;
private getModelPricing;
/** Inspect a Solana route without making or paying for a model call. */
route(prompt: string, options?: SmartChatOptions): Promise<RoutingDecision>;
/** Smart one-line chat paid on Solana. */
smartChat(prompt: string, options?: SmartChatOptions): Promise<SmartChatResponse>;
/** Smart full message/tool completion paid on Solana. */
smartChatCompletion(messages: ChatMessage[], options?: SmartChatCompletionOptions): Promise<SmartChatCompletionResponse>;
/**
* Stream a Server-Sent Events endpoint, paid on Solana.
*
* The Solana counterpart to `BlockrunClient.stream`, and the reason it had to
* exist: a streaming harness cannot use this client at all without it, so
* "BlockRun supports Solana" stopped being true the moment a caller streamed.
* `chatCompletion` buffers the whole answer, which is the wrong shape for an
* agent loop and for anything that shows tokens as they arrive.
*
* The handshake is the one the non-streaming paths use — `402`, sign an SPL
* TransferChecked authorization locally, replay with `PAYMENT-SIGNATURE` —
* with the same verification-phase re-sign on a stale blockhash. What differs
* is that the paid response is not read as JSON: it is handed to the SSE
* reader with its body untouched.
*
* A `200` on the first request is returned as-is and settles nothing. That is
* the free tier (the gateway answers a `billing_mode: "free"` model without a
* 402 at all) and it is also API-key mode, where billing is on the account
* rather than on a wallet.
*
* Yields each `data:` frame parsed as JSON, and stops at `data: [DONE]`.
* Malformed frames are skipped rather than thrown — see {@link readSseFrames}.
*
* @example
* for await (const chunk of client.stream<ChatChunk>("/v1/chat/completions", {
* model: "deepseek/deepseek-chat",
* messages: [{ role: "user", content: "Hi" }],
* stream: true,
* })) {
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
* }
*
* @param path - endpoint after the API root; a leading `/api` is tolerated.
* @param body - JSON request body. Set `stream: true` yourself — this method
* does not inject it, because the gateway prices a streaming and a
* non-streaming request the same and silently rewriting a caller's body is
* how you end up debugging a request you did not send.
* @returns each decoded SSE frame, in order.
*/
stream<T = unknown>(path: string, body?: Record<string, unknown>): AsyncGenerator<T, void, undefined>;
/**
* Get to a streaming response, paying for it if the gateway asks.
*
* Separate from {@link SolanaLLMClient.stream} because a generator cannot
* retry cleanly around a `yield`: the stale-blockhash re-sign has to finish
* before the first frame is handed out, and putting the loop here keeps the
* payment decision entirely ahead of any output the caller has seen.
*
* @param url - resolved endpoint URL.
* @param requestBody - the serialized body, reused verbatim on the paid retry
* so the gateway prices and answers the same request it quoted for.
* @returns a response whose body has not been read.
*/
private openPaidStream;
/**
* Resolve an endpoint path against this client's API root.
*
* A leading `/api` is stripped for the same reason `BlockrunClient` strips
* it: the documented paths are written `/api/v1/…` on the website and
* `/v1/…` in this SDK, and a caller who copies one into the other should get
* their request rather than a 404.
* @param path - endpoint path, with or without a leading slash.
* @returns the absolute URL to call.
*/
private buildUrl;
/** List available models. */
listModels(): Promise<Model[]>;
/**
* Get Solana USDC balance.
*
* @returns USDC balance as a float
*/
getBalance(options?: {
strict?: boolean;
}): Promise<number>;
/** Edit an image using img2img (Solana payment). */
/**
* Generate an image on Solana.
*
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
* that to completion, re-signing per poll, and settles once at the end. Fast
* models answer 200 inline and settle there.
*/
image(prompt: string, options?: {
model?: string;
size?: string;
n?: number;
} & Record<string, unknown>): Promise<ImageResponse>;
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
/**
* Generate a short video on Solana.
*
* The POST is verified, not charged. The job is then polled with a wallet
* signature (no payment) until it finishes, and ONE payment is signed for
* the finished video; a failed job is never charged. If the gateway does not
* accept poll signatures for the job, it is polled with a payment per poll
* instead, which is likewise only charged on completion.
*
* @param prompt - text description of the video.
* @param options - `model` (default `xai/grok-imagine-video`), `budgetMs`
* (total wait, default 15 min); any other keys are sent verbatim, so use
* the gateway's snake_case names (`duration_seconds`, `image_url`,
* `aspect_ratio`, `resolution`, …).
*/
video(prompt: string, options?: {
model?: string;
budgetMs?: number;
} & Record<string, unknown>): Promise<VideoResponse>;
/**
* Generate a music track on Solana. Paid at the POST; a slow track is then
* polled with a wallet signature, never another payment.
*
* @param prompt - music style, mood, or description.
* @param options - `model` (default `minimax/music-2.5+`), `instrumental`
* (default true), `lyrics` (requires `instrumental: false`), `budgetMs`
* (total wait, default 5 min); other keys are sent verbatim.
*/
music(prompt: string, options?: {
model?: string;
instrumental?: boolean;
lyrics?: string;
budgetMs?: number;
} & Record<string, unknown>): Promise<MusicResponse>;
/** Standalone search (Solana payment). */
search(query: string, options?: SearchOptions): Promise<SearchResult>;
pm(path: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
pmQuery(path: string, query: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Generic Exa endpoint proxy (POST, Solana payment). Powered by Exa.
*
* @param path - Exa endpoint: "search" | "find-similar" | "contents" | "answer"
* @param body - Request body (see Exa API docs)
*
* @example
* const results = await client.exa("search", { query: "latest AI research", numResults: 5 });
*/
exa(path: string, body: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Neural and keyword web search via Exa (Solana payment).
*
* @example
* const results = await client.exaSearch("latest AI papers", { numResults: 5 });
*/
exaSearch(query: string, options?: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Find pages semantically similar to a given URL via Exa (Solana payment).
*
* @example
* const results = await client.exaFindSimilar("https://openai.com/research/gpt-4", { numResults: 5 });
*/
exaFindSimilar(url: string, options?: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Extract full text content from URLs via Exa (Solana payment).
*
* @example
* const data = await client.exaContents(["https://arxiv.org/abs/2303.08774"]);
*/
exaContents(urls: string[], options?: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* AI-generated answer grounded in live web search via Exa (Solana payment).
*
* @example
* const answer = await client.exaAnswer("What is the current state of AI safety research?");
*/
exaAnswer(query: string, options?: Record<string, unknown>): Promise<Record<string, unknown>>;
/**
* Query DefiLlama DeFi data (GET passthrough). Powered by DefiLlama.
* for protocols / protocol/{slug} / chains / yields;
* for prices/{coins}.
*
* @param path - e.g. "protocols", "protocol/aave", "chains", "yields",
* "prices/coingecko:bitcoin,base:0x..."
* @param params - Query parameters passed through to DefiLlama
*/
defi(path: string, params?: Record<string, string>): Promise<Record<string, unknown>>;
/** All DeFi protocols with TVL. */
defiProtocols(): Promise<Record<string, unknown>>;
/** Single protocol details + historical TVL. */
defiProtocol(slug: string): Promise<Record<string, unknown>>;
/** Current TVL of every chain. */
defiChains(): Promise<Record<string, unknown>>;
/** Yield pools with APY/TVL. */
defiYields(params?: Record<string, string>): Promise<Record<string, unknown>>;
/** Token price lookup. Coins like "coingecko:bitcoin" or "{chain}:{address}". */
defiPrices(coins: string | string[]): Promise<Record<string, unknown>>;
/**
* Query the 0x Swap / Gasless APIs (free — no x402 payment; BlockRun
* takes an on-chain affiliate fee on executed swaps instead).
*
* @param path - "price", "quote", "gasless/price", "gasless/quote",
* "gasless/submit" (POST), "gasless/status/{hash}",
* "gasless/approval-tokens", "gasless/chains", "swap/chains"
* @param params - Query params (chainId, sellToken, buyToken, sellAmount, taker, ...)
* @param body - JSON body — pass to switch to POST (gasless/submit only)
*/
dex(path: string, params?: Record<string, string>, body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Indicative Permit2 swap price — no commitment (free). */
dexPrice(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Firm Permit2 swap quote with permit2.eip712 + tx data (free). */
dexQuote(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Gasless indicative price quote (free). */
dexGaslessPrice(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Gasless firm quote — returns trade.eip712 to sign (free). */
dexGaslessQuote(params: Record<string, string>): Promise<Record<string, unknown>>;
/** Submit a signed gasless trade; the 0x relayer pays gas (free). */
dexGaslessSubmit(body: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Poll a gasless trade's status by tradeHash (free). */
dexGaslessStatus(tradeHash: string): Promise<Record<string, unknown>>;
/** Chains where the Swap API is supported (free). */
dexChains(): Promise<Record<string, unknown>>;
/** Chains where the Gasless API is supported (free). */
dexGaslessChains(): Promise<Record<string, unknown>>;
/**
* Call the Modal sandbox compute API (POST passthrough).
*
* @param path - "sandbox/create" (CPU / GPU tiers), "sandbox/exec",
* "sandbox/status", "sandbox/terminate"
* @param body - JSON body for the endpoint
*/
modal(path: string, body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Create a sandboxed compute environment (CPU / GPU tiers). */
modalSandboxCreate(body?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Execute a command in a sandbox; returns stdout/stderr. */
modalSandboxExec(sandboxId: string, command: string[], extra?: Record<string, unknown>): Promise<Record<string, unknown>>;
/** Check a sandbox's status. */
modalSandboxStatus(sandboxId: string): Promise<Record<string, unknown>>;
/** Terminate a sandbox. */
modalSandboxTerminate(sandboxId: string): Promise<Record<string, unknown>>;
/** Get session spending. */
getSpending(): Spending;
/** True if using sol.blockrun.ai. */
isSolana(): boolean;
/**
* Chat request with automatic x402 payment.
*
* Every error it throws carries a {@link RetryDisposition}, which is what
* {@link SolanaLLMClient.chatCompletion}'s fallback walk honours: errors of
* the unpaid first request, its 402 challenge and the signing step are
* `"unpaid"`; anything after a payment was sent (exact or batch) is
* `"paid-or-in-doubt"`, and so is any error with an API key once the billed
* request was sent, except the account API's explicit 4xx refusal.
*/
private requestWithPayment;
/**
* Cost of a chat response served without a payment: 0 with a wallet (a free
* model). With an API key the call is billed to the account, so costUsd stays
* undefined (see the dashboard).
*/
private withUnpaidCost;
/**
* Read the JSON body of a 2xx served without a payment: the free tier, or
* with an API key, where this very request was the billed one.
*/
private readUnpaidJson;
/**
* Send a request that carries no payment, marking what it throws with its
* {@link RetryDisposition}: `"unpaid"` with a wallet; with an API key, see
* {@link accountErrorDisposition} (the request itself is billed).
*/
private sendUnpaid;
/** Read a paid 2xx response's JSON body. The call was charged, so a failure here is never retried. */
private readPaidJson;
/**
* Follow a 202 `{ id, poll_url }` to completion on a Solana media route
* (image, music, video).
*
* **Polls prove ownership, they do not pay.** A Solana payment is a
* transaction pinned to a recent blockhash (~60s), so paying every poll of a
* multi-minute job meant signing a fresh transaction every few seconds. Each
* poll here instead carries the `x-poll-*` headers: the submitting wallet's
* ed25519 signature over `blockrun-poll:v1:<kind>:<job id>:<unix seconds>`
* (see `poll-ownership.ts`). That proof never charges.
*
* **Who pays when:**
* - image / music: the POST already settled (sol settles at submit because the
* signed transaction would be dead by completion). The caller recorded the
* cost at POST; this loop only fetches the result and never signs a payment.
* A batch-billed image job still answers its proof poll with 402: that job
* falls back to the per-poll payment below.
* - video: settles on completion, and a failed job is never charged. A
* finished, unpaid job answers the proof poll with 402 `{status:"completed"}`
* and a normal x402 challenge; ONE payment is signed from that challenge and
* the same poll is re-sent with it. The cost is recorded here, on delivery.
* A paid poll the gateway answers 402 (settlement in progress on another
* poll, outcome unknown, stale blockhash) is re-polled after its
* Retry-After — the gateway's per-job settlement claim makes a later fresh
* payment safe — up to `MAX_PAID_POLL_REFUSALS` times.
*
* **Fallback, once per job:** if the gateway refuses the proof with its "no
* such job" answer (video 400, image/music 404) or answers it with a plain
* payment challenge (a gateway or job that predates poll proofs), the rest of
* this job is polled the old way — an x-payment signed per poll, which the
* gateway verifies against the job's payer and does not charge until a video
* completes. Logged to stderr; never switches back.
*/
private followSolanaJob;
/**
* Absolute URL for a job's `poll_url`. The gateway returns `/api/v1/...`;
* {@link buildUrl} drops the `/api` that `apiUrl` already ends with. An
* absolute poll_url must be on the gateway's own origin: the poll carries a
* wallet signature (and, for a finished video, a payment).
*/
private resolvePollUrl;
/**
* Turn a `402` into a signed Solana payment payload.
*
* Extracted because four call sites need it — chat, the raw POST helpers, the
* raw GET helper, and {@link SolanaLLMClient.stream} — and it had been
* written out three times before this. That mattered more than ordinary
* duplication: this is the code that signs a transfer of the caller's USDC,
* so three copies meant every fix to it had to be applied three times or
* quietly apply to two thirds of the paths.
*
* @param url - the request being paid for.
* @param response - the gateway's `402`, not yet consumed.
* @param forceFreshBlockhash - set on a re-sign after a stale-blockhash
* rejection, so the retry cannot produce byte-identical transaction bytes.
* @param resourceFallback - resource URL to claim when the 402 states none.
* @returns the header value to replay with, and what it will settle for.
* @throws PaymentError when the 402 carries no usable Solana requirements.
*/
private signPaymentFrom402;
/** Read a `402`'s payment requirements from its header, or its JSON body. */
private readPaymentRequired;
/** Sign the `exact` accept of a 402 as an SPL TransferChecked payment. */
private signExactPayment;
private handlePaymentAndRetry;
private requestWithPaymentRaw;
private handlePaymentAndRetryRaw;
private getWithPaymentRaw;
private handleGetPaymentAndRetryRaw;
/**
* Fail a post-payment response, telling a re-signable rejection from a real one.
*
* A `402` here is not "pay again": it is the gateway refusing the payment we
* just signed. Only a rejection the gateway attributes to the VERIFICATION
* phase is safe to re-sign — anything settled, or ambiguous about which
* phase it failed in, could already have moved USDC, and re-signing it would
* pay twice. {@link isSafeStaleBlockhashResponse} is where that judgement
* lives.
* @param response - the reply to the paid request.
* @throws SafeStaleBlockhashError when the caller should re-sign, PaymentError
* when it should not, APIError for any other failure. Every one but the
* first is marked `"paid-or-in-doubt"`: the payment may have settled.
*/
private assertPaid;
/**
* Count one settled x402 payment against the session total.
*
* Takes the paid response so no booking site can forget the check: a
* response whose settlement the gateway skipped ({@link settlementSkipped}:
* `X-Settlement-Skipped: free-fallback | empty-result | unexecuted | …`, or
* `X-Payment-Waived`) moved nothing on chain and books nothing.
* `X-Payment-Settled: false` alone is still booked: the payment is owed.
*/
private recordSettlement;
private fetchWithTimeout;
}
/**
* Convenience function: create SolanaLLMClient for sol.blockrun.ai.
*/
declare function solanaClient(options?: SolanaLLMClientOptions): SolanaLLMClient;
declare const SOLANA_WALLET_FILE: string;
interface SolanaWalletInfo {
privateKey: string;
address: string;
isNew: boolean;
}
/**
* Create a new Solana wallet.
* Requires @solana/web3.js (optional dep) — loaded lazily via dynamic
* import so callers that never touch Solana don't pay the resolution cost
* and ESM consumers don't trip over esbuild's __require shim.
*/
declare function createSolanaWallet(): Promise<{
address: string;
privateKey: string;
}>;
/**
* Convert a Solana private key string to Uint8Array (64 bytes).
* Accepts: bs58-encoded 64-byte key (standard Solana format), the Solana CLI
* JSON byte-array format (~/.config/solana/id.json), or a 64-byte hex string
* (with or without 0x). A 32-byte hex key is rejected with an explicit hint
* that it is the EVM (Base) wallet format, not a Solana key.
*/
declare function solanaKeyToBytes(privateKey: string): Promise<Uint8Array>;
/**
* Get Solana public key (address) from bs58 private key.
*/
declare function solanaPublicKey(privateKey: string): Promise<string>;
declare function saveSolanaWallet(privateKey: string): string;
/**
* Discover ~/.<dir>/solana-wallet.json files from other providers.
*
* Each file should contain JSON with "privateKey" and "address" fields.
* Also checks ~/.brcc/wallet.json for BRCC wallets.
* Results are sorted by modification time (most recent first). Discovery is
* opt-in and never changes the active BlockRun wallet automatically.
*
* @returns Array of wallet objects with secretKey and publicKey
*/
declare function scanSolanaWallets(): Array<{
secretKey: string;
publicKey: string;
source: string;
}>;
declare function loadSolanaWallet(): string | null;
/**
* List Solana wallets from other applications, safe to show to a user.
*
* Solana counterpart of `listDiscoveredWallets()`: no secret key is returned
* and the address is derived from the key rather than trusted from the file.
* Nothing here is active — adopt one with `importSolanaWallet()`.
*
* @returns Discovered wallets as `{ address, source }`, most recent first
*/
declare function listDiscoveredSolanaWallets(): Promise<Array<{
address: string;
source: string;
}>>;
/**
* Adopt a discovered Solana wallet, making it the active BlockRun wallet.
*
* Solana counterpart of `importWallet()`. Matching is done against the address
* derived from each discovered key, and the current
* `~/.blockrun/.solana-session` is backed up before being overwritten.
*
* @param address Address to adopt, as shown by `listDiscoveredSolanaWallets()`
* @returns The adopted address
* @throws If no discovered wallet derives to that address
*/
declare function importSolanaWallet(address: string): Promise<string>;
/**
* Warn when a new Solana wallet was created while provider wallets exist.
*
* Solana counterpart of `formatWalletMigrationNotice`. Addresses are derived
* from the discovered secret key rather than trusted from the file's "address"
* field.
*
* @param newAddress Address of the wallet that was just created
* @returns Formatted notice, or null if nothing was discovered
*/
declare function formatSolanaWalletMigrationNotice(newAddress: string): Promise<string | null>;
declare function getOrCreateSolanaWallet(): Promise<SolanaWalletInfo>;
declare function getCached(key: string): unknown | null;
declare function getCachedByRequest(endpoint: string, body: Record<string, unknown>): unknown | null;
declare function setCache(key: string, data: unknown, ttlMs: number): void;
declare function saveToCache(endpoint: string, body: Record<string, unknown>, response: unknown, costUsd?: number): void;
declare function clearCache(): number;
declare function getCostLogSummary(): {
totalUsd: number;
calls: number;
byEndpoint: Record<string, number>;
};
declare function setupAgentWallet(options?: {
silent?: boolean;
}): LLMClient;
declare function setupAgentSolanaWallet(options?: {
silent?: boolean;
}): Promise<SolanaLLMClient>;
declare function status(): Promise<{
address: string;
balance: number;
}>;
/** Account API when configured; otherwise Solana for new wallets.
* Existing chain preferences and Base-only wallets remain on their chosen chain.
* The named setupAgentWallet/setupAgentSolanaWallet helpers retain their behavior.
*/
declare function setupAgentClient(options?: ApiKeyOptions & {
chain?: 'solana' | 'base';
silent?: boolean;
}): Promise<LLMClient | SolanaLLMClient>;
/** Canonical on-wire schema for cost_log.jsonl entries. */
interface CostEntry {
/** Unix epoch seconds (float, millisecond precision). */
ts: number;
/** API endpoint path, e.g. "/v1/chat/completions". */
endpoint: string;
/** Settled USDC amount (USD, 6-decimal precision). */
cost_usd: number;
/** Model id when known, e.g. "zai/glm-5-turbo". Optional for non-LLM endpoints. */
model?: string;
/** Payer wallet address (EVM 0x... or Solana base58). */
wallet?: string;
/** Network identifier — "eip155:8453" for Base, "eip155:5042" for Arc, "solana-mainnet", etc. */
network?: string;
/** Caller kind for analytics — "LLMClient", "ImageClient", "AgentClient", ... */
client_kind?: string;
}
declare function logCost(entry: CostEntry): void;
declare function getCostSummary(): {
totalUsd: number;
calls: number;
byModel: Record<string, number>;
byEndpoint: Record<string, number>;
};
interface OpenAIClientOptions extends ApiKeyOptions {
/** EVM wallet private key for explicit wallet mode. */
walletKey?: `0x${string}` | string;
/** Alternative: use privateKey like LLMClient */
privateKey?: `0x${string}` | string;
/** API endpoint URL (default: https://blockrun.ai/api) */
baseURL?: string;
/** Request timeout in milliseconds */
timeout?: number;
}
interface OpenAIChatCompletionParams {
model: string;
messages: Array<{
role: "system" | "user" | "assistant" | "tool";
content?: string | null;
name?: string;
tool_call_id?: string;
tool_calls?: ToolCall[];
}>;
max_tokens?: number;
temperature?: number;
response_format?: ResponseFormat;
top_p?: number;
stream?: boolean;
n?: number;
stop?: string | string[];
presence_penalty?: number;
frequency_penalty?: number;
user?: string;
tools?: Tool[];
tool_choice?: ToolChoice;
}
interface OpenAIChatCompletionChoice {
index: number;
message: {
role: "assistant";
content?: string | null;
tool_calls?: ToolCall[];
};
finish_reason: "stop" | "length" | "content_filter" | "tool_calls" | null;
}
interface OpenAIChatCompletionResponse {
id: string;
object: "chat.completion";
created: number;
model: string;
choices: OpenAIChatCompletionChoice[];
usage?: {
prompt_tokens: number;
completion_tokens: number;
total_tokens: number;
};
/** BlockRun extension present when a `blockrun/*` Auto alias was used. */
routing?: RoutingDecision;
}
interface OpenAIChatCompletionChunk {
id: string;
object: "chat.completion.chunk";
created: number;
model: string;
choices: Array<{
index: number;
delta: {
role?: "assistant";
content?: string;
};
finish_reason: string | null;
}>;
}
/**
* Chat completions API (OpenAI-compatible)
*/
declare class ChatCompletions {
private client;
private apiUrl;
private timeout;
constructor(client: LLMClient, apiUrl: string, timeout: number);
/**
* Create a chat completion (OpenAI-compatible).
*/
create(params: OpenAIChatCompletionParams & {
stream: true;
}): Promise<AsyncIterable<OpenAIChatCompletionChunk>>;
create(params: OpenAIChatCompletionParams): Promise<OpenAIChatCompletionResponse>;
private createStream;
private transformResponse;
}
/**
* Chat API namespace
*/
declare class Chat {
completions: ChatCompletions;
constructor(client: LLMClient, apiUrl: string, timeout: number);
}
/**
* OpenAI-compatible client for BlockRun.
*
* Drop-in replacement for the OpenAI SDK.
*
* @example
* import { OpenAI } from '@blockrun/llm';
*
* const client = new OpenAI({ walletKey: '0x...' });
*
* const response = await client.chat.completions.create({
* model: 'gpt-5.2',
* messages: [{ role: 'user', content: 'Hello!' }]
* });
*
* console.log(response.choices[0].message.content);
*/
declare class OpenAI {
chat: Chat;
get authMode(): "api-key" | "wallet";
private client;
constructor(options?: OpenAIClientOptions);
/**
* Get the wallet address being used for payments.
*/
getWalletAddress(): string;
}
interface BlockRunAnthropicOptions extends ApiKeyOptions {
privateKey?: `0x${string}` | string;
apiUrl?: string;
timeout?: number;
}
declare class AnthropicClient {
private _client;
private _clientPromise;
private apiAuth?;
get authMode(): "api-key" | "wallet";
private walletKey?;
private get _privateKey();
private walletAccount?;
private get _account();
private _apiUrl;
private _timeout;
constructor(options?: BlockRunAnthropicOptions);
private _getClient;
private _x402Fetch;
get messages(): _anthropic_ai_sdk.default['messages'];
getWalletAddress(): string;
}
/**
* Input validation and security utilities for BlockRun LLM SDK.
*
* This module provides validation functions to ensure:
* - Private keys are properly formatted
* - API URLs use HTTPS
* - Server responses don't leak sensitive information
* - Resource URLs match expected domains
*/
/**
* Known LLM providers (for optional validation).
*/
declare const KNOWN_PROVIDERS: Set<string>;
/**
* Validates that a model ID is a non-empty string.
*
* @param model - The model ID (e.g., "openai/gpt-5.2", "anthropic/claude-sonnet-4.5")
* @throws {Error} If the model is invalid
*
* @example
* validateModel("openai/gpt-5.2");
*/
declare function validateModel(model: string): void;
/**
* Mirrors the gateway's own contract for `max_tokens`, which is exactly
* `z.number().int().min(1).optional()` — an integer, at least 1, and
* deliberately **no upper bound**.
*
* There is no ceiling here on purpose. The gateway clamps every request with
* `min(requested, model.maxOutput, contextHeadroom)` and then quotes only a
* fraction of the clamped value, so an oversized `maxTokens` cannot inflate a
* payment: 1e9 and 1_000_000 produce a byte-identical quote, because both land
* on the same `model.maxOutput`. A client-side ceiling could therefore only
* ever reject a request the gateway would have accepted and handled correctly.
*
* Earlier versions of this file did carry one, justified in a comment as
* stopping a stray 1e9 from "becoming a payment quote". That was never true —
* it was asserted here without being checked against the route handler, which
* is the same mistake as the old `"maxTokens too large (maximum: 100000)"`
* message that got recorded downstream as an upstream model ceiling. Verify
* against the gateway before reintroducing any number in this file.
*
* @param maxTokens - Maximum number of tokens to generate
* @throws {Error} If maxTokens is not a positive integer
*
* @example
* validateMaxTokens(1000);
*/
declare function validateMaxTokens(maxTokens?: number): void;
/**
* Validates that temperature is a number between 0 and 2.
*
* @param temperature - Sampling temperature (0-2)
* @throws {Error} If temperature is invalid
*
* @example
* validateTemperature(0.7);
*/
declare function validateTemperature(temperature?: number): void;
/**
* Validates that top_p is a number between 0 and 1.
*
* @param topP - Top-p sampling parameter (0-1)
* @throws {Error} If topP is invalid
*
* @example
* validateTopP(0.9);
*/
declare function validateTopP(topP?: number): void;
/**
* Wire format for xAI search on the BlockRun gateways.
*
* The SDK's `SearchParameters` / `SearchSource` types are camelCase, but the
* gateways validate the OpenAI-style snake_case body (`included_x_handles`,
* `from_date`, …) with a non-strict schema that silently strips unknown keys.
* Sending the camelCase object verbatim therefore dropped every filter: an
* "only these X handles since yesterday" search ran over all of X, all time.
* Everything that leaves the SDK goes through {@link toWireSearchParameters}.
*
* Native xAI search tools (`{ type: "x_search" }` / `{ type: "web_search" }`)
* are accepted in `tools` for xai/* models and converted by {@link toWireTools}.
* https://docs.x.ai/developers/tools/x-search
*/
type Wire = Record<string, unknown>;
/** camelCase `SearchParameters` → the gateway's snake_case `search_parameters`. */
declare function toWireSearchParameters(sp: SearchParameters): Wire;
/** Function tools pass through; x_search / web_search tools get xAI's field names. */
declare function toWireTools(tools: AnyTool[]): Wire[];
/**
* What one paid call actually cost, read from the gateway's PAYMENT-RESPONSE.
*
* The x402 receipt header is base64 JSON: `{ success, transaction, network,
* payer, amount?, extra?: { chargedAmount? } }`. Under batch / metered schemes
* the real charge is `extra.chargedAmount` (atomic USDC), else `amount`; under
* `exact` neither is set and the signed quote is the charge. The receipt is
* decoded best-effort: a missing or malformed header never fails the call.
*
* Some gateway routes also (or only) set `X-Payment-Receipt`, whose value is
* the raw settlement transaction hash, not base64 JSON.
*/
/**
* The cost and settlement for one response.
*
* `costUsd` is the charge the receipt states, else `quotedUsd`.
* @param quotedUsd - what was signed (the exact price, or the batch charge the payer reported).
* @param scheme - how it was paid.
*/
declare function readSettlement(response: Response, quotedUsd: number, scheme: Settlement["scheme"]): {
costUsd: number;
settlement: Settlement;
};
export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type AnyTool, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, BLOCKRUN_SOL_OPERATOR, type BarResolution, type BatchAttempt, BatchChannelLockedError, BatchCloseDeferredError, type BatchPayloadKind, BatchPaymentUnresolvedError, type BatchUnresolvedReason, type BlockRunAnthropicOptions, BlockrunClient, type BlockrunClientOptions, BlockrunError, type CallInitiatedResponse, type CallModel, type CallOptions, type CallStatusResponse, type ChatChoice, type ChatCompletionOptions, type ChatMessage, type ChatOptions, type ChatResponse, type ChatResponseWithCost, type ChatUsage, type CostEntry, type CostEstimate, type CreatePaymentOptions, DEFAULT_BATCH_IN_DOUBT, DEFAULT_BATCH_LOCK_WAIT_MS, DEFAULT_BATCH_RATE_LIMIT, EVM_NETWORKS, type EvmNetwork, type FunctionCall, type FunctionDefinition, type HistoryOptions, ImageClient, type ImageClientOptions, type ImageData, type ImageEditOptions, type ImageGenerateOptions, type ImageModel, type ImageResponse, KNOWN_PROVIDERS, LLMClient, type LLMClientOptions, type ListOptions, type MarketSession, type Model, MusicClient, type MusicClientOptions, type MusicGenerateOptions, type MusicResponse, NETWORK_ALIASES, type NewsSearchSource, OpenAI, type OpenAIChatCompletionChoice, type OpenAIChatCompletionChunk, type OpenAIChatCompletionParams, type OpenAIChatCompletionResponse, type OpenAIClientOptions, PHONE_PRICES, PORTAL_URL, PORTRAIT_ENROLLMENT_PRICE_USD, PaymentError, type PaymentLinks, type PhoneBuyOptions, type PhoneBuyResponse, PhoneClient, type PhoneClientOptions, type PhoneListResponse, type PhoneLookupResponse, type PhoneNumberRecord, type PhoneReleaseResponse, type PhoneRenewResponse, type PollOptions, PortraitClient, type PortraitClientOptions, type PortraitEnrollOptions, type PortraitEnrollResponse, type PriceBar, type PriceCategory, PriceClient, type PriceClientOptions, type PriceHistoryResponse, type PriceOptions, type PricePoint, RPC_PRICE_USD, type Rechallenge, type ResponseFormat, RetiredEndpointError, type RetryDisposition, type RoutingDecision, type RoutingProfile, type RoutingTaskType, type RoutingTier, type RoutingTierConfig, type RpcBatchRequest, RpcClient, type RpcClientOptions, type RpcError, type RpcNetwork, type RpcResponse, type RssSearchSource, SOLANA_NETWORK, SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH, SUPPORTED_NETWORKS, SearchClient, type SearchClientOptions, type SearchOptions, type SearchParameters, type SearchResult, type SearchSource, type SearchUsage, type Settlement, type SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, type SolanaBatchEvent, type SolanaBatchOptions, SolanaBatchPayer, type SolanaBatchPayerInit, type SolanaBatchStats, SolanaLLMClient, type SolanaLLMClientOptions, type SolanaWalletInfo, type SoundEffectOptions, type SpeechAudio, SpeechClient, type SpeechClientOptions, type SpeechGenerateOptions, type SpeechModel, type SpeechResponse, type SpeechVoice, type Spending, type SpendingReport, type StockMarket, SurfClient, type SurfClientOptions, type SymbolListResponse, type Tool, type ToolCall, type ToolChoice, USDC_ARC, USDC_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, type UrlCitation, VideoClient, type VideoClientOptions, type VideoClip, type VideoGenerateOptions, type VideoModel, type VideoResponse, VoiceClient, type VoiceClientOptions, type VoiceInfo, type VoicePreset, WALLET_DIR_PATH, WALLET_FILE_PATH, type WalletInfo, type WebSearchSource, type WebSearchTool, type X402ClientLike, type X402HookContext, type XSearchSource, type XSearchTool, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, evmNetwork, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, readSettlement, retryDisposition, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, toWireSearchParameters, toWireTools, validateMaxTokens, validateModel, validateTemperature, validateTopP };