@blockrun/franklin 3.15.101 → 3.15.102
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/stats/cost-log.d.ts +6 -0
- package/dist/stats/cost-log.js +63 -2
- package/package.json +1 -1
package/dist/stats/cost-log.d.ts
CHANGED
|
@@ -29,6 +29,12 @@ export interface SettlementRow {
|
|
|
29
29
|
costUsd: number;
|
|
30
30
|
/** Unix milliseconds (normalized — SDK writes seconds). */
|
|
31
31
|
ts: number;
|
|
32
|
+
/** Wallet that signed (lowercased). Used for test-wallet filtering. */
|
|
33
|
+
wallet?: string;
|
|
34
|
+
/** Model that was charged (e.g. `openai/gpt-5.5`). */
|
|
35
|
+
model?: string;
|
|
36
|
+
/** Which client wrote the row (LLMClient / AgentClient / ProxyClient / AsyncLLMClient). */
|
|
37
|
+
clientKind?: string;
|
|
32
38
|
}
|
|
33
39
|
export interface SettlementSummary {
|
|
34
40
|
/** Path to cost_log.jsonl (or the fallback location). */
|
package/dist/stats/cost-log.js
CHANGED
|
@@ -25,6 +25,27 @@
|
|
|
25
25
|
import fs from 'node:fs';
|
|
26
26
|
import path from 'node:path';
|
|
27
27
|
import { BLOCKRUN_DIR } from '../config.js';
|
|
28
|
+
/**
|
|
29
|
+
* Anvil/Hardhat deterministic test accounts. The first one
|
|
30
|
+
* (0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266) leaked into a real
|
|
31
|
+
* cost_log on 2026-05-13 — some SDK path signed with a hardcoded test
|
|
32
|
+
* key in production. These addresses are public knowledge (the private
|
|
33
|
+
* keys are in the Anvil source), so a settlement signed by them is
|
|
34
|
+
* definitionally not a real user spend. Filter them out at read time
|
|
35
|
+
* so dashboards / stats don't surface phantom rows.
|
|
36
|
+
*/
|
|
37
|
+
const KNOWN_TEST_WALLETS = new Set([
|
|
38
|
+
'0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', // Anvil #0
|
|
39
|
+
'0x70997970c51812dc3a010c7d01b50e0d17dc79c8', // Anvil #1
|
|
40
|
+
'0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc', // Anvil #2
|
|
41
|
+
'0x90f79bf6eb2c4f870365e785982e1f101e93b906', // Anvil #3
|
|
42
|
+
'0x15d34aaf54267db7d7c367839aaf71a00a2c6a65', // Anvil #4
|
|
43
|
+
'0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc', // Anvil #5
|
|
44
|
+
'0x976ea74026e726554db657fa54763abd0c3a0aa9', // Anvil #6
|
|
45
|
+
'0x14dc79964da2c08b23698b3d3cc7ca32193d9955', // Anvil #7
|
|
46
|
+
'0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f', // Anvil #8
|
|
47
|
+
'0xa0ee7a142d267c1f36714e4a8f75612f20a79720', // Anvil #9
|
|
48
|
+
]);
|
|
28
49
|
function getCostLogPath() {
|
|
29
50
|
return path.join(BLOCKRUN_DIR, 'cost_log.jsonl');
|
|
30
51
|
}
|
|
@@ -77,9 +98,49 @@ export function loadSdkSettlements(opts) {
|
|
|
77
98
|
const ts = tsRaw < 1e12 ? Math.round(tsRaw * 1000) : Math.round(tsRaw);
|
|
78
99
|
if (ts < sinceMs || ts > untilMs)
|
|
79
100
|
continue;
|
|
80
|
-
|
|
101
|
+
// Filter out known test-wallet leaks. Verified 2026-05-13: a real
|
|
102
|
+
// cost_log had a $1 entry written under Anvil account #0
|
|
103
|
+
// (0xf39Fd6...) — public test key. Any settlement under those
|
|
104
|
+
// addresses is by definition not real user spend; drop.
|
|
105
|
+
const walletRaw = typeof obj.wallet === 'string' ? obj.wallet : undefined;
|
|
106
|
+
const wallet = walletRaw?.toLowerCase();
|
|
107
|
+
if (wallet && KNOWN_TEST_WALLETS.has(wallet))
|
|
108
|
+
continue;
|
|
109
|
+
const model = typeof obj.model === 'string' ? obj.model : undefined;
|
|
110
|
+
const clientKindRaw = obj.client_kind ?? obj.clientKind;
|
|
111
|
+
const clientKind = typeof clientKindRaw === 'string' ? clientKindRaw : undefined;
|
|
112
|
+
rows.push({ endpoint, costUsd, ts, wallet, model, clientKind });
|
|
113
|
+
}
|
|
114
|
+
return dedupeRows(rows);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Collapse SDK double-writes. Verified 2026-05-13: a single
|
|
118
|
+
* `gpt-5.5 / /v1/chat/completions / $1.00` call generated THREE
|
|
119
|
+
* cost_log rows in the same physical second (two `LLMClient`, one
|
|
120
|
+
* `AsyncLLMClient`) because the SDK wraps the same fetch through two
|
|
121
|
+
* client classes, both of which call `appendCostLog`. Bucket by
|
|
122
|
+
* `(second, endpoint, model, cost-in-micro-USDC)` and keep the first;
|
|
123
|
+
* the others were always duplicates.
|
|
124
|
+
*
|
|
125
|
+
* Edge case: two legitimate same-second / same-model / same-price
|
|
126
|
+
* calls would also dedupe to one. Accepting that trade-off — the SDK
|
|
127
|
+
* bug currently inflates by 200-300%; a worst-case 1-row undercount
|
|
128
|
+
* on rapid-fire identical calls is a much smaller error and the user's
|
|
129
|
+
* dashboards round to cents anyway.
|
|
130
|
+
*/
|
|
131
|
+
function dedupeRows(rows) {
|
|
132
|
+
const seen = new Map();
|
|
133
|
+
for (const r of rows) {
|
|
134
|
+
const bucket = Math.round(r.ts / 1000);
|
|
135
|
+
const microUsd = Math.round(r.costUsd * 1e6);
|
|
136
|
+
const key = `${bucket}|${r.endpoint}|${r.model ?? ''}|${microUsd}`;
|
|
137
|
+
// Keep the FIRST row in each bucket (chronologically earliest by ts).
|
|
138
|
+
// If the existing row in the map already has earlier ts, leave it.
|
|
139
|
+
const existing = seen.get(key);
|
|
140
|
+
if (!existing || r.ts < existing.ts)
|
|
141
|
+
seen.set(key, r);
|
|
81
142
|
}
|
|
82
|
-
return
|
|
143
|
+
return [...seen.values()].sort((a, b) => a.ts - b.ts);
|
|
83
144
|
}
|
|
84
145
|
/**
|
|
85
146
|
* Append one settlement row to ~/.blockrun/cost_log.jsonl in the same
|
package/package.json
CHANGED