chain-insights 0.2.16
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/LICENSE +21 -0
- package/README.md +165 -0
- package/bin/cli.js +10 -0
- package/bin/install.cjs +252 -0
- package/bin/mcp-proxy.cjs +10 -0
- package/dist/active-BSrxLKwn.mjs +50 -0
- package/dist/active-BSrxLKwn.mjs.map +1 -0
- package/dist/active-Dv7Tu-O4.cjs +68 -0
- package/dist/app-BjjuQM0B.mjs +155 -0
- package/dist/app-BjjuQM0B.mjs.map +1 -0
- package/dist/app-Dq1TdB6p.cjs +161 -0
- package/dist/artifact-server-DoxJ7fCx.cjs +47 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs +48 -0
- package/dist/artifact-server-Dxz5YbuQ.mjs.map +1 -0
- package/dist/assets/bg-pattern.png +0 -0
- package/dist/assets/logo.png +0 -0
- package/dist/call-args-DQA2QcRA.cjs +27 -0
- package/dist/call-args-Lk_wOJxd.mjs +29 -0
- package/dist/call-args-Lk_wOJxd.mjs.map +1 -0
- package/dist/capabilities-CB97WMA5.cjs +83 -0
- package/dist/capabilities-DliMBim-.mjs +84 -0
- package/dist/capabilities-DliMBim-.mjs.map +1 -0
- package/dist/cases-By7INiOa.mjs +6 -0
- package/dist/cases-CDcNU91B.cjs +9 -0
- package/dist/chunk-CZWwpsFl.cjs +43 -0
- package/dist/cli.cjs +752 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +753 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/client-D4Bq0rp9.mjs +111 -0
- package/dist/client-D4Bq0rp9.mjs.map +1 -0
- package/dist/client-D4fZgIaO.cjs +132 -0
- package/dist/config-Bmdl5hdk.cjs +67 -0
- package/dist/config-BwrBYmiC.mjs +44 -0
- package/dist/config-BwrBYmiC.mjs.map +1 -0
- package/dist/data-extractor-BNGj7ECT.cjs +347 -0
- package/dist/data-extractor-DFzsa5CS.mjs +336 -0
- package/dist/data-extractor-DFzsa5CS.mjs.map +1 -0
- package/dist/dossier-BsroDgD3.mjs +76 -0
- package/dist/dossier-BsroDgD3.mjs.map +1 -0
- package/dist/dossier-DtxREpPm.cjs +76 -0
- package/dist/evidence-BGcdKxuV.cjs +200 -0
- package/dist/evidence-BhvFW-y_.mjs +195 -0
- package/dist/evidence-BhvFW-y_.mjs.map +1 -0
- package/dist/format-Ce1RObVl.mjs +22 -0
- package/dist/format-Ce1RObVl.mjs.map +1 -0
- package/dist/format-DOrPvXEr.cjs +20 -0
- package/dist/frontmatter-D8wWCeOa.mjs +26 -0
- package/dist/frontmatter-D8wWCeOa.mjs.map +1 -0
- package/dist/frontmatter-DgAuai7E.cjs +35 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs +130 -0
- package/dist/graph-normalizer-Cv9yK9Pg.mjs.map +1 -0
- package/dist/graph-normalizer-DeIj6Ses.cjs +133 -0
- package/dist/graph-reports-C4TBjCkM.mjs +63 -0
- package/dist/graph-reports-C4TBjCkM.mjs.map +1 -0
- package/dist/graph-reports-DU05YCei.cjs +64 -0
- package/dist/html-generator-CAv81IWH.cjs +85 -0
- package/dist/html-generator-V6Bp0uRb.mjs +68 -0
- package/dist/html-generator-V6Bp0uRb.mjs.map +1 -0
- package/dist/index.cjs +31 -0
- package/dist/index.d.cts +187 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +187 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +9 -0
- package/dist/init-BjuFt54X.cjs +232 -0
- package/dist/init-CaOsHTIo.mjs +232 -0
- package/dist/init-CaOsHTIo.mjs.map +1 -0
- package/dist/mcp-proxy.cjs +1257 -0
- package/dist/mcp-proxy.d.cts +12 -0
- package/dist/mcp-proxy.d.cts.map +1 -0
- package/dist/mcp-proxy.d.mts +12 -0
- package/dist/mcp-proxy.d.mts.map +1 -0
- package/dist/mcp-proxy.mjs +1255 -0
- package/dist/mcp-proxy.mjs.map +1 -0
- package/dist/output-root-CFYms3ad.cjs +43 -0
- package/dist/output-root-CmWM7aV2.mjs +33 -0
- package/dist/output-root-CmWM7aV2.mjs.map +1 -0
- package/dist/parser-BUIWW1OH.cjs +182 -0
- package/dist/parser-DO0_SssG.mjs +182 -0
- package/dist/parser-DO0_SssG.mjs.map +1 -0
- package/dist/public-tools-D4UI-Zb0.mjs +2554 -0
- package/dist/public-tools-D4UI-Zb0.mjs.map +1 -0
- package/dist/public-tools-XSpkz2ky.cjs +2556 -0
- package/dist/resolver-C2ZS7oC8.mjs +201 -0
- package/dist/resolver-C2ZS7oC8.mjs.map +1 -0
- package/dist/resolver-zYbu4wDV.cjs +203 -0
- package/dist/rolldown-runtime-wcPFST8Q.mjs +13 -0
- package/dist/runner-1Eq55OYb.cjs +148 -0
- package/dist/runner-BhUHbiHG.mjs +149 -0
- package/dist/runner-BhUHbiHG.mjs.map +1 -0
- package/dist/schema-4XpzDFQM.cjs +55 -0
- package/dist/schema-8d0rVIdZ.mjs +37 -0
- package/dist/schema-8d0rVIdZ.mjs.map +1 -0
- package/dist/schema-cache-9CksD7tX.mjs +34 -0
- package/dist/schema-cache-9CksD7tX.mjs.map +1 -0
- package/dist/schema-cache-CgWRCN2N.cjs +36 -0
- package/dist/selector-CkFcTXzz.cjs +10 -0
- package/dist/selector-xjm6NTHI.mjs +12 -0
- package/dist/selector-xjm6NTHI.mjs.map +1 -0
- package/dist/server-BkM5xrXb.mjs +45 -0
- package/dist/server-BkM5xrXb.mjs.map +1 -0
- package/dist/server-DXowbpfi.cjs +54 -0
- package/dist/session-BpNylyuJ.cjs +115 -0
- package/dist/session-CcTgYxsj.mjs +115 -0
- package/dist/session-CcTgYxsj.mjs.map +1 -0
- package/dist/setup-DOpKPrlx.cjs +81 -0
- package/dist/setup-DyrWHuwQ.mjs +80 -0
- package/dist/setup-DyrWHuwQ.mjs.map +1 -0
- package/dist/store-BiUhQOIf.cjs +230 -0
- package/dist/store-BoWE-Gtl.mjs +225 -0
- package/dist/store-BoWE-Gtl.mjs.map +1 -0
- package/dist/templates/graph.html +1406 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs +28 -0
- package/dist/tool-visibility-3Z_KvO9Q.mjs.map +1 -0
- package/dist/tool-visibility-CwgY205r.cjs +36 -0
- package/dist/tools-Cp2jAAAb.mjs +100 -0
- package/dist/tools-Cp2jAAAb.mjs.map +1 -0
- package/dist/tools-f_vJUZAF.cjs +139 -0
- package/dist/topup-server-BZuQifvh.cjs +940 -0
- package/dist/topup-server-DUjyFftI.mjs +919 -0
- package/dist/topup-server-DUjyFftI.mjs.map +1 -0
- package/dist/version-1gP19Lhi.mjs +8 -0
- package/dist/version-1gP19Lhi.mjs.map +1 -0
- package/dist/version-BNGtdpmH.cjs +18 -0
- package/dist/viz-BlCJe6Tk.mjs +35 -0
- package/dist/viz-BlCJe6Tk.mjs.map +1 -0
- package/dist/viz-ClezVXrJ.cjs +44 -0
- package/dist/wallet-BMelXBYP.mjs +104 -0
- package/dist/wallet-BMelXBYP.mjs.map +1 -0
- package/dist/wallet-RnvvSpV2.cjs +146 -0
- package/docs/architecture.md +145 -0
- package/docs/contributing.md +68 -0
- package/docs/debugging.md +68 -0
- package/docs/development.md +44 -0
- package/docs/graph-tools.md +251 -0
- package/docs/images/graph-mcp-iframe.png +0 -0
- package/docs/images/graph-visualization.png +0 -0
- package/docs/images/topup-page.png +0 -0
- package/docs/investigation-workspaces.md +151 -0
- package/docs/mcp-proxy.md +180 -0
- package/package.json +59 -0
- package/skills/chain-insights-developer-experience/SKILL.md +101 -0
- package/skills/chain-insights-investigation/SKILL.md +285 -0
- package/skills/chain-insights-investigation/agents/openai.yaml +4 -0
- package/skills/chain-insights-investigation/scripts/run-target-uat.sh +197 -0
- package/skills/chain-insights-trace-funds/SKILL.md +249 -0
- package/skills/ci-case/SKILL.md +43 -0
- package/skills/ci-status/SKILL.md +45 -0
- package/skills/test-chain-insights-graphrag-mcp/SKILL.md +75 -0
- package/skills/test-chain-insights-graphrag-mcp/agents/openai.yaml +4 -0
- package/skills/test-chain-insights-graphrag-mcp/scripts/run-uat.sh +414 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topup-server-DUjyFftI.mjs","names":["USDC_ADDRESS","getTopupUrl","startTopupServer","getCopiedTopupUrl","startCopiedTopupServer"],"sources":["../src/wallet/mcp-proxy/qr.ts","../src/wallet/mcp-proxy/tools.ts","../src/wallet/mcp-proxy/topup-server.ts","../src/wallet/topup-server.ts"],"sourcesContent":["/**\n * Minimal QR code generator — produces SVG string server-side.\n * Supports alphanumeric mode (sufficient for Ethereum addresses).\n * No external dependencies.\n */\n\n// Error correction level M (15% recovery)\nconst EC_LEVEL = 0; // L=0, M=1 — using L for simplicity with short data\n\n// QR code version 2 (25x25) is sufficient for 42-char ETH addresses\nconst VERSION = 2;\nconst SIZE = 25; // modules per side for version 2\n\n// Generator polynomial for version 2-L: 10 EC codewords\nconst EC_CODEWORDS = 10;\nconst DATA_CODEWORDS = 34;\n\n// Format info for version 2, mask 0, EC level L\nconst FORMAT_BITS = 0b111011111000100;\n\n// Byte mode indicator\nconst MODE_BYTE = 0b0100;\n\nfunction createMatrix(): number[][] {\n const m: number[][] = [];\n for (let i = 0; i < SIZE; i++) {\n m[i] = new Array(SIZE).fill(-1);\n }\n return m;\n}\n\nfunction addFinderPattern(matrix: number[][], row: number, col: number): void {\n for (let r = -1; r <= 7; r++) {\n for (let c = -1; c <= 7; c++) {\n const mr = row + r;\n const mc = col + c;\n if (mr < 0 || mr >= SIZE || mc < 0 || mc >= SIZE) continue;\n if (r >= 0 && r <= 6 && c >= 0 && c <= 6) {\n if (r === 0 || r === 6 || c === 0 || c === 6 || (r >= 2 && r <= 4 && c >= 2 && c <= 4)) {\n matrix[mr][mc] = 1;\n } else {\n matrix[mr][mc] = 0;\n }\n } else {\n matrix[mr][mc] = 0;\n }\n }\n }\n}\n\nfunction addAlignmentPattern(matrix: number[][], row: number, col: number): void {\n for (let r = -2; r <= 2; r++) {\n for (let c = -2; c <= 2; c++) {\n if (Math.abs(r) === 2 || Math.abs(c) === 2 || (r === 0 && c === 0)) {\n matrix[row + r][col + c] = 1;\n } else {\n matrix[row + r][col + c] = 0;\n }\n }\n }\n}\n\nfunction addTimingPatterns(matrix: number[][]): void {\n for (let i = 8; i < SIZE - 8; i++) {\n if (matrix[6][i] === -1) matrix[6][i] = i % 2 === 0 ? 1 : 0;\n if (matrix[i][6] === -1) matrix[i][6] = i % 2 === 0 ? 1 : 0;\n }\n}\n\nfunction addFormatInfo(matrix: number[][]): void {\n const bits = FORMAT_BITS;\n for (let i = 0; i <= 5; i++) matrix[8][i] = (bits >> (14 - i)) & 1;\n matrix[8][7] = (bits >> 8) & 1;\n matrix[8][8] = (bits >> 7) & 1;\n matrix[7][8] = (bits >> 6) & 1;\n for (let i = 0; i <= 5; i++) matrix[5 - i][8] = (bits >> (i)) & 1;\n\n for (let i = 0; i <= 7; i++) matrix[SIZE - 1 - i][8] = (bits >> (14 - i)) & 1;\n for (let i = 0; i <= 7; i++) matrix[8][SIZE - 8 + i] = (bits >> (7 - i)) & 1;\n\n // Dark module\n matrix[SIZE - 8][8] = 1;\n}\n\nfunction encodeData(text: string): number[] {\n const bytes = new TextEncoder().encode(text);\n const bits: number[] = [];\n\n // Mode indicator (4 bits): byte mode\n for (let i = 3; i >= 0; i--) bits.push((MODE_BYTE >> i) & 1);\n\n // Character count (8 bits for version 1-9 byte mode)\n for (let i = 7; i >= 0; i--) bits.push((bytes.length >> i) & 1);\n\n // Data\n for (const b of bytes) {\n for (let i = 7; i >= 0; i--) bits.push((b >> i) & 1);\n }\n\n // Terminator\n while (bits.length < DATA_CODEWORDS * 8 && bits.length < DATA_CODEWORDS * 8) {\n bits.push(0);\n if (bits.length >= DATA_CODEWORDS * 8) break;\n }\n\n // Pad to byte boundary\n while (bits.length % 8 !== 0) bits.push(0);\n\n // Pad codewords\n const padBytes = [0xec, 0x11];\n let padIdx = 0;\n while (bits.length < DATA_CODEWORDS * 8) {\n const pb = padBytes[padIdx % 2];\n for (let i = 7; i >= 0; i--) bits.push((pb >> i) & 1);\n padIdx++;\n }\n\n // Convert to bytes\n const codewords: number[] = [];\n for (let i = 0; i < bits.length; i += 8) {\n let val = 0;\n for (let j = 0; j < 8; j++) val = (val << 1) | (bits[i + j] || 0);\n codewords.push(val);\n }\n\n return codewords;\n}\n\n// GF(256) arithmetic for Reed-Solomon\nconst GF_EXP = new Array(512).fill(0);\nconst GF_LOG = new Array(256).fill(0);\n\n(function initGF() {\n let x = 1;\n for (let i = 0; i < 255; i++) {\n GF_EXP[i] = x;\n GF_LOG[x] = i;\n x <<= 1;\n if (x & 0x100) x ^= 0x11d;\n }\n for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255];\n})();\n\nfunction gfMul(a: number, b: number): number {\n if (a === 0 || b === 0) return 0;\n return GF_EXP[GF_LOG[a] + GF_LOG[b]];\n}\n\nfunction rsEncode(data: number[], ecCount: number): number[] {\n // Generate generator polynomial\n let gen = [1];\n for (let i = 0; i < ecCount; i++) {\n const next = new Array(gen.length + 1).fill(0);\n for (let j = 0; j < gen.length; j++) {\n next[j] ^= gen[j];\n next[j + 1] ^= gfMul(gen[j], GF_EXP[i]);\n }\n gen = next;\n }\n\n const msg = [...data, ...new Array(ecCount).fill(0)];\n for (let i = 0; i < data.length; i++) {\n const coef = msg[i];\n if (coef !== 0) {\n for (let j = 0; j < gen.length; j++) {\n msg[i + j] ^= gfMul(gen[j], coef);\n }\n }\n }\n\n return msg.slice(data.length);\n}\n\nfunction placeData(matrix: number[][], dataBits: number[]): void {\n let bitIdx = 0;\n let upward = true;\n\n for (let right = SIZE - 1; right >= 1; right -= 2) {\n if (right === 6) right = 5; // skip timing column\n\n const rows = upward\n ? Array.from({ length: SIZE }, (_, i) => SIZE - 1 - i)\n : Array.from({ length: SIZE }, (_, i) => i);\n\n for (const row of rows) {\n for (let c = 0; c < 2; c++) {\n const col = right - c;\n if (matrix[row][col] !== -1) continue;\n matrix[row][col] = bitIdx < dataBits.length ? dataBits[bitIdx++] : 0;\n }\n }\n upward = !upward;\n }\n}\n\nfunction applyMask0(matrix: number[][], reserved: number[][]): void {\n for (let r = 0; r < SIZE; r++) {\n for (let c = 0; c < SIZE; c++) {\n if (reserved[r][c] !== -1) continue;\n if ((r + c) % 2 === 0) {\n matrix[r][c] ^= 1;\n }\n }\n }\n}\n\nexport interface QrOptions {\n cellSize?: number;\n fgColor?: string;\n bgColor?: string;\n finderColor?: string;\n logoBase64?: string; // data URI for center logo\n logoWidth?: number; // logo width in modules (default: 7)\n logoHeight?: number; // logo height in modules (default: 5)\n}\n\nexport function generateQrSvg(text: string, opts: QrOptions | number = 4): string {\n // Backward compat: accept bare cellSize number\n const options: QrOptions = typeof opts === \"number\" ? { cellSize: opts } : opts;\n const cellSize = options.cellSize ?? 4;\n const fgColor = options.fgColor ?? \"#000\";\n const bgColor = options.bgColor ?? \"#fff\";\n const finderColor = options.finderColor ?? fgColor;\n\n const matrix = createMatrix();\n\n // Finder patterns\n addFinderPattern(matrix, 0, 0);\n addFinderPattern(matrix, 0, SIZE - 7);\n addFinderPattern(matrix, SIZE - 7, 0);\n\n // Alignment pattern (version 2: at position 18)\n addAlignmentPattern(matrix, 18, 18);\n\n // Timing\n addTimingPatterns(matrix);\n\n // Format info placeholder\n addFormatInfo(matrix);\n\n // Save reserved areas\n const reserved = matrix.map(row => [...row]);\n\n // Encode data + EC\n const dataCodewords = encodeData(text);\n const ecCodewords = rsEncode(dataCodewords, EC_CODEWORDS);\n const allCodewords = [...dataCodewords, ...ecCodewords];\n\n // Convert to bits\n const dataBits: number[] = [];\n for (const cw of allCodewords) {\n for (let i = 7; i >= 0; i--) dataBits.push((cw >> i) & 1);\n }\n\n // Place data\n placeData(matrix, dataBits);\n\n // Apply mask 0\n applyMask0(matrix, reserved);\n\n // Re-apply format info (mask may have flipped it)\n addFormatInfo(matrix);\n\n // Logo exclusion zone (center of QR)\n const logoW = options.logoWidth ?? 7;\n const logoH = options.logoHeight ?? 5;\n const logoStartC = Math.floor((SIZE - logoW) / 2);\n const logoStartR = Math.floor((SIZE - logoH) / 2);\n const hasLogo = !!options.logoBase64;\n\n // Generate SVG\n const svgSize = SIZE * cellSize;\n let svg = `<svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" width=\"${svgSize}\" height=\"${svgSize}\" viewBox=\"0 0 ${svgSize} ${svgSize}\">`;\n svg += `<rect width=\"${svgSize}\" height=\"${svgSize}\" fill=\"${bgColor}\" rx=\"4\"/>`;\n\n // Finder pattern regions for coloring\n const isFinderModule = (r: number, c: number): boolean =>\n (r < 7 && c < 7) || (r < 7 && c >= SIZE - 7) || (r >= SIZE - 7 && c < 7);\n\n for (let r = 0; r < SIZE; r++) {\n for (let c = 0; c < SIZE; c++) {\n // Skip logo area\n if (hasLogo && r >= logoStartR && r < logoStartR + logoH && c >= logoStartC && c < logoStartC + logoW) {\n continue;\n }\n if (matrix[r][c] === 1) {\n const color = isFinderModule(r, c) ? finderColor : fgColor;\n svg += `<rect x=\"${c * cellSize}\" y=\"${r * cellSize}\" width=\"${cellSize}\" height=\"${cellSize}\" fill=\"${color}\" rx=\"0.5\"/>`;\n }\n }\n }\n\n // Embed logo in center\n if (hasLogo && options.logoBase64) {\n const lx = logoStartC * cellSize;\n const ly = logoStartR * cellSize;\n const lw = logoW * cellSize;\n const lh = logoH * cellSize;\n // White background behind logo\n svg += `<rect x=\"${lx - 1}\" y=\"${ly - 1}\" width=\"${lw + 2}\" height=\"${lh + 2}\" fill=\"${bgColor}\" rx=\"3\"/>`;\n svg += `<image x=\"${lx + 2}\" y=\"${ly + 2}\" width=\"${lw - 4}\" height=\"${lh - 4}\" href=\"${options.logoBase64}\" xlink:href=\"${options.logoBase64}\" preserveAspectRatio=\"xMidYMid meet\"/>`;\n }\n\n svg += '</svg>';\n return svg;\n}\n","import type { WalletData } from \"./types.js\";\nimport { createPublicClient, http, formatEther, formatUnits } from \"viem\";\nimport { base } from \"viem/chains\";\n\nconst USDC_ADDRESS = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\" as const;\nconst DEFAULT_BASE_RPC_URL = \"https://mainnet.base.org\";\nconst PUBLIC_BASE_RPC_URLS = [\n DEFAULT_BASE_RPC_URL,\n \"https://base-rpc.publicnode.com\",\n \"https://base.drpc.org\",\n \"https://1rpc.io/base\",\n] as const;\nconst USDC_ABI = [\n {\n name: \"balanceOf\",\n type: \"function\",\n stateMutability: \"view\",\n inputs: [{ name: \"account\", type: \"address\" }],\n outputs: [{ name: \"\", type: \"uint256\" }],\n },\n] as const;\n\nexport async function getBalanceUsdc(wallet: WalletData): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL;\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ];\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) });\n const balance = await client.readContract({\n address: USDC_ADDRESS,\n abi: USDC_ABI,\n functionName: \"balanceOf\",\n args: [wallet.address as `0x${string}`],\n });\n return formatUnits(balance, 6);\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return \"unknown\";\n}\n\nexport async function getBalanceEth(wallet: WalletData): Promise<string> {\n const envRpcUrl = process.env.BASE_RPC_URL;\n const rpcUrls = [\n ...(envRpcUrl ? [envRpcUrl] : []),\n ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl),\n ];\n\n for (const rpcUrl of rpcUrls) {\n try {\n const client = createPublicClient({ chain: base, transport: http(rpcUrl) });\n const balance = await client.getBalance({ address: wallet.address as `0x${string}` });\n return formatEther(balance);\n } catch {\n // Try the next public Base RPC endpoint.\n }\n }\n\n return \"unknown\";\n}\n\nexport async function getBalance(wallet: WalletData): Promise<string> {\n const [balanceUsdc, balanceEth] = await Promise.all([\n getBalanceUsdc(wallet),\n getBalanceEth(wallet),\n ]);\n return [\n `Balance: ${balanceUsdc} USDC`,\n `Gas: ${balanceEth} ETH on Base`,\n `Network: Base`,\n `Base ETH is required for one-time USDC Permit2 approval gas.`,\n `Address: ${wallet.address}`,\n ].filter(Boolean).join(\"\\n\");\n}\n\nexport function getTopupInfo(wallet: WalletData): string {\n return JSON.stringify({\n wallet_address: wallet.address,\n network: \"Base (Chain ID 8453)\",\n token: \"USDC\",\n contract: USDC_ADDRESS,\n instructions: [\n `Send USDC on Base network to: ${wallet.address}`,\n ],\n });\n}\n","import { createServer, type Server } from \"node:http\";\nimport { readFileSync } from \"node:fs\";\nimport { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { WalletData } from \"./types.js\";\nimport { generateQrSvg } from \"./qr.js\";\nimport { getBalanceEth, getBalanceUsdc } from \"./tools.js\";\n\nconst USDC_ADDRESS = \"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913\";\nconst BASE_CHAIN_ID = \"0x2105\"; // 8453\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n// Pre-load assets — try dist/assets first (installed), then src/assets (dev)\nfunction loadAsset(name: string): Buffer {\n const paths = [\n join(__dirname, \"assets\", name), // dist/assets/ (global install)\n join(__dirname, \"..\", \"src\", \"assets\", name), // src/assets/ (dev)\n ];\n for (const p of paths) {\n try { return readFileSync(p); } catch { /* try next */ }\n }\n console.error(`[chain-insights] Warning: asset ${name} not found`);\n return Buffer.alloc(0);\n}\n\nconst logoPng = loadAsset(\"logo.png\");\nconst bgPatternPng = loadAsset(\"bg-pattern.png\");\n\nlet server: Server | null = null;\nlet serverPort: number | null = null;\n\nexport function getTopupUrl(): string | null {\n return serverPort ? `http://localhost:${serverPort}` : null;\n}\n\nexport async function startTopupServer(wallet: WalletData): Promise<string> {\n if (server && serverPort) {\n return `http://localhost:${serverPort}`;\n }\n\n return new Promise((resolve, reject) => {\n server = createServer((req, res) => {\n if (req.url === \"/api/wallet\") {\n res.writeHead(200, { \"Content-Type\": \"application/json\", \"Access-Control-Allow-Origin\": \"*\" });\n res.end(JSON.stringify({ address: wallet.address }));\n return;\n }\n\n if (req.url === \"/api/balance\") {\n Promise.all([getBalanceUsdc(wallet), getBalanceEth(wallet)]).then(([balanceUsdc, balanceEth]) => {\n res.writeHead(200, { \"Content-Type\": \"application/json\", \"Access-Control-Allow-Origin\": \"*\" });\n res.end(JSON.stringify({ balance_usdc: balanceUsdc, balance_eth: balanceEth }));\n }).catch(() => {\n res.writeHead(200, { \"Content-Type\": \"application/json\", \"Access-Control-Allow-Origin\": \"*\" });\n res.end(JSON.stringify({ balance_usdc: \"unknown\", balance_eth: \"unknown\" }));\n });\n return;\n }\n\n if (req.url === \"/assets/logo.png\") {\n res.writeHead(200, { \"Content-Type\": \"image/png\", \"Cache-Control\": \"public, max-age=86400\", \"Access-Control-Allow-Origin\": \"*\" });\n res.end(logoPng);\n return;\n }\n\n if (req.url === \"/assets/bg-pattern.png\") {\n res.writeHead(200, { \"Content-Type\": \"image/png\", \"Cache-Control\": \"public, max-age=86400\", \"Access-Control-Allow-Origin\": \"*\" });\n res.end(bgPatternPng);\n return;\n }\n\n res.writeHead(200, { \"Content-Type\": \"text/html\" });\n res.end(generatePage(wallet.address));\n });\n\n server.listen(0, \"127.0.0.1\", () => {\n const addr = server!.address();\n if (addr && typeof addr === \"object\") {\n serverPort = addr.port;\n const url = `http://localhost:${serverPort}`;\n console.error(`[chain-insights] Topup server running at ${url}`);\n resolve(url);\n } else {\n reject(new Error(\"Failed to start topup server\"));\n }\n });\n\n server.on(\"error\", reject);\n });\n}\n\nexport function generateArtifactHtml(walletAddress: string, topupUrl: string): string {\n const LOGO_B64 = \"data:image/png;base64,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\";\n\n // Generate QR code SVG server-side — standard B&W, 2x size\n const qrSvg = generateQrSvg(walletAddress, { cellSize: 8 });\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1) url('${topupUrl}/assets/bg-pattern.png') top left / contain no-repeat;\n color: rgba(255, 255, 255, 0.9);\n display: flex;\n justify-content: center;\n padding: 24px;\n line-height: 1.3;\n }\n .card {\n max-width: 400px;\n width: 100%;\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 28px 24px;\n text-align: center;\n }\n .logo { margin-bottom: 8px; }\n .logo img { height: 24px; }\n .subtitle { color: rgba(255, 255, 255, 0.5); font-size: 13px; margin-bottom: 20px; }\n .qr { margin-bottom: 16px; }\n .qr svg { border-radius: 12px; background: #fff; padding: 10px; }\n .address-wrap {\n position: relative;\n margin-bottom: 16px;\n }\n .address {\n width: 100%;\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 10px 14px;\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 11px; color: #f2dda6; word-break: break-all;\n cursor: pointer; text-align: center;\n transition: border-color 0.3s linear;\n -webkit-user-select: all; user-select: all;\n outline: none;\n }\n .address:focus { border-color: #ae9d71; }\n .address-hint {\n font-size: 10px; color: rgba(255,255,255,0.3);\n text-align: center; margin-top: 4px;\n }\n .badge {\n display: inline-flex; align-items: center; gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px; padding: 5px 12px;\n font-size: 11px; color: rgba(255, 255, 255, 0.5); margin-bottom: 20px;\n }\n .badge .dot { width: 7px; height: 7px; border-radius: 50%; background: #f2dda6; }\n .balance-line {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 20px;\n line-height: 1.4;\n }\n .balance-line .amount {\n color: #4feb69;\n font-weight: 600;\n }\n .balance-line .gas {\n color: #f2dda6;\n font-weight: 600;\n }\n @keyframes flash { 0%{opacity:0.4} 100%{opacity:1} }\n .balance-line.flash .amount { animation: flash 1s ease-out; }\n .hint {\n margin-top: 14px; font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.5;\n }\n</style>\n</head>\n<body>\n<div class=\"card\">\n <div class=\"logo\"><img src=\"${topupUrl}/assets/logo.png\" alt=\"Chain Insights\"></div>\n <p class=\"subtitle\">Fund your wallet with USDC on Base</p>\n <div class=\"qr\">${qrSvg}</div>\n <div class=\"address-wrap\">\n <div class=\"address\" id=\"addr\" onclick=\"selectAndCopy()\">${walletAddress}</div>\n <div class=\"address-hint\" id=\"addrHint\">Click to copy address</div>\n </div>\n <div class=\"badge\"><span class=\"dot\"></span>Base Network · USDC</div>\n <p class=\"balance-line\" id=\"balLine\">Current balance: <span class=\"amount\" id=\"bal\">--</span> USDC<br>Gas balance: <span class=\"gas\" id=\"gas\">--</span> ETH<br>Base ETH is used for one-time approval gas.</p>\n</div>\n<script>\n// MCP Apps protocol handshake (matches @modelcontextprotocol/ext-apps App.connect())\n(function() {\n var initId = 1;\n\n // Listen for host messages\n window.addEventListener('message', function(event) {\n var data = event.data;\n if (!data || data.jsonrpc !== '2.0') return;\n\n // Initialize response\n if (data.id === initId && data.result) {\n // Send initialized notification\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/initialized',\n params: {}\n }, '*');\n\n // Send initial size\n var rect = document.documentElement.getBoundingClientRect();\n window.parent.postMessage({\n jsonrpc: '2.0',\n method: 'ui/notifications/size-changed',\n params: { width: Math.ceil(rect.width), height: Math.ceil(rect.height) }\n }, '*');\n }\n\n // Respond to pings\n if (data.method === 'ping' && data.id != null) {\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: data.id,\n result: {}\n }, '*');\n }\n });\n\n // Send initialize request (must match App class protocol)\n window.parent.postMessage({\n jsonrpc: '2.0',\n id: initId,\n method: 'ui/initialize',\n params: {\n appInfo: { name: 'Chain Insights Topup', version: '1.0.0' },\n appCapabilities: {},\n protocolVersion: '2026-01-26'\n }\n }, '*');\n})();\n\n// Live balance polling\nvar lastBal = null;\nfunction fetchBal() {\n fetch('${topupUrl}/api/balance')\n .then(function(r) { return r.json(); })\n .then(function(d) {\n var el = document.getElementById('bal');\n var gas = document.getElementById('gas');\n var line = document.getElementById('balLine');\n var val = parseFloat(d.balance_usdc || '0').toFixed(2);\n var gasVal = d.balance_eth === 'unknown' ? '--' : parseFloat(d.balance_eth || '0').toFixed(6);\n if (d.balance_usdc === 'unknown') { el.textContent = '--'; gas.textContent = gasVal; return; }\n el.textContent = val;\n gas.textContent = gasVal;\n if (lastBal !== null && val !== lastBal) {\n line.classList.remove('flash');\n void line.offsetWidth;\n line.classList.add('flash');\n }\n lastBal = val;\n })\n .catch(function() {});\n}\nfetchBal();\nsetInterval(fetchBal, 10000);\n\nfunction selectAndCopy() {\n var addr = document.getElementById('addr');\n var hint = document.getElementById('addrHint');\n // Select the text\n var r = document.createRange();\n r.selectNodeContents(addr);\n var s = window.getSelection();\n s.removeAllRanges();\n s.addRange(r);\n // Try every clipboard method available\n var copied = false;\n try { copied = document.execCommand('copy'); } catch(e) {}\n if (!copied && navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(addr.textContent).then(function() {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n }).catch(function() {});\n }\n if (copied) {\n hint.textContent = 'Copied!'; hint.style.color = '#4feb69';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);\n } else {\n hint.textContent = 'Selected — press Ctrl+C'; hint.style.color = '#f2dda6';\n setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 3000);\n }\n}\n<\\/script>\n</body>\n</html>`;\n}\n\nfunction generatePage(walletAddress: string): string {\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Chain Insights — Fund Wallet</title>\n<script src=\"https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js\"></script>\n<style>\n * { margin: 0; padding: 0; box-sizing: border-box; }\n\n body {\n font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;\n background: rgba(10, 12, 17, 1);\n color: rgba(255, 255, 255, 0.9);\n min-height: 100vh;\n display: flex;\n justify-content: center;\n align-items: center;\n line-height: 1.3;\n }\n\n .container {\n max-width: 480px;\n width: 100%;\n padding: 24px;\n }\n\n .logo {\n text-align: center;\n margin-bottom: 32px;\n }\n\n .logo h1 {\n font-size: 24px;\n font-weight: 600;\n color: #f2dda6;\n letter-spacing: -0.5px;\n }\n\n .logo p {\n color: rgba(255, 255, 255, 0.5);\n font-size: 14px;\n margin-top: 4px;\n }\n\n .card {\n background: rgba(19, 19, 24, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 12px;\n padding: 32px 24px;\n }\n\n .qr-container {\n display: flex;\n justify-content: center;\n margin-bottom: 24px;\n }\n\n .qr-container canvas, .qr-container img {\n border-radius: 12px;\n background: #fff;\n padding: 12px;\n }\n\n .address-box {\n background: rgba(10, 12, 17, 1);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n padding: 12px 16px;\n display: flex;\n align-items: center;\n gap: 8px;\n margin-bottom: 24px;\n }\n\n .address-box code {\n font-family: 'SF Mono', 'Fira Code', monospace;\n font-size: 12px;\n color: #f2dda6;\n word-break: break-all;\n flex: 1;\n }\n\n .copy-btn {\n background: none;\n border: none;\n color: rgba(255, 255, 255, 0.4);\n cursor: pointer;\n padding: 4px;\n font-size: 18px;\n transition: color 0.3s linear;\n }\n\n .copy-btn:hover { color: #f2dda6; }\n .copy-btn.copied { color: #4feb69; }\n\n .network-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n background: rgba(255, 255, 255, 0.04);\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 20px;\n padding: 6px 12px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 24px;\n }\n\n .network-badge .dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n background: #f2dda6;\n }\n\n .balance-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 0;\n border-top: 1px solid rgba(255, 255, 255, 0.06);\n margin-bottom: 16px;\n }\n\n .balance-label { color: rgba(255, 255, 255, 0.5); font-size: 14px; }\n\n .balance-value {\n font-size: 20px;\n font-weight: 600;\n color: rgba(255, 255, 255, 0.9);\n }\n\n .balance-value .currency {\n font-size: 14px;\n color: rgba(255, 255, 255, 0.5);\n font-weight: 400;\n margin-left: 4px;\n }\n\n .gas-note {\n margin-top: -8px;\n margin-bottom: 16px;\n color: rgba(255,255,255,0.45);\n font-size: 12px;\n line-height: 1.4;\n text-align: left;\n }\n\n .metamask-btn {\n width: 100%;\n padding: 14px;\n border: none;\n border-radius: 10px;\n font-size: 15px;\n font-weight: 600;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n transition: all 0.2s;\n background: #f6851b;\n color: #fff;\n }\n\n .metamask-btn:hover { background: #e2761b; transform: translateY(-1px); }\n .metamask-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }\n\n .metamask-btn svg { width: 22px; height: 22px; }\n\n .amount-input {\n width: 100%;\n padding: 12px 16px;\n background: #0a0c11;\n border: 1px solid rgba(255, 255, 255, 0.06);\n border-radius: 8px;\n color: rgba(255, 255, 255, 0.9);\n font-size: 16px;\n margin-bottom: 16px;\n outline: none;\n transition: border-color 0.2s;\n }\n\n .amount-input:focus { border-color: #f2dda6; }\n\n .amount-label {\n font-size: 13px;\n color: rgba(255, 255, 255, 0.5);\n margin-bottom: 6px;\n display: block;\n }\n\n .status {\n text-align: center;\n padding: 12px;\n border-radius: 8px;\n font-size: 14px;\n margin-top: 16px;\n display: none;\n }\n\n .status.success { display: block; background: #0a2e1a; color: #4feb69; border: 1px solid #1a4a2e; }\n .status.error { display: block; background: #2e0a0a; color: #eb4f4f; border: 1px solid #4a1a1a; }\n .status.pending { display: block; background: #1a1a0a; color: #f2dda6; border: 1px solid #4a4a1a; }\n\n .info {\n text-align: center;\n margin-top: 24px;\n font-size: 12px;\n color: rgba(255, 255, 255, 0.3);\n line-height: 1.6;\n }\n</style>\n</head>\n<body>\n<div class=\"container\">\n <div class=\"logo\">\n <h1>Chain Insights</h1>\n <p>Fund your wallet to use blockchain intelligence tools</p>\n </div>\n\n <div class=\"card\">\n <div class=\"qr-container\" id=\"qr\"></div>\n\n <div class=\"address-box\">\n <code id=\"address\">${walletAddress}</code>\n <button class=\"copy-btn\" onclick=\"copyAddress()\" id=\"copyBtn\" title=\"Copy address\">⎘</button>\n </div>\n\n <div class=\"network-badge\">\n <span class=\"dot\"></span>\n Base Network · USDC\n </div>\n\n <div class=\"balance-row\">\n <span class=\"balance-label\">Current balance</span>\n <span class=\"balance-value\" id=\"balance\">—<span class=\"currency\">USDC</span></span>\n </div>\n <div class=\"balance-row\">\n <span class=\"balance-label\">Gas balance</span>\n <span class=\"balance-value\" id=\"gasBalance\">—<span class=\"currency\">ETH</span></span>\n </div>\n <p class=\"gas-note\">Base ETH is used for one-time approval gas.</p>\n\n <div class=\"status\" id=\"status\"></div>\n </div>\n\n <p class=\"info\">Send Base USDC to the wallet address above.</p>\n</div>\n\n<script>\nconst WALLET = '${walletAddress}';\nconst USDC = '${USDC_ADDRESS}';\nconst CHAIN_ID = '${BASE_CHAIN_ID}';\n\n// QR code\n(function() {\n var qr = qrcode(0, 'M');\n qr.addData(WALLET);\n qr.make();\n document.getElementById('qr').innerHTML = qr.createSvgTag(5, 0);\n var svg = document.querySelector('#qr svg');\n if (svg) { svg.style.borderRadius = '12px'; svg.style.background = '#fff'; svg.style.padding = '12px'; }\n})();\n\n// Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked\nfunction copyAddress() {\n var ok = false;\n // Try modern clipboard API first\n if (navigator.clipboard && navigator.clipboard.writeText) {\n navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});\n }\n // Fallback: hidden textarea + execCommand\n if (!ok) {\n var ta = document.createElement('textarea');\n ta.value = WALLET;\n ta.style.position = 'fixed';\n ta.style.left = '-9999px';\n document.body.appendChild(ta);\n ta.select();\n try { document.execCommand('copy'); } catch(e) {}\n document.body.removeChild(ta);\n }\n var btn = document.getElementById('copyBtn');\n btn.classList.add('copied');\n btn.innerHTML = '✓';\n setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '⎘'; }, 2000);\n}\n\n// Fetch balance\nasync function fetchBalance() {\n try {\n var resp = await fetch('/api/balance');\n var json = await resp.json();\n if (json.balance_usdc === 'unknown') throw new Error('balance unavailable');\n var balance = Number(json.balance_usdc || 0).toFixed(2);\n document.getElementById('balance').innerHTML = balance + '<span class=\"currency\">USDC</span>';\n var gasBalance = json.balance_eth === 'unknown' ? '—' : Number(json.balance_eth || 0).toFixed(6);\n document.getElementById('gasBalance').innerHTML = gasBalance + '<span class=\"currency\">ETH</span>';\n } catch(e) {\n document.getElementById('balance').innerHTML = '—<span class=\"currency\">USDC</span>';\n document.getElementById('gasBalance').innerHTML = '—<span class=\"currency\">ETH</span>';\n }\n}\nfetchBalance();\nsetInterval(fetchBalance, 15000);\n\n</script>\n</body>\n</html>`;\n}\n","import {\n generateArtifactHtml,\n getTopupUrl as getCopiedTopupUrl,\n startTopupServer as startCopiedTopupServer,\n} from './mcp-proxy/topup-server.js'\nimport { createServer, type Server, type ServerResponse } from 'node:http'\nimport type { WalletData } from './mcp-proxy/types.js'\nimport type { PaymentWalletAccount } from './tools.js'\n\nconst FALLBACK_PRIVATE_KEY = `0x${'0'.repeat(63)}1`\n\ninterface ArtifactServerState {\n address: string\n assetServerUrl: string\n server: Server\n url: string\n}\n\nlet artifactServerState: ArtifactServerState | null = null\n\nfunction toWalletData(account: PaymentWalletAccount | string): WalletData {\n if (typeof account === 'string') {\n return {\n address: account,\n privateKey: FALLBACK_PRIVATE_KEY,\n createdAt: new Date(0).toISOString(),\n }\n }\n\n return {\n address: account.address,\n privateKey: account.privateKey,\n createdAt: new Date(0).toISOString(),\n }\n}\n\nfunction send(res: ServerResponse, status: number, body: string | Buffer, contentType: string): void {\n res.writeHead(status, {\n 'content-type': contentType,\n 'cache-control': 'no-store',\n 'access-control-allow-origin': '*',\n })\n res.end(body)\n}\n\nasync function proxyToCopiedServer(reqUrl: string, res: ServerResponse, assetServerUrl: string): Promise<void> {\n const upstreamUrl = new URL(reqUrl, assetServerUrl)\n const upstream = await fetch(upstreamUrl)\n const contentType = upstream.headers.get('content-type') ?? 'application/octet-stream'\n const body = Buffer.from(await upstream.arrayBuffer())\n send(res, upstream.status, body, contentType)\n}\n\nexport { generateArtifactHtml }\n\nexport function getTopupArtifactUrl(): string | null {\n return artifactServerState?.url ?? null\n}\n\nexport function getTopupUrl(): string | null {\n return getTopupArtifactUrl() ?? getCopiedTopupUrl()\n}\n\nexport async function startTopupServer(account: PaymentWalletAccount | string): Promise<string> {\n const wallet = toWalletData(account)\n\n if (artifactServerState && artifactServerState.address.toLowerCase() === wallet.address.toLowerCase()) {\n return artifactServerState.url\n }\n\n const assetServerUrl = await startCopiedTopupServer(wallet)\n\n if (artifactServerState) {\n await new Promise<void>((resolve) => artifactServerState?.server.close(() => resolve()))\n artifactServerState = null\n }\n\n const server = createServer((req, res) => {\n const reqUrl = req.url ?? '/'\n const pathname = new URL(reqUrl, 'http://localhost').pathname\n\n if (pathname === '/' || pathname === '/index.html') {\n const artifactUrl = artifactServerState?.url ?? assetServerUrl\n send(res, 200, generateArtifactHtml(wallet.address, artifactUrl), 'text/html; charset=utf-8')\n return\n }\n\n if (pathname.startsWith('/assets/') || pathname.startsWith('/api/')) {\n void proxyToCopiedServer(reqUrl, res, assetServerUrl).catch((err) => {\n send(res, 502, JSON.stringify({ error: (err as Error).message }) + '\\n', 'application/json; charset=utf-8')\n })\n return\n }\n\n send(res, 404, JSON.stringify({ error: 'Not found' }) + '\\n', 'application/json; charset=utf-8')\n })\n\n const url = await new Promise<string>((resolve, reject) => {\n server.once('error', reject)\n server.listen(0, '127.0.0.1', () => {\n const addressInfo = server.address()\n if (!addressInfo || typeof addressInfo === 'string') {\n reject(new Error('Failed to start topup artifact server'))\n return\n }\n resolve(`http://localhost:${addressInfo.port}`)\n })\n })\n\n artifactServerState = {\n address: wallet.address,\n assetServerUrl,\n server,\n url,\n }\n\n return url\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
//#region src/version.ts
|
|
3
|
+
const PACKAGE_INFO = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
4
|
+
const PACKAGE_VERSION = PACKAGE_INFO.version;
|
|
5
|
+
//#endregion
|
|
6
|
+
export { PACKAGE_VERSION as n, PACKAGE_INFO as t };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=version-1gP19Lhi.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version-1gP19Lhi.mjs","names":[],"sources":["../src/version.ts"],"sourcesContent":["import { readFileSync } from 'node:fs'\n\nexport const PACKAGE_INFO = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8')\n) as { name: string; version: string }\n\nexport const PACKAGE_VERSION = PACKAGE_INFO.version\n"],"mappings":";;AAEA,MAAa,eAAe,KAAK,MAC/B,aAAa,IAAI,IAAI,mBAAmB,OAAO,KAAK,IAAI,EAAE,OAAO,CAClE;AAED,MAAa,kBAAkB,aAAa"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_fs = require("node:fs");
|
|
3
|
+
//#region src/version.ts
|
|
4
|
+
const PACKAGE_INFO = JSON.parse((0, node_fs.readFileSync)(new URL("../package.json", require("url").pathToFileURL(__filename).href), "utf8"));
|
|
5
|
+
const PACKAGE_VERSION = PACKAGE_INFO.version;
|
|
6
|
+
//#endregion
|
|
7
|
+
Object.defineProperty(exports, "PACKAGE_INFO", {
|
|
8
|
+
enumerable: true,
|
|
9
|
+
get: function() {
|
|
10
|
+
return PACKAGE_INFO;
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
Object.defineProperty(exports, "PACKAGE_VERSION", {
|
|
14
|
+
enumerable: true,
|
|
15
|
+
get: function() {
|
|
16
|
+
return PACKAGE_VERSION;
|
|
17
|
+
}
|
|
18
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import { n as truncateGraph } from "./data-extractor-DFzsa5CS.mjs";
|
|
3
|
+
import { r as writeVizHtml, t as generateHtml } from "./html-generator-V6Bp0uRb.mjs";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
//#region src/viz/index.ts
|
|
6
|
+
var viz_exports = /* @__PURE__ */ __exportAll({ generateVisualization: () => generateVisualization });
|
|
7
|
+
async function generateVisualization(opts) {
|
|
8
|
+
let rawData;
|
|
9
|
+
if (opts.dataFile) {
|
|
10
|
+
const content = await readFile(opts.dataFile, "utf-8");
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(content);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.");
|
|
16
|
+
}
|
|
17
|
+
const { extractGraphFromJson } = await import("./data-extractor-DFzsa5CS.mjs").then((n) => n.t);
|
|
18
|
+
rawData = extractGraphFromJson(parsed);
|
|
19
|
+
} else if (opts.caseId) {
|
|
20
|
+
const { extractGraphFromCase } = await import("./data-extractor-DFzsa5CS.mjs").then((n) => n.t);
|
|
21
|
+
const extracted = await extractGraphFromCase(opts.caseId);
|
|
22
|
+
if (extracted.nodes.length === 0) throw new Error("No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.");
|
|
23
|
+
rawData = extracted;
|
|
24
|
+
} else throw new Error("Provide either a case ID or --data <file.json>");
|
|
25
|
+
const data = truncateGraph(rawData);
|
|
26
|
+
const vizId = opts.caseId ? `${opts.caseId}_${Date.now()}` : `adhoc_${Date.now()}`;
|
|
27
|
+
return {
|
|
28
|
+
vizId,
|
|
29
|
+
htmlPath: await writeVizHtml(vizId, generateHtml(data, data.metadata.caseId ? `${data.metadata.caseId} - Money Flow` : "Ad-hoc Visualization"), opts.caseId)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
export { viz_exports as n, generateVisualization as t };
|
|
34
|
+
|
|
35
|
+
//# sourceMappingURL=viz-BlCJe6Tk.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viz-BlCJe6Tk.mjs","names":[],"sources":["../src/viz/index.ts"],"sourcesContent":["export { GraphData, GraphNode, GraphEdge, EntityType, RiskLevel, truncateGraph } from './graph-model.js'\nexport type { GraphData as GraphDataType, GraphNode as GraphNodeType, GraphEdge as GraphEdgeType } from './graph-model.js'\nexport { generateHtml, writeVizHtml, transformToGraphHtml } from './html-generator.js'\nexport { DataExtractor, extractGraphFromCase, extractGraphFromJson } from './data-extractor.js'\n\nimport { readFile } from 'node:fs/promises'\nimport { truncateGraph } from './graph-model.js'\nimport { generateHtml, writeVizHtml } from './html-generator.js'\n\nexport async function generateVisualization(opts: {\n caseId?: string\n dataFile?: string\n}): Promise<{ vizId: string; htmlPath: string }> {\n let rawData: unknown\n\n if (opts.dataFile) {\n const content = await readFile(opts.dataFile, 'utf-8')\n let parsed: unknown\n try {\n parsed = JSON.parse(content)\n } catch {\n throw new Error('Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.')\n }\n const { extractGraphFromJson } = await import('./data-extractor.js')\n rawData = extractGraphFromJson(parsed)\n } else if (opts.caseId) {\n const { extractGraphFromCase } = await import('./data-extractor.js')\n const extracted = await extractGraphFromCase(opts.caseId)\n if (extracted.nodes.length === 0) {\n throw new Error('No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.')\n }\n rawData = extracted\n } else {\n throw new Error('Provide either a case ID or --data <file.json>')\n }\n\n const data = truncateGraph(rawData as Parameters<typeof truncateGraph>[0])\n\n const vizId = opts.caseId\n ? `${opts.caseId}_${Date.now()}`\n : `adhoc_${Date.now()}`\n\n const title = data.metadata.caseId\n ? `${data.metadata.caseId} - Money Flow`\n : 'Ad-hoc Visualization'\n\n const html = generateHtml(data, title)\n const htmlPath = await writeVizHtml(vizId, html, opts.caseId)\n\n return { vizId, htmlPath }\n}\n"],"mappings":";;;;;;AASA,eAAsB,sBAAsB,MAGK;CAC/C,IAAI;AAEJ,KAAI,KAAK,UAAU;EACjB,MAAM,UAAU,MAAM,SAAS,KAAK,UAAU,QAAQ;EACtD,IAAI;AACJ,MAAI;AACF,YAAS,KAAK,MAAM,QAAQ;UACtB;AACN,SAAM,IAAI,MAAM,mIAAmI;;EAErJ,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;AAC9C,YAAU,qBAAqB,OAAO;YAC7B,KAAK,QAAQ;EACtB,MAAM,EAAE,yBAAyB,MAAM,OAAO,iCAAA,MAAA,MAAA,EAAA,EAAA;EAC9C,MAAM,YAAY,MAAM,qBAAqB,KAAK,OAAO;AACzD,MAAI,UAAU,MAAM,WAAW,EAC7B,OAAM,IAAI,MAAM,8LAA8L;AAEhN,YAAU;OAEV,OAAM,IAAI,MAAM,iDAAiD;CAGnE,MAAM,OAAO,cAAc,QAA+C;CAE1E,MAAM,QAAQ,KAAK,SACf,GAAG,KAAK,OAAO,GAAG,KAAK,KAAK,KAC5B,SAAS,KAAK,KAAK;AASvB,QAAO;EAAE;EAAO,UAAA,MAFO,aAAa,OADvB,aAAa,MAJZ,KAAK,SAAS,SACxB,GAAG,KAAK,SAAS,OAAO,iBACxB,uBAG2C,EAAE,KAAK,OAAO;EAEnC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
const require_data_extractor = require("./data-extractor-BNGj7ECT.cjs");
|
|
3
|
+
const require_html_generator = require("./html-generator-CAv81IWH.cjs");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
//#region src/viz/index.ts
|
|
6
|
+
var viz_exports = /* @__PURE__ */ require_chunk.__exportAll({ generateVisualization: () => generateVisualization });
|
|
7
|
+
async function generateVisualization(opts) {
|
|
8
|
+
let rawData;
|
|
9
|
+
if (opts.dataFile) {
|
|
10
|
+
const content = await (0, node_fs_promises.readFile)(opts.dataFile, "utf-8");
|
|
11
|
+
let parsed;
|
|
12
|
+
try {
|
|
13
|
+
parsed = JSON.parse(content);
|
|
14
|
+
} catch {
|
|
15
|
+
throw new Error("Invalid transaction data. The input file must contain a JSON array of transaction objects with `from`, `to`, and `value` fields.");
|
|
16
|
+
}
|
|
17
|
+
const { extractGraphFromJson } = await Promise.resolve().then(() => require("./data-extractor-BNGj7ECT.cjs")).then((n) => n.data_extractor_exports);
|
|
18
|
+
rawData = extractGraphFromJson(parsed);
|
|
19
|
+
} else if (opts.caseId) {
|
|
20
|
+
const { extractGraphFromCase } = await Promise.resolve().then(() => require("./data-extractor-BNGj7ECT.cjs")).then((n) => n.data_extractor_exports);
|
|
21
|
+
const extracted = await extractGraphFromCase(opts.caseId);
|
|
22
|
+
if (extracted.nodes.length === 0) throw new Error("No Transaction Data. This case has no evidence with transaction data. Add evidence using `chain-insights evidence add` or provide a JSON file with `chain-insights viz --data <file.json>`.");
|
|
23
|
+
rawData = extracted;
|
|
24
|
+
} else throw new Error("Provide either a case ID or --data <file.json>");
|
|
25
|
+
const data = require_data_extractor.truncateGraph(rawData);
|
|
26
|
+
const vizId = opts.caseId ? `${opts.caseId}_${Date.now()}` : `adhoc_${Date.now()}`;
|
|
27
|
+
return {
|
|
28
|
+
vizId,
|
|
29
|
+
htmlPath: await require_html_generator.writeVizHtml(vizId, require_html_generator.generateHtml(data, data.metadata.caseId ? `${data.metadata.caseId} - Money Flow` : "Ad-hoc Visualization"), opts.caseId)
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
//#endregion
|
|
33
|
+
Object.defineProperty(exports, "generateVisualization", {
|
|
34
|
+
enumerable: true,
|
|
35
|
+
get: function() {
|
|
36
|
+
return generateVisualization;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(exports, "viz_exports", {
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get: function() {
|
|
42
|
+
return viz_exports;
|
|
43
|
+
}
|
|
44
|
+
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-wcPFST8Q.mjs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import crypto from "node:crypto";
|
|
6
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
7
|
+
//#region src/wallet/index.ts
|
|
8
|
+
var wallet_exports = /* @__PURE__ */ __exportAll({
|
|
9
|
+
decryptKey: () => decryptKey,
|
|
10
|
+
encryptKey: () => encryptKey,
|
|
11
|
+
isWalletConfigured: () => isWalletConfigured,
|
|
12
|
+
normalizeWalletPrivateKey: () => normalizeWalletPrivateKey,
|
|
13
|
+
setWalletPrivateKey: () => setWalletPrivateKey,
|
|
14
|
+
walletAddressFromPrivateKey: () => walletAddressFromPrivateKey,
|
|
15
|
+
walletPath: () => walletPath
|
|
16
|
+
});
|
|
17
|
+
function walletPath() {
|
|
18
|
+
return path.join(os.homedir(), ".chain-insights", "wallet.json");
|
|
19
|
+
}
|
|
20
|
+
function deriveKey(salt) {
|
|
21
|
+
return crypto.scryptSync(`${os.hostname()}:${os.userInfo().username}`, salt, 32);
|
|
22
|
+
}
|
|
23
|
+
function normalizeWalletPrivateKey(value) {
|
|
24
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) throw new Error("Stored wallet private key is not a valid 0x-prefixed EVM private key");
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function walletAddressFromPrivateKey(privateKey) {
|
|
28
|
+
return privateKeyToAccount(normalizeWalletPrivateKey(privateKey)).address;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Encrypts a private key and writes it to ~/.chain-insights/wallet.json.
|
|
32
|
+
* Uses AES-256-GCM with a machine-identity-derived key and a random per-wallet salt.
|
|
33
|
+
* File is written with 0o600 permissions (owner read/write only).
|
|
34
|
+
*
|
|
35
|
+
* @param privateKey - The EVM private key to encrypt (0x-prefixed)
|
|
36
|
+
*/
|
|
37
|
+
async function encryptKey(privateKey) {
|
|
38
|
+
const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey);
|
|
39
|
+
const salt = crypto.randomBytes(16);
|
|
40
|
+
const key = deriveKey(salt);
|
|
41
|
+
const iv = crypto.randomBytes(12);
|
|
42
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
43
|
+
const encrypted = Buffer.concat([cipher.update(normalizedPrivateKey, "utf8"), cipher.final()]);
|
|
44
|
+
const tag = cipher.getAuthTag();
|
|
45
|
+
const walletData = {
|
|
46
|
+
salt: salt.toString("hex"),
|
|
47
|
+
iv: iv.toString("hex"),
|
|
48
|
+
tag: tag.toString("hex"),
|
|
49
|
+
data: encrypted.toString("hex")
|
|
50
|
+
};
|
|
51
|
+
const p = walletPath();
|
|
52
|
+
await mkdir(path.dirname(p), { recursive: true });
|
|
53
|
+
await writeFile(p, JSON.stringify(walletData, null, 2) + "\n", { mode: 384 });
|
|
54
|
+
}
|
|
55
|
+
async function setWalletPrivateKey(privateKey) {
|
|
56
|
+
const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey);
|
|
57
|
+
const address = walletAddressFromPrivateKey(normalizedPrivateKey);
|
|
58
|
+
await encryptKey(normalizedPrivateKey);
|
|
59
|
+
return address;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Reads and decrypts the private key from ~/.chain-insights/wallet.json.
|
|
63
|
+
* Throws a human-readable error if wallet is absent or decryption fails.
|
|
64
|
+
*
|
|
65
|
+
* @returns The decrypted EVM private key string
|
|
66
|
+
*/
|
|
67
|
+
async function decryptKey() {
|
|
68
|
+
let raw;
|
|
69
|
+
try {
|
|
70
|
+
raw = await readFile(walletPath(), "utf8");
|
|
71
|
+
} catch (err) {
|
|
72
|
+
if (err.code === "ENOENT") throw new Error("Wallet not configured. Run `chain-insights config set walletPrivateKey <key>` to enable paid MCP calls");
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const stored = JSON.parse(raw);
|
|
77
|
+
const key = deriveKey(Buffer.from(stored.salt, "hex"));
|
|
78
|
+
const iv = Buffer.from(stored.iv, "hex");
|
|
79
|
+
const tag = Buffer.from(stored.tag, "hex");
|
|
80
|
+
const encrypted = Buffer.from(stored.data, "hex");
|
|
81
|
+
const decipher = crypto.createDecipheriv("aes-256-gcm", key, iv);
|
|
82
|
+
decipher.setAuthTag(tag);
|
|
83
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
84
|
+
} catch {
|
|
85
|
+
throw new Error("Wallet decryption failed. If you changed your hostname or username, re-configure with `chain-insights config set walletPrivateKey <key>`.");
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns true if wallet.json exists, false if absent.
|
|
90
|
+
* Does not validate the wallet contents.
|
|
91
|
+
*/
|
|
92
|
+
async function isWalletConfigured() {
|
|
93
|
+
try {
|
|
94
|
+
await stat(walletPath());
|
|
95
|
+
return true;
|
|
96
|
+
} catch (err) {
|
|
97
|
+
if (err.code === "ENOENT") return false;
|
|
98
|
+
throw err;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
export { setWalletPrivateKey as a, normalizeWalletPrivateKey as i, encryptKey as n, walletAddressFromPrivateKey as o, isWalletConfigured as r, wallet_exports as s, decryptKey as t };
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=wallet-BMelXBYP.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wallet-BMelXBYP.mjs","names":["nodeErr"],"sources":["../src/wallet/index.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { readFile, writeFile, mkdir, stat } from 'node:fs/promises'\nimport type { Address, Hex } from 'viem'\nimport { privateKeyToAccount } from 'viem/accounts'\n\n// Path derived at call time so tests can override HOME.\nexport function walletPath(): string {\n return path.join(os.homedir(), '.chain-insights', 'wallet.json')\n}\n\n// Derive a 32-byte key from the machine identity (hostname + username) and a\n// random per-wallet salt. The salt prevents precomputation attacks across wallets.\nfunction deriveKey(salt: Buffer): Buffer {\n return crypto.scryptSync(\n `${os.hostname()}:${os.userInfo().username}`,\n salt,\n 32,\n )\n}\n\ninterface WalletData {\n salt: string\n iv: string\n tag: string\n data: string\n}\n\nexport function normalizeWalletPrivateKey(value: string): Hex {\n if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {\n throw new Error('Stored wallet private key is not a valid 0x-prefixed EVM private key')\n }\n return value as Hex\n}\n\nexport function walletAddressFromPrivateKey(privateKey: string): Address {\n return privateKeyToAccount(normalizeWalletPrivateKey(privateKey)).address\n}\n\n/**\n * Encrypts a private key and writes it to ~/.chain-insights/wallet.json.\n * Uses AES-256-GCM with a machine-identity-derived key and a random per-wallet salt.\n * File is written with 0o600 permissions (owner read/write only).\n *\n * @param privateKey - The EVM private key to encrypt (0x-prefixed)\n */\nexport async function encryptKey(privateKey: string): Promise<void> {\n const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey)\n const salt = crypto.randomBytes(16)\n const key = deriveKey(salt)\n const iv = crypto.randomBytes(12)\n const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)\n\n const encrypted = Buffer.concat([\n cipher.update(normalizedPrivateKey, 'utf8'),\n cipher.final(),\n ])\n\n // getAuthTag() MUST be called after final()\n const tag = cipher.getAuthTag()\n\n const walletData: WalletData = {\n salt: salt.toString('hex'),\n iv: iv.toString('hex'),\n tag: tag.toString('hex'),\n data: encrypted.toString('hex'),\n }\n\n const p = walletPath()\n await mkdir(path.dirname(p), { recursive: true })\n await writeFile(p, JSON.stringify(walletData, null, 2) + '\\n', { mode: 0o600 })\n}\n\nexport async function setWalletPrivateKey(privateKey: string): Promise<Address> {\n const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey)\n const address = walletAddressFromPrivateKey(normalizedPrivateKey)\n await encryptKey(normalizedPrivateKey)\n return address\n}\n\n/**\n * Reads and decrypts the private key from ~/.chain-insights/wallet.json.\n * Throws a human-readable error if wallet is absent or decryption fails.\n *\n * @returns The decrypted EVM private key string\n */\nexport async function decryptKey(): Promise<string> {\n let raw: string\n try {\n raw = await readFile(walletPath(), 'utf8')\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code === 'ENOENT') {\n throw new Error(\n 'Wallet not configured. Run `chain-insights config set walletPrivateKey <key>` to enable paid MCP calls',\n )\n }\n throw err\n }\n\n try {\n const stored = JSON.parse(raw) as WalletData\n const salt = Buffer.from(stored.salt, 'hex')\n const key = deriveKey(salt)\n const iv = Buffer.from(stored.iv, 'hex')\n const tag = Buffer.from(stored.tag, 'hex')\n const encrypted = Buffer.from(stored.data, 'hex')\n\n const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)\n // setAuthTag() MUST be called before update()\n decipher.setAuthTag(tag)\n\n const decrypted = Buffer.concat([\n decipher.update(encrypted),\n decipher.final(),\n ])\n\n return decrypted.toString('utf8')\n } catch {\n throw new Error(\n 'Wallet decryption failed. If you changed your hostname or username, re-configure with `chain-insights config set walletPrivateKey <key>`.',\n )\n }\n}\n\n/**\n * Returns true if wallet.json exists, false if absent.\n * Does not validate the wallet contents.\n */\nexport async function isWalletConfigured(): Promise<boolean> {\n try {\n await stat(walletPath())\n return true\n } catch (err: unknown) {\n const nodeErr = err as NodeJS.ErrnoException\n if (nodeErr.code === 'ENOENT') return false\n throw err\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAQA,SAAgB,aAAqB;AACnC,QAAO,KAAK,KAAK,GAAG,SAAS,EAAE,mBAAmB,cAAc;;AAKlE,SAAS,UAAU,MAAsB;AACvC,QAAO,OAAO,WACZ,GAAG,GAAG,UAAU,CAAC,GAAG,GAAG,UAAU,CAAC,YAClC,MACA,GACD;;AAUH,SAAgB,0BAA0B,OAAoB;AAC5D,KAAI,CAAC,sBAAsB,KAAK,MAAM,CACpC,OAAM,IAAI,MAAM,uEAAuE;AAEzF,QAAO;;AAGT,SAAgB,4BAA4B,YAA6B;AACvE,QAAO,oBAAoB,0BAA0B,WAAW,CAAC,CAAC;;;;;;;;;AAUpE,eAAsB,WAAW,YAAmC;CAClE,MAAM,uBAAuB,0BAA0B,WAAW;CAClE,MAAM,OAAO,OAAO,YAAY,GAAG;CACnC,MAAM,MAAM,UAAU,KAAK;CAC3B,MAAM,KAAK,OAAO,YAAY,GAAG;CACjC,MAAM,SAAS,OAAO,eAAe,eAAe,KAAK,GAAG;CAE5D,MAAM,YAAY,OAAO,OAAO,CAC9B,OAAO,OAAO,sBAAsB,OAAO,EAC3C,OAAO,OAAO,CACf,CAAC;CAGF,MAAM,MAAM,OAAO,YAAY;CAE/B,MAAM,aAAyB;EAC7B,MAAM,KAAK,SAAS,MAAM;EAC1B,IAAI,GAAG,SAAS,MAAM;EACtB,KAAK,IAAI,SAAS,MAAM;EACxB,MAAM,UAAU,SAAS,MAAM;EAChC;CAED,MAAM,IAAI,YAAY;AACtB,OAAM,MAAM,KAAK,QAAQ,EAAE,EAAE,EAAE,WAAW,MAAM,CAAC;AACjD,OAAM,UAAU,GAAG,KAAK,UAAU,YAAY,MAAM,EAAE,GAAG,MAAM,EAAE,MAAM,KAAO,CAAC;;AAGjF,eAAsB,oBAAoB,YAAsC;CAC9E,MAAM,uBAAuB,0BAA0B,WAAW;CAClE,MAAM,UAAU,4BAA4B,qBAAqB;AACjE,OAAM,WAAW,qBAAqB;AACtC,QAAO;;;;;;;;AAST,eAAsB,aAA8B;CAClD,IAAI;AACJ,KAAI;AACF,QAAM,MAAM,SAAS,YAAY,EAAE,OAAO;UACnC,KAAc;AAErB,MAAIA,IAAQ,SAAS,SACnB,OAAM,IAAI,MACR,yGACD;AAEH,QAAM;;AAGR,KAAI;EACF,MAAM,SAAS,KAAK,MAAM,IAAI;EAE9B,MAAM,MAAM,UADC,OAAO,KAAK,OAAO,MAAM,MACZ,CAAC;EAC3B,MAAM,KAAK,OAAO,KAAK,OAAO,IAAI,MAAM;EACxC,MAAM,MAAM,OAAO,KAAK,OAAO,KAAK,MAAM;EAC1C,MAAM,YAAY,OAAO,KAAK,OAAO,MAAM,MAAM;EAEjD,MAAM,WAAW,OAAO,iBAAiB,eAAe,KAAK,GAAG;AAEhE,WAAS,WAAW,IAAI;AAOxB,SALkB,OAAO,OAAO,CAC9B,SAAS,OAAO,UAAU,EAC1B,SAAS,OAAO,CACjB,CAEe,CAAC,SAAS,OAAO;SAC3B;AACN,QAAM,IAAI,MACR,4IACD;;;;;;;AAQL,eAAsB,qBAAuC;AAC3D,KAAI;AACF,QAAM,KAAK,YAAY,CAAC;AACxB,SAAO;UACA,KAAc;AAErB,MAAIA,IAAQ,SAAS,SAAU,QAAO;AACtC,QAAM"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_path = require("node:path");
|
|
3
|
+
node_path = require_chunk.__toESM(node_path, 1);
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
node_os = require_chunk.__toESM(node_os, 1);
|
|
7
|
+
let node_crypto = require("node:crypto");
|
|
8
|
+
node_crypto = require_chunk.__toESM(node_crypto, 1);
|
|
9
|
+
let viem_accounts = require("viem/accounts");
|
|
10
|
+
//#region src/wallet/index.ts
|
|
11
|
+
var wallet_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
12
|
+
decryptKey: () => decryptKey,
|
|
13
|
+
encryptKey: () => encryptKey,
|
|
14
|
+
isWalletConfigured: () => isWalletConfigured,
|
|
15
|
+
normalizeWalletPrivateKey: () => normalizeWalletPrivateKey,
|
|
16
|
+
setWalletPrivateKey: () => setWalletPrivateKey,
|
|
17
|
+
walletAddressFromPrivateKey: () => walletAddressFromPrivateKey,
|
|
18
|
+
walletPath: () => walletPath
|
|
19
|
+
});
|
|
20
|
+
function walletPath() {
|
|
21
|
+
return node_path.default.join(node_os.default.homedir(), ".chain-insights", "wallet.json");
|
|
22
|
+
}
|
|
23
|
+
function deriveKey(salt) {
|
|
24
|
+
return node_crypto.default.scryptSync(`${node_os.default.hostname()}:${node_os.default.userInfo().username}`, salt, 32);
|
|
25
|
+
}
|
|
26
|
+
function normalizeWalletPrivateKey(value) {
|
|
27
|
+
if (!/^0x[0-9a-fA-F]{64}$/.test(value)) throw new Error("Stored wallet private key is not a valid 0x-prefixed EVM private key");
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function walletAddressFromPrivateKey(privateKey) {
|
|
31
|
+
return (0, viem_accounts.privateKeyToAccount)(normalizeWalletPrivateKey(privateKey)).address;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Encrypts a private key and writes it to ~/.chain-insights/wallet.json.
|
|
35
|
+
* Uses AES-256-GCM with a machine-identity-derived key and a random per-wallet salt.
|
|
36
|
+
* File is written with 0o600 permissions (owner read/write only).
|
|
37
|
+
*
|
|
38
|
+
* @param privateKey - The EVM private key to encrypt (0x-prefixed)
|
|
39
|
+
*/
|
|
40
|
+
async function encryptKey(privateKey) {
|
|
41
|
+
const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey);
|
|
42
|
+
const salt = node_crypto.default.randomBytes(16);
|
|
43
|
+
const key = deriveKey(salt);
|
|
44
|
+
const iv = node_crypto.default.randomBytes(12);
|
|
45
|
+
const cipher = node_crypto.default.createCipheriv("aes-256-gcm", key, iv);
|
|
46
|
+
const encrypted = Buffer.concat([cipher.update(normalizedPrivateKey, "utf8"), cipher.final()]);
|
|
47
|
+
const tag = cipher.getAuthTag();
|
|
48
|
+
const walletData = {
|
|
49
|
+
salt: salt.toString("hex"),
|
|
50
|
+
iv: iv.toString("hex"),
|
|
51
|
+
tag: tag.toString("hex"),
|
|
52
|
+
data: encrypted.toString("hex")
|
|
53
|
+
};
|
|
54
|
+
const p = walletPath();
|
|
55
|
+
await (0, node_fs_promises.mkdir)(node_path.default.dirname(p), { recursive: true });
|
|
56
|
+
await (0, node_fs_promises.writeFile)(p, JSON.stringify(walletData, null, 2) + "\n", { mode: 384 });
|
|
57
|
+
}
|
|
58
|
+
async function setWalletPrivateKey(privateKey) {
|
|
59
|
+
const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey);
|
|
60
|
+
const address = walletAddressFromPrivateKey(normalizedPrivateKey);
|
|
61
|
+
await encryptKey(normalizedPrivateKey);
|
|
62
|
+
return address;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Reads and decrypts the private key from ~/.chain-insights/wallet.json.
|
|
66
|
+
* Throws a human-readable error if wallet is absent or decryption fails.
|
|
67
|
+
*
|
|
68
|
+
* @returns The decrypted EVM private key string
|
|
69
|
+
*/
|
|
70
|
+
async function decryptKey() {
|
|
71
|
+
let raw;
|
|
72
|
+
try {
|
|
73
|
+
raw = await (0, node_fs_promises.readFile)(walletPath(), "utf8");
|
|
74
|
+
} catch (err) {
|
|
75
|
+
if (err.code === "ENOENT") throw new Error("Wallet not configured. Run `chain-insights config set walletPrivateKey <key>` to enable paid MCP calls");
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const stored = JSON.parse(raw);
|
|
80
|
+
const key = deriveKey(Buffer.from(stored.salt, "hex"));
|
|
81
|
+
const iv = Buffer.from(stored.iv, "hex");
|
|
82
|
+
const tag = Buffer.from(stored.tag, "hex");
|
|
83
|
+
const encrypted = Buffer.from(stored.data, "hex");
|
|
84
|
+
const decipher = node_crypto.default.createDecipheriv("aes-256-gcm", key, iv);
|
|
85
|
+
decipher.setAuthTag(tag);
|
|
86
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
87
|
+
} catch {
|
|
88
|
+
throw new Error("Wallet decryption failed. If you changed your hostname or username, re-configure with `chain-insights config set walletPrivateKey <key>`.");
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Returns true if wallet.json exists, false if absent.
|
|
93
|
+
* Does not validate the wallet contents.
|
|
94
|
+
*/
|
|
95
|
+
async function isWalletConfigured() {
|
|
96
|
+
try {
|
|
97
|
+
await (0, node_fs_promises.stat)(walletPath());
|
|
98
|
+
return true;
|
|
99
|
+
} catch (err) {
|
|
100
|
+
if (err.code === "ENOENT") return false;
|
|
101
|
+
throw err;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
Object.defineProperty(exports, "decryptKey", {
|
|
106
|
+
enumerable: true,
|
|
107
|
+
get: function() {
|
|
108
|
+
return decryptKey;
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
Object.defineProperty(exports, "encryptKey", {
|
|
112
|
+
enumerable: true,
|
|
113
|
+
get: function() {
|
|
114
|
+
return encryptKey;
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
Object.defineProperty(exports, "isWalletConfigured", {
|
|
118
|
+
enumerable: true,
|
|
119
|
+
get: function() {
|
|
120
|
+
return isWalletConfigured;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
Object.defineProperty(exports, "normalizeWalletPrivateKey", {
|
|
124
|
+
enumerable: true,
|
|
125
|
+
get: function() {
|
|
126
|
+
return normalizeWalletPrivateKey;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
Object.defineProperty(exports, "setWalletPrivateKey", {
|
|
130
|
+
enumerable: true,
|
|
131
|
+
get: function() {
|
|
132
|
+
return setWalletPrivateKey;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
Object.defineProperty(exports, "walletAddressFromPrivateKey", {
|
|
136
|
+
enumerable: true,
|
|
137
|
+
get: function() {
|
|
138
|
+
return walletAddressFromPrivateKey;
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
Object.defineProperty(exports, "wallet_exports", {
|
|
142
|
+
enumerable: true,
|
|
143
|
+
get: function() {
|
|
144
|
+
return wallet_exports;
|
|
145
|
+
}
|
|
146
|
+
});
|