@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.
@@ -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). */
@@ -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
- rows.push({ endpoint, costUsd, ts });
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 rows;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.101",
3
+ "version": "3.15.102",
4
4
  "description": "Franklin Agent — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {