@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.js
CHANGED
|
@@ -112,8 +112,8 @@ async function createSolanaPaymentPayload(secretKey, fromAddress, recipient, amo
|
|
|
112
112
|
const { Connection, PublicKey, TransactionMessage, VersionedTransaction, ComputeBudgetProgram } = await import("@solana/web3.js");
|
|
113
113
|
const { getAssociatedTokenAddress, createTransferCheckedInstruction, getMint } = await import("@solana/spl-token");
|
|
114
114
|
const { Keypair } = await import("@solana/web3.js");
|
|
115
|
-
const rpcUrl = options.rpcUrl || "https://
|
|
116
|
-
const connection = new Connection(rpcUrl);
|
|
115
|
+
const rpcUrl = options.rpcUrl || "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
116
|
+
const connection = options.rpcHeaders ? new Connection(rpcUrl, { httpHeaders: options.rpcHeaders }) : new Connection(rpcUrl);
|
|
117
117
|
const keypair = Keypair.fromSecretKey(secretKey);
|
|
118
118
|
const feePayerPubkey = new PublicKey(feePayer);
|
|
119
119
|
const ownerPubkey = keypair.publicKey;
|
|
@@ -2310,11 +2310,22 @@ var VideoClient = class {
|
|
|
2310
2310
|
* @param options - Optional generation parameters
|
|
2311
2311
|
*/
|
|
2312
2312
|
async generate(prompt, options) {
|
|
2313
|
+
if (options?.imageUrl && options?.realFaceAssetId) {
|
|
2314
|
+
throw new Error(
|
|
2315
|
+
"imageUrl and realFaceAssetId are mutually exclusive; pass at most one."
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
2318
|
+
if (options?.realFaceAssetId && !/^ta_[A-Za-z0-9]+$/.test(options.realFaceAssetId)) {
|
|
2319
|
+
throw new Error(
|
|
2320
|
+
"realFaceAssetId must be a Token360 asset id matching 'ta_[A-Za-z0-9]+' (e.g. 'ta_abc123xyz')"
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2313
2323
|
const body = {
|
|
2314
2324
|
model: options?.model || DEFAULT_MODEL3,
|
|
2315
2325
|
prompt
|
|
2316
2326
|
};
|
|
2317
2327
|
if (options?.imageUrl) body.image_url = options.imageUrl;
|
|
2328
|
+
if (options?.realFaceAssetId) body.real_face_asset_id = options.realFaceAssetId;
|
|
2318
2329
|
if (options?.durationSeconds !== void 0) body.duration_seconds = options.durationSeconds;
|
|
2319
2330
|
if (options?.aspectRatio) body.aspect_ratio = options.aspectRatio;
|
|
2320
2331
|
if (options?.resolution) body.resolution = options.resolution;
|
|
@@ -2668,15 +2679,37 @@ var VoiceClient = class {
|
|
|
2668
2679
|
}
|
|
2669
2680
|
};
|
|
2670
2681
|
|
|
2671
|
-
// src/
|
|
2682
|
+
// src/phone.ts
|
|
2672
2683
|
import { privateKeyToAccount as privateKeyToAccount6 } from "viem/accounts";
|
|
2673
2684
|
var DEFAULT_API_URL6 = "https://blockrun.ai/api";
|
|
2674
2685
|
var DEFAULT_TIMEOUT6 = 6e4;
|
|
2675
|
-
var
|
|
2686
|
+
var PHONE_PRICES = Object.freeze({
|
|
2687
|
+
lookup: 0.01,
|
|
2688
|
+
"lookup/fraud": 0.05,
|
|
2689
|
+
"numbers/buy": 5,
|
|
2690
|
+
"numbers/renew": 5,
|
|
2691
|
+
"numbers/list": 1e-3,
|
|
2692
|
+
"numbers/release": 0
|
|
2693
|
+
});
|
|
2694
|
+
function requireE164(value) {
|
|
2695
|
+
if (!value || typeof value !== "string") {
|
|
2696
|
+
throw new Error("phoneNumber is required (E.164 format, e.g. '+14155552671')");
|
|
2697
|
+
}
|
|
2698
|
+
const v = value.trim();
|
|
2699
|
+
if (!/^\+\d{7,15}$/.test(v)) {
|
|
2700
|
+
throw new Error(
|
|
2701
|
+
`phoneNumber must be E.164 (e.g. '+14155552671'), got ${JSON.stringify(value)}`
|
|
2702
|
+
);
|
|
2703
|
+
}
|
|
2704
|
+
return v;
|
|
2705
|
+
}
|
|
2706
|
+
var PhoneClient = class {
|
|
2676
2707
|
account;
|
|
2677
2708
|
privateKey;
|
|
2678
2709
|
apiUrl;
|
|
2679
2710
|
timeout;
|
|
2711
|
+
sessionTotalUsd = 0;
|
|
2712
|
+
sessionCalls = 0;
|
|
2680
2713
|
constructor(options = {}) {
|
|
2681
2714
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2682
2715
|
const privateKey = options.privateKey || envKey;
|
|
@@ -2693,6 +2726,203 @@ var SearchClient = class {
|
|
|
2693
2726
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2694
2727
|
this.timeout = options.timeout || DEFAULT_TIMEOUT6;
|
|
2695
2728
|
}
|
|
2729
|
+
/** EVM wallet address used for payments. */
|
|
2730
|
+
getWalletAddress() {
|
|
2731
|
+
return this.account.address;
|
|
2732
|
+
}
|
|
2733
|
+
/** Session-cumulative USD spend + call count across this client instance. */
|
|
2734
|
+
getSpending() {
|
|
2735
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
2736
|
+
}
|
|
2737
|
+
// ─── Lookup ────────────────────────────────────────────────────────────
|
|
2738
|
+
/**
|
|
2739
|
+
* Carrier + line-type lookup. ~$0.01.
|
|
2740
|
+
*
|
|
2741
|
+
* @param phoneNumber E.164 number (e.g. "+14155552671").
|
|
2742
|
+
*/
|
|
2743
|
+
async lookup(phoneNumber) {
|
|
2744
|
+
const num = requireE164(phoneNumber);
|
|
2745
|
+
return this.request("lookup", { phoneNumber: num });
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Lookup + fraud signals (SIM swap, call forwarding). ~$0.05.
|
|
2749
|
+
*
|
|
2750
|
+
* @param phoneNumber E.164 number.
|
|
2751
|
+
*/
|
|
2752
|
+
async lookupFraud(phoneNumber) {
|
|
2753
|
+
const num = requireE164(phoneNumber);
|
|
2754
|
+
return this.request("lookup/fraud", { phoneNumber: num });
|
|
2755
|
+
}
|
|
2756
|
+
// ─── Provisioning ──────────────────────────────────────────────────────
|
|
2757
|
+
/**
|
|
2758
|
+
* Provision a dedicated phone number for 30 days. $5.00.
|
|
2759
|
+
*
|
|
2760
|
+
* Payment is settled only after Twilio confirms the purchase, so failed
|
|
2761
|
+
* buys never charge your wallet.
|
|
2762
|
+
*
|
|
2763
|
+
* @param options.country ISO country code, "US" or "CA" (default "US").
|
|
2764
|
+
* @param options.areaCode Optional 3-digit area-code hint. Availability is
|
|
2765
|
+
* not guaranteed — the backend falls back to any number in the country if
|
|
2766
|
+
* the area code can't be matched.
|
|
2767
|
+
*/
|
|
2768
|
+
async buyNumber(options = {}) {
|
|
2769
|
+
const country = options.country ?? "US";
|
|
2770
|
+
if (country !== "US" && country !== "CA") {
|
|
2771
|
+
throw new Error("country must be 'US' or 'CA'");
|
|
2772
|
+
}
|
|
2773
|
+
const body = { country };
|
|
2774
|
+
if (options.areaCode !== void 0) {
|
|
2775
|
+
const ac = options.areaCode;
|
|
2776
|
+
if (typeof ac !== "string" || !/^\d{3}$/.test(ac)) {
|
|
2777
|
+
throw new Error("areaCode must be a 3-digit string, e.g. '415'");
|
|
2778
|
+
}
|
|
2779
|
+
body.areaCode = ac;
|
|
2780
|
+
}
|
|
2781
|
+
const data = await this.request("numbers/buy", body);
|
|
2782
|
+
return data;
|
|
2783
|
+
}
|
|
2784
|
+
/**
|
|
2785
|
+
* Extend an existing provisioned number by 30 days. $5.00.
|
|
2786
|
+
*
|
|
2787
|
+
* @throws {APIError} 403 when the wallet doesn't own the number or it has expired.
|
|
2788
|
+
*/
|
|
2789
|
+
async renewNumber(phoneNumber) {
|
|
2790
|
+
const num = requireE164(phoneNumber);
|
|
2791
|
+
const data = await this.request("numbers/renew", {
|
|
2792
|
+
phoneNumber: num
|
|
2793
|
+
});
|
|
2794
|
+
return data;
|
|
2795
|
+
}
|
|
2796
|
+
/** List the wallet's active phone numbers. ~$0.001. */
|
|
2797
|
+
async listNumbers() {
|
|
2798
|
+
const data = await this.request("numbers/list", {});
|
|
2799
|
+
return data;
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Release a provisioned number back to the Twilio pool. Free, but still
|
|
2803
|
+
* flows through x402 so the backend can verify ownership.
|
|
2804
|
+
*/
|
|
2805
|
+
async releaseNumber(phoneNumber) {
|
|
2806
|
+
const num = requireE164(phoneNumber);
|
|
2807
|
+
const data = await this.request("numbers/release", {
|
|
2808
|
+
phoneNumber: num
|
|
2809
|
+
});
|
|
2810
|
+
return data;
|
|
2811
|
+
}
|
|
2812
|
+
// ─── Internals ─────────────────────────────────────────────────────────
|
|
2813
|
+
async request(path5, body) {
|
|
2814
|
+
const url = `${this.apiUrl}/v1/phone/${path5}`;
|
|
2815
|
+
const response = await this.fetchWithTimeout(url, {
|
|
2816
|
+
method: "POST",
|
|
2817
|
+
headers: { "Content-Type": "application/json" },
|
|
2818
|
+
body: JSON.stringify(body)
|
|
2819
|
+
});
|
|
2820
|
+
if (response.status === 402) {
|
|
2821
|
+
return this.handlePaymentAndRetry(url, body, response);
|
|
2822
|
+
}
|
|
2823
|
+
return this.unwrap(response, false);
|
|
2824
|
+
}
|
|
2825
|
+
async handlePaymentAndRetry(url, body, response) {
|
|
2826
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
2827
|
+
if (!paymentHeader) {
|
|
2828
|
+
try {
|
|
2829
|
+
const rb = await response.json();
|
|
2830
|
+
if (rb.x402 || rb.accepts) paymentHeader = btoa(JSON.stringify(rb));
|
|
2831
|
+
} catch {
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
if (!paymentHeader) {
|
|
2835
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
2836
|
+
}
|
|
2837
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
2838
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
2839
|
+
const paymentPayload = await createPaymentPayload(
|
|
2840
|
+
this.privateKey,
|
|
2841
|
+
this.account.address,
|
|
2842
|
+
details.recipient,
|
|
2843
|
+
details.amount,
|
|
2844
|
+
details.network || "eip155:8453",
|
|
2845
|
+
{
|
|
2846
|
+
resourceUrl: details.resource?.url || url,
|
|
2847
|
+
resourceDescription: details.resource?.description || "BlockRun Phone",
|
|
2848
|
+
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
2849
|
+
extra: details.extra
|
|
2850
|
+
}
|
|
2851
|
+
);
|
|
2852
|
+
const retry = await this.fetchWithTimeout(url, {
|
|
2853
|
+
method: "POST",
|
|
2854
|
+
headers: {
|
|
2855
|
+
"Content-Type": "application/json",
|
|
2856
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
2857
|
+
},
|
|
2858
|
+
body: JSON.stringify(body)
|
|
2859
|
+
});
|
|
2860
|
+
if (retry.status === 402) {
|
|
2861
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
2862
|
+
}
|
|
2863
|
+
const data = await this.unwrap(retry, true);
|
|
2864
|
+
const costUsd = Number.parseFloat(details.amount) / 1e6;
|
|
2865
|
+
this.sessionCalls += 1;
|
|
2866
|
+
this.sessionTotalUsd += costUsd;
|
|
2867
|
+
const txHash = retry.headers.get("x-payment-receipt") || retry.headers.get("X-Payment-Receipt");
|
|
2868
|
+
if (txHash && data && typeof data === "object" && !("txHash" in data)) {
|
|
2869
|
+
data.txHash = txHash;
|
|
2870
|
+
}
|
|
2871
|
+
return data;
|
|
2872
|
+
}
|
|
2873
|
+
async unwrap(response, afterPayment) {
|
|
2874
|
+
if (response.ok) {
|
|
2875
|
+
return response.json();
|
|
2876
|
+
}
|
|
2877
|
+
let errorBody;
|
|
2878
|
+
try {
|
|
2879
|
+
errorBody = await response.json();
|
|
2880
|
+
} catch {
|
|
2881
|
+
errorBody = { error: "Request failed" };
|
|
2882
|
+
}
|
|
2883
|
+
const prefix = afterPayment ? "API error after payment" : "API error";
|
|
2884
|
+
throw new APIError(
|
|
2885
|
+
`${prefix}: ${response.status}`,
|
|
2886
|
+
response.status,
|
|
2887
|
+
sanitizeErrorResponse(errorBody)
|
|
2888
|
+
);
|
|
2889
|
+
}
|
|
2890
|
+
async fetchWithTimeout(url, init) {
|
|
2891
|
+
const controller = new AbortController();
|
|
2892
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2893
|
+
try {
|
|
2894
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
2895
|
+
} finally {
|
|
2896
|
+
clearTimeout(timeoutId);
|
|
2897
|
+
}
|
|
2898
|
+
}
|
|
2899
|
+
};
|
|
2900
|
+
|
|
2901
|
+
// src/search.ts
|
|
2902
|
+
import { privateKeyToAccount as privateKeyToAccount7 } from "viem/accounts";
|
|
2903
|
+
var DEFAULT_API_URL7 = "https://blockrun.ai/api";
|
|
2904
|
+
var DEFAULT_TIMEOUT7 = 6e4;
|
|
2905
|
+
var SearchClient = class {
|
|
2906
|
+
account;
|
|
2907
|
+
privateKey;
|
|
2908
|
+
apiUrl;
|
|
2909
|
+
timeout;
|
|
2910
|
+
constructor(options = {}) {
|
|
2911
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
2912
|
+
const privateKey = options.privateKey || envKey;
|
|
2913
|
+
if (!privateKey) {
|
|
2914
|
+
throw new Error(
|
|
2915
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
2916
|
+
);
|
|
2917
|
+
}
|
|
2918
|
+
validatePrivateKey(privateKey);
|
|
2919
|
+
this.privateKey = privateKey;
|
|
2920
|
+
this.account = privateKeyToAccount7(privateKey);
|
|
2921
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL7;
|
|
2922
|
+
validateApiUrl(apiUrl);
|
|
2923
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2924
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT7;
|
|
2925
|
+
}
|
|
2696
2926
|
async search(query, options) {
|
|
2697
2927
|
if (!query || query.length > 1e3) {
|
|
2698
2928
|
throw new Error("query must be 1-1000 characters");
|
|
@@ -2805,9 +3035,9 @@ var SearchClient = class {
|
|
|
2805
3035
|
};
|
|
2806
3036
|
|
|
2807
3037
|
// src/x-client.ts
|
|
2808
|
-
import { privateKeyToAccount as
|
|
2809
|
-
var
|
|
2810
|
-
var
|
|
3038
|
+
import { privateKeyToAccount as privateKeyToAccount8 } from "viem/accounts";
|
|
3039
|
+
var DEFAULT_API_URL8 = "https://blockrun.ai/api";
|
|
3040
|
+
var DEFAULT_TIMEOUT8 = 6e4;
|
|
2811
3041
|
var XClient = class _XClient {
|
|
2812
3042
|
account;
|
|
2813
3043
|
privateKey;
|
|
@@ -2830,11 +3060,11 @@ var XClient = class _XClient {
|
|
|
2830
3060
|
}
|
|
2831
3061
|
validatePrivateKey(privateKey);
|
|
2832
3062
|
this.privateKey = privateKey;
|
|
2833
|
-
this.account =
|
|
2834
|
-
const apiUrl = options.apiUrl ||
|
|
3063
|
+
this.account = privateKeyToAccount8(privateKey);
|
|
3064
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL8;
|
|
2835
3065
|
validateApiUrl(apiUrl);
|
|
2836
3066
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
2837
|
-
this.timeout = options.timeout ||
|
|
3067
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT8;
|
|
2838
3068
|
}
|
|
2839
3069
|
// ───────── User endpoints ─────────
|
|
2840
3070
|
userLookup(usernames) {
|
|
@@ -3012,9 +3242,9 @@ var XClient = class _XClient {
|
|
|
3012
3242
|
};
|
|
3013
3243
|
|
|
3014
3244
|
// src/price.ts
|
|
3015
|
-
import { privateKeyToAccount as
|
|
3016
|
-
var
|
|
3017
|
-
var
|
|
3245
|
+
import { privateKeyToAccount as privateKeyToAccount9 } from "viem/accounts";
|
|
3246
|
+
var DEFAULT_API_URL9 = "https://blockrun.ai/api";
|
|
3247
|
+
var DEFAULT_TIMEOUT9 = 3e4;
|
|
3018
3248
|
var PriceClient = class {
|
|
3019
3249
|
account = null;
|
|
3020
3250
|
privateKey = null;
|
|
@@ -3032,12 +3262,12 @@ var PriceClient = class {
|
|
|
3032
3262
|
if (privateKey) {
|
|
3033
3263
|
validatePrivateKey(privateKey);
|
|
3034
3264
|
this.privateKey = privateKey;
|
|
3035
|
-
this.account =
|
|
3265
|
+
this.account = privateKeyToAccount9(privateKey);
|
|
3036
3266
|
}
|
|
3037
|
-
const apiUrl = options.apiUrl ||
|
|
3267
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL9;
|
|
3038
3268
|
validateApiUrl(apiUrl);
|
|
3039
3269
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3040
|
-
this.timeout = options.timeout ||
|
|
3270
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT9;
|
|
3041
3271
|
}
|
|
3042
3272
|
async price(category, symbol, options) {
|
|
3043
3273
|
if (!symbol) throw new Error("symbol is required");
|
|
@@ -3216,9 +3446,9 @@ function buildUrl(base, query) {
|
|
|
3216
3446
|
}
|
|
3217
3447
|
|
|
3218
3448
|
// src/surf.ts
|
|
3219
|
-
import { privateKeyToAccount as
|
|
3220
|
-
var
|
|
3221
|
-
var
|
|
3449
|
+
import { privateKeyToAccount as privateKeyToAccount10 } from "viem/accounts";
|
|
3450
|
+
var DEFAULT_API_URL10 = "https://blockrun.ai/api";
|
|
3451
|
+
var DEFAULT_TIMEOUT10 = 6e4;
|
|
3222
3452
|
var SURF_PREFIX = "/v1/surf";
|
|
3223
3453
|
var SurfClient = class {
|
|
3224
3454
|
account;
|
|
@@ -3235,11 +3465,11 @@ var SurfClient = class {
|
|
|
3235
3465
|
}
|
|
3236
3466
|
validatePrivateKey(privateKey);
|
|
3237
3467
|
this.privateKey = privateKey;
|
|
3238
|
-
this.account =
|
|
3239
|
-
const apiUrl = options.apiUrl ||
|
|
3468
|
+
this.account = privateKeyToAccount10(privateKey);
|
|
3469
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL10;
|
|
3240
3470
|
validateApiUrl(apiUrl);
|
|
3241
3471
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3242
|
-
this.timeout = options.timeout ||
|
|
3472
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT10;
|
|
3243
3473
|
}
|
|
3244
3474
|
/**
|
|
3245
3475
|
* GET a Surf endpoint. `path` is everything after `/v1/surf` (a leading
|
|
@@ -3370,8 +3600,386 @@ var SurfClient = class {
|
|
|
3370
3600
|
}
|
|
3371
3601
|
};
|
|
3372
3602
|
|
|
3603
|
+
// src/blockrun.ts
|
|
3604
|
+
import { privateKeyToAccount as privateKeyToAccount11 } from "viem/accounts";
|
|
3605
|
+
var DEFAULT_API_URL11 = "https://blockrun.ai/api";
|
|
3606
|
+
var DEFAULT_TIMEOUT11 = 6e4;
|
|
3607
|
+
var DEFAULT_POLL_INTERVAL_MS = 5e3;
|
|
3608
|
+
var DEFAULT_POLL_BUDGET_MS = 3e5;
|
|
3609
|
+
var MAX_SIGNED_AUTH_SECONDS = 600;
|
|
3610
|
+
var BlockrunClient = class {
|
|
3611
|
+
account;
|
|
3612
|
+
privateKey;
|
|
3613
|
+
apiUrl;
|
|
3614
|
+
timeout;
|
|
3615
|
+
sessionTotalUsd = 0;
|
|
3616
|
+
sessionCalls = 0;
|
|
3617
|
+
constructor(options = {}) {
|
|
3618
|
+
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3619
|
+
const privateKey = options.privateKey || envKey;
|
|
3620
|
+
if (!privateKey) {
|
|
3621
|
+
throw new Error(
|
|
3622
|
+
"Private key required. Pass privateKey in options or set BLOCKRUN_WALLET_KEY environment variable."
|
|
3623
|
+
);
|
|
3624
|
+
}
|
|
3625
|
+
validatePrivateKey(privateKey);
|
|
3626
|
+
this.privateKey = privateKey;
|
|
3627
|
+
this.account = privateKeyToAccount11(privateKey);
|
|
3628
|
+
const apiUrl = options.apiUrl || DEFAULT_API_URL11;
|
|
3629
|
+
validateApiUrl(apiUrl);
|
|
3630
|
+
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3631
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT11;
|
|
3632
|
+
}
|
|
3633
|
+
/**
|
|
3634
|
+
* GET a BlockRun endpoint. `path` is everything after `/api` (a leading
|
|
3635
|
+
* `/api` is tolerated and stripped). Query params are URL-encoded; arrays
|
|
3636
|
+
* become repeated keys (`?a=1&a=2`); undefined/null are dropped.
|
|
3637
|
+
*/
|
|
3638
|
+
async get(path5, params) {
|
|
3639
|
+
const url = this.buildUrl(path5, params);
|
|
3640
|
+
return this.requestWithPayment(url, "GET");
|
|
3641
|
+
}
|
|
3642
|
+
/**
|
|
3643
|
+
* POST a BlockRun endpoint with a JSON body.
|
|
3644
|
+
*/
|
|
3645
|
+
async post(path5, body) {
|
|
3646
|
+
const url = this.buildUrl(path5);
|
|
3647
|
+
return this.requestWithPayment(url, "POST", body);
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Submit a long-running job and poll until it completes.
|
|
3651
|
+
*
|
|
3652
|
+
* Pattern: submit → 402 → sign → 202 `{ id, poll_url, status }` → loop GET
|
|
3653
|
+
* the poll_url with the SAME `PAYMENT-SIGNATURE` until status=completed (or
|
|
3654
|
+
* deadline exceeded). Settlement happens only when upstream returns 200 +
|
|
3655
|
+
* completed — upstream failure or caller giving up = no charge.
|
|
3656
|
+
*
|
|
3657
|
+
* If the gateway returns 200 directly on submit (no async surface), this
|
|
3658
|
+
* short-circuits and returns the body. Most long-running endpoints (image,
|
|
3659
|
+
* video, music, voice) return 202 with a poll_url.
|
|
3660
|
+
*/
|
|
3661
|
+
async poll(path5, body, options) {
|
|
3662
|
+
const submitUrl = this.buildUrl(path5);
|
|
3663
|
+
const budgetMs = options?.budgetMs ?? DEFAULT_POLL_BUDGET_MS;
|
|
3664
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
3665
|
+
const resp402 = await this.fetchWithTimeout(submitUrl, {
|
|
3666
|
+
method: "POST",
|
|
3667
|
+
headers: { "Content-Type": "application/json" },
|
|
3668
|
+
body: JSON.stringify(body ?? {})
|
|
3669
|
+
});
|
|
3670
|
+
if (resp402.status === 200) {
|
|
3671
|
+
return resp402.json();
|
|
3672
|
+
}
|
|
3673
|
+
if (resp402.status !== 402) {
|
|
3674
|
+
await this.throwApiError(resp402, `poll submit failed (${submitUrl})`);
|
|
3675
|
+
}
|
|
3676
|
+
const paymentPayload = await this.signFrom402(resp402, submitUrl, {
|
|
3677
|
+
description: "BlockRun async job",
|
|
3678
|
+
maxTimeoutSeconds: MAX_SIGNED_AUTH_SECONDS
|
|
3679
|
+
});
|
|
3680
|
+
const submitResp = await this.fetchWithTimeout(submitUrl, {
|
|
3681
|
+
method: "POST",
|
|
3682
|
+
headers: {
|
|
3683
|
+
"Content-Type": "application/json",
|
|
3684
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3685
|
+
},
|
|
3686
|
+
body: JSON.stringify(body ?? {})
|
|
3687
|
+
});
|
|
3688
|
+
if (submitResp.status === 402) {
|
|
3689
|
+
throw new PaymentError("Payment was rejected. Check your wallet balance.");
|
|
3690
|
+
}
|
|
3691
|
+
if (submitResp.status !== 200 && submitResp.status !== 202) {
|
|
3692
|
+
await this.throwApiError(submitResp, `poll submit failed (${submitUrl})`);
|
|
3693
|
+
}
|
|
3694
|
+
if (submitResp.status === 200) {
|
|
3695
|
+
this.recordSpending();
|
|
3696
|
+
return submitResp.json();
|
|
3697
|
+
}
|
|
3698
|
+
const submitData = await submitResp.json();
|
|
3699
|
+
if (!submitData.id || !submitData.poll_url) {
|
|
3700
|
+
throw new APIError(
|
|
3701
|
+
"Async submit response missing id/poll_url",
|
|
3702
|
+
submitResp.status,
|
|
3703
|
+
{ response: submitData }
|
|
3704
|
+
);
|
|
3705
|
+
}
|
|
3706
|
+
const pollUrl = this.absolute(submitData.poll_url);
|
|
3707
|
+
const deadline = Date.now() + budgetMs;
|
|
3708
|
+
let lastStatus = submitData.status || "queued";
|
|
3709
|
+
while (Date.now() < deadline) {
|
|
3710
|
+
await sleep(intervalMs);
|
|
3711
|
+
const pollResp = await this.fetchWithTimeout(pollUrl, {
|
|
3712
|
+
method: "GET",
|
|
3713
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3714
|
+
});
|
|
3715
|
+
let pollData = {};
|
|
3716
|
+
try {
|
|
3717
|
+
pollData = await pollResp.json();
|
|
3718
|
+
} catch {
|
|
3719
|
+
}
|
|
3720
|
+
lastStatus = pollData.status || lastStatus;
|
|
3721
|
+
if (pollResp.status === 202 && (lastStatus === "queued" || lastStatus === "in_progress")) {
|
|
3722
|
+
continue;
|
|
3723
|
+
}
|
|
3724
|
+
if (lastStatus === "failed") {
|
|
3725
|
+
throw new APIError(
|
|
3726
|
+
`Upstream job failed: ${pollData.error || "unknown"}`,
|
|
3727
|
+
pollResp.status,
|
|
3728
|
+
sanitizeErrorResponse(pollData)
|
|
3729
|
+
);
|
|
3730
|
+
}
|
|
3731
|
+
if (pollResp.status === 200 && lastStatus === "completed") {
|
|
3732
|
+
this.recordSpending();
|
|
3733
|
+
return pollData;
|
|
3734
|
+
}
|
|
3735
|
+
if (pollResp.status !== 200 && pollResp.status !== 202 && pollResp.status !== 504) {
|
|
3736
|
+
await this.throwApiError(pollResp, `poll failed (${pollUrl})`);
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
throw new APIError(
|
|
3740
|
+
`Job did not complete within ${Math.round(budgetMs / 1e3)}s (last status: ${lastStatus}). No payment was taken.`,
|
|
3741
|
+
504,
|
|
3742
|
+
{ id: submitData.id, last_status: lastStatus }
|
|
3743
|
+
);
|
|
3744
|
+
}
|
|
3745
|
+
/**
|
|
3746
|
+
* Stream a Server-Sent Events endpoint.
|
|
3747
|
+
*
|
|
3748
|
+
* Yields each `data: …` line parsed as JSON. Stops when the upstream emits
|
|
3749
|
+
* `data: [DONE]` or closes the connection. Caller is responsible for typing
|
|
3750
|
+
* the chunk shape; pass a generic for typed yields.
|
|
3751
|
+
*
|
|
3752
|
+
* Example — streaming chat:
|
|
3753
|
+
* for await (const chunk of br.stream<ChatChunk>("/v1/chat/completions", {
|
|
3754
|
+
* model: "anthropic/claude-sonnet-4-6",
|
|
3755
|
+
* messages: [{ role: "user", content: "Hi" }],
|
|
3756
|
+
* stream: true,
|
|
3757
|
+
* })) {
|
|
3758
|
+
* process.stdout.write(chunk.choices?.[0]?.delta?.content ?? "");
|
|
3759
|
+
* }
|
|
3760
|
+
*/
|
|
3761
|
+
async *stream(path5, body) {
|
|
3762
|
+
const url = this.buildUrl(path5);
|
|
3763
|
+
const requestBody = JSON.stringify(body ?? {});
|
|
3764
|
+
const resp402 = await this.fetchWithTimeout(url, {
|
|
3765
|
+
method: "POST",
|
|
3766
|
+
headers: { "Content-Type": "application/json" },
|
|
3767
|
+
body: requestBody
|
|
3768
|
+
});
|
|
3769
|
+
let streamResp;
|
|
3770
|
+
if (resp402.status === 200) {
|
|
3771
|
+
streamResp = resp402;
|
|
3772
|
+
} else if (resp402.status === 402) {
|
|
3773
|
+
const paymentPayload = await this.signFrom402(resp402, url, {
|
|
3774
|
+
description: "BlockRun stream"
|
|
3775
|
+
});
|
|
3776
|
+
streamResp = await this.fetchWithTimeout(url, {
|
|
3777
|
+
method: "POST",
|
|
3778
|
+
headers: {
|
|
3779
|
+
"Content-Type": "application/json",
|
|
3780
|
+
"PAYMENT-SIGNATURE": paymentPayload
|
|
3781
|
+
},
|
|
3782
|
+
body: requestBody
|
|
3783
|
+
});
|
|
3784
|
+
if (streamResp.status === 402) {
|
|
3785
|
+
throw new PaymentError(
|
|
3786
|
+
"Payment was rejected. Check your wallet balance."
|
|
3787
|
+
);
|
|
3788
|
+
}
|
|
3789
|
+
if (!streamResp.ok) {
|
|
3790
|
+
await this.throwApiError(streamResp, `stream failed after payment (${url})`);
|
|
3791
|
+
}
|
|
3792
|
+
this.recordSpending();
|
|
3793
|
+
} else {
|
|
3794
|
+
await this.throwApiError(resp402, `stream failed (${url})`);
|
|
3795
|
+
return;
|
|
3796
|
+
}
|
|
3797
|
+
if (!streamResp.body) {
|
|
3798
|
+
throw new APIError("Stream response has no body", streamResp.status, {});
|
|
3799
|
+
}
|
|
3800
|
+
const reader = streamResp.body.getReader();
|
|
3801
|
+
const decoder = new TextDecoder();
|
|
3802
|
+
let buffer = "";
|
|
3803
|
+
try {
|
|
3804
|
+
while (true) {
|
|
3805
|
+
const { done, value } = await reader.read();
|
|
3806
|
+
if (done) break;
|
|
3807
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3808
|
+
const lines = buffer.split("\n");
|
|
3809
|
+
buffer = lines.pop() || "";
|
|
3810
|
+
for (const line of lines) {
|
|
3811
|
+
const trimmed = line.trim();
|
|
3812
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
3813
|
+
const data = trimmed.slice(6);
|
|
3814
|
+
if (data === "[DONE]") return;
|
|
3815
|
+
try {
|
|
3816
|
+
yield JSON.parse(data);
|
|
3817
|
+
} catch {
|
|
3818
|
+
}
|
|
3819
|
+
}
|
|
3820
|
+
}
|
|
3821
|
+
} finally {
|
|
3822
|
+
reader.releaseLock();
|
|
3823
|
+
}
|
|
3824
|
+
}
|
|
3825
|
+
// --------------------------------------------------------------------
|
|
3826
|
+
// Internal: shared infrastructure
|
|
3827
|
+
// --------------------------------------------------------------------
|
|
3828
|
+
buildUrl(path5, params) {
|
|
3829
|
+
let normalized = path5.startsWith("/") ? path5 : `/${path5}`;
|
|
3830
|
+
if (normalized.startsWith("/api/")) {
|
|
3831
|
+
normalized = normalized.slice(4);
|
|
3832
|
+
}
|
|
3833
|
+
const base = `${this.apiUrl}${normalized}`;
|
|
3834
|
+
if (!params) return base;
|
|
3835
|
+
const qs = new URLSearchParams();
|
|
3836
|
+
for (const [key, value] of Object.entries(params)) {
|
|
3837
|
+
if (value === void 0 || value === null) continue;
|
|
3838
|
+
if (Array.isArray(value)) {
|
|
3839
|
+
for (const v of value) {
|
|
3840
|
+
if (v === void 0 || v === null) continue;
|
|
3841
|
+
qs.append(key, String(v));
|
|
3842
|
+
}
|
|
3843
|
+
} else {
|
|
3844
|
+
qs.append(key, String(value));
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
const query = qs.toString();
|
|
3848
|
+
return query ? `${base}?${query}` : base;
|
|
3849
|
+
}
|
|
3850
|
+
absolute(url) {
|
|
3851
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
3852
|
+
const base = this.apiUrl.endsWith("/api") ? this.apiUrl.slice(0, -"/api".length) : this.apiUrl;
|
|
3853
|
+
return `${base}${url}`;
|
|
3854
|
+
}
|
|
3855
|
+
async requestWithPayment(url, method, body) {
|
|
3856
|
+
const init = { method };
|
|
3857
|
+
if (method === "POST") {
|
|
3858
|
+
init.headers = { "Content-Type": "application/json" };
|
|
3859
|
+
init.body = JSON.stringify(body ?? {});
|
|
3860
|
+
}
|
|
3861
|
+
const response = await this.fetchWithTimeout(url, init);
|
|
3862
|
+
if (response.status === 402) {
|
|
3863
|
+
return this.handlePaymentAndRetry(url, method, body, response);
|
|
3864
|
+
}
|
|
3865
|
+
if (!response.ok) {
|
|
3866
|
+
await this.throwApiError(response, `${method} ${url} failed`);
|
|
3867
|
+
}
|
|
3868
|
+
return response.json();
|
|
3869
|
+
}
|
|
3870
|
+
async handlePaymentAndRetry(url, method, body, response) {
|
|
3871
|
+
const paymentPayload = await this.signFrom402(response, url, {
|
|
3872
|
+
description: "BlockRun"
|
|
3873
|
+
});
|
|
3874
|
+
const retryInit = {
|
|
3875
|
+
method,
|
|
3876
|
+
headers: { "PAYMENT-SIGNATURE": paymentPayload }
|
|
3877
|
+
};
|
|
3878
|
+
if (method === "POST") {
|
|
3879
|
+
retryInit.headers = {
|
|
3880
|
+
...retryInit.headers,
|
|
3881
|
+
"Content-Type": "application/json"
|
|
3882
|
+
};
|
|
3883
|
+
retryInit.body = JSON.stringify(body ?? {});
|
|
3884
|
+
}
|
|
3885
|
+
const retry = await this.fetchWithTimeout(url, retryInit);
|
|
3886
|
+
if (retry.status === 402) {
|
|
3887
|
+
throw new PaymentError(
|
|
3888
|
+
"Payment was rejected. Check your wallet balance."
|
|
3889
|
+
);
|
|
3890
|
+
}
|
|
3891
|
+
if (!retry.ok) {
|
|
3892
|
+
await this.throwApiError(retry, `${method} ${url} failed after payment`);
|
|
3893
|
+
}
|
|
3894
|
+
this.recordSpending();
|
|
3895
|
+
return retry.json();
|
|
3896
|
+
}
|
|
3897
|
+
/**
|
|
3898
|
+
* Read a 402 response's payment requirements (header or body), then sign and
|
|
3899
|
+
* return the base64 PAYMENT-SIGNATURE payload. Also records the cost-to-be
|
|
3900
|
+
* onto the response context (settled on `recordSpending`).
|
|
3901
|
+
*/
|
|
3902
|
+
async signFrom402(response, url, opts) {
|
|
3903
|
+
let paymentHeader = response.headers.get("payment-required");
|
|
3904
|
+
if (!paymentHeader) {
|
|
3905
|
+
try {
|
|
3906
|
+
const respBody = await response.json();
|
|
3907
|
+
if (respBody.x402Version !== void 0 || respBody.accepts !== void 0) {
|
|
3908
|
+
paymentHeader = btoa(JSON.stringify(respBody));
|
|
3909
|
+
}
|
|
3910
|
+
} catch {
|
|
3911
|
+
}
|
|
3912
|
+
}
|
|
3913
|
+
if (!paymentHeader) {
|
|
3914
|
+
throw new PaymentError("402 response but no payment requirements found");
|
|
3915
|
+
}
|
|
3916
|
+
const paymentRequired = parsePaymentRequired(paymentHeader);
|
|
3917
|
+
const details = extractPaymentDetails(paymentRequired);
|
|
3918
|
+
this.pendingCostUsd = parseFloat(details.amount) / 1e6;
|
|
3919
|
+
return createPaymentPayload(
|
|
3920
|
+
this.privateKey,
|
|
3921
|
+
this.account.address,
|
|
3922
|
+
details.recipient,
|
|
3923
|
+
details.amount,
|
|
3924
|
+
details.network || "eip155:8453",
|
|
3925
|
+
{
|
|
3926
|
+
resourceUrl: details.resource?.url || url,
|
|
3927
|
+
resourceDescription: details.resource?.description || opts.description,
|
|
3928
|
+
maxTimeoutSeconds: Math.max(
|
|
3929
|
+
details.maxTimeoutSeconds || 0,
|
|
3930
|
+
opts.maxTimeoutSeconds || 300
|
|
3931
|
+
),
|
|
3932
|
+
extra: details.extra
|
|
3933
|
+
}
|
|
3934
|
+
);
|
|
3935
|
+
}
|
|
3936
|
+
/** Accumulates the most-recent pending cost; settled by recordSpending. */
|
|
3937
|
+
pendingCostUsd = 0;
|
|
3938
|
+
recordSpending() {
|
|
3939
|
+
if (this.pendingCostUsd > 0) {
|
|
3940
|
+
this.sessionCalls += 1;
|
|
3941
|
+
this.sessionTotalUsd += this.pendingCostUsd;
|
|
3942
|
+
this.pendingCostUsd = 0;
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
async throwApiError(resp, prefix) {
|
|
3946
|
+
let errorBody;
|
|
3947
|
+
try {
|
|
3948
|
+
errorBody = await resp.json();
|
|
3949
|
+
} catch {
|
|
3950
|
+
errorBody = { error: "Request failed" };
|
|
3951
|
+
}
|
|
3952
|
+
throw new APIError(
|
|
3953
|
+
`${prefix}: HTTP ${resp.status}`,
|
|
3954
|
+
resp.status,
|
|
3955
|
+
sanitizeErrorResponse(errorBody)
|
|
3956
|
+
);
|
|
3957
|
+
}
|
|
3958
|
+
async fetchWithTimeout(url, init) {
|
|
3959
|
+
const controller = new AbortController();
|
|
3960
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
3961
|
+
try {
|
|
3962
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
3963
|
+
} finally {
|
|
3964
|
+
clearTimeout(timeoutId);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
// --------------------------------------------------------------------
|
|
3968
|
+
// Public surface: wallet + spending
|
|
3969
|
+
// --------------------------------------------------------------------
|
|
3970
|
+
getWalletAddress() {
|
|
3971
|
+
return this.account.address;
|
|
3972
|
+
}
|
|
3973
|
+
getSpending() {
|
|
3974
|
+
return { totalUsd: this.sessionTotalUsd, calls: this.sessionCalls };
|
|
3975
|
+
}
|
|
3976
|
+
};
|
|
3977
|
+
function sleep(ms) {
|
|
3978
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
3979
|
+
}
|
|
3980
|
+
|
|
3373
3981
|
// src/wallet.ts
|
|
3374
|
-
import { privateKeyToAccount as
|
|
3982
|
+
import { privateKeyToAccount as privateKeyToAccount12, generatePrivateKey } from "viem/accounts";
|
|
3375
3983
|
import * as fs2 from "fs";
|
|
3376
3984
|
import * as path2 from "path";
|
|
3377
3985
|
import * as os2 from "os";
|
|
@@ -3381,7 +3989,7 @@ var WALLET_DIR = path2.join(os2.homedir(), ".blockrun");
|
|
|
3381
3989
|
var WALLET_FILE = path2.join(WALLET_DIR, ".session");
|
|
3382
3990
|
function createWallet() {
|
|
3383
3991
|
const privateKey = generatePrivateKey();
|
|
3384
|
-
const account =
|
|
3992
|
+
const account = privateKeyToAccount12(privateKey);
|
|
3385
3993
|
return {
|
|
3386
3994
|
address: account.address,
|
|
3387
3995
|
privateKey
|
|
@@ -3437,12 +4045,12 @@ function loadWallet() {
|
|
|
3437
4045
|
function getOrCreateWallet() {
|
|
3438
4046
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3439
4047
|
if (envKey) {
|
|
3440
|
-
const account =
|
|
4048
|
+
const account = privateKeyToAccount12(envKey);
|
|
3441
4049
|
return { address: account.address, privateKey: envKey, isNew: false };
|
|
3442
4050
|
}
|
|
3443
4051
|
const fileKey = loadWallet();
|
|
3444
4052
|
if (fileKey) {
|
|
3445
|
-
const account =
|
|
4053
|
+
const account = privateKeyToAccount12(fileKey);
|
|
3446
4054
|
return { address: account.address, privateKey: fileKey, isNew: false };
|
|
3447
4055
|
}
|
|
3448
4056
|
const { address, privateKey } = createWallet();
|
|
@@ -3452,11 +4060,11 @@ function getOrCreateWallet() {
|
|
|
3452
4060
|
function getWalletAddress() {
|
|
3453
4061
|
const envKey = typeof process !== "undefined" && process.env ? process.env.BLOCKRUN_WALLET_KEY || process.env.BASE_CHAIN_WALLET_KEY : void 0;
|
|
3454
4062
|
if (envKey) {
|
|
3455
|
-
return
|
|
4063
|
+
return privateKeyToAccount12(envKey).address;
|
|
3456
4064
|
}
|
|
3457
4065
|
const fileKey = loadWallet();
|
|
3458
4066
|
if (fileKey) {
|
|
3459
|
-
return
|
|
4067
|
+
return privateKeyToAccount12(fileKey).address;
|
|
3460
4068
|
}
|
|
3461
4069
|
return null;
|
|
3462
4070
|
}
|
|
@@ -3636,14 +4244,37 @@ async function getOrCreateSolanaWallet() {
|
|
|
3636
4244
|
// src/solana-client.ts
|
|
3637
4245
|
var SOLANA_API_URL = "https://sol.blockrun.ai/api";
|
|
3638
4246
|
var DEFAULT_MAX_TOKENS2 = 1024;
|
|
3639
|
-
var
|
|
4247
|
+
var DEFAULT_TIMEOUT12 = 6e4;
|
|
3640
4248
|
var SDK_VERSION2 = "0.3.0";
|
|
3641
4249
|
var USER_AGENT2 = `blockrun-ts/${SDK_VERSION2}`;
|
|
4250
|
+
var DEFAULT_SOLANA_RPC_URL = "https://sol.blockrun.ai/api/v1/solana/rpc";
|
|
4251
|
+
function resolveRpcConfig(rpcUrl, rpcHeaders) {
|
|
4252
|
+
const env = typeof process !== "undefined" && process.env ? process.env : {};
|
|
4253
|
+
const url = rpcUrl || env.SOLANA_RPC_URL || DEFAULT_SOLANA_RPC_URL;
|
|
4254
|
+
let headers;
|
|
4255
|
+
if (rpcHeaders) {
|
|
4256
|
+
headers = { ...rpcHeaders };
|
|
4257
|
+
} else if (env.SOLANA_RPC_HEADERS) {
|
|
4258
|
+
try {
|
|
4259
|
+
const parsed = JSON.parse(env.SOLANA_RPC_HEADERS);
|
|
4260
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
4261
|
+
headers = Object.fromEntries(
|
|
4262
|
+
Object.entries(parsed).map(([k, v]) => [String(k), String(v)])
|
|
4263
|
+
);
|
|
4264
|
+
}
|
|
4265
|
+
} catch {
|
|
4266
|
+
}
|
|
4267
|
+
} else if (env.SOLANA_RPC_API_KEY) {
|
|
4268
|
+
headers = { "x-api-key": env.SOLANA_RPC_API_KEY };
|
|
4269
|
+
}
|
|
4270
|
+
return headers ? { url, headers } : { url };
|
|
4271
|
+
}
|
|
3642
4272
|
var SolanaLLMClient = class {
|
|
3643
4273
|
static SOLANA_API_URL = SOLANA_API_URL;
|
|
3644
4274
|
privateKey;
|
|
3645
4275
|
apiUrl;
|
|
3646
4276
|
rpcUrl;
|
|
4277
|
+
rpcHeaders;
|
|
3647
4278
|
timeout;
|
|
3648
4279
|
sessionTotalUsd = 0;
|
|
3649
4280
|
sessionCalls = 0;
|
|
@@ -3660,8 +4291,10 @@ var SolanaLLMClient = class {
|
|
|
3660
4291
|
const apiUrl = options.apiUrl || SOLANA_API_URL;
|
|
3661
4292
|
validateApiUrl(apiUrl);
|
|
3662
4293
|
this.apiUrl = apiUrl.replace(/\/$/, "");
|
|
3663
|
-
|
|
3664
|
-
this.
|
|
4294
|
+
const rpc = resolveRpcConfig(options.rpcUrl, options.rpcHeaders);
|
|
4295
|
+
this.rpcUrl = rpc.url;
|
|
4296
|
+
this.rpcHeaders = rpc.headers;
|
|
4297
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT12;
|
|
3665
4298
|
}
|
|
3666
4299
|
/** Get Solana wallet address (public key in base58). */
|
|
3667
4300
|
async getWalletAddress() {
|
|
@@ -3719,7 +4352,7 @@ var SolanaLLMClient = class {
|
|
|
3719
4352
|
try {
|
|
3720
4353
|
const response = await fetch(this.rpcUrl, {
|
|
3721
4354
|
method: "POST",
|
|
3722
|
-
headers: { "Content-Type": "application/json" },
|
|
4355
|
+
headers: { "Content-Type": "application/json", ...this.rpcHeaders ?? {} },
|
|
3723
4356
|
body: JSON.stringify({
|
|
3724
4357
|
jsonrpc: "2.0",
|
|
3725
4358
|
id: 1,
|
|
@@ -3970,7 +4603,8 @@ var SolanaLLMClient = class {
|
|
|
3970
4603
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
3971
4604
|
extra: details.extra,
|
|
3972
4605
|
extensions,
|
|
3973
|
-
rpcUrl: this.rpcUrl
|
|
4606
|
+
rpcUrl: this.rpcUrl,
|
|
4607
|
+
rpcHeaders: this.rpcHeaders
|
|
3974
4608
|
}
|
|
3975
4609
|
);
|
|
3976
4610
|
const retryResponse = await this.fetchWithTimeout(url, {
|
|
@@ -4061,7 +4695,8 @@ var SolanaLLMClient = class {
|
|
|
4061
4695
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4062
4696
|
extra: details.extra,
|
|
4063
4697
|
extensions,
|
|
4064
|
-
rpcUrl: this.rpcUrl
|
|
4698
|
+
rpcUrl: this.rpcUrl,
|
|
4699
|
+
rpcHeaders: this.rpcHeaders
|
|
4065
4700
|
}
|
|
4066
4701
|
);
|
|
4067
4702
|
const retryResponse = await this.fetchWithTimeout(url, {
|
|
@@ -4152,7 +4787,8 @@ var SolanaLLMClient = class {
|
|
|
4152
4787
|
maxTimeoutSeconds: details.maxTimeoutSeconds || 300,
|
|
4153
4788
|
extra: details.extra,
|
|
4154
4789
|
extensions,
|
|
4155
|
-
rpcUrl: this.rpcUrl
|
|
4790
|
+
rpcUrl: this.rpcUrl,
|
|
4791
|
+
rpcHeaders: this.rpcHeaders
|
|
4156
4792
|
}
|
|
4157
4793
|
);
|
|
4158
4794
|
const query = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
@@ -4568,7 +5204,7 @@ var OpenAI = class {
|
|
|
4568
5204
|
};
|
|
4569
5205
|
|
|
4570
5206
|
// src/anthropic-compat.ts
|
|
4571
|
-
import { privateKeyToAccount as
|
|
5207
|
+
import { privateKeyToAccount as privateKeyToAccount13 } from "viem/accounts";
|
|
4572
5208
|
var AnthropicClient = class {
|
|
4573
5209
|
_client = null;
|
|
4574
5210
|
_clientPromise = null;
|
|
@@ -4581,7 +5217,7 @@ var AnthropicClient = class {
|
|
|
4581
5217
|
const key = options.privateKey ?? wallet.privateKey;
|
|
4582
5218
|
validatePrivateKey(key);
|
|
4583
5219
|
this._privateKey = key;
|
|
4584
|
-
this._account =
|
|
5220
|
+
this._account = privateKeyToAccount13(this._privateKey);
|
|
4585
5221
|
const apiUrl = options.apiUrl ?? "https://blockrun.ai/api";
|
|
4586
5222
|
validateApiUrl(apiUrl);
|
|
4587
5223
|
this._apiUrl = apiUrl.replace(/\/$/, "");
|
|
@@ -4679,13 +5315,16 @@ export {
|
|
|
4679
5315
|
APIError,
|
|
4680
5316
|
AnthropicClient,
|
|
4681
5317
|
BASE_CHAIN_ID,
|
|
5318
|
+
BlockrunClient,
|
|
4682
5319
|
BlockrunError,
|
|
4683
5320
|
ImageClient,
|
|
4684
5321
|
KNOWN_PROVIDERS,
|
|
4685
5322
|
LLMClient,
|
|
4686
5323
|
MusicClient,
|
|
4687
5324
|
OpenAI,
|
|
5325
|
+
PHONE_PRICES,
|
|
4688
5326
|
PaymentError,
|
|
5327
|
+
PhoneClient,
|
|
4689
5328
|
PriceClient,
|
|
4690
5329
|
SOLANA_NETWORK,
|
|
4691
5330
|
SOLANA_WALLET_FILE as SOLANA_WALLET_FILE_PATH,
|