@blockrun/franklin 3.26.1 → 3.27.1

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.
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Swap ledger — one line per executed on-chain swap, at ~/.blockrun/swaps.jsonl.
3
+ * The swap tools (0x on Base, Jupiter on Solana) append here on success so the
4
+ * desktop wallet view can show a "swaps" history with explorer links. Mirrors
5
+ * the JSONL pattern of cost-log / audit. All writes are best-effort.
6
+ */
7
+ export interface SwapRow {
8
+ ts: number;
9
+ chain: 'base' | 'solana';
10
+ dex: string;
11
+ sellSym: string;
12
+ sellAmount: number;
13
+ buySym: string;
14
+ buyAmount: number;
15
+ txHash: string;
16
+ explorer?: string;
17
+ }
18
+ export declare function appendSwap(row: SwapRow): void;
19
+ export declare function readSwaps(limit?: number): SwapRow[];
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Swap ledger — one line per executed on-chain swap, at ~/.blockrun/swaps.jsonl.
3
+ * The swap tools (0x on Base, Jupiter on Solana) append here on success so the
4
+ * desktop wallet view can show a "swaps" history with explorer links. Mirrors
5
+ * the JSONL pattern of cost-log / audit. All writes are best-effort.
6
+ */
7
+ import fs from 'node:fs';
8
+ import path from 'node:path';
9
+ import { BLOCKRUN_DIR } from '../config.js';
10
+ const SWAP_FILE = path.join(BLOCKRUN_DIR, 'swaps.jsonl');
11
+ export function appendSwap(row) {
12
+ try {
13
+ fs.mkdirSync(BLOCKRUN_DIR, { recursive: true });
14
+ fs.appendFileSync(SWAP_FILE, JSON.stringify(row) + '\n', { mode: 0o600 });
15
+ }
16
+ catch { /* best-effort */ }
17
+ }
18
+ export function readSwaps(limit = 100) {
19
+ try {
20
+ const lines = fs.readFileSync(SWAP_FILE, 'utf-8').trim().split('\n').filter(Boolean);
21
+ const rows = [];
22
+ for (const line of lines) {
23
+ try {
24
+ rows.push(JSON.parse(line));
25
+ }
26
+ catch { /* skip bad line */ }
27
+ }
28
+ return rows.sort((a, b) => b.ts - a.ts).slice(0, limit);
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ }
@@ -27,6 +27,7 @@ import { base } from 'viem/chains';
27
27
  import { getOrCreateWallet } from '@blockrun/llm';
28
28
  import { loadConfig } from '../commands/config.js';
29
29
  import { loadChain, API_URLS, VERSION } from '../config.js';
30
+ import { appendSwap } from '../stats/swap-log.js';
30
31
  // ─── BlockRun affiliate identity on Base ─────────────────────────────────
31
32
  // Reuses the existing BlockRun ops wallet that already receives x402
32
33
  // settlements on Base. Every swap routed through these tools deposits 20
@@ -407,9 +408,43 @@ async function executeBase0xSwap(input, ctx) {
407
408
  }
408
409
  liveSwapCount += 1;
409
410
  const explorer = `https://basescan.org/tx/${txHash}`;
411
+ // Confirm on-chain before recording: a submitted tx can still revert (e.g.
412
+ // slippage floor exceeded), and the swap log feeds the desktop wallet
413
+ // history — same confirmed-only gating as the gasless tool. Base blocks land
414
+ // in ~2s, so the wait is cheap.
415
+ let confirmed = false;
416
+ try {
417
+ const receipt = await client.waitForTransactionReceipt({ hash: txHash, timeout: 60_000 });
418
+ if (receipt.status !== 'success') {
419
+ return {
420
+ output: `Swap reverted on-chain (likely the slippage floor was exceeded — no tokens moved, only gas was spent).\n` +
421
+ `Tx hash: ${txHash}\n${explorer}`,
422
+ isError: true,
423
+ };
424
+ }
425
+ confirmed = true;
426
+ }
427
+ catch { /* receipt wait timed out / RPC hiccup — report submitted, don't record */ }
428
+ if (confirmed) {
429
+ // Record the swap so the desktop wallet can show a history (best-effort).
430
+ try {
431
+ appendSwap({
432
+ ts: Date.now(),
433
+ chain: 'base',
434
+ dex: '0x',
435
+ sellSym: symbolFor(quote.sellToken),
436
+ sellAmount: Number(formatUnits(BigInt(quote.sellAmount), decimalsFor(quote.sellToken))),
437
+ buySym: symbolFor(quote.buyToken),
438
+ buyAmount: Number(formatUnits(BigInt(quote.buyAmount), decimalsFor(quote.buyToken))),
439
+ txHash,
440
+ explorer,
441
+ });
442
+ }
443
+ catch { /* best-effort */ }
444
+ }
410
445
  return {
411
446
  output: [
412
- '✓ Swap executed on Base.',
447
+ confirmed ? '✓ Swap executed on Base.' : '✓ Swap submitted on Base (confirmation pending — check the explorer).',
413
448
  formatQuoteText(quote),
414
449
  `Tx hash: ${txHash}`,
415
450
  explorer,
@@ -24,6 +24,7 @@ import { createWalletClient, http, publicActions } from 'viem';
24
24
  import { getOrCreateWallet } from '@blockrun/llm';
25
25
  import { loadConfig } from '../commands/config.js';
26
26
  import { loadChain, API_URLS, VERSION } from '../config.js';
27
+ import { appendSwap } from '../stats/swap-log.js';
27
28
  import { logger } from '../logger.js';
28
29
  // ─── Constants ────────────────────────────────────────────────────────────
29
30
  const ZEROX_GATEWAY_PATH = '/v1/zerox/gasless';
@@ -409,6 +410,22 @@ async function executeBase0xGaslessSwap(input, ctx) {
409
410
  : final.status === 'failed'
410
411
  ? `✗ Swap failed: ${final.reason ?? 'unknown reason'}`
411
412
  : `⏳ Still pending after ${MAX_STATUS_POLL_MS / 1000}s — relayer is backlogged. Check status later via /v1/zerox/gasless/status/${submitRes.tradeHash}.`;
413
+ if ((final.status === 'confirmed' || final.status === 'succeeded') && onChainHash) {
414
+ try {
415
+ appendSwap({
416
+ ts: Date.now(),
417
+ chain: 'base',
418
+ dex: '0x',
419
+ sellSym: symbolFor(quote.sellToken),
420
+ sellAmount: Number(formatUnits(BigInt(quote.sellAmount), decimalsFor(quote.sellToken))),
421
+ buySym: symbolFor(quote.buyToken),
422
+ buyAmount: Number(formatUnits(BigInt(quote.buyAmount), decimalsFor(quote.buyToken))),
423
+ txHash: onChainHash,
424
+ explorer: explorer ?? undefined,
425
+ });
426
+ }
427
+ catch { /* best-effort */ }
428
+ }
412
429
  const lines = [
413
430
  statusLine,
414
431
  formatGaslessQuoteText(quote),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.26.1",
3
+ "version": "3.27.1",
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": {
@@ -69,6 +69,7 @@
69
69
  "@blockrun/llm": "^2.0.0",
70
70
  "@colbymchenry/codegraph": "^0.9.7",
71
71
  "@modelcontextprotocol/sdk": "^1.29.0",
72
+ "@slack/bolt": "^4.7.3",
72
73
  "@solana/spl-token": "^0.4.14",
73
74
  "@solana/web3.js": "^1.98.4",
74
75
  "@types/react": "^19.2.14",