@blockrun/llm 1.4.1 → 1.4.3

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/dist/index.cjs ADDED
@@ -0,0 +1,2806 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ APIError: () => APIError,
34
+ AnthropicClient: () => AnthropicClient,
35
+ BASE_CHAIN_ID: () => BASE_CHAIN_ID,
36
+ BlockrunError: () => BlockrunError,
37
+ ImageClient: () => ImageClient,
38
+ LLMClient: () => LLMClient,
39
+ OpenAI: () => OpenAI,
40
+ PaymentError: () => PaymentError,
41
+ SOLANA_NETWORK: () => SOLANA_NETWORK,
42
+ SOLANA_WALLET_FILE_PATH: () => SOLANA_WALLET_FILE,
43
+ SolanaLLMClient: () => SolanaLLMClient,
44
+ USDC_BASE: () => USDC_BASE,
45
+ USDC_BASE_CONTRACT: () => USDC_BASE_CONTRACT,
46
+ USDC_SOLANA: () => USDC_SOLANA,
47
+ WALLET_DIR_PATH: () => WALLET_DIR_PATH,
48
+ WALLET_FILE_PATH: () => WALLET_FILE_PATH,
49
+ clearCache: () => clearCache,
50
+ createPaymentPayload: () => createPaymentPayload,
51
+ createSolanaPaymentPayload: () => createSolanaPaymentPayload,
52
+ createSolanaWallet: () => createSolanaWallet,
53
+ createWallet: () => createWallet,
54
+ default: () => client_default,
55
+ extractPaymentDetails: () => extractPaymentDetails,
56
+ formatFundingMessageCompact: () => formatFundingMessageCompact,
57
+ formatNeedsFundingMessage: () => formatNeedsFundingMessage,
58
+ formatWalletCreatedMessage: () => formatWalletCreatedMessage,
59
+ getCached: () => getCached,
60
+ getCachedByRequest: () => getCachedByRequest,
61
+ getCostSummary: () => getCostSummary,
62
+ getEip681Uri: () => getEip681Uri,
63
+ getOrCreateSolanaWallet: () => getOrCreateSolanaWallet,
64
+ getOrCreateWallet: () => getOrCreateWallet,
65
+ getPaymentLinks: () => getPaymentLinks,
66
+ getWalletAddress: () => getWalletAddress,
67
+ loadSolanaWallet: () => loadSolanaWallet,
68
+ loadWallet: () => loadWallet,
69
+ logCost: () => logCost,
70
+ parsePaymentRequired: () => parsePaymentRequired,
71
+ saveSolanaWallet: () => saveSolanaWallet,
72
+ saveToCache: () => saveToCache,
73
+ saveWallet: () => saveWallet,
74
+ scanSolanaWallets: () => scanSolanaWallets,
75
+ scanWallets: () => scanWallets,
76
+ setCache: () => setCache,
77
+ setupAgentSolanaWallet: () => setupAgentSolanaWallet,
78
+ setupAgentWallet: () => setupAgentWallet,
79
+ solanaClient: () => solanaClient,
80
+ solanaKeyToBytes: () => solanaKeyToBytes,
81
+ solanaPublicKey: () => solanaPublicKey,
82
+ status: () => status,
83
+ testnetClient: () => testnetClient
84
+ });
85
+ module.exports = __toCommonJS(index_exports);
86
+
87
+ // src/client.ts
88
+ var import_accounts2 = require("viem/accounts");
89
+
90
+ // src/types.ts
91
+ var BlockrunError = class extends Error {
92
+ constructor(message) {
93
+ super(message);
94
+ this.name = "BlockrunError";
95
+ }
96
+ };
97
+ var PaymentError = class extends BlockrunError {
98
+ constructor(message) {
99
+ super(message);
100
+ this.name = "PaymentError";
101
+ }
102
+ };
103
+ var APIError = class extends BlockrunError {
104
+ statusCode;
105
+ response;
106
+ constructor(message, statusCode, response) {
107
+ super(message);
108
+ this.name = "APIError";
109
+ this.statusCode = statusCode;
110
+ this.response = response;
111
+ }
112
+ };
113
+
114
+ // src/client.ts
115
+ var import_clawrouter = require("@blockrun/clawrouter");
116
+
117
+ // src/x402.ts
118
+ var import_accounts = require("viem/accounts");
119
+ var BASE_CHAIN_ID = 8453;
120
+ var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
121
+ var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
122
+ var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
123
+ var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
124
+ var DEFAULT_COMPUTE_UNIT_LIMIT = 8e3;
125
+ var USDC_DOMAIN = {
126
+ name: "USD Coin",
127
+ version: "2",
128
+ chainId: BASE_CHAIN_ID,
129
+ verifyingContract: USDC_BASE
130
+ };
131
+ var TRANSFER_TYPES = {
132
+ TransferWithAuthorization: [
133
+ { name: "from", type: "address" },
134
+ { name: "to", type: "address" },
135
+ { name: "value", type: "uint256" },
136
+ { name: "validAfter", type: "uint256" },
137
+ { name: "validBefore", type: "uint256" },
138
+ { name: "nonce", type: "bytes32" }
139
+ ]
140
+ };
141
+ function createNonce() {
142
+ const bytes = new Uint8Array(32);
143
+ crypto.getRandomValues(bytes);
144
+ return `0x${Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("")}`;
145
+ }
146
+ async function createPaymentPayload(privateKey, fromAddress, recipient, amount, network = "eip155:8453", options = {}) {
147
+ const now = Math.floor(Date.now() / 1e3);
148
+ const validAfter = now - 600;
149
+ const validBefore = now + (options.maxTimeoutSeconds || 300);
150
+ const nonce = createNonce();
151
+ const domain = USDC_DOMAIN;
152
+ const signature = await (0, import_accounts.signTypedData)({
153
+ privateKey,
154
+ domain,
155
+ types: TRANSFER_TYPES,
156
+ primaryType: "TransferWithAuthorization",
157
+ message: {
158
+ from: fromAddress,
159
+ to: recipient,
160
+ value: BigInt(amount),
161
+ validAfter: BigInt(validAfter),
162
+ validBefore: BigInt(validBefore),
163
+ nonce
164
+ }
165
+ });
166
+ const paymentData = {
167
+ x402Version: 2,
168
+ resource: {
169
+ url: options.resourceUrl || "https://blockrun.ai/api/v1/chat/completions",
170
+ description: options.resourceDescription || "BlockRun AI API call",
171
+ mimeType: "application/json"
172
+ },
173
+ accepted: {
174
+ scheme: "exact",
175
+ network,
176
+ amount,
177
+ asset: USDC_BASE,
178
+ payTo: recipient,
179
+ maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
180
+ extra: { name: "USD Coin", version: "2" }
181
+ },
182
+ payload: {
183
+ signature,
184
+ authorization: {
185
+ from: fromAddress,
186
+ to: recipient,
187
+ value: amount,
188
+ validAfter: validAfter.toString(),
189
+ validBefore: validBefore.toString(),
190
+ nonce
191
+ }
192
+ },
193
+ extensions: options.extensions || {}
194
+ };
195
+ return btoa(JSON.stringify(paymentData));
196
+ }
197
+ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amount, feePayer, options = {}) {
198
+ const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
199
+ const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("@solana/spl-token");
200
+ const { Keypair } = await import("@solana/web3.js");
201
+ const rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
202
+ const connection = new Connection(rpcUrl);
203
+ const keypair = Keypair.fromSecretKey(secretKey);
204
+ const feePayerPubkey = new PublicKey(feePayer);
205
+ const ownerPubkey = keypair.publicKey;
206
+ const tokenMint = new PublicKey(USDC_SOLANA);
207
+ const payToPubkey = new PublicKey(recipient);
208
+ const mintInfo = await getMint(connection, tokenMint);
209
+ const sourceATA = await getAssociatedTokenAddress(tokenMint, ownerPubkey, false);
210
+ const destinationATA = await getAssociatedTokenAddress(tokenMint, payToPubkey, false);
211
+ const { blockhash } = await connection.getLatestBlockhash();
212
+ const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
213
+ microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
214
+ });
215
+ const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
216
+ units: DEFAULT_COMPUTE_UNIT_LIMIT
217
+ });
218
+ const transferIx = createTransferCheckedInstruction(
219
+ sourceATA,
220
+ tokenMint,
221
+ destinationATA,
222
+ ownerPubkey,
223
+ BigInt(amount),
224
+ mintInfo.decimals
225
+ );
226
+ const messageV0 = new TransactionMessage({
227
+ payerKey: feePayerPubkey,
228
+ recentBlockhash: blockhash,
229
+ instructions: [setComputeUnitLimitIx, setComputeUnitPriceIx, transferIx]
230
+ }).compileToV0Message();
231
+ const transaction = new VersionedTransaction(messageV0);
232
+ transaction.sign([keypair]);
233
+ const serializedTx = Buffer.from(transaction.serialize()).toString("base64");
234
+ const paymentData = {
235
+ x402Version: 2,
236
+ resource: {
237
+ url: options.resourceUrl || "https://blockrun.ai/api/v1/chat/completions",
238
+ description: options.resourceDescription || "BlockRun AI API call",
239
+ mimeType: "application/json"
240
+ },
241
+ accepted: {
242
+ scheme: "exact",
243
+ network: SOLANA_NETWORK,
244
+ amount,
245
+ asset: USDC_SOLANA,
246
+ payTo: recipient,
247
+ maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
248
+ extra: options.extra || { feePayer }
249
+ },
250
+ payload: {
251
+ transaction: serializedTx
252
+ },
253
+ extensions: options.extensions || {}
254
+ };
255
+ return btoa(JSON.stringify(paymentData));
256
+ }
257
+ function parsePaymentRequired(headerValue) {
258
+ try {
259
+ const decoded = atob(headerValue);
260
+ const parsed = JSON.parse(decoded);
261
+ if (!parsed.accepts || !Array.isArray(parsed.accepts)) {
262
+ throw new Error("Invalid payment required structure: missing or invalid 'accepts' field");
263
+ }
264
+ return parsed;
265
+ } catch (error) {
266
+ if (error instanceof Error) {
267
+ if (error.message.includes("Invalid payment required structure")) {
268
+ throw error;
269
+ }
270
+ throw new Error("Failed to parse payment required header: invalid format");
271
+ }
272
+ throw new Error("Failed to parse payment required header");
273
+ }
274
+ }
275
+ function extractPaymentDetails(paymentRequired, preferredNetwork) {
276
+ const accepts = paymentRequired.accepts || [];
277
+ if (accepts.length === 0) {
278
+ throw new Error("No payment options in payment required response");
279
+ }
280
+ let option = null;
281
+ if (preferredNetwork) {
282
+ option = accepts.find((opt) => opt.network === preferredNetwork) || null;
283
+ }
284
+ if (!option) {
285
+ option = accepts[0];
286
+ }
287
+ const amount = option.amount || option.maxAmountRequired;
288
+ if (!amount) {
289
+ throw new Error("No amount found in payment requirements");
290
+ }
291
+ return {
292
+ amount,
293
+ recipient: option.payTo,
294
+ network: option.network,
295
+ asset: option.asset,
296
+ scheme: option.scheme,
297
+ maxTimeoutSeconds: option.maxTimeoutSeconds || 300,
298
+ extra: option.extra,
299
+ resource: paymentRequired.resource
300
+ };
301
+ }
302
+
303
+ // src/validation.ts
304
+ var LOCALHOST_DOMAINS = ["localhost", "127.0.0.1"];
305
+ function validatePrivateKey(key) {
306
+ if (typeof key !== "string") {
307
+ throw new Error("Private key must be a string");
308
+ }
309
+ if (!key.startsWith("0x")) {
310
+ throw new Error("Private key must start with 0x");
311
+ }
312
+ if (key.length !== 66) {
313
+ throw new Error(
314
+ "Private key must be 66 characters (0x + 64 hexadecimal characters)"
315
+ );
316
+ }
317
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
318
+ throw new Error(
319
+ "Private key must contain only hexadecimal characters (0-9, a-f, A-F)"
320
+ );
321
+ }
322
+ }
323
+ function validateApiUrl(url) {
324
+ let parsed;
325
+ try {
326
+ parsed = new URL(url);
327
+ } catch {
328
+ throw new Error("Invalid API URL format");
329
+ }
330
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
331
+ throw new Error(
332
+ `Invalid protocol: ${parsed.protocol}. Use http:// or https://`
333
+ );
334
+ }
335
+ const isLocalhost = LOCALHOST_DOMAINS.includes(parsed.hostname);
336
+ if (parsed.protocol !== "https:" && !isLocalhost) {
337
+ throw new Error(
338
+ `API URL must use HTTPS for non-localhost endpoints. Use https:// instead of ${parsed.protocol}//`
339
+ );
340
+ }
341
+ }
342
+ function sanitizeErrorResponse(errorBody) {
343
+ if (typeof errorBody !== "object" || errorBody === null) {
344
+ return { message: "API request failed" };
345
+ }
346
+ const body = errorBody;
347
+ return {
348
+ message: typeof body.error === "string" ? body.error : "API request failed",
349
+ code: typeof body.code === "string" ? body.code : void 0
350
+ };
351
+ }
352
+ function validateResourceUrl(url, baseUrl) {
353
+ try {
354
+ const parsed = new URL(url);
355
+ const baseParsed = new URL(baseUrl);
356
+ if (parsed.hostname !== baseParsed.hostname) {
357
+ console.warn(
358
+ `Resource URL hostname mismatch: ${parsed.hostname} vs ${baseParsed.hostname}. Using safe default instead.`
359
+ );
360
+ return `${baseUrl}/v1/chat/completions`;
361
+ }
362
+ if (parsed.protocol !== baseParsed.protocol) {
363
+ console.warn(
364
+ `Resource URL protocol mismatch: ${parsed.protocol} vs ${baseParsed.protocol}. Using safe default instead.`
365
+ );
366
+ return `${baseUrl}/v1/chat/completions`;
367
+ }
368
+ return url;
369
+ } catch {
370
+ console.warn(`Invalid resource URL format: ${url}. Using safe default.`);
371
+ return `${baseUrl}/v1/chat/completions`;
372
+ }
373
+ }
374
+
375
+ // src/client.ts
376
+ var DEFAULT_API_URL = "https://blockrun.ai/api";
377
+ var TESTNET_API_URL = "https://testnet.blockrun.ai/api";
378
+ var DEFAULT_MAX_TOKENS = 1024;
379
+ var DEFAULT_TIMEOUT = 6e4;
380
+ var SDK_VERSION = "0.3.0";
381
+ var USER_AGENT = `blockrun-ts/${SDK_VERSION}`;
382
+ var LLMClient = class {
383
+ static DEFAULT_API_URL = DEFAULT_API_URL;
384
+ static TESTNET_API_URL = TESTNET_API_URL;
385
+ account;
386
+ privateKey;
387
+ apiUrl;
388
+ timeout;
389
+ sessionTotalUsd = 0;
390
+ sessionCalls = 0;
391
+ modelPricingCache = null;
392
+ modelPricingPromise = null;
393
+ /**
394
+ * Initialize the BlockRun LLM client.
395
+ *
396
+ * @param options - Client configuration options (optional if BASE_CHAIN_WALLET_KEY env var is set)
397
+ */
398
+ constructor(options = {}) {
399
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BASE_CHAIN_WALLET_KEY : void 0;
400
+ const privateKey = options.privateKey || envKey;
401
+ if (!privateKey) {
402
+ throw new Error(
403
+ "Private key required. Pass privateKey in options or set BASE_CHAIN_WALLET_KEY environment variable."
404
+ );
405
+ }
406
+ validatePrivateKey(privateKey);
407
+ this.privateKey = privateKey;
408
+ this.account = (0, import_accounts2.privateKeyToAccount)(privateKey);
409
+ const apiUrl = options.apiUrl || DEFAULT_API_URL;
410
+ validateApiUrl(apiUrl);
411
+ this.apiUrl = apiUrl.replace(/\/$/, "");
412
+ this.timeout = options.timeout || DEFAULT_TIMEOUT;
413
+ }
414
+ /**
415
+ * Simple 1-line chat interface.
416
+ *
417
+ * @param model - Model ID (e.g., 'openai/gpt-4o', 'anthropic/claude-sonnet-4')
418
+ * @param prompt - User message
419
+ * @param options - Optional chat parameters
420
+ * @returns Assistant's response text
421
+ *
422
+ * @example
423
+ * const response = await client.chat('gpt-4o', 'What is the capital of France?');
424
+ * console.log(response); // 'The capital of France is Paris.'
425
+ */
426
+ async chat(model, prompt, options) {
427
+ const messages = [];
428
+ if (options?.system) {
429
+ messages.push({ role: "system", content: options.system });
430
+ }
431
+ messages.push({ role: "user", content: prompt });
432
+ const result = await this.chatCompletion(model, messages, {
433
+ maxTokens: options?.maxTokens,
434
+ temperature: options?.temperature,
435
+ topP: options?.topP,
436
+ search: options?.search,
437
+ searchParameters: options?.searchParameters
438
+ });
439
+ return result.choices[0].message.content || "";
440
+ }
441
+ /**
442
+ * Smart chat with automatic model routing.
443
+ *
444
+ * Uses ClawRouter's 14-dimension rule-based scoring algorithm (<1ms, 100% local)
445
+ * to select the cheapest model that can handle your request.
446
+ *
447
+ * @param prompt - User message
448
+ * @param options - Optional chat and routing parameters
449
+ * @returns SmartChatResponse with response text, selected model, and routing metadata
450
+ *
451
+ * @example Simple usage (auto profile)
452
+ * ```ts
453
+ * const result = await client.smartChat('What is 2+2?');
454
+ * console.log(result.response); // '4'
455
+ * console.log(result.model); // 'google/gemini-2.5-flash-lite'
456
+ * console.log(result.routing.savings); // 0.78 (78% savings)
457
+ * ```
458
+ *
459
+ * @example With routing profile
460
+ * ```ts
461
+ * // Free tier only (zero cost)
462
+ * const result = await client.smartChat('Hello!', { routingProfile: 'free' });
463
+ *
464
+ * // Eco mode (budget optimized)
465
+ * const result = await client.smartChat('Explain quantum computing', { routingProfile: 'eco' });
466
+ *
467
+ * // Premium mode (best quality)
468
+ * const result = await client.smartChat('Write a business plan', { routingProfile: 'premium' });
469
+ * ```
470
+ */
471
+ async smartChat(prompt, options) {
472
+ const modelPricing = await this.getModelPricing();
473
+ const maxOutputTokens = options?.maxOutputTokens || options?.maxTokens || 1024;
474
+ const decision = (0, import_clawrouter.route)(prompt, options?.system, maxOutputTokens, {
475
+ config: import_clawrouter.DEFAULT_ROUTING_CONFIG,
476
+ modelPricing,
477
+ routingProfile: options?.routingProfile
478
+ });
479
+ const response = await this.chat(decision.model, prompt, {
480
+ system: options?.system,
481
+ maxTokens: options?.maxTokens,
482
+ temperature: options?.temperature,
483
+ topP: options?.topP,
484
+ search: options?.search,
485
+ searchParameters: options?.searchParameters
486
+ });
487
+ return {
488
+ response,
489
+ model: decision.model,
490
+ routing: decision
491
+ };
492
+ }
493
+ /**
494
+ * Get model pricing map (cached).
495
+ * Fetches from API on first call, then returns cached result.
496
+ */
497
+ async getModelPricing() {
498
+ if (this.modelPricingCache) {
499
+ return this.modelPricingCache;
500
+ }
501
+ if (this.modelPricingPromise) {
502
+ return this.modelPricingPromise;
503
+ }
504
+ this.modelPricingPromise = this.fetchModelPricing();
505
+ try {
506
+ this.modelPricingCache = await this.modelPricingPromise;
507
+ return this.modelPricingCache;
508
+ } finally {
509
+ this.modelPricingPromise = null;
510
+ }
511
+ }
512
+ /**
513
+ * Fetch model pricing from API.
514
+ */
515
+ async fetchModelPricing() {
516
+ const models = await this.listModels();
517
+ const pricing = /* @__PURE__ */ new Map();
518
+ for (const model of models) {
519
+ pricing.set(model.id, {
520
+ inputPrice: model.inputPrice,
521
+ outputPrice: model.outputPrice
522
+ });
523
+ }
524
+ return pricing;
525
+ }
526
+ /**
527
+ * Full chat completion interface (OpenAI-compatible).
528
+ *
529
+ * @param model - Model ID
530
+ * @param messages - Array of messages with role and content
531
+ * @param options - Optional completion parameters
532
+ * @returns ChatResponse object with choices and usage
533
+ */
534
+ async chatCompletion(model, messages, options) {
535
+ const body = {
536
+ model,
537
+ messages,
538
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS
539
+ };
540
+ if (options?.temperature !== void 0) {
541
+ body.temperature = options.temperature;
542
+ }
543
+ if (options?.topP !== void 0) {
544
+ body.top_p = options.topP;
545
+ }
546
+ if (options?.searchParameters !== void 0) {
547
+ body.search_parameters = options.searchParameters;
548
+ } else if (options?.search === true) {
549
+ body.search_parameters = { mode: "on" };
550
+ }
551
+ if (options?.tools !== void 0) {
552
+ body.tools = options.tools;
553
+ }
554
+ if (options?.toolChoice !== void 0) {
555
+ body.tool_choice = options.toolChoice;
556
+ }
557
+ return this.requestWithPayment("/v1/chat/completions", body);
558
+ }
559
+ /**
560
+ * Make a request with automatic x402 payment handling.
561
+ */
562
+ async requestWithPayment(endpoint, body) {
563
+ const url = `${this.apiUrl}${endpoint}`;
564
+ const response = await this.fetchWithTimeout(url, {
565
+ method: "POST",
566
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
567
+ body: JSON.stringify(body)
568
+ });
569
+ if (response.status === 402) {
570
+ return this.handlePaymentAndRetry(url, body, response);
571
+ }
572
+ if (!response.ok) {
573
+ let errorBody;
574
+ try {
575
+ errorBody = await response.json();
576
+ } catch {
577
+ errorBody = { error: "Request failed" };
578
+ }
579
+ throw new APIError(
580
+ `API error: ${response.status}`,
581
+ response.status,
582
+ sanitizeErrorResponse(errorBody)
583
+ );
584
+ }
585
+ return response.json();
586
+ }
587
+ /**
588
+ * Handle 402 response: parse requirements, sign payment, retry.
589
+ */
590
+ async handlePaymentAndRetry(url, body, response) {
591
+ let paymentHeader = response.headers.get("payment-required");
592
+ if (!paymentHeader) {
593
+ try {
594
+ const respBody = await response.json();
595
+ if (respBody.x402 || respBody.accepts) {
596
+ paymentHeader = btoa(JSON.stringify(respBody));
597
+ }
598
+ } catch (parseError) {
599
+ console.debug("Failed to parse payment header from response body", parseError);
600
+ }
601
+ }
602
+ if (!paymentHeader) {
603
+ throw new PaymentError("402 response but no payment requirements found");
604
+ }
605
+ const paymentRequired = parsePaymentRequired(paymentHeader);
606
+ const details = extractPaymentDetails(paymentRequired);
607
+ const extensions = paymentRequired.extensions;
608
+ const paymentPayload = await createPaymentPayload(
609
+ this.privateKey,
610
+ this.account.address,
611
+ details.recipient,
612
+ details.amount,
613
+ details.network || "eip155:8453",
614
+ {
615
+ resourceUrl: validateResourceUrl(
616
+ details.resource?.url || `${this.apiUrl}/v1/chat/completions`,
617
+ this.apiUrl
618
+ ),
619
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
620
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
621
+ extra: details.extra,
622
+ extensions
623
+ }
624
+ );
625
+ const retryResponse = await this.fetchWithTimeout(url, {
626
+ method: "POST",
627
+ headers: {
628
+ "Content-Type": "application/json",
629
+ "User-Agent": USER_AGENT,
630
+ "PAYMENT-SIGNATURE": paymentPayload
631
+ },
632
+ body: JSON.stringify(body)
633
+ });
634
+ if (retryResponse.status === 402) {
635
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
636
+ }
637
+ if (!retryResponse.ok) {
638
+ let errorBody;
639
+ try {
640
+ errorBody = await retryResponse.json();
641
+ } catch {
642
+ errorBody = { error: "Request failed" };
643
+ }
644
+ throw new APIError(
645
+ `API error after payment: ${retryResponse.status}`,
646
+ retryResponse.status,
647
+ sanitizeErrorResponse(errorBody)
648
+ );
649
+ }
650
+ const costUsd = parseFloat(details.amount) / 1e6;
651
+ this.sessionCalls += 1;
652
+ this.sessionTotalUsd += costUsd;
653
+ return retryResponse.json();
654
+ }
655
+ /**
656
+ * Make a request with automatic x402 payment handling, returning raw JSON.
657
+ * Used for non-ChatResponse endpoints (X/Twitter, search, image edit, etc.).
658
+ */
659
+ async requestWithPaymentRaw(endpoint, body) {
660
+ const url = `${this.apiUrl}${endpoint}`;
661
+ const response = await this.fetchWithTimeout(url, {
662
+ method: "POST",
663
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
664
+ body: JSON.stringify(body)
665
+ });
666
+ if (response.status === 402) {
667
+ return this.handlePaymentAndRetryRaw(url, body, response);
668
+ }
669
+ if (!response.ok) {
670
+ let errorBody;
671
+ try {
672
+ errorBody = await response.json();
673
+ } catch {
674
+ errorBody = { error: "Request failed" };
675
+ }
676
+ throw new APIError(
677
+ `API error: ${response.status}`,
678
+ response.status,
679
+ sanitizeErrorResponse(errorBody)
680
+ );
681
+ }
682
+ return response.json();
683
+ }
684
+ /**
685
+ * Handle 402 response for raw endpoints: parse requirements, sign payment, retry.
686
+ */
687
+ async handlePaymentAndRetryRaw(url, body, response) {
688
+ let paymentHeader = response.headers.get("payment-required");
689
+ if (!paymentHeader) {
690
+ try {
691
+ const respBody = await response.json();
692
+ if (respBody.x402 || respBody.accepts) {
693
+ paymentHeader = btoa(JSON.stringify(respBody));
694
+ }
695
+ } catch {
696
+ console.debug("Failed to parse payment header from response body");
697
+ }
698
+ }
699
+ if (!paymentHeader) {
700
+ throw new PaymentError("402 response but no payment requirements found");
701
+ }
702
+ const paymentRequired = parsePaymentRequired(paymentHeader);
703
+ const details = extractPaymentDetails(paymentRequired);
704
+ const extensions = paymentRequired.extensions;
705
+ const paymentPayload = await createPaymentPayload(
706
+ this.privateKey,
707
+ this.account.address,
708
+ details.recipient,
709
+ details.amount,
710
+ details.network || "eip155:8453",
711
+ {
712
+ resourceUrl: validateResourceUrl(
713
+ details.resource?.url || url,
714
+ this.apiUrl
715
+ ),
716
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
717
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
718
+ extra: details.extra,
719
+ extensions
720
+ }
721
+ );
722
+ const retryResponse = await this.fetchWithTimeout(url, {
723
+ method: "POST",
724
+ headers: {
725
+ "Content-Type": "application/json",
726
+ "User-Agent": USER_AGENT,
727
+ "PAYMENT-SIGNATURE": paymentPayload
728
+ },
729
+ body: JSON.stringify(body)
730
+ });
731
+ if (retryResponse.status === 402) {
732
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
733
+ }
734
+ if (!retryResponse.ok) {
735
+ let errorBody;
736
+ try {
737
+ errorBody = await retryResponse.json();
738
+ } catch {
739
+ errorBody = { error: "Request failed" };
740
+ }
741
+ throw new APIError(
742
+ `API error after payment: ${retryResponse.status}`,
743
+ retryResponse.status,
744
+ sanitizeErrorResponse(errorBody)
745
+ );
746
+ }
747
+ const costUsd = parseFloat(details.amount) / 1e6;
748
+ this.sessionCalls += 1;
749
+ this.sessionTotalUsd += costUsd;
750
+ return retryResponse.json();
751
+ }
752
+ /**
753
+ * GET with automatic x402 payment handling, returning raw JSON.
754
+ * Used for Predexon prediction market endpoints that use GET + query params.
755
+ */
756
+ async getWithPaymentRaw(endpoint, params) {
757
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
758
+ const url = `${this.apiUrl}${endpoint}${query}`;
759
+ const response = await this.fetchWithTimeout(url, {
760
+ method: "GET",
761
+ headers: { "User-Agent": USER_AGENT }
762
+ });
763
+ if (response.status === 402) {
764
+ return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
765
+ }
766
+ if (!response.ok) {
767
+ let errorBody;
768
+ try {
769
+ errorBody = await response.json();
770
+ } catch {
771
+ errorBody = { error: "Request failed" };
772
+ }
773
+ throw new APIError(
774
+ `API error: ${response.status}`,
775
+ response.status,
776
+ sanitizeErrorResponse(errorBody)
777
+ );
778
+ }
779
+ return response.json();
780
+ }
781
+ /**
782
+ * Handle 402 response for GET endpoints: parse requirements, sign payment, retry with GET.
783
+ */
784
+ async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
785
+ let paymentHeader = response.headers.get("payment-required");
786
+ if (!paymentHeader) {
787
+ try {
788
+ const respBody = await response.json();
789
+ if (respBody.x402 || respBody.accepts) {
790
+ paymentHeader = btoa(JSON.stringify(respBody));
791
+ }
792
+ } catch {
793
+ console.debug("Failed to parse payment header from response body");
794
+ }
795
+ }
796
+ if (!paymentHeader) {
797
+ throw new PaymentError("402 response but no payment requirements found");
798
+ }
799
+ const paymentRequired = parsePaymentRequired(paymentHeader);
800
+ const details = extractPaymentDetails(paymentRequired);
801
+ const extensions = paymentRequired.extensions;
802
+ const paymentPayload = await createPaymentPayload(
803
+ this.privateKey,
804
+ this.account.address,
805
+ details.recipient,
806
+ details.amount,
807
+ details.network || "eip155:8453",
808
+ {
809
+ resourceUrl: validateResourceUrl(
810
+ details.resource?.url || url,
811
+ this.apiUrl
812
+ ),
813
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
814
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
815
+ extra: details.extra,
816
+ extensions
817
+ }
818
+ );
819
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
820
+ const retryUrl = `${this.apiUrl}${endpoint}${query}`;
821
+ const retryResponse = await this.fetchWithTimeout(retryUrl, {
822
+ method: "GET",
823
+ headers: {
824
+ "User-Agent": USER_AGENT,
825
+ "PAYMENT-SIGNATURE": paymentPayload
826
+ }
827
+ });
828
+ if (retryResponse.status === 402) {
829
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
830
+ }
831
+ if (!retryResponse.ok) {
832
+ let errorBody;
833
+ try {
834
+ errorBody = await retryResponse.json();
835
+ } catch {
836
+ errorBody = { error: "Request failed" };
837
+ }
838
+ throw new APIError(
839
+ `API error after payment: ${retryResponse.status}`,
840
+ retryResponse.status,
841
+ sanitizeErrorResponse(errorBody)
842
+ );
843
+ }
844
+ const costUsd = parseFloat(details.amount) / 1e6;
845
+ this.sessionCalls += 1;
846
+ this.sessionTotalUsd += costUsd;
847
+ return retryResponse.json();
848
+ }
849
+ /**
850
+ * Fetch with timeout.
851
+ */
852
+ async fetchWithTimeout(url, options) {
853
+ const controller = new AbortController();
854
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
855
+ try {
856
+ const response = await fetch(url, {
857
+ ...options,
858
+ signal: controller.signal
859
+ });
860
+ return response;
861
+ } finally {
862
+ clearTimeout(timeoutId);
863
+ }
864
+ }
865
+ /**
866
+ * List available LLM models with pricing.
867
+ */
868
+ async listModels() {
869
+ const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, {
870
+ method: "GET"
871
+ });
872
+ if (!response.ok) {
873
+ let errorBody;
874
+ try {
875
+ errorBody = await response.json();
876
+ } catch {
877
+ errorBody = { error: "Request failed" };
878
+ }
879
+ throw new APIError(
880
+ `Failed to list models: ${response.status}`,
881
+ response.status,
882
+ sanitizeErrorResponse(errorBody)
883
+ );
884
+ }
885
+ const data = await response.json();
886
+ return (data.data || []).map((m) => ({
887
+ id: m.id,
888
+ name: m.name || m.id,
889
+ provider: m.owned_by || "",
890
+ description: m.description || "",
891
+ inputPrice: m.pricing?.input ?? m.pricing?.flat ?? 0,
892
+ outputPrice: m.pricing?.output ?? 0,
893
+ contextWindow: m.context_window || 0,
894
+ maxOutput: m.max_output || 0,
895
+ categories: m.categories || [],
896
+ available: true
897
+ }));
898
+ }
899
+ /**
900
+ * List available image generation models with pricing.
901
+ */
902
+ async listImageModels() {
903
+ const response = await this.fetchWithTimeout(
904
+ `${this.apiUrl}/v1/images/models`,
905
+ { method: "GET" }
906
+ );
907
+ if (!response.ok) {
908
+ throw new APIError(
909
+ `Failed to list image models: ${response.status}`,
910
+ response.status
911
+ );
912
+ }
913
+ const data = await response.json();
914
+ return data.data || [];
915
+ }
916
+ /**
917
+ * List all available models (both LLM and image) with pricing.
918
+ *
919
+ * @returns Array of all models with 'type' field ('llm' or 'image')
920
+ *
921
+ * @example
922
+ * const models = await client.listAllModels();
923
+ * for (const model of models) {
924
+ * if (model.type === 'llm') {
925
+ * console.log(`LLM: ${model.id} - $${model.inputPrice}/M input`);
926
+ * } else {
927
+ * console.log(`Image: ${model.id} - $${model.pricePerImage}/image`);
928
+ * }
929
+ * }
930
+ */
931
+ async listAllModels() {
932
+ const llmModels = await this.listModels();
933
+ for (const model of llmModels) {
934
+ model.type = "llm";
935
+ }
936
+ const imageModels = await this.listImageModels();
937
+ for (const model of imageModels) {
938
+ model.type = "image";
939
+ }
940
+ return [...llmModels, ...imageModels];
941
+ }
942
+ /**
943
+ * Edit an image using img2img.
944
+ *
945
+ * @param prompt - Text description of the desired edit
946
+ * @param image - Base64-encoded image or URL of the source image
947
+ * @param options - Optional edit parameters
948
+ * @returns ImageResponse with edited image URLs
949
+ */
950
+ async imageEdit(prompt, image, options) {
951
+ const body = {
952
+ model: options?.model || "openai/gpt-image-1",
953
+ prompt,
954
+ image,
955
+ size: options?.size || "1024x1024",
956
+ n: options?.n || 1
957
+ };
958
+ if (options?.mask !== void 0) {
959
+ body.mask = options.mask;
960
+ }
961
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
962
+ return data;
963
+ }
964
+ /**
965
+ * Standalone search (web, X/Twitter, news).
966
+ *
967
+ * @param query - Search query
968
+ * @param options - Optional search parameters
969
+ * @returns SearchResult with summary and citations
970
+ */
971
+ async search(query, options) {
972
+ const body = {
973
+ query,
974
+ max_results: options?.maxResults || 10
975
+ };
976
+ if (options?.sources !== void 0) body.sources = options.sources;
977
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
978
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
979
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
980
+ return data;
981
+ }
982
+ /**
983
+ * Get USDC balance on Base network.
984
+ *
985
+ * Automatically detects mainnet vs testnet based on API URL.
986
+ *
987
+ * @returns USDC balance as a float (6 decimal places normalized)
988
+ *
989
+ * @example
990
+ * const balance = await client.getBalance();
991
+ * console.log(`Balance: $${balance.toFixed(2)} USDC`);
992
+ */
993
+ async getBalance() {
994
+ const isTestnet = this.isTestnet();
995
+ const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
996
+ const rpcs = isTestnet ? ["https://sepolia.base.org", "https://base-sepolia-rpc.publicnode.com"] : ["https://base.publicnode.com", "https://mainnet.base.org", "https://base.meowrpc.com"];
997
+ const selector = "0x70a08231";
998
+ const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
999
+ const data = selector + paddedAddress;
1000
+ const payload = {
1001
+ jsonrpc: "2.0",
1002
+ method: "eth_call",
1003
+ params: [{ to: usdcContract, data }, "latest"],
1004
+ id: 1
1005
+ };
1006
+ let lastError;
1007
+ for (const rpc of rpcs) {
1008
+ try {
1009
+ const response = await fetch(rpc, {
1010
+ method: "POST",
1011
+ headers: { "Content-Type": "application/json" },
1012
+ body: JSON.stringify(payload)
1013
+ });
1014
+ const result = await response.json();
1015
+ const balanceRaw = parseInt(result.result || "0x0", 16);
1016
+ return balanceRaw / 1e6;
1017
+ } catch (e) {
1018
+ lastError = e;
1019
+ }
1020
+ }
1021
+ throw lastError || new Error("All RPCs failed");
1022
+ }
1023
+ // ============================================================
1024
+ // X/Twitter endpoints (powered by AttentionVC)
1025
+ // ============================================================
1026
+ /**
1027
+ * Look up X/Twitter user profiles by username.
1028
+ *
1029
+ * Powered by AttentionVC. $0.002 per user (min $0.02, max $0.20).
1030
+ *
1031
+ * @param usernames - Single username or array of usernames (without @)
1032
+ */
1033
+ async xUserLookup(usernames) {
1034
+ const names = Array.isArray(usernames) ? usernames : [usernames];
1035
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
1036
+ return data;
1037
+ }
1038
+ /**
1039
+ * Get followers of an X/Twitter user.
1040
+ *
1041
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
1042
+ *
1043
+ * @param username - X/Twitter username (without @)
1044
+ * @param cursor - Pagination cursor from previous response
1045
+ */
1046
+ async xFollowers(username, cursor) {
1047
+ const body = { username };
1048
+ if (cursor !== void 0) body.cursor = cursor;
1049
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
1050
+ return data;
1051
+ }
1052
+ /**
1053
+ * Get accounts an X/Twitter user is following.
1054
+ *
1055
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
1056
+ *
1057
+ * @param username - X/Twitter username (without @)
1058
+ * @param cursor - Pagination cursor from previous response
1059
+ */
1060
+ async xFollowings(username, cursor) {
1061
+ const body = { username };
1062
+ if (cursor !== void 0) body.cursor = cursor;
1063
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
1064
+ return data;
1065
+ }
1066
+ /**
1067
+ * Get detailed profile info for a single X/Twitter user.
1068
+ *
1069
+ * Powered by AttentionVC. $0.002 per request.
1070
+ *
1071
+ * @param username - X/Twitter username (without @)
1072
+ */
1073
+ async xUserInfo(username) {
1074
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
1075
+ return data;
1076
+ }
1077
+ /**
1078
+ * Get verified (blue-check) followers of an X/Twitter user.
1079
+ *
1080
+ * Powered by AttentionVC. $0.048 per page.
1081
+ *
1082
+ * @param userId - X/Twitter user ID (not username)
1083
+ * @param cursor - Pagination cursor from previous response
1084
+ */
1085
+ async xVerifiedFollowers(userId, cursor) {
1086
+ const body = { userId };
1087
+ if (cursor !== void 0) body.cursor = cursor;
1088
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
1089
+ return data;
1090
+ }
1091
+ /**
1092
+ * Get tweets posted by an X/Twitter user.
1093
+ *
1094
+ * Powered by AttentionVC. $0.032 per page.
1095
+ *
1096
+ * @param username - X/Twitter username (without @)
1097
+ * @param includeReplies - Include reply tweets (default: false)
1098
+ * @param cursor - Pagination cursor from previous response
1099
+ */
1100
+ async xUserTweets(username, includeReplies = false, cursor) {
1101
+ const body = { username, includeReplies };
1102
+ if (cursor !== void 0) body.cursor = cursor;
1103
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
1104
+ return data;
1105
+ }
1106
+ /**
1107
+ * Get tweets that mention an X/Twitter user.
1108
+ *
1109
+ * Powered by AttentionVC. $0.032 per page.
1110
+ *
1111
+ * @param username - X/Twitter username (without @)
1112
+ * @param sinceTime - Start time filter (ISO8601 or Unix timestamp)
1113
+ * @param untilTime - End time filter (ISO8601 or Unix timestamp)
1114
+ * @param cursor - Pagination cursor from previous response
1115
+ */
1116
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
1117
+ const body = { username };
1118
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
1119
+ if (untilTime !== void 0) body.untilTime = untilTime;
1120
+ if (cursor !== void 0) body.cursor = cursor;
1121
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
1122
+ return data;
1123
+ }
1124
+ /**
1125
+ * Fetch full tweet data for up to 200 tweet IDs.
1126
+ *
1127
+ * Powered by AttentionVC. $0.16 per batch.
1128
+ *
1129
+ * @param tweetIds - Single tweet ID or array of tweet IDs (max 200)
1130
+ */
1131
+ async xTweetLookup(tweetIds) {
1132
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
1133
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
1134
+ return data;
1135
+ }
1136
+ /**
1137
+ * Get replies to a specific tweet.
1138
+ *
1139
+ * Powered by AttentionVC. $0.032 per page.
1140
+ *
1141
+ * @param tweetId - The tweet ID to get replies for
1142
+ * @param queryType - Sort order: 'Latest' or 'Default'
1143
+ * @param cursor - Pagination cursor from previous response
1144
+ */
1145
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
1146
+ const body = { tweetId, queryType };
1147
+ if (cursor !== void 0) body.cursor = cursor;
1148
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
1149
+ return data;
1150
+ }
1151
+ /**
1152
+ * Get the full thread context for a tweet.
1153
+ *
1154
+ * Powered by AttentionVC. $0.032 per page.
1155
+ *
1156
+ * @param tweetId - The tweet ID to get thread for
1157
+ * @param cursor - Pagination cursor from previous response
1158
+ */
1159
+ async xTweetThread(tweetId, cursor) {
1160
+ const body = { tweetId };
1161
+ if (cursor !== void 0) body.cursor = cursor;
1162
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
1163
+ return data;
1164
+ }
1165
+ /**
1166
+ * Search X/Twitter with advanced query operators.
1167
+ *
1168
+ * Powered by AttentionVC. $0.032 per page.
1169
+ *
1170
+ * @param query - Search query (supports Twitter search operators)
1171
+ * @param queryType - Sort order: 'Latest', 'Top', or 'Default'
1172
+ * @param cursor - Pagination cursor from previous response
1173
+ */
1174
+ async xSearch(query, queryType = "Latest", cursor) {
1175
+ const body = { query, queryType };
1176
+ if (cursor !== void 0) body.cursor = cursor;
1177
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
1178
+ return data;
1179
+ }
1180
+ /**
1181
+ * Get current trending topics on X/Twitter.
1182
+ *
1183
+ * Powered by AttentionVC. $0.002 per request.
1184
+ */
1185
+ async xTrending() {
1186
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1187
+ return data;
1188
+ }
1189
+ /**
1190
+ * Get rising/viral articles from X/Twitter.
1191
+ *
1192
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1193
+ */
1194
+ async xArticlesRising() {
1195
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1196
+ return data;
1197
+ }
1198
+ /**
1199
+ * Get author analytics and intelligence metrics for an X/Twitter user.
1200
+ *
1201
+ * Powered by AttentionVC intelligence layer. $0.02 per request.
1202
+ *
1203
+ * @param handle - X/Twitter handle (without @)
1204
+ */
1205
+ async xAuthorAnalytics(handle) {
1206
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1207
+ return data;
1208
+ }
1209
+ /**
1210
+ * Compare two X/Twitter authors side-by-side with intelligence metrics.
1211
+ *
1212
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1213
+ *
1214
+ * @param handle1 - First X/Twitter handle (without @)
1215
+ * @param handle2 - Second X/Twitter handle (without @)
1216
+ */
1217
+ async xCompareAuthors(handle1, handle2) {
1218
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1219
+ return data;
1220
+ }
1221
+ // ── Prediction Markets (Powered by Predexon) ────────────────────────────
1222
+ /**
1223
+ * Query Predexon prediction market data (GET endpoints).
1224
+ *
1225
+ * Access real-time data from Polymarket, Kalshi, dFlow, and Binance Futures.
1226
+ * Powered by Predexon. $0.001 per request.
1227
+ *
1228
+ * @param path - Endpoint path, e.g. "polymarket/events", "kalshi/markets/12345"
1229
+ * @param params - Query parameters passed to the endpoint
1230
+ *
1231
+ * @example
1232
+ * const events = await client.pm("polymarket/events");
1233
+ * const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
1234
+ * const results = await client.pm("polymarket/search", { q: "bitcoin" });
1235
+ */
1236
+ async pm(path5, params) {
1237
+ return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
1238
+ }
1239
+ /**
1240
+ * Structured query for Predexon prediction market data (POST endpoints).
1241
+ *
1242
+ * For complex queries that require a JSON body. $0.005 per request.
1243
+ *
1244
+ * @param path - Endpoint path, e.g. "polymarket/query", "kalshi/query"
1245
+ * @param query - JSON body for the structured query
1246
+ *
1247
+ * @example
1248
+ * const data = await client.pmQuery("polymarket/query", { filter: "active", limit: 10 });
1249
+ */
1250
+ async pmQuery(path5, query) {
1251
+ return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1252
+ }
1253
+ /**
1254
+ * Get current session spending.
1255
+ *
1256
+ * @returns Object with totalUsd and calls count
1257
+ *
1258
+ * @example
1259
+ * const spending = client.getSpending();
1260
+ * console.log(`Spent $${spending.totalUsd.toFixed(4)} across ${spending.calls} calls`);
1261
+ */
1262
+ getSpending() {
1263
+ return {
1264
+ totalUsd: this.sessionTotalUsd,
1265
+ calls: this.sessionCalls
1266
+ };
1267
+ }
1268
+ /**
1269
+ * Get the wallet address being used for payments.
1270
+ */
1271
+ getWalletAddress() {
1272
+ return this.account.address;
1273
+ }
1274
+ /**
1275
+ * Check if client is configured for testnet.
1276
+ */
1277
+ isTestnet() {
1278
+ return this.apiUrl.includes("testnet.blockrun.ai");
1279
+ }
1280
+ };
1281
+ function testnetClient(options = {}) {
1282
+ return new LLMClient({
1283
+ ...options,
1284
+ apiUrl: TESTNET_API_URL
1285
+ });
1286
+ }
1287
+ var client_default = LLMClient;
1288
+
1289
+ // src/image.ts
1290
+ var import_accounts3 = require("viem/accounts");
1291
+ var DEFAULT_API_URL2 = "https://blockrun.ai/api";
1292
+ var DEFAULT_MODEL = "google/nano-banana";
1293
+ var DEFAULT_SIZE = "1024x1024";
1294
+ var DEFAULT_TIMEOUT2 = 12e4;
1295
+ var ImageClient = class {
1296
+ account;
1297
+ privateKey;
1298
+ apiUrl;
1299
+ timeout;
1300
+ sessionTotalUsd = 0;
1301
+ sessionCalls = 0;
1302
+ /**
1303
+ * Initialize the BlockRun Image client.
1304
+ *
1305
+ * @param options - Client configuration options
1306
+ */
1307
+ constructor(options = {}) {
1308
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
1309
+ const privateKey = options.privateKey || envKey;
1310
+ if (!privateKey) {
1311
+ throw new Error(
1312
+ "Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
1313
+ );
1314
+ }
1315
+ validatePrivateKey(privateKey);
1316
+ this.privateKey = privateKey;
1317
+ this.account = (0, import_accounts3.privateKeyToAccount)(privateKey);
1318
+ const apiUrl = options.apiUrl || DEFAULT_API_URL2;
1319
+ validateApiUrl(apiUrl);
1320
+ this.apiUrl = apiUrl.replace(/\/$/, "");
1321
+ this.timeout = options.timeout || DEFAULT_TIMEOUT2;
1322
+ }
1323
+ /**
1324
+ * Generate an image from a text prompt.
1325
+ *
1326
+ * @param prompt - Text description of the image to generate
1327
+ * @param options - Optional generation parameters
1328
+ * @returns ImageResponse with generated image URLs
1329
+ *
1330
+ * @example
1331
+ * const result = await client.generate('A sunset over mountains');
1332
+ * console.log(result.data[0].url);
1333
+ */
1334
+ async generate(prompt, options) {
1335
+ const body = {
1336
+ model: options?.model || DEFAULT_MODEL,
1337
+ prompt,
1338
+ size: options?.size || DEFAULT_SIZE,
1339
+ n: options?.n || 1
1340
+ };
1341
+ if (options?.quality) {
1342
+ body.quality = options.quality;
1343
+ }
1344
+ return this.requestWithPayment("/v1/images/generations", body);
1345
+ }
1346
+ /**
1347
+ * Edit an image using img2img.
1348
+ *
1349
+ * @param prompt - Text description of the desired edit
1350
+ * @param image - Base64-encoded image or URL of the source image
1351
+ * @param options - Optional edit parameters
1352
+ * @returns ImageResponse with edited image URLs
1353
+ *
1354
+ * @example
1355
+ * const result = await client.edit('Make it a painting', imageBase64);
1356
+ * console.log(result.data[0].url);
1357
+ */
1358
+ async edit(prompt, image, options) {
1359
+ const body = {
1360
+ model: options?.model || "openai/gpt-image-1",
1361
+ prompt,
1362
+ image,
1363
+ size: options?.size || "1024x1024",
1364
+ n: options?.n || 1
1365
+ };
1366
+ if (options?.mask !== void 0) {
1367
+ body.mask = options.mask;
1368
+ }
1369
+ return this.requestWithPayment("/v1/images/image2image", body);
1370
+ }
1371
+ /**
1372
+ * List available image generation models with pricing.
1373
+ */
1374
+ async listImageModels() {
1375
+ const response = await this.fetchWithTimeout(
1376
+ `${this.apiUrl}/v1/images/models`,
1377
+ { method: "GET" }
1378
+ );
1379
+ if (!response.ok) {
1380
+ throw new APIError(
1381
+ `Failed to list image models: ${response.status}`,
1382
+ response.status
1383
+ );
1384
+ }
1385
+ const data = await response.json();
1386
+ return data.data || [];
1387
+ }
1388
+ /**
1389
+ * Make a request with automatic x402 payment handling.
1390
+ */
1391
+ async requestWithPayment(endpoint, body) {
1392
+ const url = `${this.apiUrl}${endpoint}`;
1393
+ const response = await this.fetchWithTimeout(url, {
1394
+ method: "POST",
1395
+ headers: { "Content-Type": "application/json" },
1396
+ body: JSON.stringify(body)
1397
+ });
1398
+ if (response.status === 402) {
1399
+ return this.handlePaymentAndRetry(url, body, response);
1400
+ }
1401
+ if (!response.ok) {
1402
+ let errorBody;
1403
+ try {
1404
+ errorBody = await response.json();
1405
+ } catch {
1406
+ errorBody = { error: "Request failed" };
1407
+ }
1408
+ throw new APIError(
1409
+ `API error: ${response.status}`,
1410
+ response.status,
1411
+ sanitizeErrorResponse(errorBody)
1412
+ );
1413
+ }
1414
+ return response.json();
1415
+ }
1416
+ /**
1417
+ * Handle 402 response: parse requirements, sign payment, retry.
1418
+ */
1419
+ async handlePaymentAndRetry(url, body, response) {
1420
+ let paymentHeader = response.headers.get("payment-required");
1421
+ if (!paymentHeader) {
1422
+ try {
1423
+ const respBody = await response.json();
1424
+ if (respBody.x402 || respBody.accepts) {
1425
+ paymentHeader = btoa(JSON.stringify(respBody));
1426
+ }
1427
+ } catch {
1428
+ }
1429
+ }
1430
+ if (!paymentHeader) {
1431
+ throw new PaymentError("402 response but no payment requirements found");
1432
+ }
1433
+ const paymentRequired = parsePaymentRequired(paymentHeader);
1434
+ const details = extractPaymentDetails(paymentRequired);
1435
+ const extensions = paymentRequired.extensions;
1436
+ const paymentPayload = await createPaymentPayload(
1437
+ this.privateKey,
1438
+ this.account.address,
1439
+ details.recipient,
1440
+ details.amount,
1441
+ details.network || "eip155:8453",
1442
+ {
1443
+ resourceUrl: validateResourceUrl(
1444
+ details.resource?.url || `${this.apiUrl}/v1/images/generations`,
1445
+ this.apiUrl
1446
+ ),
1447
+ resourceDescription: details.resource?.description || "BlockRun Image Generation",
1448
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
1449
+ extra: details.extra,
1450
+ extensions
1451
+ }
1452
+ );
1453
+ const retryResponse = await this.fetchWithTimeout(url, {
1454
+ method: "POST",
1455
+ headers: {
1456
+ "Content-Type": "application/json",
1457
+ "PAYMENT-SIGNATURE": paymentPayload
1458
+ },
1459
+ body: JSON.stringify(body)
1460
+ });
1461
+ if (retryResponse.status === 402) {
1462
+ throw new PaymentError(
1463
+ "Payment was rejected. Check your wallet balance."
1464
+ );
1465
+ }
1466
+ if (!retryResponse.ok) {
1467
+ let errorBody;
1468
+ try {
1469
+ errorBody = await retryResponse.json();
1470
+ } catch {
1471
+ errorBody = { error: "Request failed" };
1472
+ }
1473
+ throw new APIError(
1474
+ `API error after payment: ${retryResponse.status}`,
1475
+ retryResponse.status,
1476
+ sanitizeErrorResponse(errorBody)
1477
+ );
1478
+ }
1479
+ return retryResponse.json();
1480
+ }
1481
+ /**
1482
+ * Fetch with timeout.
1483
+ */
1484
+ async fetchWithTimeout(url, options) {
1485
+ const controller = new AbortController();
1486
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1487
+ try {
1488
+ const response = await fetch(url, {
1489
+ ...options,
1490
+ signal: controller.signal
1491
+ });
1492
+ return response;
1493
+ } finally {
1494
+ clearTimeout(timeoutId);
1495
+ }
1496
+ }
1497
+ /**
1498
+ * Get the wallet address being used for payments.
1499
+ */
1500
+ getWalletAddress() {
1501
+ return this.account.address;
1502
+ }
1503
+ /**
1504
+ * Get session spending information.
1505
+ */
1506
+ getSpending() {
1507
+ return {
1508
+ totalUsd: this.sessionTotalUsd,
1509
+ calls: this.sessionCalls
1510
+ };
1511
+ }
1512
+ };
1513
+
1514
+ // src/wallet.ts
1515
+ var import_accounts4 = require("viem/accounts");
1516
+ var fs = __toESM(require("fs"), 1);
1517
+ var path = __toESM(require("path"), 1);
1518
+ var os = __toESM(require("os"), 1);
1519
+ var USDC_BASE_CONTRACT = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
1520
+ var BASE_CHAIN_ID2 = "8453";
1521
+ var WALLET_DIR = path.join(os.homedir(), ".blockrun");
1522
+ var WALLET_FILE = path.join(WALLET_DIR, ".session");
1523
+ function createWallet() {
1524
+ const privateKey = (0, import_accounts4.generatePrivateKey)();
1525
+ const account = (0, import_accounts4.privateKeyToAccount)(privateKey);
1526
+ return {
1527
+ address: account.address,
1528
+ privateKey
1529
+ };
1530
+ }
1531
+ function saveWallet(privateKey) {
1532
+ if (!fs.existsSync(WALLET_DIR)) {
1533
+ fs.mkdirSync(WALLET_DIR, { recursive: true });
1534
+ }
1535
+ fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
1536
+ return WALLET_FILE;
1537
+ }
1538
+ function scanWallets() {
1539
+ const home = os.homedir();
1540
+ const results = [];
1541
+ try {
1542
+ const entries = fs.readdirSync(home, { withFileTypes: true });
1543
+ for (const entry of entries) {
1544
+ if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
1545
+ const walletFile = path.join(home, entry.name, "wallet.json");
1546
+ if (!fs.existsSync(walletFile)) continue;
1547
+ try {
1548
+ const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
1549
+ const pk = data.privateKey || "";
1550
+ const addr = data.address || "";
1551
+ if (pk && addr) {
1552
+ const mtime = fs.statSync(walletFile).mtimeMs;
1553
+ results.push({ mtime, privateKey: pk, address: addr });
1554
+ }
1555
+ } catch {
1556
+ continue;
1557
+ }
1558
+ }
1559
+ } catch {
1560
+ }
1561
+ results.sort((a, b) => b.mtime - a.mtime);
1562
+ return results.map(({ privateKey, address }) => ({ privateKey, address }));
1563
+ }
1564
+ function loadWallet() {
1565
+ const wallets = scanWallets();
1566
+ if (wallets.length > 0) return wallets[0].privateKey;
1567
+ if (fs.existsSync(WALLET_FILE)) {
1568
+ const key = fs.readFileSync(WALLET_FILE, "utf-8").trim();
1569
+ if (key) return key;
1570
+ }
1571
+ const legacyFile = path.join(WALLET_DIR, "wallet.key");
1572
+ if (fs.existsSync(legacyFile)) {
1573
+ const key = fs.readFileSync(legacyFile, "utf-8").trim();
1574
+ if (key) return key;
1575
+ }
1576
+ return null;
1577
+ }
1578
+ function getOrCreateWallet() {
1579
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
1580
+ if (envKey) {
1581
+ const account = (0, import_accounts4.privateKeyToAccount)(envKey);
1582
+ return { address: account.address, privateKey: envKey, isNew: false };
1583
+ }
1584
+ const fileKey = loadWallet();
1585
+ if (fileKey) {
1586
+ const account = (0, import_accounts4.privateKeyToAccount)(fileKey);
1587
+ return { address: account.address, privateKey: fileKey, isNew: false };
1588
+ }
1589
+ const { address, privateKey } = createWallet();
1590
+ saveWallet(privateKey);
1591
+ return { address, privateKey, isNew: true };
1592
+ }
1593
+ function getWalletAddress() {
1594
+ const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
1595
+ if (envKey) {
1596
+ return (0, import_accounts4.privateKeyToAccount)(envKey).address;
1597
+ }
1598
+ const fileKey = loadWallet();
1599
+ if (fileKey) {
1600
+ return (0, import_accounts4.privateKeyToAccount)(fileKey).address;
1601
+ }
1602
+ return null;
1603
+ }
1604
+ function getEip681Uri(address, amountUsdc = 1) {
1605
+ const amountWei = Math.floor(amountUsdc * 1e6);
1606
+ return `ethereum:${USDC_BASE_CONTRACT}@${BASE_CHAIN_ID2}/transfer?address=${address}&uint256=${amountWei}`;
1607
+ }
1608
+ function getPaymentLinks(address) {
1609
+ return {
1610
+ basescan: `https://basescan.org/address/${address}`,
1611
+ walletLink: `ethereum:${USDC_BASE_CONTRACT}@${BASE_CHAIN_ID2}/transfer?address=${address}`,
1612
+ ethereum: `ethereum:${address}@${BASE_CHAIN_ID2}`,
1613
+ blockrun: `https://blockrun.ai/fund?address=${address}`
1614
+ };
1615
+ }
1616
+ function formatWalletCreatedMessage(address) {
1617
+ const links = getPaymentLinks(address);
1618
+ return `
1619
+ I'm your BlockRun Agent! I can access GPT-4, Grok, image generation, and more.
1620
+
1621
+ Please send $1-5 USDC on Base to start:
1622
+
1623
+ ${address}
1624
+
1625
+ What is Base? Base is Coinbase's blockchain network.
1626
+ You can buy USDC on Coinbase and send it directly to me.
1627
+
1628
+ What $1 USDC gets you:
1629
+ - ~1,000 GPT-4o calls
1630
+ - ~100 image generations
1631
+ - ~10,000 DeepSeek calls
1632
+
1633
+ Quick links:
1634
+ - Check my balance: ${links.basescan}
1635
+ - Get USDC: https://www.coinbase.com or https://bridge.base.org
1636
+
1637
+ Questions? care@blockrun.ai | Issues? github.com/BlockRunAI/blockrun-llm-ts/issues
1638
+
1639
+ Key stored securely in ~/.blockrun/
1640
+ Your private key never leaves your machine - only signatures are sent.
1641
+ `;
1642
+ }
1643
+ function formatNeedsFundingMessage(address) {
1644
+ const links = getPaymentLinks(address);
1645
+ return `
1646
+ I've run out of funds! Please send more USDC on Base to continue helping you.
1647
+
1648
+ Send to my address:
1649
+ ${address}
1650
+
1651
+ Check my balance: ${links.basescan}
1652
+
1653
+ What $1 USDC gets you: ~1,000 GPT-4o calls or ~100 images.
1654
+ Questions? care@blockrun.ai | Issues? github.com/BlockRunAI/blockrun-llm-ts/issues
1655
+
1656
+ Your private key never leaves your machine - only signatures are sent.
1657
+ `;
1658
+ }
1659
+ function formatFundingMessageCompact(address) {
1660
+ const links = getPaymentLinks(address);
1661
+ return `I need a little top-up to keep helping you! Send USDC on Base to: ${address}
1662
+ Check my balance: ${links.basescan}`;
1663
+ }
1664
+ var WALLET_FILE_PATH = WALLET_FILE;
1665
+ var WALLET_DIR_PATH = WALLET_DIR;
1666
+
1667
+ // src/solana-wallet.ts
1668
+ var fs2 = __toESM(require("fs"), 1);
1669
+ var path2 = __toESM(require("path"), 1);
1670
+ var os2 = __toESM(require("os"), 1);
1671
+ var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
1672
+ var SOLANA_WALLET_FILE = path2.join(WALLET_DIR2, ".solana-session");
1673
+ function createSolanaWallet() {
1674
+ const { Keypair } = require("@solana/web3.js");
1675
+ const bs58 = require("bs58");
1676
+ const keypair = Keypair.generate();
1677
+ return {
1678
+ address: keypair.publicKey.toBase58(),
1679
+ privateKey: bs58.default?.encode(keypair.secretKey) ?? bs58.encode(keypair.secretKey)
1680
+ };
1681
+ }
1682
+ async function solanaKeyToBytes(privateKey) {
1683
+ try {
1684
+ const bs58 = await import("bs58");
1685
+ const bytes = (bs58.default ?? bs58).decode(privateKey);
1686
+ if (bytes.length !== 64) {
1687
+ throw new Error(`Invalid Solana key length: expected 64 bytes, got ${bytes.length}`);
1688
+ }
1689
+ return bytes;
1690
+ } catch (err) {
1691
+ const msg = err instanceof Error ? err.message : String(err);
1692
+ throw new Error(`Invalid Solana private key: ${msg}`);
1693
+ }
1694
+ }
1695
+ async function solanaPublicKey(privateKey) {
1696
+ const { Keypair } = await import("@solana/web3.js");
1697
+ const bytes = await solanaKeyToBytes(privateKey);
1698
+ return Keypair.fromSecretKey(bytes).publicKey.toBase58();
1699
+ }
1700
+ function saveSolanaWallet(privateKey) {
1701
+ if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
1702
+ fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
1703
+ return SOLANA_WALLET_FILE;
1704
+ }
1705
+ function scanSolanaWallets() {
1706
+ const home = os2.homedir();
1707
+ const results = [];
1708
+ try {
1709
+ const entries = fs2.readdirSync(home, { withFileTypes: true });
1710
+ for (const entry of entries) {
1711
+ if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
1712
+ const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
1713
+ if (fs2.existsSync(solanaWalletFile)) {
1714
+ try {
1715
+ const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
1716
+ const pk = data.privateKey || "";
1717
+ const addr = data.address || "";
1718
+ if (pk && addr) {
1719
+ const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
1720
+ results.push({ mtime, secretKey: pk, publicKey: addr });
1721
+ }
1722
+ } catch {
1723
+ }
1724
+ }
1725
+ if (entry.name === ".brcc") {
1726
+ const brccWalletFile = path2.join(home, entry.name, "wallet.json");
1727
+ if (fs2.existsSync(brccWalletFile)) {
1728
+ try {
1729
+ const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
1730
+ const pk = data.privateKey || "";
1731
+ const addr = data.address || "";
1732
+ if (pk && addr) {
1733
+ const mtime = fs2.statSync(brccWalletFile).mtimeMs;
1734
+ results.push({ mtime, secretKey: pk, publicKey: addr });
1735
+ }
1736
+ } catch {
1737
+ }
1738
+ }
1739
+ }
1740
+ }
1741
+ } catch {
1742
+ }
1743
+ results.sort((a, b) => b.mtime - a.mtime);
1744
+ return results.map(({ secretKey, publicKey }) => ({ secretKey, publicKey }));
1745
+ }
1746
+ function loadSolanaWallet() {
1747
+ const wallets = scanSolanaWallets();
1748
+ if (wallets.length > 0) return wallets[0].secretKey;
1749
+ if (fs2.existsSync(SOLANA_WALLET_FILE)) {
1750
+ const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
1751
+ if (key) return key;
1752
+ }
1753
+ return null;
1754
+ }
1755
+ async function getOrCreateSolanaWallet() {
1756
+ const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
1757
+ if (envKey) {
1758
+ const address2 = await solanaPublicKey(envKey);
1759
+ return { privateKey: envKey, address: address2, isNew: false };
1760
+ }
1761
+ const wallets = scanSolanaWallets();
1762
+ if (wallets.length > 0) {
1763
+ return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
1764
+ }
1765
+ if (fs2.existsSync(SOLANA_WALLET_FILE)) {
1766
+ const fileKey = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
1767
+ if (fileKey) {
1768
+ const address2 = await solanaPublicKey(fileKey);
1769
+ return { privateKey: fileKey, address: address2, isNew: false };
1770
+ }
1771
+ }
1772
+ const { address, privateKey } = createSolanaWallet();
1773
+ saveSolanaWallet(privateKey);
1774
+ return { address, privateKey, isNew: true };
1775
+ }
1776
+
1777
+ // src/solana-client.ts
1778
+ var SOLANA_API_URL = "https://sol.blockrun.ai/api";
1779
+ var DEFAULT_MAX_TOKENS2 = 1024;
1780
+ var DEFAULT_TIMEOUT3 = 6e4;
1781
+ var SDK_VERSION2 = "0.3.0";
1782
+ var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
1783
+ var SolanaLLMClient = class {
1784
+ static SOLANA_API_URL = SOLANA_API_URL;
1785
+ privateKey;
1786
+ apiUrl;
1787
+ rpcUrl;
1788
+ timeout;
1789
+ sessionTotalUsd = 0;
1790
+ sessionCalls = 0;
1791
+ addressCache = null;
1792
+ constructor(options = {}) {
1793
+ const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
1794
+ const privateKey = options.privateKey || envKey;
1795
+ if (!privateKey) {
1796
+ throw new Error(
1797
+ "Private key required. Pass privateKey in options or set SOLANA_WALLET_KEY environment variable."
1798
+ );
1799
+ }
1800
+ this.privateKey = privateKey;
1801
+ const apiUrl = options.apiUrl || SOLANA_API_URL;
1802
+ validateApiUrl(apiUrl);
1803
+ this.apiUrl = apiUrl.replace(/\/$/, "");
1804
+ this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
1805
+ this.timeout = options.timeout || DEFAULT_TIMEOUT3;
1806
+ }
1807
+ /** Get Solana wallet address (public key in base58). */
1808
+ async getWalletAddress() {
1809
+ if (!this.addressCache) {
1810
+ this.addressCache = await solanaPublicKey(this.privateKey);
1811
+ }
1812
+ return this.addressCache;
1813
+ }
1814
+ /** Simple 1-line chat. */
1815
+ async chat(model, prompt, options) {
1816
+ const messages = [];
1817
+ if (options?.system) messages.push({ role: "system", content: options.system });
1818
+ messages.push({ role: "user", content: prompt });
1819
+ const result = await this.chatCompletion(model, messages, {
1820
+ maxTokens: options?.maxTokens,
1821
+ temperature: options?.temperature,
1822
+ topP: options?.topP,
1823
+ search: options?.search,
1824
+ searchParameters: options?.searchParameters
1825
+ });
1826
+ return result.choices[0].message.content || "";
1827
+ }
1828
+ /** Full chat completion (OpenAI-compatible). */
1829
+ async chatCompletion(model, messages, options) {
1830
+ const body = {
1831
+ model,
1832
+ messages,
1833
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS2
1834
+ };
1835
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
1836
+ if (options?.topP !== void 0) body.top_p = options.topP;
1837
+ if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
1838
+ else if (options?.search === true) body.search_parameters = { mode: "on" };
1839
+ if (options?.tools !== void 0) body.tools = options.tools;
1840
+ if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
1841
+ return this.requestWithPayment("/v1/chat/completions", body);
1842
+ }
1843
+ /** List available models. */
1844
+ async listModels() {
1845
+ const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, { method: "GET" });
1846
+ if (!response.ok) {
1847
+ throw new APIError(`Failed to list models: ${response.status}`, response.status);
1848
+ }
1849
+ const data = await response.json();
1850
+ return data.data || [];
1851
+ }
1852
+ /**
1853
+ * Get Solana USDC balance.
1854
+ *
1855
+ * @returns USDC balance as a float
1856
+ */
1857
+ async getBalance() {
1858
+ const usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
1859
+ const address = await this.getWalletAddress();
1860
+ try {
1861
+ const response = await fetch(this.rpcUrl, {
1862
+ method: "POST",
1863
+ headers: { "Content-Type": "application/json" },
1864
+ body: JSON.stringify({
1865
+ jsonrpc: "2.0",
1866
+ id: 1,
1867
+ method: "getTokenAccountsByOwner",
1868
+ params: [address, { mint: usdc_mint }, { encoding: "jsonParsed" }]
1869
+ })
1870
+ });
1871
+ const data = await response.json();
1872
+ const accounts = data.result?.value || [];
1873
+ if (!accounts.length) return 0;
1874
+ let total = 0;
1875
+ for (const acct of accounts) {
1876
+ total += acct.account?.data?.parsed?.info?.tokenAmount?.uiAmount || 0;
1877
+ }
1878
+ return total;
1879
+ } catch {
1880
+ return 0;
1881
+ }
1882
+ }
1883
+ /** Edit an image using img2img (Solana payment). */
1884
+ async imageEdit(prompt, image, options) {
1885
+ const body = {
1886
+ model: options?.model || "openai/gpt-image-1",
1887
+ prompt,
1888
+ image,
1889
+ size: options?.size || "1024x1024",
1890
+ n: options?.n || 1
1891
+ };
1892
+ if (options?.mask !== void 0) body.mask = options.mask;
1893
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
1894
+ return data;
1895
+ }
1896
+ /** Standalone search (Solana payment). */
1897
+ async search(query, options) {
1898
+ const body = { query, max_results: options?.maxResults || 10 };
1899
+ if (options?.sources !== void 0) body.sources = options.sources;
1900
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
1901
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
1902
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
1903
+ return data;
1904
+ }
1905
+ // ============================================================
1906
+ // X/Twitter endpoints (powered by AttentionVC)
1907
+ // ============================================================
1908
+ async xUserLookup(usernames) {
1909
+ const names = Array.isArray(usernames) ? usernames : [usernames];
1910
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
1911
+ return data;
1912
+ }
1913
+ async xFollowers(username, cursor) {
1914
+ const body = { username };
1915
+ if (cursor !== void 0) body.cursor = cursor;
1916
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
1917
+ return data;
1918
+ }
1919
+ async xFollowings(username, cursor) {
1920
+ const body = { username };
1921
+ if (cursor !== void 0) body.cursor = cursor;
1922
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
1923
+ return data;
1924
+ }
1925
+ async xUserInfo(username) {
1926
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
1927
+ return data;
1928
+ }
1929
+ async xVerifiedFollowers(userId, cursor) {
1930
+ const body = { userId };
1931
+ if (cursor !== void 0) body.cursor = cursor;
1932
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
1933
+ return data;
1934
+ }
1935
+ async xUserTweets(username, includeReplies = false, cursor) {
1936
+ const body = { username, includeReplies };
1937
+ if (cursor !== void 0) body.cursor = cursor;
1938
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
1939
+ return data;
1940
+ }
1941
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
1942
+ const body = { username };
1943
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
1944
+ if (untilTime !== void 0) body.untilTime = untilTime;
1945
+ if (cursor !== void 0) body.cursor = cursor;
1946
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
1947
+ return data;
1948
+ }
1949
+ async xTweetLookup(tweetIds) {
1950
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
1951
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
1952
+ return data;
1953
+ }
1954
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
1955
+ const body = { tweetId, queryType };
1956
+ if (cursor !== void 0) body.cursor = cursor;
1957
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
1958
+ return data;
1959
+ }
1960
+ async xTweetThread(tweetId, cursor) {
1961
+ const body = { tweetId };
1962
+ if (cursor !== void 0) body.cursor = cursor;
1963
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
1964
+ return data;
1965
+ }
1966
+ async xSearch(query, queryType = "Latest", cursor) {
1967
+ const body = { query, queryType };
1968
+ if (cursor !== void 0) body.cursor = cursor;
1969
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
1970
+ return data;
1971
+ }
1972
+ async xTrending() {
1973
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1974
+ return data;
1975
+ }
1976
+ async xArticlesRising() {
1977
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1978
+ return data;
1979
+ }
1980
+ async xAuthorAnalytics(handle) {
1981
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1982
+ return data;
1983
+ }
1984
+ async xCompareAuthors(handle1, handle2) {
1985
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1986
+ return data;
1987
+ }
1988
+ // ── Prediction Markets (Powered by Predexon) ────────────────────────────
1989
+ async pm(path5, params) {
1990
+ return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
1991
+ }
1992
+ async pmQuery(path5, query) {
1993
+ return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1994
+ }
1995
+ /** Get session spending. */
1996
+ getSpending() {
1997
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
1998
+ }
1999
+ /** True if using sol.blockrun.ai. */
2000
+ isSolana() {
2001
+ return this.apiUrl.includes("sol.blockrun.ai");
2002
+ }
2003
+ async requestWithPayment(endpoint, body) {
2004
+ const url = `${this.apiUrl}${endpoint}`;
2005
+ const response = await this.fetchWithTimeout(url, {
2006
+ method: "POST",
2007
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
2008
+ body: JSON.stringify(body)
2009
+ });
2010
+ if (response.status === 402) {
2011
+ return this.handlePaymentAndRetry(url, body, response);
2012
+ }
2013
+ if (!response.ok) {
2014
+ let errorBody;
2015
+ try {
2016
+ errorBody = await response.json();
2017
+ } catch {
2018
+ errorBody = { error: "Request failed" };
2019
+ }
2020
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2021
+ }
2022
+ return response.json();
2023
+ }
2024
+ async handlePaymentAndRetry(url, body, response) {
2025
+ let paymentHeader = response.headers.get("payment-required");
2026
+ if (!paymentHeader) {
2027
+ try {
2028
+ const respBody = await response.json();
2029
+ if (respBody.accepts || respBody.x402Version) {
2030
+ paymentHeader = btoa(JSON.stringify(respBody));
2031
+ }
2032
+ } catch {
2033
+ }
2034
+ }
2035
+ if (!paymentHeader) {
2036
+ throw new PaymentError("402 response but no payment requirements found");
2037
+ }
2038
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2039
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
2040
+ if (!details.network?.startsWith("solana:")) {
2041
+ throw new PaymentError(
2042
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
2043
+ );
2044
+ }
2045
+ const feePayer = details.extra?.feePayer;
2046
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
2047
+ const fromAddress = await this.getWalletAddress();
2048
+ const secretKey = await solanaKeyToBytes(this.privateKey);
2049
+ const extensions = paymentRequired.extensions;
2050
+ const paymentPayload = await createSolanaPaymentPayload(
2051
+ secretKey,
2052
+ fromAddress,
2053
+ details.recipient,
2054
+ details.amount,
2055
+ feePayer,
2056
+ {
2057
+ resourceUrl: validateResourceUrl(
2058
+ details.resource?.url || `${this.apiUrl}/v1/chat/completions`,
2059
+ this.apiUrl
2060
+ ),
2061
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
2062
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2063
+ extra: details.extra,
2064
+ extensions,
2065
+ rpcUrl: this.rpcUrl
2066
+ }
2067
+ );
2068
+ const retryResponse = await this.fetchWithTimeout(url, {
2069
+ method: "POST",
2070
+ headers: {
2071
+ "Content-Type": "application/json",
2072
+ "User-Agent": USER_AGENT2,
2073
+ "PAYMENT-SIGNATURE": paymentPayload
2074
+ },
2075
+ body: JSON.stringify(body)
2076
+ });
2077
+ if (retryResponse.status === 402) {
2078
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
2079
+ }
2080
+ if (!retryResponse.ok) {
2081
+ let errorBody;
2082
+ try {
2083
+ errorBody = await retryResponse.json();
2084
+ } catch {
2085
+ errorBody = { error: "Request failed" };
2086
+ }
2087
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2088
+ }
2089
+ const costUsd = parseFloat(details.amount) / 1e6;
2090
+ this.sessionCalls += 1;
2091
+ this.sessionTotalUsd += costUsd;
2092
+ return retryResponse.json();
2093
+ }
2094
+ async requestWithPaymentRaw(endpoint, body) {
2095
+ const url = `${this.apiUrl}${endpoint}`;
2096
+ const response = await this.fetchWithTimeout(url, {
2097
+ method: "POST",
2098
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
2099
+ body: JSON.stringify(body)
2100
+ });
2101
+ if (response.status === 402) {
2102
+ return this.handlePaymentAndRetryRaw(url, body, response);
2103
+ }
2104
+ if (!response.ok) {
2105
+ let errorBody;
2106
+ try {
2107
+ errorBody = await response.json();
2108
+ } catch {
2109
+ errorBody = { error: "Request failed" };
2110
+ }
2111
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2112
+ }
2113
+ return response.json();
2114
+ }
2115
+ async handlePaymentAndRetryRaw(url, body, response) {
2116
+ let paymentHeader = response.headers.get("payment-required");
2117
+ if (!paymentHeader) {
2118
+ try {
2119
+ const respBody = await response.json();
2120
+ if (respBody.accepts || respBody.x402Version) {
2121
+ paymentHeader = btoa(JSON.stringify(respBody));
2122
+ }
2123
+ } catch {
2124
+ }
2125
+ }
2126
+ if (!paymentHeader) {
2127
+ throw new PaymentError("402 response but no payment requirements found");
2128
+ }
2129
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2130
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
2131
+ if (!details.network?.startsWith("solana:")) {
2132
+ throw new PaymentError(
2133
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
2134
+ );
2135
+ }
2136
+ const feePayer = details.extra?.feePayer;
2137
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
2138
+ const fromAddress = await this.getWalletAddress();
2139
+ const secretKey = await solanaKeyToBytes(this.privateKey);
2140
+ const extensions = paymentRequired.extensions;
2141
+ const paymentPayload = await createSolanaPaymentPayload(
2142
+ secretKey,
2143
+ fromAddress,
2144
+ details.recipient,
2145
+ details.amount,
2146
+ feePayer,
2147
+ {
2148
+ resourceUrl: validateResourceUrl(
2149
+ details.resource?.url || url,
2150
+ this.apiUrl
2151
+ ),
2152
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
2153
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2154
+ extra: details.extra,
2155
+ extensions,
2156
+ rpcUrl: this.rpcUrl
2157
+ }
2158
+ );
2159
+ const retryResponse = await this.fetchWithTimeout(url, {
2160
+ method: "POST",
2161
+ headers: {
2162
+ "Content-Type": "application/json",
2163
+ "User-Agent": USER_AGENT2,
2164
+ "PAYMENT-SIGNATURE": paymentPayload
2165
+ },
2166
+ body: JSON.stringify(body)
2167
+ });
2168
+ if (retryResponse.status === 402) {
2169
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
2170
+ }
2171
+ if (!retryResponse.ok) {
2172
+ let errorBody;
2173
+ try {
2174
+ errorBody = await retryResponse.json();
2175
+ } catch {
2176
+ errorBody = { error: "Request failed" };
2177
+ }
2178
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2179
+ }
2180
+ const costUsd = parseFloat(details.amount) / 1e6;
2181
+ this.sessionCalls += 1;
2182
+ this.sessionTotalUsd += costUsd;
2183
+ return retryResponse.json();
2184
+ }
2185
+ async getWithPaymentRaw(endpoint, params) {
2186
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
2187
+ const url = `${this.apiUrl}${endpoint}${query}`;
2188
+ const response = await this.fetchWithTimeout(url, {
2189
+ method: "GET",
2190
+ headers: { "User-Agent": USER_AGENT2 }
2191
+ });
2192
+ if (response.status === 402) {
2193
+ return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
2194
+ }
2195
+ if (!response.ok) {
2196
+ let errorBody;
2197
+ try {
2198
+ errorBody = await response.json();
2199
+ } catch {
2200
+ errorBody = { error: "Request failed" };
2201
+ }
2202
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2203
+ }
2204
+ return response.json();
2205
+ }
2206
+ async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
2207
+ let paymentHeader = response.headers.get("payment-required");
2208
+ if (!paymentHeader) {
2209
+ try {
2210
+ const respBody = await response.json();
2211
+ if (respBody.accepts || respBody.x402Version) {
2212
+ paymentHeader = btoa(JSON.stringify(respBody));
2213
+ }
2214
+ } catch {
2215
+ }
2216
+ }
2217
+ if (!paymentHeader) {
2218
+ throw new PaymentError("402 response but no payment requirements found");
2219
+ }
2220
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2221
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
2222
+ if (!details.network?.startsWith("solana:")) {
2223
+ throw new PaymentError(
2224
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
2225
+ );
2226
+ }
2227
+ const feePayer = details.extra?.feePayer;
2228
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
2229
+ const fromAddress = await this.getWalletAddress();
2230
+ const secretKey = await solanaKeyToBytes(this.privateKey);
2231
+ const extensions = paymentRequired.extensions;
2232
+ const paymentPayload = await createSolanaPaymentPayload(
2233
+ secretKey,
2234
+ fromAddress,
2235
+ details.recipient,
2236
+ details.amount,
2237
+ feePayer,
2238
+ {
2239
+ resourceUrl: validateResourceUrl(
2240
+ details.resource?.url || url,
2241
+ this.apiUrl
2242
+ ),
2243
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
2244
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2245
+ extra: details.extra,
2246
+ extensions,
2247
+ rpcUrl: this.rpcUrl
2248
+ }
2249
+ );
2250
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
2251
+ const retryUrl = `${this.apiUrl}${endpoint}${query}`;
2252
+ const retryResponse = await this.fetchWithTimeout(retryUrl, {
2253
+ method: "GET",
2254
+ headers: {
2255
+ "User-Agent": USER_AGENT2,
2256
+ "PAYMENT-SIGNATURE": paymentPayload
2257
+ }
2258
+ });
2259
+ if (retryResponse.status === 402) {
2260
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
2261
+ }
2262
+ if (!retryResponse.ok) {
2263
+ let errorBody;
2264
+ try {
2265
+ errorBody = await retryResponse.json();
2266
+ } catch {
2267
+ errorBody = { error: "Request failed" };
2268
+ }
2269
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2270
+ }
2271
+ const costUsd = parseFloat(details.amount) / 1e6;
2272
+ this.sessionCalls += 1;
2273
+ this.sessionTotalUsd += costUsd;
2274
+ return retryResponse.json();
2275
+ }
2276
+ async fetchWithTimeout(url, options) {
2277
+ const controller = new AbortController();
2278
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2279
+ try {
2280
+ return await fetch(url, { ...options, signal: controller.signal });
2281
+ } finally {
2282
+ clearTimeout(timeoutId);
2283
+ }
2284
+ }
2285
+ };
2286
+ function solanaClient(options = {}) {
2287
+ return new SolanaLLMClient({ ...options, apiUrl: SOLANA_API_URL });
2288
+ }
2289
+
2290
+ // src/cache.ts
2291
+ var fs3 = __toESM(require("fs"), 1);
2292
+ var path3 = __toESM(require("path"), 1);
2293
+ var os3 = __toESM(require("os"), 1);
2294
+ var crypto2 = __toESM(require("crypto"), 1);
2295
+ var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
2296
+ var DEFAULT_TTL = {
2297
+ "/v1/x/": 3600 * 1e3,
2298
+ "/v1/partner/": 3600 * 1e3,
2299
+ "/v1/pm/": 1800 * 1e3,
2300
+ "/v1/chat/": 0,
2301
+ "/v1/search": 900 * 1e3,
2302
+ "/v1/image": 0,
2303
+ "/v1/models": 86400 * 1e3
2304
+ };
2305
+ function getTtl(endpoint) {
2306
+ for (const [pattern, ttl] of Object.entries(DEFAULT_TTL)) {
2307
+ if (endpoint.includes(pattern)) return ttl;
2308
+ }
2309
+ return 3600 * 1e3;
2310
+ }
2311
+ function cacheKey(endpoint, body) {
2312
+ const keyData = JSON.stringify({ endpoint, body }, Object.keys({ endpoint, body }).sort());
2313
+ return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
2314
+ }
2315
+ function cachePath(key) {
2316
+ return path3.join(CACHE_DIR, `${key}.json`);
2317
+ }
2318
+ function getCached(key) {
2319
+ const filePath = cachePath(key);
2320
+ if (!fs3.existsSync(filePath)) return null;
2321
+ try {
2322
+ const raw = fs3.readFileSync(filePath, "utf-8");
2323
+ const entry = JSON.parse(raw);
2324
+ const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
2325
+ if (ttl <= 0) return null;
2326
+ if (Date.now() - entry.cachedAt > ttl) {
2327
+ try {
2328
+ fs3.unlinkSync(filePath);
2329
+ } catch {
2330
+ }
2331
+ return null;
2332
+ }
2333
+ return entry.response;
2334
+ } catch {
2335
+ return null;
2336
+ }
2337
+ }
2338
+ function getCachedByRequest(endpoint, body) {
2339
+ const ttl = getTtl(endpoint);
2340
+ if (ttl <= 0) return null;
2341
+ const key = cacheKey(endpoint, body);
2342
+ return getCached(key);
2343
+ }
2344
+ function setCache(key, data, ttlMs) {
2345
+ if (ttlMs <= 0) return;
2346
+ try {
2347
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2348
+ } catch {
2349
+ }
2350
+ const entry = {
2351
+ cachedAt: Date.now(),
2352
+ response: data,
2353
+ ttlMs
2354
+ };
2355
+ try {
2356
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2357
+ } catch {
2358
+ }
2359
+ }
2360
+ function saveToCache(endpoint, body, response, costUsd = 0) {
2361
+ const ttl = getTtl(endpoint);
2362
+ if (ttl <= 0) return;
2363
+ try {
2364
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2365
+ } catch {
2366
+ }
2367
+ const key = cacheKey(endpoint, body);
2368
+ const entry = {
2369
+ cachedAt: Date.now(),
2370
+ endpoint,
2371
+ body,
2372
+ response,
2373
+ costUsd
2374
+ };
2375
+ try {
2376
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2377
+ } catch {
2378
+ }
2379
+ }
2380
+ function clearCache() {
2381
+ if (!fs3.existsSync(CACHE_DIR)) return 0;
2382
+ let count = 0;
2383
+ try {
2384
+ const files = fs3.readdirSync(CACHE_DIR);
2385
+ for (const file of files) {
2386
+ if (file.endsWith(".json")) {
2387
+ try {
2388
+ fs3.unlinkSync(path3.join(CACHE_DIR, file));
2389
+ count++;
2390
+ } catch {
2391
+ }
2392
+ }
2393
+ }
2394
+ } catch {
2395
+ }
2396
+ return count;
2397
+ }
2398
+
2399
+ // src/setup.ts
2400
+ function setupAgentWallet(options) {
2401
+ const { address, privateKey, isNew } = getOrCreateWallet();
2402
+ if (isNew && !options?.silent) {
2403
+ console.error(
2404
+ `
2405
+ BlockRun Agent Wallet Created!
2406
+ Address: ${address}
2407
+ Send USDC on Base to get started.
2408
+ `
2409
+ );
2410
+ }
2411
+ return new LLMClient({ privateKey });
2412
+ }
2413
+ async function setupAgentSolanaWallet(options) {
2414
+ const result = await getOrCreateSolanaWallet();
2415
+ if (result.isNew && !options?.silent) {
2416
+ console.error(
2417
+ `
2418
+ BlockRun Solana Agent Wallet Created!
2419
+ Address: ${result.address}
2420
+ Send USDC on Solana to get started.
2421
+ `
2422
+ );
2423
+ }
2424
+ return new SolanaLLMClient({ privateKey: result.privateKey });
2425
+ }
2426
+ async function status() {
2427
+ const client = setupAgentWallet({ silent: true });
2428
+ const address = client.getWalletAddress();
2429
+ const balance = await client.getBalance();
2430
+ console.log(`Wallet: ${address}`);
2431
+ console.log(`Balance: $${balance.toFixed(2)} USDC`);
2432
+ return { address, balance };
2433
+ }
2434
+
2435
+ // src/cost-log.ts
2436
+ var fs4 = __toESM(require("fs"), 1);
2437
+ var path4 = __toESM(require("path"), 1);
2438
+ var os4 = __toESM(require("os"), 1);
2439
+ var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
2440
+ var COST_LOG_FILE = path4.join(DATA_DIR, "costs.jsonl");
2441
+ function logCost(entry) {
2442
+ try {
2443
+ fs4.mkdirSync(DATA_DIR, { recursive: true });
2444
+ } catch {
2445
+ }
2446
+ try {
2447
+ fs4.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2448
+ } catch {
2449
+ }
2450
+ }
2451
+ function getCostSummary() {
2452
+ if (!fs4.existsSync(COST_LOG_FILE)) {
2453
+ return { totalUsd: 0, calls: 0, byModel: {} };
2454
+ }
2455
+ let totalUsd = 0;
2456
+ let calls = 0;
2457
+ const byModel = {};
2458
+ try {
2459
+ const content = fs4.readFileSync(COST_LOG_FILE, "utf-8").trim();
2460
+ if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
2461
+ for (const line of content.split("\n")) {
2462
+ if (!line) continue;
2463
+ try {
2464
+ const entry = JSON.parse(line);
2465
+ totalUsd += entry.costUsd;
2466
+ calls += 1;
2467
+ byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
2468
+ } catch {
2469
+ }
2470
+ }
2471
+ } catch {
2472
+ }
2473
+ return { totalUsd, calls, byModel };
2474
+ }
2475
+
2476
+ // src/openai-compat.ts
2477
+ var StreamingResponse = class {
2478
+ reader;
2479
+ decoder;
2480
+ buffer = "";
2481
+ model;
2482
+ id;
2483
+ constructor(response, model) {
2484
+ if (!response.body) {
2485
+ throw new Error("Response body is null");
2486
+ }
2487
+ this.reader = response.body.getReader();
2488
+ this.decoder = new TextDecoder();
2489
+ this.model = model;
2490
+ this.id = `chatcmpl-${Date.now()}`;
2491
+ }
2492
+ async *[Symbol.asyncIterator]() {
2493
+ try {
2494
+ while (true) {
2495
+ const { done, value } = await this.reader.read();
2496
+ if (done) break;
2497
+ this.buffer += this.decoder.decode(value, { stream: true });
2498
+ const lines = this.buffer.split("\n");
2499
+ this.buffer = lines.pop() || "";
2500
+ for (const line of lines) {
2501
+ const trimmed = line.trim();
2502
+ if (!trimmed || !trimmed.startsWith("data: ")) continue;
2503
+ const data = trimmed.slice(6);
2504
+ if (data === "[DONE]") return;
2505
+ try {
2506
+ const parsed = JSON.parse(data);
2507
+ yield this.transformChunk(parsed);
2508
+ } catch {
2509
+ }
2510
+ }
2511
+ }
2512
+ } finally {
2513
+ this.reader.releaseLock();
2514
+ }
2515
+ }
2516
+ transformChunk(data) {
2517
+ const choices = data.choices || [];
2518
+ return {
2519
+ id: data.id || this.id,
2520
+ object: "chat.completion.chunk",
2521
+ created: data.created || Math.floor(Date.now() / 1e3),
2522
+ model: data.model || this.model,
2523
+ choices: choices.map((choice, index) => ({
2524
+ index: choice.index ?? index,
2525
+ delta: {
2526
+ role: choice.delta?.role,
2527
+ content: choice.delta?.content
2528
+ },
2529
+ finish_reason: choice.finish_reason || null
2530
+ }))
2531
+ };
2532
+ }
2533
+ };
2534
+ var ChatCompletions = class {
2535
+ constructor(client, apiUrl, timeout) {
2536
+ this.client = client;
2537
+ this.apiUrl = apiUrl;
2538
+ this.timeout = timeout;
2539
+ }
2540
+ async create(params) {
2541
+ if (params.stream) {
2542
+ return this.createStream(params);
2543
+ }
2544
+ const response = await this.client.chatCompletion(
2545
+ params.model,
2546
+ params.messages,
2547
+ {
2548
+ maxTokens: params.max_tokens,
2549
+ temperature: params.temperature,
2550
+ topP: params.top_p,
2551
+ tools: params.tools,
2552
+ toolChoice: params.tool_choice
2553
+ }
2554
+ );
2555
+ return this.transformResponse(response);
2556
+ }
2557
+ async createStream(params) {
2558
+ const url = `${this.apiUrl}/v1/chat/completions`;
2559
+ const body = {
2560
+ model: params.model,
2561
+ messages: params.messages,
2562
+ max_tokens: params.max_tokens || 1024,
2563
+ temperature: params.temperature,
2564
+ top_p: params.top_p,
2565
+ stream: true
2566
+ };
2567
+ if (params.tools) {
2568
+ body.tools = params.tools;
2569
+ }
2570
+ if (params.tool_choice) {
2571
+ body.tool_choice = params.tool_choice;
2572
+ }
2573
+ const controller = new AbortController();
2574
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
2575
+ try {
2576
+ const response = await fetch(url, {
2577
+ method: "POST",
2578
+ headers: { "Content-Type": "application/json" },
2579
+ body: JSON.stringify(body),
2580
+ signal: controller.signal
2581
+ });
2582
+ if (response.status === 402) {
2583
+ const paymentHeader = response.headers.get("payment-required");
2584
+ if (!paymentHeader) {
2585
+ throw new Error("402 response but no payment requirements found");
2586
+ }
2587
+ throw new Error(
2588
+ "Streaming with automatic payment requires direct wallet access. Please use non-streaming mode or contact support for streaming setup."
2589
+ );
2590
+ }
2591
+ if (!response.ok) {
2592
+ throw new Error(`API error: ${response.status}`);
2593
+ }
2594
+ return new StreamingResponse(response, params.model);
2595
+ } finally {
2596
+ clearTimeout(timeoutId);
2597
+ }
2598
+ }
2599
+ transformResponse(response) {
2600
+ return {
2601
+ id: response.id || `chatcmpl-${Date.now()}`,
2602
+ object: "chat.completion",
2603
+ created: response.created || Math.floor(Date.now() / 1e3),
2604
+ model: response.model,
2605
+ choices: response.choices.map((choice, index) => ({
2606
+ index: choice.index ?? index,
2607
+ message: {
2608
+ role: "assistant",
2609
+ content: choice.message.content,
2610
+ tool_calls: choice.message.tool_calls
2611
+ },
2612
+ finish_reason: choice.finish_reason || "stop"
2613
+ })),
2614
+ usage: response.usage
2615
+ };
2616
+ }
2617
+ };
2618
+ var Chat = class {
2619
+ completions;
2620
+ constructor(client, apiUrl, timeout) {
2621
+ this.completions = new ChatCompletions(client, apiUrl, timeout);
2622
+ }
2623
+ };
2624
+ var OpenAI = class {
2625
+ chat;
2626
+ client;
2627
+ constructor(options = {}) {
2628
+ const privateKey = options.walletKey || options.privateKey;
2629
+ const apiUrl = options.baseURL || "https://blockrun.ai/api";
2630
+ const timeout = options.timeout || 6e4;
2631
+ this.client = new LLMClient({
2632
+ privateKey,
2633
+ apiUrl,
2634
+ timeout
2635
+ });
2636
+ this.chat = new Chat(this.client, apiUrl, timeout);
2637
+ }
2638
+ /**
2639
+ * Get the wallet address being used for payments.
2640
+ */
2641
+ getWalletAddress() {
2642
+ return this.client.getWalletAddress();
2643
+ }
2644
+ };
2645
+
2646
+ // src/anthropic-compat.ts
2647
+ var import_accounts5 = require("viem/accounts");
2648
+ var AnthropicClient = class {
2649
+ _client = null;
2650
+ _clientPromise = null;
2651
+ _privateKey;
2652
+ _account;
2653
+ _apiUrl;
2654
+ _timeout;
2655
+ constructor(options = {}) {
2656
+ const wallet = getOrCreateWallet();
2657
+ const key = options.privateKey ?? wallet.privateKey;
2658
+ validatePrivateKey(key);
2659
+ this._privateKey = key;
2660
+ this._account = (0, import_accounts5.privateKeyToAccount)(this._privateKey);
2661
+ const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
2662
+ validateApiUrl(apiUrl);
2663
+ this._apiUrl = apiUrl.replace(/\/$/, "");
2664
+ this._timeout = options.timeout ?? 6e4;
2665
+ }
2666
+ async _getClient() {
2667
+ if (this._client) return this._client;
2668
+ if (this._clientPromise) return this._clientPromise;
2669
+ this._clientPromise = (async () => {
2670
+ const { default: Anthropic } = await import("@anthropic-ai/sdk");
2671
+ this._client = new Anthropic({
2672
+ baseURL: this._apiUrl,
2673
+ apiKey: "blockrun",
2674
+ fetch: this._x402Fetch.bind(this)
2675
+ });
2676
+ return this._client;
2677
+ })();
2678
+ return this._clientPromise;
2679
+ }
2680
+ async _x402Fetch(input, init) {
2681
+ const controller = new AbortController();
2682
+ const timeoutId = setTimeout(() => controller.abort(), this._timeout);
2683
+ try {
2684
+ const mergedInit = { ...init, signal: controller.signal };
2685
+ let response = await globalThis.fetch(input, mergedInit);
2686
+ if (response.status === 402) {
2687
+ let paymentHeader = response.headers.get("payment-required");
2688
+ if (!paymentHeader) {
2689
+ try {
2690
+ const respBody = await response.json();
2691
+ if (respBody.x402 || respBody.accepts) {
2692
+ paymentHeader = btoa(JSON.stringify(respBody));
2693
+ }
2694
+ } catch {
2695
+ }
2696
+ }
2697
+ if (!paymentHeader) {
2698
+ throw new Error("402 response but no payment requirements found");
2699
+ }
2700
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2701
+ const details = extractPaymentDetails(paymentRequired);
2702
+ const extensions = paymentRequired.extensions;
2703
+ const paymentPayload = await createPaymentPayload(
2704
+ this._privateKey,
2705
+ this._account.address,
2706
+ details.recipient,
2707
+ details.amount,
2708
+ details.network || "eip155:8453",
2709
+ {
2710
+ resourceUrl: validateResourceUrl(
2711
+ details.resource?.url || `${this._apiUrl}/v1/messages`,
2712
+ this._apiUrl
2713
+ ),
2714
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
2715
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2716
+ extra: details.extra,
2717
+ extensions
2718
+ }
2719
+ );
2720
+ const newHeaders = new Headers(init?.headers);
2721
+ newHeaders.set("PAYMENT-SIGNATURE", paymentPayload);
2722
+ response = await globalThis.fetch(input, {
2723
+ ...init,
2724
+ headers: newHeaders,
2725
+ signal: controller.signal
2726
+ });
2727
+ if (response.status === 402) {
2728
+ throw new Error(
2729
+ "Payment was rejected. Check your wallet balance."
2730
+ );
2731
+ }
2732
+ }
2733
+ return response;
2734
+ } finally {
2735
+ clearTimeout(timeoutId);
2736
+ }
2737
+ }
2738
+ get messages() {
2739
+ const handler = {
2740
+ get: (_target, prop) => {
2741
+ return async (...args) => {
2742
+ const client = await this._getClient();
2743
+ const messages = client.messages;
2744
+ return messages[prop](...args);
2745
+ };
2746
+ }
2747
+ };
2748
+ return new Proxy({}, handler);
2749
+ }
2750
+ getWalletAddress() {
2751
+ return this._account.address;
2752
+ }
2753
+ };
2754
+ // Annotate the CommonJS export names for ESM import in node:
2755
+ 0 && (module.exports = {
2756
+ APIError,
2757
+ AnthropicClient,
2758
+ BASE_CHAIN_ID,
2759
+ BlockrunError,
2760
+ ImageClient,
2761
+ LLMClient,
2762
+ OpenAI,
2763
+ PaymentError,
2764
+ SOLANA_NETWORK,
2765
+ SOLANA_WALLET_FILE_PATH,
2766
+ SolanaLLMClient,
2767
+ USDC_BASE,
2768
+ USDC_BASE_CONTRACT,
2769
+ USDC_SOLANA,
2770
+ WALLET_DIR_PATH,
2771
+ WALLET_FILE_PATH,
2772
+ clearCache,
2773
+ createPaymentPayload,
2774
+ createSolanaPaymentPayload,
2775
+ createSolanaWallet,
2776
+ createWallet,
2777
+ extractPaymentDetails,
2778
+ formatFundingMessageCompact,
2779
+ formatNeedsFundingMessage,
2780
+ formatWalletCreatedMessage,
2781
+ getCached,
2782
+ getCachedByRequest,
2783
+ getCostSummary,
2784
+ getEip681Uri,
2785
+ getOrCreateSolanaWallet,
2786
+ getOrCreateWallet,
2787
+ getPaymentLinks,
2788
+ getWalletAddress,
2789
+ loadSolanaWallet,
2790
+ loadWallet,
2791
+ logCost,
2792
+ parsePaymentRequired,
2793
+ saveSolanaWallet,
2794
+ saveToCache,
2795
+ saveWallet,
2796
+ scanSolanaWallets,
2797
+ scanWallets,
2798
+ setCache,
2799
+ setupAgentSolanaWallet,
2800
+ setupAgentWallet,
2801
+ solanaClient,
2802
+ solanaKeyToBytes,
2803
+ solanaPublicKey,
2804
+ status,
2805
+ testnetClient
2806
+ });