@blockrun/llm 1.1.0 → 1.3.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
@@ -573,6 +573,200 @@ var LLMClient = class {
573
573
  this.sessionTotalUsd += costUsd;
574
574
  return retryResponse.json();
575
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
+ }
673
+ /**
674
+ * GET with automatic x402 payment handling, returning raw JSON.
675
+ * Used for Predexon prediction market endpoints that use GET + query params.
676
+ */
677
+ async getWithPaymentRaw(endpoint, params) {
678
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
679
+ const url = `${this.apiUrl}${endpoint}${query}`;
680
+ const response = await this.fetchWithTimeout(url, {
681
+ method: "GET",
682
+ headers: { "User-Agent": USER_AGENT }
683
+ });
684
+ if (response.status === 402) {
685
+ return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
686
+ }
687
+ if (!response.ok) {
688
+ let errorBody;
689
+ try {
690
+ errorBody = await response.json();
691
+ } catch {
692
+ errorBody = { error: "Request failed" };
693
+ }
694
+ throw new APIError(
695
+ `API error: ${response.status}`,
696
+ response.status,
697
+ sanitizeErrorResponse(errorBody)
698
+ );
699
+ }
700
+ return response.json();
701
+ }
702
+ /**
703
+ * Handle 402 response for GET endpoints: parse requirements, sign payment, retry with GET.
704
+ */
705
+ async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
706
+ let paymentHeader = response.headers.get("payment-required");
707
+ if (!paymentHeader) {
708
+ try {
709
+ const respBody = await response.json();
710
+ if (respBody.x402 || respBody.accepts) {
711
+ paymentHeader = btoa(JSON.stringify(respBody));
712
+ }
713
+ } catch {
714
+ console.debug("Failed to parse payment header from response body");
715
+ }
716
+ }
717
+ if (!paymentHeader) {
718
+ throw new PaymentError("402 response but no payment requirements found");
719
+ }
720
+ const paymentRequired = parsePaymentRequired(paymentHeader);
721
+ const details = extractPaymentDetails(paymentRequired);
722
+ const extensions = paymentRequired.extensions;
723
+ const paymentPayload = await createPaymentPayload(
724
+ this.privateKey,
725
+ this.account.address,
726
+ details.recipient,
727
+ details.amount,
728
+ details.network || "eip155:8453",
729
+ {
730
+ resourceUrl: validateResourceUrl(
731
+ details.resource?.url || url,
732
+ this.apiUrl
733
+ ),
734
+ resourceDescription: details.resource?.description || "BlockRun AI API call",
735
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
736
+ extra: details.extra,
737
+ extensions
738
+ }
739
+ );
740
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
741
+ const retryUrl = `${this.apiUrl}${endpoint}${query}`;
742
+ const retryResponse = await this.fetchWithTimeout(retryUrl, {
743
+ method: "GET",
744
+ headers: {
745
+ "User-Agent": USER_AGENT,
746
+ "PAYMENT-SIGNATURE": paymentPayload
747
+ }
748
+ });
749
+ if (retryResponse.status === 402) {
750
+ throw new PaymentError("Payment was rejected. Check your wallet balance.");
751
+ }
752
+ if (!retryResponse.ok) {
753
+ let errorBody;
754
+ try {
755
+ errorBody = await retryResponse.json();
756
+ } catch {
757
+ errorBody = { error: "Request failed" };
758
+ }
759
+ throw new APIError(
760
+ `API error after payment: ${retryResponse.status}`,
761
+ retryResponse.status,
762
+ sanitizeErrorResponse(errorBody)
763
+ );
764
+ }
765
+ const costUsd = parseFloat(details.amount) / 1e6;
766
+ this.sessionCalls += 1;
767
+ this.sessionTotalUsd += costUsd;
768
+ return retryResponse.json();
769
+ }
576
770
  /**
577
771
  * Fetch with timeout.
578
772
  */
@@ -655,6 +849,317 @@ var LLMClient = class {
655
849
  }
656
850
  return [...llmModels, ...imageModels];
657
851
  }
