@blockrun/llm 2.4.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -0
- package/dist/index.cjs +680 -38
- package/dist/index.d.cts +343 -10
- package/dist/index.d.ts +343 -10
- package/dist/index.js +676 -37
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -33,13 +33,16 @@ __export(index_exports, {
|
|
|
33
33
|
APIError: () => APIError,
|
|
34
34
|
AnthropicClient: () => AnthropicClient,
|
|
35
35
|
BASE_CHAIN_ID: () => BASE_CHAIN_ID,
|
|
36
|
+
BlockrunClient: () => BlockrunClient,
|
|
36
37
|
BlockrunError: () => BlockrunError,
|
|
37
38
|
ImageClient: () => ImageClient,
|
|
38
39
|
KNOWN_PROVIDERS: () => KNOWN_PROVIDERS,
|
|
39
40
|
LLMClient: () => LLMClient,
|
|
40
41
|
MusicClient: () => MusicClient,
|
|
41
42
|
OpenAI: () => OpenAI,
|
|
43
|
+
PHONE_PRICES: () => PHONE_PRICES,
|
|
42
44
|
PaymentError: () => PaymentError,
|
|
45
|
+
PhoneClient: () => PhoneClient,
|
|
43
46
|
PriceClient: () => PriceClient,
|
|
44
47
|
SOLANA_NETWORK: () => SOLANA_NETWORK,
|
|
45
48
|
SOLANA_WALLET_FILE_PATH: () => SOLANA_WALLET_FILE,
|
|
@@ -210,8 +213,8 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
|
|
|
210
213
|
const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
|
|
211
214
|
const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("@solana/spl-token");
|
|
212
215
|
const { Keypair } = await import("@solana/web3.js");
|
|
213
|
-
const rpcUrl = options.rpcUrl || "https://
|
|
214
|
-
const connection = new Connection(rpcUrl);
|
|
216
|
+
const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
217
|
+
const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
|
|
215
218
|
const keypair = Keypair.fromSecretKey(secretKey);
|
|
216
219
|
const feePayerPubkey = new PublicKey(feePayer);
|
|
217
220
|
const ownerPubkey = keypair.publicKey;
|
|
@@ -2408,11 +2411,22 @@ var VideoClient = class {
|
|
|
2408
2411
|
* @param options - Optional generation parameters
|
|
2409
2412
|
*/
|
|
2410
2413
|
async generate(prompt, options) {
|
|
2414
|
+
if (options?.imageUrl && options?.realFaceAssetId) {
|
|
2415
|
+
throw new Error(
|
|
2416
|
+
"imageUrl and realFaceAssetId are mutually exclusive; pass at most one."
|
|
2417
|
+
);
|
|
2418
|
+
}
|
|
2419
|
+
if (options?.realFaceAssetId && !/^ta_[A-Za-z0-9]+$/.test(options.realFaceAssetId)) {
|
|
2420
|
+
throw new Error(
|
|
2421
|
+
"realFaceAssetId must be a Token360 asset id matching 'ta_[A-Za-z0-9]+' (e.g. 'ta_abc123xyz')"
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2411
2424
|
const body = {
|
|
2412
2425
|
model: options?.model || DEFAULT_MODEL3,
|
|
2413
2426
|
prompt
|
|
2414
2427
|
};
|
|
2415
2428
|
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2429
|
+
if (options?.realFaceAssetId) body.real_face_asset_id = options.realFaceAssetId;
|
|
2416
2430
|
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2417
2431
|
if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
2418
2432
|
if (options?.resolution) body.resolution = options.resolution;
|
|
@@ -2766,15 +2780,37 @@ var VoiceClient = class {
|
|
|
2766
2780
|
}
|
|
2767
2781
|
};
|
|
2768
2782
|
|
|
2769
|
-
// src/
|
|
2783
|
+
// src/phone.ts
|
|
2770
2784
|
var import_accounts7 = require("viem/accounts");
|
|
2771
2785
|
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2772
2786
|
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2773
|
-
var
|
|
2787
|
+
var PHONE_PRICES = Object.freeze({
|
|
2788
|
+
lookup: 0.01,
|
|
2789
|
+
"lookup/fraud": 0.05,
|
|
2790
|
+
"numbers/buy": 5,
|
|
2791
|
+
"numbers/renew": 5,
|
|
2792
|
+
"numbers/list": 1e-3,
|
|
2793
|
+
"numbers/release": 0
|
|
2794
|
+
});
|
|
2795
|
+
function requireE164(value) {
|
|
2796
|
+
if (!value || typeof value !== "string") {
|
|
2797
|
+
throw new Error("phoneNumber is required (E.164 format, e.g. '+14155552671')");
|
|
2798
|
+
}
|
|
2799
|
+
const v = value.trim();
|
|
2800
|
+
if (!/^\+\d{7,15}$/.test(v)) {
|
|
2801
|
+
throw new Error(
|
|
2802
|
+
`phoneNumber must be E.164 (e.g. '+14155552671'), got ${JSON.stringify(value)}`
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
return v;
|
|
2806
|
+
}
|
|
2807
|
+
var PhoneClient = class {
|
|
2774
2808
|
account;
|
|
2775
2809
|
privateKey;
|
|
2776
2810
|
apiUrl;
|
|
2777
2811
|
timeout;
|
|
2812
|
+
sessionTotalUsd = 0;
|
|
2813
|
+
sessionCalls = 0;
|
|
2778
2814
|
constructor(options = {}) {
|
|
2779
2815
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2780
2816
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2791,6 +2827,203 @@ var SearchClient = class {
|
|
|
2791
2827
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2792
2828
|
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2793
2829
|
}
|
|
2830
|
+
/** EVM wallet address used for payments. */
|
|
2831
|
+
getWalletAddress() {
|
|
2832
|
+
return this.account.address;
|
|
2833
|
+
}
|
|
2834
|
+
/** Session-cumulative USD spend + call count across this client instance. */
|
|
2835
|
+
getSpending() {
|
|
2836
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2837
|
+
}
|
|
2838
|
+
// ─── Lookup ────────────────────────────────────────────────────────────
|
|
2839
|
+
/**
|
|
2840
|
+
* Carrier + line-type lookup. ~$0.01.
|
|
2841
|
+
*
|
|
2842
|
+
* @param phoneNumber E.164 number (e.g. "+14155552671").
|
|
2843
|
+
*/
|
|
2844
|
+
async lookup(phoneNumber) {
|
|
2845
|
+
const num = requireE164(phoneNumber);
|
|
2846
|
+
return this.request("lookup", { phoneNumber: num });
|
|
2847
|
+
}
|
|
2848
|
+
/**
|
|
2849
|
+
* Lookup + fraud signals (SIM swap, call forwarding). ~$0.05.
|
|
2850
|
+
*
|
|
2851
|
+
* @param phoneNumber E.164 number.
|
|
2852
|
+
*/
|
|
2853
|
+
async lookupFraud(phoneNumber) {
|
|
2854
|
+
const num = requireE164(phoneNumber);
|
|
2855
|
+
return this.request("lookup/fraud", { phoneNumber: num });
|
|
2856
|
+
}
|
|
2857
|
+
// ─── Provisioning ──────────────────────────────────────────────────────
|
|
2858
|
+
/**
|
|
2859
|
+
* Provision a dedicated phone number for 30 days. $5.00.
|
|
2860
|
+
*
|
|
2861
|
+
* Payment is settled only after Twilio confirms the purchase, so failed
|
|
2862
|
+
* buys never charge your wallet.
|
|
2863
|
+
*
|
|
2864
|
+
* @param options.country ISO country code, "US" or "CA" (default "US").
|
|
2865
|
+
* @param options.areaCode Optional 3-digit area-code hint. Availability is
|
|
2866
|
+
* not guaranteed — the backend falls back to any number in the country if
|
|
2867
|
+
* the area code can't be matched.
|
|
2868
|
+
*/
|
|
2869
|
+
async buyNumber(options = {}) {
|
|
2870
|
+
const country = options.country ?? "US";
|
|
2871
|
+
if (country !== "US" && country !== "CA") {
|
|
2872
|
+
throw new Error("country must be 'US' or 'CA'");
|
|
2873
|
+
}
|
|
2874
|
+
const body = { country };
|
|
2875
|
+
if (options.areaCode !== void 0) {
|
|
2876
|
+
const ac = options.areaCode;
|
|
2877
|
+
if (typeof ac !== "string" || !/^\d{3}$/.test(ac)) {
|
|
2878
|
+
throw new Error("areaCode must be a 3-digit string, e.g. '415'");
|
|
2879
|
+
}
|
|
2880
|
+
body.areaCode = ac;
|
|
2881
|
+
}
|
|
2882
|
+
const data = await this.request("numbers/buy", body);
|
|
2883
|
+
return data;
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Extend an existing provisioned number by 30 days. $5.00.
|
|
2887
|
+
*
|
|
2888
|
+
* @throws {APIError} 403 when the wallet doesn't own the number or it has expired.
|
|
2889
|
+
*/
|
|
2890
|
+
async renewNumber(phoneNumber) {
|
|
2891
|
+
const num = requireE164(phoneNumber);
|
|
2892
|
+
const data = await this.request("numbers/renew", {
|
|
2893
|
+
phoneNumber: num
|
|
2894
|
+
});
|
|
2895
|
+
return data;
|
|
2896
|
+
}
|
|
2897
|
+
/** List the wallet's active phone numbers. ~$0.001. */
|
|
2898
|
+
async listNumbers() {
|
|
2899
|
+
const data = await this.request("numbers/list", {});
|
|
2900
|
+
return data;
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Release a provisioned number back to the Twilio pool. Free, but still
|
|
2904
|
+
* flows through x402 so the backend can verify ownership.
|
|
2905
|
+
*/
|
|
2906
|
+
async releaseNumber(phoneNumber) {
|
|
2907
|
+
const num = requireE164(phoneNumber);
|
|
2908
|
+
const data = await this.request("numbers/release", {
|
|
2909
|
+
phoneNumber: num
|
|
2910
|
+
});
|
|
2911
|
+
return data;
|
|
2912
|
+
}
|
|
2913
|
+
// ─── Internals ─────────────────────────────────────────────────────────
|
|
2914
|
+
async request(path5, body) {
|
|
2915
|
+
const url = `${this.apiUrl}/v1/phone/${path5}`;
|
|
2916
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2917
|
+
method: "POST",
|
|
2918
|
+
headers: { "Content-Type": "application/json" },
|
|
2919
|
+
body: JSON.stringify(body)
|
|
2920
|
+
});
|
|
2921
|
+
if (response.status === 402) {
|
|
2922
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2923
|
+
}
|
|
2924
|
+
return this.unwrap(response, false);
|
|
2925
|
+
}
|
|
2926
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2927
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2928
|
+
if (!paymentHeader) {
|
|
2929
|
+
try {
|
|
2930
|
+
const rb = await response.json();
|
|
2931
|
+
if (rb.x402 || rb.accepts) paymentHeader = btoa(JSON.stringify(rb));
|
|
2932
|
+
} catch {
|
|
2933
|
+
}
|
|
2934
|
+
}
|
|
2935
|
+
if (!paymentHeader) {
|
|
2936
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2937
|
+
}
|
|
2938
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2939
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2940
|
+
const paymentPayload = await createPaymentPayload(
|
|
2941
|
+
this.privateKey,
|
|
2942
|
+
this.account.address,
|
|
2943
|
+
details.recipient,
|
|
2944
|
+
details.amount,
|
|
2945
|
+
details.network || "eip155:8453",
|
|
2946
|
+
{
|
|
2947
|
+
resourceUrl: details.resource?.url || url,
|
|
2948
|
+
resourceDescription: details.resource?.description || "BlockRun Phone",
|
|
2949
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2950
|
+
extra: details.extra
|
|
2951
|
+
}
|
|
2952
|
+
);
|
|
2953
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2954
|
+
method: "POST",
|
|
2955
|
+
headers: {
|
|
2956
|
+
"Content-Type": "application/json",
|
|
2957
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2958
|
+
},
|
|
2959
|
+
body: JSON.stringify(body)
|
|
2960
|
+
});
|
|
2961
|
+
if (retry.status === 402) {
|
|
2962
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2963
|
+
}
|
|
2964
|
+
const data = await this.unwrap(retry, true);
|
|
2965
|
+
const costUsd = Number.parseFloat(details.amount) / 1e6;
|
|
2966
|
+
this.sessionCalls += 1;
|
|
2967
|
+
this.sessionTotalUsd += costUsd;
|
|
2968
|
+
const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
|
|
2969
|
+
if (txHash && data && typeof data === "object" && !("txHash" in data)) {
|
|
2970
|
+
data.txHash = txHash;
|
|
2971
|
+
}
|
|
2972
|
+
return data;
|
|
2973
|
+
}
|
|
2974
|
+
async unwrap(response, afterPayment) {
|
|
2975
|
+
if (response.ok) {
|
|
2976
|
+
return response.json();
|
|
2977
|
+
}
|
|
2978
|
+
let errorBody;
|
|
2979
|
+
try {
|
|
2980
|
+
errorBody = await response.json();
|
|
2981
|
+
} catch {
|
|
2982
|
+
errorBody = { error: "Request failed" };
|
|
2983
|
+
}
|
|
2984
|
+
const prefix = afterPayment ? "API error after payment" : "API error";
|
|
2985
|
+
throw new APIError(
|
|
2986
|
+
`${prefix}: ${response.status}`,
|
|
2987
|
+
response.status,
|
|
2988
|
+
sanitizeErrorResponse(errorBody)
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
async fetchWithTimeout(url, init) {
|
|
2992
|
+
const controller = new AbortController();
|
|
2993
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2994
|
+
try {
|
|
2995
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2996
|
+
} finally {
|
|
2997
|
+
clearTimeout(timeoutId);
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
3000
|
+
};
|
|
3001
|
+
|
|
3002
|
+
// src/search.ts
|
|
3003
|
+
var import_accounts8 = require("viem/accounts");
|
|
3004
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
3005
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
3006
|
+
var SearchClient = class {
|
|
3007
|
+
account;
|
|
3008
|
+
privateKey;
|
|
3009
|
+
apiUrl;
|
|
3010
|
+
timeout;
|
|
3011
|
+
constructor(options = {}) {
|
|
3012
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3013
|
+
const privateKey = options.privateKey || envKey;
|
|
3014
|
+
if (!privateKey) {
|
|
3015
|
+
throw new Error(
|
|
3016
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
3017
|
+
);
|
|
3018
|
+
}
|
|
3019
|
+
validatePrivateKey(privateKey);
|
|
3020
|
+
this.privateKey = privateKey;
|
|
3021
|
+
this.account = (0, import_accounts8.privateKeyToAccount)(privateKey);
|
|
3022
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
3023
|
+
validateApiUrl(apiUrl);
|
|
3024
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3025
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
3026
|
+
}
|
|
2794
3027
|
async search(query, options) {
|
|
2795
3028
|
if (!query || query.length > 1e3) {
|
|
2796
3029
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2903,9 +3136,9 @@ var SearchClient = class {
|
|
|
2903
3136
|
};
|
|
2904
3137
|
|
|
2905
3138
|
// src/x-client.ts
|
|
2906
|
-
var
|
|
2907
|
-
var
|
|
2908
|
-
var
|
|
3139
|
+
var import_accounts9 = require("viem/accounts");
|
|
3140
|
+
var DEFAULT_API_URL8 = "https://blockrun.ai/api";
|
|
3141
|
+
var DEFAULT_TIMEOUT8 = 6e4;
|
|
2909
3142
|
var XClient = class _XClient {
|
|
2910
3143
|
account;
|
|
2911
3144
|
privateKey;
|
|
@@ -2928,11 +3161,11 @@ var XClient = class _XClient {
|
|
|
2928
3161
|
}
|
|
2929
3162
|
validatePrivateKey(privateKey);
|
|
2930
3163
|
this.privateKey = privateKey;
|
|
2931
|
-
this.account = (0,
|
|
2932
|
-
const apiUrl = options.apiUrl ||
|
|
3164
|
+
this.account = (0, import_accounts9.privateKeyToAccount)(privateKey);
|
|
3165
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL8;
|
|
2933
3166
|
validateApiUrl(apiUrl);
|
|
2934
3167
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2935
|
-
this.timeout = options.timeout ||
|
|
3168
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
2936
3169
|
}
|
|
2937
3170
|
// ───────── User endpoints ─────────
|
|
2938
3171
|
userLookup(usernames) {
|
|
@@ -3110,9 +3343,9 @@ var XClient = class _XClient {
|
|
|
3110
3343
|
};
|
|
3111
3344
|
|
|
3112
3345
|
// src/price.ts
|
|
3113
|
-
var
|
|
3114
|
-
var
|
|
3115
|
-
var
|
|
3346
|
+
var import_accounts10 = require("viem/accounts");
|
|
3347
|
+
var DEFAULT_API_URL9 = "https://blockrun.ai/api";
|
|
3348
|
+
var DEFAULT_TIMEOUT9 = 3e4;
|
|
3116
3349
|
var PriceClient = class {
|
|
3117
3350
|
account = null;
|
|
3118
3351
|
privateKey = null;
|
|
@@ -3130,12 +3363,12 @@ var PriceClient = class {
|
|
|
3130
3363
|
if (privateKey) {
|
|
3131
3364
|
validatePrivateKey(privateKey);
|
|
3132
3365
|
this.privateKey = privateKey;
|
|
3133
|
-
this.account = (0,
|
|
3366
|
+
this.account = (0, import_accounts10.privateKeyToAccount)(privateKey);
|
|
3134
3367
|
}
|
|
3135
|
-
const apiUrl = options.apiUrl ||
|
|
3368
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL9;
|
|
3136
3369
|
validateApiUrl(apiUrl);
|
|
3137
3370
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3138
|
-
this.timeout = options.timeout ||
|
|
3371
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT9;
|
|
3139
3372
|
}
|
|
3140
3373
|
async price(category, symbol, options) {
|
|
3141
3374
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -3314,9 +3547,9 @@ function buildUrl(base, query) {
|
|
|
3314
3547
|
}
|
|
3315
3548
|
|
|
3316
3549
|
// src/surf.ts
|
|
3317
|
-
var
|
|
3318
|
-
var
|
|
3319
|
-
var
|
|
3550
|
+
var import_accounts11 = require("viem/accounts");
|
|
3551
|
+
var DEFAULT_API_URL10 = "https://blockrun.ai/api";
|
|
3552
|
+
var DEFAULT_TIMEOUT10 = 6e4;
|
|
3320
3553
|
var SURF_PREFIX = "/v1/surf";
|
|
3321
3554
|
var SurfClient = class {
|
|
3322
3555
|
account;
|
|
@@ -3333,11 +3566,11 @@ var SurfClient = class {
|
|
|
3333
3566
|
}
|
|
3334
3567
|
validatePrivateKey(privateKey);
|
|
3335
3568
|
this.privateKey = privateKey;
|
|
3336
|
-
this.account = (0,
|
|
3337
|
-
const apiUrl = options.apiUrl ||
|
|
3569
|
+
this.account = (0, import_accounts11.privateKeyToAccount)(privateKey);
|
|
3570
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL10;
|
|
3338
3571
|
validateApiUrl(apiUrl);
|
|
3339
3572
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3340
|
-
this.timeout = options.timeout ||
|
|
3573
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT10;
|
|
3341
3574
|
}
|
|
3342
3575
|
/**
|
|
3343
3576
|
* GET a Surf endpoint. `path` is everything after `/v1/surf` (a leading
|
|
@@ -3468,8 +3701,386 @@ var SurfClient = class {
|
|
|
3468
3701
|
}
|
|
3469
3702
|
};
|
|
3470
3703
|
|
|
3704
|
+
// src/blockrun.ts
|
|
3705
|
+
var import_accounts12 = require("viem/accounts");
|
|
3706
|
+
var DEFAULT_API_URL11 = "https://blockrun.ai/api";
|
|
3707
|
+
var DEFAULT_TIMEOUT11 = 6e4;
|
|
3708
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
3709
|
+
var DEFAULT_POLL_BUDGET_MS = 3e5;
|
|
3710
|
+
var MAX_SIGNED_AUTH_SECONDS = 600;
|
|
3711
|
+
var BlockrunClient = class {
|
|
3712
|
+
account;
|
|
3713
|
+
privateKey;
|
|
3714
|
+
apiUrl;
|
|
3715
|
+
timeout;
|
|
3716
|
+
sessionTotalUsd = 0;
|
|
3717
|
+
sessionCalls = 0;
|
|
3718
|
+
constructor(options = {}) {
|
|
3719
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3720
|
+
const privateKey = options.privateKey || envKey;
|
|
3721
|
+
if (!privateKey) {
|
|
3722
|
+
throw new Error(
|
|
3723
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
3724
|
+
);
|
|
3725
|
+
}
|
|
3726
|
+
validatePrivateKey(privateKey);
|
|
3727
|
+
this.privateKey = privateKey;
|
|
3728
|
+
this.account = (0, import_accounts12.privateKeyToAccount)(privateKey);
|
|
3729
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL11;
|
|
3730
|
+
validateApiUrl(apiUrl);
|
|
3731
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3732
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT11;
|
|
3733
|
+
}
|
|
3734
|
+
/**
|
|
3735
|
+
* GET a BlockRun endpoint. `path` is everything after `/api` (a leading
|
|
3736
|
+
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
|
|
3737
|
+
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
|
|
3738
|
+
*/
|
|
3739
|
+
async get(path5, params) {
|
|
3740
|
+
const url = this.buildUrl(path5, params);
|
|
3741
|
+
return this.requestWithPayment(url, "GET");
|
|
3742
|
+
}
|
|
3743
|
+
/**
|
|
3744
|
+
* POST a BlockRun endpoint with a JSON body.
|
|
3745
|
+
*/
|
|
3746
|
+
async post(path5, body) {
|
|
3747
|
+
const url = this.buildUrl(path5);
|
|
3748
|
+
return this.requestWithPayment(url, "POST", body);
|
|
3749
|
+
}
|
|
3750
|
+
/**
|
|
3751
|
+
* Submit a long-running job and poll until it completes.
|
|
3752
|
+
*
|
|
3753
|
+
* Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
|
|
3754
|
+
* the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
|
|
3755
|
+
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
3756
|
+
* completed — upstream failure or caller giving up = no charge.
|
|
3757
|
+
*
|
|
3758
|
+
* If the gateway returns 200 directly on submit (no async surface), this
|
|
3759
|
+
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
3760
|
+
* video, music, voice) return 202 with a poll_url.
|
|
3761
|
+
*/
|
|
3762
|
+
async poll(path5, body, options) {
|
|
3763
|
+
const submitUrl = this.buildUrl(path5);
|
|
3764
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
|
|
3765
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3766
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
3767
|
+
method: "POST",
|
|
3768
|
+
headers: { "Content-Type": "application/json" },
|
|
3769
|
+
body: JSON.stringify(body ?? {})
|
|
3770
|
+
});
|
|
3771
|
+
if (resp402.status === 200) {
|
|
3772
|
+
return resp402.json();
|
|
3773
|
+
}
|
|
3774
|
+
if (resp402.status !== 402) {
|
|
3775
|
+
await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
|
|
3776
|
+
}
|
|
3777
|
+
const paymentPayload = await this.signFrom402(resp402, submitUrl, {
|
|
3778
|
+
description: "BlockRun async job",
|
|
3779
|
+
maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
|
|
3780
|
+
});
|
|
3781
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
3782
|
+
method: "POST",
|
|
3783
|
+
headers: {
|
|
3784
|
+
"Content-Type": "application/json",
|
|
3785
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3786
|
+
},
|
|
3787
|
+
body: JSON.stringify(body ?? {})
|
|
3788
|
+
});
|
|
3789
|
+
if (submitResp.status === 402) {
|
|
3790
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
3791
|
+
}
|
|
3792
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
3793
|
+
await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
|
|
3794
|
+
}
|
|
3795
|
+
if (submitResp.status === 200) {
|
|
3796
|
+
this.recordSpending();
|
|
3797
|
+
return submitResp.json();
|
|
3798
|
+
}
|
|
3799
|
+
const submitData = await submitResp.json();
|
|
3800
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
3801
|
+
throw new APIError(
|
|
3802
|
+
"Async submit response missing id/poll_url",
|
|
3803
|
+
submitResp.status,
|
|
3804
|
+
{ response: submitData }
|
|
3805
|
+
);
|
|
3806
|
+
}
|
|
3807
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
3808
|
+
const deadline = Date.now() + budgetMs;
|
|
3809
|
+
let lastStatus = submitData.status || "queued";
|
|
3810
|
+
while (Date.now() < deadline) {
|
|
3811
|
+
await sleep(intervalMs);
|
|
3812
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
3813
|
+
method: "GET",
|
|
3814
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3815
|
+
});
|
|
3816
|
+
let pollData = {};
|
|
3817
|
+
try {
|
|
3818
|
+
pollData = await pollResp.json();
|
|
3819
|
+
} catch {
|
|
3820
|
+
}
|
|
3821
|
+
lastStatus = pollData.status || lastStatus;
|
|
3822
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
3823
|
+
continue;
|
|
3824
|
+
}
|
|
3825
|
+
if (lastStatus === "failed") {
|
|
3826
|
+
throw new APIError(
|
|
3827
|
+
`Upstream job failed: ${pollData.error || "unknown"}`,
|
|
3828
|
+
pollResp.status,
|
|
3829
|
+
sanitizeErrorResponse(pollData)
|
|
3830
|
+
);
|
|
3831
|
+
}
|
|
3832
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
3833
|
+
this.recordSpending();
|
|
3834
|
+
return pollData;
|
|
3835
|
+
}
|
|
3836
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
3837
|
+
await this.throwApiError(pollResp, `poll failed (${pollUrl})`);
|
|
3838
|
+
}
|
|
3839
|
+
}
|
|
3840
|
+
throw new APIError(
|
|
3841
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
3842
|
+
504,
|
|
3843
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
3844
|
+
);
|
|
3845
|
+
}
|
|
3846
|
+
/**
|
|
3847
|
+
* Stream a Server-Sent Events endpoint.
|
|
3848
|
+
*
|
|
3849
|
+
* Yields each `data: …` line parsed as JSON. Stops when the upstream emits
|
|
3850
|
+
* `data: [DONE]` or closes the connection. Caller is responsible for typing
|
|
3851
|
+
* the chunk shape; pass a generic for typed yields.
|
|
3852
|
+
*
|
|
3853
|
+
* Example — streaming chat:
|
|
3854
|
+
* for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
|
|
3855
|
+
* model: "anthropic/claude-sonnet-4-6",
|
|
3856
|
+
* messages: [{ role: "user", content: "Hi" }],
|
|
3857
|
+
* stream: true,
|
|
3858
|
+
* })) {
|
|
3859
|
+
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
3860
|
+
* }
|
|
3861
|
+
*/
|
|
3862
|
+
async *stream(path5, body) {
|
|
3863
|
+
const url = this.buildUrl(path5);
|
|
3864
|
+
const requestBody = JSON.stringify(body ?? {});
|
|
3865
|
+
const resp402 = await this.fetchWithTimeout(url, {
|
|
3866
|
+
method: "POST",
|
|
3867
|
+
headers: { "Content-Type": "application/json" },
|
|
3868
|
+
body: requestBody
|
|
3869
|
+
});
|
|
3870
|
+
let streamResp;
|
|
3871
|
+
if (resp402.status === 200) {
|
|
3872
|
+
streamResp = resp402;
|
|
3873
|
+
} else if (resp402.status === 402) {
|
|
3874
|
+
const paymentPayload = await this.signFrom402(resp402, url, {
|
|
3875
|
+
description: "BlockRun stream"
|
|
3876
|
+
});
|
|
3877
|
+
streamResp = await this.fetchWithTimeout(url, {
|
|
3878
|
+
method: "POST",
|
|
3879
|
+
headers: {
|
|
3880
|
+
"Content-Type": "application/json",
|
|
3881
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3882
|
+
},
|
|
3883
|
+
body: requestBody
|
|
3884
|
+
});
|
|
3885
|
+
if (streamResp.status === 402) {
|
|
3886
|
+
throw new PaymentError(
|
|
3887
|
+
"Payment was rejected. Check your wallet balance."
|
|
3888
|
+
);
|
|
3889
|
+
}
|
|
3890
|
+
if (!streamResp.ok) {
|
|
3891
|
+
await this.throwApiError(streamResp, `stream failed after payment (${url})`);
|
|
3892
|
+
}
|
|
3893
|
+
this.recordSpending();
|
|
3894
|
+
} else {
|
|
3895
|
+
await this.throwApiError(resp402, `stream failed (${url})`);
|
|
3896
|
+
return;
|
|
3897
|
+
}
|
|
3898
|
+
if (!streamResp.body) {
|
|
3899
|
+
throw new APIError("Stream response has no body", streamResp.status, {});
|
|
3900
|
+
}
|
|
3901
|
+
const reader = streamResp.body.getReader();
|
|
3902
|
+
const decoder = new TextDecoder();
|
|
3903
|
+
let buffer = "";
|
|
3904
|
+
try {
|
|
3905
|
+
while (true) {
|
|
3906
|
+
const { done, value } = await reader.read();
|
|
3907
|
+
if (done) break;
|
|
3908
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3909
|
+
const lines = buffer.split("\n");
|
|
3910
|
+
buffer = lines.pop() || "";
|
|
3911
|
+
for (const line of lines) {
|
|
3912
|
+
const trimmed = line.trim();
|
|
3913
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
3914
|
+
const data = trimmed.slice(6);
|
|
3915
|
+
if (data === "[DONE]") return;
|
|
3916
|
+
try {
|
|
3917
|
+
yield JSON.parse(data);
|
|
3918
|
+
} catch {
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
}
|
|
3922
|
+
} finally {
|
|
3923
|
+
reader.releaseLock();
|
|
3924
|
+
}
|
|
3925
|
+
}
|
|
3926
|
+
// --------------------------------------------------------------------
|
|
3927
|
+
// Internal: shared infrastructure
|
|
3928
|
+
// --------------------------------------------------------------------
|
|
3929
|
+
buildUrl(path5, params) {
|
|
3930
|
+
let normalized = path5.startsWith("/") ? path5 : `/${path5}`;
|
|
3931
|
+
if (normalized.startsWith("/api/")) {
|
|
3932
|
+
normalized = normalized.slice(4);
|
|
3933
|
+
}
|
|
3934
|
+
const base = `${this.apiUrl}${normalized}`;
|
|
3935
|
+
if (!params) return base;
|
|
3936
|
+
const qs = new URLSearchParams();
|
|
3937
|
+
for (const [key, value] of Object.entries(params)) {
|
|
3938
|
+
if (value === void 0 || value === null) continue;
|
|
3939
|
+
if (Array.isArray(value)) {
|
|
3940
|
+
for (const v of value) {
|
|
3941
|
+
if (v === void 0 || v === null) continue;
|
|
3942
|
+
qs.append(key, String(v));
|
|
3943
|
+
}
|
|
3944
|
+
} else {
|
|
3945
|
+
qs.append(key, String(value));
|
|
3946
|
+
}
|
|
3947
|
+
}
|
|
3948
|
+
const query = qs.toString();
|
|
3949
|
+
return query ? `${base}?${query}` : base;
|
|
3950
|
+
}
|
|
3951
|
+
absolute(url) {
|
|
3952
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
3953
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
3954
|
+
return `${base}${url}`;
|
|
3955
|
+
}
|
|
3956
|
+
async requestWithPayment(url, method, body) {
|
|
3957
|
+
const init = { method };
|
|
3958
|
+
if (method === "POST") {
|
|
3959
|
+
init.headers = { "Content-Type": "application/json" };
|
|
3960
|
+
init.body = JSON.stringify(body ?? {});
|
|
3961
|
+
}
|
|
3962
|
+
const response = await this.fetchWithTimeout(url, init);
|
|
3963
|
+
if (response.status === 402) {
|
|
3964
|
+
return this.handlePaymentAndRetry(url, method, body, response);
|
|
3965
|
+
}
|
|
3966
|
+
if (!response.ok) {
|
|
3967
|
+
await this.throwApiError(response, `${method} ${url} failed`);
|
|
3968
|
+
}
|
|
3969
|
+
return response.json();
|
|
3970
|
+
}
|
|
3971
|
+
async handlePaymentAndRetry(url, method, body, response) {
|
|
3972
|
+
const paymentPayload = await this.signFrom402(response, url, {
|
|
3973
|
+
description: "BlockRun"
|
|
3974
|
+
});
|
|
3975
|
+
const retryInit = {
|
|
3976
|
+
method,
|
|
3977
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3978
|
+
};
|
|
3979
|
+
if (method === "POST") {
|
|
3980
|
+
retryInit.headers = {
|
|
3981
|
+
...retryInit.headers,
|
|
3982
|
+
"Content-Type": "application/json"
|
|
3983
|
+
};
|
|
3984
|
+
retryInit.body = JSON.stringify(body ?? {});
|
|
3985
|
+
}
|
|
3986
|
+
const retry = await this.fetchWithTimeout(url, retryInit);
|
|
3987
|
+
if (retry.status === 402) {
|
|
3988
|
+
throw new PaymentError(
|
|
3989
|
+
"Payment was rejected. Check your wallet balance."
|
|
3990
|
+
);
|
|
3991
|
+
}
|
|
3992
|
+
if (!retry.ok) {
|
|
3993
|
+
await this.throwApiError(retry, `${method} ${url} failed after payment`);
|
|
3994
|
+
}
|
|
3995
|
+
this.recordSpending();
|
|
3996
|
+
return retry.json();
|
|
3997
|
+
}
|
|
3998
|
+
/**
|
|
3999
|
+
* Read a 402 response's payment requirements (header or body), then sign and
|
|
4000
|
+
* return the base64 PAYMENT-SIGNATURE payload. Also records the cost-to-be
|
|
4001
|
+
* onto the response context (settled on `recordSpending`).
|
|
4002
|
+
*/
|
|
4003
|
+
async signFrom402(response, url, opts) {
|
|
4004
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
4005
|
+
if (!paymentHeader) {
|
|
4006
|
+
try {
|
|
4007
|
+
const respBody = await response.json();
|
|
4008
|
+
if (respBody.x402Version !== void 0 || respBody.accepts !== void 0) {
|
|
4009
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
4010
|
+
}
|
|
4011
|
+
} catch {
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
if (!paymentHeader) {
|
|
4015
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
4016
|
+
}
|
|
4017
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
4018
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
4019
|
+
this.pendingCostUsd = parseFloat(details.amount) / 1e6;
|
|
4020
|
+
return createPaymentPayload(
|
|
4021
|
+
this.privateKey,
|
|
4022
|
+
this.account.address,
|
|
4023
|
+
details.recipient,
|
|
4024
|
+
details.amount,
|
|
4025
|
+
details.network || "eip155:8453",
|
|
4026
|
+
{
|
|
4027
|
+
resourceUrl: details.resource?.url || url,
|
|
4028
|
+
resourceDescription: details.resource?.description || opts.description,
|
|
4029
|
+
maxTimeoutSeconds: Math.max(
|
|
4030
|
+
details.maxTimeoutSeconds || 0,
|
|
4031
|
+
opts.maxTimeoutSeconds || 300
|
|
4032
|
+
),
|
|
4033
|
+
extra: details.extra
|
|
4034
|
+
}
|
|
4035
|
+
);
|
|
4036
|
+
}
|
|
4037
|
+
/** Accumulates the most-recent pending cost; settled by recordSpending. */
|
|
4038
|
+
pendingCostUsd = 0;
|
|
4039
|
+
recordSpending() {
|
|
4040
|
+
if (this.pendingCostUsd > 0) {
|
|
4041
|
+
this.sessionCalls += 1;
|
|
4042
|
+
this.sessionTotalUsd += this.pendingCostUsd;
|
|
4043
|
+
this.pendingCostUsd = 0;
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
async throwApiError(resp, prefix) {
|
|
4047
|
+
let errorBody;
|
|
4048
|
+
try {
|
|
4049
|
+
errorBody = await resp.json();
|
|
4050
|
+
} catch {
|
|
4051
|
+
errorBody = { error: "Request failed" };
|
|
4052
|
+
}
|
|
4053
|
+
throw new APIError(
|
|
4054
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
4055
|
+
resp.status,
|
|
4056
|
+
sanitizeErrorResponse(errorBody)
|
|
4057
|
+
);
|
|
4058
|
+
}
|
|
4059
|
+
async fetchWithTimeout(url, init) {
|
|
4060
|
+
const controller = new AbortController();
|
|
4061
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
4062
|
+
try {
|
|
4063
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
4064
|
+
} finally {
|
|
4065
|
+
clearTimeout(timeoutId);
|
|
4066
|
+
}
|
|
4067
|
+
}
|
|
4068
|
+
// --------------------------------------------------------------------
|
|
4069
|
+
// Public surface: wallet + spending
|
|
4070
|
+
// --------------------------------------------------------------------
|
|
4071
|
+
getWalletAddress() {
|
|
4072
|
+
return this.account.address;
|
|
4073
|
+
}
|
|
4074
|
+
getSpending() {
|
|
4075
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
4076
|
+
}
|
|
4077
|
+
};
|
|
4078
|
+
function sleep(ms) {
|
|
4079
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
4080
|
+
}
|
|
4081
|
+
|
|
3471
4082
|
// src/wallet.ts
|
|
3472
|
-
var
|
|
4083
|
+
var import_accounts13 = require("viem/accounts");
|
|
3473
4084
|
var fs2 = __toESM(require("fs"), 1);
|
|
3474
4085
|
var path2 = __toESM(require("path"), 1);
|
|
3475
4086
|
var os2 = __toESM(require("os"), 1);
|
|
@@ -3478,8 +4089,8 @@ var BASE_CHAIN_ID2 = "8453";
|
|
|
3478
4089
|
var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
3479
4090
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
3480
4091
|
function createWallet() {
|
|
3481
|
-
const privateKey = (0,
|
|
3482
|
-
const account = (0,
|
|
4092
|
+
const privateKey = (0, import_accounts13.generatePrivateKey)();
|
|
4093
|
+
const account = (0, import_accounts13.privateKeyToAccount)(privateKey);
|
|
3483
4094
|
return {
|
|
3484
4095
|
address: account.address,
|
|
3485
4096
|
privateKey
|
|
@@ -3535,12 +4146,12 @@ function loadWallet() {
|
|
|
3535
4146
|
function getOrCreateWallet() {
|
|
3536
4147
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3537
4148
|
if (envKey) {
|
|
3538
|
-
const account = (0,
|
|
4149
|
+
const account = (0, import_accounts13.privateKeyToAccount)(envKey);
|
|
3539
4150
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
3540
4151
|
}
|
|
3541
4152
|
const fileKey = loadWallet();
|
|
3542
4153
|
if (fileKey) {
|
|
3543
|
-
const account = (0,
|
|
4154
|
+
const account = (0, import_accounts13.privateKeyToAccount)(fileKey);
|
|
3544
4155
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
3545
4156
|
}
|
|
3546
4157
|
const { address, privateKey } = createWallet();
|
|
@@ -3550,11 +4161,11 @@ function getOrCreateWallet() {
|
|
|
3550
4161
|
function getWalletAddress() {
|
|
3551
4162
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3552
4163
|
if (envKey) {
|
|
3553
|
-
return (0,
|
|
4164
|
+
return (0, import_accounts13.privateKeyToAccount)(envKey).address;
|
|
3554
4165
|
}
|
|
3555
4166
|
const fileKey = loadWallet();
|
|
3556
4167
|
if (fileKey) {
|
|
3557
|
-
return (0,
|
|
4168
|
+
return (0, import_accounts13.privateKeyToAccount)(fileKey).address;
|
|
3558
4169
|
}
|
|
3559
4170
|
return null;
|
|
3560
4171
|
}
|
|
@@ -3734,14 +4345,37 @@ async function getOrCreateSolanaWallet() {
|
|
|
3734
4345
|
// src/solana-client.ts
|
|
3735
4346
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3736
4347
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3737
|
-
var
|
|
4348
|
+
var DEFAULT_TIMEOUT12 = 6e4;
|
|
3738
4349
|
var SDK_VERSION2 = "0.3.0";
|
|
3739
4350
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
4351
|
+
var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
4352
|
+
function resolveRpcConfig(rpcUrl, rpcHeaders) {
|
|
4353
|
+
const env = typeof process !== "undefined" && process.env ? process.env : {};
|
|
4354
|
+
const url = rpcUrl || env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC_URL;
|
|
4355
|
+
let headers;
|
|
4356
|
+
if (rpcHeaders) {
|
|
4357
|
+
headers = { ...rpcHeaders };
|
|
4358
|
+
} else if (env.SOLANA_RPC_HEADERS) {
|
|
4359
|
+
try {
|
|
4360
|
+
const parsed = JSON.parse(env.SOLANA_RPC_HEADERS);
|
|
4361
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4362
|
+
headers = Object.fromEntries(
|
|
4363
|
+
Object.entries(parsed).map(([k, v]) => [String(k), String(v)])
|
|
4364
|
+
);
|
|
4365
|
+
}
|
|
4366
|
+
} catch {
|
|
4367
|
+
}
|
|
4368
|
+
} else if (env.SOLANA_RPC_API_KEY) {
|
|
4369
|
+
headers = { "x-api-key": env.SOLANA_RPC_API_KEY };
|
|
4370
|
+
}
|
|
4371
|
+
return headers ? { url, headers } : { url };
|
|
4372
|
+
}
|
|
3740
4373
|
var SolanaLLMClient = class {
|
|
3741
4374
|
static SOLANA_API_URL = SOLANA_API_URL;
|
|
3742
4375
|
privateKey;
|
|
3743
4376
|
apiUrl;
|
|
3744
4377
|
rpcUrl;
|
|
4378
|
+
rpcHeaders;
|
|
3745
4379
|
timeout;
|
|
3746
4380
|
sessionTotalUsd = 0;
|
|
3747
4381
|
sessionCalls = 0;
|
|
@@ -3758,8 +4392,10 @@ var SolanaLLMClient = class {
|
|
|
3758
4392
|
const apiUrl = options.apiUrl || SOLANA_API_URL;
|
|
3759
4393
|
validateApiUrl(apiUrl);
|
|
3760
4394
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3761
|
-
|
|
3762
|
-
this.
|
|
4395
|
+
const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
|
|
4396
|
+
this.rpcUrl = rpc.url;
|
|
4397
|
+
this.rpcHeaders = rpc.headers;
|
|
4398
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT12;
|
|
3763
4399
|
}
|
|
3764
4400
|
/** Get Solana wallet address (public key in base58). */
|
|
3765
4401
|
async getWalletAddress() {
|
|
@@ -3817,7 +4453,7 @@ var SolanaLLMClient = class {
|
|
|
3817
4453
|
try {
|
|
3818
4454
|
const response = await fetch(this.rpcUrl, {
|
|
3819
4455
|
method: "POST",
|
|
3820
|
-
headers: { "Content-Type": "application/json" },
|
|
4456
|
+
headers: { "Content-Type": "application/json", ...this.rpcHeaders ?? {} },
|
|
3821
4457
|
body: JSON.stringify({
|
|
3822
4458
|
jsonrpc: "2.0",
|
|
3823
4459
|
id: 1,
|
|
@@ -4068,7 +4704,8 @@ var SolanaLLMClient = class {
|
|
|
4068
4704
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4069
4705
|
extra: details.extra,
|
|
4070
4706
|
extensions,
|
|
4071
|
-
rpcUrl: this.rpcUrl
|
|
4707
|
+
rpcUrl: this.rpcUrl,
|
|
4708
|
+
rpcHeaders: this.rpcHeaders
|
|
4072
4709
|
}
|
|
4073
4710
|
);
|
|
4074
4711
|
const retryResponse = await this.fetchWithTimeout(url, {
|
|
@@ -4159,7 +4796,8 @@ var SolanaLLMClient = class {
|
|
|
4159
4796
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4160
4797
|
extra: details.extra,
|
|
4161
4798
|
extensions,
|
|
4162
|
-
rpcUrl: this.rpcUrl
|
|
4799
|
+
rpcUrl: this.rpcUrl,
|
|
4800
|
+
rpcHeaders: this.rpcHeaders
|
|
4163
4801
|
}
|
|
4164
4802
|
);
|
|
4165
4803
|
const retryResponse = await this.fetchWithTimeout(url, {
|
|
@@ -4250,7 +4888,8 @@ var SolanaLLMClient = class {
|
|
|
4250
4888
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4251
4889
|
extra: details.extra,
|
|
4252
4890
|
extensions,
|
|
4253
|
-
rpcUrl: this.rpcUrl
|
|
4891
|
+
rpcUrl: this.rpcUrl,
|
|
4892
|
+
rpcHeaders: this.rpcHeaders
|
|
4254
4893
|
}
|
|
4255
4894
|
);
|
|
4256
4895
|
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
@@ -4666,7 +5305,7 @@ var OpenAI = class {
|
|
|
4666
5305
|
};
|
|
4667
5306
|
|
|
4668
5307
|
// src/anthropic-compat.ts
|
|
4669
|
-
var
|
|
5308
|
+
var import_accounts14 = require("viem/accounts");
|
|
4670
5309
|
var AnthropicClient = class {
|
|
4671
5310
|
_client = null;
|
|
4672
5311
|
_clientPromise = null;
|
|
@@ -4679,7 +5318,7 @@ var AnthropicClient = class {
|
|
|
4679
5318
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4680
5319
|
validatePrivateKey(key);
|
|
4681
5320
|
this._privateKey = key;
|
|
4682
|
-
this._account = (0,
|
|
5321
|
+
this._account = (0, import_accounts14.privateKeyToAccount)(this._privateKey);
|
|
4683
5322
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4684
5323
|
validateApiUrl(apiUrl);
|
|
4685
5324
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4778,13 +5417,16 @@ var AnthropicClient = class {
|
|
|
4778
5417
|
APIError,
|
|
4779
5418
|
AnthropicClient,
|
|
4780
5419
|
BASE_CHAIN_ID,
|
|
5420
|
+
BlockrunClient,
|
|
4781
5421
|
BlockrunError,
|
|
4782
5422
|
ImageClient,
|
|
4783
5423
|
KNOWN_PROVIDERS,
|
|
4784
5424
|
LLMClient,
|
|
4785
5425
|
MusicClient,
|
|
4786
5426
|
OpenAI,
|
|
5427
|
+
PHONE_PRICES,
|
|
4787
5428
|
PaymentError,
|
|
5429
|
+
PhoneClient,
|
|
4788
5430
|
PriceClient,
|
|
4789
5431
|
SOLANA_NETWORK,
|
|
4790
5432
|
SOLANA_WALLET_FILE_PATH,
|