@blockrun/llm 3.8.3 → 3.9.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 +8 -8
- package/dist/index.cjs +129 -23
- package/dist/index.d.cts +5 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +129 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @blockrun/llm (TypeScript SDK)
|
|
2
2
|
|
|
3
|
-
> **@blockrun/llm** is a TypeScript/Node.js SDK for accessing
|
|
3
|
+
> **@blockrun/llm** is a TypeScript/Node.js SDK for accessing <!-- br:models.chatVisible -->66<!-- /br:models.chatVisible --> large language models (GPT-5, Claude, Gemini, Grok, DeepSeek, Kimi, and more) with automatic pay-per-request USDC micropayments via the x402 protocol. No API keys required — your wallet signature is your authentication. Supports **streaming**, smart routing, Base and Solana chains.
|
|
4
4
|
>
|
|
5
5
|
> 🆓 **Includes 7 fully-free NVIDIA-hosted models** (5 visible in `/v1/models`, 2 hidden but directly callable) — DeepSeek V4 Flash (1M context), Nemotron Nano Omni (vision), Qwen3 Coder, Llama 4, Mistral, plus the gpt-oss pair. Zero USDC, no rate-limit gimmicks. Use `routingProfile: 'free'` or call any `nvidia/*` model directly.
|
|
6
6
|
|
|
@@ -232,7 +232,7 @@ const client = new LLMClient();
|
|
|
232
232
|
const result = await client.smartChat('What is 2+2?');
|
|
233
233
|
console.log(result.response); // '4'
|
|
234
234
|
console.log(result.model); // 'moonshot/kimi-k2.5' (cheap, fast)
|
|
235
|
-
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved
|
|
235
|
+
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 87%'
|
|
236
236
|
|
|
237
237
|
// Complex reasoning task -> routes to reasoning model
|
|
238
238
|
const complex = await client.smartChat('Prove the Riemann hypothesis step by step');
|
|
@@ -263,7 +263,7 @@ const reply = await client.chat('nvidia/deepseek-v4-flash', 'hello', {
|
|
|
263
263
|
|
|
264
264
|
| Profile | Description | Best For |
|
|
265
265
|
|---------|-------------|----------|
|
|
266
|
-
| `free` | NVIDIA free tier — smart-routes across 8 models (DeepSeek V4 Flash, Nemotron Nano Omni, Qwen3, Llama 4, Mistral, plus 2 hidden gpt-oss) | Zero-cost testing, dev, prod |
|
|
266
|
+
| `free` | NVIDIA free tier — smart-routes across <!-- br:models.free -->8<!-- /br:models.free --> models (DeepSeek V4 Flash, Nemotron Nano Omni, Qwen3, Llama 4, Mistral, plus 2 hidden gpt-oss) | Zero-cost testing, dev, prod |
|
|
267
267
|
| `eco` | Cheapest models per tier (DeepSeek, xAI) | Cost-sensitive production |
|
|
268
268
|
| `auto` | Best balance of cost/quality (default) | General use |
|
|
269
269
|
| `premium` | Top-tier models (OpenAI, Anthropic) | Quality-critical tasks |
|
|
@@ -748,7 +748,7 @@ Generic escape hatches: `client.defi(path, params)`, `client.dex(path, params, b
|
|
|
748
748
|
### Multi-chain RPC
|
|
749
749
|
|
|
750
750
|
`RpcClient` wraps `POST /v1/rpc/{network}` — standard JSON-RPC 2.0 access to
|
|
751
|
-
40
|
|
751
|
+
<!-- br:chains.rpc -->40<!-- /br:chains.rpc --> chains through one endpoint (Ethereum, Base, Solana, Polygon, BSC,
|
|
752
752
|
Arbitrum, Optimism, Avalanche, Bitcoin, Sui, and more; powered by Tatum's RPC
|
|
753
753
|
gateway). No API key, no per-chain endpoints: flat **$0.002 per call** in
|
|
754
754
|
USDC; a JSON-RPC batch charges per element.
|
|
@@ -858,7 +858,7 @@ const response2 = await client.chat('anthropic/claude-sonnet-4', 'Write a haiku'
|
|
|
858
858
|
|
|
859
859
|
### Smart Routing (ClawRouter)
|
|
860
860
|
|
|
861
|
-
Save up to
|
|
861
|
+
Save up to <!-- br:savings.autoVsBaselinePct -->87<!-- /br:savings.autoVsBaselinePct -->% on inference costs with intelligent model routing. ClawRouter uses a <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions -->-dimension rule-based scoring algorithm to select the cheapest model that can handle your request (<1ms, 100% local).
|
|
862
862
|
|
|
863
863
|
```typescript
|
|
864
864
|
import { LLMClient } from '@blockrun/llm';
|
|
@@ -870,7 +870,7 @@ const result = await client.smartChat('What is 2+2?');
|
|
|
870
870
|
console.log(result.response); // '4'
|
|
871
871
|
console.log(result.model); // 'google/gemini-2.5-flash'
|
|
872
872
|
console.log(result.routing.tier); // 'SIMPLE'
|
|
873
|
-
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved
|
|
873
|
+
console.log(`Saved ${(result.routing.savings * 100).toFixed(0)}%`); // 'Saved 87%'
|
|
874
874
|
|
|
875
875
|
// Routing profiles
|
|
876
876
|
const free = await client.smartChat('Hello!', { routingProfile: 'free' }); // Zero cost
|
|
@@ -883,7 +883,7 @@ const premium = await client.smartChat('Write a legal brief', { routingProfile:
|
|
|
883
883
|
|
|
884
884
|
| Profile | Description | Best For |
|
|
885
885
|
|---------|-------------|----------|
|
|
886
|
-
| `free` | NVIDIA free tier (
|
|
886
|
+
| `free` | NVIDIA free tier (<!-- br:models.free -->8<!-- /br:models.free --> models, smart-routed) | Zero-cost testing, dev, prod |
|
|
887
887
|
| `eco` | Budget-optimized | Cost-sensitive workloads |
|
|
888
888
|
| `auto` | Intelligent routing (default) | General use |
|
|
889
889
|
| `premium` | Best quality models | Critical tasks |
|
|
@@ -1427,7 +1427,7 @@ The `AnthropicClient` wraps the official `@anthropic-ai/sdk` with a custom fetch
|
|
|
1427
1427
|
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.
|
|
1428
1428
|
|
|
1429
1429
|
### What is smart routing / ClawRouter?
|
|
1430
|
-
ClawRouter is a built-in smart routing engine that analyzes your request across
|
|
1430
|
+
ClawRouter is a built-in smart routing engine that analyzes your request across <!-- br:clawrouter.dimensions -->15<!-- /br:clawrouter.dimensions --> dimensions and automatically picks the cheapest model capable of handling it. Routing happens locally in under 1ms. It can save up to <!-- br:savings.autoVsBaselinePct -->87<!-- /br:savings.autoVsBaselinePct -->% on LLM costs compared to using premium models for every request.
|
|
1431
1431
|
|
|
1432
1432
|
### Does it support streaming?
|
|
1433
1433
|
Yes — as of v1.6.1. Use `client.chatCompletionStream()` for native streaming or `stream: true` in the OpenAI-compatible client. Payment is handled automatically: the SDK signs USDC payment before streaming begins, and caches payment requirements per model so subsequent calls skip the 402 round-trip (~200ms faster).
|
package/dist/index.cjs
CHANGED
|
@@ -144,8 +144,47 @@ var BASE_CHAIN_ID = 8453;
|
|
|
144
144
|
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
145
145
|
var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
146
146
|
var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
147
|
+
var SOLANA_USDC_DECIMALS = 6;
|
|
147
148
|
var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
148
149
|
var DEFAULT_COMPUTE_UNIT_LIMIT = 8e3;
|
|
150
|
+
var BLOCKHASH_TTL_MS = 1e4;
|
|
151
|
+
var MAX_FEE_NONCE_STEPS = 64;
|
|
152
|
+
var MAX_TRACKED_BLOCKHASHES = 8;
|
|
153
|
+
var MAX_TRACKED_ENDPOINTS = 8;
|
|
154
|
+
var blockhashCache = /* @__PURE__ */ new Map();
|
|
155
|
+
function trimOldest(map, limit) {
|
|
156
|
+
while (map.size > limit) {
|
|
157
|
+
const oldest = map.keys().next().value;
|
|
158
|
+
map.delete(oldest);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
var issuedByBlockhash = /* @__PURE__ */ new Map();
|
|
162
|
+
function issuedFor(blockhash) {
|
|
163
|
+
let issued = issuedByBlockhash.get(blockhash);
|
|
164
|
+
if (!issued) {
|
|
165
|
+
issued = /* @__PURE__ */ new Set();
|
|
166
|
+
issuedByBlockhash.set(blockhash, issued);
|
|
167
|
+
trimOldest(issuedByBlockhash, MAX_TRACKED_BLOCKHASHES);
|
|
168
|
+
}
|
|
169
|
+
return issued;
|
|
170
|
+
}
|
|
171
|
+
async function getBlockhashEntry(connection, rpcUrl, forceRefresh) {
|
|
172
|
+
const now = Date.now();
|
|
173
|
+
const cached = blockhashCache.get(rpcUrl);
|
|
174
|
+
if (!forceRefresh && cached && now - cached.fetchedAt < BLOCKHASH_TTL_MS) {
|
|
175
|
+
return cached;
|
|
176
|
+
}
|
|
177
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
178
|
+
const current = blockhashCache.get(rpcUrl);
|
|
179
|
+
if (current?.blockhash === blockhash) {
|
|
180
|
+
current.fetchedAt = now;
|
|
181
|
+
return current;
|
|
182
|
+
}
|
|
183
|
+
const entry = { blockhash, fetchedAt: now };
|
|
184
|
+
blockhashCache.set(rpcUrl, entry);
|
|
185
|
+
trimOldest(blockhashCache, MAX_TRACKED_ENDPOINTS);
|
|
186
|
+
return entry;
|
|
187
|
+
}
|
|
149
188
|
var USDC_DOMAIN = {
|
|
150
189
|
name: "USD Coin",
|
|
151
190
|
version: "2",
|
|
@@ -230,7 +269,7 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
230
269
|
}
|
|
231
270
|
async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amount, feePayer, options = {}) {
|
|
232
271
|
const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
|
|
233
|
-
const { getAssociatedTokenAddress, createTransferCheckedInstruction
|
|
272
|
+
const { getAssociatedTokenAddress, createTransferCheckedInstruction } = await import("@solana/spl-token");
|
|
234
273
|
const { Keypair } = await import("@solana/web3.js");
|
|
235
274
|
const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
236
275
|
const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
|
|
@@ -239,32 +278,53 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
|
|
|
239
278
|
const ownerPubkey = keypair.publicKey;
|
|
240
279
|
const tokenMint = new PublicKey(USDC_SOLANA);
|
|
241
280
|
const payToPubkey = new PublicKey(recipient);
|
|
242
|
-
const mintInfo = await getMint(connection, tokenMint);
|
|
243
281
|
const sourceATA = await getAssociatedTokenAddress(tokenMint, ownerPubkey, false);
|
|
244
282
|
const destinationATA = await getAssociatedTokenAddress(tokenMint, payToPubkey, false);
|
|
245
|
-
const { blockhash } = await connection.getLatestBlockhash();
|
|
246
|
-
const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
|
|
247
|
-
microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
|
|
248
|
-
});
|
|
249
|
-
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
|
|
250
|
-
units: DEFAULT_COMPUTE_UNIT_LIMIT
|
|
251
|
-
});
|
|
252
283
|
const transferIx = createTransferCheckedInstruction(
|
|
253
284
|
sourceATA,
|
|
254
285
|
tokenMint,
|
|
255
286
|
destinationATA,
|
|
256
287
|
ownerPubkey,
|
|
257
288
|
BigInt(amount),
|
|
258
|
-
|
|
289
|
+
SOLANA_USDC_DECIMALS
|
|
259
290
|
);
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
291
|
+
const buildSignedTx = (blockhash, unitPriceMicroLamports) => {
|
|
292
|
+
const messageV0 = new TransactionMessage({
|
|
293
|
+
payerKey: feePayerPubkey,
|
|
294
|
+
recentBlockhash: blockhash,
|
|
295
|
+
instructions: [
|
|
296
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: DEFAULT_COMPUTE_UNIT_LIMIT }),
|
|
297
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: unitPriceMicroLamports }),
|
|
298
|
+
transferIx
|
|
299
|
+
]
|
|
300
|
+
}).compileToV0Message();
|
|
301
|
+
const transaction = new VersionedTransaction(messageV0);
|
|
302
|
+
transaction.sign([keypair]);
|
|
303
|
+
return Buffer.from(transaction.serialize()).toString("base64");
|
|
304
|
+
};
|
|
305
|
+
const findDistinctTx = (candidate) => {
|
|
306
|
+
const issued = issuedFor(candidate.blockhash);
|
|
307
|
+
for (let step = 0; step <= MAX_FEE_NONCE_STEPS; step++) {
|
|
308
|
+
const tx = buildSignedTx(
|
|
309
|
+
candidate.blockhash,
|
|
310
|
+
DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS + step
|
|
311
|
+
);
|
|
312
|
+
if (!issued.has(tx)) return tx;
|
|
313
|
+
}
|
|
314
|
+
return null;
|
|
315
|
+
};
|
|
316
|
+
let entry = await getBlockhashEntry(connection, rpcUrl, false);
|
|
317
|
+
let serializedTx = findDistinctTx(entry);
|
|
318
|
+
if (serializedTx === null) {
|
|
319
|
+
entry = await getBlockhashEntry(connection, rpcUrl, true);
|
|
320
|
+
serializedTx = findDistinctTx(entry);
|
|
321
|
+
}
|
|
322
|
+
if (serializedTx === null) {
|
|
323
|
+
throw new Error(
|
|
324
|
+
`Solana payment: ${MAX_FEE_NONCE_STEPS + 1} identical payments already issued against blockhash ${entry.blockhash}. Retry once it rotates.`
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
issuedFor(entry.blockhash).add(serializedTx);
|
|
268
328
|
const paymentData = {
|
|
269
329
|
x402Version: 2,
|
|
270
330
|
resource: {
|
|
@@ -504,7 +564,7 @@ function getCostSummary() {
|
|
|
504
564
|
}
|
|
505
565
|
|
|
506
566
|
// src/version.ts
|
|
507
|
-
var SDK_VERSION = "3.
|
|
567
|
+
var SDK_VERSION = "3.9.0";
|
|
508
568
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
509
569
|
|
|
510
570
|
// src/client.ts
|
|
@@ -4843,16 +4903,51 @@ async function createSolanaWallet() {
|
|
|
4843
4903
|
};
|
|
4844
4904
|
}
|
|
4845
4905
|
async function solanaKeyToBytes(privateKey) {
|
|
4906
|
+
const key = privateKey.trim();
|
|
4907
|
+
if (key.startsWith("[")) {
|
|
4908
|
+
let parsed;
|
|
4909
|
+
try {
|
|
4910
|
+
parsed = JSON.parse(key);
|
|
4911
|
+
} catch {
|
|
4912
|
+
throw new Error(
|
|
4913
|
+
"Invalid Solana private key: looks like a JSON byte array but is not valid JSON"
|
|
4914
|
+
);
|
|
4915
|
+
}
|
|
4916
|
+
if (!Array.isArray(parsed) || !parsed.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
4917
|
+
throw new Error(
|
|
4918
|
+
"Invalid Solana private key: JSON array must contain only byte values (0-255)"
|
|
4919
|
+
);
|
|
4920
|
+
}
|
|
4921
|
+
if (parsed.length !== 64) {
|
|
4922
|
+
throw new Error(
|
|
4923
|
+
`Invalid Solana key length: expected 64 bytes, got ${parsed.length}`
|
|
4924
|
+
);
|
|
4925
|
+
}
|
|
4926
|
+
return Uint8Array.from(parsed);
|
|
4927
|
+
}
|
|
4928
|
+
const hex = key.startsWith("0x") || key.startsWith("0X") ? key.slice(2) : key;
|
|
4929
|
+
if (/^[0-9a-fA-F]+$/.test(hex) && (hex.length === 64 || hex.length === 128)) {
|
|
4930
|
+
if (hex.length === 64) {
|
|
4931
|
+
throw new Error(
|
|
4932
|
+
"Invalid Solana private key: this is a 32-byte hex key \u2014 the EVM (Base) wallet format, not a Solana key. Solana keys are 64 bytes, usually base58-encoded (86-88 characters)."
|
|
4933
|
+
);
|
|
4934
|
+
}
|
|
4935
|
+
const bytes = new Uint8Array(64);
|
|
4936
|
+
for (let i = 0; i < 64; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
4937
|
+
return bytes;
|
|
4938
|
+
}
|
|
4846
4939
|
try {
|
|
4847
4940
|
const bs58 = await import("bs58");
|
|
4848
|
-
const bytes = (bs58.default ?? bs58).decode(
|
|
4941
|
+
const bytes = (bs58.default ?? bs58).decode(key);
|
|
4849
4942
|
if (bytes.length !== 64) {
|
|
4850
4943
|
throw new Error(`Invalid Solana key length: expected 64 bytes, got ${bytes.length}`);
|
|
4851
4944
|
}
|
|
4852
4945
|
return bytes;
|
|
4853
4946
|
} catch (err) {
|
|
4854
4947
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4855
|
-
throw new Error(
|
|
4948
|
+
throw new Error(
|
|
4949
|
+
`Invalid Solana private key: ${msg}. Expected a base58-encoded 64-byte key (standard Solana format), a 64-byte hex string, or a Solana CLI JSON byte array.`
|
|
4950
|
+
);
|
|
4856
4951
|
}
|
|
4857
4952
|
}
|
|
4858
4953
|
async function solanaPublicKey(privateKey) {
|
|
@@ -4992,16 +5087,27 @@ Your current wallet is backed up first. You can also set
|
|
|
4992
5087
|
SOLANA_WALLET_KEY=<private-key> for a single run without changing anything.
|
|
4993
5088
|
`;
|
|
4994
5089
|
}
|
|
5090
|
+
async function solanaPublicKeyFrom(privateKey, source) {
|
|
5091
|
+
try {
|
|
5092
|
+
return await solanaPublicKey(privateKey);
|
|
5093
|
+
} catch (err) {
|
|
5094
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5095
|
+
throw new Error(`${msg} (key loaded from ${source})`);
|
|
5096
|
+
}
|
|
5097
|
+
}
|
|
4995
5098
|
async function getOrCreateSolanaWallet() {
|
|
4996
5099
|
const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
|
|
4997
5100
|
if (envKey) {
|
|
4998
|
-
const address2 = await
|
|
5101
|
+
const address2 = await solanaPublicKeyFrom(
|
|
5102
|
+
envKey,
|
|
5103
|
+
"the SOLANA_WALLET_KEY environment variable"
|
|
5104
|
+
);
|
|
4999
5105
|
return { privateKey: envKey, address: address2, isNew: false };
|
|
5000
5106
|
}
|
|
5001
5107
|
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
5002
5108
|
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
5003
5109
|
if (fileKey) {
|
|
5004
|
-
const address2 = await
|
|
5110
|
+
const address2 = await solanaPublicKeyFrom(fileKey, SOLANA_WALLET_FILE);
|
|
5005
5111
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
5006
5112
|
}
|
|
5007
5113
|
}
|
package/dist/index.d.cts
CHANGED
|
@@ -2860,8 +2860,11 @@ declare function createSolanaWallet(): Promise<{
|
|
|
2860
2860
|
privateKey: string;
|
|
2861
2861
|
}>;
|
|
2862
2862
|
/**
|
|
2863
|
-
* Convert a
|
|
2864
|
-
* Accepts: bs58-encoded 64-byte key (standard Solana format)
|
|
2863
|
+
* Convert a Solana private key string to Uint8Array (64 bytes).
|
|
2864
|
+
* Accepts: bs58-encoded 64-byte key (standard Solana format), the Solana CLI
|
|
2865
|
+
* JSON byte-array format (~/.config/solana/id.json), or a 64-byte hex string
|
|
2866
|
+
* (with or without 0x). A 32-byte hex key is rejected with an explicit hint
|
|
2867
|
+
* that it is the EVM (Base) wallet format, not a Solana key.
|
|
2865
2868
|
*/
|
|
2866
2869
|
declare function solanaKeyToBytes(privateKey: string): Promise<Uint8Array>;
|
|
2867
2870
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -2860,8 +2860,11 @@ declare function createSolanaWallet(): Promise<{
|
|
|
2860
2860
|
privateKey: string;
|
|
2861
2861
|
}>;
|
|
2862
2862
|
/**
|
|
2863
|
-
* Convert a
|
|
2864
|
-
* Accepts: bs58-encoded 64-byte key (standard Solana format)
|
|
2863
|
+
* Convert a Solana private key string to Uint8Array (64 bytes).
|
|
2864
|
+
* Accepts: bs58-encoded 64-byte key (standard Solana format), the Solana CLI
|
|
2865
|
+
* JSON byte-array format (~/.config/solana/id.json), or a 64-byte hex string
|
|
2866
|
+
* (with or without 0x). A 32-byte hex key is rejected with an explicit hint
|
|
2867
|
+
* that it is the EVM (Base) wallet format, not a Solana key.
|
|
2865
2868
|
*/
|
|
2866
2869
|
declare function solanaKeyToBytes(privateKey: string): Promise<Uint8Array>;
|
|
2867
2870
|
/**
|
package/dist/index.js
CHANGED
|
@@ -31,8 +31,47 @@ var BASE_CHAIN_ID = 8453;
|
|
|
31
31
|
var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
32
32
|
var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
|
|
33
33
|
var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
34
|
+
var SOLANA_USDC_DECIMALS = 6;
|
|
34
35
|
var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
|
|
35
36
|
var DEFAULT_COMPUTE_UNIT_LIMIT = 8e3;
|
|
37
|
+
var BLOCKHASH_TTL_MS = 1e4;
|
|
38
|
+
var MAX_FEE_NONCE_STEPS = 64;
|
|
39
|
+
var MAX_TRACKED_BLOCKHASHES = 8;
|
|
40
|
+
var MAX_TRACKED_ENDPOINTS = 8;
|
|
41
|
+
var blockhashCache = /* @__PURE__ */ new Map();
|
|
42
|
+
function trimOldest(map, limit) {
|
|
43
|
+
while (map.size > limit) {
|
|
44
|
+
const oldest = map.keys().next().value;
|
|
45
|
+
map.delete(oldest);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var issuedByBlockhash = /* @__PURE__ */ new Map();
|
|
49
|
+
function issuedFor(blockhash) {
|
|
50
|
+
let issued = issuedByBlockhash.get(blockhash);
|
|
51
|
+
if (!issued) {
|
|
52
|
+
issued = /* @__PURE__ */ new Set();
|
|
53
|
+
issuedByBlockhash.set(blockhash, issued);
|
|
54
|
+
trimOldest(issuedByBlockhash, MAX_TRACKED_BLOCKHASHES);
|
|
55
|
+
}
|
|
56
|
+
return issued;
|
|
57
|
+
}
|
|
58
|
+
async function getBlockhashEntry(connection, rpcUrl, forceRefresh) {
|
|
59
|
+
const now = Date.now();
|
|
60
|
+
const cached = blockhashCache.get(rpcUrl);
|
|
61
|
+
if (!forceRefresh && cached && now - cached.fetchedAt < BLOCKHASH_TTL_MS) {
|
|
62
|
+
return cached;
|
|
63
|
+
}
|
|
64
|
+
const { blockhash } = await connection.getLatestBlockhash();
|
|
65
|
+
const current = blockhashCache.get(rpcUrl);
|
|
66
|
+
if (current?.blockhash === blockhash) {
|
|
67
|
+
current.fetchedAt = now;
|
|
68
|
+
return current;
|
|
69
|
+
}
|
|
70
|
+
const entry = { blockhash, fetchedAt: now };
|
|
71
|
+
blockhashCache.set(rpcUrl, entry);
|
|
72
|
+
trimOldest(blockhashCache, MAX_TRACKED_ENDPOINTS);
|
|
73
|
+
return entry;
|
|
74
|
+
}
|
|
36
75
|
var USDC_DOMAIN = {
|
|
37
76
|
name: "USD Coin",
|
|
38
77
|
version: "2",
|
|
@@ -117,7 +156,7 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
|
|
|
117
156
|
}
|
|
118
157
|
async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amount, feePayer, options = {}) {
|
|
119
158
|
const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
|
|
120
|
-
const { getAssociatedTokenAddress, createTransferCheckedInstruction
|
|
159
|
+
const { getAssociatedTokenAddress, createTransferCheckedInstruction } = await import("@solana/spl-token");
|
|
121
160
|
const { Keypair } = await import("@solana/web3.js");
|
|
122
161
|
const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
123
162
|
const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
|
|
@@ -126,32 +165,53 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
|
|
|
126
165
|
const ownerPubkey = keypair.publicKey;
|
|
127
166
|
const tokenMint = new PublicKey(USDC_SOLANA);
|
|
128
167
|
const payToPubkey = new PublicKey(recipient);
|
|
129
|
-
const mintInfo = await getMint(connection, tokenMint);
|
|
130
168
|
const sourceATA = await getAssociatedTokenAddress(tokenMint, ownerPubkey, false);
|
|
131
169
|
const destinationATA = await getAssociatedTokenAddress(tokenMint, payToPubkey, false);
|
|
132
|
-
const { blockhash } = await connection.getLatestBlockhash();
|
|
133
|
-
const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
|
|
134
|
-
microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
|
|
135
|
-
});
|
|
136
|
-
const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
|
|
137
|
-
units: DEFAULT_COMPUTE_UNIT_LIMIT
|
|
138
|
-
});
|
|
139
170
|
const transferIx = createTransferCheckedInstruction(
|
|
140
171
|
sourceATA,
|
|
141
172
|
tokenMint,
|
|
142
173
|
destinationATA,
|
|
143
174
|
ownerPubkey,
|
|
144
175
|
BigInt(amount),
|
|
145
|
-
|
|
176
|
+
SOLANA_USDC_DECIMALS
|
|
146
177
|
);
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
178
|
+
const buildSignedTx = (blockhash, unitPriceMicroLamports) => {
|
|
179
|
+
const messageV0 = new TransactionMessage({
|
|
180
|
+
payerKey: feePayerPubkey,
|
|
181
|
+
recentBlockhash: blockhash,
|
|
182
|
+
instructions: [
|
|
183
|
+
ComputeBudgetProgram.setComputeUnitLimit({ units: DEFAULT_COMPUTE_UNIT_LIMIT }),
|
|
184
|
+
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: unitPriceMicroLamports }),
|
|
185
|
+
transferIx
|
|
186
|
+
]
|
|
187
|
+
}).compileToV0Message();
|
|
188
|
+
const transaction = new VersionedTransaction(messageV0);
|
|
189
|
+
transaction.sign([keypair]);
|
|
190
|
+
return Buffer.from(transaction.serialize()).toString("base64");
|
|
191
|
+
};
|
|
192
|
+
const findDistinctTx = (candidate) => {
|
|
193
|
+
const issued = issuedFor(candidate.blockhash);
|
|
194
|
+
for (let step = 0; step <= MAX_FEE_NONCE_STEPS; step++) {
|
|
195
|
+
const tx = buildSignedTx(
|
|
196
|
+
candidate.blockhash,
|
|
197
|
+
DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS + step
|
|
198
|
+
);
|
|
199
|
+
if (!issued.has(tx)) return tx;
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
};
|
|
203
|
+
let entry = await getBlockhashEntry(connection, rpcUrl, false);
|
|
204
|
+
let serializedTx = findDistinctTx(entry);
|
|
205
|
+
if (serializedTx === null) {
|
|
206
|
+
entry = await getBlockhashEntry(connection, rpcUrl, true);
|
|
207
|
+
serializedTx = findDistinctTx(entry);
|
|
208
|
+
}
|
|
209
|
+
if (serializedTx === null) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`Solana payment: ${MAX_FEE_NONCE_STEPS + 1} identical payments already issued against blockhash ${entry.blockhash}. Retry once it rotates.`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
issuedFor(entry.blockhash).add(serializedTx);
|
|
155
215
|
const paymentData = {
|
|
156
216
|
x402Version: 2,
|
|
157
217
|
resource: {
|
|
@@ -391,7 +451,7 @@ function getCostSummary() {
|
|
|
391
451
|
}
|
|
392
452
|
|
|
393
453
|
// src/version.ts
|
|
394
|
-
var SDK_VERSION = "3.
|
|
454
|
+
var SDK_VERSION = "3.9.0";
|
|
395
455
|
var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
|
|
396
456
|
|
|
397
457
|
// src/client.ts
|
|
@@ -4730,16 +4790,51 @@ async function createSolanaWallet() {
|
|
|
4730
4790
|
};
|
|
4731
4791
|
}
|
|
4732
4792
|
async function solanaKeyToBytes(privateKey) {
|
|
4793
|
+
const key = privateKey.trim();
|
|
4794
|
+
if (key.startsWith("[")) {
|
|
4795
|
+
let parsed;
|
|
4796
|
+
try {
|
|
4797
|
+
parsed = JSON.parse(key);
|
|
4798
|
+
} catch {
|
|
4799
|
+
throw new Error(
|
|
4800
|
+
"Invalid Solana private key: looks like a JSON byte array but is not valid JSON"
|
|
4801
|
+
);
|
|
4802
|
+
}
|
|
4803
|
+
if (!Array.isArray(parsed) || !parsed.every((n) => Number.isInteger(n) && n >= 0 && n <= 255)) {
|
|
4804
|
+
throw new Error(
|
|
4805
|
+
"Invalid Solana private key: JSON array must contain only byte values (0-255)"
|
|
4806
|
+
);
|
|
4807
|
+
}
|
|
4808
|
+
if (parsed.length !== 64) {
|
|
4809
|
+
throw new Error(
|
|
4810
|
+
`Invalid Solana key length: expected 64 bytes, got ${parsed.length}`
|
|
4811
|
+
);
|
|
4812
|
+
}
|
|
4813
|
+
return Uint8Array.from(parsed);
|
|
4814
|
+
}
|
|
4815
|
+
const hex = key.startsWith("0x") || key.startsWith("0X") ? key.slice(2) : key;
|
|
4816
|
+
if (/^[0-9a-fA-F]+$/.test(hex) && (hex.length === 64 || hex.length === 128)) {
|
|
4817
|
+
if (hex.length === 64) {
|
|
4818
|
+
throw new Error(
|
|
4819
|
+
"Invalid Solana private key: this is a 32-byte hex key \u2014 the EVM (Base) wallet format, not a Solana key. Solana keys are 64 bytes, usually base58-encoded (86-88 characters)."
|
|
4820
|
+
);
|
|
4821
|
+
}
|
|
4822
|
+
const bytes = new Uint8Array(64);
|
|
4823
|
+
for (let i = 0; i < 64; i++) bytes[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
4824
|
+
return bytes;
|
|
4825
|
+
}
|
|
4733
4826
|
try {
|
|
4734
4827
|
const bs58 = await import("bs58");
|
|
4735
|
-
const bytes = (bs58.default ?? bs58).decode(
|
|
4828
|
+
const bytes = (bs58.default ?? bs58).decode(key);
|
|
4736
4829
|
if (bytes.length !== 64) {
|
|
4737
4830
|
throw new Error(`Invalid Solana key length: expected 64 bytes, got ${bytes.length}`);
|
|
4738
4831
|
}
|
|
4739
4832
|
return bytes;
|
|
4740
4833
|
} catch (err) {
|
|
4741
4834
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4742
|
-
throw new Error(
|
|
4835
|
+
throw new Error(
|
|
4836
|
+
`Invalid Solana private key: ${msg}. Expected a base58-encoded 64-byte key (standard Solana format), a 64-byte hex string, or a Solana CLI JSON byte array.`
|
|
4837
|
+
);
|
|
4743
4838
|
}
|
|
4744
4839
|
}
|
|
4745
4840
|
async function solanaPublicKey(privateKey) {
|
|
@@ -4879,16 +4974,27 @@ Your current wallet is backed up first. You can also set
|
|
|
4879
4974
|
SOLANA_WALLET_KEY=<private-key> for a single run without changing anything.
|
|
4880
4975
|
`;
|
|
4881
4976
|
}
|
|
4977
|
+
async function solanaPublicKeyFrom(privateKey, source) {
|
|
4978
|
+
try {
|
|
4979
|
+
return await solanaPublicKey(privateKey);
|
|
4980
|
+
} catch (err) {
|
|
4981
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4982
|
+
throw new Error(`${msg} (key loaded from ${source})`);
|
|
4983
|
+
}
|
|
4984
|
+
}
|
|
4882
4985
|
async function getOrCreateSolanaWallet() {
|
|
4883
4986
|
const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
|
|
4884
4987
|
if (envKey) {
|
|
4885
|
-
const address2 = await
|
|
4988
|
+
const address2 = await solanaPublicKeyFrom(
|
|
4989
|
+
envKey,
|
|
4990
|
+
"the SOLANA_WALLET_KEY environment variable"
|
|
4991
|
+
);
|
|
4886
4992
|
return { privateKey: envKey, address: address2, isNew: false };
|
|
4887
4993
|
}
|
|
4888
4994
|
if (fs3.existsSync(SOLANA_WALLET_FILE)) {
|
|
4889
4995
|
const fileKey = fs3.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
|
|
4890
4996
|
if (fileKey) {
|
|
4891
|
-
const address2 = await
|
|
4997
|
+
const address2 = await solanaPublicKeyFrom(fileKey, SOLANA_WALLET_FILE);
|
|
4892
4998
|
return { privateKey: fileKey, address: address2, isNew: false };
|
|
4893
4999
|
}
|
|
4894
5000
|
}
|