chain-insights 0.22.2 → 0.23.0
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/README.md +29 -50
- package/dist/cli.cjs +21 -83
- package/dist/cli.mjs +21 -83
- package/dist/cli.mjs.map +1 -1
- package/dist/{config-BVFf3v8-.cjs → config-B542AM4w.cjs} +2 -3
- package/dist/{config-CnNMdt4M.mjs → config-DbbPiGk0.mjs} +2 -3
- package/dist/{config-CnNMdt4M.mjs.map → config-DbbPiGk0.mjs.map} +1 -1
- package/dist/html-generator-BCDtYcGs.mjs +16 -0
- package/dist/{html-generator-D2BDzLED.mjs.map → html-generator-BCDtYcGs.mjs.map} +1 -1
- package/dist/html-generator-DFwMRWN3.cjs +16 -0
- package/dist/index.cjs +2 -8
- package/dist/index.d.cts +1 -99
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +1 -99
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +3 -6
- package/dist/mcp-proxy.cjs +35 -156
- package/dist/mcp-proxy.d.cts.map +1 -1
- package/dist/mcp-proxy.d.mts.map +1 -1
- package/dist/mcp-proxy.mjs +35 -156
- package/dist/mcp-proxy.mjs.map +1 -1
- package/dist/{public-tools-BFs_7U_Z.cjs → public-tools-Bou25cki.cjs} +153 -5
- package/dist/{public-tools-BjqZGGAC.mjs → public-tools-CpOLkME5.mjs} +152 -4
- package/dist/public-tools-CpOLkME5.mjs.map +1 -0
- package/dist/{schema-DOJA2nNi.mjs → schema-B0OuHW_l.mjs} +64 -5
- package/dist/schema-B0OuHW_l.mjs.map +1 -0
- package/dist/{schema-BpeV3xDV.cjs → schema-CtFWYoJz.cjs} +72 -7
- package/dist/{tool-visibility-DZBAP62b.cjs → tool-visibility-C8F9Yoxy.cjs} +1 -2
- package/dist/{tool-visibility-C0ALejXI.mjs → tool-visibility-CXbwfKhv.mjs} +2 -3
- package/dist/tool-visibility-CXbwfKhv.mjs.map +1 -0
- package/dist/{topup-server-rqI7bqem.mjs → topup-server-BLdF1SFQ.mjs} +2 -2
- package/dist/topup-server-BLdF1SFQ.mjs.map +1 -0
- package/dist/{topup-server-DAqmAuu1.cjs → topup-server-YN3iHrHW.cjs} +1 -1
- package/dist/{update-CJUfGCxs.mjs → update-MsoEFbhn.mjs} +2 -36
- package/dist/{update-CJUfGCxs.mjs.map → update-MsoEFbhn.mjs.map} +1 -1
- package/dist/{update-BJoXYucO.cjs → update-RKd0UW90.cjs} +0 -35
- package/docs/architecture.md +16 -46
- package/docs/contributing.md +0 -3
- package/docs/development.md +3 -5
- package/docs/graph-tools.md +4 -29
- package/docs/mcp-proxy.md +10 -36
- package/docs/stability.md +0 -12
- package/package.json +3 -4
- package/skills/chain-insights-address-risk/SKILL.md +0 -1
- package/dist/app-7NRCqx_Q.mjs +0 -140
- package/dist/app-7NRCqx_Q.mjs.map +0 -1
- package/dist/app-l9JE6bt3.cjs +0 -145
- package/dist/artifact-server-CH-asxF5.cjs +0 -47
- package/dist/artifact-server-DoxJr0z8.mjs +0 -49
- package/dist/artifact-server-DoxJr0z8.mjs.map +0 -1
- package/dist/graph-normalizer-B2Q2lgVy.cjs +0 -130
- package/dist/graph-normalizer-BA3Rbd6l.mjs +0 -127
- package/dist/graph-normalizer-BA3Rbd6l.mjs.map +0 -1
- package/dist/graph-reports-B6yoYbPq.mjs +0 -63
- package/dist/graph-reports-B6yoYbPq.mjs.map +0 -1
- package/dist/graph-reports-_gqzxvvs.cjs +0 -64
- package/dist/html-generator-B9f_GIi2.cjs +0 -86
- package/dist/html-generator-D2BDzLED.mjs +0 -70
- package/dist/init-Djhml4ih.mjs +0 -319
- package/dist/init-Djhml4ih.mjs.map +0 -1
- package/dist/init-DvrKxK15.cjs +0 -319
- package/dist/limits-D3Mn-gig.mjs +0 -94
- package/dist/limits-D3Mn-gig.mjs.map +0 -1
- package/dist/limits-km3pmmL3.cjs +0 -115
- package/dist/mcp-endpoint-BIWrUOFG.cjs +0 -56
- package/dist/mcp-endpoint-DbWtLcba.mjs +0 -41
- package/dist/mcp-endpoint-DbWtLcba.mjs.map +0 -1
- package/dist/output-root-BK4pdjyz.mjs +0 -36
- package/dist/output-root-BK4pdjyz.mjs.map +0 -1
- package/dist/output-root-BewoQaTx.cjs +0 -46
- package/dist/public-tools-BjqZGGAC.mjs.map +0 -1
- package/dist/resolve-port-CbaEoODz.cjs +0 -18
- package/dist/resolve-port-DxPK7Ipx.mjs +0 -20
- package/dist/resolve-port-DxPK7Ipx.mjs.map +0 -1
- package/dist/schema-DOJA2nNi.mjs.map +0 -1
- package/dist/server-CmvzYu7v.cjs +0 -55
- package/dist/server-mDSRgk1X.mjs +0 -46
- package/dist/server-mDSRgk1X.mjs.map +0 -1
- package/dist/templates/graph.html +0 -1450
- package/dist/tool-visibility-C0ALejXI.mjs.map +0 -1
- package/dist/topup-server-rqI7bqem.mjs.map +0 -1
- package/dist/viz-BJ_kyRjI.cjs +0 -217
- package/dist/viz-Uh_EF-qB.mjs +0 -206
- package/dist/viz-Uh_EF-qB.mjs.map +0 -1
- package/docs/images/graph-mcp-iframe.png +0 -0
- package/docs/images/graph-visualization.png +0 -0
- package/docs/images/quickstart-demo.svg +0 -1
- package/docs/investigation-workspaces.md +0 -108
- package/docs/search-limits.md +0 -73
|
@@ -1,12 +1,71 @@
|
|
|
1
1
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
2
|
-
const require_mcp_endpoint = require("./mcp-endpoint-BIWrUOFG.cjs");
|
|
3
|
-
const require_limits = require("./limits-km3pmmL3.cjs");
|
|
4
2
|
let node_path = require("node:path");
|
|
5
3
|
node_path = require_rolldown_runtime.__toESM(node_path, 1);
|
|
6
4
|
let node_os = require("node:os");
|
|
7
5
|
node_os = require_rolldown_runtime.__toESM(node_os, 1);
|
|
8
6
|
let zod = require("zod");
|
|
9
7
|
zod = require_rolldown_runtime.__toESM(zod, 1);
|
|
8
|
+
let node_net = require("node:net");
|
|
9
|
+
//#region src/config/mcp-endpoint.ts
|
|
10
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
11
|
+
"localhost",
|
|
12
|
+
"127.0.0.1",
|
|
13
|
+
"::1",
|
|
14
|
+
"[::1]"
|
|
15
|
+
]);
|
|
16
|
+
const DEFAULT_GRAPH_MCP_ENDPOINT = "https://mcp.chain-insights.ai/";
|
|
17
|
+
function isLoopbackHost(hostname) {
|
|
18
|
+
const normalized = hostname.toLowerCase().replace(/^\[(.*)\]$/, "$1");
|
|
19
|
+
if (LOOPBACK_HOSTS.has(normalized)) return true;
|
|
20
|
+
if ((0, node_net.isIP)(normalized) !== 4) return false;
|
|
21
|
+
return normalized.split(".")[0] === "127";
|
|
22
|
+
}
|
|
23
|
+
function isKubernetesServiceHost(hostname) {
|
|
24
|
+
return hostname.toLowerCase().endsWith(".svc.cluster.local");
|
|
25
|
+
}
|
|
26
|
+
function graphMcpEndpointEnvOverride() {
|
|
27
|
+
const envEndpoint = process.env.CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT?.trim() || process.env.GRAPH_MCP_ENDPOINT?.trim();
|
|
28
|
+
return envEndpoint ? validateMcpEndpoint(envEndpoint, "graphMcpEndpoint") : void 0;
|
|
29
|
+
}
|
|
30
|
+
function validateMcpEndpoint(value, key) {
|
|
31
|
+
const trimmed = value.trim();
|
|
32
|
+
if (!trimmed) throw new Error(`${key} must be a non-empty absolute URL.`);
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = new URL(trimmed);
|
|
36
|
+
} catch {
|
|
37
|
+
throw new Error(`${key} must be an absolute URL (example: https://mcp.example.com/mcp or http://127.0.0.1:8012/mcp).`);
|
|
38
|
+
}
|
|
39
|
+
if (parsed.username || parsed.password) throw new Error(`${key} must not include URL credentials.`);
|
|
40
|
+
if (parsed.search || parsed.hash) throw new Error(`${key} must not include query parameters or URL fragments.`);
|
|
41
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error(`${key} must use either https:// or http://.`);
|
|
42
|
+
if (parsed.protocol === "http:" && !isLoopbackHost(parsed.hostname) && !isKubernetesServiceHost(parsed.hostname)) throw new Error(`${key} must use https:// for remote hosts. http:// is allowed only for localhost, loopback addresses, or Kubernetes *.svc.cluster.local service DNS.`);
|
|
43
|
+
return trimmed;
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region src/config/limits.ts
|
|
47
|
+
const LIMIT_SPECS = { viz_max_nodes: {
|
|
48
|
+
builtin: 100,
|
|
49
|
+
min: 1,
|
|
50
|
+
ceiling: 2e3,
|
|
51
|
+
description: "Nodes rendered in a generated graph view before truncation."
|
|
52
|
+
} };
|
|
53
|
+
Object.keys(LIMIT_SPECS);
|
|
54
|
+
function isLimitKey(key) {
|
|
55
|
+
return Object.prototype.hasOwnProperty.call(LIMIT_SPECS, key);
|
|
56
|
+
}
|
|
57
|
+
const NETWORK_LIMIT_DEFAULTS = {};
|
|
58
|
+
/**
|
|
59
|
+
* The maximum this network will accept. A per-network entry may lower it;
|
|
60
|
+
* nothing may raise it above the built-in ceiling.
|
|
61
|
+
*/
|
|
62
|
+
function limitCeiling(key, network) {
|
|
63
|
+
const builtinCeiling = LIMIT_SPECS[key].ceiling;
|
|
64
|
+
const networkCeiling = network ? NETWORK_LIMIT_DEFAULTS[network]?.ceilings?.[key] : void 0;
|
|
65
|
+
if (networkCeiling === void 0 || !Number.isFinite(networkCeiling)) return builtinCeiling;
|
|
66
|
+
return Math.min(builtinCeiling, Math.trunc(networkCeiling));
|
|
67
|
+
}
|
|
68
|
+
//#endregion
|
|
10
69
|
//#region src/config/schema.ts
|
|
11
70
|
var schema_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
12
71
|
CONFIG_KEYS: () => CONFIG_KEYS,
|
|
@@ -20,15 +79,15 @@ function limitBagSchema(network) {
|
|
|
20
79
|
return zod.record(zod.string(), zod.number()).superRefine((bag, ctx) => {
|
|
21
80
|
for (const [key, value] of Object.entries(bag)) {
|
|
22
81
|
const where = network ? `networkLimits.${network}.${key}` : `limits.${key}`;
|
|
23
|
-
if (!
|
|
82
|
+
if (!isLimitKey(key)) {
|
|
24
83
|
ctx.addIssue({
|
|
25
84
|
code: zod.ZodIssueCode.custom,
|
|
26
85
|
message: `${where}: unknown limit key`
|
|
27
86
|
});
|
|
28
87
|
continue;
|
|
29
88
|
}
|
|
30
|
-
const spec =
|
|
31
|
-
const ceiling =
|
|
89
|
+
const spec = LIMIT_SPECS[key];
|
|
90
|
+
const ceiling = limitCeiling(key, network);
|
|
32
91
|
if (!Number.isInteger(value) || value < spec.min || value > ceiling) ctx.addIssue({
|
|
33
92
|
code: zod.ZodIssueCode.custom,
|
|
34
93
|
message: `${where}: must be an integer between ${spec.min} and ${ceiling} (got ${value})`
|
|
@@ -50,7 +109,7 @@ const NetworkLimitsSchema = zod.record(zod.string(), zod.record(zod.string(), zo
|
|
|
50
109
|
function endpointSchema(key) {
|
|
51
110
|
return zod.string().transform((value, ctx) => {
|
|
52
111
|
try {
|
|
53
|
-
return
|
|
112
|
+
return validateMcpEndpoint(value, key);
|
|
54
113
|
} catch (err) {
|
|
55
114
|
ctx.addIssue({
|
|
56
115
|
code: zod.ZodIssueCode.custom,
|
|
@@ -61,7 +120,7 @@ function endpointSchema(key) {
|
|
|
61
120
|
});
|
|
62
121
|
}
|
|
63
122
|
const ConfigSchema = zod.object({
|
|
64
|
-
graphMcpEndpoint: endpointSchema("graphMcpEndpoint").default(
|
|
123
|
+
graphMcpEndpoint: endpointSchema("graphMcpEndpoint").default(DEFAULT_GRAPH_MCP_ENDPOINT),
|
|
65
124
|
graphMcpAuthToken: zod.string().optional(),
|
|
66
125
|
graphMcpMode: zod.enum(["paid", "debug"]).default("paid"),
|
|
67
126
|
walletAddress: zod.string().optional(),
|
|
@@ -99,6 +158,12 @@ Object.defineProperty(exports, "DEFAULT_CONFIG", {
|
|
|
99
158
|
return DEFAULT_CONFIG;
|
|
100
159
|
}
|
|
101
160
|
});
|
|
161
|
+
Object.defineProperty(exports, "graphMcpEndpointEnvOverride", {
|
|
162
|
+
enumerable: true,
|
|
163
|
+
get: function() {
|
|
164
|
+
return graphMcpEndpointEnvOverride;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
102
167
|
Object.defineProperty(exports, "parseInvestigatorConfig", {
|
|
103
168
|
enumerable: true,
|
|
104
169
|
get: function() {
|
|
@@ -36,8 +36,7 @@ const PUBLIC_MCP_TOOL_ALLOWED_ARGS = {
|
|
|
36
36
|
aml_address_risk: [
|
|
37
37
|
"address",
|
|
38
38
|
"network",
|
|
39
|
-
"compare_address"
|
|
40
|
-
"include_attachments"
|
|
39
|
+
"compare_address"
|
|
41
40
|
],
|
|
42
41
|
graph_query: [
|
|
43
42
|
"query",
|
|
@@ -71,4 +70,4 @@ function validatePublicMcpToolArguments(name, args) {
|
|
|
71
70
|
//#endregion
|
|
72
71
|
export { tool_visibility_exports as i, PUBLIC_MCP_TOOL_ALLOWED_ARGS as n, PUBLIC_MCP_TOOL_REQUIRED_ARGS as r, HIDDEN_REMOTE_TOOL_NAMES as t };
|
|
73
72
|
|
|
74
|
-
//# sourceMappingURL=tool-visibility-
|
|
73
|
+
//# sourceMappingURL=tool-visibility-CXbwfKhv.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-visibility-CXbwfKhv.mjs","names":[],"sources":["../src/mcp/tool-visibility.ts"],"sourcesContent":["export const HIDDEN_REMOTE_TOOL_NAMES = new Set([\n 'topup',\n 'address_risk',\n 'trace_victim_funds',\n 'trace_suspect_funds',\n 'trace_deposit_sources',\n 'trace_funds',\n 'track_funds',\n // Retired AML trace tools: never exposed publicly. Built by concatenation\n // so the retired names do not appear as literals in this file.\n ['aml_trace_victim', '_funds'].join(''),\n ['aml_trace_suspect', '_funds'].join(''),\n ['aml_trace_deposit', '_sources'].join(''),\n 'network_capabilities',\n 'usage_status',\n 'balance',\n 'help',\n 'money_flows_between_exchanges',\n 'address_connection_risk',\n])\n\nexport const PUBLIC_MCP_TOOL_REQUIRED_ARGS: Record<string, string[]> = {\n aml_address_risk: ['address', 'network'],\n graph_query: ['query', 'network'],\n graph_query_batch: ['network', 'queries'],\n}\n\n// normalizeRemoteToolArguments FILTERS pass-through arguments to this list, so\n// an argument that is missing here is silently STRIPPED and the caller gets a\n// default-bounded result with no error and no warning. `time_scope` shipped\n// broken for exactly this reason. Every tunable search bound below\n// (per_address_limit, row_limit) must therefore be listed the moment it\n// appears on a tool schema, and is covered end-to-end through the proxy in\n// tests/mcp-proxy.test.ts.\nexport const PUBLIC_MCP_TOOL_ALLOWED_ARGS: Record<string, string[]> = {\n aml_address_risk: ['address', 'network', 'compare_address'],\n // `time_scope` narrows a `USE topology` query to a temporal-shard subset\n graph_query: ['query', 'network', 'time_scope'],\n graph_query_batch: ['network', 'queries', 'per_query_timeout_seconds', 'time_scope'],\n}\n\nexport function isHiddenRemoteToolName(name: string): boolean {\n return HIDDEN_REMOTE_TOOL_NAMES.has(name)\n}\n\nexport function visibleRemoteTools<T extends { name: string }>(tools: T[]): T[] {\n return tools.filter((tool) => !isHiddenRemoteToolName(tool.name))\n}\n\nexport function assertPublicMcpToolName(name: string): void {\n if (!isHiddenRemoteToolName(name)) return\n const replacement =\n name === 'address_risk'\n ? ' Use aml_address_risk instead.'\n : name === 'network_capabilities'\n ? ' Use meta_network_capabilities instead.'\n : name === 'usage_status'\n ? ' Use meta_usage_status instead.'\n : name === 'balance'\n ? ' Use wallet_balance instead.'\n : name === 'help'\n ? ' Use meta_help instead.'\n : ''\n throw new Error(`MCP tool '${name}' is not exposed by Chain Insights.${replacement}`)\n}\n\nexport function validatePublicMcpToolArguments(name: string, args: Record<string, unknown>): void {\n const allowedArgs = PUBLIC_MCP_TOOL_ALLOWED_ARGS[name]\n if (!allowedArgs) return\n\n const unsupportedArgs = Object.keys(args).filter((argName) => !allowedArgs.includes(argName))\n if (unsupportedArgs.length === 0) return\n\n throw new Error(\n [\n `Unsupported argument${unsupportedArgs.length === 1 ? '' : 's'} for ${name}: ${unsupportedArgs.join(', ')}.`,\n `Allowed arguments: ${allowedArgs.join(', ')}.`,\n ].join(' ')\n )\n}\n"],"mappings":";;;;;;;;;;;AAAA,MAAa,2CAA2B,IAAI,IAAI;CAC9C;CACA;CACA;CACA;CACA;CACA;CACA;CAGA,CAAC,oBAAoB,QAAQ,CAAC,CAAC,KAAK,EAAE;CACtC,CAAC,qBAAqB,QAAQ,CAAC,CAAC,KAAK,EAAE;CACvC,CAAC,qBAAqB,UAAU,CAAC,CAAC,KAAK,EAAE;CACzC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,MAAa,gCAA0D;CACrE,kBAAkB,CAAC,WAAW,SAAS;CACvC,aAAa,CAAC,SAAS,SAAS;CAChC,mBAAmB,CAAC,WAAW,SAAS;AAC1C;AASA,MAAa,+BAAyD;CACpE,kBAAkB;EAAC;EAAW;EAAW;CAAiB;CAE1D,aAAa;EAAC;EAAS;EAAW;CAAY;CAC9C,mBAAmB;EAAC;EAAW;EAAW;EAA6B;CAAY;AACrF;AAEA,SAAgB,uBAAuB,MAAuB;CAC5D,OAAO,yBAAyB,IAAI,IAAI;AAC1C;AAEA,SAAgB,mBAA+C,OAAiB;CAC9E,OAAO,MAAM,QAAQ,SAAS,CAAC,uBAAuB,KAAK,IAAI,CAAC;AAClE;AAEA,SAAgB,wBAAwB,MAAoB;CAC1D,IAAI,CAAC,uBAAuB,IAAI,GAAG;CAanC,MAAM,IAAI,MAAM,aAAa,KAAK,qCAXhC,SAAS,iBACL,mCACA,SAAS,yBACP,4CACA,SAAS,iBACP,oCACA,SAAS,YACP,iCACA,SAAS,SACP,4BACA,IACsE;AACtF;AAEA,SAAgB,+BAA+B,MAAc,MAAqC;CAChG,MAAM,cAAc,6BAA6B;CACjD,IAAI,CAAC,aAAa;CAElB,MAAM,kBAAkB,OAAO,KAAK,IAAI,CAAC,CAAC,QAAQ,YAAY,CAAC,YAAY,SAAS,OAAO,CAAC;CAC5F,IAAI,gBAAgB,WAAW,GAAG;CAElC,MAAM,IAAI,MACR,CACE,uBAAuB,gBAAgB,WAAW,IAAI,KAAK,IAAI,OAAO,KAAK,IAAI,gBAAgB,KAAK,IAAI,EAAE,IAC1G,sBAAsB,YAAY,KAAK,IAAI,EAAE,EAC/C,CAAC,CAAC,KAAK,GAAG,CACZ;AACF"}
|
|
@@ -429,7 +429,7 @@ function generateArtifactHtml(walletAddressInput, topupUrl) {
|
|
|
429
429
|
<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 payment setup gas.</p>
|
|
430
430
|
</div>
|
|
431
431
|
<script>
|
|
432
|
-
// MCP Apps protocol handshake
|
|
432
|
+
// MCP Apps protocol handshake for the wallet top-up page.
|
|
433
433
|
(function() {
|
|
434
434
|
var initId = 1;
|
|
435
435
|
|
|
@@ -977,4 +977,4 @@ async function startTopupServer(account) {
|
|
|
977
977
|
//#endregion
|
|
978
978
|
export { generateArtifactHtml as i, startTopupServer as n, topup_server_exports as r, getTopupUrl as t };
|
|
979
979
|
|
|
980
|
-
//# sourceMappingURL=topup-server-
|
|
980
|
+
//# sourceMappingURL=topup-server-BLdF1SFQ.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topup-server-BLdF1SFQ.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 (\n hasLogo &&\n r >= logoStartR &&\n r < logoStartR + logoH &&\n c >= logoStartC &&\n c < logoStartC + logoW\n ) {\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\nfunction walletAddress(wallet: WalletData | string): `0x${string}` {\n return (typeof wallet === 'string' ? wallet : wallet.address) as `0x${string}`\n}\n\nexport async function getBalanceUsdc(wallet: WalletData | string): 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: [walletAddress(wallet)],\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 | string): 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: walletAddress(wallet) })\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 `Payment wallet: ${wallet.address}`,\n `USDC on Base: ${balanceUsdc}`,\n `Gas on Base: ${balanceEth} ETH`,\n `Payment network: Base`,\n `Base ETH is used only for one-time payment setup gas.`,\n ]\n .filter(Boolean)\n .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: [`Send USDC on Base network to: ${wallet.address}`],\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 { isAddress } from 'viem'\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 {\n return readFileSync(p)\n } catch {\n /* try next */\n }\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\nfunction assertWalletAddress(wallet: WalletData | string): string {\n const walletAddress = typeof wallet === 'string' ? wallet : wallet.address\n if (!isAddress(walletAddress)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return walletAddress\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll('&', '&')\n .replaceAll('<', '<')\n .replaceAll('>', '>')\n .replaceAll('\"', '"')\n .replaceAll(\"'\", ''')\n}\n\nfunction jsonForScript(value: string): string {\n return JSON.stringify(value)\n .replaceAll('<', '\\\\u003c')\n .replaceAll('>', '\\\\u003e')\n .replaceAll('&', '\\\\u0026')\n}\n\nexport function getTopupUrl(): string | null {\n return serverPort ? `http://localhost:${serverPort}` : null\n}\n\nexport async function startTopupServer(wallet: WalletData | string): Promise<string> {\n const walletAddress = assertWalletAddress(wallet)\n\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, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify({ address: walletAddress }))\n return\n }\n\n if (req.url === '/api/balance') {\n Promise.all([getBalanceUsdc(walletAddress), getBalanceEth(walletAddress)])\n .then(([balanceUsdc, balanceEth]) => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(JSON.stringify({ balance_usdc: balanceUsdc, balance_eth: balanceEth }))\n })\n .catch(() => {\n res.writeHead(200, {\n 'Content-Type': 'application/json',\n 'Access-Control-Allow-Origin': '*',\n })\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, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(logoPng)\n return\n }\n\n if (req.url === '/assets/bg-pattern.png') {\n res.writeHead(200, {\n 'Content-Type': 'image/png',\n 'Cache-Control': 'public, max-age=86400',\n 'Access-Control-Allow-Origin': '*',\n })\n res.end(bgPatternPng)\n return\n }\n\n res.writeHead(200, { 'Content-Type': 'text/html' })\n res.end(generatePage(walletAddress))\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(walletAddressInput: string, topupUrl: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const safeTopupUrl = escapeHtml(topupUrl)\n const topupUrlJson = jsonForScript(topupUrl)\n\n const LOGO_B64 =\n '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('${safeTopupUrl}/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=\"${safeTopupUrl}/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()\">${safeWalletAddress}</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 payment setup gas.</p>\n</div>\n<script>\n// MCP Apps protocol handshake for the wallet top-up page.\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;\nvar TOPUP_URL = ${topupUrlJson};\nfunction fetchBal() {\n fetch(TOPUP_URL + '/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(walletAddressInput: string): string {\n const walletAddress = assertWalletAddress(walletAddressInput)\n const safeWalletAddress = escapeHtml(walletAddress)\n const walletAddressJson = jsonForScript(walletAddress)\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<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\">${safeWalletAddress}</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 payment setup 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 = ${walletAddressJson};\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 { isAddress } from 'viem'\nimport type { PaymentWalletAccount } from './tools.js'\n\ninterface ArtifactServerState {\n address: string\n assetServerUrl: string\n server: Server\n url: string\n}\n\nlet artifactServerState: ArtifactServerState | null = null\nconst REQUEST_TARGET_BASE = 'http://localhost'\n\nclass ProxyRequestError extends Error {\n status: number\n\n constructor(status: number, message: string) {\n super(message)\n this.name = 'ProxyRequestError'\n this.status = status\n }\n}\n\ninterface NormalizedProxyTarget {\n pathAndSearch: string\n pathname: string\n}\n\nfunction toWalletAddress(account: PaymentWalletAccount | string): string {\n const address = typeof account === 'string' ? account : account.address\n if (!isAddress(address)) {\n throw new Error('Wallet address must be a valid 0x-prefixed 20-byte EVM address')\n }\n return address\n}\n\nfunction send(\n res: ServerResponse,\n status: number,\n body: string | Buffer,\n contentType: string\n): 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\nfunction normalizeProxyTarget(reqUrl: string): NormalizedProxyTarget {\n if (!reqUrl.startsWith('/') || reqUrl.startsWith('//')) {\n throw new ProxyRequestError(400, 'Proxy request target must be an origin-form path')\n }\n\n const parsed = new URL(reqUrl, REQUEST_TARGET_BASE)\n if (parsed.origin !== REQUEST_TARGET_BASE) {\n throw new ProxyRequestError(400, 'Absolute and protocol-relative proxy targets are not allowed')\n }\n\n let decodedPathname: string\n try {\n decodedPathname = decodeURIComponent(parsed.pathname)\n } catch {\n throw new ProxyRequestError(400, 'Proxy request target contains invalid encoding')\n }\n\n if (decodedPathname.startsWith('//') || /^\\/[a-z][a-z0-9+.-]*:\\/\\//i.test(decodedPathname)) {\n throw new ProxyRequestError(400, 'Encoded host override targets are not allowed')\n }\n\n return {\n pathAndSearch: `${parsed.pathname}${parsed.search}`,\n pathname: parsed.pathname,\n }\n}\n\nasync function proxyToCopiedServer(\n proxyTarget: NormalizedProxyTarget,\n res: ServerResponse,\n assetServerUrl: string\n): Promise<void> {\n const allowedOrigin = new URL(assetServerUrl).origin\n const upstreamUrl = new URL(proxyTarget.pathAndSearch, assetServerUrl)\n if (upstreamUrl.origin !== allowedOrigin) {\n throw new ProxyRequestError(403, 'Upstream origin is not allowed')\n }\n\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 stopTopupServer(): Promise<void> {\n if (!artifactServerState) {\n return\n }\n\n const { server } = artifactServerState\n artifactServerState = null\n await new Promise<void>((resolve) => server.close(() => resolve()))\n}\n\nexport async function startTopupServer(account: PaymentWalletAccount | string): Promise<string> {\n const walletAddress = toWalletAddress(account)\n\n if (\n artifactServerState &&\n artifactServerState.address.toLowerCase() === walletAddress.toLowerCase()\n ) {\n return artifactServerState.url\n }\n\n const assetServerUrl = await startCopiedTopupServer(walletAddress)\n\n if (artifactServerState) {\n await stopTopupServer()\n }\n\n const server = createServer((req, res) => {\n const reqUrl = req.url ?? '/'\n let proxyTarget: NormalizedProxyTarget\n try {\n proxyTarget = normalizeProxyTarget(reqUrl)\n } catch (err) {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 400,\n JSON.stringify({ error: 'Invalid request target' }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n const { pathname } = proxyTarget\n\n if (pathname === '/' || pathname === '/index.html') {\n const artifactUrl = artifactServerState?.url ?? assetServerUrl\n send(res, 200, generateArtifactHtml(walletAddress, artifactUrl), 'text/html; charset=utf-8')\n return\n }\n\n if (pathname.startsWith('/assets/') || pathname.startsWith('/api/')) {\n void proxyToCopiedServer(proxyTarget, res, assetServerUrl).catch((err) => {\n if (err instanceof ProxyRequestError) {\n send(\n res,\n err.status,\n JSON.stringify({ error: err.message }) + '\\n',\n 'application/json; charset=utf-8'\n )\n return\n }\n send(\n res,\n 502,\n JSON.stringify({ error: (err as Error).message }) + '\\n',\n 'application/json; charset=utf-8'\n )\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: walletAddress,\n assetServerUrl,\n server,\n url,\n }\n\n return url\n}\n"],"mappings":"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"}
|
|
@@ -429,7 +429,7 @@ function generateArtifactHtml(walletAddressInput, topupUrl) {
|
|
|
429
429
|
<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 payment setup gas.</p>
|
|
430
430
|
</div>
|
|
431
431
|
<script>
|
|
432
|
-
// MCP Apps protocol handshake
|
|
432
|
+
// MCP Apps protocol handshake for the wallet top-up page.
|
|
433
433
|
(function() {
|
|
434
434
|
var initId = 1;
|
|
435
435
|
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { n as PACKAGE_VERSION, t as PACKAGE_INFO } from "./version-BA3J8hu4.mjs";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
|
-
import { createInterface } from "node:readline/promises";
|
|
4
|
-
import { stdin, stdout } from "node:process";
|
|
5
3
|
//#region src/update.ts
|
|
6
4
|
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
7
5
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -98,38 +96,6 @@ function runPackageUpdate(packageName = PACKAGE_INFO.name) {
|
|
|
98
96
|
throw new Error(`Update failed with ${detail}`);
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
|
-
function shouldPromptForUpdate(options = {}) {
|
|
102
|
-
const env = options.env ?? process.env;
|
|
103
|
-
if (env["CHAIN_INSIGHTS_SKIP_UPDATE_CHECK"] === "1") return false;
|
|
104
|
-
if (env["CI"] === "true") return false;
|
|
105
|
-
const input = options.input ?? stdin;
|
|
106
|
-
const output = options.output ?? stdout;
|
|
107
|
-
return input.isTTY === true && output.isTTY === true;
|
|
108
|
-
}
|
|
109
|
-
async function maybePromptForUpdate(options = {}) {
|
|
110
|
-
if (!shouldPromptForUpdate(options)) return;
|
|
111
|
-
const check = await checkForUpdate(options);
|
|
112
|
-
if (check.error || !check.updateAvailable || !check.latestVersion) return;
|
|
113
|
-
const output = options.output ?? stdout;
|
|
114
|
-
output.write(`\nChain Insights ${check.latestVersion} is available (current ${check.currentVersion}).\n`);
|
|
115
|
-
output.write(`Update command: ${check.updateCommand}\n`);
|
|
116
|
-
const rl = createInterface({
|
|
117
|
-
input: options.input ?? stdin,
|
|
118
|
-
output
|
|
119
|
-
});
|
|
120
|
-
try {
|
|
121
|
-
const answer = await rl.question("Update now? [Y/n] ");
|
|
122
|
-
if (!answer.trim() || answer.trim().toLowerCase().startsWith("y")) try {
|
|
123
|
-
runPackageUpdate(check.packageName);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
output.write(`Update failed: ${err.message}\n`);
|
|
126
|
-
output.write("Workspace initialization is complete. Run `cia update` when ready.\n");
|
|
127
|
-
}
|
|
128
|
-
else output.write("Skipped update. Run `cia update` when ready.\n");
|
|
129
|
-
} finally {
|
|
130
|
-
rl.close();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
99
|
function parseSemverCore(version) {
|
|
134
100
|
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+].*)?$/.exec(version);
|
|
135
101
|
if (!match) throw new Error(`Invalid semver version: ${version}`);
|
|
@@ -140,6 +106,6 @@ function parseSemverCore(version) {
|
|
|
140
106
|
];
|
|
141
107
|
}
|
|
142
108
|
//#endregion
|
|
143
|
-
export { checkForUpdate,
|
|
109
|
+
export { checkForUpdate, runPackageUpdate };
|
|
144
110
|
|
|
145
|
-
//# sourceMappingURL=update-
|
|
111
|
+
//# sourceMappingURL=update-MsoEFbhn.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update-
|
|
1
|
+
{"version":3,"file":"update-MsoEFbhn.mjs","names":[],"sources":["../src/update.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process'\nimport type { Readable, Writable } from 'node:stream'\nimport { createInterface } from 'node:readline/promises'\nimport { stdin, stdout } from 'node:process'\nimport { PACKAGE_INFO, PACKAGE_VERSION } from './version.js'\n\nconst DEFAULT_REGISTRY_URL = 'https://registry.npmjs.org'\nconst DEFAULT_TIMEOUT_MS = 3000\n\nexport interface UpdateCheckOptions {\n packageName?: string\n currentVersion?: string\n registryUrl?: string\n timeoutMs?: number\n fetchImpl?: typeof fetch\n}\n\nexport interface UpdateCheckResult {\n packageName: string\n currentVersion: string\n latestVersion?: string\n updateAvailable: boolean\n updateCommand: string\n error?: string\n}\n\nexport interface NpmInvocation {\n command: string\n args: string[]\n displayCommand: string\n}\n\nexport interface UpdatePromptOptions extends UpdateCheckOptions {\n input?: NodeJS.ReadStream & Readable\n output?: NodeJS.WriteStream & Writable\n env?: NodeJS.ProcessEnv\n}\n\nexport function compareSemverVersions(a: string, b: string): number {\n const left = parseSemverCore(a)\n const right = parseSemverCore(b)\n for (let i = 0; i < 3; i += 1) {\n if (left[i] > right[i]) return 1\n if (left[i] < right[i]) return -1\n }\n return 0\n}\n\nexport function resolveUpdateRegistryUrl(env: NodeJS.ProcessEnv = process.env): string {\n return env['CHAIN_INSIGHTS_NPM_REGISTRY_URL']?.trim() || DEFAULT_REGISTRY_URL\n}\n\nexport function buildPackageLatestUrl(\n packageName: string,\n registryUrl = DEFAULT_REGISTRY_URL\n): string {\n const base = registryUrl.endsWith('/') ? registryUrl.slice(0, -1) : registryUrl\n return `${base}/${encodeURIComponent(packageName)}/latest`\n}\n\nexport function resolveNpmUpdateInvocation(\n packageName = PACKAGE_INFO.name,\n env: NodeJS.ProcessEnv = process.env\n): NpmInvocation {\n const packageSpec = `${packageName}@latest`\n const displayCommand = `npm install -g ${packageSpec}`\n const npmExecPath = env['npm_execpath']?.trim()\n if (npmExecPath) {\n return {\n command: process.execPath,\n args: [npmExecPath, 'install', '-g', packageSpec],\n displayCommand,\n }\n }\n return {\n command: process.platform === 'win32' ? 'npm.cmd' : 'npm',\n args: ['install', '-g', packageSpec],\n displayCommand,\n }\n}\n\nexport async function fetchLatestPackageVersion(options: UpdateCheckOptions = {}): Promise<string> {\n const packageName = options.packageName ?? PACKAGE_INFO.name\n const registryUrl = options.registryUrl ?? resolveUpdateRegistryUrl()\n const fetchImpl = options.fetchImpl ?? fetch\n const controller = new AbortController()\n const timeout = setTimeout(() => controller.abort(), options.timeoutMs ?? DEFAULT_TIMEOUT_MS)\n\n try {\n const response = await fetchImpl(buildPackageLatestUrl(packageName, registryUrl), {\n headers: {\n accept: 'application/json',\n 'user-agent': `chain-insights/${PACKAGE_VERSION}`,\n },\n signal: controller.signal,\n })\n if (!response.ok) {\n throw new Error(`npmjs registry returned ${response.status}`)\n }\n const body = (await response.json()) as { version?: unknown }\n if (typeof body.version !== 'string' || !body.version.trim()) {\n throw new Error('npmjs registry response did not include a version')\n }\n return body.version.trim()\n } finally {\n clearTimeout(timeout)\n }\n}\n\nexport async function checkForUpdate(options: UpdateCheckOptions = {}): Promise<UpdateCheckResult> {\n const packageName = options.packageName ?? PACKAGE_INFO.name\n const currentVersion = options.currentVersion ?? PACKAGE_VERSION\n const updateCommand = resolveNpmUpdateInvocation(packageName).displayCommand\n try {\n const latestVersion = await fetchLatestPackageVersion(options)\n return {\n packageName,\n currentVersion,\n latestVersion,\n updateAvailable: compareSemverVersions(latestVersion, currentVersion) > 0,\n updateCommand,\n }\n } catch (err) {\n return {\n packageName,\n currentVersion,\n updateAvailable: false,\n updateCommand,\n error: (err as Error).message,\n }\n }\n}\n\nexport function runPackageUpdate(packageName = PACKAGE_INFO.name): void {\n const invocation = resolveNpmUpdateInvocation(packageName)\n const result = spawnSync(invocation.command, invocation.args, { stdio: 'inherit' })\n if (result.error) {\n throw result.error\n }\n if (result.status !== 0) {\n const detail = result.signal\n ? `signal ${result.signal}`\n : `exit code ${result.status ?? 'unknown'}`\n throw new Error(`Update failed with ${detail}`)\n }\n}\n\nexport function shouldPromptForUpdate(options: UpdatePromptOptions = {}): boolean {\n const env = options.env ?? process.env\n if (env['CHAIN_INSIGHTS_SKIP_UPDATE_CHECK'] === '1') return false\n if (env['CI'] === 'true') return false\n const input = options.input ?? stdin\n const output = options.output ?? stdout\n return input.isTTY === true && output.isTTY === true\n}\n\nexport async function maybePromptForUpdate(options: UpdatePromptOptions = {}): Promise<void> {\n if (!shouldPromptForUpdate(options)) return\n\n const check = await checkForUpdate(options)\n if (check.error || !check.updateAvailable || !check.latestVersion) return\n\n const output = options.output ?? stdout\n output.write(\n `\\nChain Insights ${check.latestVersion} is available (current ${check.currentVersion}).\\n`\n )\n output.write(`Update command: ${check.updateCommand}\\n`)\n\n const rl = createInterface({\n input: options.input ?? stdin,\n output,\n })\n try {\n const answer = await rl.question('Update now? [Y/n] ')\n if (!answer.trim() || answer.trim().toLowerCase().startsWith('y')) {\n try {\n runPackageUpdate(check.packageName)\n } catch (err) {\n output.write(`Update failed: ${(err as Error).message}\\n`)\n output.write('Workspace initialization is complete. Run `cia update` when ready.\\n')\n }\n } else {\n output.write('Skipped update. Run `cia update` when ready.\\n')\n }\n } finally {\n rl.close()\n }\n}\n\nfunction parseSemverCore(version: string): [number, number, number] {\n const match = /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:[-+].*)?$/.exec(version)\n if (!match) throw new Error(`Invalid semver version: ${version}`)\n return [Number(match[1]), Number(match[2]), Number(match[3])]\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
const require_version = require("./version-CO9Or_YV.cjs");
|
|
2
2
|
let node_child_process = require("node:child_process");
|
|
3
|
-
let node_readline_promises = require("node:readline/promises");
|
|
4
|
-
let node_process = require("node:process");
|
|
5
3
|
//#region src/update.ts
|
|
6
4
|
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
7
5
|
const DEFAULT_TIMEOUT_MS = 3e3;
|
|
@@ -98,38 +96,6 @@ function runPackageUpdate(packageName = require_version.PACKAGE_INFO.name) {
|
|
|
98
96
|
throw new Error(`Update failed with ${detail}`);
|
|
99
97
|
}
|
|
100
98
|
}
|
|
101
|
-
function shouldPromptForUpdate(options = {}) {
|
|
102
|
-
const env = options.env ?? process.env;
|
|
103
|
-
if (env["CHAIN_INSIGHTS_SKIP_UPDATE_CHECK"] === "1") return false;
|
|
104
|
-
if (env["CI"] === "true") return false;
|
|
105
|
-
const input = options.input ?? node_process.stdin;
|
|
106
|
-
const output = options.output ?? node_process.stdout;
|
|
107
|
-
return input.isTTY === true && output.isTTY === true;
|
|
108
|
-
}
|
|
109
|
-
async function maybePromptForUpdate(options = {}) {
|
|
110
|
-
if (!shouldPromptForUpdate(options)) return;
|
|
111
|
-
const check = await checkForUpdate(options);
|
|
112
|
-
if (check.error || !check.updateAvailable || !check.latestVersion) return;
|
|
113
|
-
const output = options.output ?? node_process.stdout;
|
|
114
|
-
output.write(`\nChain Insights ${check.latestVersion} is available (current ${check.currentVersion}).\n`);
|
|
115
|
-
output.write(`Update command: ${check.updateCommand}\n`);
|
|
116
|
-
const rl = (0, node_readline_promises.createInterface)({
|
|
117
|
-
input: options.input ?? node_process.stdin,
|
|
118
|
-
output
|
|
119
|
-
});
|
|
120
|
-
try {
|
|
121
|
-
const answer = await rl.question("Update now? [Y/n] ");
|
|
122
|
-
if (!answer.trim() || answer.trim().toLowerCase().startsWith("y")) try {
|
|
123
|
-
runPackageUpdate(check.packageName);
|
|
124
|
-
} catch (err) {
|
|
125
|
-
output.write(`Update failed: ${err.message}\n`);
|
|
126
|
-
output.write("Workspace initialization is complete. Run `cia update` when ready.\n");
|
|
127
|
-
}
|
|
128
|
-
else output.write("Skipped update. Run `cia update` when ready.\n");
|
|
129
|
-
} finally {
|
|
130
|
-
rl.close();
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
99
|
function parseSemverCore(version) {
|
|
134
100
|
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:[-+].*)?$/.exec(version);
|
|
135
101
|
if (!match) throw new Error(`Invalid semver version: ${version}`);
|
|
@@ -141,5 +107,4 @@ function parseSemverCore(version) {
|
|
|
141
107
|
}
|
|
142
108
|
//#endregion
|
|
143
109
|
exports.checkForUpdate = checkForUpdate;
|
|
144
|
-
exports.maybePromptForUpdate = maybePromptForUpdate;
|
|
145
110
|
exports.runPackageUpdate = runPackageUpdate;
|
package/docs/architecture.md
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# Architecture
|
|
2
2
|
|
|
3
|
-
Chain Insights is a
|
|
4
|
-
|
|
5
|
-
and leaves graph computation to the configured Chain Insights Graph backend.
|
|
3
|
+
Chain Insights is a CLI and MCP proxy around a remote graph execution endpoint.
|
|
4
|
+
It leaves graph computation to the configured Chain Insights Graph backend.
|
|
6
5
|
|
|
7
6
|
## Product Layers
|
|
8
7
|
|
|
@@ -12,12 +11,10 @@ flowchart LR
|
|
|
12
11
|
Agent --> CLI[Chain Insights CLI]
|
|
13
12
|
CLI --> Config[Local config]
|
|
14
13
|
CLI --> Wallet[Encrypted wallet]
|
|
15
|
-
CLI --> Workspace[Investigation workspace]
|
|
16
14
|
Proxy --> ChainInsightsGraph[Chain Insights Graph]
|
|
17
15
|
CLI --> ChainInsightsGraph
|
|
18
16
|
ChainInsightsGraph --> GraphData[(Graph intelligence)]
|
|
19
17
|
Wallet --> Base[Base RPC]
|
|
20
|
-
Workspace --> Browser[Local browser reports]
|
|
21
18
|
```
|
|
22
19
|
|
|
23
20
|
The CLI is the operator entry point. The MCP proxy exposes the same local
|
|
@@ -26,26 +23,20 @@ against the unified topology graph and facts.
|
|
|
26
23
|
|
|
27
24
|
## Module Responsibilities
|
|
28
25
|
|
|
29
|
-
| Module area
|
|
30
|
-
|
|
|
31
|
-
| CLI
|
|
32
|
-
| Config
|
|
33
|
-
| Wallet
|
|
34
|
-
| MCP client/proxy
|
|
35
|
-
| Workspace evidence | Evidence, dossiers, reports, and workspace sessions |
|
|
36
|
-
| Visualization | Graph data model and self-contained HTML generation |
|
|
37
|
-
| Server | Local report and visualization server |
|
|
38
|
-
| Workspace init | Investigation workspace scaffold and runtime schema notes |
|
|
26
|
+
| Module area | Responsibility |
|
|
27
|
+
| ---------------- | ----------------------------------------------------------- |
|
|
28
|
+
| CLI | Command routing and user-facing workflows |
|
|
29
|
+
| Config | Local config schema and owner-only storage |
|
|
30
|
+
| Wallet | Encrypted EVM wallet and Base USDC balance checks |
|
|
31
|
+
| MCP client/proxy | x402, debug-token, test-key auth, schema cache, stdio proxy |
|
|
39
32
|
|
|
40
33
|
## Data Flow
|
|
41
34
|
|
|
42
|
-
1.
|
|
43
|
-
2.
|
|
44
|
-
3.
|
|
45
|
-
4.
|
|
46
|
-
|
|
47
|
-
summaries in the active workspace.
|
|
48
|
-
6. Case evidence references report files instead of embedding large payloads.
|
|
35
|
+
1. Chain Insights reads local config for endpoint and auth mode.
|
|
36
|
+
2. Graph queries go to the configured Chain Insights Graph endpoint.
|
|
37
|
+
3. The graph backend executes against `topology` or `facts`.
|
|
38
|
+
4. Chain Insights returns tool summaries and structured facts through the CLI
|
|
39
|
+
or MCP proxy.
|
|
49
40
|
|
|
50
41
|
## Config
|
|
51
42
|
|
|
@@ -60,8 +51,9 @@ chain-insights config set graphMcpEndpoint https://mcp.chain-insights.ai/
|
|
|
60
51
|
export CHAIN_INSIGHTS_GRAPH_MCP_ENDPOINT=https://mcp.chain-insights.ai/
|
|
61
52
|
```
|
|
62
53
|
|
|
63
|
-
The runtime default is
|
|
64
|
-
|
|
54
|
+
The runtime default is the hosted production endpoint
|
|
55
|
+
`https://mcp.chain-insights.ai/`. Local loopback
|
|
56
|
+
`http://127.0.0.1:8012/mcp` remains available through explicit configuration.
|
|
65
57
|
|
|
66
58
|
Supported config keys:
|
|
67
59
|
|
|
@@ -71,7 +63,6 @@ Supported config keys:
|
|
|
71
63
|
| `graphMcpAuthToken` | Chain Insights Graph bearer credential for test access keys or local debug UAT |
|
|
72
64
|
| `graphMcpMode` | Endpoint access mode: `paid` (default) or `debug` |
|
|
73
65
|
| `walletAddress` | Optional wallet metadata |
|
|
74
|
-
| `serverPort` | Local visualization and graph report server port |
|
|
75
66
|
| `dataDir` | Local Chain Insights data directory |
|
|
76
67
|
| `version` | Config schema version |
|
|
77
68
|
|
|
@@ -89,25 +80,6 @@ Default local data directory:
|
|
|
89
80
|
mcp-schema-*.json
|
|
90
81
|
```
|
|
91
82
|
|
|
92
|
-
Investigation outputs belong in initialized workspaces, not in the global data
|
|
93
|
-
directory.
|
|
94
|
-
|
|
95
|
-
Workspace outputs include:
|
|
96
|
-
|
|
97
|
-
```text
|
|
98
|
-
artifacts/
|
|
99
|
-
entities/
|
|
100
|
-
imports/
|
|
101
|
-
reports/
|
|
102
|
-
reports/graphs/
|
|
103
|
-
reports/tables/
|
|
104
|
-
sessions/
|
|
105
|
-
templates/
|
|
106
|
-
.chain-insights/schema/
|
|
107
|
-
.chain-insights/runtime/
|
|
108
|
-
.chain-insights/runtime-skill/
|
|
109
|
-
```
|
|
110
|
-
|
|
111
83
|
## Security Model
|
|
112
84
|
|
|
113
85
|
- `wallet.json`, `config.json`, and schema cache files use owner-only
|
|
@@ -116,8 +88,6 @@ templates/
|
|
|
116
88
|
- Debug bearer tokens are redacted in CLI output.
|
|
117
89
|
- Test access keys are payment bypass credentials.
|
|
118
90
|
- Production x402 should use a hot wallet with limited funds.
|
|
119
|
-
- Graph report JSON is stored in the active workspace and served from
|
|
120
|
-
`127.0.0.1`.
|
|
121
91
|
- Chain Insights does not custody user funds.
|
|
122
92
|
- CI runs typecheck, tests, build, npm package packing, vulnerability audit,
|
|
123
93
|
registry signature verification, secret-pattern scanning, CodeQL, OpenSSF
|
package/docs/contributing.md
CHANGED
|
@@ -25,15 +25,12 @@ When adding a tool, document:
|
|
|
25
25
|
- Chain Insights Graph primitive calls.
|
|
26
26
|
- Use of `topology` and `facts`.
|
|
27
27
|
- Result contract.
|
|
28
|
-
- Workspace artifact and report behavior.
|
|
29
|
-
- Graph report behavior.
|
|
30
28
|
- Tests and dogfood path.
|
|
31
29
|
|
|
32
30
|
## Documentation Updates
|
|
33
31
|
|
|
34
32
|
- README stays product-first.
|
|
35
33
|
- Detailed graph contracts belong in `docs/graph-tools.md`.
|
|
36
|
-
- Workspace behavior belongs in `docs/investigation-workspaces.md`.
|
|
37
34
|
- MCP proxy/client setup belongs in `docs/mcp-proxy.md`.
|
|
38
35
|
- Debug and UAT details belong in `docs/debugging.md`.
|
|
39
36
|
- Agent-facing contributor guidance belongs in shipped skills under `skills/`.
|