@blockrun/clawrouter 0.12.270 → 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/README.md +14 -6
- package/dist/cli.js +145 -11
- package/dist/index.d.ts +17 -0
- package/dist/index.js +138 -11
- package/docs/configuration.md +17 -1
- package/package.json +1 -1
- package/skills/surf/SKILL.md +21 -5
package/README.md
CHANGED
|
@@ -444,11 +444,19 @@ Surf is BlockRun's unified crypto data API — **84 endpoints across 13 domains*
|
|
|
444
444
|
|
|
445
445
|
ClawRouter ships Surf as a **skill, not as typed wrappers**. The proxy whitelists `/v1/surf/*` so any call through the local proxy is paid x402 from the same wallet; the agent reads `skills/surf/SKILL.md` for the endpoint catalog and crafts the HTTP call. No `blockrun_surf_*` tool definitions to maintain; a new Surf endpoint requires zero ClawRouter release.
|
|
446
446
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
|
447
|
+
**Flat per-call pricing** — every Surf endpoint costs the same. The old
|
|
448
|
+
$0.001/$0.005/$0.020 tiers no longer apply; upstream prices all three tiers
|
|
449
|
+
identically, and the tier now describes only endpoint weight.
|
|
450
|
+
|
|
451
|
+
| Rail | Price per call |
|
|
452
|
+
| --------------------------- | ---------------------------------- |
|
|
453
|
+
| API key (`api.blockrun.ai`) | **$0.0075** (base rate, no tx fee) |
|
|
454
|
+
| Wallet — Solana | **$0.0075** |
|
|
455
|
+
| Wallet — Base | **$0.0085** |
|
|
456
|
+
|
|
457
|
+
Measured 2026-09-05: the API-key rate from the account ledger, the wallet rates
|
|
458
|
+
from the x402 challenge, which quotes the price before anything is signed. On the
|
|
459
|
+
wallet rail that quote is always authoritative.
|
|
452
460
|
|
|
453
461
|
**Usage (HTTP):**
|
|
454
462
|
|
|
@@ -729,7 +737,7 @@ For basic usage, no configuration needed. For advanced options:
|
|
|
729
737
|
| `BLOCKRUN_PROXY_PORT` | `8402` | Local proxy port |
|
|
730
738
|
| `CLAWROUTER_DISABLED` | `false` | Disable smart routing |
|
|
731
739
|
| `CLAWROUTER_DEBUG_HEADERS` | `on` | Set to `off` to suppress `x-clawrouter-*` debug response headers |
|
|
732
|
-
| `CLAWROUTER_SOLANA_RPC_URL` | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint
|
|
740
|
+
| `CLAWROUTER_SOLANA_RPC_URL` | `https://api.mainnet-beta.solana.com` | Solana RPC endpoint — balance checks and payment signing |
|
|
733
741
|
|
|
734
742
|
**Full reference:** [docs/configuration.md](docs/configuration.md)
|
|
735
743
|
|
package/dist/cli.js
CHANGED
|
@@ -40713,6 +40713,18 @@ function isBalanceError(error) {
|
|
|
40713
40713
|
function isRpcError(error) {
|
|
40714
40714
|
return error instanceof Error && error.code === "RPC_ERROR";
|
|
40715
40715
|
}
|
|
40716
|
+
function describeFetchError(error) {
|
|
40717
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
40718
|
+
const cause = error?.cause;
|
|
40719
|
+
if (!(cause instanceof Error)) return message;
|
|
40720
|
+
const detail = cause;
|
|
40721
|
+
const host = detail.hostname || detail.address;
|
|
40722
|
+
const where = host && detail.port ? `${host}:${detail.port}` : host;
|
|
40723
|
+
const parts = [detail.code, where, detail.code ? void 0 : detail.message].filter(
|
|
40724
|
+
(part) => typeof part === "string" && part !== ""
|
|
40725
|
+
);
|
|
40726
|
+
return parts.length > 0 ? `${message} (${parts.join(" ")})` : message;
|
|
40727
|
+
}
|
|
40716
40728
|
var InsufficientFundsError2, EmptyWalletError, RpcError2;
|
|
40717
40729
|
var init_errors5 = __esm({
|
|
40718
40730
|
"src/errors.ts"() {
|
|
@@ -60152,6 +60164,40 @@ async function clearApiKey() {
|
|
|
60152
60164
|
}
|
|
60153
60165
|
return { removed, envStillSet: Boolean(process["env"].BLOCKRUN_API_KEY?.trim()) };
|
|
60154
60166
|
}
|
|
60167
|
+
async function fetchCreditBalance(apiKey, timeoutMs = 5e3) {
|
|
60168
|
+
try {
|
|
60169
|
+
const controller = new AbortController();
|
|
60170
|
+
const timer2 = setTimeout(() => controller.abort(), timeoutMs);
|
|
60171
|
+
const res = await fetch(`${BLOCKRUN_API_KEY_API}/v1/credits`, {
|
|
60172
|
+
headers: { authorization: `Bearer ${apiKey}` },
|
|
60173
|
+
signal: controller.signal
|
|
60174
|
+
});
|
|
60175
|
+
clearTimeout(timer2);
|
|
60176
|
+
if (!res.ok) return void 0;
|
|
60177
|
+
const b = await res.json();
|
|
60178
|
+
const num2 = (v) => typeof v === "number" ? v : null;
|
|
60179
|
+
return {
|
|
60180
|
+
accountId: typeof b.account_id === "string" ? b.account_id : void 0,
|
|
60181
|
+
billingMode: typeof b.billing_mode === "string" ? b.billing_mode : void 0,
|
|
60182
|
+
currency: typeof b.currency === "string" ? b.currency : "USD",
|
|
60183
|
+
grantedUsd: num2(b.granted_usd),
|
|
60184
|
+
spentUsd: num2(b.spent_usd),
|
|
60185
|
+
remainingUsd: num2(b.remaining_usd),
|
|
60186
|
+
blocked: b.blocked === true,
|
|
60187
|
+
blockedReason: typeof b.blocked_reason === "string" ? b.blocked_reason : void 0
|
|
60188
|
+
};
|
|
60189
|
+
} catch {
|
|
60190
|
+
return void 0;
|
|
60191
|
+
}
|
|
60192
|
+
}
|
|
60193
|
+
function formatCreditBalance(b) {
|
|
60194
|
+
const money = (v) => v === null ? void 0 : `$${v.toFixed(v < 0.01 && v > 0 ? 6 : 2)}`;
|
|
60195
|
+
const remaining = money(b.remainingUsd);
|
|
60196
|
+
const spent = money(b.spentUsd);
|
|
60197
|
+
if (remaining) return `${remaining} remaining${spent ? ` (spent ${spent})` : ""}`;
|
|
60198
|
+
const mode = b.billingMode === "ungated" ? "no prepaid limit" : b.billingMode ?? "unknown";
|
|
60199
|
+
return `${spent ? `spent ${spent}` : "unknown"} \u2014 ${mode}`;
|
|
60200
|
+
}
|
|
60155
60201
|
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
60156
60202
|
return async (input, init2) => {
|
|
60157
60203
|
const headers = new Headers(init2?.headers);
|
|
@@ -90673,6 +90719,24 @@ var init_client3 = __esm({
|
|
|
90673
90719
|
}
|
|
90674
90720
|
});
|
|
90675
90721
|
|
|
90722
|
+
// node_modules/@x402/svm/dist/esm/v1/index.mjs
|
|
90723
|
+
var v1_exports = {};
|
|
90724
|
+
__export(v1_exports, {
|
|
90725
|
+
ExactSvmSchemeV1: () => ExactSvmSchemeV1,
|
|
90726
|
+
NETWORKS: () => NETWORKS2
|
|
90727
|
+
});
|
|
90728
|
+
var init_v1 = __esm({
|
|
90729
|
+
"node_modules/@x402/svm/dist/esm/v1/index.mjs"() {
|
|
90730
|
+
"use strict";
|
|
90731
|
+
init_chunk_WWACQNRQ();
|
|
90732
|
+
init_chunk_MNNIECS4();
|
|
90733
|
+
init_chunk_WIPN332D();
|
|
90734
|
+
init_chunk_FVTMOTG6();
|
|
90735
|
+
init_chunk_FJ6REVHH();
|
|
90736
|
+
init_chunk_QEBTC3LJ();
|
|
90737
|
+
}
|
|
90738
|
+
});
|
|
90739
|
+
|
|
90676
90740
|
// src/proxy.ts
|
|
90677
90741
|
import { AsyncLocalStorage } from "async_hooks";
|
|
90678
90742
|
import { createHmac } from "crypto";
|
|
@@ -91427,6 +91491,23 @@ function estimateImageCost(model, size5, n = 1) {
|
|
|
91427
91491
|
const pricePerImage = sizePrice ?? pricing.default;
|
|
91428
91492
|
return pricePerImage * n * 1.05;
|
|
91429
91493
|
}
|
|
91494
|
+
function blockrunRequestId(response) {
|
|
91495
|
+
if (!response) return void 0;
|
|
91496
|
+
for (const name of ["x-blockrun-request-id", "x-request-id", "request-id"]) {
|
|
91497
|
+
const value = response.headers.get(name)?.trim();
|
|
91498
|
+
if (value) return value;
|
|
91499
|
+
}
|
|
91500
|
+
return void 0;
|
|
91501
|
+
}
|
|
91502
|
+
function gatewaySettledCostUsd(response) {
|
|
91503
|
+
const raw = response?.headers.get("x-blockrun-cost-usd");
|
|
91504
|
+
if (raw === null || raw === void 0) return void 0;
|
|
91505
|
+
const trimmed = raw.trim();
|
|
91506
|
+
if (trimmed === "") return void 0;
|
|
91507
|
+
const value = Number(trimmed);
|
|
91508
|
+
if (!Number.isFinite(value) || value < 0) return void 0;
|
|
91509
|
+
return value;
|
|
91510
|
+
}
|
|
91430
91511
|
async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
|
|
91431
91512
|
const startTime = Date.now();
|
|
91432
91513
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
@@ -91476,8 +91557,9 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91476
91557
|
res.end();
|
|
91477
91558
|
const latencyMs = Date.now() - startTime;
|
|
91478
91559
|
console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
|
|
91479
|
-
const
|
|
91480
|
-
const
|
|
91560
|
+
const settledCostUsd = gatewaySettledCostUsd(upstream);
|
|
91561
|
+
const paidAmount = settledCostUsd ?? getActualPaymentUsd();
|
|
91562
|
+
const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
|
|
91481
91563
|
paidAmount,
|
|
91482
91564
|
isPhone,
|
|
91483
91565
|
upstreamStatus: upstream.status,
|
|
@@ -91486,6 +91568,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91486
91568
|
});
|
|
91487
91569
|
logUsage({
|
|
91488
91570
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91571
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91489
91572
|
model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
|
|
91490
91573
|
tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
|
|
91491
91574
|
cost: requestCost,
|
|
@@ -91677,8 +91760,20 @@ async function startProxy(options) {
|
|
|
91677
91760
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91678
91761
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
91679
91762
|
solanaAddress = solanaSigner.address;
|
|
91763
|
+
const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
|
|
91680
91764
|
registerExactSvmScheme2(x402, { signer: solanaSigner });
|
|
91765
|
+
if (solanaRpcUrl) {
|
|
91766
|
+
const { ExactSvmScheme: ExactSvmScheme2 } = await Promise.resolve().then(() => (init_client3(), client_exports));
|
|
91767
|
+
const { ExactSvmSchemeV1: ExactSvmSchemeV12, NETWORKS: SVM_V1_NETWORKS } = await Promise.resolve().then(() => (init_v1(), v1_exports));
|
|
91768
|
+
x402.register("solana:*", new ExactSvmScheme2(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91769
|
+
for (const network of SVM_V1_NETWORKS) {
|
|
91770
|
+
x402.registerV1(network, new ExactSvmSchemeV12(solanaSigner, { rpcUrl: solanaRpcUrl }));
|
|
91771
|
+
}
|
|
91772
|
+
}
|
|
91681
91773
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91774
|
+
if (solanaRpcUrl) {
|
|
91775
|
+
console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
|
|
91776
|
+
}
|
|
91682
91777
|
}
|
|
91683
91778
|
x402?.onAfterPaymentCreation(async (context) => {
|
|
91684
91779
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
@@ -91778,7 +91873,15 @@ async function startProxy(options) {
|
|
|
91778
91873
|
)
|
|
91779
91874
|
]);
|
|
91780
91875
|
if (authMode === "api-key") {
|
|
91781
|
-
|
|
91876
|
+
const credit = await fetchCreditBalance(apiKey, BALANCE_CHECK_TIMEOUT_MS);
|
|
91877
|
+
response.balance = credit?.remainingUsd ?? null;
|
|
91878
|
+
response.creditSpent = credit?.spentUsd ?? null;
|
|
91879
|
+
response.creditGranted = credit?.grantedUsd ?? null;
|
|
91880
|
+
response.billingMode = credit?.billingMode ?? null;
|
|
91881
|
+
response.creditSummary = credit ? formatCreditBalance(credit) : null;
|
|
91882
|
+
response.blocked = credit?.blocked ?? false;
|
|
91883
|
+
if (credit?.blockedReason) response.blockedReason = credit.blockedReason;
|
|
91884
|
+
response.isEmpty = credit?.remainingUsd !== null && (credit?.remainingUsd ?? 1) <= 0;
|
|
91782
91885
|
response.billing = "BlockRun account credit";
|
|
91783
91886
|
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91784
91887
|
} else {
|
|
@@ -92883,7 +92986,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92883
92986
|
errorBody,
|
|
92884
92987
|
errorStatus: response.status,
|
|
92885
92988
|
isProviderError: category !== null,
|
|
92886
|
-
errorCategory: category ?? void 0
|
|
92989
|
+
errorCategory: category ?? void 0,
|
|
92990
|
+
upstreamRequestId: blockrunRequestId(response)
|
|
92887
92991
|
};
|
|
92888
92992
|
}
|
|
92889
92993
|
const contentType = response.headers.get("content-type") || "";
|
|
@@ -92920,9 +93024,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92920
93024
|
errorCategory: "payment_error"
|
|
92921
93025
|
};
|
|
92922
93026
|
}
|
|
93027
|
+
const networkMsg = describeFetchError(err);
|
|
93028
|
+
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" : "";
|
|
93029
|
+
console.error(`[ClawRouter] ${modelId} network error: ${networkMsg}${solanaSigningHint}`);
|
|
92923
93030
|
return {
|
|
92924
93031
|
success: false,
|
|
92925
|
-
errorBody:
|
|
93032
|
+
errorBody: `${networkMsg}${solanaSigningHint}`,
|
|
92926
93033
|
errorStatus: 500,
|
|
92927
93034
|
isProviderError: true
|
|
92928
93035
|
// Network errors are retryable
|
|
@@ -92955,6 +93062,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
92955
93062
|
let accumulatedContent = "";
|
|
92956
93063
|
let responseInputTokens;
|
|
92957
93064
|
let responseOutputTokens;
|
|
93065
|
+
let upstreamRequestId;
|
|
92958
93066
|
let requestHadError = false;
|
|
92959
93067
|
let requestSummaryForStore = "";
|
|
92960
93068
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
@@ -94361,7 +94469,8 @@ data: [DONE]
|
|
|
94361
94469
|
}
|
|
94362
94470
|
lastError = {
|
|
94363
94471
|
body: result.errorBody || "Unknown error",
|
|
94364
|
-
status: result.errorStatus || 500
|
|
94472
|
+
status: result.errorStatus || 500,
|
|
94473
|
+
requestId: result.upstreamRequestId
|
|
94365
94474
|
};
|
|
94366
94475
|
failedAttempts.push({
|
|
94367
94476
|
model: tryModel,
|
|
@@ -94433,7 +94542,8 @@ data: [DONE]
|
|
|
94433
94542
|
}
|
|
94434
94543
|
lastError = {
|
|
94435
94544
|
body: retryResult.errorBody || lastError?.body || "Unknown error",
|
|
94436
|
-
status: retryResult.errorStatus || lastError?.status || 500
|
|
94545
|
+
status: retryResult.errorStatus || lastError?.status || 500,
|
|
94546
|
+
requestId: retryResult.upstreamRequestId ?? lastError?.requestId
|
|
94437
94547
|
};
|
|
94438
94548
|
failedAttempts.push({
|
|
94439
94549
|
model: tryModel,
|
|
@@ -94575,10 +94685,13 @@ data: [DONE]
|
|
|
94575
94685
|
}
|
|
94576
94686
|
}
|
|
94577
94687
|
}
|
|
94688
|
+
upstreamRequestId = blockrunRequestId(upstream);
|
|
94578
94689
|
if (!upstream) {
|
|
94579
94690
|
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
94580
94691
|
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
94581
|
-
console.log(
|
|
94692
|
+
console.log(
|
|
94693
|
+
`[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
|
|
94694
|
+
);
|
|
94582
94695
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
94583
94696
|
const errStatus = lastError?.status || 502;
|
|
94584
94697
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
@@ -94616,7 +94729,8 @@ data: [DONE]
|
|
|
94616
94729
|
res.writeHead(errStatus, {
|
|
94617
94730
|
"Content-Type": "application/json",
|
|
94618
94731
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
94619
|
-
"x-context-limit-kb": String(CONTEXT_LIMIT_KB)
|
|
94732
|
+
"x-context-limit-kb": String(CONTEXT_LIMIT_KB),
|
|
94733
|
+
...lastError?.requestId ? { "x-blockrun-request-id": lastError.requestId } : {}
|
|
94620
94734
|
});
|
|
94621
94735
|
res.end(transformedErr);
|
|
94622
94736
|
deduplicator.complete(dedupKey, {
|
|
@@ -95080,12 +95194,24 @@ data: [DONE]
|
|
|
95080
95194
|
maxTokens,
|
|
95081
95195
|
routingProfile ?? void 0
|
|
95082
95196
|
);
|
|
95083
|
-
|
|
95197
|
+
const apiKeyPricing = options.apiKey ? routerOpts.modelPricing.get(logModel) : void 0;
|
|
95198
|
+
if (FREE_MODELS.has(logModel)) {
|
|
95199
|
+
logCost = 0;
|
|
95200
|
+
logSavings = 1;
|
|
95201
|
+
} else if (apiKeyPricing) {
|
|
95202
|
+
const inTokens = responseInputTokens ?? chargedInputTokens;
|
|
95203
|
+
const outTokens = responseOutputTokens ?? 0;
|
|
95204
|
+
logCost = (inTokens * (apiKeyPricing.inputPrice ?? 0) + outTokens * (apiKeyPricing.outputPrice ?? 0)) / 1e6;
|
|
95205
|
+
logSavings = costs.baselineCost > 0 ? Math.max(0, (costs.baselineCost - logCost) / costs.baselineCost) : 0;
|
|
95206
|
+
} else {
|
|
95207
|
+
logCost = costs.costEstimate;
|
|
95208
|
+
logSavings = costs.savings;
|
|
95209
|
+
}
|
|
95084
95210
|
logBaseline = costs.baselineCost;
|
|
95085
|
-
logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
|
|
95086
95211
|
}
|
|
95087
95212
|
const entry = {
|
|
95088
95213
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95214
|
+
...upstreamRequestId ? { requestId: upstreamRequestId } : {},
|
|
95089
95215
|
model: logModel,
|
|
95090
95216
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
95091
95217
|
cost: logCost,
|
|
@@ -95125,6 +95251,7 @@ var init_proxy = __esm({
|
|
|
95125
95251
|
init_api_key();
|
|
95126
95252
|
init_spend_control();
|
|
95127
95253
|
init_compression();
|
|
95254
|
+
init_errors5();
|
|
95128
95255
|
init_version4();
|
|
95129
95256
|
init_session();
|
|
95130
95257
|
init_updater();
|
|
@@ -230609,6 +230736,12 @@ ClawRouter Status (port ${port})
|
|
|
230609
230736
|
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230610
230737
|
console.log(` Gateway: ${data.gateway}`);
|
|
230611
230738
|
console.log(` Billing: account credit \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230739
|
+
if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
|
|
230740
|
+
if (data.blocked) {
|
|
230741
|
+
console.log(` \u26A0 Account is blocked${data.blockedReason ? `: ${data.blockedReason}` : ""}`);
|
|
230742
|
+
} else if (data.isEmpty) {
|
|
230743
|
+
console.log(` \u26A0 Credit exhausted \u2014 top up at ${PORTAL_CREDITS_URL}`);
|
|
230744
|
+
}
|
|
230612
230745
|
} else {
|
|
230613
230746
|
console.log(` Auth: wallet (x402)`);
|
|
230614
230747
|
console.log(` Payment Chain: ${data.paymentChain}`);
|
|
@@ -230636,6 +230769,7 @@ ClawRouter Account
|
|
|
230636
230769
|
console.log(` Auth: BlockRun API key ${data.apiKey}`);
|
|
230637
230770
|
console.log(` Gateway: ${data.gateway}`);
|
|
230638
230771
|
console.log(` Billing: account credit (no wallet, no chain)`);
|
|
230772
|
+
if (data.creditSummary) console.log(` Credit: ${data.creditSummary}`);
|
|
230639
230773
|
console.log(` Top up: ${PORTAL_CREDITS_URL}`);
|
|
230640
230774
|
console.log(` Usage: ${PORTAL_URL}/dashboard`);
|
|
230641
230775
|
console.log();
|
package/dist/index.d.ts
CHANGED
|
@@ -1416,6 +1416,23 @@ type UsageEntry = {
|
|
|
1416
1416
|
partnerId?: string;
|
|
1417
1417
|
/** Partner service name (e.g., "BlockRun") — only set for partner API calls */
|
|
1418
1418
|
service?: string;
|
|
1419
|
+
/**
|
|
1420
|
+
* The gateway's own id for this request, from the `x-blockrun-request-id`
|
|
1421
|
+
* response header. Absent for requests that never reached the gateway (a
|
|
1422
|
+
* cache hit, a local refusal, an aborted call).
|
|
1423
|
+
*
|
|
1424
|
+
* This is the JOIN KEY for billing reconciliation. Every `cost` in this file
|
|
1425
|
+
* is a LOCAL estimate computed from our copy of the price table, so it drifts
|
|
1426
|
+
* whenever that copy goes stale — six prices were wrong until v0.12.270, and
|
|
1427
|
+
* `/stats` had been reporting the wrong money the whole time. Recording the
|
|
1428
|
+
* gateway's id is what will let us diff this journal line-by-line against a
|
|
1429
|
+
* server-side ledger and show what was actually charged instead of what we
|
|
1430
|
+
* guessed. It cannot be backfilled: a call whose id we did not record is
|
|
1431
|
+
* unreconcilable forever, which is why this is captured before the ledger API
|
|
1432
|
+
* that will consume it exists. It is also the id support needs to trace one
|
|
1433
|
+
* failed call.
|
|
1434
|
+
*/
|
|
1435
|
+
requestId?: string;
|
|
1419
1436
|
};
|
|
1420
1437
|
/**
|
|
1421
1438
|
* Log a usage entry as a JSON line.
|
package/dist/index.js
CHANGED
|
@@ -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"() {
|
|
@@ -59939,6 +59951,40 @@ async function resolveApiKey() {
|
|
|
59939
59951
|
}
|
|
59940
59952
|
return void 0;
|
|
59941
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
|
+
}
|
|
59942
59988
|
function createApiKeyFetch(apiKey, baseFetch = fetch) {
|
|
59943
59989
|
return async (input, init2) => {
|
|
59944
59990
|
const headers = new Headers(init2?.headers);
|
|
@@ -90460,6 +90506,24 @@ var init_client3 = __esm({
|
|
|
90460
90506
|
}
|
|
90461
90507
|
});
|
|
90462
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
|
+
|
|
90463
90527
|
// src/proxy.ts
|
|
90464
90528
|
import { AsyncLocalStorage } from "async_hooks";
|
|
90465
90529
|
import { createHmac } from "crypto";
|
|
@@ -91214,6 +91278,23 @@ function estimateImageCost(model, size5, n = 1) {
|
|
|
91214
91278
|
const pricePerImage = sizePrice ?? pricing.default;
|
|
91215
91279
|
return pricePerImage * n * 1.05;
|
|
91216
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
|
+
}
|
|
91217
91298
|
async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPaymentUsd) {
|
|
91218
91299
|
const startTime = Date.now();
|
|
91219
91300
|
const upstreamUrl = `${apiBase}${req.url}`;
|
|
@@ -91263,8 +91344,9 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91263
91344
|
res.end();
|
|
91264
91345
|
const latencyMs = Date.now() - startTime;
|
|
91265
91346
|
console.log(`[ClawRouter] ${requestLabel} response: ${upstream.status} (${latencyMs}ms)`);
|
|
91266
|
-
const
|
|
91267
|
-
const
|
|
91347
|
+
const settledCostUsd = gatewaySettledCostUsd(upstream);
|
|
91348
|
+
const paidAmount = settledCostUsd ?? getActualPaymentUsd();
|
|
91349
|
+
const requestCost = settledCostUsd !== void 0 ? settledCostUsd : resolvePhoneTelemetryCost({
|
|
91268
91350
|
paidAmount,
|
|
91269
91351
|
isPhone,
|
|
91270
91352
|
upstreamStatus: upstream.status,
|
|
@@ -91273,6 +91355,7 @@ async function proxyPaidApiRequest(req, res, apiBase, payFetch, getActualPayment
|
|
|
91273
91355
|
});
|
|
91274
91356
|
logUsage({
|
|
91275
91357
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
91358
|
+
...blockrunRequestId(upstream) ? { requestId: blockrunRequestId(upstream) } : {},
|
|
91276
91359
|
model: isBlockrunExa ? "blockrun-exa" : isModalSandbox ? "modal-sandbox" : isPhone ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : isSurf ? (req.url ?? "").replace(/^\/v1\//, "").split("?")[0] : "partner",
|
|
91277
91360
|
tier: isPhone ? "PHONE" : isSurf ? "SURF" : "PARTNER",
|
|
91278
91361
|
cost: requestCost,
|
|
@@ -91464,8 +91547,20 @@ async function startProxy(options) {
|
|
|
91464
91547
|
const { createKeyPairSignerFromPrivateKeyBytes: createKeyPairSignerFromPrivateKeyBytes2 } = await Promise.resolve().then(() => (init_index_node37(), index_node_exports));
|
|
91465
91548
|
const solanaSigner = await createKeyPairSignerFromPrivateKeyBytes2(solanaPrivateKeyBytes);
|
|
91466
91549
|
solanaAddress = solanaSigner.address;
|
|
91550
|
+
const solanaRpcUrl = process["env"].CLAWROUTER_SOLANA_RPC_URL;
|
|
91467
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
|
+
}
|
|
91468
91560
|
console.log(`[ClawRouter] Solana wallet: ${solanaAddress}`);
|
|
91561
|
+
if (solanaRpcUrl) {
|
|
91562
|
+
console.log(`[ClawRouter] Solana RPC (payment signing + balance): ${solanaRpcUrl}`);
|
|
91563
|
+
}
|
|
91469
91564
|
}
|
|
91470
91565
|
x402?.onAfterPaymentCreation(async (context) => {
|
|
91471
91566
|
if (!context.selectedRequirements.network.startsWith("eip155")) return;
|
|
@@ -91565,7 +91660,15 @@ async function startProxy(options) {
|
|
|
91565
91660
|
)
|
|
91566
91661
|
]);
|
|
91567
91662
|
if (authMode === "api-key") {
|
|
91568
|
-
|
|
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;
|
|
91569
91672
|
response.billing = "BlockRun account credit";
|
|
91570
91673
|
response.topUpUrl = PORTAL_CREDITS_URL;
|
|
91571
91674
|
} else {
|
|
@@ -92670,7 +92773,8 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92670
92773
|
errorBody,
|
|
92671
92774
|
errorStatus: response.status,
|
|
92672
92775
|
isProviderError: category !== null,
|
|
92673
|
-
errorCategory: category ?? void 0
|
|
92776
|
+
errorCategory: category ?? void 0,
|
|
92777
|
+
upstreamRequestId: blockrunRequestId(response)
|
|
92674
92778
|
};
|
|
92675
92779
|
}
|
|
92676
92780
|
const contentType = response.headers.get("content-type") || "";
|
|
@@ -92707,9 +92811,12 @@ async function tryModelRequest(upstreamUrl, method, headers, body, modelId, maxT
|
|
|
92707
92811
|
errorCategory: "payment_error"
|
|
92708
92812
|
};
|
|
92709
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}`);
|
|
92710
92817
|
return {
|
|
92711
92818
|
success: false,
|
|
92712
|
-
errorBody:
|
|
92819
|
+
errorBody: `${networkMsg}${solanaSigningHint}`,
|
|
92713
92820
|
errorStatus: 500,
|
|
92714
92821
|
isProviderError: true
|
|
92715
92822
|
// Network errors are retryable
|
|
@@ -92742,6 +92849,7 @@ async function proxyRequest(req, res, apiBase, payFetch, options, routerOpts, de
|
|
|
92742
92849
|
let accumulatedContent = "";
|
|
92743
92850
|
let responseInputTokens;
|
|
92744
92851
|
let responseOutputTokens;
|
|
92852
|
+
let upstreamRequestId;
|
|
92745
92853
|
let requestHadError = false;
|
|
92746
92854
|
let requestSummaryForStore = "";
|
|
92747
92855
|
const isChatCompletion = req.url?.includes("/chat/completions");
|
|
@@ -94148,7 +94256,8 @@ data: [DONE]
|
|
|
94148
94256
|
}
|
|
94149
94257
|
lastError = {
|
|
94150
94258
|
body: result.errorBody || "Unknown error",
|
|
94151
|
-
status: result.errorStatus || 500
|
|
94259
|
+
status: result.errorStatus || 500,
|
|
94260
|
+
requestId: result.upstreamRequestId
|
|
94152
94261
|
};
|
|
94153
94262
|
failedAttempts.push({
|
|
94154
94263
|
model: tryModel,
|
|
@@ -94220,7 +94329,8 @@ data: [DONE]
|
|
|
94220
94329
|
}
|
|
94221
94330
|
lastError = {
|
|
94222
94331
|
body: retryResult.errorBody || lastError?.body || "Unknown error",
|
|
94223
|
-
status: retryResult.errorStatus || lastError?.status || 500
|
|
94332
|
+
status: retryResult.errorStatus || lastError?.status || 500,
|
|
94333
|
+
requestId: retryResult.upstreamRequestId ?? lastError?.requestId
|
|
94224
94334
|
};
|
|
94225
94335
|
failedAttempts.push({
|
|
94226
94336
|
model: tryModel,
|
|
@@ -94362,10 +94472,13 @@ data: [DONE]
|
|
|
94362
94472
|
}
|
|
94363
94473
|
}
|
|
94364
94474
|
}
|
|
94475
|
+
upstreamRequestId = blockrunRequestId(upstream);
|
|
94365
94476
|
if (!upstream) {
|
|
94366
94477
|
const attemptSummary = failedAttempts.length > 0 ? failedAttempts.map((a) => `${a.model} (${a.reason})`).join(", ") : "unknown";
|
|
94367
94478
|
const structuredMessage = failedAttempts.length > 0 ? `All ${failedAttempts.length} models failed. Tried: ${attemptSummary}` : "All models in fallback chain failed";
|
|
94368
|
-
console.log(
|
|
94479
|
+
console.log(
|
|
94480
|
+
`[ClawRouter] ${structuredMessage}${lastError?.requestId ? ` | gateway request id: ${lastError.requestId}` : ""}`
|
|
94481
|
+
);
|
|
94369
94482
|
const rawErrBody = lastError?.body || structuredMessage;
|
|
94370
94483
|
const errStatus = lastError?.status || 502;
|
|
94371
94484
|
const transformedErr = transformPaymentError(rawErrBody);
|
|
@@ -94403,7 +94516,8 @@ data: [DONE]
|
|
|
94403
94516
|
res.writeHead(errStatus, {
|
|
94404
94517
|
"Content-Type": "application/json",
|
|
94405
94518
|
"x-context-used-kb": String(originalContextSizeKB),
|
|
94406
|
-
"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 } : {}
|
|
94407
94521
|
});
|
|
94408
94522
|
res.end(transformedErr);
|
|
94409
94523
|
deduplicator.complete(dedupKey, {
|
|
@@ -94867,12 +94981,24 @@ data: [DONE]
|
|
|
94867
94981
|
maxTokens,
|
|
94868
94982
|
routingProfile ?? void 0
|
|
94869
94983
|
);
|
|
94870
|
-
|
|
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
|
+
}
|
|
94871
94997
|
logBaseline = costs.baselineCost;
|
|
94872
|
-
logSavings = FREE_MODELS.has(logModel) ? 1 : costs.savings;
|
|
94873
94998
|
}
|
|
94874
94999
|
const entry = {
|
|
94875
95000
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
95001
|
+
...upstreamRequestId ? { requestId: upstreamRequestId } : {},
|
|
94876
95002
|
model: logModel,
|
|
94877
95003
|
tier: routingDecision?.tier ?? "DIRECT",
|
|
94878
95004
|
cost: logCost,
|
|
@@ -94912,6 +95038,7 @@ var init_proxy = __esm({
|
|
|
94912
95038
|
init_api_key();
|
|
94913
95039
|
init_spend_control();
|
|
94914
95040
|
init_compression();
|
|
95041
|
+
init_errors5();
|
|
94915
95042
|
init_version4();
|
|
94916
95043
|
init_session();
|
|
94917
95044
|
init_updater();
|
package/docs/configuration.md
CHANGED
|
@@ -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
|
|
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.
|
|
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",
|
package/skills/surf/SKILL.md
CHANGED
|
@@ -26,11 +26,27 @@ license: MIT
|
|
|
26
26
|
|
|
27
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
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
**Pricing — flat per call, the same for every endpoint.**
|
|
30
|
+
|
|
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
|
|