@blockrun/clawrouter 0.12.269 → 0.12.271

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
@@ -783,8 +783,8 @@ var init_models = __esm({
783
783
  id: "openai/gpt-5.6-sol",
784
784
  name: "GPT-5.6 Sol",
785
785
  version: "5.6",
786
- inputPrice: 5,
787
- outputPrice: 30,
786
+ inputPrice: 4,
787
+ outputPrice: 20,
788
788
  contextWindow: 105e4,
789
789
  maxOutput: 128e3,
790
790
  reasoning: true,
@@ -825,8 +825,8 @@ var init_models = __esm({
825
825
  id: "openai/gpt-5.6-sol-pro",
826
826
  name: "GPT-5.6 Sol Pro",
827
827
  version: "5.6",
828
- inputPrice: 5,
829
- outputPrice: 30,
828
+ inputPrice: 4,
829
+ outputPrice: 20,
830
830
  contextWindow: 105e4,
831
831
  maxOutput: 128e3,
832
832
  reasoning: true,
@@ -1264,8 +1264,8 @@ var init_models = __esm({
1264
1264
  id: "google/gemini-3.6-flash",
1265
1265
  name: "Gemini 3.6 Flash",
1266
1266
  version: "3.6",
1267
- inputPrice: 1.5,
1268
- outputPrice: 7.5,
1267
+ inputPrice: 0.75,
1268
+ outputPrice: 3.75,
1269
1269
  contextWindow: 1048576,
1270
1270
  maxOutput: 65536,
1271
1271
  reasoning: true,
@@ -1661,8 +1661,8 @@ var init_models = __esm({
1661
1661
  id: "xai/grok-4.5",
1662
1662
  name: "Grok 4.5",
1663
1663
  version: "4.5",
1664
- inputPrice: 2.5,
1665
- outputPrice: 9,
1664
+ inputPrice: 2,
1665
+ outputPrice: 6,
1666
1666
  contextWindow: 5e5,
1667
1667
  maxOutput: 16384,
1668
1668
  reasoning: true,
@@ -1676,8 +1676,8 @@ var init_models = __esm({
1676
1676
  id: "xai/grok-4.3",
1677
1677
  name: "Grok 4.3",
1678
1678
  version: "4.3",
1679
- inputPrice: 1.5,
1680
- outputPrice: 4,
1679
+ inputPrice: 1.25,
1680
+ outputPrice: 2.5,
1681
1681
  contextWindow: 1e6,
1682
1682
  maxOutput: 16384,
1683
1683
  reasoning: true,
@@ -1689,8 +1689,8 @@ var init_models = __esm({
1689
1689
  id: "xai/grok-build-0.1",
1690
1690
  name: "Grok Build 0.1",
1691
1691
  version: "0.1",
1692
- inputPrice: 1.5,
1693
- outputPrice: 3,
1692
+ inputPrice: 1,
1693
+ outputPrice: 2,
1694
1694
  contextWindow: 256e3,
1695
1695
  maxOutput: 16384,
1696
1696
  agentic: true,
@@ -40581,6 +40581,18 @@ function isBalanceError(error) {
40581
40581
  function isRpcError(error) {
40582
40582
  return error instanceof Error && error.code === "RPC_ERROR";
40583
40583
  }
40584
+ function describeFetchError(error) {
40585
+ const message = error instanceof Error ? error.message : String(error);
40586
+ const cause = error?.cause;
40587
+ if (!(cause instanceof Error)) return message;
40588
+ const detail = cause;
40589
+ const host = detail.hostname || detail.address;
40590
+ const where = host && detail.port ? `${host}:${detail.port}` : host;
40591
+ const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
40592
+ (part) => typeof part === "string" && part !== ""
40593
+ );
40594
+ return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
40595
+ }
40584
40596
  var InsufficientFundsError2, EmptyWalletError, RpcError2;
40585
40597
  var init_errors5 = __esm({
40586
40598
  "src/errors.ts"() {
@@ -59861,13 +59873,18 @@ async function loadPaymentChain() {
59861
59873
  const core = await readOptional(CORE_CHAIN_FILE);
59862
59874
  if (core === "solana") return "solana";
59863
59875
  if (core === "base") return "base";
59864
- try {
59865
- const content = (await readTextFile(CHAIN_FILE)).trim();
59866
- if (content === "solana") return "solana";
59867
- return "base";
59868
- } catch {
59869
- return "base";
59870
- }
59876
+ const legacy = await readOptional(CHAIN_FILE);
59877
+ if (legacy === "solana") return "solana";
59878
+ if (legacy === "base") return "base";
59879
+ return await hasExistingWallet() ? "base" : "solana";
59880
+ }
59881
+ async function hasExistingWallet() {
59882
+ if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
59883
+ const [core, legacy] = await Promise.all([
59884
+ readOptional(CORE_WALLET_FILE),
59885
+ readOptional(WALLET_FILE)
59886
+ ]);
59887
+ return Boolean(core || legacy);
59871
59888
  }
59872
59889
  async function resolvePaymentChain() {
59873
59890
  if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
@@ -59934,6 +59951,40 @@ async function resolveApiKey() {
59934
59951
  }
59935
59952
  return void 0;
59936
59953
  }
59954
+ async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
59955
+ try {
59956
+ const controller = new AbortController();
59957
+ const timer2 = setTimeout(() => controller.abort(), timeoutMs);
59958
+ const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
59959
+ headers: { authorization: `Bearer ${apiKey}` },
59960
+ signal: controller.signal
59961
+ });
59962
+ clearTimeout(timer2);
59963
+ if (!res.ok) return void 0;
59964
+ const b = await res.json();
59965
+ const num2 = (v) => typeof v === "number" ? v : null;
59966
+ return {
59967
+ accountId: typeof b.account_id === "string" ? b.account_id : void 0,
59968
+ billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
59969
+ currency: typeof b.currency === "string" ? b.currency : "USD",
59970
+ grantedUsd: num2(b.granted_usd),
59971
+ spentUsd: num2(b.spent_usd),
59972
+ remainingUsd: num2(b.remaining_usd),
59973
+ blocked: b.blocked === true,
59974
+ blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
59975
+ };
59976
+ } catch {
59977
+ return void 0;
59978
+ }
59979
+ }
59980
+ function formatCreditBalance(b) {
59981
+ const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
59982
+ const remaining = money(b.remainingUsd);
59983
+ const spent = money(b.spentUsd);
59984
+ if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
59985
+ const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
59986
+ return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
59987
+ }
59937
59988
  function createApiKeyFetch(apiKey, baseFetch = fetch) {
59938
59989
  return async (input, init2) => {
59939
59990
  const headers = new Headers(init2?.headers);
@@ -59981,7 +60032,7 @@ var init_api_key = __esm({
59981
60032
  HINTS = {
59982
60033
  401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
59983
60034
  402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
59984
- 404: `api.blockrun.ai does not serve this endpoint yet \u2014 it currently carries chat and text completions. For media and partner APIs, run ClawRouter in wallet mode (unset BLOCKRUN_API_KEY and run "clawrouter logout").`
60035
+ 404: `api.blockrun.ai does not serve this endpoint. Chat, Anthropic-shaped messages, images, speech, video and the partner APIs (Surf, Exa, prediction markets, phone lookup) all work on an API key. The wallet-only exceptions are the routes that bind a lease or a position to a payer address \u2014 buying/renewing/releasing phone numbers, and Polymarket trading \u2014 which need a wallet to own the thing being bought. For those, unset BLOCKRUN_API_KEY and run "clawrouter logout".`
59985
60036
  };
59986
60037
  }
59987
60038
  });
@@ -90455,6 +90506,24 @@ var init_client3 = __esm({
90455
90506
  }
90456
90507
  });
90457
90508
 
90509
+ // node_modules/@x402/svm/dist/esm/v1/index.mjs
90510
+ var v1_exports = {};
90511
+ __export(v1_exports, {
90512
+ ExactSvmSchemeV1: () => ExactSvmSchemeV1,
90513
+ NETWORKS: () => NETWORKS2
90514
+ });
90515
+ var init_v1 = __esm({
90516
+ "node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
90517
+ "use strict";
90518
+ init_chunk_WWACQNRQ();
90519
+ init_chunk_MNNIECS4();
90520
+ init_chunk_WIPN332D();
90521
+ init_chunk_FVTMOTG6();
90522
+ init_chunk_FJ6REVHH();
90523
+ init_chunk_QEBTC3LJ();
90524
+ }
90525
+ });
90526
+
90458
90527
  // src/proxy.ts
90459
90528
  import { AsyncLocalStorage } from "async_hooks";
90460
90529
  import { createHmac } from "crypto";
@@ -91209,6 +91278,23 @@ function estimateImageCost(model, size5, n = 1) {
91209
91278
  const pricePerImage = sizePrice ?? pricing.default;
91210
91279
  return pricePerImage * n * 1.05;
91211
91280
  }
91281
+ function blockrunRequestId(response) {
91282
+ if (!response) return void 0;
91283
+ for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
91284
+ const value = response.headers.get(name)?.trim();
91285
+ if (value) return value;
91286
+ }
91287
+ return void 0;
91288
+ }
91289
+ function gatewaySettledCostUsd(response) {
91290
+ const raw = response?.headers.get("x-blockrun-cost-usd");
91291
+ if (raw === null || raw === void 0) return void 0;
91292
+ const trimmed = raw.trim();
91293
+ if (trimmed === "") return void 0;
91294
+ const value = Number(trimmed);
91295
+ if (!Number.isFinite(value) || value < 0) return void 0;
91296
+ return value;
91297
+ }
91212
91298
  async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
91213
91299
  const startTime = Date.now();
91214
91300
  const upstreamUrl = `${apiBase}${req.url}`;
@@ -91258,8 +91344,9 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91258
91344
  res.end();
91259
91345
  const latencyMs = Date.now() - startTime;
91260
91346
  console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
91261
- const paidAmount = getActualPaymentUsd();
91262
- const requestCost = resolvePhoneTelemetryCost({
91347
+ const settledCostUsd = gatewaySettledCostUsd(upstream);
91348
+ const paidAmount = settledCostUsd ?? getActualPaymentUsd();
91349
+ const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
91263
91350
  paidAmount,
91264
91351
  isPhone,
91265
91352
  upstreamStatus: upstream.status,
@@ -91268,6 +91355,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
91268
91355
  });
91269
91356
  logUsage({
91270
91357
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
91358
+ ...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
91271
91359
  model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
91272
91360
  tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
91273
91361
  cost: requestCost,
@@ -91459,8 +91547,20 @@ async function startProxy(options) {
91459
91547
  const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
91460
91548
  const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
91461
91549
  solanaAddress = solanaSigner.address;
91550
+ const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
91462
91551
  registerExactSvmScheme2(x402, { signer: solanaSigner });
91552
+ if (solanaRpcUrl) {
91553
+ const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
91554
+ const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
91555
+ x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
91556
+ for (const network of SVM_V1_NETWORKS) {
91557
+ x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
91558
+ }
91559
+ }
91463
91560
  console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
91561
+ if (solanaRpcUrl) {
91562
+ console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
91563
+ }
91464
91564
  }
91465
91565
  x402?.onAfterPaymentCreation(async (context) => {
91466
91566
  if (!context.selectedRequirements.network.startsWith("eip155")) return;
@@ -91560,7 +91660,15 @@ async function startProxy(options) {
91560
91660
  )
91561
91661
  ]);
91562
91662
  if (authMode === "api-key") {
91563
- response.balance = null;
91663
+ const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
91664
+ response.balance = credit?.remainingUsd ?? null;
91665
+ response.creditSpent = credit?.spentUsd ?? null;
91666
+ response.creditGranted = credit?.grantedUsd ?? null;
91667
+ response.billingMode = credit?.billingMode ?? null;
91668
+ response.creditSummary = credit ? formatCreditBalance(credit) : null;
91669
+ response.blocked = credit?.blocked ?? false;
91670
+ if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
91671
+ response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
91564
91672
  response.billing = "BlockRun account credit";
91565
91673
  response.topUpUrl = PORTAL_CREDITS_URL;
91566
91674
  } else {
@@ -92365,7 +92473,8 @@ async function startProxy(options) {
92365
92473
  }
92366
92474
  return;
92367
92475
  }
92368
- if (req.url?.match(
92476
+ const isVerbatimGatewayPath = req.url === "/v1/messages" || (req.url?.startsWith("/v1/messages?") ?? false) || req.url === "/v1/audio/speech" || (req.url?.startsWith("/v1/audio/speech?") ?? false);
92477
+ if (isVerbatimGatewayPath || req.url?.match(
92369
92478
  /^\/v1\/(?:partner|pm|exa|modal|stocks|usstock|crypto|fx|commodity|phone|voice|surf)\//
92370
92479
  )) {
92371
92480
  try {
@@ -92397,6 +92506,19 @@ async function startProxy(options) {
92397
92506
  res.end(JSON.stringify({ error: "Not found" }));
92398
92507
  return;
92399
92508
  }
92509
+ if (!req.url.includes("/chat/completions")) {
92510
+ res.writeHead(404, { "Content-Type": "application/json" });
92511
+ res.end(
92512
+ JSON.stringify({
92513
+ error: {
92514
+ message: `ClawRouter does not proxy ${req.url}. It serves /v1/chat/completions, /v1/messages, /v1/models, /v1/images/generations, /v1/images/image2image, /v1/videos/generations, /v1/audio/generations, /v1/audio/speech, and the partner prefixes /v1/{partner,pm,exa,modal,stocks,usstock,crypto,fx,commodity,phone,voice,surf}/. If BlockRun serves this endpoint, call it directly against the gateway.`,
92515
+ type: "invalid_request_error",
92516
+ code: "unsupported_endpoint"
92517
+ }
92518
+ })
92519
+ );
92520
+ return;
92521
+ }
92400
92522
  try {
92401
92523
  await proxyRequest(
92402
92524
  req,
@@ -92651,7 +92773,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
92651
92773
  errorBody,
92652
92774
  errorStatus: response.status,
92653
92775
  isProviderError: category !== null,
92654
- errorCategory: category ?? void 0
92776
+ errorCategory: category ?? void 0,
92777
+ upstreamRequestId: blockrunRequestId(response)
92655
92778
  };
92656
92779
  }
92657
92780
  const contentType = response.headers.get("content-type") || "";
@@ -92688,9 +92811,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
92688
92811
  errorCategory: "payment_error"
92689
92812
  };
92690
92813
  }
92814
+ const networkMsg = describeFetchError(err);
92815
+ const solanaSigningHint = upstreamUrl.startsWith(BLOCKRUN_SOLANA_API) && networkMsg.startsWith("fetch failed") ? " \u2014 on the Solana rail this may be the RPC that signs the payment rather than the gateway; if api.mainnet-beta.solana.com is unreachable from this host, set CLAWROUTER_SOLANA_RPC_URL to your own endpoint" : "";
92816
+ console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
92691
92817
  return {
92692
92818
  success: false,
92693
- errorBody: errorMsg,
92819
+ errorBody: `${networkMsg}${solanaSigningHint}`,
92694
92820
  errorStatus: 500,
92695
92821
  isProviderError: true
92696
92822
  // Network errors are retryable
@@ -92723,6 +92849,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
92723
92849
  let accumulatedContent = "";
92724
92850
  let responseInputTokens;
92725
92851
  let responseOutputTokens;
92852
+ let upstreamRequestId;
92726
92853
  let requestHadError = false;
92727
92854
  let requestSummaryForStore = "";
92728
92855
  const isChatCompletion = req.url?.includes("/chat/completions");
@@ -94129,7 +94256,8 @@ data: [DONE]
94129
94256
  }
94130
94257
  lastError = {
94131
94258
  body: result.errorBody || "Unknown error",
94132
- status: result.errorStatus || 500
94259
+ status: result.errorStatus || 500,
94260
+ requestId: result.upstreamRequestId
94133
94261
  };
94134
94262
  failedAttempts.push({
94135
94263
  model: tryModel,
@@ -94201,7 +94329,8 @@ data: [DONE]
94201
94329
  }
94202
94330
  lastError = {
94203
94331
  body: retryResult.errorBody || lastError?.body || "Unknown error",
94204
- status: retryResult.errorStatus || lastError?.status || 500
94332
+ status: retryResult.errorStatus || lastError?.status || 500,
94333
+ requestId: retryResult.upstreamRequestId ?? lastError?.requestId
94205
94334
  };
94206
94335
  failedAttempts.push({
94207
94336
  model: tryModel,
@@ -94343,10 +94472,13 @@ data: [DONE]
94343
94472
  }
94344
94473
  }
94345
94474
  }
94475
+ upstreamRequestId = blockrunRequestId(upstream);
94346
94476
  if (!upstream) {
94347
94477
  const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
94348
94478
  const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
94349
- console.log(`[ClawRouter] ${structuredMessage}`);
94479
+ console.log(
94480
+ `[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
94481
+ );
94350
94482
  const rawErrBody = lastError?.body || structuredMessage;
94351
94483
  const errStatus = lastError?.status || 502;
94352
94484
  const transformedErr = transformPaymentError(rawErrBody);
@@ -94384,7 +94516,8 @@ data: [DONE]
94384
94516
  res.writeHead(errStatus, {
94385
94517
  "Content-Type": "application/json",
94386
94518
  "x-context-used-kb": String(originalContextSizeKB),
94387
- "x-context-limit-kb": String(CONTEXT_LIMIT_KB)
94519
+ "x-context-limit-kb": String(CONTEXT_LIMIT_KB),
94520
+ ...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
94388
94521
  });
94389
94522
  res.end(transformedErr);
94390
94523
  deduplicator.complete(dedupKey, {
@@ -94848,12 +94981,24 @@ data: [DONE]
94848
94981
  maxTokens,
94849
94982
  routingProfile ?? void 0
94850
94983
  );
94851
- logCost = FREE_MODELS.has(logModel) ? 0 : costs.costEstimate;
94984
+ const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
94985
+ if (FREE_MODELS.has(logModel)) {
94986
+ logCost = 0;
94987
+ logSavings = 1;
94988
+ } else if (apiKeyPricing) {
94989
+ const inTokens = responseInputTokens ?? chargedInputTokens;
94990
+ const outTokens = responseOutputTokens ?? 0;
94991
+ logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
94992
+ logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
94993
+ } else {
94994
+ logCost = costs.costEstimate;
94995
+ logSavings = costs.savings;
94996
+ }
94852
94997
  logBaseline = costs.baselineCost;
94853
- logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
94854
94998
  }
94855
94999
  const entry = {
94856
95000
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
95001
+ ...upstreamRequestId ? { requestId: upstreamRequestId } : {},
94857
95002
  model: logModel,
94858
95003
  tier: routingDecision?.tier ?? "DIRECT",
94859
95004
  cost: logCost,
@@ -94893,6 +95038,7 @@ var init_proxy = __esm({
94893
95038
  init_api_key();
94894
95039
  init_spend_control();
94895
95040
  init_compression();
95041
+ init_errors5();
94896
95042
  init_version4();
94897
95043
  init_session();
94898
95044
  init_updater();
@@ -229338,7 +229484,15 @@ ${errText}`
229338
229484
  });
229339
229485
  if (!isGatewayMode()) {
229340
229486
  if (shouldLogRegistration) {
229341
- resolveOrGenerateWalletKey().then(({ address: address2, source }) => {
229487
+ void (async () => {
229488
+ const keyResolution = await resolveApiKey().catch(() => void 0);
229489
+ if (keyResolution) {
229490
+ api.logger.info(
229491
+ `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
229492
+ );
229493
+ return;
229494
+ }
229495
+ const { address: address2, source } = await resolveOrGenerateWalletKey();
229342
229496
  if (source === "generated") {
229343
229497
  api.logger.warn(`\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550`);
229344
229498
  api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
@@ -229355,7 +229509,7 @@ ${errText}`
229355
229509
  } else {
229356
229510
  api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address2}`);
229357
229511
  }
229358
- }).catch((err) => {
229512
+ })().catch((err) => {
229359
229513
  api.logger.warn(
229360
229514
  `Failed to initialize wallet: ${err instanceof Error ? err.message : String(err)}`
229361
229515
  );
@@ -209,7 +209,8 @@ Per-request alternative: send `x-clawrouter-debug: false` on the request.
209
209
 
210
210
  ### CLAWROUTER_SOLANA_RPC_URL
211
211
 
212
- Override the Solana RPC endpoint used for USDC balance checks (Solana chain only):
212
+ Override the Solana RPC endpoint used for USDC balance checks **and for signing
213
+ payments** (Solana chain only):
213
214
 
214
215
  ```bash
215
216
  export CLAWROUTER_SOLANA_RPC_URL=https://your-rpc-provider.com
@@ -218,6 +219,21 @@ openclaw gateway restart
218
219
 
219
220
  Public RPC may rate-limit on heavy usage. Use a dedicated RPC for production.
220
221
 
222
+ Unlike Base, where the EIP-3009 signature is produced entirely offline, signing
223
+ a Solana payment reads the payment asset's mint account over RPC. A host that
224
+ cannot reach `api.mainnet-beta.solana.com` therefore fails **every** paid Solana
225
+ call with `fetch failed` while `sol.blockrun.ai` itself answers normally. Check
226
+ egress before blaming the gateway:
227
+
228
+ ```bash
229
+ curl -s -m 10 -X POST https://api.mainnet-beta.solana.com \
230
+ -H 'content-type: application/json' \
231
+ -d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo","params":["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",{"encoding":"base64"}]}'
232
+ ```
233
+
234
+ If that hangs or is refused, point this variable at an endpoint the host can
235
+ reach.
236
+
221
237
  ---
222
238
 
223
239
  ## Wallet Configuration
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.269",
3
+ "version": "0.12.271",
4
4
  "description": "Smart LLM router — save 84% on inference costs. 76 models (7 free). Pay by credit card (API key) or USDC (x402 wallet).",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -53,7 +53,7 @@ metadata:
53
53
 
54
54
  # ClawRouter
55
55
 
56
- Hosted-gateway LLM router that saves <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across <!-- br:models.chatVisible -->76<!-- /br:models.chatVisible --> models from 9 providers (<!-- br:models.free -->7<!-- /br:models.free --> free open-weight models). All billing flows through one USDC wallet; you do not hold provider API keys.
56
+ Hosted-gateway LLM router that saves <!-- br:savings.autoVsBaselinePct -->84<!-- /br:savings.autoVsBaselinePct -->% on inference costs by forwarding each request to the blockrun.ai gateway, which picks the cheapest model capable of handling it across <!-- br:models.chatVisible -->76<!-- /br:models.chatVisible --> models from 9 providers (<!-- br:models.free -->7<!-- /br:models.free --> free open-weight models). All billing flows through one BlockRun credential — either a USDC wallet paying x402 per call, or a BlockRun API key drawing on card-funded account credit. Either way you do not hold provider API keys.
57
57
 
58
58
  **This is not a local-inference tool.** ClawRouter is a thin local proxy. Your prompts are sent over HTTPS to the blockrun.ai gateway for model execution. If your workload requires inference that never leaves your machine, use a local runtime like Ollama — ClawRouter is not the right tool for that use case.
59
59
 
@@ -69,7 +69,7 @@ Your app → localhost proxy (ClawRouter) → https://blockrun.ai/api (or sol.b
69
69
  Response → back through proxy → your app
70
70
  ```
71
71
 
72
- **Sent to blockrun.ai on every request:** the model name, the full prompt/messages body, sampling params (temperature, max_tokens, tools, etc.), and an `X-PAYMENT` header containing a signed x402 USDC micropayment.
72
+ **Sent to blockrun.ai on every request:** the model name, the full prompt/messages body, sampling params (temperature, max_tokens, tools, etc.), and a payment credential — an `X-PAYMENT` header containing a signed x402 USDC micropayment in wallet mode, or an `Authorization: Bearer brk_live_…` header in API-key mode.
73
73
 
74
74
  **Not sent:** your wallet private key (only the detached payment signature is sent), any other local files, environment variables, or OpenClaw config beyond what's needed for this request.
75
75
 
@@ -81,12 +81,12 @@ ClawRouter does **not** collect or forward third-party provider API keys. You do
81
81
 
82
82
  **What `models.providers.blockrun` stores (fully enumerated):**
83
83
 
84
- | Field | Sensitive | Purpose |
85
- | ----------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
- | `walletKey` | Yes | EVM private key used to sign USDC micropayments via x402. **Auto-generated locally on first run** — no user input required. Never transmitted over the network; only detached payment signatures are sent. |
87
- | `solanaKey` | Yes | Solana keypair (BIP-44 `m/44'/501'/0'/0'`). Auto-derived from the same local mnemonic via `@scure/bip32` + `@scure/bip39`. |
88
- | `gateway` | No | Gateway URL. Defaults: `https://sol.blockrun.ai/api` (Solana, default chain for new installs) · `https://blockrun.ai/api` (Base, default for pre-existing installs). |
89
- | `routing` | No | Optional override of the router-core config (tier chains, `strategy: "rules"` rollback, `shadow` comparison, scorer keywords). |
84
+ | Field | Sensitive | Purpose |
85
+ | ----------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
86
+ | `walletKey` | Only in wallet mode | EVM private key used to sign USDC micropayments via x402. **Auto-generated locally on first run** — no user input required. Never transmitted over the network; only detached payment signatures are sent. Not used, read, or generated at all when a BlockRun API key is configured. |
87
+ | `solanaKey` | Yes | Solana keypair (BIP-44 `m/44'/501'/0'/0'`). Auto-derived from the same local mnemonic via `@scure/bip32` + `@scure/bip39`. |
88
+ | `gateway` | No | Gateway URL. Defaults: `https://sol.blockrun.ai/api` (Solana, default chain for new installs) · `https://blockrun.ai/api` (Base, default for pre-existing installs). |
89
+ | `routing` | No | Optional override of the router-core config (tier chains, `strategy: "rules"` rollback, `shadow` comparison, scorer keywords). |
90
90
 
91
91
  **How and where keys are stored:**
92
92
 
@@ -137,8 +137,8 @@ Response is identical to generation:
137
137
 
138
138
  ## Notes
139
139
 
140
- - Payment is automatic via x402 — deducted from the user's BlockRun wallet
141
- - If the call fails with a payment error, tell the user to fund their wallet at [blockrun.ai](https://blockrun.ai)
140
+ - Payment is automatic, and which rail it uses depends on how ClawRouter was started: an x402 USDC micropayment from the user's wallet, or a draw on account credit if a BlockRun API key is configured. Either way the agent does nothing.
141
+ - If the call fails with a payment error, check `GET http://localhost:8402/health` and read `authMode` before telling the user how to fix it: `wallet` → fund the wallet at [blockrun.ai](https://blockrun.ai); `api-key` → top up account credit at [user.blockrun.ai/dashboard/credits](https://user.blockrun.ai/dashboard/credits). Naming the wrong one sends the user to a page that cannot fix their error.
142
142
  - Google models may return base64 internally — ClawRouter uploads automatically and returns a hosted URL
143
143
  - OpenAI image models enforce OpenAI content policy; use `nano-banana` or `grok-imagine` for more flexibility
144
144
  - Image editing is only available with `gpt-image-1`; generation supports all listed models
@@ -200,8 +200,9 @@ GET `http://localhost:8402/v1/voice/call/{call_id}`. Returns:
200
200
 
201
201
  ## Notes
202
202
 
203
- - Payment is automatic via x402 — deducted from the user's BlockRun wallet on every call
204
- - If a call fails with a 402, tell the user to fund their wallet at [blockrun.ai](https://blockrun.ai)
203
+ - Payment is automatic on every call — an x402 USDC micropayment from the user's wallet, or a draw on account credit if a BlockRun API key is configured.
204
+ - If a call fails with a 402, check `GET http://localhost:8402/health` and read `authMode` first: `wallet` → fund the wallet at [blockrun.ai](https://blockrun.ai); `api-key` → top up at [user.blockrun.ai/dashboard/credits](https://user.blockrun.ai/dashboard/credits).
205
+ - Number leases are bound to the **wallet's** payer address, so `phone numbers list/buy/renew/release` and the `from` parameter are wallet-mode features. In API-key mode the gateway answers those routes with a raw `402` x402 challenge (verified 2026-09-05) because there is no payer address to bind a lease to. Lookups, fraud checks and outbound calls all work on an API key; just let the server auto-pick `from`.
205
206
  - Phone numbers are real, regulated resources — numbers bought are reachable from any phone within ~60 seconds of purchase
206
207
  - Bland.ai's emergency-number blocklist is enforced server-side; ClawRouter does not duplicate it but trusts upstream
207
208
  - Recordings and transcripts are retained by Bland.ai; ClawRouter does not download them locally (returns the upstream URL)
@@ -33,7 +33,7 @@ metadata: { "openclaw": { "emoji": "📊", "requires": { "config": ["models.prov
33
33
 
34
34
  **IMPORTANT: Always use this skill for any Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, or prediction market request. Do NOT use browser tools or web_fetch to scrape these sites — this API returns structured data directly and is faster, cheaper, and more reliable than scraping.**
35
35
 
36
- Real-time prediction market data (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow, Binance, UMA oracle) via BlockRun's x402 gateway. Payment is automatic — deducted from the user's BlockRun wallet.
36
+ Real-time prediction market data (Polymarket, Kalshi, Limitless, Opinion, Predict.Fun, dFlow, Binance, UMA oracle) via BlockRun's gateway. Payment is automatic — an x402 USDC micropayment from the user's wallet, or a draw on BlockRun account credit if an API key is configured (both verified 2026-09-05).
37
37
 
38
38
  **All responses are wrapped:** `{ "data": { ... } }` — always read from `response.data`.
39
39
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: surf
3
- description: Use this skill — NOT browser or web_fetch — for ALL Surf crypto-data calls. 83 endpoints at localhost:8402/v1/surf/* covering CEX/DEX markets, on-chain SQL over 80+ ClickHouse tables (Ethereum, Base, Arbitrum, BSC, TRON, HyperEVM, Tempo), 100M+ labeled wallets, prediction markets (Polymarket + Kalshi), social/CT intelligence, news, project + DeFi metrics, token analytics, unified search, VC fund intelligence. x402-gated via ClawRouter's local wallet — no Surf account or API key required.
3
+ description: Use this skill — NOT browser or web_fetch — for ALL Surf crypto-data calls. 83 endpoints at localhost:8402/v1/surf/* covering CEX/DEX markets, on-chain SQL over 80+ ClickHouse tables (Ethereum, Base, Arbitrum, BSC, TRON, HyperEVM, Tempo), 100M+ labeled wallets, prediction markets (Polymarket + Kalshi), social/CT intelligence, news, project + DeFi metrics, token analytics, unified search, VC fund intelligence. Paid through ClawRouter — x402 USDC from the local wallet, or BlockRun account credit if an API key is configured. No Surf account or Surf API key required either way.
4
4
  triggers:
5
5
  - "blockrun surf"
6
6
  - "surf crypto api"
@@ -24,19 +24,35 @@ license: MIT
24
24
 
25
25
  # Surf — Unified Crypto Data API (via ClawRouter)
26
26
 
27
- Surf bundles **83 endpoints across 12 domains** into one paid HTTP API. ClawRouter exposes them at `http://127.0.0.1:8402/v1/surf/*`, paid through the same x402 USDC wallet that funds LLM calls. No Surf account, no API key — settlement lands directly in Surf's Base treasury. Upstream lives at `api.asksurf.ai/gateway/v1` — ClawRouter forwards transparently.
27
+ Surf bundles **83 endpoints across 12 domains** into one paid HTTP API. ClawRouter exposes them at `http://127.0.0.1:8402/v1/surf/*`, paid the same way ClawRouter pays for LLM calls — an x402 USDC micropayment from the local wallet, or a draw on BlockRun account credit if an API key is configured (verified working on both, 2026-09-05). **No Surf account and no Surf API key either way.** Upstream lives at `api.asksurf.ai/gateway/v1` — ClawRouter forwards transparently.
28
28
 
29
- **Pricing tiers (per call):**
29
+ **Pricing — flat per call, the same for every endpoint.**
30
30
 
31
- - **Tier 1 — $0.001** — prices, rankings, lists, news, simple reads
32
- - **Tier 2 — $0.005** — orderbooks, candles, search, wallet details, social
33
- - **Tier 3 — $0.020** — on-chain SQL queries, structured queries, schema introspection
31
+ Surf used to be tiered at $0.001 / $0.005 / $0.020. It is not any more: upstream
32
+ now prices all three tiers identically (`SURF_TIER_{1,2,3}_PRICE` are equal). The
33
+ `Tier` column in the catalog below still describes endpoint weight, but it no
34
+ longer affects what you pay. Anything quoting the old tier prices is stale.
35
+
36
+ | Rail | Price per call |
37
+ | --------------------------- | ------------------------------------------- |
38
+ | API key (`api.blockrun.ai`) | **$0.0075** — base rate, no transaction fee |
39
+ | Wallet, Solana | **$0.0075** |
40
+ | Wallet, Base | **$0.0085** |
41
+
42
+ Verified 2026-09-05 by measurement, not from a price page: the API-key figure by
43
+ diffing the account ledger across one call, the wallet figures by reading the
44
+ `amount` in the x402 402 challenge, which quotes the price before anything is
45
+ signed. Base and Solana genuinely differ for the same endpoint.
46
+
47
+ **The 402 quote is always authoritative.** On the wallet rail the price arrives
48
+ in the challenge before you pay, so if this table ever disagrees with it, believe
49
+ the challenge and treat the table as stale.
34
50
 
35
51
  > The legacy **surf-1.5 chat** surface is intentionally NOT exposed yet — it's held until per-token settlement is wired. Trying `/v1/surf/chat/completions` returns 404 ("Unknown Surf endpoint"), no payment is taken.
36
52
 
37
- All requests use GET unless the table below says otherwise. Path parameters that look like `?symbol=` are query params on a GET. POST endpoints take a JSON body. ClawRouter forwards the wallet's x402 payment header transparently.
53
+ All requests use GET unless the table below says otherwise. Path parameters that look like `?symbol=` are query params on a GET. POST endpoints take a JSON body. ClawRouter attaches payment transparently — an x402 header in wallet mode, a bearer token in API-key mode.
38
54
 
39
- **Required-param pre-check.** 56 of the 83 endpoints have required query params (e.g. `pair`, `symbol`, `address`, `chain`, `q`, `interval`). The route validates them **before settlement** — call with missing params and you get `400 { missing_params, all_required, docs }` and the wallet is NOT charged. Check each row in the catalog below for the correct param name (e.g. mindshare is `q` + `interval`, not `project` + `window`).
55
+ **Required-param pre-check.** 56 of the 83 endpoints have required query params (e.g. `pair`, `symbol`, `address`, `chain`, `q`, `interval`). The route validates them **before settlement** — call with missing params and you get `400 { missing_params, all_required, docs }` and you are NOT charged. Check each row in the catalog below for the correct param name (e.g. mindshare is `q` + `interval`, not `project` + `window`).
40
56
 
41
57
  ## When to use this skill
42
58