@blockrun/llm 3.15.1 → 3.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -7
- package/dist/index.cjs +162 -11
- package/dist/index.d.cts +74 -4
- package/dist/index.d.ts +74 -4
- package/dist/index.js +158 -11
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
### Cut your LLM bill by <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%. One line of TypeScript.
|
|
6
6
|
|
|
7
|
-
The smart-routing SDK for <!-- br:models.chatVisible -->
|
|
7
|
+
The smart-routing SDK for <!-- br:models.chatVisible -->79<!-- /br:models.chatVisible --> models — every request goes to the cheapest model that can handle it,
|
|
8
8
|
paid with an API key or per-request USDC on Solana or Base. No vendor lock-in.
|
|
9
9
|
|
|
10
10
|
[](https://www.npmjs.com/package/@blockrun/llm)
|
|
@@ -44,7 +44,7 @@ console.log(r.response); // the proof
|
|
|
44
44
|
## Why This SDK
|
|
45
45
|
|
|
46
46
|
- 🧠 **Smart routing that pays for itself** — the bundled [Router Core V3](https://github.com/BlockRunAI/router-core) engine (shared with [ClawRouter](https://github.com/BlockRunAI/ClawRouter)) classifies every request locally in <1ms across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions and routes to the cheapest capable model. The main event.
|
|
47
|
-
- 🆓 **<!-- br:models.free -->
|
|
47
|
+
- 🆓 **<!-- br:models.free -->6<!-- /br:models.free --> genuinely free models** — $0 in and out, incl. two 1M-context Nemotrons, a multimodal one, and free coding models from Cohere and Poolside. No rate-limit gimmicks.
|
|
48
48
|
- 🔐 **Two ways to connect** — a **BlockRun API key** billed against account credit ([sign up at user.blockrun.ai](https://user.blockrun.ai), [create a key](https://user.blockrun.ai/dashboard/keys), [add credit](https://user.blockrun.ai/dashboard/credits)), or a wallet signature with x402 micropayments and no account at all. Same code either way.
|
|
49
49
|
- 💸 **Pay per request in USDC** — x402 micropayments on Solana or Base. $5 covers thousands of requests; agents can pay their own way.
|
|
50
50
|
- 🛡️ **Automatic failover** — transient errors (timeouts, 429, 5xx) walk the router's ranked fallback chain instead of failing your request.
|
|
@@ -56,8 +56,8 @@ console.log(r.response); // the proof
|
|
|
56
56
|
| | OpenAI SDK | OpenRouter | LiteLLM | **@blockrun/llm** |
|
|
57
57
|
| ------------------ | -------------- | ----------------- | ---------------- | ----------------------------------------------------------------------- |
|
|
58
58
|
| **Cost routing** | ✗ one vendor | Manual selection | Manual selection | **Automatic — <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% cheaper** |
|
|
59
|
-
| **Models** | GPT only | 200+ | 100+ (BYO keys) | **<!-- br:models.chatVisible -->
|
|
60
|
-
| **Free tier** | ✗ | Rate-limited | ✗ | **<!-- br:models.free -->
|
|
59
|
+
| **Models** | GPT only | 200+ | 100+ (BYO keys) | **<!-- br:models.chatVisible -->79<!-- /br:models.chatVisible -->, one credential** |
|
|
60
|
+
| **Free tier** | ✗ | Rate-limited | ✗ | **<!-- br:models.free -->6<!-- /br:models.free --> models, no signup** |
|
|
61
61
|
| **Auth** | API key | Account + API key | Your API keys | **API key *or* wallet signature** |
|
|
62
62
|
| **Payment** | Card + invoice | Credit card | BYO keys | **Account credit or USDC per-request** |
|
|
63
63
|
| **Agent-ready** | ✗ | ✗ | ✗ | **✓ — one key, or agents fund their own wallet** |
|
|
@@ -313,7 +313,7 @@ const reply = await client.chat('nvidia/nemotron-3.5-lightning', 'hello', {
|
|
|
313
313
|
|
|
314
314
|
| Profile | Strategy | Savings vs Opus 5 | Best For |
|
|
315
315
|
|---------|----------|-------------------|----------|
|
|
316
|
-
| `eco` | Cheapest capable model — ranks the <!-- br:models.free -->
|
|
316
|
+
| `eco` | Cheapest capable model — ranks the <!-- br:models.free -->6<!-- /br:models.free -->-model free tier first | **<!-- br:savings.ecoVsBaselinePct -->98<!-- /br:savings.ecoVsBaselinePct -->%** | Cost-sensitive production, zero-cost testing |
|
|
317
317
|
| `auto` | Best balance of cost/quality (default) | **<!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->%** | General use |
|
|
318
318
|
| `premium` | Top-tier models (OpenAI, Anthropic) | 0% | Quality-critical tasks |
|
|
319
319
|
|
|
@@ -444,6 +444,31 @@ const tweet = await client.chat('xai/grok-4.5', 'What is trending on X?', { sear
|
|
|
444
444
|
**Supported endpoint:** `https://sol.blockrun.ai/api`
|
|
445
445
|
**Payment:** Solana USDC (SPL, mainnet)
|
|
446
446
|
|
|
447
|
+
## Arc Support
|
|
448
|
+
|
|
449
|
+
The same `LLMClient` pays on [Circle's Arc](https://www.arc.network) via [arc.blockrun.ai](https://arc.blockrun.ai) — point `apiUrl` at it and hold USDC on Arc in the same EVM wallet:
|
|
450
|
+
|
|
451
|
+
```typescript
|
|
452
|
+
import { LLMClient } from '@blockrun/llm';
|
|
453
|
+
|
|
454
|
+
const client = new LLMClient({
|
|
455
|
+
privateKey: process.env.BLOCKRUN_WALLET_KEY,
|
|
456
|
+
apiUrl: 'https://arc.blockrun.ai/api',
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
const response = await client.chat('openai/gpt-4o', 'gm Arc');
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
The 402 from that host names `eip155:5042`, and the SDK signs the EIP-3009 authorization against Arc's USDC (`0x3600…0000`, EIP-712 domain `USDC` v2) — never Base's. Circle's facilitator verifies and settles it on Arc; you pay no gas. Which networks the SDK can sign for is the exported `EVM_NETWORKS` table (Base, Arc, Base Sepolia); a 402 naming any other network, or a non-USDC asset, is refused before anything is signed.
|
|
463
|
+
|
|
464
|
+
**Setup:**
|
|
465
|
+
1. Same wallet key as Base: `export BLOCKRUN_WALLET_KEY="0x..."`
|
|
466
|
+
2. Fund it with USDC on Arc (Arc's native token, shown as the ERC-20 at `0x3600…0000`)
|
|
467
|
+
3. `apiUrl: 'https://arc.blockrun.ai/api'` — payments are automatic via x402
|
|
468
|
+
|
|
469
|
+
**Supported endpoint:** `https://arc.blockrun.ai/api`
|
|
470
|
+
**Payment:** USDC on Arc (chain 5042), settled by Circle
|
|
471
|
+
|
|
447
472
|
## How Payment Works
|
|
448
473
|
|
|
449
474
|
In wallet mode, no API key is required. You hold USDC in your own wallet, and **every request pays for itself** with an on-chain micropayment. Two phases:
|
|
@@ -1158,7 +1183,7 @@ const free = await client.chat('nvidia/nemotron-3.5-lightning', 'Hello!');
|
|
|
1158
1183
|
|
|
1159
1184
|
| Profile | Description | Best For |
|
|
1160
1185
|
|---------|-------------|----------|
|
|
1161
|
-
| `eco` | Budget-optimized — ranks the <!-- br:models.free -->
|
|
1186
|
+
| `eco` | Budget-optimized — ranks the <!-- br:models.free -->6<!-- /br:models.free -->-model free tier first | Cost-sensitive workloads, zero-cost testing |
|
|
1162
1187
|
| `auto` | Intelligent routing (default) | General use |
|
|
1163
1188
|
| `premium` | Best quality models | Critical tasks |
|
|
1164
1189
|
|
|
@@ -1749,7 +1774,7 @@ The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch
|
|
|
1749
1774
|
## Frequently Asked Questions
|
|
1750
1775
|
|
|
1751
1776
|
### What is @blockrun/llm?
|
|
1752
|
-
@blockrun/llm is a TypeScript SDK that cuts LLM costs by up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% with built-in smart routing: every request is routed to the cheapest of <!-- br:models.chatVisible -->
|
|
1777
|
+
@blockrun/llm is a TypeScript SDK that cuts LLM costs by up to <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% with built-in smart routing: every request is routed to the cheapest of <!-- br:models.chatVisible -->79<!-- /br:models.chatVisible --> models (OpenAI, Anthropic, Google, xAI, DeepSeek, Moonshot, and more) that can handle it, then paid per-request in USDC via the x402 protocol — with API key account billing or x402 wallet payments on Solana or Base.
|
|
1753
1778
|
|
|
1754
1779
|
### How does payment work?
|
|
1755
1780
|
When you make an API call, the SDK automatically handles x402 payment. It signs a USDC transaction locally using your wallet private key (which never leaves your machine), and includes the payment proof in the request header. Settlement is non-custodial and instant on Base or Solana.
|
package/dist/index.cjs
CHANGED
|
@@ -32,10 +32,12 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
APIError: () => APIError,
|
|
34
34
|
API_KEY_URL: () => API_KEY_URL,
|
|
35
|
+
ARC_CHAIN_ID: () => ARC_CHAIN_ID,
|
|
35
36
|
AnthropicClient: () => AnthropicClient,
|
|
36
37
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
37
38
|
BlockrunClient: () => BlockrunClient,
|
|
38
39
|
BlockrunError: () => BlockrunError,
|
|
40
|
+
EVM_NETWORKS: () => EVM_NETWORKS,
|
|
39
41
|
ImageClient: () => ImageClient,
|
|
40
42
|
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
41
43
|
LLMClient: () => LLMClient,
|
|
@@ -59,6 +61,7 @@ __export(index_exports, {
|
|
|
59
61
|
SolanaLLMClient: () => SolanaLLMClient,
|
|
60
62
|
SpeechClient: () => SpeechClient,
|
|
61
63
|
SurfClient: () => SurfClient,
|
|
64
|
+
USDC_ARC: () => USDC_ARC,
|
|
62
65
|
USDC_BASE: () => USDC_BASE,
|
|
63
66
|
USDC_BASE_CONTRACT: () => USDC_BASE_CONTRACT,
|
|
64
67
|
USDC_SOLANA: () => USDC_SOLANA,
|
|
@@ -72,6 +75,7 @@ __export(index_exports, {
|
|
|
72
75
|
createSolanaWallet: () => createSolanaWallet,
|
|
73
76
|
createWallet: () => createWallet,
|
|
74
77
|
default: () => client_default,
|
|
78
|
+
evmNetwork: () => evmNetwork,
|
|
75
79
|
extractPaymentDetails: () => extractPaymentDetails,
|
|
76
80
|
formatFundingMessageCompact: () => formatFundingMessageCompact,
|
|
77
81
|
formatNeedsFundingMessage: () => formatNeedsFundingMessage,
|
|
@@ -4312,6 +4316,42 @@ async function loadSplToken(what) {
|
|
|
4312
4316
|
// src/x402.ts
|
|
4313
4317
|
var BASE_CHAIN_ID = 8453;
|
|
4314
4318
|
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
4319
|
+
var ARC_CHAIN_ID = 5042;
|
|
4320
|
+
var USDC_ARC = "0x3600000000000000000000000000000000000000";
|
|
4321
|
+
var EVM_NETWORKS = {
|
|
4322
|
+
"eip155:8453": {
|
|
4323
|
+
name: "Base",
|
|
4324
|
+
chainId: BASE_CHAIN_ID,
|
|
4325
|
+
usdc: USDC_BASE,
|
|
4326
|
+
domain: { name: "USD Coin", version: "2", chainId: BASE_CHAIN_ID, verifyingContract: USDC_BASE }
|
|
4327
|
+
},
|
|
4328
|
+
"eip155:5042": {
|
|
4329
|
+
name: "Arc",
|
|
4330
|
+
chainId: ARC_CHAIN_ID,
|
|
4331
|
+
usdc: USDC_ARC,
|
|
4332
|
+
domain: { name: "USDC", version: "2", chainId: ARC_CHAIN_ID, verifyingContract: USDC_ARC }
|
|
4333
|
+
},
|
|
4334
|
+
"eip155:84532": {
|
|
4335
|
+
name: "Base Sepolia",
|
|
4336
|
+
chainId: 84532,
|
|
4337
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
4338
|
+
domain: {
|
|
4339
|
+
name: "USDC",
|
|
4340
|
+
version: "2",
|
|
4341
|
+
chainId: 84532,
|
|
4342
|
+
verifyingContract: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
4345
|
+
};
|
|
4346
|
+
function evmNetwork(network) {
|
|
4347
|
+
const net = EVM_NETWORKS[network];
|
|
4348
|
+
if (!net) {
|
|
4349
|
+
throw new Error(
|
|
4350
|
+
`Unsupported x402 network "${network}": this SDK signs USDC payments on ${Object.keys(EVM_NETWORKS).join(", ")}`
|
|
4351
|
+
);
|
|
4352
|
+
}
|
|
4353
|
+
return net;
|
|
4354
|
+
}
|
|
4315
4355
|
var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
4316
4356
|
var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
4317
4357
|
var SOLANA_USDC_DECIMALS = 6;
|
|
@@ -4355,12 +4395,6 @@ async function getBlockhashEntry(connection, rpcUrl, forceRefresh) {
|
|
|
4355
4395
|
trimOldest(blockhashCache, MAX_TRACKED_ENDPOINTS);
|
|
4356
4396
|
return entry;
|
|
4357
4397
|
}
|
|
4358
|
-
var USDC_DOMAIN = {
|
|
4359
|
-
name: "USD Coin",
|
|
4360
|
-
version: "2",
|
|
4361
|
-
chainId: BASE_CHAIN_ID,
|
|
4362
|
-
verifyingContract: USDC_BASE
|
|
4363
|
-
};
|
|
4364
4398
|
var TRANSFER_TYPES = {
|
|
4365
4399
|
TransferWithAuthorization: [
|
|
4366
4400
|
{ name: "from", type: "address" },
|
|
@@ -4391,7 +4425,13 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
4391
4425
|
const validAfter = now - 600;
|
|
4392
4426
|
const validBefore = now + (options.maxTimeoutSeconds || 300);
|
|
4393
4427
|
const nonce = createNonce();
|
|
4394
|
-
const
|
|
4428
|
+
const net = evmNetwork(network);
|
|
4429
|
+
if (options.asset && options.asset.toLowerCase() !== net.usdc.toLowerCase()) {
|
|
4430
|
+
throw new Error(
|
|
4431
|
+
`x402 asset mismatch: the 402 asks for ${options.asset} on ${network}, but this SDK only pays USDC there (${net.usdc})`
|
|
4432
|
+
);
|
|
4433
|
+
}
|
|
4434
|
+
const domain = net.domain;
|
|
4395
4435
|
const signature = await (0, import_accounts.signTypedData)({
|
|
4396
4436
|
privateKey,
|
|
4397
4437
|
domain,
|
|
@@ -4417,10 +4457,10 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
4417
4457
|
scheme: "exact",
|
|
4418
4458
|
network,
|
|
4419
4459
|
amount,
|
|
4420
|
-
asset:
|
|
4460
|
+
asset: net.usdc,
|
|
4421
4461
|
payTo: recipient,
|
|
4422
4462
|
maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
|
|
4423
|
-
extra: { name:
|
|
4463
|
+
extra: { name: net.domain.name, version: net.domain.version }
|
|
4424
4464
|
},
|
|
4425
4465
|
payload: {
|
|
4426
4466
|
signature,
|
|
@@ -4620,7 +4660,7 @@ function getCostSummary() {
|
|
|
4620
4660
|
}
|
|
4621
4661
|
|
|
4622
4662
|
// src/version.ts
|
|
4623
|
-
var SDK_VERSION = "3.
|
|
4663
|
+
var SDK_VERSION = "3.16.0";
|
|
4624
4664
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4625
4665
|
|
|
4626
4666
|
// src/client.ts
|
|
@@ -5080,6 +5120,7 @@ var LLMClient = class _LLMClient {
|
|
|
5080
5120
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5081
5121
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5082
5122
|
extra: details.extra,
|
|
5123
|
+
asset: details.asset,
|
|
5083
5124
|
extensions
|
|
5084
5125
|
}
|
|
5085
5126
|
);
|
|
@@ -5165,6 +5206,7 @@ var LLMClient = class _LLMClient {
|
|
|
5165
5206
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5166
5207
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5167
5208
|
extra: details.extra,
|
|
5209
|
+
asset: details.asset,
|
|
5168
5210
|
extensions
|
|
5169
5211
|
}
|
|
5170
5212
|
);
|
|
@@ -5376,6 +5418,7 @@ var LLMClient = class _LLMClient {
|
|
|
5376
5418
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5377
5419
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5378
5420
|
extra: details.extra,
|
|
5421
|
+
asset: details.asset,
|
|
5379
5422
|
extensions
|
|
5380
5423
|
}
|
|
5381
5424
|
);
|
|
@@ -5523,6 +5566,7 @@ var LLMClient = class _LLMClient {
|
|
|
5523
5566
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5524
5567
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5525
5568
|
extra: details.extra,
|
|
5569
|
+
asset: details.asset,
|
|
5526
5570
|
extensions
|
|
5527
5571
|
}
|
|
5528
5572
|
);
|
|
@@ -6316,6 +6360,7 @@ var ImageClient = class {
|
|
|
6316
6360
|
resourceDescription: details.resource?.description || "BlockRun Image Generation",
|
|
6317
6361
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
6318
6362
|
extra: details.extra,
|
|
6363
|
+
asset: details.asset,
|
|
6319
6364
|
extensions
|
|
6320
6365
|
}
|
|
6321
6366
|
);
|
|
@@ -8670,6 +8715,15 @@ var BlockrunClient = class {
|
|
|
8670
8715
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
8671
8716
|
* completed — upstream failure or caller giving up = no charge.
|
|
8672
8717
|
*
|
|
8718
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
8719
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
8720
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
8721
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
8722
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
8723
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
8724
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
8725
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
8726
|
+
*
|
|
8673
8727
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
8674
8728
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8675
8729
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -8923,7 +8977,8 @@ var BlockrunClient = class {
|
|
|
8923
8977
|
details.maxTimeoutSeconds || 0,
|
|
8924
8978
|
opts.maxTimeoutSeconds || 300
|
|
8925
8979
|
),
|
|
8926
|
-
extra: details.extra
|
|
8980
|
+
extra: details.extra,
|
|
8981
|
+
asset: details.asset
|
|
8927
8982
|
}
|
|
8928
8983
|
);
|
|
8929
8984
|
}
|
|
@@ -9903,6 +9958,18 @@ var SolanaLLMClient = class {
|
|
|
9903
9958
|
}
|
|
9904
9959
|
}
|
|
9905
9960
|
/** Edit an image using img2img (Solana payment). */
|
|
9961
|
+
/**
|
|
9962
|
+
* Generate an image on Solana.
|
|
9963
|
+
*
|
|
9964
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
9965
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
9966
|
+
* models answer 200 inline and settle there.
|
|
9967
|
+
*/
|
|
9968
|
+
async image(prompt, options = {}) {
|
|
9969
|
+
const body = { prompt, ...options };
|
|
9970
|
+
const data = await this.requestWithPaymentRaw("/v1/images/generations", body);
|
|
9971
|
+
return data;
|
|
9972
|
+
}
|
|
9906
9973
|
async imageEdit(prompt, image, options) {
|
|
9907
9974
|
const body = {
|
|
9908
9975
|
model: options?.model || "openai/gpt-image-2",
|
|
@@ -10150,6 +10217,81 @@ var SolanaLLMClient = class {
|
|
|
10150
10217
|
* @returns the header value to replay with, and what it will settle for.
|
|
10151
10218
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
10152
10219
|
*/
|
|
10220
|
+
/**
|
|
10221
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
10222
|
+
*
|
|
10223
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
10224
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
10225
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
10226
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
10227
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
10228
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
10229
|
+
* so signing per poll costs signatures, never money.
|
|
10230
|
+
*/
|
|
10231
|
+
async followSolanaJob(submitBody, budgetMs, intervalMs = 2e3) {
|
|
10232
|
+
const id = submitBody.id;
|
|
10233
|
+
const pollPath = submitBody.poll_url;
|
|
10234
|
+
if (!id || !pollPath) return submitBody;
|
|
10235
|
+
const pollUrl = pollPath.startsWith("http") ? pollPath : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
10236
|
+
const deadline = Date.now() + budgetMs;
|
|
10237
|
+
let lastStatus = submitBody.status || "queued";
|
|
10238
|
+
while (Date.now() < deadline) {
|
|
10239
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
10240
|
+
const challenge = await this.fetchWithTimeout(pollUrl, {
|
|
10241
|
+
method: "GET",
|
|
10242
|
+
headers: { "User-Agent": USER_AGENT }
|
|
10243
|
+
});
|
|
10244
|
+
if (challenge.status === 200) {
|
|
10245
|
+
const done = await challenge.json();
|
|
10246
|
+
return done;
|
|
10247
|
+
}
|
|
10248
|
+
if (challenge.status !== 402) {
|
|
10249
|
+
let errorBody;
|
|
10250
|
+
try {
|
|
10251
|
+
errorBody = await challenge.json();
|
|
10252
|
+
} catch {
|
|
10253
|
+
errorBody = { error: "Poll failed" };
|
|
10254
|
+
}
|
|
10255
|
+
throw new APIError(`Poll failed: ${challenge.status}`, challenge.status, sanitizeErrorResponse(errorBody));
|
|
10256
|
+
}
|
|
10257
|
+
const { paymentPayload, costUsd } = await this.signPaymentFrom402(
|
|
10258
|
+
pollUrl,
|
|
10259
|
+
challenge,
|
|
10260
|
+
true,
|
|
10261
|
+
// always a fresh blockhash; the last one is stale by now
|
|
10262
|
+
pollUrl
|
|
10263
|
+
);
|
|
10264
|
+
const paid = await this.fetchWithTimeout(pollUrl, {
|
|
10265
|
+
method: "GET",
|
|
10266
|
+
headers: { "User-Agent": USER_AGENT, "PAYMENT-SIGNATURE": paymentPayload }
|
|
10267
|
+
});
|
|
10268
|
+
let data = {};
|
|
10269
|
+
try {
|
|
10270
|
+
data = await paid.json();
|
|
10271
|
+
} catch {
|
|
10272
|
+
}
|
|
10273
|
+
lastStatus = data.status || lastStatus;
|
|
10274
|
+
if (lastStatus === "failed") {
|
|
10275
|
+
throw new APIError(
|
|
10276
|
+
`Upstream job failed: ${data.error || "unknown"}`,
|
|
10277
|
+
paid.status,
|
|
10278
|
+
sanitizeErrorResponse(data)
|
|
10279
|
+
);
|
|
10280
|
+
}
|
|
10281
|
+
if (paid.status === 200 && lastStatus === "completed") {
|
|
10282
|
+
this.recordSettlement(costUsd);
|
|
10283
|
+
return data;
|
|
10284
|
+
}
|
|
10285
|
+
if (paid.status !== 200 && paid.status !== 202 && paid.status !== 504) {
|
|
10286
|
+
throw new APIError(`Poll failed: ${paid.status}`, paid.status, sanitizeErrorResponse(data));
|
|
10287
|
+
}
|
|
10288
|
+
}
|
|
10289
|
+
throw new APIError(
|
|
10290
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
10291
|
+
504,
|
|
10292
|
+
{ id, last_status: lastStatus }
|
|
10293
|
+
);
|
|
10294
|
+
}
|
|
10153
10295
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10154
10296
|
let paymentHeader = response.headers.get("payment-required");
|
|
10155
10297
|
if (!paymentHeader) {
|
|
@@ -10260,6 +10402,10 @@ var SolanaLLMClient = class {
|
|
|
10260
10402
|
body: JSON.stringify(body)
|
|
10261
10403
|
});
|
|
10262
10404
|
await this.assertPaid(retryResponse);
|
|
10405
|
+
if (retryResponse.status === 202) {
|
|
10406
|
+
const submitted = await retryResponse.json();
|
|
10407
|
+
return this.followSolanaJob(submitted, this.timeout);
|
|
10408
|
+
}
|
|
10263
10409
|
this.recordSettlement(costUsd);
|
|
10264
10410
|
return retryResponse.json();
|
|
10265
10411
|
}
|
|
@@ -10854,6 +11000,7 @@ var AnthropicClient = class {
|
|
|
10854
11000
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
10855
11001
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
10856
11002
|
extra: details.extra,
|
|
11003
|
+
asset: details.asset,
|
|
10857
11004
|
extensions
|
|
10858
11005
|
}
|
|
10859
11006
|
);
|
|
@@ -10896,10 +11043,12 @@ var AnthropicClient = class {
|
|
|
10896
11043
|
0 && (module.exports = {
|
|
10897
11044
|
APIError,
|
|
10898
11045
|
API_KEY_URL,
|
|
11046
|
+
ARC_CHAIN_ID,
|
|
10899
11047
|
AnthropicClient,
|
|
10900
11048
|
BASE_CHAIN_ID,
|
|
10901
11049
|
BlockrunClient,
|
|
10902
11050
|
BlockrunError,
|
|
11051
|
+
EVM_NETWORKS,
|
|
10903
11052
|
ImageClient,
|
|
10904
11053
|
KNOWN_PROVIDERS,
|
|
10905
11054
|
LLMClient,
|
|
@@ -10923,6 +11072,7 @@ var AnthropicClient = class {
|
|
|
10923
11072
|
SolanaLLMClient,
|
|
10924
11073
|
SpeechClient,
|
|
10925
11074
|
SurfClient,
|
|
11075
|
+
USDC_ARC,
|
|
10926
11076
|
USDC_BASE,
|
|
10927
11077
|
USDC_BASE_CONTRACT,
|
|
10928
11078
|
USDC_SOLANA,
|
|
@@ -10935,6 +11085,7 @@ var AnthropicClient = class {
|
|
|
10935
11085
|
createSolanaPaymentPayload,
|
|
10936
11086
|
createSolanaWallet,
|
|
10937
11087
|
createWallet,
|
|
11088
|
+
evmNetwork,
|
|
10938
11089
|
extractPaymentDetails,
|
|
10939
11090
|
formatFundingMessageCompact,
|
|
10940
11091
|
formatNeedsFundingMessage,
|
package/dist/index.d.cts
CHANGED
|
@@ -1164,7 +1164,9 @@ declare class APIError extends BlockrunError {
|
|
|
1164
1164
|
* BlockRun LLM Gateway Client.
|
|
1165
1165
|
*
|
|
1166
1166
|
* Provides access to multiple LLM providers (OpenAI, Anthropic, Google, etc.)
|
|
1167
|
-
* with automatic x402 micropayments on Base chain
|
|
1167
|
+
* with automatic x402 micropayments in USDC on Base (chain 8453) or, with
|
|
1168
|
+
* `apiUrl` pointed at arc.blockrun.ai, on Circle's Arc (chain 5042) — the
|
|
1169
|
+
* 402's `network` selects the chain (see EVM_NETWORKS in x402.ts).
|
|
1168
1170
|
* API base: https://blockrun.ai/api
|
|
1169
1171
|
*/
|
|
1170
1172
|
declare class LLMClient {
|
|
@@ -2262,6 +2264,15 @@ declare class BlockrunClient {
|
|
|
2262
2264
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
2263
2265
|
* completed — upstream failure or caller giving up = no charge.
|
|
2264
2266
|
*
|
|
2267
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
2268
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
2269
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
2270
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
2271
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
2272
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
2273
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
2274
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
2275
|
+
*
|
|
2265
2276
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
2266
2277
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
2267
2278
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -2312,16 +2323,51 @@ declare class BlockrunClient {
|
|
|
2312
2323
|
|
|
2313
2324
|
declare const BASE_CHAIN_ID = 8453;
|
|
2314
2325
|
declare const USDC_BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2326
|
+
declare const ARC_CHAIN_ID = 5042;
|
|
2327
|
+
declare const USDC_ARC: "0x3600000000000000000000000000000000000000";
|
|
2328
|
+
/**
|
|
2329
|
+
* The EVM networks a BlockRun gateway settles on, keyed by the CAIP-2
|
|
2330
|
+
* `network` a 402 carries, each with the SDK's OWN values for that network's
|
|
2331
|
+
* USDC and its EIP-712 domain.
|
|
2332
|
+
*
|
|
2333
|
+
* Until 3.16.0 there was one domain — Base's — signed whatever the 402 said,
|
|
2334
|
+
* so against arc.blockrun.ai (eip155:5042) or testnet.blockrun.ai every
|
|
2335
|
+
* payment was a signature over the wrong domain: the facilitator recovered a
|
|
2336
|
+
* different signer and answered 401 after the SDK had reported a payment.
|
|
2337
|
+
*
|
|
2338
|
+
* The 402 SELECTS a network from this table; it never supplies the domain.
|
|
2339
|
+
* That keeps the rule the single constant enforced — a hostile 402's `extra`
|
|
2340
|
+
* cannot steer a signature onto another contract — while letting the same
|
|
2341
|
+
* SDK pay on every host. Arc's USDC is the chain's native token exposed as an
|
|
2342
|
+
* ERC-20 at 0x3600…0000, and its domain name is "USDC", not Base's "USD Coin".
|
|
2343
|
+
*/
|
|
2344
|
+
interface EvmNetwork {
|
|
2345
|
+
name: string;
|
|
2346
|
+
chainId: number;
|
|
2347
|
+
usdc: `0x${string}`;
|
|
2348
|
+
domain: {
|
|
2349
|
+
name: string;
|
|
2350
|
+
version: string;
|
|
2351
|
+
chainId: number;
|
|
2352
|
+
verifyingContract: `0x${string}`;
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
declare const EVM_NETWORKS: Readonly<Record<string, EvmNetwork>>;
|
|
2356
|
+
/** The network table entry for a 402's `network`, or a refusal that names what IS supported. */
|
|
2357
|
+
declare function evmNetwork(network: string): EvmNetwork;
|
|
2315
2358
|
declare const SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
2316
2359
|
declare const USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
2317
2360
|
interface CreatePaymentOptions {
|
|
2318
2361
|
resourceUrl?: string;
|
|
2319
2362
|
resourceDescription?: string;
|
|
2320
2363
|
maxTimeoutSeconds?: number;
|
|
2364
|
+
/** The 402's `extra`. Echoed nowhere: the domain comes from EVM_NETWORKS. */
|
|
2321
2365
|
extra?: {
|
|
2322
2366
|
name?: string;
|
|
2323
2367
|
version?: string;
|
|
2324
2368
|
};
|
|
2369
|
+
/** The 402's `asset`. Checked against the network's USDC; a mismatch is refused. */
|
|
2370
|
+
asset?: string;
|
|
2325
2371
|
extensions?: Record<string, unknown>;
|
|
2326
2372
|
}
|
|
2327
2373
|
/**
|
|
@@ -2331,7 +2377,7 @@ interface CreatePaymentOptions {
|
|
|
2331
2377
|
* @param fromAddress - Sender wallet address
|
|
2332
2378
|
* @param recipient - Payment recipient address
|
|
2333
2379
|
* @param amount - Amount in micro USDC (6 decimals)
|
|
2334
|
-
* @param network -
|
|
2380
|
+
* @param network - CAIP-2 network from the 402 (default: eip155:8453); must be in EVM_NETWORKS
|
|
2335
2381
|
* @param options - Additional options for resource info
|
|
2336
2382
|
* @returns Base64-encoded signed payment payload
|
|
2337
2383
|
*/
|
|
@@ -2731,6 +2777,18 @@ declare class SolanaLLMClient {
|
|
|
2731
2777
|
*/
|
|
2732
2778
|
getBalance(): Promise<number>;
|
|
2733
2779
|
/** Edit an image using img2img (Solana payment). */
|
|
2780
|
+
/**
|
|
2781
|
+
* Generate an image on Solana.
|
|
2782
|
+
*
|
|
2783
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
2784
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
2785
|
+
* models answer 200 inline and settle there.
|
|
2786
|
+
*/
|
|
2787
|
+
image(prompt: string, options?: {
|
|
2788
|
+
model?: string;
|
|
2789
|
+
size?: string;
|
|
2790
|
+
n?: number;
|
|
2791
|
+
} & Record<string, unknown>): Promise<ImageResponse>;
|
|
2734
2792
|
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
|
|
2735
2793
|
/** Standalone search (Solana payment). */
|
|
2736
2794
|
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
@@ -2860,6 +2918,18 @@ declare class SolanaLLMClient {
|
|
|
2860
2918
|
* @returns the header value to replay with, and what it will settle for.
|
|
2861
2919
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
2862
2920
|
*/
|
|
2921
|
+
/**
|
|
2922
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
2923
|
+
*
|
|
2924
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
2925
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
2926
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
2927
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
2928
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
2929
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
2930
|
+
* so signing per poll costs signatures, never money.
|
|
2931
|
+
*/
|
|
2932
|
+
private followSolanaJob;
|
|
2863
2933
|
private signPaymentFrom402;
|
|
2864
2934
|
private handlePaymentAndRetry;
|
|
2865
2935
|
private requestWithPaymentRaw;
|
|
@@ -3014,7 +3084,7 @@ interface CostEntry {
|
|
|
3014
3084
|
model?: string;
|
|
3015
3085
|
/** Payer wallet address (EVM 0x... or Solana base58). */
|
|
3016
3086
|
wallet?: string;
|
|
3017
|
-
/** Network identifier — "eip155:8453" for Base
|
|
3087
|
+
/** Network identifier — "eip155:8453" for Base, "eip155:5042" for Arc, "solana-mainnet", etc. */
|
|
3018
3088
|
network?: string;
|
|
3019
3089
|
/** Caller kind for analytics — "LLMClient", "ImageClient", "AgentClient", ... */
|
|
3020
3090
|
client_kind?: string;
|
|
@@ -3243,4 +3313,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
3243
3313
|
*/
|
|
3244
3314
|
declare function validateTopP(topP?: number): void;
|
|
3245
3315
|
|
|
3246
|
-
export { APIError, API_KEY_URL, AnthropicClient, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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 ResponseFormat, RetiredEndpointError, 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 SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
|
3316
|
+
export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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 ResponseFormat, RetiredEndpointError, 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 SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, 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, 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 XSearchSource, 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, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
package/dist/index.d.ts
CHANGED
|
@@ -1164,7 +1164,9 @@ declare class APIError extends BlockrunError {
|
|
|
1164
1164
|
* BlockRun LLM Gateway Client.
|
|
1165
1165
|
*
|
|
1166
1166
|
* Provides access to multiple LLM providers (OpenAI, Anthropic, Google, etc.)
|
|
1167
|
-
* with automatic x402 micropayments on Base chain
|
|
1167
|
+
* with automatic x402 micropayments in USDC on Base (chain 8453) or, with
|
|
1168
|
+
* `apiUrl` pointed at arc.blockrun.ai, on Circle's Arc (chain 5042) — the
|
|
1169
|
+
* 402's `network` selects the chain (see EVM_NETWORKS in x402.ts).
|
|
1168
1170
|
* API base: https://blockrun.ai/api
|
|
1169
1171
|
*/
|
|
1170
1172
|
declare class LLMClient {
|
|
@@ -2262,6 +2264,15 @@ declare class BlockrunClient {
|
|
|
2262
2264
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
2263
2265
|
* completed — upstream failure or caller giving up = no charge.
|
|
2264
2266
|
*
|
|
2267
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
2268
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
2269
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
2270
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
2271
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
2272
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
2273
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
2274
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
2275
|
+
*
|
|
2265
2276
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
2266
2277
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
2267
2278
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -2312,16 +2323,51 @@ declare class BlockrunClient {
|
|
|
2312
2323
|
|
|
2313
2324
|
declare const BASE_CHAIN_ID = 8453;
|
|
2314
2325
|
declare const USDC_BASE: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
2326
|
+
declare const ARC_CHAIN_ID = 5042;
|
|
2327
|
+
declare const USDC_ARC: "0x3600000000000000000000000000000000000000";
|
|
2328
|
+
/**
|
|
2329
|
+
* The EVM networks a BlockRun gateway settles on, keyed by the CAIP-2
|
|
2330
|
+
* `network` a 402 carries, each with the SDK's OWN values for that network's
|
|
2331
|
+
* USDC and its EIP-712 domain.
|
|
2332
|
+
*
|
|
2333
|
+
* Until 3.16.0 there was one domain — Base's — signed whatever the 402 said,
|
|
2334
|
+
* so against arc.blockrun.ai (eip155:5042) or testnet.blockrun.ai every
|
|
2335
|
+
* payment was a signature over the wrong domain: the facilitator recovered a
|
|
2336
|
+
* different signer and answered 401 after the SDK had reported a payment.
|
|
2337
|
+
*
|
|
2338
|
+
* The 402 SELECTS a network from this table; it never supplies the domain.
|
|
2339
|
+
* That keeps the rule the single constant enforced — a hostile 402's `extra`
|
|
2340
|
+
* cannot steer a signature onto another contract — while letting the same
|
|
2341
|
+
* SDK pay on every host. Arc's USDC is the chain's native token exposed as an
|
|
2342
|
+
* ERC-20 at 0x3600…0000, and its domain name is "USDC", not Base's "USD Coin".
|
|
2343
|
+
*/
|
|
2344
|
+
interface EvmNetwork {
|
|
2345
|
+
name: string;
|
|
2346
|
+
chainId: number;
|
|
2347
|
+
usdc: `0x${string}`;
|
|
2348
|
+
domain: {
|
|
2349
|
+
name: string;
|
|
2350
|
+
version: string;
|
|
2351
|
+
chainId: number;
|
|
2352
|
+
verifyingContract: `0x${string}`;
|
|
2353
|
+
};
|
|
2354
|
+
}
|
|
2355
|
+
declare const EVM_NETWORKS: Readonly<Record<string, EvmNetwork>>;
|
|
2356
|
+
/** The network table entry for a 402's `network`, or a refusal that names what IS supported. */
|
|
2357
|
+
declare function evmNetwork(network: string): EvmNetwork;
|
|
2315
2358
|
declare const SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
2316
2359
|
declare const USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
2317
2360
|
interface CreatePaymentOptions {
|
|
2318
2361
|
resourceUrl?: string;
|
|
2319
2362
|
resourceDescription?: string;
|
|
2320
2363
|
maxTimeoutSeconds?: number;
|
|
2364
|
+
/** The 402's `extra`. Echoed nowhere: the domain comes from EVM_NETWORKS. */
|
|
2321
2365
|
extra?: {
|
|
2322
2366
|
name?: string;
|
|
2323
2367
|
version?: string;
|
|
2324
2368
|
};
|
|
2369
|
+
/** The 402's `asset`. Checked against the network's USDC; a mismatch is refused. */
|
|
2370
|
+
asset?: string;
|
|
2325
2371
|
extensions?: Record<string, unknown>;
|
|
2326
2372
|
}
|
|
2327
2373
|
/**
|
|
@@ -2331,7 +2377,7 @@ interface CreatePaymentOptions {
|
|
|
2331
2377
|
* @param fromAddress - Sender wallet address
|
|
2332
2378
|
* @param recipient - Payment recipient address
|
|
2333
2379
|
* @param amount - Amount in micro USDC (6 decimals)
|
|
2334
|
-
* @param network -
|
|
2380
|
+
* @param network - CAIP-2 network from the 402 (default: eip155:8453); must be in EVM_NETWORKS
|
|
2335
2381
|
* @param options - Additional options for resource info
|
|
2336
2382
|
* @returns Base64-encoded signed payment payload
|
|
2337
2383
|
*/
|
|
@@ -2731,6 +2777,18 @@ declare class SolanaLLMClient {
|
|
|
2731
2777
|
*/
|
|
2732
2778
|
getBalance(): Promise<number>;
|
|
2733
2779
|
/** Edit an image using img2img (Solana payment). */
|
|
2780
|
+
/**
|
|
2781
|
+
* Generate an image on Solana.
|
|
2782
|
+
*
|
|
2783
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
2784
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
2785
|
+
* models answer 200 inline and settle there.
|
|
2786
|
+
*/
|
|
2787
|
+
image(prompt: string, options?: {
|
|
2788
|
+
model?: string;
|
|
2789
|
+
size?: string;
|
|
2790
|
+
n?: number;
|
|
2791
|
+
} & Record<string, unknown>): Promise<ImageResponse>;
|
|
2734
2792
|
imageEdit(prompt: string, image: string | string[], options?: ImageEditOptions): Promise<ImageResponse>;
|
|
2735
2793
|
/** Standalone search (Solana payment). */
|
|
2736
2794
|
search(query: string, options?: SearchOptions): Promise<SearchResult>;
|
|
@@ -2860,6 +2918,18 @@ declare class SolanaLLMClient {
|
|
|
2860
2918
|
* @returns the header value to replay with, and what it will settle for.
|
|
2861
2919
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
2862
2920
|
*/
|
|
2921
|
+
/**
|
|
2922
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
2923
|
+
*
|
|
2924
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
2925
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
2926
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
2927
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
2928
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
2929
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
2930
|
+
* so signing per poll costs signatures, never money.
|
|
2931
|
+
*/
|
|
2932
|
+
private followSolanaJob;
|
|
2863
2933
|
private signPaymentFrom402;
|
|
2864
2934
|
private handlePaymentAndRetry;
|
|
2865
2935
|
private requestWithPaymentRaw;
|
|
@@ -3014,7 +3084,7 @@ interface CostEntry {
|
|
|
3014
3084
|
model?: string;
|
|
3015
3085
|
/** Payer wallet address (EVM 0x... or Solana base58). */
|
|
3016
3086
|
wallet?: string;
|
|
3017
|
-
/** Network identifier — "eip155:8453" for Base
|
|
3087
|
+
/** Network identifier — "eip155:8453" for Base, "eip155:5042" for Arc, "solana-mainnet", etc. */
|
|
3018
3088
|
network?: string;
|
|
3019
3089
|
/** Caller kind for analytics — "LLMClient", "ImageClient", "AgentClient", ... */
|
|
3020
3090
|
client_kind?: string;
|
|
@@ -3243,4 +3313,4 @@ declare function validateTemperature(temperature?: number): void;
|
|
|
3243
3313
|
*/
|
|
3244
3314
|
declare function validateTopP(topP?: number): void;
|
|
3245
3315
|
|
|
3246
|
-
export { APIError, API_KEY_URL, AnthropicClient, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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 ResponseFormat, RetiredEndpointError, 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 SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, 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_BASE, USDC_BASE_CONTRACT, USDC_SOLANA, 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 XSearchSource, clearCache, createPaymentPayload, createSolanaPaymentPayload, createSolanaWallet, createWallet, LLMClient as default, extractPaymentDetails, formatFundingMessageCompact, formatNeedsFundingMessage, formatSolanaWalletMigrationNotice, formatWalletCreatedMessage, formatWalletMigrationNotice, getCached, getCachedByRequest, getCostLogSummary, getCostSummary, getEip681Uri, getOrCreateSolanaWallet, getOrCreateWallet, getPaymentLinks, getWalletAddress, importSolanaWallet, importWallet, listDiscoveredSolanaWallets, listDiscoveredWallets, loadSolanaWallet, loadWallet, logCost, parsePaymentRequired, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
|
3316
|
+
export { APIError, API_KEY_URL, ARC_CHAIN_ID, AnthropicClient, type ApiKeyOptions, type AudioModel, type AudioTrack, BASE_CHAIN_ID, type BarResolution, 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, 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 ResponseFormat, RetiredEndpointError, 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 SmartChatCompletionOptions, type SmartChatCompletionResponse, type SmartChatOptions, type SmartChatResponse, 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, 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 XSearchSource, 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, saveSolanaWallet, saveToCache, saveWallet, scanSolanaWallets, scanWallets, setCache, setupAgentClient, setupAgentSolanaWallet, setupAgentWallet, solanaClient, solanaKeyToBytes, solanaPublicKey, status, validateMaxTokens, validateModel, validateTemperature, validateTopP };
|
package/dist/index.js
CHANGED
|
@@ -4195,6 +4195,42 @@ async function loadSplToken(what) {
|
|
|
4195
4195
|
// src/x402.ts
|
|
4196
4196
|
var BASE_CHAIN_ID = 8453;
|
|
4197
4197
|
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
4198
|
+
var ARC_CHAIN_ID = 5042;
|
|
4199
|
+
var USDC_ARC = "0x3600000000000000000000000000000000000000";
|
|
4200
|
+
var EVM_NETWORKS = {
|
|
4201
|
+
"eip155:8453": {
|
|
4202
|
+
name: "Base",
|
|
4203
|
+
chainId: BASE_CHAIN_ID,
|
|
4204
|
+
usdc: USDC_BASE,
|
|
4205
|
+
domain: { name: "USD Coin", version: "2", chainId: BASE_CHAIN_ID, verifyingContract: USDC_BASE }
|
|
4206
|
+
},
|
|
4207
|
+
"eip155:5042": {
|
|
4208
|
+
name: "Arc",
|
|
4209
|
+
chainId: ARC_CHAIN_ID,
|
|
4210
|
+
usdc: USDC_ARC,
|
|
4211
|
+
domain: { name: "USDC", version: "2", chainId: ARC_CHAIN_ID, verifyingContract: USDC_ARC }
|
|
4212
|
+
},
|
|
4213
|
+
"eip155:84532": {
|
|
4214
|
+
name: "Base Sepolia",
|
|
4215
|
+
chainId: 84532,
|
|
4216
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
4217
|
+
domain: {
|
|
4218
|
+
name: "USDC",
|
|
4219
|
+
version: "2",
|
|
4220
|
+
chainId: 84532,
|
|
4221
|
+
verifyingContract: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
4222
|
+
}
|
|
4223
|
+
}
|
|
4224
|
+
};
|
|
4225
|
+
function evmNetwork(network) {
|
|
4226
|
+
const net = EVM_NETWORKS[network];
|
|
4227
|
+
if (!net) {
|
|
4228
|
+
throw new Error(
|
|
4229
|
+
`Unsupported x402 network "${network}": this SDK signs USDC payments on ${Object.keys(EVM_NETWORKS).join(", ")}`
|
|
4230
|
+
);
|
|
4231
|
+
}
|
|
4232
|
+
return net;
|
|
4233
|
+
}
|
|
4198
4234
|
var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
4199
4235
|
var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
4200
4236
|
var SOLANA_USDC_DECIMALS = 6;
|
|
@@ -4238,12 +4274,6 @@ async function getBlockhashEntry(connection, rpcUrl, forceRefresh) {
|
|
|
4238
4274
|
trimOldest(blockhashCache, MAX_TRACKED_ENDPOINTS);
|
|
4239
4275
|
return entry;
|
|
4240
4276
|
}
|
|
4241
|
-
var USDC_DOMAIN = {
|
|
4242
|
-
name: "USD Coin",
|
|
4243
|
-
version: "2",
|
|
4244
|
-
chainId: BASE_CHAIN_ID,
|
|
4245
|
-
verifyingContract: USDC_BASE
|
|
4246
|
-
};
|
|
4247
4277
|
var TRANSFER_TYPES = {
|
|
4248
4278
|
TransferWithAuthorization: [
|
|
4249
4279
|
{ name: "from", type: "address" },
|
|
@@ -4274,7 +4304,13 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
4274
4304
|
const validAfter = now - 600;
|
|
4275
4305
|
const validBefore = now + (options.maxTimeoutSeconds || 300);
|
|
4276
4306
|
const nonce = createNonce();
|
|
4277
|
-
const
|
|
4307
|
+
const net = evmNetwork(network);
|
|
4308
|
+
if (options.asset && options.asset.toLowerCase() !== net.usdc.toLowerCase()) {
|
|
4309
|
+
throw new Error(
|
|
4310
|
+
`x402 asset mismatch: the 402 asks for ${options.asset} on ${network}, but this SDK only pays USDC there (${net.usdc})`
|
|
4311
|
+
);
|
|
4312
|
+
}
|
|
4313
|
+
const domain = net.domain;
|
|
4278
4314
|
const signature = await signTypedData({
|
|
4279
4315
|
privateKey,
|
|
4280
4316
|
domain,
|
|
@@ -4300,10 +4336,10 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
4300
4336
|
scheme: "exact",
|
|
4301
4337
|
network,
|
|
4302
4338
|
amount,
|
|
4303
|
-
asset:
|
|
4339
|
+
asset: net.usdc,
|
|
4304
4340
|
payTo: recipient,
|
|
4305
4341
|
maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
|
|
4306
|
-
extra: { name:
|
|
4342
|
+
extra: { name: net.domain.name, version: net.domain.version }
|
|
4307
4343
|
},
|
|
4308
4344
|
payload: {
|
|
4309
4345
|
signature,
|
|
@@ -4503,7 +4539,7 @@ function getCostSummary() {
|
|
|
4503
4539
|
}
|
|
4504
4540
|
|
|
4505
4541
|
// src/version.ts
|
|
4506
|
-
var SDK_VERSION = "3.
|
|
4542
|
+
var SDK_VERSION = "3.16.0";
|
|
4507
4543
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
4508
4544
|
|
|
4509
4545
|
// src/client.ts
|
|
@@ -4963,6 +4999,7 @@ var LLMClient = class _LLMClient {
|
|
|
4963
4999
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
4964
5000
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4965
5001
|
extra: details.extra,
|
|
5002
|
+
asset: details.asset,
|
|
4966
5003
|
extensions
|
|
4967
5004
|
}
|
|
4968
5005
|
);
|
|
@@ -5048,6 +5085,7 @@ var LLMClient = class _LLMClient {
|
|
|
5048
5085
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5049
5086
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5050
5087
|
extra: details.extra,
|
|
5088
|
+
asset: details.asset,
|
|
5051
5089
|
extensions
|
|
5052
5090
|
}
|
|
5053
5091
|
);
|
|
@@ -5259,6 +5297,7 @@ var LLMClient = class _LLMClient {
|
|
|
5259
5297
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5260
5298
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5261
5299
|
extra: details.extra,
|
|
5300
|
+
asset: details.asset,
|
|
5262
5301
|
extensions
|
|
5263
5302
|
}
|
|
5264
5303
|
);
|
|
@@ -5406,6 +5445,7 @@ var LLMClient = class _LLMClient {
|
|
|
5406
5445
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
5407
5446
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
5408
5447
|
extra: details.extra,
|
|
5448
|
+
asset: details.asset,
|
|
5409
5449
|
extensions
|
|
5410
5450
|
}
|
|
5411
5451
|
);
|
|
@@ -6199,6 +6239,7 @@ var ImageClient = class {
|
|
|
6199
6239
|
resourceDescription: details.resource?.description || "BlockRun Image Generation",
|
|
6200
6240
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
6201
6241
|
extra: details.extra,
|
|
6242
|
+
asset: details.asset,
|
|
6202
6243
|
extensions
|
|
6203
6244
|
}
|
|
6204
6245
|
);
|
|
@@ -8553,6 +8594,15 @@ var BlockrunClient = class {
|
|
|
8553
8594
|
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
8554
8595
|
* completed — upstream failure or caller giving up = no charge.
|
|
8555
8596
|
*
|
|
8597
|
+
* **EVM only, and enforced upstream.** Replaying one signature works because
|
|
8598
|
+
* an EIP-3009 authorization stays valid for as long as it was signed for. A
|
|
8599
|
+
* Solana payment is a transaction pinned to a recent blockhash and expires in
|
|
8600
|
+
* ~150 blocks (~60s), so replaying one could not cover a long render. No
|
|
8601
|
+
* guard is needed here: `signFrom402` already refuses a `solana:` network
|
|
8602
|
+
* outright, so this loop is unreachable on Solana. Use `SolanaLLMClient`,
|
|
8603
|
+
* whose poll loop signs afresh each time; the gateway binds a job to the
|
|
8604
|
+
* payer address rather than to the signature, and still settles exactly once.
|
|
8605
|
+
*
|
|
8556
8606
|
* If the gateway returns 200 directly on submit (no async surface), this
|
|
8557
8607
|
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
8558
8608
|
* video, music, voice) return 202 with a poll_url.
|
|
@@ -8806,7 +8856,8 @@ var BlockrunClient = class {
|
|
|
8806
8856
|
details.maxTimeoutSeconds || 0,
|
|
8807
8857
|
opts.maxTimeoutSeconds || 300
|
|
8808
8858
|
),
|
|
8809
|
-
extra: details.extra
|
|
8859
|
+
extra: details.extra,
|
|
8860
|
+
asset: details.asset
|
|
8810
8861
|
}
|
|
8811
8862
|
);
|
|
8812
8863
|
}
|
|
@@ -9786,6 +9837,18 @@ var SolanaLLMClient = class {
|
|
|
9786
9837
|
}
|
|
9787
9838
|
}
|
|
9788
9839
|
/** Edit an image using img2img (Solana payment). */
|
|
9840
|
+
/**
|
|
9841
|
+
* Generate an image on Solana.
|
|
9842
|
+
*
|
|
9843
|
+
* Slow models answer 202 `{ id, poll_url }`; `requestWithPaymentRaw` follows
|
|
9844
|
+
* that to completion, re-signing per poll, and settles once at the end. Fast
|
|
9845
|
+
* models answer 200 inline and settle there.
|
|
9846
|
+
*/
|
|
9847
|
+
async image(prompt, options = {}) {
|
|
9848
|
+
const body = { prompt, ...options };
|
|
9849
|
+
const data = await this.requestWithPaymentRaw("/v1/images/generations", body);
|
|
9850
|
+
return data;
|
|
9851
|
+
}
|
|
9789
9852
|
async imageEdit(prompt, image, options) {
|
|
9790
9853
|
const body = {
|
|
9791
9854
|
model: options?.model || "openai/gpt-image-2",
|
|
@@ -10033,6 +10096,81 @@ var SolanaLLMClient = class {
|
|
|
10033
10096
|
* @returns the header value to replay with, and what it will settle for.
|
|
10034
10097
|
* @throws PaymentError when the 402 carries no usable Solana requirements.
|
|
10035
10098
|
*/
|
|
10099
|
+
/**
|
|
10100
|
+
* Follow a 202 `{ id, poll_url }` to completion, on Solana.
|
|
10101
|
+
*
|
|
10102
|
+
* The Base SDK signs one EIP-3009 authorization and replays it on every poll.
|
|
10103
|
+
* That cannot work here: a Solana payment is a transaction pinned to a recent
|
|
10104
|
+
* blockhash, valid for ~150 blocks (~60s), and a long render outlives it. So
|
|
10105
|
+
* every poll takes a **fresh** 402 and signs again. The gateway binds the job
|
|
10106
|
+
* to the payer address rather than to the signature, which is what makes that
|
|
10107
|
+
* legal — and it settles exactly once, on the poll that returns `completed`,
|
|
10108
|
+
* so signing per poll costs signatures, never money.
|
|
10109
|
+
*/
|
|
10110
|
+
async followSolanaJob(submitBody, budgetMs, intervalMs = 2e3) {
|
|
10111
|
+
const id = submitBody.id;
|
|
10112
|
+
const pollPath = submitBody.poll_url;
|
|
10113
|
+
if (!id || !pollPath) return submitBody;
|
|
10114
|
+
const pollUrl = pollPath.startsWith("http") ? pollPath : `${this.apiUrl}${pollPath.startsWith("/") ? "" : "/"}${pollPath}`;
|
|
10115
|
+
const deadline = Date.now() + budgetMs;
|
|
10116
|
+
let lastStatus = submitBody.status || "queued";
|
|
10117
|
+
while (Date.now() < deadline) {
|
|
10118
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
10119
|
+
const challenge = await this.fetchWithTimeout(pollUrl, {
|
|
10120
|
+
method: "GET",
|
|
10121
|
+
headers: { "User-Agent": USER_AGENT }
|
|
10122
|
+
});
|
|
10123
|
+
if (challenge.status === 200) {
|
|
10124
|
+
const done = await challenge.json();
|
|
10125
|
+
return done;
|
|
10126
|
+
}
|
|
10127
|
+
if (challenge.status !== 402) {
|
|
10128
|
+
let errorBody;
|
|
10129
|
+
try {
|
|
10130
|
+
errorBody = await challenge.json();
|
|
10131
|
+
} catch {
|
|
10132
|
+
errorBody = { error: "Poll failed" };
|
|
10133
|
+
}
|
|
10134
|
+
throw new APIError(`Poll failed: ${challenge.status}`, challenge.status, sanitizeErrorResponse(errorBody));
|
|
10135
|
+
}
|
|
10136
|
+
const { paymentPayload, costUsd } = await this.signPaymentFrom402(
|
|
10137
|
+
pollUrl,
|
|
10138
|
+
challenge,
|
|
10139
|
+
true,
|
|
10140
|
+
// always a fresh blockhash; the last one is stale by now
|
|
10141
|
+
pollUrl
|
|
10142
|
+
);
|
|
10143
|
+
const paid = await this.fetchWithTimeout(pollUrl, {
|
|
10144
|
+
method: "GET",
|
|
10145
|
+
headers: { "User-Agent": USER_AGENT, "PAYMENT-SIGNATURE": paymentPayload }
|
|
10146
|
+
});
|
|
10147
|
+
let data = {};
|
|
10148
|
+
try {
|
|
10149
|
+
data = await paid.json();
|
|
10150
|
+
} catch {
|
|
10151
|
+
}
|
|
10152
|
+
lastStatus = data.status || lastStatus;
|
|
10153
|
+
if (lastStatus === "failed") {
|
|
10154
|
+
throw new APIError(
|
|
10155
|
+
`Upstream job failed: ${data.error || "unknown"}`,
|
|
10156
|
+
paid.status,
|
|
10157
|
+
sanitizeErrorResponse(data)
|
|
10158
|
+
);
|
|
10159
|
+
}
|
|
10160
|
+
if (paid.status === 200 && lastStatus === "completed") {
|
|
10161
|
+
this.recordSettlement(costUsd);
|
|
10162
|
+
return data;
|
|
10163
|
+
}
|
|
10164
|
+
if (paid.status !== 200 && paid.status !== 202 && paid.status !== 504) {
|
|
10165
|
+
throw new APIError(`Poll failed: ${paid.status}`, paid.status, sanitizeErrorResponse(data));
|
|
10166
|
+
}
|
|
10167
|
+
}
|
|
10168
|
+
throw new APIError(
|
|
10169
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
10170
|
+
504,
|
|
10171
|
+
{ id, last_status: lastStatus }
|
|
10172
|
+
);
|
|
10173
|
+
}
|
|
10036
10174
|
async signPaymentFrom402(url, response, forceFreshBlockhash, resourceFallback = url) {
|
|
10037
10175
|
let paymentHeader = response.headers.get("payment-required");
|
|
10038
10176
|
if (!paymentHeader) {
|
|
@@ -10143,6 +10281,10 @@ var SolanaLLMClient = class {
|
|
|
10143
10281
|
body: JSON.stringify(body)
|
|
10144
10282
|
});
|
|
10145
10283
|
await this.assertPaid(retryResponse);
|
|
10284
|
+
if (retryResponse.status === 202) {
|
|
10285
|
+
const submitted = await retryResponse.json();
|
|
10286
|
+
return this.followSolanaJob(submitted, this.timeout);
|
|
10287
|
+
}
|
|
10146
10288
|
this.recordSettlement(costUsd);
|
|
10147
10289
|
return retryResponse.json();
|
|
10148
10290
|
}
|
|
@@ -10737,6 +10879,7 @@ var AnthropicClient = class {
|
|
|
10737
10879
|
resourceDescription: details.resource?.description || "BlockRun AI API call",
|
|
10738
10880
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
10739
10881
|
extra: details.extra,
|
|
10882
|
+
asset: details.asset,
|
|
10740
10883
|
extensions
|
|
10741
10884
|
}
|
|
10742
10885
|
);
|
|
@@ -10778,10 +10921,12 @@ var AnthropicClient = class {
|
|
|
10778
10921
|
export {
|
|
10779
10922
|
APIError,
|
|
10780
10923
|
API_KEY_URL,
|
|
10924
|
+
ARC_CHAIN_ID,
|
|
10781
10925
|
AnthropicClient,
|
|
10782
10926
|
BASE_CHAIN_ID,
|
|
10783
10927
|
BlockrunClient,
|
|
10784
10928
|
BlockrunError,
|
|
10929
|
+
EVM_NETWORKS,
|
|
10785
10930
|
ImageClient,
|
|
10786
10931
|
KNOWN_PROVIDERS,
|
|
10787
10932
|
LLMClient,
|
|
@@ -10805,6 +10950,7 @@ export {
|
|
|
10805
10950
|
SolanaLLMClient,
|
|
10806
10951
|
SpeechClient,
|
|
10807
10952
|
SurfClient,
|
|
10953
|
+
USDC_ARC,
|
|
10808
10954
|
USDC_BASE,
|
|
10809
10955
|
USDC_BASE_CONTRACT,
|
|
10810
10956
|
USDC_SOLANA,
|
|
@@ -10818,6 +10964,7 @@ export {
|
|
|
10818
10964
|
createSolanaWallet,
|
|
10819
10965
|
createWallet,
|
|
10820
10966
|
client_default as default,
|
|
10967
|
+
evmNetwork,
|
|
10821
10968
|
extractPaymentDetails,
|
|
10822
10969
|
formatFundingMessageCompact,
|
|
10823
10970
|
formatNeedsFundingMessage,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blockrun/llm",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "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.",
|
|
6
6
|
"main": "dist/index.cjs",
|