@blockrun/llm 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,9 @@
1
- import "./chunk-2ESYSVXG.js";
1
+ import {
2
+ __require,
3
+ __toCommonJS,
4
+ index_esm_exports,
5
+ init_index_esm
6
+ } from "./chunk-KRDGCX7W.js";
2
7
 
3
8
  // src/client.ts
4
9
  import { privateKeyToAccount } from "viem/accounts";
@@ -34,6 +39,10 @@ import { route, DEFAULT_ROUTING_CONFIG } from "@blockrun/clawrouter";
34
39
  import { signTypedData } from "viem/accounts";
35
40
  var BASE_CHAIN_ID = 8453;
36
41
  var USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
42
+ var SOLANA_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
43
+ var USDC_SOLANA = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
44
+ var DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS = 1;
45
+ var DEFAULT_COMPUTE_UNIT_LIMIT = 8e3;
37
46
  var USDC_DOMAIN = {
38
47
  name: "USD Coin",
39
48
  version: "2",
@@ -106,6 +115,66 @@ async function createPaymentPayload(privateKey, fromAddress, recipient, amount,
106
115
  };
107
116
  return btoa(JSON.stringify(paymentData));
108
117
  }
118
+ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amount, feePayer, options = {}) {
119
+ const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("./index.esm-SXKIFLA7.js");
120
+ const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("./esm-PTFDM6PE.js");
121
+ const { Keypair } = await import("./index.esm-SXKIFLA7.js");
122
+ const rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
123
+ const connection = new Connection(rpcUrl);
124
+ const keypair = Keypair.fromSecretKey(secretKey);
125
+ const feePayerPubkey = new PublicKey(feePayer);
126
+ const ownerPubkey = keypair.publicKey;
127
+ const tokenMint = new PublicKey(USDC_SOLANA);
128
+ const payToPubkey = new PublicKey(recipient);
129
+ const mintInfo = await getMint(connection, tokenMint);
130
+ const sourceATA = await getAssociatedTokenAddress(tokenMint, ownerPubkey, false);
131
+ const destinationATA = await getAssociatedTokenAddress(tokenMint, payToPubkey, false);
132
+ const { blockhash } = await connection.getLatestBlockhash();
133
+ const setComputeUnitPriceIx = ComputeBudgetProgram.setComputeUnitPrice({
134
+ microLamports: DEFAULT_COMPUTE_UNIT_PRICE_MICROLAMPORTS
135
+ });
136
+ const setComputeUnitLimitIx = ComputeBudgetProgram.setComputeUnitLimit({
137
+ units: DEFAULT_COMPUTE_UNIT_LIMIT
138
+ });
139
+ const transferIx = createTransferCheckedInstruction(
140
+ sourceATA,
141
+ tokenMint,
142
+ destinationATA,
143
+ ownerPubkey,
144
+ BigInt(amount),
145
+ mintInfo.decimals
146
+ );
147
+ const messageV0 = new TransactionMessage({
148
+ payerKey: feePayerPubkey,
149
+ recentBlockhash: blockhash,
150
+ instructions: [setComputeUnitLimitIx, setComputeUnitPriceIx, transferIx]
151
+ }).compileToV0Message();
152
+ const transaction = new VersionedTransaction(messageV0);
153
+ transaction.sign([keypair]);
154
+ const serializedTx = Buffer.from(transaction.serialize()).toString("base64");
155
+ const paymentData = {
156
+ x402Version: 2,
157
+ resource: {
158
+ url: options.resourceUrl || "https://blockrun.ai/api/v1/chat/completions",
159
+ description: options.resourceDescription || "BlockRun AI API call",
160
+ mimeType: "application/json"
161
+ },
162
+ accepted: {
163
+ scheme: "exact",
164
+ network: SOLANA_NETWORK,
165
+ amount,
166
+ asset: USDC_SOLANA,
167
+ payTo: recipient,
168
+ maxTimeoutSeconds: options.maxTimeoutSeconds || 300,
169
+ extra: options.extra || { feePayer }
170
+ },
171
+ payload: {
172
+ transaction: serializedTx
173
+ },
174
+ extensions: options.extensions || {}
175
+ };
176
+ return btoa(JSON.stringify(paymentData));
177
+ }
109
178
  function parsePaymentRequired(headerValue) {
110
179
  try {
111
180
  const decoded = atob(headerValue);
@@ -304,7 +373,7 @@ var LLMClient = class {
304
373
  * ```ts
305
374
  * const result = await client.smartChat('What is 2+2?');
306
375
  * console.log(result.response); // '4'
307
- * console.log(result.model); // 'google/gemini-2.5-flash'
376
+ * console.log(result.model); // 'google/gemini-2.5-flash-lite'
308
377
  * console.log(result.routing.savings); // 0.78 (78% savings)
309
378
  * ```
310
379
  *
@@ -504,6 +573,103 @@ var LLMClient = class {
504
573
  this.sessionTotalUsd += costUsd;
505
574
  return retryResponse.json();
506
575
  }
576
+ /**
577
+ * Make a request with automatic x402 payment handling, returning raw JSON.
578
+ * Used for non-ChatResponse endpoints (X/Twitter, search, image edit, etc.).
579
+ */
580
+ async requestWithPaymentRaw(endpoint, body) {
581
+ const url = `${this.apiUrl}${endpoint}`;
582
+ const response = await this.fetchWithTimeout(url, {
583
+ method: "POST",
584
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT },
585
+ body: JSON.stringify(body)
586
+ });
587
+ if (response.status === 402) {
588
+ return this.handlePaymentAndRetryRaw(url, body, response);
589
+ }
590
+ if (!response.ok) {
591
+ let errorBody;
592
+ try {
593
+ errorBody = await response.json();
594
+ } catch {
595
+ errorBody = { error: "Request failed" };
596
+ }
597
+ throw new APIError(
598
+ `API error: ${response.status}`,
599
+ response.status,
600
+ sanitizeErrorResponse(errorBody)
601
+ );
602
+ }
603
+ return response.json();
604
+ }
605
+ /**
606
+ * Handle 402 response for raw endpoints: parse requirements, sign payment, retry.
607
+ */
608
+ async handlePaymentAndRetryRaw(url, body, response) {
609
+ let paymentHeader = response.headers.get("payment-required");
610
+ if (!paymentHeader) {
611
+ try {
612
+ const respBody = await response.json();
613
+ if (respBody.x402 || respBody.accepts) {
614
+ paymentHeader = btoa(JSON.stringify(respBody));
615
+ }
616
+ } catch {
617
+ console.debug("Failed to parse payment header from response body");
618
+ }
619
+ }
620
+ if (!paymentHeader) {
621
+ throw new PaymentError("402 response but no payment requirements found");
622
+ }
623
+ const paymentRequired = parsePaymentRequired(paymentHeader);
624
+ const details = extractPaymentDetails(paymentRequired);
625
+ const extensions = paymentRequired.extensions;
626
+ const paymentPayload = await createPaymentPayload(
627
+ this.privateKey,
628
+ this.account.address,
629
+ details.recipient,
630
+ details.amount,
631
+ details.network || "eip155:8453",
632
+ {
633
+ resourceUrl: validateResourceUrl(
634
+ details.resource?.url || url,
635
+ this.apiUrl
636
+ ),
637
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
638
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
639
+ extra: details.extra,
640
+ extensions
641
+ }
642
+ );
643
+ const retryResponse = await this.fetchWithTimeout(url, {
644
+ method: "POST",
645
+ headers: {
646
+ "Content-Type": "application/json",
647
+ "User-Agent": USER_AGENT,
648
+ "PAYMENT-SIGNATURE": paymentPayload
649
+ },
650
+ body: JSON.stringify(body)
651
+ });
652
+ if (retryResponse.status === 402) {
653
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
654
+ }
655
+ if (!retryResponse.ok) {
656
+ let errorBody;
657
+ try {
658
+ errorBody = await retryResponse.json();
659
+ } catch {
660
+ errorBody = { error: "Request failed" };
661
+ }
662
+ throw new APIError(
663
+ `API error after payment: ${retryResponse.status}`,
664
+ retryResponse.status,
665
+ sanitizeErrorResponse(errorBody)
666
+ );
667
+ }
668
+ const costUsd = parseFloat(details.amount) / 1e6;
669
+ this.sessionCalls += 1;
670
+ this.sessionTotalUsd += costUsd;
671
+ return retryResponse.json();
672
+ }
507
673
  /**
508
674
  * Fetch with timeout.
509
675
  */
@@ -586,6 +752,285 @@ var LLMClient = class {
586
752
  }
587
753
  return [...llmModels, ...imageModels];
588
754
  }
755
+ /**
756
+ * Edit an image using img2img.
757
+ *
758
+ * @param prompt - Text description of the desired edit
759
+ * @param image - Base64-encoded image or URL of the source image
760
+ * @param options - Optional edit parameters
761
+ * @returns ImageResponse with edited image URLs
762
+ */
763
+ async imageEdit(prompt, image, options) {
764
+ const body = {
765
+ model: options?.model || "openai/gpt-image-1",
766
+ prompt,
767
+ image,
768
+ size: options?.size || "1024x1024",
769
+ n: options?.n || 1
770
+ };
771
+ if (options?.mask !== void 0) {
772
+ body.mask = options.mask;
773
+ }
774
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
775
+ return data;
776
+ }
777
+ /**
778
+ * Standalone search (web, X/Twitter, news).
779
+ *
780
+ * @param query - Search query
781
+ * @param options - Optional search parameters
782
+ * @returns SearchResult with summary and citations
783
+ */
784
+ async search(query, options) {
785
+ const body = {
786
+ query,
787
+ max_results: options?.maxResults || 10
788
+ };
789
+ if (options?.sources !== void 0) body.sources = options.sources;
790
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
791
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
792
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
793
+ return data;
794
+ }
795
+ /**
796
+ * Get USDC balance on Base network.
797
+ *
798
+ * Automatically detects mainnet vs testnet based on API URL.
799
+ *
800
+ * @returns USDC balance as a float (6 decimal places normalized)
801
+ *
802
+ * @example
803
+ * const balance = await client.getBalance();
804
+ * console.log(`Balance: $${balance.toFixed(2)} USDC`);
805
+ */
806
+ async getBalance() {
807
+ const isTestnet = this.isTestnet();
808
+ const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
809
+ 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"];
810
+ const selector = "0x70a08231";
811
+ const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
812
+ const data = selector + paddedAddress;
813
+ const payload = {
814
+ jsonrpc: "2.0",
815
+ method: "eth_call",
816
+ params: [{ to: usdcContract, data }, "latest"],
817
+ id: 1
818
+ };
819
+ let lastError;
820
+ for (const rpc of rpcs) {
821
+ try {
822
+ const response = await fetch(rpc, {
823
+ method: "POST",
824
+ headers: { "Content-Type": "application/json" },
825
+ body: JSON.stringify(payload)
826
+ });
827
+ const result = await response.json();
828
+ const balanceRaw = parseInt(result.result || "0x0", 16);
829
+ return balanceRaw / 1e6;
830
+ } catch (e) {
831
+ lastError = e;
832
+ }
833
+ }
834
+ throw lastError || new Error("All RPCs failed");
835
+ }
836
+ // ============================================================
837
+ // X/Twitter endpoints (powered by AttentionVC)
838
+ // ============================================================
839
+ /**
840
+ * Look up X/Twitter user profiles by username.
841
+ *
842
+ * Powered by AttentionVC. $0.002 per user (min $0.02, max $0.20).
843
+ *
844
+ * @param usernames - Single username or array of usernames (without @)
845
+ */
846
+ async xUserLookup(usernames) {
847
+ const names = Array.isArray(usernames) ? usernames : [usernames];
848
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
849
+ return data;
850
+ }
851
+ /**
852
+ * Get followers of an X/Twitter user.
853
+ *
854
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
855
+ *
856
+ * @param username - X/Twitter username (without @)
857
+ * @param cursor - Pagination cursor from previous response
858
+ */
859
+ async xFollowers(username, cursor) {
860
+ const body = { username };
861
+ if (cursor !== void 0) body.cursor = cursor;
862
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
863
+ return data;
864
+ }
865
+ /**
866
+ * Get accounts an X/Twitter user is following.
867
+ *
868
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
869
+ *
870
+ * @param username - X/Twitter username (without @)
871
+ * @param cursor - Pagination cursor from previous response
872
+ */
873
+ async xFollowings(username, cursor) {
874
+ const body = { username };
875
+ if (cursor !== void 0) body.cursor = cursor;
876
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
877
+ return data;
878
+ }
879
+ /**
880
+ * Get detailed profile info for a single X/Twitter user.
881
+ *
882
+ * Powered by AttentionVC. $0.002 per request.
883
+ *
884
+ * @param username - X/Twitter username (without @)
885
+ */
886
+ async xUserInfo(username) {
887
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
888
+ return data;
889
+ }
890
+ /**
891
+ * Get verified (blue-check) followers of an X/Twitter user.
892
+ *
893
+ * Powered by AttentionVC. $0.048 per page.
894
+ *
895
+ * @param userId - X/Twitter user ID (not username)
896
+ * @param cursor - Pagination cursor from previous response
897
+ */
898
+ async xVerifiedFollowers(userId, cursor) {
899
+ const body = { userId };
900
+ if (cursor !== void 0) body.cursor = cursor;
901
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
902
+ return data;
903
+ }
904
+ /**
905
+ * Get tweets posted by an X/Twitter user.
906
+ *
907
+ * Powered by AttentionVC. $0.032 per page.
908
+ *
909
+ * @param username - X/Twitter username (without @)
910
+ * @param includeReplies - Include reply tweets (default: false)
911
+ * @param cursor - Pagination cursor from previous response
912
+ */
913
+ async xUserTweets(username, includeReplies = false, cursor) {
914
+ const body = { username, includeReplies };
915
+ if (cursor !== void 0) body.cursor = cursor;
916
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
917
+ return data;
918
+ }
919
+ /**
920
+ * Get tweets that mention an X/Twitter user.
921
+ *
922
+ * Powered by AttentionVC. $0.032 per page.
923
+ *
924
+ * @param username - X/Twitter username (without @)
925
+ * @param sinceTime - Start time filter (ISO8601 or Unix timestamp)
926
+ * @param untilTime - End time filter (ISO8601 or Unix timestamp)
927
+ * @param cursor - Pagination cursor from previous response
928
+ */
929
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
930
+ const body = { username };
931
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
932
+ if (untilTime !== void 0) body.untilTime = untilTime;
933
+ if (cursor !== void 0) body.cursor = cursor;
934
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
935
+ return data;
936
+ }
937
+ /**
938
+ * Fetch full tweet data for up to 200 tweet IDs.
939
+ *
940
+ * Powered by AttentionVC. $0.16 per batch.
941
+ *
942
+ * @param tweetIds - Single tweet ID or array of tweet IDs (max 200)
943
+ */
944
+ async xTweetLookup(tweetIds) {
945
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
946
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
947
+ return data;
948
+ }
949
+ /**
950
+ * Get replies to a specific tweet.
951
+ *
952
+ * Powered by AttentionVC. $0.032 per page.
953
+ *
954
+ * @param tweetId - The tweet ID to get replies for
955
+ * @param queryType - Sort order: 'Latest' or 'Default'
956
+ * @param cursor - Pagination cursor from previous response
957
+ */
958
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
959
+ const body = { tweetId, queryType };
960
+ if (cursor !== void 0) body.cursor = cursor;
961
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
962
+ return data;
963
+ }
964
+ /**
965
+ * Get the full thread context for a tweet.
966
+ *
967
+ * Powered by AttentionVC. $0.032 per page.
968
+ *
969
+ * @param tweetId - The tweet ID to get thread for
970
+ * @param cursor - Pagination cursor from previous response
971
+ */
972
+ async xTweetThread(tweetId, cursor) {
973
+ const body = { tweetId };
974
+ if (cursor !== void 0) body.cursor = cursor;
975
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
976
+ return data;
977
+ }
978
+ /**
979
+ * Search X/Twitter with advanced query operators.
980
+ *
981
+ * Powered by AttentionVC. $0.032 per page.
982
+ *
983
+ * @param query - Search query (supports Twitter search operators)
984
+ * @param queryType - Sort order: 'Latest', 'Top', or 'Default'
985
+ * @param cursor - Pagination cursor from previous response
986
+ */
987
+ async xSearch(query, queryType = "Latest", cursor) {
988
+ const body = { query, queryType };
989
+ if (cursor !== void 0) body.cursor = cursor;
990
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
991
+ return data;
992
+ }
993
+ /**
994
+ * Get current trending topics on X/Twitter.
995
+ *
996
+ * Powered by AttentionVC. $0.002 per request.
997
+ */
998
+ async xTrending() {
999
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1000
+ return data;
1001
+ }
1002
+ /**
1003
+ * Get rising/viral articles from X/Twitter.
1004
+ *
1005
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1006
+ */
1007
+ async xArticlesRising() {
1008
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1009
+ return data;
1010
+ }
1011
+ /**
1012
+ * Get author analytics and intelligence metrics for an X/Twitter user.
1013
+ *
1014
+ * Powered by AttentionVC intelligence layer. $0.02 per request.
1015
+ *
1016
+ * @param handle - X/Twitter handle (without @)
1017
+ */
1018
+ async xAuthorAnalytics(handle) {
1019
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1020
+ return data;
1021
+ }
1022
+ /**
1023
+ * Compare two X/Twitter authors side-by-side with intelligence metrics.
1024
+ *
1025
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1026
+ *
1027
+ * @param handle1 - First X/Twitter handle (without @)
1028
+ * @param handle2 - Second X/Twitter handle (without @)
1029
+ */
1030
+ async xCompareAuthors(handle1, handle2) {
1031
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1032
+ return data;
1033
+ }
589
1034
  /**
590
1035
  * Get current session spending.
591
1036
  *
@@ -679,6 +1124,31 @@ var ImageClient = class {
679
1124
  }
680
1125
  return this.requestWithPayment("/v1/images/generations", body);
681
1126
  }
1127
+ /**
1128
+ * Edit an image using img2img.
1129
+ *
1130
+ * @param prompt - Text description of the desired edit
1131
+ * @param image - Base64-encoded image or URL of the source image
1132
+ * @param options - Optional edit parameters
1133
+ * @returns ImageResponse with edited image URLs
1134
+ *
1135
+ * @example
1136
+ * const result = await client.edit('Make it a painting', imageBase64);
1137
+ * console.log(result.data[0].url);
1138
+ */
1139
+ async edit(prompt, image, options) {
1140
+ const body = {
1141
+ model: options?.model || "openai/gpt-image-1",
1142
+ prompt,
1143
+ image,
1144
+ size: options?.size || "1024x1024",
1145
+ n: options?.n || 1
1146
+ };
1147
+ if (options?.mask !== void 0) {
1148
+ body.mask = options.mask;
1149
+ }
1150
+ return this.requestWithPayment("/v1/images/image2image", body);
1151
+ }
682
1152
  /**
683
1153
  * List available image generation models with pricing.
684
1154
  */
