@blockrun/clawrouter 0.12.268 → 0.12.270

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,
@@ -34446,6 +34446,11 @@ function canonicalLimits(limits) {
34446
34446
  Object.fromEntries(Object.entries(limits).sort(([a], [b]) => a < b ? -1 : 1))
34447
34447
  );
34448
34448
  }
34449
+ function getSharedSpendControl() {
34450
+ const host = sharedControlHost();
34451
+ host.__clawrouterSharedSpendControl ??= new SpendControl();
34452
+ return host.__clawrouterSharedSpendControl;
34453
+ }
34449
34454
  function parseQuotedAmountUsd(selected) {
34450
34455
  const raw = selected.amount ?? selected.maxAmountRequired;
34451
34456
  if (typeof raw !== "string" || !CANONICAL_AMOUNT.test(raw)) {
@@ -34521,7 +34526,7 @@ function formatDuration(seconds) {
34521
34526
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
34522
34527
  }
34523
34528
  }
34524
- var WALLET_DIR, HOUR_MS, DAY_MS, CAIP2_BASE, CAIP2_SOLANA_MAINNET, PAYABLE_NETWORKS, POLICY_LISTS, ADDRESS_LISTS, EVM_ADDRESS, MalformedSpendPolicyError, SpendPolicyConflictError, FileSpendControlStorage, InMemorySpendControlStorage, RESERVATION_TTL_MS, SpendControl, SpendPolicyError, CANONICAL_AMOUNT;
34529
+ var WALLET_DIR, HOUR_MS, DAY_MS, CAIP2_BASE, CAIP2_SOLANA_MAINNET, PAYABLE_NETWORKS, POLICY_LISTS, ADDRESS_LISTS, EVM_ADDRESS, UnreadableSpendPolicyError, MalformedSpendPolicyError, SpendPolicyConflictError, FileSpendControlStorage, InMemorySpendControlStorage, RESERVATION_TTL_MS, SpendControl, sharedControlHost, SpendPolicyError, CANONICAL_AMOUNT;
34525
34530
  var init_spend_control = __esm({
34526
34531
  "src/spend-control.ts"() {
34527
34532
  "use strict";
@@ -34540,6 +34545,12 @@ var init_spend_control = __esm({
34540
34545
  ];
34541
34546
  ADDRESS_LISTS = ["allowedPayees", "blockedPayees", "allowedAssets"];
34542
34547
  EVM_ADDRESS = /^0x[0-9a-fA-F]{40}$/;
34548
+ UnreadableSpendPolicyError = class extends Error {
34549
+ constructor(cause) {
34550
+ super(`[ClawRouter] Failed to load spending data: ${cause}`);
34551
+ this.name = "UnreadableSpendPolicyError";
34552
+ }
34553
+ };
34543
34554
  MalformedSpendPolicyError = class extends Error {
34544
34555
  constructor(key2) {
34545
34556
  super(
@@ -34600,9 +34611,7 @@ var init_spend_control = __esm({
34600
34611
  if (err instanceof MalformedSpendPolicyError) {
34601
34612
  throw err;
34602
34613
  }
34603
- console.error(
34604
- `[ClawRouter] Failed to load spending data, starting fresh (any configured spend policy is NOT in effect until this file is repaired): ${err}`
34605
- );
34614
+ throw new UnreadableSpendPolicyError(err);
34606
34615
  }
34607
34616
  return null;
34608
34617
  }
@@ -34743,6 +34752,33 @@ var init_spend_control = __esm({
34743
34752
  getPolicyFileError() {
34744
34753
  return this.policyFileBroken;
34745
34754
  }
34755
+ /**
34756
+ * Re-read limits from storage, keeping this instance's history and open
34757
+ * reservations. Used on an in-process proxy restart so a hand-edit to
34758
+ * spending.json made while the process was running still applies, without
34759
+ * resetting the rolling windows. Fails closed exactly like the constructor:
34760
+ * a malformed file refuses every payment until repaired, and a repaired
34761
+ * file clears that refusal.
34762
+ */
34763
+ reloadLimits() {
34764
+ let data;
34765
+ try {
34766
+ data = this.storage.load();
34767
+ } catch (err) {
34768
+ if (err instanceof UnreadableSpendPolicyError) {
34769
+ console.error(`${err.message} \u2014 keeping the limits already in effect`);
34770
+ return;
34771
+ }
34772
+ if (!(err instanceof MalformedSpendPolicyError)) throw err;
34773
+ this.policyFileBroken = err.message;
34774
+ console.error(`[ClawRouter] ${err.message}`);
34775
+ return;
34776
+ }
34777
+ this.policyFileBroken = void 0;
34778
+ this.limits = data ? cloneLimits(data.limits) : {};
34779
+ this.diskLimits = cloneLimits(this.limits);
34780
+ this.limitsDirty = false;
34781
+ }
34746
34782
  check(estimatedCost2, counterparty) {
34747
34783
  if (this.policyFileBroken !== void 0) {
34748
34784
  return {
@@ -34987,6 +35023,16 @@ var init_spend_control = __esm({
34987
35023
  const records = [...this.history].reverse();
34988
35024
  return limit ? records.slice(0, limit) : records;
34989
35025
  }
35026
+ /**
35027
+ * Reset the session window. `sessionSpent`/`sessionCalls` are instance state
35028
+ * that is never persisted, so this used to happen implicitly: every
35029
+ * startProxy() built a fresh SpendControl. The process-wide ledger outlives
35030
+ * an in-process proxy restart, which would silently redefine `session` as
35031
+ * "since the gateway booted" — docs/configuration.md and the /policy help
35032
+ * both promise "session resets on restart". The restart path in index.ts
35033
+ * calls this to keep that promise. History and the rolling hourly/daily
35034
+ * windows are deliberately untouched.
35035
+ */
34990
35036
  resetSession() {
34991
35037
  this.sessionSpent = 0;
34992
35038
  this.sessionCalls = 0;
@@ -35008,10 +35054,13 @@ var init_spend_control = __esm({
35008
35054
  this.storage.saveLimits(cloneLimits(this.limits), cloneLimits(this.diskLimits));
35009
35055
  } catch (err) {
35010
35056
  if (err instanceof SpendPolicyConflictError) {
35011
- const current = this.storage.load();
35012
- this.limits = cloneLimits(current?.limits ?? {});
35013
- this.diskLimits = cloneLimits(this.limits);
35014
- this.limitsDirty = false;
35057
+ try {
35058
+ const current = this.storage.load();
35059
+ this.limits = cloneLimits(current?.limits ?? {});
35060
+ this.diskLimits = cloneLimits(this.limits);
35061
+ this.limitsDirty = false;
35062
+ } catch {
35063
+ }
35015
35064
  }
35016
35065
  throw err;
35017
35066
  }
@@ -35029,6 +35078,12 @@ var init_spend_control = __esm({
35029
35078
  try {
35030
35079
  data = this.storage.load();
35031
35080
  } catch (err) {
35081
+ if (err instanceof UnreadableSpendPolicyError) {
35082
+ console.error(
35083
+ `${err.message} \u2014 starting fresh (any configured spend policy is NOT in effect until this file is repaired)`
35084
+ );
35085
+ return;
35086
+ }
35032
35087
  if (!(err instanceof MalformedSpendPolicyError)) throw err;
35033
35088
  this.policyFileBroken = err.message;
35034
35089
  console.error(`[ClawRouter] ${err.message}`);
@@ -35045,6 +35100,7 @@ var init_spend_control = __esm({
35045
35100
  }
35046
35101
  }
35047
35102
  };
35103
+ sharedControlHost = () => process;
35048
35104
  SpendPolicyError = class extends Error {
35049
35105
  blockedBy;
35050
35106
  blockedByPolicy;
@@ -35082,6 +35138,21 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
35082
35138
  }
35083
35139
  }
35084
35140
  const cacheKey2 = `${urlPath}:${requestModel}`;
35141
+ const notifyAcceptedPayment = (response2, payload2) => {
35142
+ const settled = response2.headers.has("payment-response") || response2.headers.has("x-payment-response");
35143
+ if (!settled || !options?.onPayment) return;
35144
+ try {
35145
+ options.onPayment({
35146
+ model: requestModel,
35147
+ amount: payload2.accepted.amount,
35148
+ network: payload2.accepted.network
35149
+ });
35150
+ } catch (error) {
35151
+ console.error(
35152
+ `[ClawRouter] onPayment callback failed: ${error instanceof Error ? error.message : String(error)}`
35153
+ );
35154
+ }
35155
+ };
35085
35156
  const estimateMicros = () => {
35086
35157
  if (!options?.estimateAmount || !requestModel) return void 0;
35087
35158
  const est = options.estimateAmount(requestModel, bodyLength, maxTokens);
@@ -35102,6 +35173,7 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
35102
35173
  paymentInFlight = true;
35103
35174
  const response2 = await baseFetch(preAuthRequest);
35104
35175
  if (response2.status !== 402) {
35176
+ notifyAcceptedPayment(response2, payload2);
35105
35177
  return response2;
35106
35178
  }
35107
35179
  paymentInFlight = false;
@@ -35148,7 +35220,9 @@ function createPayFetchWithPreAuth(baseFetch, client, ttlMs = DEFAULT_TTL_MS, op
35148
35220
  for (const [key2, value] of Object.entries(paymentHeaders)) {
35149
35221
  clonedRequest.headers.set(key2, value);
35150
35222
  }
35151
- return baseFetch(clonedRequest);
35223
+ const paidResponse = await baseFetch(clonedRequest);
35224
+ notifyAcceptedPayment(paidResponse, payload);
35225
+ return paidResponse;
35152
35226
  };
35153
35227
  }
35154
35228
  var DEFAULT_TTL_MS;
@@ -59787,13 +59861,18 @@ async function loadPaymentChain() {
59787
59861
  const core = await readOptional(CORE_CHAIN_FILE);
59788
59862
  if (core === "solana") return "solana";
59789
59863
  if (core === "base") return "base";
59790
- try {
59791
- const content = (await readTextFile(CHAIN_FILE)).trim();
59792
- if (content === "solana") return "solana";
59793
- return "base";
59794
- } catch {
59795
- return "base";
59796
- }
59864
+ const legacy = await readOptional(CHAIN_FILE);
59865
+ if (legacy === "solana") return "solana";
59866
+ if (legacy === "base") return "base";
59867
+ return await hasExistingWallet() ? "base" : "solana";
59868
+ }
59869
+ async function hasExistingWallet() {
59870
+ if (process["env"].BLOCKRUN_WALLET_KEY?.trim()) return true;
59871
+ const [core, legacy] = await Promise.all([
59872
+ readOptional(CORE_WALLET_FILE),
59873
+ readOptional(WALLET_FILE)
59874
+ ]);
59875
+ return Boolean(core || legacy);
59797
59876
  }
59798
59877
  async function resolvePaymentChain() {
59799
59878
  if (process["env"].CLAWROUTER_PAYMENT_CHAIN === "solana") return "solana";
@@ -59907,7 +59986,7 @@ var init_api_key = __esm({
59907
59986
  HINTS = {
59908
59987
  401: `Check BLOCKRUN_API_KEY, or mint a new key at ${PORTAL_KEYS_URL}.`,
59909
59988
  402: `Top up your BlockRun credit at ${PORTAL_CREDITS_URL}.`,
59910
- 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").`
59989
+ 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".`
59911
59990
  };
59912
59991
  }
59913
59992
  });
@@ -91375,7 +91454,7 @@ async function startProxy(options) {
91375
91454
  if (x402 && account) {
91376
91455
  const evmPublicClient = createPublicClient({ chain: base, transport: http() });
91377
91456
  const evmSigner = toClientEvmSigner(account, evmPublicClient);
91378
- const spendControl = options.spendControl ?? new SpendControl();
91457
+ const spendControl = options.spendControl ?? getSharedSpendControl();
91379
91458
  registerSpendPolicyHook(x402, spendControl);
91380
91459
  registerExactEvmScheme(x402, { signer: evmSigner });
91381
91460
  }
@@ -91407,7 +91486,11 @@ async function startProxy(options) {
91407
91486
  // Per-request cost estimate so pre-auth is only reused when the cached
91408
91487
  // payment still covers the (possibly larger) request — BlockRun prices per
91409
91488
  // token, so one model can cost different amounts across requests.
91410
- estimateAmount
91489
+ estimateAmount,
91490
+ // Only the wallet rail settles per call. The API-key rail is billed
91491
+ // server-side against account credit, so there is no per-request
91492
+ // settlement for an observer to report.
91493
+ onPayment: options.onPayment
91411
91494
  });
91412
91495
  let balanceMonitor;
91413
91496
  if (options._balanceMonitorOverride) {
@@ -92287,7 +92370,8 @@ async function startProxy(options) {
92287
92370
  }
92288
92371
  return;
92289
92372
  }
92290
- if (req.url?.match(
92373
+ const isVerbatimGatewayPath = req.url === "/v1/messages" || (req.url?.startsWith("/v1/messages?") ?? false) || req.url === "/v1/audio/speech" || (req.url?.startsWith("/v1/audio/speech?") ?? false);
92374
+ if (isVerbatimGatewayPath || req.url?.match(
92291
92375
  /^\/v1\/(?:partner|pm|exa|modal|stocks|usstock|crypto|fx|commodity|phone|voice|surf)\//
92292
92376
  )) {
92293
92377
  try {
@@ -92319,6 +92403,19 @@ async function startProxy(options) {
92319
92403
  res.end(JSON.stringify({ error: "Not found" }));
92320
92404
  return;
92321
92405
  }
92406
+ if (!req.url.includes("/chat/completions")) {
92407
+ res.writeHead(404, { "Content-Type": "application/json" });
92408
+ res.end(
92409
+ JSON.stringify({
92410
+ error: {
92411
+ 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.`,
92412
+ type: "invalid_request_error",
92413
+ code: "unsupported_endpoint"
92414
+ }
92415
+ })
92416
+ );
92417
+ return;
92418
+ }
92322
92419
  try {
92323
92420
  await proxyRequest(
92324
92421
  req,
@@ -117475,9 +117572,7 @@ var init_client5 = __esm({
117475
117572
 
117476
117573
  // src/polymarket/spend-policy.ts
117477
117574
  function resolveSpendControl(deps) {
117478
- if (deps?.spendControl) return deps.spendControl;
117479
- defaultControl ??= new SpendControl();
117480
- return defaultControl;
117575
+ return deps?.spendControl ?? getSharedSpendControl();
117481
117576
  }
117482
117577
  function usdToMicros(usd) {
117483
117578
  return String(Math.ceil(usd * 1e6));
@@ -117494,7 +117589,6 @@ async function signUnderSpendPolicy(deps, quote, action, sign4) {
117494
117589
  throw err;
117495
117590
  }
117496
117591
  }
117497
- var defaultControl;
117498
117592
  var init_spend_policy = __esm({
117499
117593
  "src/polymarket/spend-policy.ts"() {
117500
117594
  "use strict";
@@ -226859,7 +226953,7 @@ var init_fund = __esm({
226859
226953
  });
226860
226954
 
226861
226955
  // src/polymarket/tool.ts
226862
- function buildPolymarketTool() {
226956
+ function buildPolymarketTool(deps) {
226863
226957
  return {
226864
226958
  name: "blockrun_polymarket",
226865
226959
  description: DESCRIPTION,
@@ -226944,26 +227038,32 @@ function buildPolymarketTool() {
226944
227038
  result = await runSetup({ confirm });
226945
227039
  break;
226946
227040
  case "fund":
226947
- result = await fundVault({
226948
- amount_usd: params.amount_usd,
226949
- confirm
226950
- });
227041
+ result = await fundVault(
227042
+ {
227043
+ amount_usd: params.amount_usd,
227044
+ confirm
227045
+ },
227046
+ deps
227047
+ );
226951
227048
  break;
226952
227049
  case "buy":
226953
227050
  case "sell":
226954
- result = await executeTrade({
226955
- action,
226956
- token_id: params.token_id,
226957
- condition_id: params.condition_id,
226958
- outcome: params.outcome,
226959
- price: params.price,
226960
- size: params.size,
226961
- amount_usd: params.amount_usd,
226962
- order_type: params.order_type,
226963
- expires_at: params.expires_at,
226964
- post_only: params.post_only,
226965
- confirm
226966
- });
227051
+ result = await executeTrade(
227052
+ {
227053
+ action,
227054
+ token_id: params.token_id,
227055
+ condition_id: params.condition_id,
227056
+ outcome: params.outcome,
227057
+ price: params.price,
227058
+ size: params.size,
227059
+ amount_usd: params.amount_usd,
227060
+ order_type: params.order_type,
227061
+ expires_at: params.expires_at,
227062
+ post_only: params.post_only,
227063
+ confirm
227064
+ },
227065
+ deps
227066
+ );
226967
227067
  break;
226968
227068
  case "orders":
226969
227069
  result = await listOpenOrders({
@@ -226980,17 +227080,23 @@ function buildPolymarketTool() {
226980
227080
  result = await listPositions();
226981
227081
  break;
226982
227082
  case "redeem":
226983
- result = await redeemPosition({
226984
- condition_id: params.condition_id,
226985
- confirm
226986
- });
227083
+ result = await redeemPosition(
227084
+ {
227085
+ condition_id: params.condition_id,
227086
+ confirm
227087
+ },
227088
+ deps
227089
+ );
226987
227090
  break;
226988
227091
  case "withdraw":
226989
- result = await withdrawFunds({
226990
- amount_usd: params.amount_usd,
226991
- to_address: params.to_address,
226992
- confirm
226993
- });
227092
+ result = await withdrawFunds(
227093
+ {
227094
+ amount_usd: params.amount_usd,
227095
+ to_address: params.to_address,
227096
+ confirm
227097
+ },
227098
+ deps
227099
+ );
226994
227100
  break;
226995
227101
  default:
226996
227102
  throw new Error(`Unknown blockrun_polymarket action: ${action}`);
@@ -228110,12 +228216,18 @@ async function startProxyInBackground(api, startupGeneration) {
228110
228216
  `Cost cap: $${maxCostPerRunUsd.toFixed(2)} per session (mode: ${maxCostPerRunMode})`
228111
228217
  );
228112
228218
  }
228219
+ const sharedControl = getSharedSpendControl();
228220
+ sharedControl.reloadLimits();
228221
+ sharedControl.resetSession();
228113
228222
  const proxy = await startProxy({
228114
228223
  ...apiKey ? { apiKey } : { wallet },
228115
228224
  routingConfig,
228116
228225
  maxCostPerRunUsd,
228117
228226
  maxCostPerRunMode,
228118
- spendControl: liveSpendControl = new SpendControl(),
228227
+ // The process-wide ledger: the polymarket tool and the /policy command
228228
+ // registered below use the same instance, so hourly/daily/session windows
228229
+ // cover every surface and a /policy write reaches the live signer.
228230
+ spendControl: sharedControl,
228119
228231
  onReady: (port) => {
228120
228232
  api.logger.info(`BlockRun ${apiKey ? "API-key" : "x402"} proxy listening on port ${port}`);
228121
228233
  },
@@ -228858,7 +228970,7 @@ function createWalletCommand(api) {
228858
228970
  }
228859
228971
  };
228860
228972
  }
228861
- var activeProxyHandle, liveSpendControl, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, plugin, index_default2;
228973
+ var activeProxyHandle, pendingConfiguredStartupApi, IMAGE_DIR2, AUDIO_DIR2, VIDEO_DIR2, plugin, index_default2;
228862
228974
  var init_index = __esm({
228863
228975
  "src/index.ts"() {
228864
228976
  init_provider();
@@ -228876,10 +228988,10 @@ var init_index = __esm({
228876
228988
  init_stats();
228877
228989
  init_partners();
228878
228990
  init_tool();
228991
+ init_spend_control();
228879
228992
  init_stats2();
228880
228993
  init_exclude();
228881
228994
  init_policy();
228882
- init_spend_control();
228883
228995
  init_mcp_config();
228884
228996
  init_openclaw_plugin_config();
228885
228997
  init_openclaw_plugin_config();
@@ -228901,7 +229013,6 @@ var init_index = __esm({
228901
229013
  init_response_cache();
228902
229014
  init_partners();
228903
229015
  activeProxyHandle = null;
228904
- liveSpendControl = null;
228905
229016
  pendingConfiguredStartupApi = null;
228906
229017
  IMAGE_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "images");
228907
229018
  AUDIO_DIR2 = join17(homedir14(), ".openclaw", "blockrun", "audio");
@@ -229217,7 +229328,7 @@ ${errText}`
229217
229328
  api.registerCommand(createExcludeCommand());
229218
229329
  api.registerCommand(
229219
229330
  createPolicyCommand({
229220
- liveControl: () => activeProxyHandle ? liveSpendControl ?? void 0 : void 0
229331
+ liveControl: () => process.__clawrouterProxyStarted ? getSharedSpendControl() : void 0
229221
229332
  })
229222
229333
  );
229223
229334
  if (shouldLogRegistration) {
@@ -229246,7 +229357,15 @@ ${errText}`
229246
229357
  });
229247
229358
  if (!isGatewayMode()) {
229248
229359
  if (shouldLogRegistration) {
229249
- resolveOrGenerateWalletKey().then(({ address: address2, source }) => {
229360
+ void (async () => {
229361
+ const keyResolution = await resolveApiKey().catch(() => void 0);
229362
+ if (keyResolution) {
229363
+ api.logger.info(
229364
+ `Using BlockRun API key ${maskApiKey(keyResolution.key)} (from ${keyResolution.source}) \u2014 billing account credit, no wallet`
229365
+ );
229366
+ return;
229367
+ }
229368
+ const { address: address2, source } = await resolveOrGenerateWalletKey();
229250
229369
  if (source === "generated") {
229251
229370
  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`);
229252
229371
  api.logger.warn(` NEW WALLET GENERATED \u2014 BACK UP YOUR KEY NOW!`);
@@ -229263,7 +229382,7 @@ ${errText}`
229263
229382
  } else {
229264
229383
  api.logger.info(`Using wallet from BLOCKRUN_WALLET_KEY: ${address2}`);
229265
229384
  }
229266
- }).catch((err) => {
229385
+ })().catch((err) => {
229267
229386
  api.logger.warn(
229268
229387
  `Failed to initialize wallet: ${err instanceof Error ? err.message : String(err)}`
229269
229388
  );
@@ -229415,6 +229534,7 @@ export {
229415
229534
  SolanaBalanceMonitor,
229416
229535
  SpendControl,
229417
229536
  SpendPolicyError,
229537
+ UnreadableSpendPolicyError,
229418
229538
  VISIBLE_OPENCLAW_MODELS,
229419
229539
  blockrunProvider,
229420
229540
  buildImageGenerationProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/clawrouter",
3
- "version": "0.12.268",
3
+ "version": "0.12.270",
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,7 +24,7 @@ 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
29
  **Pricing tiers (per call):**
30
30
 
@@ -34,9 +34,9 @@ Surf bundles **83 endpoints across 12 domains** into one paid HTTP API. ClawRout
34
34
 
35
35
  > 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
36
 
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.
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 attaches payment transparently — an x402 header in wallet mode, a bearer token in API-key mode.
38
38
 
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`).
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 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
40
 
41
41
  ## When to use this skill
42
42