@blockrun/franklin 3.15.62 → 3.15.64

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.
@@ -6,6 +6,8 @@ import fs from 'node:fs';
6
6
  import path from 'node:path';
7
7
  import { execSync } from 'node:child_process';
8
8
  import { getWalletAddress as getBaseWalletAddress } from '@blockrun/llm';
9
+ import { Keypair } from '@solana/web3.js';
10
+ import bs58 from 'bs58';
9
11
  import { loadLearnings, decayLearnings, saveLearnings, formatForPrompt, loadSkills, formatSkillsForPrompt } from '../learnings/store.js';
10
12
  // ─── System Instructions Assembly ──────────────────────────────────────────
11
13
  // Composable prompt sections — each independently maintainable and conditionally includable.
@@ -164,10 +166,16 @@ Franklin stores wallet keys in ~/.blockrun/. When the user asks about wallet loc
164
166
  Format: 66-char hex string starting with 0x (file name intentionally looks like a session token for obscurity)
165
167
  Address: derivable from the key; also available via getWalletAddress() from @blockrun/llm
166
168
  - Solana wallet:
167
- File: ~/.blockrun/solana-wallet.json (JSON with address + private_key)
168
- - Chain selection: ~/.blockrun/.chain ("base" or "solana")
169
- - Spending tracker: ~/.blockrun/spending.json
170
- - Programmatic access: import { getWalletAddress, getOrCreateWallet } from '@blockrun/llm'
169
+ Private key file: ~/.blockrun/.solana-session
170
+ Format: bare base58 secret key (file name mirrors the Base wallet's obscurity convention; mode 600)
171
+ Address: derivable; available via getOrCreateSolanaWallet() from @blockrun/llm
172
+ - Chain selection: ~/.blockrun/payment-chain ("base" or "solana"). Legacy file ~/.blockrun/.chain may also exist on installs that haven't migrated; canonical is payment-chain.
173
+ - Spending data:
174
+ - ~/.blockrun/franklin-stats.json — rolling totals + per-model breakdown (what \`franklin stats\` reads).
175
+ - ~/.blockrun/franklin-audit.jsonl — append-only forensic ledger of every LLM call.
176
+ - ~/.blockrun/cost_log.jsonl — append-only ledger of every settled x402 payment (written by @blockrun/llm SDK).
177
+ - Use \`franklin stats\` / \`franklin content list\` instead of parsing files when the user asks "how much did I spend".
178
+ - Programmatic access: import { getWalletAddress, getOrCreateWallet, getOrCreateSolanaWallet } from '@blockrun/llm'
171
179
 
172
180
  When the user asks about "my wallet" without qualifier, default to Base (it's the primary chain shown at launch). Only mention Solana if the chain file says solana or the user explicitly asks.`;
173
181
  }
@@ -604,7 +612,7 @@ function buildEnvironmentSection(workingDir) {
604
612
  if (wallet.base)
605
613
  lines.push(`- Base wallet address: ${wallet.base} (private key at ~/.blockrun/.session)`);
606
614
  if (wallet.solana)
607
- lines.push(`- Solana wallet address: ${wallet.solana} (private key at ~/.blockrun/solana-wallet.json)`);
615
+ lines.push(`- Solana wallet address: ${wallet.solana} (private key at ~/.blockrun/.solana-session)`);
608
616
  }
609
617
  return lines.join('\n');
610
618
  }