@@ -947,6 +1417,482 @@ Check my balance: ${links.basescan}`;
947
1417
  var WALLET_FILE_PATH = WALLET_FILE;
948
1418
  var WALLET_DIR_PATH = WALLET_DIR;
949
1419
 
1420
+ // src/solana-wallet.ts
1421
+ import * as fs2 from "fs";
1422
+ import * as path2 from "path";
1423
+ import * as os2 from "os";
1424
+ var WALLET_DIR2 = path2.join(os2.homedir(), ".blockrun");
1425
+ var SOLANA_WALLET_FILE = path2.join(WALLET_DIR2, ".solana-session");
1426
+ function createSolanaWallet() {
1427
+ const { Keypair } = (init_index_esm(), __toCommonJS(index_esm_exports));
1428
+ const bs58 = __require("bs58");
1429
+ const keypair = Keypair.generate();
1430
+ return {
1431
+ address: keypair.publicKey.toBase58(),
1432
+ privateKey: bs58.default?.encode(keypair.secretKey) ?? bs58.encode(keypair.secretKey)
1433
+ };
1434
+ }
1435
+ async function solanaKeyToBytes(privateKey) {
1436
+ try {
1437
+ const bs58 = await import("bs58");
1438
+ const bytes = (bs58.default ?? bs58).decode(privateKey);
1439
+ if (bytes.length !== 64) {
1440
+ throw new Error(`Invalid Solana key length: expected 64 bytes, got ${bytes.length}`);
1441
+ }
1442
+ return bytes;
1443
+ } catch (err) {
1444
+ const msg = err instanceof Error ? err.message : String(err);
1445
+ throw new Error(`Invalid Solana private key: ${msg}`);
1446
+ }
1447
+ }
1448
+ async function solanaPublicKey(privateKey) {
1449
+ const { Keypair } = await import("./index.esm-SXKIFLA7.js");
1450
+ const bytes = await solanaKeyToBytes(privateKey);
1451
+ return Keypair.fromSecretKey(bytes).publicKey.toBase58();
1452
+ }
1453
+ function saveSolanaWallet(privateKey) {
1454
+ if (!fs2.existsSync(WALLET_DIR2)) fs2.mkdirSync(WALLET_DIR2, { recursive: true });
1455
+ fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
1456
+ return SOLANA_WALLET_FILE;
1457
+ }
1458
+ function loadSolanaWallet() {
1459
+ if (fs2.existsSync(SOLANA_WALLET_FILE)) {
1460
+ const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
1461
+ if (key) return key;
1462
+ }
1463
+ return null;
1464
+ }
1465
+ async function getOrCreateSolanaWallet() {
1466
+ const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
1467
+ if (envKey) {
1468
+ const address2 = await solanaPublicKey(envKey);
1469
+ return { privateKey: envKey, address: address2, isNew: false };
1470
+ }
1471
+ const fileKey = loadSolanaWallet();
1472
+ if (fileKey) {
1473
+ const address2 = await solanaPublicKey(fileKey);
1474
+ return { privateKey: fileKey, address: address2, isNew: false };
1475
+ }
1476
+ const { address, privateKey } = createSolanaWallet();
1477
+ saveSolanaWallet(privateKey);
1478
+ return { address, privateKey, isNew: true };
1479
+ }
1480
+
1481
+ // src/solana-client.ts
1482
+ var SOLANA_API_URL = "https://sol.blockrun.ai/api";
1483
+ var DEFAULT_MAX_TOKENS2 = 1024;
1484
+ var DEFAULT_TIMEOUT3 = 6e4;
1485
+ var SDK_VERSION2 = "0.3.0";
1486
+ var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
1487
+ var SolanaLLMClient = class {
1488
+ static SOLANA_API_URL = SOLANA_API_URL;
1489
+ privateKey;
1490
+ apiUrl;
1491
+ rpcUrl;
1492
+ timeout;
1493
+ sessionTotalUsd = 0;
1494
+ sessionCalls = 0;
1495
+ addressCache = null;
1496
+ constructor(options = {}) {
1497
+ const envKey = typeof process !== "undefined" && process.env ? process.env.SOLANA_WALLET_KEY : void 0;
1498
+ const privateKey = options.privateKey || envKey;
1499
+ if (!privateKey) {
1500
+ throw new Error(
1501
+ "Private key required. Pass privateKey in options or set SOLANA_WALLET_KEY environment variable."
1502
+ );
1503
+ }
1504
+ this.privateKey = privateKey;
1505
+ const apiUrl = options.apiUrl || SOLANA_API_URL;
1506
+ validateApiUrl(apiUrl);
1507
+ this.apiUrl = apiUrl.replace(/\/$/, "");
1508
+ this.rpcUrl = options.rpcUrl || "https://api.mainnet-beta.solana.com";
1509
+ this.timeout = options.timeout || DEFAULT_TIMEOUT3;
1510
+ }
1511
+ /** Get Solana wallet address (public key in base58). */
1512
+ async getWalletAddress() {
1513
+ if (!this.addressCache) {
1514
+ this.addressCache = await solanaPublicKey(this.privateKey);
1515
+ }
1516
+ return this.addressCache;
1517
+ }
1518
+ /** Simple 1-line chat. */
1519
+ async chat(model, prompt, options) {
1520
+ const messages = [];
1521
+ if (options?.system) messages.push({ role: "system", content: options.system });
1522
+ messages.push({ role: "user", content: prompt });
1523
+ const result = await this.chatCompletion(model, messages, {
1524
+ maxTokens: options?.maxTokens,
1525
+ temperature: options?.temperature,
1526
+ topP: options?.topP,
1527
+ search: options?.search,
1528
+ searchParameters: options?.searchParameters
1529
+ });
1530
+ return result.choices[0].message.content || "";
1531
+ }
1532
+ /** Full chat completion (OpenAI-compatible). */
1533
+ async chatCompletion(model, messages, options) {
1534
+ const body = {
1535
+ model,
1536
+ messages,
1537
+ max_tokens: options?.maxTokens || DEFAULT_MAX_TOKENS2
1538
+ };
1539
+ if (options?.temperature !== void 0) body.temperature = options.temperature;
1540
+ if (options?.topP !== void 0) body.top_p = options.topP;
1541
+ if (options?.searchParameters !== void 0) body.search_parameters = options.searchParameters;
1542
+ else if (options?.search === true) body.search_parameters = { mode: "on" };
1543
+ if (options?.tools !== void 0) body.tools = options.tools;
1544
+ if (options?.toolChoice !== void 0) body.tool_choice = options.toolChoice;
1545
+ return this.requestWithPayment("/v1/chat/completions", body);
1546
+ }
1547
+ /** List available models. */
1548
+ async listModels() {
1549
+ const response = await this.fetchWithTimeout(`${this.apiUrl}/v1/models`, { method: "GET" });
1550
+ if (!response.ok) {
1551
+ throw new APIError(`Failed to list models: ${response.status}`, response.status);
1552
+ }
1553
+ const data = await response.json();
1554
+ return data.data || [];
1555
+ }
1556
+ /**
1557
+ * Get Solana USDC balance.
1558
+ *
1559
+ * @returns USDC balance as a float
1560
+ */
1561
+ async getBalance() {
1562
+ const usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
1563
+ const address = await this.getWalletAddress();
1564
+ try {
1565
+ const response = await fetch(this.rpcUrl, {
1566
+ method: "POST",
1567
+ headers: { "Content-Type": "application/json" },
1568
+ body: JSON.stringify({
1569
+ jsonrpc: "2.0",
1570
+ id: 1,
1571
+ method: "getTokenAccountsByOwner",
1572
+ params: [address, { mint: usdc_mint }, { encoding: "jsonParsed" }]
1573
+ })
1574
+ });
1575
+ const data = await response.json();
1576
+ const accounts = data.result?.value || [];
1577
+ if (!accounts.length) return 0;
1578
+ let total = 0;
1579
+ for (const acct of accounts) {
1580
+ total += acct.account?.data?.parsed?.info?.tokenAmount?.uiAmount || 0;
1581
+ }
1582
+ return total;
1583
+ } catch {
1584
+ return 0;
1585
+ }
1586
+ }
1587
+ /** Edit an image using img2img (Solana payment). */
1588
+ async imageEdit(prompt, image, options) {
1589
+ const body = {
1590
+ model: options?.model || "openai/gpt-image-1",
1591
+ prompt,
1592
+ image,
1593
+ size: options?.size || "1024x1024",
1594
+ n: options?.n || 1
1595
+ };
1596
+ if (options?.mask !== void 0) body.mask = options.mask;
1597
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
1598
+ return data;
1599
+ }
1600
+ /** Standalone search (Solana payment). */
1601
+ async search(query, options) {
1602
+ const body = { query, max_results: options?.maxResults || 10 };
1603
+ if (options?.sources !== void 0) body.sources = options.sources;
1604
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
1605
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
1606
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
1607
+ return data;
1608
+ }
1609
+ // ============================================================
1610
+ // X/Twitter endpoints (powered by AttentionVC)
1611
+ // ============================================================
1612
+ async xUserLookup(usernames) {
1613
+ const names = Array.isArray(usernames) ? usernames : [usernames];
1614
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
1615
+ return data;
1616
+ }
1617
+ async xFollowers(username, cursor) {
1618
+ const body = { username };
1619
+ if (cursor !== void 0) body.cursor = cursor;
1620
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
1621
+ return data;
1622
+ }
1623
+ async xFollowings(username, cursor) {
1624
+ const body = { username };
1625
+ if (cursor !== void 0) body.cursor = cursor;
1626
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
1627
+ return data;
1628
+ }
1629
+ async xUserInfo(username) {
1630
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
1631
+ return data;
1632
+ }
1633
+ async xVerifiedFollowers(userId, cursor) {
1634
+ const body = { userId };
1635
+ if (cursor !== void 0) body.cursor = cursor;
1636
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
1637
+ return data;
1638
+ }
1639
+ async xUserTweets(username, includeReplies = false, cursor) {
1640
+ const body = { username, includeReplies };
1641
+ if (cursor !== void 0) body.cursor = cursor;
1642
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
1643
+ return data;
1644
+ }
1645
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
1646
+ const body = { username };
1647
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
1648
+ if (untilTime !== void 0) body.untilTime = untilTime;
1649
+ if (cursor !== void 0) body.cursor = cursor;
1650
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
1651
+ return data;
1652
+ }
1653
+ async xTweetLookup(tweetIds) {
1654
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
1655
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
1656
+ return data;
1657
+ }
1658
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
1659
+ const body = { tweetId, queryType };
1660
+ if (cursor !== void 0) body.cursor = cursor;
1661
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
1662
+ return data;
1663
+ }
1664
+ async xTweetThread(tweetId, cursor) {
1665
+ const body = { tweetId };
1666
+ if (cursor !== void 0) body.cursor = cursor;
1667
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
1668
+ return data;
1669
+ }
1670
+ async xSearch(query, queryType = "Latest", cursor) {
1671
+ const body = { query, queryType };
1672
+ if (cursor !== void 0) body.cursor = cursor;
1673
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
1674
+ return data;
1675
+ }
1676
+ async xTrending() {
1677
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1678
+ return data;
1679
+ }
1680
+ async xArticlesRising() {
1681
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1682
+ return data;
1683
+ }
1684
+ async xAuthorAnalytics(handle) {
1685
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1686
+ return data;
1687
+ }
1688
+ async xCompareAuthors(handle1, handle2) {
1689
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1690
+ return data;
1691
+ }
1692
+ /** Get session spending. */
1693
+ getSpending() {
1694
+ return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
1695
+ }
1696
+ /** True if using sol.blockrun.ai. */
1697
+ isSolana() {
1698
+ return this.apiUrl.includes("sol.blockrun.ai");
1699
+ }
1700
+ async requestWithPayment(endpoint, body) {
1701
+ const url = `${this.apiUrl}${endpoint}`;
1702
+ const response = await this.fetchWithTimeout(url, {
1703
+ method: "POST",
1704
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
1705
+ body: JSON.stringify(body)
1706
+ });
1707
+ if (response.status === 402) {
1708
+ return this.handlePaymentAndRetry(url, body, response);
1709
+ }
1710
+ if (!response.ok) {
1711
+ let errorBody;
1712
+ try {
1713
+ errorBody = await response.json();
1714
+ } catch {
1715
+ errorBody = { error: "Request failed" };
1716
+ }
1717
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
1718
+ }
1719
+ return response.json();
1720
+ }
1721
+ async handlePaymentAndRetry(url, body, response) {
1722
+ let paymentHeader = response.headers.get("payment-required");
1723
+ if (!paymentHeader) {
1724
+ try {
1725
+ const respBody = await response.json();
1726
+ if (respBody.accepts || respBody.x402Version) {
1727
+ paymentHeader = btoa(JSON.stringify(respBody));
1728
+ }
1729
+ } catch {
1730
+ }
1731
+ }
1732
+ if (!paymentHeader) {
1733
+ throw new PaymentError("402 response but no payment requirements found");
1734
+ }
1735
+ const paymentRequired = parsePaymentRequired(paymentHeader);
1736
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
1737
+ if (!details.network?.startsWith("solana:")) {
1738
+ throw new PaymentError(
1739
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
1740
+ );
1741
+ }
1742
+ const feePayer = details.extra?.feePayer;
1743
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
1744
+ const fromAddress = await this.getWalletAddress();
1745
+ const secretKey = await solanaKeyToBytes(this.privateKey);
1746
+ const extensions = paymentRequired.extensions;
1747
+ const paymentPayload = await createSolanaPaymentPayload(
1748
+ secretKey,
1749
+ fromAddress,
1750
+ details.recipient,
1751
+ details.amount,
1752
+ feePayer,
1753
+ {
1754
+ resourceUrl: validateResourceUrl(
1755
+ details.resource?.url || `${this.apiUrl}/v1/chat/completions`,
1756
+ this.apiUrl
1757
+ ),
1758
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
1759
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
1760
+ extra: details.extra,
1761
+ extensions,
1762
+ rpcUrl: this.rpcUrl
1763
+ }
1764
+ );
1765
+ const retryResponse = await this.fetchWithTimeout(url, {
1766
+ method: "POST",
1767
+ headers: {
1768
+ "Content-Type": "application/json",
1769
+ "User-Agent": USER_AGENT2,
1770
+ "PAYMENT-SIGNATURE": paymentPayload
1771
+ },
1772
+ body: JSON.stringify(body)
1773
+ });
1774
+ if (retryResponse.status === 402) {
1775
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
1776
+ }
1777
+ if (!retryResponse.ok) {
1778
+ let errorBody;
1779
+ try {
1780
+ errorBody = await retryResponse.json();
1781
+ } catch {
1782
+ errorBody = { error: "Request failed" };
1783
+ }
1784
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
1785
+ }
1786
+ const costUsd = parseFloat(details.amount) / 1e6;
1787
+ this.sessionCalls += 1;
1788
+ this.sessionTotalUsd += costUsd;
1789
+ return retryResponse.json();
1790
+ }
1791
+ async requestWithPaymentRaw(endpoint, body) {
1792
+ const url = `${this.apiUrl}${endpoint}`;
1793
+ const response = await this.fetchWithTimeout(url, {
1794
+ method: "POST",
1795
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
1796
+ body: JSON.stringify(body)
1797
+ });
1798
+ if (response.status === 402) {
1799
+ return this.handlePaymentAndRetryRaw(url, body, response);
1800
+ }
1801
+ if (!response.ok) {
1802
+ let errorBody;
1803
+ try {
1804
+ errorBody = await response.json();
1805
+ } catch {
1806
+ errorBody = { error: "Request failed" };
1807
+ }
1808
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
1809
+ }
1810
+ return response.json();
1811
+ }
1812
+ async handlePaymentAndRetryRaw(url, body, response) {
1813
+ let paymentHeader = response.headers.get("payment-required");
1814
+ if (!paymentHeader) {
1815
+ try {
1816
+ const respBody = await response.json();
1817
+ if (respBody.accepts || respBody.x402Version) {
1818
+ paymentHeader = btoa(JSON.stringify(respBody));
1819
+ }
1820
+ } catch {
1821
+ }
1822
+ }
1823
+ if (!paymentHeader) {
1824
+ throw new PaymentError("402 response but no payment requirements found");
1825
+ }
1826
+ const paymentRequired = parsePaymentRequired(paymentHeader);
1827
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
1828
+ if (!details.network?.startsWith("solana:")) {
1829
+ throw new PaymentError(
1830
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
1831
+ );
1832
+ }
1833
+ const feePayer = details.extra?.feePayer;
1834
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
1835
+ const fromAddress = await this.getWalletAddress();
1836
+ const secretKey = await solanaKeyToBytes(this.privateKey);
1837
+ const extensions = paymentRequired.extensions;
1838
+ const paymentPayload = await createSolanaPaymentPayload(
1839
+ secretKey,
1840
+ fromAddress,
1841
+ details.recipient,
1842
+ details.amount,
1843
+ feePayer,
1844
+ {
1845
+ resourceUrl: validateResourceUrl(
1846
+ details.resource?.url || url,
1847
+ this.apiUrl
1848
+ ),
1849
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
1850
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
1851
+ extra: details.extra,
1852
+ extensions,
1853
+ rpcUrl: this.rpcUrl
1854
+ }
1855
+ );
1856
+ const retryResponse = await this.fetchWithTimeout(url, {
1857
+ method: "POST",
1858
+ headers: {
1859
+ "Content-Type": "application/json",
1860
+ "User-Agent": USER_AGENT2,
1861
+ "PAYMENT-SIGNATURE": paymentPayload
1862
+ },
1863
+ body: JSON.stringify(body)
1864
+ });
1865
+ if (retryResponse.status === 402) {
1866
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
1867
+ }
1868
+ if (!retryResponse.ok) {
1869
+ let errorBody;
1870
+ try {
1871
+ errorBody = await retryResponse.json();
1872
+ } catch {
1873
+ errorBody = { error: "Request failed" };
1874
+ }
1875
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
1876
+ }
1877
+ const costUsd = parseFloat(details.amount) / 1e6;
1878
+ this.sessionCalls += 1;
1879
+ this.sessionTotalUsd += costUsd;
1880
+ return retryResponse.json();
1881
+ }
1882
+ async fetchWithTimeout(url, options) {
1883
+ const controller = new AbortController();
1884
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
1885
+ try {
1886
+ return await fetch(url, { ...options, signal: controller.signal });
1887
+ } finally {
1888
+ clearTimeout(timeoutId);
1889
+ }
1890
+ }
1891
+ };
1892
+ function solanaClient(options = {}) {
1893
+ return new SolanaLLMClient({ ...options, apiUrl: SOLANA_API_URL });
1894
+ }
1895
+
950
1896
  // src/openai-compat.ts
951
1897
  var StreamingResponse = class {
952
1898
  reader;
@@ -1124,20 +2070,32 @@ export {
1124
2070
  LLMClient,
1125
2071
  OpenAI,
1126
2072
  PaymentError,
2073
+ SOLANA_NETWORK,
2074
+ SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH,
2075
+ SolanaLLMClient,
1127
2076
  USDC_BASE,
1128
2077
  USDC_BASE_CONTRACT,
2078
+ USDC_SOLANA,
1129
2079
  WALLET_DIR_PATH,
1130
2080
  WALLET_FILE_PATH,
2081
+ createSolanaPaymentPayload,
2082
+ createSolanaWallet,
1131
2083
  createWallet,
1132
2084
  client_default as default,
1133
2085
  formatFundingMessageCompact,
1134
2086
  formatNeedsFundingMessage,
1135
2087
  formatWalletCreatedMessage,
1136
2088
  getEip681Uri,
2089
+ getOrCreateSolanaWallet,
1137
2090
  getOrCreateWallet,
1138
2091
  getPaymentLinks,
1139
2092
  getWalletAddress,
2093
+ loadSolanaWallet,
1140
2094
  loadWallet,
2095
+ saveSolanaWallet,
1141
2096
  saveWallet,
2097
+ solanaClient,
2098
+ solanaKeyToBytes,
2099
+ solanaPublicKey,
1142
2100
  testnetClient
1143
2101
  };