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,940 @@
|
|
|
1
|
+
const require_chunk = require("./chunk-CZWwpsFl.cjs");
|
|
2
|
+
let node_url = require("node:url");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs = require("node:fs");
|
|
5
|
+
let viem = require("viem");
|
|
6
|
+
let viem_chains = require("viem/chains");
|
|
7
|
+
let node_http = require("node:http");
|
|
8
|
+
//#region src/wallet/mcp-proxy/qr.ts
|
|
9
|
+
const SIZE = 25;
|
|
10
|
+
const EC_CODEWORDS = 10;
|
|
11
|
+
const DATA_CODEWORDS = 34;
|
|
12
|
+
const FORMAT_BITS = 30660;
|
|
13
|
+
const MODE_BYTE = 4;
|
|
14
|
+
function createMatrix() {
|
|
15
|
+
const m = [];
|
|
16
|
+
for (let i = 0; i < SIZE; i++) m[i] = new Array(SIZE).fill(-1);
|
|
17
|
+
return m;
|
|
18
|
+
}
|
|
19
|
+
function addFinderPattern(matrix, row, col) {
|
|
20
|
+
for (let r = -1; r <= 7; r++) for (let c = -1; c <= 7; c++) {
|
|
21
|
+
const mr = row + r;
|
|
22
|
+
const mc = col + c;
|
|
23
|
+
if (mr < 0 || mr >= SIZE || mc < 0 || mc >= SIZE) continue;
|
|
24
|
+
if (r >= 0 && r <= 6 && c >= 0 && c <= 6) if (r === 0 || r === 6 || c === 0 || c === 6 || r >= 2 && r <= 4 && c >= 2 && c <= 4) matrix[mr][mc] = 1;
|
|
25
|
+
else matrix[mr][mc] = 0;
|
|
26
|
+
else matrix[mr][mc] = 0;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function addAlignmentPattern(matrix, row, col) {
|
|
30
|
+
for (let r = -2; r <= 2; r++) for (let c = -2; c <= 2; c++) if (Math.abs(r) === 2 || Math.abs(c) === 2 || r === 0 && c === 0) matrix[row + r][col + c] = 1;
|
|
31
|
+
else matrix[row + r][col + c] = 0;
|
|
32
|
+
}
|
|
33
|
+
function addTimingPatterns(matrix) {
|
|
34
|
+
for (let i = 8; i < SIZE - 8; i++) {
|
|
35
|
+
if (matrix[6][i] === -1) matrix[6][i] = i % 2 === 0 ? 1 : 0;
|
|
36
|
+
if (matrix[i][6] === -1) matrix[i][6] = i % 2 === 0 ? 1 : 0;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function addFormatInfo(matrix) {
|
|
40
|
+
const bits = FORMAT_BITS;
|
|
41
|
+
for (let i = 0; i <= 5; i++) matrix[8][i] = bits >> 14 - i & 1;
|
|
42
|
+
matrix[8][7] = bits >> 8 & 1;
|
|
43
|
+
matrix[8][8] = bits >> 7 & 1;
|
|
44
|
+
matrix[7][8] = bits >> 6 & 1;
|
|
45
|
+
for (let i = 0; i <= 5; i++) matrix[5 - i][8] = bits >> i & 1;
|
|
46
|
+
for (let i = 0; i <= 7; i++) matrix[SIZE - 1 - i][8] = bits >> 14 - i & 1;
|
|
47
|
+
for (let i = 0; i <= 7; i++) matrix[8][SIZE - 8 + i] = bits >> 7 - i & 1;
|
|
48
|
+
matrix[SIZE - 8][8] = 1;
|
|
49
|
+
}
|
|
50
|
+
function encodeData(text) {
|
|
51
|
+
const bytes = new TextEncoder().encode(text);
|
|
52
|
+
const bits = [];
|
|
53
|
+
for (let i = 3; i >= 0; i--) bits.push(MODE_BYTE >> i & 1);
|
|
54
|
+
for (let i = 7; i >= 0; i--) bits.push(bytes.length >> i & 1);
|
|
55
|
+
for (const b of bytes) for (let i = 7; i >= 0; i--) bits.push(b >> i & 1);
|
|
56
|
+
while (bits.length < DATA_CODEWORDS * 8 && bits.length < DATA_CODEWORDS * 8) {
|
|
57
|
+
bits.push(0);
|
|
58
|
+
if (bits.length >= DATA_CODEWORDS * 8) break;
|
|
59
|
+
}
|
|
60
|
+
while (bits.length % 8 !== 0) bits.push(0);
|
|
61
|
+
const padBytes = [236, 17];
|
|
62
|
+
let padIdx = 0;
|
|
63
|
+
while (bits.length < DATA_CODEWORDS * 8) {
|
|
64
|
+
const pb = padBytes[padIdx % 2];
|
|
65
|
+
for (let i = 7; i >= 0; i--) bits.push(pb >> i & 1);
|
|
66
|
+
padIdx++;
|
|
67
|
+
}
|
|
68
|
+
const codewords = [];
|
|
69
|
+
for (let i = 0; i < bits.length; i += 8) {
|
|
70
|
+
let val = 0;
|
|
71
|
+
for (let j = 0; j < 8; j++) val = val << 1 | (bits[i + j] || 0);
|
|
72
|
+
codewords.push(val);
|
|
73
|
+
}
|
|
74
|
+
return codewords;
|
|
75
|
+
}
|
|
76
|
+
const GF_EXP = new Array(512).fill(0);
|
|
77
|
+
const GF_LOG = new Array(256).fill(0);
|
|
78
|
+
(function initGF() {
|
|
79
|
+
let x = 1;
|
|
80
|
+
for (let i = 0; i < 255; i++) {
|
|
81
|
+
GF_EXP[i] = x;
|
|
82
|
+
GF_LOG[x] = i;
|
|
83
|
+
x <<= 1;
|
|
84
|
+
if (x & 256) x ^= 285;
|
|
85
|
+
}
|
|
86
|
+
for (let i = 255; i < 512; i++) GF_EXP[i] = GF_EXP[i - 255];
|
|
87
|
+
})();
|
|
88
|
+
function gfMul(a, b) {
|
|
89
|
+
if (a === 0 || b === 0) return 0;
|
|
90
|
+
return GF_EXP[GF_LOG[a] + GF_LOG[b]];
|
|
91
|
+
}
|
|
92
|
+
function rsEncode(data, ecCount) {
|
|
93
|
+
let gen = [1];
|
|
94
|
+
for (let i = 0; i < ecCount; i++) {
|
|
95
|
+
const next = new Array(gen.length + 1).fill(0);
|
|
96
|
+
for (let j = 0; j < gen.length; j++) {
|
|
97
|
+
next[j] ^= gen[j];
|
|
98
|
+
next[j + 1] ^= gfMul(gen[j], GF_EXP[i]);
|
|
99
|
+
}
|
|
100
|
+
gen = next;
|
|
101
|
+
}
|
|
102
|
+
const msg = [...data, ...new Array(ecCount).fill(0)];
|
|
103
|
+
for (let i = 0; i < data.length; i++) {
|
|
104
|
+
const coef = msg[i];
|
|
105
|
+
if (coef !== 0) for (let j = 0; j < gen.length; j++) msg[i + j] ^= gfMul(gen[j], coef);
|
|
106
|
+
}
|
|
107
|
+
return msg.slice(data.length);
|
|
108
|
+
}
|
|
109
|
+
function placeData(matrix, dataBits) {
|
|
110
|
+
let bitIdx = 0;
|
|
111
|
+
let upward = true;
|
|
112
|
+
for (let right = SIZE - 1; right >= 1; right -= 2) {
|
|
113
|
+
if (right === 6) right = 5;
|
|
114
|
+
const rows = upward ? Array.from({ length: SIZE }, (_, i) => SIZE - 1 - i) : Array.from({ length: SIZE }, (_, i) => i);
|
|
115
|
+
for (const row of rows) for (let c = 0; c < 2; c++) {
|
|
116
|
+
const col = right - c;
|
|
117
|
+
if (matrix[row][col] !== -1) continue;
|
|
118
|
+
matrix[row][col] = bitIdx < dataBits.length ? dataBits[bitIdx++] : 0;
|
|
119
|
+
}
|
|
120
|
+
upward = !upward;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
function applyMask0(matrix, reserved) {
|
|
124
|
+
for (let r = 0; r < SIZE; r++) for (let c = 0; c < SIZE; c++) {
|
|
125
|
+
if (reserved[r][c] !== -1) continue;
|
|
126
|
+
if ((r + c) % 2 === 0) matrix[r][c] ^= 1;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function generateQrSvg(text, opts = 4) {
|
|
130
|
+
const options = typeof opts === "number" ? { cellSize: opts } : opts;
|
|
131
|
+
const cellSize = options.cellSize ?? 4;
|
|
132
|
+
const fgColor = options.fgColor ?? "#000";
|
|
133
|
+
const bgColor = options.bgColor ?? "#fff";
|
|
134
|
+
const finderColor = options.finderColor ?? fgColor;
|
|
135
|
+
const matrix = createMatrix();
|
|
136
|
+
addFinderPattern(matrix, 0, 0);
|
|
137
|
+
addFinderPattern(matrix, 0, SIZE - 7);
|
|
138
|
+
addFinderPattern(matrix, SIZE - 7, 0);
|
|
139
|
+
addAlignmentPattern(matrix, 18, 18);
|
|
140
|
+
addTimingPatterns(matrix);
|
|
141
|
+
addFormatInfo(matrix);
|
|
142
|
+
const reserved = matrix.map((row) => [...row]);
|
|
143
|
+
const dataCodewords = encodeData(text);
|
|
144
|
+
const ecCodewords = rsEncode(dataCodewords, EC_CODEWORDS);
|
|
145
|
+
const allCodewords = [...dataCodewords, ...ecCodewords];
|
|
146
|
+
const dataBits = [];
|
|
147
|
+
for (const cw of allCodewords) for (let i = 7; i >= 0; i--) dataBits.push(cw >> i & 1);
|
|
148
|
+
placeData(matrix, dataBits);
|
|
149
|
+
applyMask0(matrix, reserved);
|
|
150
|
+
addFormatInfo(matrix);
|
|
151
|
+
const logoW = options.logoWidth ?? 7;
|
|
152
|
+
const logoH = options.logoHeight ?? 5;
|
|
153
|
+
const logoStartC = Math.floor((SIZE - logoW) / 2);
|
|
154
|
+
const logoStartR = Math.floor((SIZE - logoH) / 2);
|
|
155
|
+
const hasLogo = !!options.logoBase64;
|
|
156
|
+
const svgSize = SIZE * cellSize;
|
|
157
|
+
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}">`;
|
|
158
|
+
svg += `<rect width="${svgSize}" height="${svgSize}" fill="${bgColor}" rx="4"/>`;
|
|
159
|
+
const isFinderModule = (r, c) => r < 7 && c < 7 || r < 7 && c >= SIZE - 7 || r >= SIZE - 7 && c < 7;
|
|
160
|
+
for (let r = 0; r < SIZE; r++) for (let c = 0; c < SIZE; c++) {
|
|
161
|
+
if (hasLogo && r >= logoStartR && r < logoStartR + logoH && c >= logoStartC && c < logoStartC + logoW) continue;
|
|
162
|
+
if (matrix[r][c] === 1) {
|
|
163
|
+
const color = isFinderModule(r, c) ? finderColor : fgColor;
|
|
164
|
+
svg += `<rect x="${c * cellSize}" y="${r * cellSize}" width="${cellSize}" height="${cellSize}" fill="${color}" rx="0.5"/>`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (hasLogo && options.logoBase64) {
|
|
168
|
+
const lx = logoStartC * cellSize;
|
|
169
|
+
const ly = logoStartR * cellSize;
|
|
170
|
+
const lw = logoW * cellSize;
|
|
171
|
+
const lh = logoH * cellSize;
|
|
172
|
+
svg += `<rect x="${lx - 1}" y="${ly - 1}" width="${lw + 2}" height="${lh + 2}" fill="${bgColor}" rx="3"/>`;
|
|
173
|
+
svg += `<image x="${lx + 2}" y="${ly + 2}" width="${lw - 4}" height="${lh - 4}" href="${options.logoBase64}" xlink:href="${options.logoBase64}" preserveAspectRatio="xMidYMid meet"/>`;
|
|
174
|
+
}
|
|
175
|
+
svg += "</svg>";
|
|
176
|
+
return svg;
|
|
177
|
+
}
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/wallet/mcp-proxy/tools.ts
|
|
180
|
+
const USDC_ADDRESS$1 = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
181
|
+
const PUBLIC_BASE_RPC_URLS = [
|
|
182
|
+
"https://mainnet.base.org",
|
|
183
|
+
"https://base-rpc.publicnode.com",
|
|
184
|
+
"https://base.drpc.org",
|
|
185
|
+
"https://1rpc.io/base"
|
|
186
|
+
];
|
|
187
|
+
const USDC_ABI = [{
|
|
188
|
+
name: "balanceOf",
|
|
189
|
+
type: "function",
|
|
190
|
+
stateMutability: "view",
|
|
191
|
+
inputs: [{
|
|
192
|
+
name: "account",
|
|
193
|
+
type: "address"
|
|
194
|
+
}],
|
|
195
|
+
outputs: [{
|
|
196
|
+
name: "",
|
|
197
|
+
type: "uint256"
|
|
198
|
+
}]
|
|
199
|
+
}];
|
|
200
|
+
async function getBalanceUsdc(wallet) {
|
|
201
|
+
const envRpcUrl = process.env.BASE_RPC_URL;
|
|
202
|
+
const rpcUrls = [...envRpcUrl ? [envRpcUrl] : [], ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl)];
|
|
203
|
+
for (const rpcUrl of rpcUrls) try {
|
|
204
|
+
return (0, viem.formatUnits)(await (0, viem.createPublicClient)({
|
|
205
|
+
chain: viem_chains.base,
|
|
206
|
+
transport: (0, viem.http)(rpcUrl)
|
|
207
|
+
}).readContract({
|
|
208
|
+
address: USDC_ADDRESS$1,
|
|
209
|
+
abi: USDC_ABI,
|
|
210
|
+
functionName: "balanceOf",
|
|
211
|
+
args: [wallet.address]
|
|
212
|
+
}), 6);
|
|
213
|
+
} catch {}
|
|
214
|
+
return "unknown";
|
|
215
|
+
}
|
|
216
|
+
async function getBalanceEth(wallet) {
|
|
217
|
+
const envRpcUrl = process.env.BASE_RPC_URL;
|
|
218
|
+
const rpcUrls = [...envRpcUrl ? [envRpcUrl] : [], ...PUBLIC_BASE_RPC_URLS.filter((url) => url !== envRpcUrl)];
|
|
219
|
+
for (const rpcUrl of rpcUrls) try {
|
|
220
|
+
return (0, viem.formatEther)(await (0, viem.createPublicClient)({
|
|
221
|
+
chain: viem_chains.base,
|
|
222
|
+
transport: (0, viem.http)(rpcUrl)
|
|
223
|
+
}).getBalance({ address: wallet.address }));
|
|
224
|
+
} catch {}
|
|
225
|
+
return "unknown";
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region src/wallet/mcp-proxy/topup-server.ts
|
|
229
|
+
const USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
230
|
+
const BASE_CHAIN_ID = "0x2105";
|
|
231
|
+
const __dirname$1 = (0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
232
|
+
function loadAsset(name) {
|
|
233
|
+
const paths = [(0, node_path.join)(__dirname$1, "assets", name), (0, node_path.join)(__dirname$1, "..", "src", "assets", name)];
|
|
234
|
+
for (const p of paths) try {
|
|
235
|
+
return (0, node_fs.readFileSync)(p);
|
|
236
|
+
} catch {}
|
|
237
|
+
console.error(`[chain-insights] Warning: asset ${name} not found`);
|
|
238
|
+
return Buffer.alloc(0);
|
|
239
|
+
}
|
|
240
|
+
const logoPng = loadAsset("logo.png");
|
|
241
|
+
const bgPatternPng = loadAsset("bg-pattern.png");
|
|
242
|
+
let server = null;
|
|
243
|
+
let serverPort = null;
|
|
244
|
+
function getTopupUrl$1() {
|
|
245
|
+
return serverPort ? `http://localhost:${serverPort}` : null;
|
|
246
|
+
}
|
|
247
|
+
async function startTopupServer$1(wallet) {
|
|
248
|
+
if (server && serverPort) return `http://localhost:${serverPort}`;
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
server = (0, node_http.createServer)((req, res) => {
|
|
251
|
+
if (req.url === "/api/wallet") {
|
|
252
|
+
res.writeHead(200, {
|
|
253
|
+
"Content-Type": "application/json",
|
|
254
|
+
"Access-Control-Allow-Origin": "*"
|
|
255
|
+
});
|
|
256
|
+
res.end(JSON.stringify({ address: wallet.address }));
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (req.url === "/api/balance") {
|
|
260
|
+
Promise.all([getBalanceUsdc(wallet), getBalanceEth(wallet)]).then(([balanceUsdc, balanceEth]) => {
|
|
261
|
+
res.writeHead(200, {
|
|
262
|
+
"Content-Type": "application/json",
|
|
263
|
+
"Access-Control-Allow-Origin": "*"
|
|
264
|
+
});
|
|
265
|
+
res.end(JSON.stringify({
|
|
266
|
+
balance_usdc: balanceUsdc,
|
|
267
|
+
balance_eth: balanceEth
|
|
268
|
+
}));
|
|
269
|
+
}).catch(() => {
|
|
270
|
+
res.writeHead(200, {
|
|
271
|
+
"Content-Type": "application/json",
|
|
272
|
+
"Access-Control-Allow-Origin": "*"
|
|
273
|
+
});
|
|
274
|
+
res.end(JSON.stringify({
|
|
275
|
+
balance_usdc: "unknown",
|
|
276
|
+
balance_eth: "unknown"
|
|
277
|
+
}));
|
|
278
|
+
});
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (req.url === "/assets/logo.png") {
|
|
282
|
+
res.writeHead(200, {
|
|
283
|
+
"Content-Type": "image/png",
|
|
284
|
+
"Cache-Control": "public, max-age=86400",
|
|
285
|
+
"Access-Control-Allow-Origin": "*"
|
|
286
|
+
});
|
|
287
|
+
res.end(logoPng);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (req.url === "/assets/bg-pattern.png") {
|
|
291
|
+
res.writeHead(200, {
|
|
292
|
+
"Content-Type": "image/png",
|
|
293
|
+
"Cache-Control": "public, max-age=86400",
|
|
294
|
+
"Access-Control-Allow-Origin": "*"
|
|
295
|
+
});
|
|
296
|
+
res.end(bgPatternPng);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
300
|
+
res.end(generatePage(wallet.address));
|
|
301
|
+
});
|
|
302
|
+
server.listen(0, "127.0.0.1", () => {
|
|
303
|
+
const addr = server.address();
|
|
304
|
+
if (addr && typeof addr === "object") {
|
|
305
|
+
serverPort = addr.port;
|
|
306
|
+
const url = `http://localhost:${serverPort}`;
|
|
307
|
+
console.error(`[chain-insights] Topup server running at ${url}`);
|
|
308
|
+
resolve(url);
|
|
309
|
+
} else reject(/* @__PURE__ */ new Error("Failed to start topup server"));
|
|
310
|
+
});
|
|
311
|
+
server.on("error", reject);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
function generateArtifactHtml(walletAddress, topupUrl) {
|
|
315
|
+
return `<!DOCTYPE html>
|
|
316
|
+
<html lang="en">
|
|
317
|
+
<head>
|
|
318
|
+
<meta charset="utf-8">
|
|
319
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
320
|
+
<style>
|
|
321
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
322
|
+
body {
|
|
323
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
324
|
+
background: rgba(10, 12, 17, 1) url('${topupUrl}/assets/bg-pattern.png') top left / contain no-repeat;
|
|
325
|
+
color: rgba(255, 255, 255, 0.9);
|
|
326
|
+
display: flex;
|
|
327
|
+
justify-content: center;
|
|
328
|
+
padding: 24px;
|
|
329
|
+
line-height: 1.3;
|
|
330
|
+
}
|
|
331
|
+
.card {
|
|
332
|
+
max-width: 400px;
|
|
333
|
+
width: 100%;
|
|
334
|
+
background: rgba(19, 19, 24, 1);
|
|
335
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
336
|
+
border-radius: 12px;
|
|
337
|
+
padding: 28px 24px;
|
|
338
|
+
text-align: center;
|
|
339
|
+
}
|
|
340
|
+
.logo { margin-bottom: 8px; }
|
|
341
|
+
.logo img { height: 24px; }
|
|
342
|
+
.subtitle { color: rgba(255, 255, 255, 0.5); font-size: 13px; margin-bottom: 20px; }
|
|
343
|
+
.qr { margin-bottom: 16px; }
|
|
344
|
+
.qr svg { border-radius: 12px; background: #fff; padding: 10px; }
|
|
345
|
+
.address-wrap {
|
|
346
|
+
position: relative;
|
|
347
|
+
margin-bottom: 16px;
|
|
348
|
+
}
|
|
349
|
+
.address {
|
|
350
|
+
width: 100%;
|
|
351
|
+
background: rgba(10, 12, 17, 1);
|
|
352
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
353
|
+
border-radius: 8px;
|
|
354
|
+
padding: 10px 14px;
|
|
355
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
356
|
+
font-size: 11px; color: #f2dda6; word-break: break-all;
|
|
357
|
+
cursor: pointer; text-align: center;
|
|
358
|
+
transition: border-color 0.3s linear;
|
|
359
|
+
-webkit-user-select: all; user-select: all;
|
|
360
|
+
outline: none;
|
|
361
|
+
}
|
|
362
|
+
.address:focus { border-color: #ae9d71; }
|
|
363
|
+
.address-hint {
|
|
364
|
+
font-size: 10px; color: rgba(255,255,255,0.3);
|
|
365
|
+
text-align: center; margin-top: 4px;
|
|
366
|
+
}
|
|
367
|
+
.badge {
|
|
368
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
369
|
+
background: rgba(255, 255, 255, 0.04);
|
|
370
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
371
|
+
border-radius: 20px; padding: 5px 12px;
|
|
372
|
+
font-size: 11px; color: rgba(255, 255, 255, 0.5); margin-bottom: 20px;
|
|
373
|
+
}
|
|
374
|
+
.badge .dot { width: 7px; height: 7px; border-radius: 50%; background: #f2dda6; }
|
|
375
|
+
.balance-line {
|
|
376
|
+
font-size: 13px;
|
|
377
|
+
color: rgba(255, 255, 255, 0.5);
|
|
378
|
+
margin-bottom: 20px;
|
|
379
|
+
line-height: 1.4;
|
|
380
|
+
}
|
|
381
|
+
.balance-line .amount {
|
|
382
|
+
color: #4feb69;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
}
|
|
385
|
+
.balance-line .gas {
|
|
386
|
+
color: #f2dda6;
|
|
387
|
+
font-weight: 600;
|
|
388
|
+
}
|
|
389
|
+
@keyframes flash { 0%{opacity:0.4} 100%{opacity:1} }
|
|
390
|
+
.balance-line.flash .amount { animation: flash 1s ease-out; }
|
|
391
|
+
.hint {
|
|
392
|
+
margin-top: 14px; font-size: 12px;
|
|
393
|
+
color: rgba(255, 255, 255, 0.3);
|
|
394
|
+
line-height: 1.5;
|
|
395
|
+
}
|
|
396
|
+
</style>
|
|
397
|
+
</head>
|
|
398
|
+
<body>
|
|
399
|
+
<div class="card">
|
|
400
|
+
<div class="logo"><img src="${topupUrl}/assets/logo.png" alt="Chain Insights"></div>
|
|
401
|
+
<p class="subtitle">Fund your wallet with USDC on Base</p>
|
|
402
|
+
<div class="qr">${generateQrSvg(walletAddress, { cellSize: 8 })}</div>
|
|
403
|
+
<div class="address-wrap">
|
|
404
|
+
<div class="address" id="addr" onclick="selectAndCopy()">${walletAddress}</div>
|
|
405
|
+
<div class="address-hint" id="addrHint">Click to copy address</div>
|
|
406
|
+
</div>
|
|
407
|
+
<div class="badge"><span class="dot"></span>Base Network · USDC</div>
|
|
408
|
+
<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>
|
|
409
|
+
</div>
|
|
410
|
+
<script>
|
|
411
|
+
// MCP Apps protocol handshake (matches @modelcontextprotocol/ext-apps App.connect())
|
|
412
|
+
(function() {
|
|
413
|
+
var initId = 1;
|
|
414
|
+
|
|
415
|
+
// Listen for host messages
|
|
416
|
+
window.addEventListener('message', function(event) {
|
|
417
|
+
var data = event.data;
|
|
418
|
+
if (!data || data.jsonrpc !== '2.0') return;
|
|
419
|
+
|
|
420
|
+
// Initialize response
|
|
421
|
+
if (data.id === initId && data.result) {
|
|
422
|
+
// Send initialized notification
|
|
423
|
+
window.parent.postMessage({
|
|
424
|
+
jsonrpc: '2.0',
|
|
425
|
+
method: 'ui/notifications/initialized',
|
|
426
|
+
params: {}
|
|
427
|
+
}, '*');
|
|
428
|
+
|
|
429
|
+
// Send initial size
|
|
430
|
+
var rect = document.documentElement.getBoundingClientRect();
|
|
431
|
+
window.parent.postMessage({
|
|
432
|
+
jsonrpc: '2.0',
|
|
433
|
+
method: 'ui/notifications/size-changed',
|
|
434
|
+
params: { width: Math.ceil(rect.width), height: Math.ceil(rect.height) }
|
|
435
|
+
}, '*');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Respond to pings
|
|
439
|
+
if (data.method === 'ping' && data.id != null) {
|
|
440
|
+
window.parent.postMessage({
|
|
441
|
+
jsonrpc: '2.0',
|
|
442
|
+
id: data.id,
|
|
443
|
+
result: {}
|
|
444
|
+
}, '*');
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// Send initialize request (must match App class protocol)
|
|
449
|
+
window.parent.postMessage({
|
|
450
|
+
jsonrpc: '2.0',
|
|
451
|
+
id: initId,
|
|
452
|
+
method: 'ui/initialize',
|
|
453
|
+
params: {
|
|
454
|
+
appInfo: { name: 'Chain Insights Topup', version: '1.0.0' },
|
|
455
|
+
appCapabilities: {},
|
|
456
|
+
protocolVersion: '2026-01-26'
|
|
457
|
+
}
|
|
458
|
+
}, '*');
|
|
459
|
+
})();
|
|
460
|
+
|
|
461
|
+
// Live balance polling
|
|
462
|
+
var lastBal = null;
|
|
463
|
+
function fetchBal() {
|
|
464
|
+
fetch('${topupUrl}/api/balance')
|
|
465
|
+
.then(function(r) { return r.json(); })
|
|
466
|
+
.then(function(d) {
|
|
467
|
+
var el = document.getElementById('bal');
|
|
468
|
+
var gas = document.getElementById('gas');
|
|
469
|
+
var line = document.getElementById('balLine');
|
|
470
|
+
var val = parseFloat(d.balance_usdc || '0').toFixed(2);
|
|
471
|
+
var gasVal = d.balance_eth === 'unknown' ? '--' : parseFloat(d.balance_eth || '0').toFixed(6);
|
|
472
|
+
if (d.balance_usdc === 'unknown') { el.textContent = '--'; gas.textContent = gasVal; return; }
|
|
473
|
+
el.textContent = val;
|
|
474
|
+
gas.textContent = gasVal;
|
|
475
|
+
if (lastBal !== null && val !== lastBal) {
|
|
476
|
+
line.classList.remove('flash');
|
|
477
|
+
void line.offsetWidth;
|
|
478
|
+
line.classList.add('flash');
|
|
479
|
+
}
|
|
480
|
+
lastBal = val;
|
|
481
|
+
})
|
|
482
|
+
.catch(function() {});
|
|
483
|
+
}
|
|
484
|
+
fetchBal();
|
|
485
|
+
setInterval(fetchBal, 10000);
|
|
486
|
+
|
|
487
|
+
function selectAndCopy() {
|
|
488
|
+
var addr = document.getElementById('addr');
|
|
489
|
+
var hint = document.getElementById('addrHint');
|
|
490
|
+
// Select the text
|
|
491
|
+
var r = document.createRange();
|
|
492
|
+
r.selectNodeContents(addr);
|
|
493
|
+
var s = window.getSelection();
|
|
494
|
+
s.removeAllRanges();
|
|
495
|
+
s.addRange(r);
|
|
496
|
+
// Try every clipboard method available
|
|
497
|
+
var copied = false;
|
|
498
|
+
try { copied = document.execCommand('copy'); } catch(e) {}
|
|
499
|
+
if (!copied && navigator.clipboard && navigator.clipboard.writeText) {
|
|
500
|
+
navigator.clipboard.writeText(addr.textContent).then(function() {
|
|
501
|
+
hint.textContent = 'Copied!'; hint.style.color = '#4feb69';
|
|
502
|
+
setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);
|
|
503
|
+
}).catch(function() {});
|
|
504
|
+
}
|
|
505
|
+
if (copied) {
|
|
506
|
+
hint.textContent = 'Copied!'; hint.style.color = '#4feb69';
|
|
507
|
+
setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 2000);
|
|
508
|
+
} else {
|
|
509
|
+
hint.textContent = 'Selected — press Ctrl+C'; hint.style.color = '#f2dda6';
|
|
510
|
+
setTimeout(function() { hint.textContent = 'Click to copy address'; hint.style.color = ''; }, 3000);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
<\/script>
|
|
514
|
+
</body>
|
|
515
|
+
</html>`;
|
|
516
|
+
}
|
|
517
|
+
function generatePage(walletAddress) {
|
|
518
|
+
return `<!DOCTYPE html>
|
|
519
|
+
<html lang="en">
|
|
520
|
+
<head>
|
|
521
|
+
<meta charset="utf-8">
|
|
522
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
523
|
+
<title>Chain Insights — Fund Wallet</title>
|
|
524
|
+
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"><\/script>
|
|
525
|
+
<style>
|
|
526
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
527
|
+
|
|
528
|
+
body {
|
|
529
|
+
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
530
|
+
background: rgba(10, 12, 17, 1);
|
|
531
|
+
color: rgba(255, 255, 255, 0.9);
|
|
532
|
+
min-height: 100vh;
|
|
533
|
+
display: flex;
|
|
534
|
+
justify-content: center;
|
|
535
|
+
align-items: center;
|
|
536
|
+
line-height: 1.3;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.container {
|
|
540
|
+
max-width: 480px;
|
|
541
|
+
width: 100%;
|
|
542
|
+
padding: 24px;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
.logo {
|
|
546
|
+
text-align: center;
|
|
547
|
+
margin-bottom: 32px;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.logo h1 {
|
|
551
|
+
font-size: 24px;
|
|
552
|
+
font-weight: 600;
|
|
553
|
+
color: #f2dda6;
|
|
554
|
+
letter-spacing: -0.5px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.logo p {
|
|
558
|
+
color: rgba(255, 255, 255, 0.5);
|
|
559
|
+
font-size: 14px;
|
|
560
|
+
margin-top: 4px;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.card {
|
|
564
|
+
background: rgba(19, 19, 24, 1);
|
|
565
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
566
|
+
border-radius: 12px;
|
|
567
|
+
padding: 32px 24px;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.qr-container {
|
|
571
|
+
display: flex;
|
|
572
|
+
justify-content: center;
|
|
573
|
+
margin-bottom: 24px;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.qr-container canvas, .qr-container img {
|
|
577
|
+
border-radius: 12px;
|
|
578
|
+
background: #fff;
|
|
579
|
+
padding: 12px;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.address-box {
|
|
583
|
+
background: rgba(10, 12, 17, 1);
|
|
584
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
585
|
+
border-radius: 8px;
|
|
586
|
+
padding: 12px 16px;
|
|
587
|
+
display: flex;
|
|
588
|
+
align-items: center;
|
|
589
|
+
gap: 8px;
|
|
590
|
+
margin-bottom: 24px;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
.address-box code {
|
|
594
|
+
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
595
|
+
font-size: 12px;
|
|
596
|
+
color: #f2dda6;
|
|
597
|
+
word-break: break-all;
|
|
598
|
+
flex: 1;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.copy-btn {
|
|
602
|
+
background: none;
|
|
603
|
+
border: none;
|
|
604
|
+
color: rgba(255, 255, 255, 0.4);
|
|
605
|
+
cursor: pointer;
|
|
606
|
+
padding: 4px;
|
|
607
|
+
font-size: 18px;
|
|
608
|
+
transition: color 0.3s linear;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
.copy-btn:hover { color: #f2dda6; }
|
|
612
|
+
.copy-btn.copied { color: #4feb69; }
|
|
613
|
+
|
|
614
|
+
.network-badge {
|
|
615
|
+
display: inline-flex;
|
|
616
|
+
align-items: center;
|
|
617
|
+
gap: 6px;
|
|
618
|
+
background: rgba(255, 255, 255, 0.04);
|
|
619
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
620
|
+
border-radius: 20px;
|
|
621
|
+
padding: 6px 12px;
|
|
622
|
+
font-size: 12px;
|
|
623
|
+
color: rgba(255, 255, 255, 0.5);
|
|
624
|
+
margin-bottom: 24px;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
.network-badge .dot {
|
|
628
|
+
width: 8px;
|
|
629
|
+
height: 8px;
|
|
630
|
+
border-radius: 50%;
|
|
631
|
+
background: #f2dda6;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
.balance-row {
|
|
635
|
+
display: flex;
|
|
636
|
+
justify-content: space-between;
|
|
637
|
+
align-items: center;
|
|
638
|
+
padding: 12px 0;
|
|
639
|
+
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
640
|
+
margin-bottom: 16px;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
.balance-label { color: rgba(255, 255, 255, 0.5); font-size: 14px; }
|
|
644
|
+
|
|
645
|
+
.balance-value {
|
|
646
|
+
font-size: 20px;
|
|
647
|
+
font-weight: 600;
|
|
648
|
+
color: rgba(255, 255, 255, 0.9);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.balance-value .currency {
|
|
652
|
+
font-size: 14px;
|
|
653
|
+
color: rgba(255, 255, 255, 0.5);
|
|
654
|
+
font-weight: 400;
|
|
655
|
+
margin-left: 4px;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
.gas-note {
|
|
659
|
+
margin-top: -8px;
|
|
660
|
+
margin-bottom: 16px;
|
|
661
|
+
color: rgba(255,255,255,0.45);
|
|
662
|
+
font-size: 12px;
|
|
663
|
+
line-height: 1.4;
|
|
664
|
+
text-align: left;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
.metamask-btn {
|
|
668
|
+
width: 100%;
|
|
669
|
+
padding: 14px;
|
|
670
|
+
border: none;
|
|
671
|
+
border-radius: 10px;
|
|
672
|
+
font-size: 15px;
|
|
673
|
+
font-weight: 600;
|
|
674
|
+
cursor: pointer;
|
|
675
|
+
display: flex;
|
|
676
|
+
align-items: center;
|
|
677
|
+
justify-content: center;
|
|
678
|
+
gap: 10px;
|
|
679
|
+
transition: all 0.2s;
|
|
680
|
+
background: #f6851b;
|
|
681
|
+
color: #fff;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.metamask-btn:hover { background: #e2761b; transform: translateY(-1px); }
|
|
685
|
+
.metamask-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
|
686
|
+
|
|
687
|
+
.metamask-btn svg { width: 22px; height: 22px; }
|
|
688
|
+
|
|
689
|
+
.amount-input {
|
|
690
|
+
width: 100%;
|
|
691
|
+
padding: 12px 16px;
|
|
692
|
+
background: #0a0c11;
|
|
693
|
+
border: 1px solid rgba(255, 255, 255, 0.06);
|
|
694
|
+
border-radius: 8px;
|
|
695
|
+
color: rgba(255, 255, 255, 0.9);
|
|
696
|
+
font-size: 16px;
|
|
697
|
+
margin-bottom: 16px;
|
|
698
|
+
outline: none;
|
|
699
|
+
transition: border-color 0.2s;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
.amount-input:focus { border-color: #f2dda6; }
|
|
703
|
+
|
|
704
|
+
.amount-label {
|
|
705
|
+
font-size: 13px;
|
|
706
|
+
color: rgba(255, 255, 255, 0.5);
|
|
707
|
+
margin-bottom: 6px;
|
|
708
|
+
display: block;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
.status {
|
|
712
|
+
text-align: center;
|
|
713
|
+
padding: 12px;
|
|
714
|
+
border-radius: 8px;
|
|
715
|
+
font-size: 14px;
|
|
716
|
+
margin-top: 16px;
|
|
717
|
+
display: none;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
.status.success { display: block; background: #0a2e1a; color: #4feb69; border: 1px solid #1a4a2e; }
|
|
721
|
+
.status.error { display: block; background: #2e0a0a; color: #eb4f4f; border: 1px solid #4a1a1a; }
|
|
722
|
+
.status.pending { display: block; background: #1a1a0a; color: #f2dda6; border: 1px solid #4a4a1a; }
|
|
723
|
+
|
|
724
|
+
.info {
|
|
725
|
+
text-align: center;
|
|
726
|
+
margin-top: 24px;
|
|
727
|
+
font-size: 12px;
|
|
728
|
+
color: rgba(255, 255, 255, 0.3);
|
|
729
|
+
line-height: 1.6;
|
|
730
|
+
}
|
|
731
|
+
</style>
|
|
732
|
+
</head>
|
|
733
|
+
<body>
|
|
734
|
+
<div class="container">
|
|
735
|
+
<div class="logo">
|
|
736
|
+
<h1>Chain Insights</h1>
|
|
737
|
+
<p>Fund your wallet to use blockchain intelligence tools</p>
|
|
738
|
+
</div>
|
|
739
|
+
|
|
740
|
+
<div class="card">
|
|
741
|
+
<div class="qr-container" id="qr"></div>
|
|
742
|
+
|
|
743
|
+
<div class="address-box">
|
|
744
|
+
<code id="address">${walletAddress}</code>
|
|
745
|
+
<button class="copy-btn" onclick="copyAddress()" id="copyBtn" title="Copy address">⎘</button>
|
|
746
|
+
</div>
|
|
747
|
+
|
|
748
|
+
<div class="network-badge">
|
|
749
|
+
<span class="dot"></span>
|
|
750
|
+
Base Network · USDC
|
|
751
|
+
</div>
|
|
752
|
+
|
|
753
|
+
<div class="balance-row">
|
|
754
|
+
<span class="balance-label">Current balance</span>
|
|
755
|
+
<span class="balance-value" id="balance">—<span class="currency">USDC</span></span>
|
|
756
|
+
</div>
|
|
757
|
+
<div class="balance-row">
|
|
758
|
+
<span class="balance-label">Gas balance</span>
|
|
759
|
+
<span class="balance-value" id="gasBalance">—<span class="currency">ETH</span></span>
|
|
760
|
+
</div>
|
|
761
|
+
<p class="gas-note">Base ETH is used for one-time approval gas.</p>
|
|
762
|
+
|
|
763
|
+
<div class="status" id="status"></div>
|
|
764
|
+
</div>
|
|
765
|
+
|
|
766
|
+
<p class="info">Send Base USDC to the wallet address above.</p>
|
|
767
|
+
</div>
|
|
768
|
+
|
|
769
|
+
<script>
|
|
770
|
+
const WALLET = '${walletAddress}';
|
|
771
|
+
const USDC = '${USDC_ADDRESS}';
|
|
772
|
+
const CHAIN_ID = '${BASE_CHAIN_ID}';
|
|
773
|
+
|
|
774
|
+
// QR code
|
|
775
|
+
(function() {
|
|
776
|
+
var qr = qrcode(0, 'M');
|
|
777
|
+
qr.addData(WALLET);
|
|
778
|
+
qr.make();
|
|
779
|
+
document.getElementById('qr').innerHTML = qr.createSvgTag(5, 0);
|
|
780
|
+
var svg = document.querySelector('#qr svg');
|
|
781
|
+
if (svg) { svg.style.borderRadius = '12px'; svg.style.background = '#fff'; svg.style.padding = '12px'; }
|
|
782
|
+
})();
|
|
783
|
+
|
|
784
|
+
// Copy address — fallback for sandboxed iframes where navigator.clipboard is blocked
|
|
785
|
+
function copyAddress() {
|
|
786
|
+
var ok = false;
|
|
787
|
+
// Try modern clipboard API first
|
|
788
|
+
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
789
|
+
navigator.clipboard.writeText(WALLET).then(function() { ok = true; }).catch(function() {});
|
|
790
|
+
}
|
|
791
|
+
// Fallback: hidden textarea + execCommand
|
|
792
|
+
if (!ok) {
|
|
793
|
+
var ta = document.createElement('textarea');
|
|
794
|
+
ta.value = WALLET;
|
|
795
|
+
ta.style.position = 'fixed';
|
|
796
|
+
ta.style.left = '-9999px';
|
|
797
|
+
document.body.appendChild(ta);
|
|
798
|
+
ta.select();
|
|
799
|
+
try { document.execCommand('copy'); } catch(e) {}
|
|
800
|
+
document.body.removeChild(ta);
|
|
801
|
+
}
|
|
802
|
+
var btn = document.getElementById('copyBtn');
|
|
803
|
+
btn.classList.add('copied');
|
|
804
|
+
btn.innerHTML = '✓';
|
|
805
|
+
setTimeout(function() { btn.classList.remove('copied'); btn.innerHTML = '⎘'; }, 2000);
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// Fetch balance
|
|
809
|
+
async function fetchBalance() {
|
|
810
|
+
try {
|
|
811
|
+
var resp = await fetch('/api/balance');
|
|
812
|
+
var json = await resp.json();
|
|
813
|
+
if (json.balance_usdc === 'unknown') throw new Error('balance unavailable');
|
|
814
|
+
var balance = Number(json.balance_usdc || 0).toFixed(2);
|
|
815
|
+
document.getElementById('balance').innerHTML = balance + '<span class="currency">USDC</span>';
|
|
816
|
+
var gasBalance = json.balance_eth === 'unknown' ? '—' : Number(json.balance_eth || 0).toFixed(6);
|
|
817
|
+
document.getElementById('gasBalance').innerHTML = gasBalance + '<span class="currency">ETH</span>';
|
|
818
|
+
} catch(e) {
|
|
819
|
+
document.getElementById('balance').innerHTML = '—<span class="currency">USDC</span>';
|
|
820
|
+
document.getElementById('gasBalance').innerHTML = '—<span class="currency">ETH</span>';
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
fetchBalance();
|
|
824
|
+
setInterval(fetchBalance, 15000);
|
|
825
|
+
|
|
826
|
+
<\/script>
|
|
827
|
+
</body>
|
|
828
|
+
</html>`;
|
|
829
|
+
}
|
|
830
|
+
//#endregion
|
|
831
|
+
//#region src/wallet/topup-server.ts
|
|
832
|
+
var topup_server_exports = /* @__PURE__ */ require_chunk.__exportAll({
|
|
833
|
+
generateArtifactHtml: () => generateArtifactHtml,
|
|
834
|
+
getTopupArtifactUrl: () => getTopupArtifactUrl,
|
|
835
|
+
getTopupUrl: () => getTopupUrl,
|
|
836
|
+
startTopupServer: () => startTopupServer
|
|
837
|
+
});
|
|
838
|
+
const FALLBACK_PRIVATE_KEY = `0x${"0".repeat(63)}1`;
|
|
839
|
+
let artifactServerState = null;
|
|
840
|
+
function toWalletData(account) {
|
|
841
|
+
if (typeof account === "string") return {
|
|
842
|
+
address: account,
|
|
843
|
+
privateKey: FALLBACK_PRIVATE_KEY,
|
|
844
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
845
|
+
};
|
|
846
|
+
return {
|
|
847
|
+
address: account.address,
|
|
848
|
+
privateKey: account.privateKey,
|
|
849
|
+
createdAt: (/* @__PURE__ */ new Date(0)).toISOString()
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
function send(res, status, body, contentType) {
|
|
853
|
+
res.writeHead(status, {
|
|
854
|
+
"content-type": contentType,
|
|
855
|
+
"cache-control": "no-store",
|
|
856
|
+
"access-control-allow-origin": "*"
|
|
857
|
+
});
|
|
858
|
+
res.end(body);
|
|
859
|
+
}
|
|
860
|
+
async function proxyToCopiedServer(reqUrl, res, assetServerUrl) {
|
|
861
|
+
const upstreamUrl = new URL(reqUrl, assetServerUrl);
|
|
862
|
+
const upstream = await fetch(upstreamUrl);
|
|
863
|
+
const contentType = upstream.headers.get("content-type") ?? "application/octet-stream";
|
|
864
|
+
const body = Buffer.from(await upstream.arrayBuffer());
|
|
865
|
+
send(res, upstream.status, body, contentType);
|
|
866
|
+
}
|
|
867
|
+
function getTopupArtifactUrl() {
|
|
868
|
+
return artifactServerState?.url ?? null;
|
|
869
|
+
}
|
|
870
|
+
function getTopupUrl() {
|
|
871
|
+
return getTopupArtifactUrl() ?? getTopupUrl$1();
|
|
872
|
+
}
|
|
873
|
+
async function startTopupServer(account) {
|
|
874
|
+
const wallet = toWalletData(account);
|
|
875
|
+
if (artifactServerState && artifactServerState.address.toLowerCase() === wallet.address.toLowerCase()) return artifactServerState.url;
|
|
876
|
+
const assetServerUrl = await startTopupServer$1(wallet);
|
|
877
|
+
if (artifactServerState) {
|
|
878
|
+
await new Promise((resolve) => artifactServerState?.server.close(() => resolve()));
|
|
879
|
+
artifactServerState = null;
|
|
880
|
+
}
|
|
881
|
+
const server = (0, node_http.createServer)((req, res) => {
|
|
882
|
+
const reqUrl = req.url ?? "/";
|
|
883
|
+
const pathname = new URL(reqUrl, "http://localhost").pathname;
|
|
884
|
+
if (pathname === "/" || pathname === "/index.html") {
|
|
885
|
+
const artifactUrl = artifactServerState?.url ?? assetServerUrl;
|
|
886
|
+
send(res, 200, generateArtifactHtml(wallet.address, artifactUrl), "text/html; charset=utf-8");
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
if (pathname.startsWith("/assets/") || pathname.startsWith("/api/")) {
|
|
890
|
+
proxyToCopiedServer(reqUrl, res, assetServerUrl).catch((err) => {
|
|
891
|
+
send(res, 502, JSON.stringify({ error: err.message }) + "\n", "application/json; charset=utf-8");
|
|
892
|
+
});
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
send(res, 404, JSON.stringify({ error: "Not found" }) + "\n", "application/json; charset=utf-8");
|
|
896
|
+
});
|
|
897
|
+
const url = await new Promise((resolve, reject) => {
|
|
898
|
+
server.once("error", reject);
|
|
899
|
+
server.listen(0, "127.0.0.1", () => {
|
|
900
|
+
const addressInfo = server.address();
|
|
901
|
+
if (!addressInfo || typeof addressInfo === "string") {
|
|
902
|
+
reject(/* @__PURE__ */ new Error("Failed to start topup artifact server"));
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
resolve(`http://localhost:${addressInfo.port}`);
|
|
906
|
+
});
|
|
907
|
+
});
|
|
908
|
+
artifactServerState = {
|
|
909
|
+
address: wallet.address,
|
|
910
|
+
assetServerUrl,
|
|
911
|
+
server,
|
|
912
|
+
url
|
|
913
|
+
};
|
|
914
|
+
return url;
|
|
915
|
+
}
|
|
916
|
+
//#endregion
|
|
917
|
+
Object.defineProperty(exports, "generateArtifactHtml", {
|
|
918
|
+
enumerable: true,
|
|
919
|
+
get: function() {
|
|
920
|
+
return generateArtifactHtml;
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
Object.defineProperty(exports, "getTopupUrl", {
|
|
924
|
+
enumerable: true,
|
|
925
|
+
get: function() {
|
|
926
|
+
return getTopupUrl;
|
|
927
|
+
}
|
|
928
|
+
});
|
|
929
|
+
Object.defineProperty(exports, "startTopupServer", {
|
|
930
|
+
enumerable: true,
|
|
931
|
+
get: function() {
|
|
932
|
+
return startTopupServer;
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
Object.defineProperty(exports, "topup_server_exports", {
|
|
936
|
+
enumerable: true,
|
|
937
|
+
get: function() {
|
|
938
|
+
return topup_server_exports;
|
|
939
|
+
}
|
|
940
|
+
});
|