@@ -614,13 +622,27 @@ function readRuntimeWallet() {
614
622
  return {};
615
623
  const blockrunDir = path.join(home, '.blockrun');
616
624
  const out = {};
625
+ // Chain selection: prefer the canonical `payment-chain` (matches
626
+ // src/config.ts:CHAIN_FILE which the rest of the codebase writes).
627
+ // Fall back to the legacy `.chain` for installs that haven't migrated.
628
+ // Verified 2026-05-05: .chain on this machine read "base" (last
629
+ // updated 2026-03-14), payment-chain read "base" (last updated
630
+ // 2026-05-04) — same value here, but the two paths can diverge any
631
+ // time the user's panel UI or `franklin <chain>` writes the new one
632
+ // while the old file stays frozen. Reading both, preferring the new,
633
+ // closes the gap silently.
617
634
  try {
618
- const chainFile = path.join(blockrunDir, '.chain');
619
- if (fs.existsSync(chainFile)) {
620
- const chain = fs.readFileSync(chainFile, 'utf-8').trim();
621
- if (chain)
622
- out.chain = chain;
635
+ const newChainFile = path.join(blockrunDir, 'payment-chain');
636
+ const legacyChainFile = path.join(blockrunDir, '.chain');
637
+ let chain = '';
638
+ if (fs.existsSync(newChainFile)) {
639
+ chain = fs.readFileSync(newChainFile, 'utf-8').trim();
623
640
  }
641
+ if (!chain && fs.existsSync(legacyChainFile)) {
642
+ chain = fs.readFileSync(legacyChainFile, 'utf-8').trim();
643
+ }
644
+ if (chain)
645
+ out.chain = chain;
624
646
  }
625
647
  catch { /* ignore */ }
626
648
  // Base address: derive via @blockrun/llm (handles the private key in .session)
@@ -630,14 +652,42 @@ function readRuntimeWallet() {
630
652
  out.base = addr;
631
653
  }
632
654
  catch { /* SDK may not be available in all contexts — skip silently */ }
633
- // Solana address: read from JSON
655
+ // Solana address: prefer the canonical SDK file `.solana-session`
656
+ // (raw base58 secret key, mode 600 — what the SDK actually writes
657
+ // and reads via getOrCreateSolanaWallet). Fall back to the legacy
658
+ // `solana-wallet.json` shape (JSON with {address, privateKey}) for
659
+ // unmigrated installs. Verified 2026-05-05: user's machine had
660
+ // both files present — `.solana-session` (88 bytes) was canonical
661
+ // and `solana-wallet.json` (123 bytes) was a leftover from an
662
+ // earlier SDK version. The pre-fix code only read the legacy file,
663
+ // so once a user `rm`s it after migration, the runtime-wallet
664
+ // section silently stops showing the Solana address.
634
665
  try {
635
- const solPath = path.join(blockrunDir, 'solana-wallet.json');
636
- if (fs.existsSync(solPath)) {
637
- const data = JSON.parse(fs.readFileSync(solPath, 'utf-8'));
638
- const addr = data.address || data.publicKey;
639
- if (addr && typeof addr === 'string')
640
- out.solana = addr;
666
+ const canonical = path.join(blockrunDir, '.solana-session');
667
+ if (fs.existsSync(canonical)) {
668
+ const key = fs.readFileSync(canonical, 'utf-8').trim();
669
+ if (key) {
670
+ // Derive the public address from the secret key. Same primitives
671
+ // jupiter.ts:229 uses for transaction signing — keeps this
672
+ // sync-with-SDK without depending on async `getOrCreateSolanaWallet`
673
+ // (which would create a wallet on first read, an unwanted side
674
+ // effect for a context-builder).
675
+ try {
676
+ const bytes = bs58.decode(key);
677
+ const kp = Keypair.fromSecretKey(bytes);
678
+ out.solana = kp.publicKey.toBase58();
679
+ }
680
+ catch { /* derivation failed — fall through to legacy probe */ }
681
+ }
682
+ }
683
+ if (!out.solana) {
684
+ const legacy = path.join(blockrunDir, 'solana-wallet.json');
685
+ if (fs.existsSync(legacy)) {
686
+ const data = JSON.parse(fs.readFileSync(legacy, 'utf-8'));
687
+ const addr = data.address || data.publicKey;
688
+ if (addr && typeof addr === 'string')
689
+ out.solana = addr;
690
+ }
641
691
  }
642
692
  }
643
693
  catch { /* ignore */ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.62",
3
+ "version": "3.15.64",
4
4
  "description": "Franklin — 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": {