852
+ /**
853
+ * Edit an image using img2img.
854
+ *
855
+ * @param prompt - Text description of the desired edit
856
+ * @param image - Base64-encoded image or URL of the source image
857
+ * @param options - Optional edit parameters
858
+ * @returns ImageResponse with edited image URLs
859
+ */
860
+ async imageEdit(prompt, image, options) {
861
+ const body = {
862
+ model: options?.model || "openai/gpt-image-1",
863
+ prompt,
864
+ image,
865
+ size: options?.size || "1024x1024",
866
+ n: options?.n || 1
867
+ };
868
+ if (options?.mask !== void 0) {
869
+ body.mask = options.mask;
870
+ }
871
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
872
+ return data;
873
+ }
874
+ /**
875
+ * Standalone search (web, X/Twitter, news).
876
+ *
877
+ * @param query - Search query
878
+ * @param options - Optional search parameters
879
+ * @returns SearchResult with summary and citations
880
+ */
881
+ async search(query, options) {
882
+ const body = {
883
+ query,
884
+ max_results: options?.maxResults || 10
885
+ };
886
+ if (options?.sources !== void 0) body.sources = options.sources;
887
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
888
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
889
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
890
+ return data;
891
+ }
892
+ /**
893
+ * Get USDC balance on Base network.
894
+ *
895
+ * Automatically detects mainnet vs testnet based on API URL.
896
+ *
897
+ * @returns USDC balance as a float (6 decimal places normalized)
898
+ *
899
+ * @example
900
+ * const balance = await client.getBalance();
901
+ * console.log(`Balance: $${balance.toFixed(2)} USDC`);
902
+ */
903
+ async getBalance() {
904
+ const isTestnet = this.isTestnet();
905
+ const usdcContract = isTestnet ? "0x036CbD53842c5426634e7929541eC2318f3dCF7e" : "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
906
+ 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"];
907
+ const selector = "0x70a08231";
908
+ const paddedAddress = this.account.address.slice(2).toLowerCase().padStart(64, "0");
909
+ const data = selector + paddedAddress;
910
+ const payload = {
911
+ jsonrpc: "2.0",
912
+ method: "eth_call",
913
+ params: [{ to: usdcContract, data }, "latest"],
914
+ id: 1
915
+ };
916
+ let lastError;
917
+ for (const rpc of rpcs) {
918
+ try {
919
+ const response = await fetch(rpc, {
920
+ method: "POST",
921
+ headers: { "Content-Type": "application/json" },
922
+ body: JSON.stringify(payload)
923
+ });
924
+ const result = await response.json();
925
+ const balanceRaw = parseInt(result.result || "0x0", 16);
926
+ return balanceRaw / 1e6;
927
+ } catch (e) {
928
+ lastError = e;
929
+ }
930
+ }
931
+ throw lastError || new Error("All RPCs failed");
932
+ }
933
+ // ============================================================
934
+ // X/Twitter endpoints (powered by AttentionVC)
935
+ // ============================================================
936
+ /**
937
+ * Look up X/Twitter user profiles by username.
938
+ *
939
+ * Powered by AttentionVC. $0.002 per user (min $0.02, max $0.20).
940
+ *
941
+ * @param usernames - Single username or array of usernames (without @)
942
+ */
943
+ async xUserLookup(usernames) {
944
+ const names = Array.isArray(usernames) ? usernames : [usernames];
945
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
946
+ return data;
947
+ }
948
+ /**
949
+ * Get followers of an X/Twitter user.
950
+ *
951
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
952
+ *
953
+ * @param username - X/Twitter username (without @)
954
+ * @param cursor - Pagination cursor from previous response
955
+ */
956
+ async xFollowers(username, cursor) {
957
+ const body = { username };
958
+ if (cursor !== void 0) body.cursor = cursor;
959
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
960
+ return data;
961
+ }
962
+ /**
963
+ * Get accounts an X/Twitter user is following.
964
+ *
965
+ * Powered by AttentionVC. $0.05 per page (~200 accounts).
966
+ *
967
+ * @param username - X/Twitter username (without @)
968
+ * @param cursor - Pagination cursor from previous response
969
+ */
970
+ async xFollowings(username, cursor) {
971
+ const body = { username };
972
+ if (cursor !== void 0) body.cursor = cursor;
973
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
974
+ return data;
975
+ }
976
+ /**
977
+ * Get detailed profile info for a single X/Twitter user.
978
+ *
979
+ * Powered by AttentionVC. $0.002 per request.
980
+ *
981
+ * @param username - X/Twitter username (without @)
982
+ */
983
+ async xUserInfo(username) {
984
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
985
+ return data;
986
+ }
987
+ /**
988
+ * Get verified (blue-check) followers of an X/Twitter user.
989
+ *
990
+ * Powered by AttentionVC. $0.048 per page.
991
+ *
992
+ * @param userId - X/Twitter user ID (not username)
993
+ * @param cursor - Pagination cursor from previous response
994
+ */
995
+ async xVerifiedFollowers(userId, cursor) {
996
+ const body = { userId };
997
+ if (cursor !== void 0) body.cursor = cursor;
998
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
999
+ return data;
1000
+ }
1001
+ /**
1002
+ * Get tweets posted by an X/Twitter user.
1003
+ *
1004
+ * Powered by AttentionVC. $0.032 per page.
1005
+ *
1006
+ * @param username - X/Twitter username (without @)
1007
+ * @param includeReplies - Include reply tweets (default: false)
1008
+ * @param cursor - Pagination cursor from previous response
1009
+ */
1010
+ async xUserTweets(username, includeReplies = false, cursor) {
1011
+ const body = { username, includeReplies };
1012
+ if (cursor !== void 0) body.cursor = cursor;
1013
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
1014
+ return data;
1015
+ }
1016
+ /**
1017
+ * Get tweets that mention an X/Twitter user.
1018
+ *
1019
+ * Powered by AttentionVC. $0.032 per page.
1020
+ *
1021
+ * @param username - X/Twitter username (without @)
1022
+ * @param sinceTime - Start time filter (ISO8601 or Unix timestamp)
1023
+ * @param untilTime - End time filter (ISO8601 or Unix timestamp)
1024
+ * @param cursor - Pagination cursor from previous response
1025
+ */
1026
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
1027
+ const body = { username };
1028
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
1029
+ if (untilTime !== void 0) body.untilTime = untilTime;
1030
+ if (cursor !== void 0) body.cursor = cursor;
1031
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
1032
+ return data;
1033
+ }
1034
+ /**
1035
+ * Fetch full tweet data for up to 200 tweet IDs.
1036
+ *
1037
+ * Powered by AttentionVC. $0.16 per batch.
1038
+ *
1039
+ * @param tweetIds - Single tweet ID or array of tweet IDs (max 200)
1040
+ */
1041
+ async xTweetLookup(tweetIds) {
1042
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
1043
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
1044
+ return data;
1045
+ }
1046
+ /**
1047
+ * Get replies to a specific tweet.
1048
+ *
1049
+ * Powered by AttentionVC. $0.032 per page.
1050
+ *
1051
+ * @param tweetId - The tweet ID to get replies for
1052
+ * @param queryType - Sort order: 'Latest' or 'Default'
1053
+ * @param cursor - Pagination cursor from previous response
1054
+ */
1055
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
1056
+ const body = { tweetId, queryType };
1057
+ if (cursor !== void 0) body.cursor = cursor;
1058
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
1059
+ return data;
1060
+ }
1061
+ /**
1062
+ * Get the full thread context for a tweet.
1063
+ *
1064
+ * Powered by AttentionVC. $0.032 per page.
1065
+ *
1066
+ * @param tweetId - The tweet ID to get thread for
1067
+ * @param cursor - Pagination cursor from previous response
1068
+ */
1069
+ async xTweetThread(tweetId, cursor) {
1070
+ const body = { tweetId };
1071
+ if (cursor !== void 0) body.cursor = cursor;
1072
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
1073
+ return data;
1074
+ }
1075
+ /**
1076
+ * Search X/Twitter with advanced query operators.
1077
+ *
1078
+ * Powered by AttentionVC. $0.032 per page.
1079
+ *
1080
+ * @param query - Search query (supports Twitter search operators)
1081
+ * @param queryType - Sort order: 'Latest', 'Top', or 'Default'
1082
+ * @param cursor - Pagination cursor from previous response
1083
+ */
1084
+ async xSearch(query, queryType = "Latest", cursor) {
1085
+ const body = { query, queryType };
1086
+ if (cursor !== void 0) body.cursor = cursor;
1087
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
1088
+ return data;
1089
+ }
1090
+ /**
1091
+ * Get current trending topics on X/Twitter.
1092
+ *
1093
+ * Powered by AttentionVC. $0.002 per request.
1094
+ */
1095
+ async xTrending() {
1096
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1097
+ return data;
1098
+ }
1099
+ /**
1100
+ * Get rising/viral articles from X/Twitter.
1101
+ *
1102
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1103
+ */
1104
+ async xArticlesRising() {
1105
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1106
+ return data;
1107
+ }
1108
+ /**
1109
+ * Get author analytics and intelligence metrics for an X/Twitter user.
1110
+ *
1111
+ * Powered by AttentionVC intelligence layer. $0.02 per request.
1112
+ *
1113
+ * @param handle - X/Twitter handle (without @)
1114
+ */
1115
+ async xAuthorAnalytics(handle) {
1116
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1117
+ return data;
1118
+ }
1119
+ /**
1120
+ * Compare two X/Twitter authors side-by-side with intelligence metrics.
1121
+ *
1122
+ * Powered by AttentionVC intelligence layer. $0.05 per request.
1123
+ *
1124
+ * @param handle1 - First X/Twitter handle (without @)
1125
+ * @param handle2 - Second X/Twitter handle (without @)
1126
+ */
1127
+ async xCompareAuthors(handle1, handle2) {
1128
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1129
+ return data;
1130
+ }
1131
+ // ── Prediction Markets (Powered by Predexon) ────────────────────────────
1132
+ /**
1133
+ * Query Predexon prediction market data (GET endpoints).
1134
+ *
1135
+ * Access real-time data from Polymarket, Kalshi, dFlow, and Binance Futures.
1136
+ * Powered by Predexon. $0.001 per request.
1137
+ *
1138
+ * @param path - Endpoint path, e.g. "polymarket/events", "kalshi/markets/12345"
1139
+ * @param params - Query parameters passed to the endpoint
1140
+ *
1141
+ * @example
1142
+ * const events = await client.pm("polymarket/events");
1143
+ * const market = await client.pm("kalshi/markets/KXBTC-25MAR14");
1144
+ * const results = await client.pm("polymarket/search", { q: "bitcoin" });
1145
+ */
1146
+ async pm(path5, params) {
1147
+ return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
1148
+ }
1149
+ /**
1150
+ * Structured query for Predexon prediction market data (POST endpoints).
1151
+ *
1152
+ * For complex queries that require a JSON body. $0.005 per request.
1153
+ *
1154
+ * @param path - Endpoint path, e.g. "polymarket/query", "kalshi/query"
1155
+ * @param query - JSON body for the structured query
1156
+ *
1157
+ * @example
1158
+ * const data = await client.pmQuery("polymarket/query", { filter: "active", limit: 10 });
1159
+ */
1160
+ async pmQuery(path5, query) {
1161
+ return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1162
+ }
658
1163
  /**
659
1164
  * Get current session spending.
660
1165
  *
@@ -748,6 +1253,31 @@ var ImageClient = class {
748
1253
  }
749
1254
  return this.requestWithPayment("/v1/images/generations", body);
750
1255
  }
1256
+ /**
1257
+ * Edit an image using img2img.
1258
+ *
1259
+ * @param prompt - Text description of the desired edit
1260
+ * @param image - Base64-encoded image or URL of the source image
1261
+ * @param options - Optional edit parameters
1262
+ * @returns ImageResponse with edited image URLs
1263
+ *
1264
+ * @example
1265
+ * const result = await client.edit('Make it a painting', imageBase64);
1266
+ * console.log(result.data[0].url);
1267
+ */
1268
+ async edit(prompt, image, options) {
1269
+ const body = {
1270
+ model: options?.model || "openai/gpt-image-1",
1271
+ prompt,
1272
+ image,
1273
+ size: options?.size || "1024x1024",
1274
+ n: options?.n || 1
1275
+ };
1276
+ if (options?.mask !== void 0) {
1277
+ body.mask = options.mask;
1278
+ }
1279
+ return this.requestWithPayment("/v1/images/image2image", body);
1280
+ }
751
1281
  /**
752
1282
  * List available image generation models with pricing.
753
1283
  */
@@ -915,7 +1445,35 @@ function saveWallet(privateKey) {
915
1445
  fs.writeFileSync(WALLET_FILE, privateKey, { mode: 384 });
916
1446
  return WALLET_FILE;
917
1447
  }
1448
+ function scanWallets() {
1449
+ const home = os.homedir();
1450
+ const results = [];
1451
+ try {
1452
+ const entries = fs.readdirSync(home, { withFileTypes: true });
1453
+ for (const entry of entries) {
1454
+ if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
1455
+ const walletFile = path.join(home, entry.name, "wallet.json");
1456
+ if (!fs.existsSync(walletFile)) continue;
1457
+ try {
1458
+ const data = JSON.parse(fs.readFileSync(walletFile, "utf-8"));
1459
+ const pk = data.privateKey || "";
1460
+ const addr = data.address || "";
1461
+ if (pk && addr) {
1462
+ const mtime = fs.statSync(walletFile).mtimeMs;
1463
+ results.push({ mtime, privateKey: pk, address: addr });
1464
+ }
1465
+ } catch {
1466
+ continue;
1467
+ }
1468
+ }
1469
+ } catch {
1470
+ }
1471
+ results.sort((a, b) => b.mtime - a.mtime);
1472
+ return results.map(({ privateKey, address }) => ({ privateKey, address }));
1473
+ }
918
1474
  function loadWallet() {
1475
+ const wallets = scanWallets();
1476
+ if (wallets.length > 0) return wallets[0].privateKey;
919
1477
  if (fs.existsSync(WALLET_FILE)) {
920
1478
  const key = fs.readFileSync(WALLET_FILE, "utf-8").trim();
921
1479
  if (key) return key;
@@ -1054,7 +1612,50 @@ function saveSolanaWallet(privateKey) {
1054
1612
  fs2.writeFileSync(SOLANA_WALLET_FILE, privateKey, { mode: 384 });
1055
1613
  return SOLANA_WALLET_FILE;
1056
1614
  }
1615
+ function scanSolanaWallets() {
1616
+ const home = os2.homedir();
1617
+ const results = [];
1618
+ try {
1619
+ const entries = fs2.readdirSync(home, { withFileTypes: true });
1620
+ for (const entry of entries) {
1621
+ if (!entry.name.startsWith(".") || !entry.isDirectory()) continue;
1622
+ const solanaWalletFile = path2.join(home, entry.name, "solana-wallet.json");
1623
+ if (fs2.existsSync(solanaWalletFile)) {
1624
+ try {
1625
+ const data = JSON.parse(fs2.readFileSync(solanaWalletFile, "utf-8"));
1626
+ const pk = data.privateKey || "";
1627
+ const addr = data.address || "";
1628
+ if (pk && addr) {
1629
+ const mtime = fs2.statSync(solanaWalletFile).mtimeMs;
1630
+ results.push({ mtime, secretKey: pk, publicKey: addr });
1631
+ }
1632
+ } catch {
1633
+ }
1634
+ }
1635
+ if (entry.name === ".brcc") {
1636
+ const brccWalletFile = path2.join(home, entry.name, "wallet.json");
1637
+ if (fs2.existsSync(brccWalletFile)) {
1638
+ try {
1639
+ const data = JSON.parse(fs2.readFileSync(brccWalletFile, "utf-8"));
1640
+ const pk = data.privateKey || "";
1641
+ const addr = data.address || "";
1642
+ if (pk && addr) {
1643
+ const mtime = fs2.statSync(brccWalletFile).mtimeMs;
1644
+ results.push({ mtime, secretKey: pk, publicKey: addr });
1645
+ }
1646
+ } catch {
1647
+ }
1648
+ }
1649
+ }
1650
+ }
1651
+ } catch {
1652
+ }
1653
+ results.sort((a, b) => b.mtime - a.mtime);
1654
+ return results.map(({ secretKey, publicKey }) => ({ secretKey, publicKey }));
1655
+ }
1057
1656
  function loadSolanaWallet() {
1657
+ const wallets = scanSolanaWallets();
1658
+ if (wallets.length > 0) return wallets[0].secretKey;
1058
1659
  if (fs2.existsSync(SOLANA_WALLET_FILE)) {
1059
1660
  const key = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
1060
1661
  if (key) return key;
@@ -1067,10 +1668,16 @@ async function getOrCreateSolanaWallet() {
1067
1668
  const address2 = await solanaPublicKey(envKey);
1068
1669
  return { privateKey: envKey, address: address2, isNew: false };
1069
1670
  }
1070
- const fileKey = loadSolanaWallet();
1071
- if (fileKey) {
1072
- const address2 = await solanaPublicKey(fileKey);
1073
- return { privateKey: fileKey, address: address2, isNew: false };
1671
+ const wallets = scanSolanaWallets();
1672
+ if (wallets.length > 0) {
1673
+ return { privateKey: wallets[0].secretKey, address: wallets[0].publicKey, isNew: false };
1674
+ }
1675
+ if (fs2.existsSync(SOLANA_WALLET_FILE)) {
1676
+ const fileKey = fs2.readFileSync(SOLANA_WALLET_FILE, "utf-8").trim();
1677
+ if (fileKey) {
1678
+ const address2 = await solanaPublicKey(fileKey);
1679
+ return { privateKey: fileKey, address: address2, isNew: false };
1680
+ }
1074
1681
  }
1075
1682
  const { address, privateKey } = createSolanaWallet();
1076
1683
  saveSolanaWallet(privateKey);
@@ -1152,6 +1759,149 @@ var SolanaLLMClient = class {
1152
1759
  const data = await response.json();
1153
1760
  return data.data || [];
1154
1761
  }
1762
+ /**
1763
+ * Get Solana USDC balance.
1764
+ *
1765
+ * @returns USDC balance as a float
1766
+ */
1767
+ async getBalance() {
1768
+ const usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
1769
+ const address = await this.getWalletAddress();
1770
+ try {
1771
+ const response = await fetch(this.rpcUrl, {
1772
+ method: "POST",
1773
+ headers: { "Content-Type": "application/json" },
1774
+ body: JSON.stringify({
1775
+ jsonrpc: "2.0",
1776
+ id: 1,
1777
+ method: "getTokenAccountsByOwner",
1778
+ params: [address, { mint: usdc_mint }, { encoding: "jsonParsed" }]
1779
+ })
1780
+ });
1781
+ const data = await response.json();
1782
+ const accounts = data.result?.value || [];
1783
+ if (!accounts.length) return 0;
1784
+ let total = 0;
1785
+ for (const acct of accounts) {
1786
+ total += acct.account?.data?.parsed?.info?.tokenAmount?.uiAmount || 0;
1787
+ }
1788
+ return total;
1789
+ } catch {
1790
+ return 0;
1791
+ }
1792
+ }
1793
+ /** Edit an image using img2img (Solana payment). */
1794
+ async imageEdit(prompt, image, options) {
1795
+ const body = {
1796
+ model: options?.model || "openai/gpt-image-1",
1797
+ prompt,
1798
+ image,
1799
+ size: options?.size || "1024x1024",
1800
+ n: options?.n || 1
1801
+ };
1802
+ if (options?.mask !== void 0) body.mask = options.mask;
1803
+ const data = await this.requestWithPaymentRaw("/v1/images/image2image", body);
1804
+ return data;
1805
+ }
1806
+ /** Standalone search (Solana payment). */
1807
+ async search(query, options) {
1808
+ const body = { query, max_results: options?.maxResults || 10 };
1809
+ if (options?.sources !== void 0) body.sources = options.sources;
1810
+ if (options?.fromDate !== void 0) body.from_date = options.fromDate;
1811
+ if (options?.toDate !== void 0) body.to_date = options.toDate;
1812
+ const data = await this.requestWithPaymentRaw("/v1/search", body);
1813
+ return data;
1814
+ }
1815
+ // ============================================================
1816
+ // X/Twitter endpoints (powered by AttentionVC)
1817
+ // ============================================================
1818
+ async xUserLookup(usernames) {
1819
+ const names = Array.isArray(usernames) ? usernames : [usernames];
1820
+ const data = await this.requestWithPaymentRaw("/v1/x/users/lookup", { usernames: names });
1821
+ return data;
1822
+ }
1823
+ async xFollowers(username, cursor) {
1824
+ const body = { username };
1825
+ if (cursor !== void 0) body.cursor = cursor;
1826
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followers", body);
1827
+ return data;
1828
+ }
1829
+ async xFollowings(username, cursor) {
1830
+ const body = { username };
1831
+ if (cursor !== void 0) body.cursor = cursor;
1832
+ const data = await this.requestWithPaymentRaw("/v1/x/users/followings", body);
1833
+ return data;
1834
+ }
1835
+ async xUserInfo(username) {
1836
+ const data = await this.requestWithPaymentRaw("/v1/x/users/info", { username });
1837
+ return data;
1838
+ }
1839
+ async xVerifiedFollowers(userId, cursor) {
1840
+ const body = { userId };
1841
+ if (cursor !== void 0) body.cursor = cursor;
1842
+ const data = await this.requestWithPaymentRaw("/v1/x/users/verified-followers", body);
1843
+ return data;
1844
+ }
1845
+ async xUserTweets(username, includeReplies = false, cursor) {
1846
+ const body = { username, includeReplies };
1847
+ if (cursor !== void 0) body.cursor = cursor;
1848
+ const data = await this.requestWithPaymentRaw("/v1/x/users/tweets", body);
1849
+ return data;
1850
+ }
1851
+ async xUserMentions(username, sinceTime, untilTime, cursor) {
1852
+ const body = { username };
1853
+ if (sinceTime !== void 0) body.sinceTime = sinceTime;
1854
+ if (untilTime !== void 0) body.untilTime = untilTime;
1855
+ if (cursor !== void 0) body.cursor = cursor;
1856
+ const data = await this.requestWithPaymentRaw("/v1/x/users/mentions", body);
1857
+ return data;
1858
+ }
1859
+ async xTweetLookup(tweetIds) {
1860
+ const ids = Array.isArray(tweetIds) ? tweetIds : [tweetIds];
1861
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/lookup", { tweet_ids: ids });
1862
+ return data;
1863
+ }
1864
+ async xTweetReplies(tweetId, queryType = "Latest", cursor) {
1865
+ const body = { tweetId, queryType };
1866
+ if (cursor !== void 0) body.cursor = cursor;
1867
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/replies", body);
1868
+ return data;
1869
+ }
1870
+ async xTweetThread(tweetId, cursor) {
1871
+ const body = { tweetId };
1872
+ if (cursor !== void 0) body.cursor = cursor;
1873
+ const data = await this.requestWithPaymentRaw("/v1/x/tweets/thread", body);
1874
+ return data;
1875
+ }
1876
+ async xSearch(query, queryType = "Latest", cursor) {
1877
+ const body = { query, queryType };
1878
+ if (cursor !== void 0) body.cursor = cursor;
1879
+ const data = await this.requestWithPaymentRaw("/v1/x/search", body);
1880
+ return data;
1881
+ }
1882
+ async xTrending() {
1883
+ const data = await this.requestWithPaymentRaw("/v1/x/trending", {});
1884
+ return data;
1885
+ }
1886
+ async xArticlesRising() {
1887
+ const data = await this.requestWithPaymentRaw("/v1/x/articles/rising", {});
1888
+ return data;
1889
+ }
1890
+ async xAuthorAnalytics(handle) {
1891
+ const data = await this.requestWithPaymentRaw("/v1/x/authors", { handle });
1892
+ return data;
1893
+ }
1894
+ async xCompareAuthors(handle1, handle2) {
1895
+ const data = await this.requestWithPaymentRaw("/v1/x/compare", { handle1, handle2 });
1896
+ return data;
1897
+ }
1898
+ // ── Prediction Markets (Powered by Predexon) ────────────────────────────
1899
+ async pm(path5, params) {
1900
+ return this.getWithPaymentRaw(`/v1/pm/${path5}`, params);
1901
+ }
1902
+ async pmQuery(path5, query) {
1903
+ return this.requestWithPaymentRaw(`/v1/pm/${path5}`, query);
1904
+ }
1155
1905
  /** Get session spending. */
1156
1906
  getSpending() {
1157
1907
  return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
@@ -1251,6 +2001,188 @@ var SolanaLLMClient = class {
1251
2001
  this.sessionTotalUsd += costUsd;
1252
2002
  return retryResponse.json();
1253
2003
  }
2004
+ async requestWithPaymentRaw(endpoint, body) {
2005
+ const url = `${this.apiUrl}${endpoint}`;
2006
+ const response = await this.fetchWithTimeout(url, {
2007
+ method: "POST",
2008
+ headers: { "Content-Type": "application/json", "User-Agent": USER_AGENT2 },
2009
+ body: JSON.stringify(body)
2010
+ });
2011
+ if (response.status === 402) {
2012
+ return this.handlePaymentAndRetryRaw(url, body, response);
2013
+ }
2014
+ if (!response.ok) {
2015
+ let errorBody;
2016
+ try {
2017
+ errorBody = await response.json();
2018
+ } catch {
2019
+ errorBody = { error: "Request failed" };
2020
+ }
2021
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2022
+ }
2023
+ return response.json();
2024
+ }
2025
+ async handlePaymentAndRetryRaw(url, body, response) {
2026
+ let paymentHeader = response.headers.get("payment-required");
2027
+ if (!paymentHeader) {
2028
+ try {
2029
+ const respBody = await response.json();
2030
+ if (respBody.accepts || respBody.x402Version) {
2031
+ paymentHeader = btoa(JSON.stringify(respBody));
2032
+ }
2033
+ } catch {
2034
+ }
2035
+ }
2036
+ if (!paymentHeader) {
2037
+ throw new PaymentError("402 response but no payment requirements found");
2038
+ }
2039
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2040
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
2041
+ if (!details.network?.startsWith("solana:")) {
2042
+ throw new PaymentError(
2043
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
2044
+ );
2045
+ }
2046
+ const feePayer = details.extra?.feePayer;
2047
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
2048
+ const fromAddress = await this.getWalletAddress();
2049
+ const secretKey = await solanaKeyToBytes(this.privateKey);
2050
+ const extensions = paymentRequired.extensions;
2051
+ const paymentPayload = await createSolanaPaymentPayload(
2052
+ secretKey,
2053
+ fromAddress,
2054
+ details.recipient,
2055
+ details.amount,
2056
+ feePayer,
2057
+ {
2058
+ resourceUrl: validateResourceUrl(
2059
+ details.resource?.url || url,
2060
+ this.apiUrl
2061
+ ),
2062
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
2063
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2064
+ extra: details.extra,
2065
+ extensions,
2066
+ rpcUrl: this.rpcUrl
2067
+ }
2068
+ );
2069
+ const retryResponse = await this.fetchWithTimeout(url, {
2070
+ method: "POST",
2071
+ headers: {
2072
+ "Content-Type": "application/json",
2073
+ "User-Agent": USER_AGENT2,
2074
+ "PAYMENT-SIGNATURE": paymentPayload
2075
+ },
2076
+ body: JSON.stringify(body)
2077
+ });
2078
+ if (retryResponse.status === 402) {
2079
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
2080
+ }
2081
+ if (!retryResponse.ok) {
2082
+ let errorBody;
2083
+ try {
2084
+ errorBody = await retryResponse.json();
2085
+ } catch {
2086
+ errorBody = { error: "Request failed" };
2087
+ }
2088
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2089
+ }
2090
+ const costUsd = parseFloat(details.amount) / 1e6;
2091
+ this.sessionCalls += 1;
2092
+ this.sessionTotalUsd += costUsd;
2093
+ return retryResponse.json();
2094
+ }
2095
+ async getWithPaymentRaw(endpoint, params) {
2096
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
2097
+ const url = `${this.apiUrl}${endpoint}${query}`;
2098
+ const response = await this.fetchWithTimeout(url, {
2099
+ method: "GET",
2100
+ headers: { "User-Agent": USER_AGENT2 }
2101
+ });
2102
+ if (response.status === 402) {
2103
+ return this.handleGetPaymentAndRetryRaw(url, endpoint, params, response);
2104
+ }
2105
+ if (!response.ok) {
2106
+ let errorBody;
2107
+ try {
2108
+ errorBody = await response.json();
2109
+ } catch {
2110
+ errorBody = { error: "Request failed" };
2111
+ }
2112
+ throw new APIError(`API error: ${response.status}`, response.status, sanitizeErrorResponse(errorBody));
2113
+ }
2114
+ return response.json();
2115
+ }
2116
+ async handleGetPaymentAndRetryRaw(url, endpoint, params, response) {
2117
+ let paymentHeader = response.headers.get("payment-required");
2118
+ if (!paymentHeader) {
2119
+ try {
2120
+ const respBody = await response.json();
2121
+ if (respBody.accepts || respBody.x402Version) {
2122
+ paymentHeader = btoa(JSON.stringify(respBody));
2123
+ }
2124
+ } catch {
2125
+ }
2126
+ }
2127
+ if (!paymentHeader) {
2128
+ throw new PaymentError("402 response but no payment requirements found");
2129
+ }
2130
+ const paymentRequired = parsePaymentRequired(paymentHeader);
2131
+ const details = extractPaymentDetails(paymentRequired, SOLANA_NETWORK);
2132
+ if (!details.network?.startsWith("solana:")) {
2133
+ throw new PaymentError(
2134
+ `Expected Solana payment network, got: ${details.network}. Use LLMClient for Base payments.`
2135
+ );
2136
+ }
2137
+ const feePayer = details.extra?.feePayer;
2138
+ if (!feePayer) throw new PaymentError("Missing feePayer in 402 extra field");
2139
+ const fromAddress = await this.getWalletAddress();
2140
+ const secretKey = await solanaKeyToBytes(this.privateKey);
2141
+ const extensions = paymentRequired.extensions;
2142
+ const paymentPayload = await createSolanaPaymentPayload(
2143
+ secretKey,
2144
+ fromAddress,
2145
+ details.recipient,
2146
+ details.amount,
2147
+ feePayer,
2148
+ {
2149
+ resourceUrl: validateResourceUrl(
2150
+ details.resource?.url || url,
2151
+ this.apiUrl
2152
+ ),
2153
+ resourceDescription: details.resource?.description || "BlockRun Solana AI API call",
2154
+ maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
2155
+ extra: details.extra,
2156
+ extensions,
2157
+ rpcUrl: this.rpcUrl
2158
+ }
2159
+ );
2160
+ const query = params ? "?" + new URLSearchParams(params).toString() : "";
2161
+ const retryUrl = `${this.apiUrl}${endpoint}${query}`;
2162
+ const retryResponse = await this.fetchWithTimeout(retryUrl, {
2163
+ method: "GET",
2164
+ headers: {
2165
+ "User-Agent": USER_AGENT2,
2166
+ "PAYMENT-SIGNATURE": paymentPayload
2167
+ }
2168
+ });
2169
+ if (retryResponse.status === 402) {
2170
+ throw new PaymentError("Payment was rejected. Check your Solana USDC balance.");
2171
+ }
2172
+ if (!retryResponse.ok) {
2173
+ let errorBody;
2174
+ try {
2175
+ errorBody = await retryResponse.json();
2176
+ } catch {
2177
+ errorBody = { error: "Request failed" };
2178
+ }
2179
+ throw new APIError(`API error after payment: ${retryResponse.status}`, retryResponse.status, sanitizeErrorResponse(errorBody));
2180
+ }
2181
+ const costUsd = parseFloat(details.amount) / 1e6;
2182
+ this.sessionCalls += 1;
2183
+ this.sessionTotalUsd += costUsd;
2184
+ return retryResponse.json();
2185
+ }
1254
2186
  async fetchWithTimeout(url, options) {
1255
2187
  const controller = new AbortController();
1256
2188
  const timeoutId = setTimeout(() => controller.abort(), this.timeout);
@@ -1265,6 +2197,192 @@ function solanaClient(options = {}) {
1265
2197
  return new SolanaLLMClient({ ...options, apiUrl: SOLANA_API_URL });
1266
2198
  }
1267
2199
 
2200
+ // src/cache.ts
2201
+ import * as fs3 from "fs";
2202
+ import * as path3 from "path";
2203
+ import * as os3 from "os";
2204
+ import * as crypto2 from "crypto";
2205
+ var CACHE_DIR = path3.join(os3.homedir(), ".blockrun", "cache");
2206
+ var DEFAULT_TTL = {
2207
+ "/v1/x/": 3600 * 1e3,
2208
+ "/v1/partner/": 3600 * 1e3,
2209
+ "/v1/pm/": 1800 * 1e3,
2210
+ "/v1/chat/": 0,
2211
+ "/v1/search": 900 * 1e3,
2212
+ "/v1/image": 0,
2213
+ "/v1/models": 86400 * 1e3
2214
+ };
2215
+ function getTtl(endpoint) {
2216
+ for (const [pattern, ttl] of Object.entries(DEFAULT_TTL)) {
2217
+ if (endpoint.includes(pattern)) return ttl;
2218
+ }
2219
+ return 3600 * 1e3;
2220
+ }
2221
+ function cacheKey(endpoint, body) {
2222
+ const keyData = JSON.stringify({ endpoint, body }, Object.keys({ endpoint, body }).sort());
2223
+ return crypto2.createHash("sha256").update(keyData).digest("hex").slice(0, 16);
2224
+ }
2225
+ function cachePath(key) {
2226
+ return path3.join(CACHE_DIR, `${key}.json`);
2227
+ }
2228
+ function getCached(key) {
2229
+ const filePath = cachePath(key);
2230
+ if (!fs3.existsSync(filePath)) return null;
2231
+ try {
2232
+ const raw = fs3.readFileSync(filePath, "utf-8");
2233
+ const entry = JSON.parse(raw);
2234
+ const ttl = entry.ttlMs ?? getTtl(entry.endpoint ?? "");
2235
+ if (ttl <= 0) return null;
2236
+ if (Date.now() - entry.cachedAt > ttl) {
2237
+ try {
2238
+ fs3.unlinkSync(filePath);
2239
+ } catch {
2240
+ }
2241
+ return null;
2242
+ }
2243
+ return entry.response;
2244
+ } catch {
2245
+ return null;
2246
+ }
2247
+ }
2248
+ function getCachedByRequest(endpoint, body) {
2249
+ const ttl = getTtl(endpoint);
2250
+ if (ttl <= 0) return null;
2251
+ const key = cacheKey(endpoint, body);
2252
+ return getCached(key);
2253
+ }
2254
+ function setCache(key, data, ttlMs) {
2255
+ if (ttlMs <= 0) return;
2256
+ try {
2257
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2258
+ } catch {
2259
+ }
2260
+ const entry = {
2261
+ cachedAt: Date.now(),
2262
+ response: data,
2263
+ ttlMs
2264
+ };
2265
+ try {
2266
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2267
+ } catch {
2268
+ }
2269
+ }
2270
+ function saveToCache(endpoint, body, response, costUsd = 0) {
2271
+ const ttl = getTtl(endpoint);
2272
+ if (ttl <= 0) return;
2273
+ try {
2274
+ fs3.mkdirSync(CACHE_DIR, { recursive: true });
2275
+ } catch {
2276
+ }
2277
+ const key = cacheKey(endpoint, body);
2278
+ const entry = {
2279
+ cachedAt: Date.now(),
2280
+ endpoint,
2281
+ body,
2282
+ response,
2283
+ costUsd
2284
+ };
2285
+ try {
2286
+ fs3.writeFileSync(cachePath(key), JSON.stringify(entry));
2287
+ } catch {
2288
+ }
2289
+ }
2290
+ function clearCache() {
2291
+ if (!fs3.existsSync(CACHE_DIR)) return 0;
2292
+ let count = 0;
2293
+ try {
2294
+ const files = fs3.readdirSync(CACHE_DIR);
2295
+ for (const file of files) {
2296
+ if (file.endsWith(".json")) {
2297
+ try {
2298
+ fs3.unlinkSync(path3.join(CACHE_DIR, file));
2299
+ count++;
2300
+ } catch {
2301
+ }
2302
+ }
2303
+ }
2304
+ } catch {
2305
+ }
2306
+ return count;
2307
+ }
2308
+
2309
+ // src/setup.ts
2310
+ function setupAgentWallet(options) {
2311
+ const { address, privateKey, isNew } = getOrCreateWallet();
2312
+ if (isNew && !options?.silent) {
2313
+ console.error(
2314
+ `
2315
+ BlockRun Agent Wallet Created!
2316
+ Address: ${address}
2317
+ Send USDC on Base to get started.
2318
+ `
2319
+ );
2320
+ }
2321
+ return new LLMClient({ privateKey });
2322
+ }
2323
+ async function setupAgentSolanaWallet(options) {
2324
+ const result = await getOrCreateSolanaWallet();
2325
+ if (result.isNew && !options?.silent) {
2326
+ console.error(
2327
+ `
2328
+ BlockRun Solana Agent Wallet Created!
2329
+ Address: ${result.address}
2330
+ Send USDC on Solana to get started.
2331
+ `
2332
+ );
2333
+ }
2334
+ return new SolanaLLMClient({ privateKey: result.privateKey });
2335
+ }
2336
+ async function status() {
2337
+ const client = setupAgentWallet({ silent: true });
2338
+ const address = client.getWalletAddress();
2339
+ const balance = await client.getBalance();
2340
+ console.log(`Wallet: ${address}`);
2341
+ console.log(`Balance: $${balance.toFixed(2)} USDC`);
2342
+ return { address, balance };
2343
+ }
2344
+
2345
+ // src/cost-log.ts
2346
+ import * as fs4 from "fs";
2347
+ import * as path4 from "path";
2348
+ import * as os4 from "os";
2349
+ var DATA_DIR = path4.join(os4.homedir(), ".blockrun", "data");
2350
+ var COST_LOG_FILE = path4.join(DATA_DIR, "costs.jsonl");
2351
+ function logCost(entry) {
2352
+ try {
2353
+ fs4.mkdirSync(DATA_DIR, { recursive: true });
2354
+ } catch {
2355
+ }
2356
+ try {
2357
+ fs4.appendFileSync(COST_LOG_FILE, JSON.stringify(entry) + "\n");
2358
+ } catch {
2359
+ }
2360
+ }
2361
+ function getCostSummary() {
2362
+ if (!fs4.existsSync(COST_LOG_FILE)) {
2363
+ return { totalUsd: 0, calls: 0, byModel: {} };
2364
+ }
2365
+ let totalUsd = 0;
2366
+ let calls = 0;
2367
+ const byModel = {};
2368
+ try {
2369
+ const content = fs4.readFileSync(COST_LOG_FILE, "utf-8").trim();
2370
+ if (!content) return { totalUsd: 0, calls: 0, byModel: {} };
2371
+ for (const line of content.split("\n")) {
2372
+ if (!line) continue;
2373
+ try {
2374
+ const entry = JSON.parse(line);
2375
+ totalUsd += entry.costUsd;
2376
+ calls += 1;
2377
+ byModel[entry.model] = (byModel[entry.model] || 0) + entry.costUsd;
2378
+ } catch {
2379
+ }
2380
+ }
2381
+ } catch {
2382
+ }
2383
+ return { totalUsd, calls, byModel };
2384
+ }
2385
+
1268
2386
  // src/openai-compat.ts
1269
2387
  var StreamingResponse = class {
1270
2388
  reader;
@@ -1450,6 +2568,7 @@ export {
1450
2568
  USDC_SOLANA,
1451
2569
  WALLET_DIR_PATH,
1452
2570
  WALLET_FILE_PATH,
2571
+ clearCache,
1453
2572
  createSolanaPaymentPayload,
1454
2573
  createSolanaWallet,
1455
2574
  createWallet,
@@ -1457,6 +2576,9 @@ export {
1457
2576
  formatFundingMessageCompact,
1458
2577
  formatNeedsFundingMessage,
1459
2578
  formatWalletCreatedMessage,
2579
+ getCached,
2580
+ getCachedByRequest,
2581
+ getCostSummary,
1460
2582
  getEip681Uri,
1461
2583
  getOrCreateSolanaWallet,
1462
2584
  getOrCreateWallet,
@@ -1464,10 +2586,18 @@ export {
1464
2586
  getWalletAddress,
1465
2587
  loadSolanaWallet,
1466
2588
  loadWallet,
2589
+ logCost,
1467
2590
  saveSolanaWallet,
2591
+ saveToCache,
1468
2592
  saveWallet,
2593
+ scanSolanaWallets,
2594
+ scanWallets,
2595
+ setCache,
2596
+ setupAgentSolanaWallet,
2597
+ setupAgentWallet,
1469
2598
  solanaClient,
1470
2599
  solanaKeyToBytes,
1471
2600
  solanaPublicKey,
2601
+ status,
1472
2602
  testnetClient
1473
2603
  };