web3-tools-mcp 1.5.0 → 2.0.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/CHANGELOG.md +95 -0
- package/README.md +346 -164
- package/dist/anvil.d.ts +1 -1
- package/dist/anvil.d.ts.map +1 -1
- package/dist/anvil.js +11 -9
- package/dist/anvil.js.map +1 -1
- package/dist/chain-meta.d.ts.map +1 -1
- package/dist/chain-meta.js +7 -3
- package/dist/chain-meta.js.map +1 -1
- package/dist/chains.d.ts +960 -0
- package/dist/chains.d.ts.map +1 -0
- package/dist/chains.js +108 -0
- package/dist/chains.js.map +1 -0
- package/dist/client.d.ts +10 -1717
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +33 -216
- package/dist/client.js.map +1 -1
- package/dist/http-server.d.ts +18 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +89 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.js +77 -32
- package/dist/index.js.map +1 -1
- package/dist/kv-storage.d.ts +23 -0
- package/dist/kv-storage.d.ts.map +1 -0
- package/dist/kv-storage.js +69 -0
- package/dist/kv-storage.js.map +1 -0
- package/dist/oauth.d.ts +31 -0
- package/dist/oauth.d.ts.map +1 -0
- package/dist/oauth.js +227 -0
- package/dist/oauth.js.map +1 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +14 -4
- package/dist/preview.js.map +1 -1
- package/dist/tools/advanced.d.ts +4 -4
- package/dist/tools/advanced.d.ts.map +1 -1
- package/dist/tools/advanced.js +228 -247
- package/dist/tools/advanced.js.map +1 -1
- package/dist/tools/balance.d.ts +1 -1
- package/dist/tools/balance.d.ts.map +1 -1
- package/dist/tools/balance.js +97 -103
- package/dist/tools/balance.js.map +1 -1
- package/dist/tools/contract-info.d.ts +3 -3
- package/dist/tools/contract-info.d.ts.map +1 -1
- package/dist/tools/contract-info.js +150 -161
- package/dist/tools/contract-info.js.map +1 -1
- package/dist/tools/contract.d.ts +4 -4
- package/dist/tools/contract.d.ts.map +1 -1
- package/dist/tools/contract.js +91 -110
- package/dist/tools/contract.js.map +1 -1
- package/dist/tools/ens.d.ts +6 -6
- package/dist/tools/ens.d.ts.map +1 -1
- package/dist/tools/ens.js +239 -297
- package/dist/tools/ens.js.map +1 -1
- package/dist/tools/gas.d.ts +4 -4
- package/dist/tools/gas.d.ts.map +1 -1
- package/dist/tools/gas.js +90 -138
- package/dist/tools/gas.js.map +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/logs.d.ts +1 -1
- package/dist/tools/logs.d.ts.map +1 -1
- package/dist/tools/logs.js +36 -37
- package/dist/tools/logs.js.map +1 -1
- package/dist/tools/signatures.d.ts.map +1 -1
- package/dist/tools/signatures.js +67 -89
- package/dist/tools/signatures.js.map +1 -1
- package/dist/tools/transactions.d.ts +48 -4
- package/dist/tools/transactions.d.ts.map +1 -1
- package/dist/tools/transactions.js +228 -44
- package/dist/tools/transactions.js.map +1 -1
- package/dist/types.d.ts +2 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +3 -8
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +36 -98
- package/dist/utils.js.map +1 -1
- package/dist/wallet-client.d.ts +11 -1
- package/dist/wallet-client.d.ts.map +1 -1
- package/dist/wallet-client.js +27 -9
- package/dist/wallet-client.js.map +1 -1
- package/dist/walletconnect.d.ts +52 -0
- package/dist/walletconnect.d.ts.map +1 -0
- package/dist/walletconnect.js +221 -0
- package/dist/walletconnect.js.map +1 -0
- package/package.json +21 -9
- package/src/anvil.ts +15 -18
- package/src/chain-meta.ts +8 -4
- package/src/chains.ts +145 -0
- package/src/client.ts +39 -252
- package/src/http-server.ts +117 -0
- package/src/index.ts +98 -49
- package/src/kv-storage.ts +83 -0
- package/src/oauth.ts +270 -0
- package/src/preview.ts +15 -14
- package/src/tools/advanced.ts +195 -213
- package/src/tools/balance.ts +103 -108
- package/src/tools/contract-info.ts +163 -184
- package/src/tools/contract.ts +123 -144
- package/src/tools/ens.ts +290 -345
- package/src/tools/gas.ts +122 -170
- package/src/tools/index.ts +2 -2
- package/src/tools/logs.ts +45 -43
- package/src/tools/signatures.ts +68 -86
- package/src/tools/transactions.ts +283 -50
- package/src/types.ts +2 -15
- package/src/utils.ts +56 -127
- package/src/wallet-client.ts +31 -9
- package/src/walletconnect.ts +269 -0
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { mkdirSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { getClientManager } from './client.js';
|
|
5
|
+
import { getKeyValueStorage } from './kv-storage.js';
|
|
6
|
+
async function importSignClient() {
|
|
7
|
+
// Imported lazily: several MB of SDK that is pointless to load without a project ID.
|
|
8
|
+
const { SignClient } = await import('@walletconnect/sign-client');
|
|
9
|
+
return SignClient;
|
|
10
|
+
}
|
|
11
|
+
const METHODS = ['eth_sendTransaction', 'personal_sign', 'eth_signTypedData_v4'];
|
|
12
|
+
const EVENTS = ['chainChanged', 'accountsChanged'];
|
|
13
|
+
const APPROVAL_TIMEOUT = 300_000;
|
|
14
|
+
/**
|
|
15
|
+
* Where this server lives, as shown to the wallet. A hosted server knows its public URL;
|
|
16
|
+
* a local one is only reachable from the machine it runs on, and says so.
|
|
17
|
+
*/
|
|
18
|
+
function serverUrl() {
|
|
19
|
+
if (process.env.MCP_PUBLIC_URL)
|
|
20
|
+
return process.env.MCP_PUBLIC_URL.replace(/\/$/, '');
|
|
21
|
+
if (process.env.MCP_HTTP_PORT)
|
|
22
|
+
return `http://localhost:${process.env.MCP_HTTP_PORT}`;
|
|
23
|
+
return 'http://localhost';
|
|
24
|
+
}
|
|
25
|
+
function storePath() {
|
|
26
|
+
const dir = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'web3-tools-mcp');
|
|
27
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
28
|
+
return join(dir, 'walletconnect.db');
|
|
29
|
+
}
|
|
30
|
+
export class PhoneSigner {
|
|
31
|
+
projectId;
|
|
32
|
+
client;
|
|
33
|
+
starting;
|
|
34
|
+
pairing;
|
|
35
|
+
/** Label shown in the wallet; whoever is driving this server says who they are. */
|
|
36
|
+
agent;
|
|
37
|
+
constructor(projectId) {
|
|
38
|
+
this.projectId = projectId;
|
|
39
|
+
}
|
|
40
|
+
async start() {
|
|
41
|
+
if (this.client)
|
|
42
|
+
return;
|
|
43
|
+
if (this.starting)
|
|
44
|
+
return this.starting;
|
|
45
|
+
this.starting = (async () => {
|
|
46
|
+
const SignClient = await importSignClient();
|
|
47
|
+
// Redis when hosted (a free-tier filesystem is ephemeral, and losing the store means
|
|
48
|
+
// re-pairing by QR after every sleep); a file next to the other config locally.
|
|
49
|
+
const storage = getKeyValueStorage();
|
|
50
|
+
this.client = (await SignClient.init({
|
|
51
|
+
projectId: this.projectId,
|
|
52
|
+
...(storage ? { storage } : { storageOptions: { database: storePath() } }),
|
|
53
|
+
// What the wallet shows when asking you to approve. Approving does not connect a
|
|
54
|
+
// website — it lets an agent request signatures for as long as the session lives,
|
|
55
|
+
// so the dialog says that. The name is self-reported and proves nothing; the URL
|
|
56
|
+
// is what distinguishes this server from any other.
|
|
57
|
+
metadata: {
|
|
58
|
+
name: this.agent ? `${this.agent} via web3-tools-mcp` : 'web3-tools-mcp (AI agent)',
|
|
59
|
+
description: 'An AI agent uses this server to request transactions. You approve each one here.',
|
|
60
|
+
url: serverUrl(),
|
|
61
|
+
icons: []
|
|
62
|
+
}
|
|
63
|
+
}));
|
|
64
|
+
})().finally(() => {
|
|
65
|
+
this.starting = undefined;
|
|
66
|
+
});
|
|
67
|
+
return this.starting;
|
|
68
|
+
}
|
|
69
|
+
/** The most recent still-valid session, if the phone is already paired. */
|
|
70
|
+
async session() {
|
|
71
|
+
await this.start();
|
|
72
|
+
const sessions = this.client?.session.getAll() ?? [];
|
|
73
|
+
const live = sessions.filter((s) => s.expiry * 1000 > Date.now());
|
|
74
|
+
const session = live[live.length - 1];
|
|
75
|
+
if (!session)
|
|
76
|
+
return undefined;
|
|
77
|
+
const accounts = session.namespaces.eip155?.accounts ?? [];
|
|
78
|
+
return {
|
|
79
|
+
topic: session.topic,
|
|
80
|
+
// "eip155:8453:0xabc…" → address / chain id
|
|
81
|
+
accounts: [...new Set(accounts.map((a) => a.split(':')[2]))],
|
|
82
|
+
chains: [...new Set(accounts.map((a) => Number(a.split(':')[1])))],
|
|
83
|
+
peer: session.peer?.metadata?.name
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async isPaired() {
|
|
87
|
+
return Boolean(await this.session());
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Start a pairing and return its URI immediately, resolving the wallet's approval in the
|
|
91
|
+
* background — a tool result only reaches the user once it returns, so blocking here
|
|
92
|
+
* would hide the QR they are supposed to scan.
|
|
93
|
+
*/
|
|
94
|
+
async pair(chains, agent) {
|
|
95
|
+
// Metadata is fixed when the client starts, so a new label needs a new client. Only
|
|
96
|
+
// safe while nothing is connected — an existing session owns the old client.
|
|
97
|
+
if (agent && agent !== this.agent && this.client && this.client.session.getAll().length === 0) {
|
|
98
|
+
this.client = undefined;
|
|
99
|
+
}
|
|
100
|
+
if (agent)
|
|
101
|
+
this.agent = agent;
|
|
102
|
+
await this.start();
|
|
103
|
+
if (!this.client)
|
|
104
|
+
throw new Error('WalletConnect failed to start');
|
|
105
|
+
const clientManager = getClientManager();
|
|
106
|
+
const eip155 = chains.map((chain) => `eip155:${clientManager.getChainId(chain)}`);
|
|
107
|
+
const { uri, approval } = await this.client.connect({
|
|
108
|
+
// Optional rather than required: wallets reject a session outright when they cannot
|
|
109
|
+
// serve every required chain, and few support all thirteen.
|
|
110
|
+
optionalNamespaces: { eip155: { chains: eip155, methods: METHODS, events: EVENTS } }
|
|
111
|
+
});
|
|
112
|
+
if (!uri)
|
|
113
|
+
throw new Error('WalletConnect did not return a pairing URI');
|
|
114
|
+
this.pairing = { uri, expiresAt: Date.now() + APPROVAL_TIMEOUT };
|
|
115
|
+
approval()
|
|
116
|
+
.then((session) => console.error(`[WalletConnect] Paired with ${session.peer?.metadata?.name ?? 'a wallet'}`))
|
|
117
|
+
.catch((error) => console.error('[WalletConnect] Pairing was not completed:', error.message));
|
|
118
|
+
return uri;
|
|
119
|
+
}
|
|
120
|
+
/** Send a request to the paired wallet. Throws if the chain is outside the session. */
|
|
121
|
+
async request(chain, method, params) {
|
|
122
|
+
const session = await this.session();
|
|
123
|
+
if (!session || !this.client)
|
|
124
|
+
throw new Error('No phone wallet paired');
|
|
125
|
+
const chainId = getClientManager().getChainId(chain);
|
|
126
|
+
if (!session.chains.includes(chainId)) {
|
|
127
|
+
throw new Error(`The paired wallet did not approve ${chain} (chain ${chainId}). It approved: ${session.chains.join(', ')}. Re-pair to add it.`);
|
|
128
|
+
}
|
|
129
|
+
return this.client.request({
|
|
130
|
+
topic: session.topic,
|
|
131
|
+
chainId: `eip155:${chainId}`,
|
|
132
|
+
request: { method, params }
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/** Every live session, not just the one that would be used for signing. */
|
|
136
|
+
async sessions() {
|
|
137
|
+
await this.start();
|
|
138
|
+
return (this.client?.session.getAll() ?? []).map((session) => ({
|
|
139
|
+
topic: session.topic,
|
|
140
|
+
accounts: [...new Set((session.namespaces.eip155?.accounts ?? []).map((a) => a.split(':')[2]))],
|
|
141
|
+
chains: [...new Set((session.namespaces.eip155?.accounts ?? []).map((a) => Number(a.split(':')[1])))],
|
|
142
|
+
peer: session.peer?.metadata?.name
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Disconnect everything: sessions, and the pairings underneath them. Each pairing
|
|
147
|
+
* attempt leaves one behind whether or not a wallet ever approved it, so they
|
|
148
|
+
* accumulate quietly in the store.
|
|
149
|
+
*/
|
|
150
|
+
async disconnectAll() {
|
|
151
|
+
await this.start();
|
|
152
|
+
if (!this.client)
|
|
153
|
+
return { sessions: 0, pairings: 0 };
|
|
154
|
+
const sessions = this.client.session.getAll();
|
|
155
|
+
for (const session of sessions) {
|
|
156
|
+
await this.client
|
|
157
|
+
.disconnect({ topic: session.topic, reason: { code: 6000, message: 'User disconnected' } })
|
|
158
|
+
.catch((error) => console.error('[WalletConnect] Could not disconnect a session:', error.message));
|
|
159
|
+
}
|
|
160
|
+
const pairings = this.client.core.pairing.getPairings();
|
|
161
|
+
for (const pairing of pairings) {
|
|
162
|
+
await this.client.core.pairing
|
|
163
|
+
.disconnect({ topic: pairing.topic })
|
|
164
|
+
.catch((error) => console.error('[WalletConnect] Could not drop a pairing:', error.message));
|
|
165
|
+
}
|
|
166
|
+
return { sessions: sessions.length, pairings: pairings.length };
|
|
167
|
+
}
|
|
168
|
+
async unpair() {
|
|
169
|
+
await this.disconnectAll();
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
let wallets;
|
|
173
|
+
/**
|
|
174
|
+
* Wallets from WalletConnect's registry, so the links we hand out are the schemes each
|
|
175
|
+
* wallet actually registered rather than ones we invented. Fetched once per process; a
|
|
176
|
+
* failure is not fatal, it just means no ready-made links.
|
|
177
|
+
*/
|
|
178
|
+
async function walletRegistry(projectId) {
|
|
179
|
+
if (wallets)
|
|
180
|
+
return wallets;
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetch(`https://explorer-api.walletconnect.com/v3/wallets?projectId=${projectId}&entries=100&page=1`);
|
|
183
|
+
const listings = (await response.json()).listings ?? {};
|
|
184
|
+
wallets = Object.values(listings)
|
|
185
|
+
.map(({ name, mobile }) => ({ name, native: mobile?.native || undefined, universal: mobile?.universal || undefined }))
|
|
186
|
+
.filter((wallet) => wallet.native || wallet.universal);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
wallets = [];
|
|
190
|
+
}
|
|
191
|
+
return wallets;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Links that open a wallet straight onto this pairing, for when the agent is running on
|
|
195
|
+
* the phone itself and there is no second screen to scan from.
|
|
196
|
+
*
|
|
197
|
+
* Native schemes are preferred over universal links: chat apps are documented to mishandle
|
|
198
|
+
* the latter, opening the wallet with no prompt or diverting to an app store.
|
|
199
|
+
*/
|
|
200
|
+
export async function pairingLinks(projectId, uri, search) {
|
|
201
|
+
const registry = await walletRegistry(projectId);
|
|
202
|
+
const encoded = encodeURIComponent(uri);
|
|
203
|
+
const matches = search
|
|
204
|
+
? registry.filter((wallet) => wallet.name.toLowerCase().includes(search.toLowerCase()))
|
|
205
|
+
: registry.slice(0, 12);
|
|
206
|
+
return Object.fromEntries(matches.map((wallet) => {
|
|
207
|
+
const base = wallet.native ?? `${wallet.universal?.replace(/\/$/, '')}/`;
|
|
208
|
+
return [wallet.name, `${base}wc?uri=${encoded}`];
|
|
209
|
+
}));
|
|
210
|
+
}
|
|
211
|
+
let signer = null;
|
|
212
|
+
/** Configured only when a WalletConnect project id is present. */
|
|
213
|
+
export function getPhoneSigner() {
|
|
214
|
+
const projectId = getClientManager().getConfig().walletConnectProjectId;
|
|
215
|
+
if (!projectId)
|
|
216
|
+
return null;
|
|
217
|
+
if (!signer)
|
|
218
|
+
signer = new PhoneSigner(projectId);
|
|
219
|
+
return signer;
|
|
220
|
+
}
|
|
221
|
+
//# sourceMappingURL=walletconnect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"walletconnect.js","sourceRoot":"","sources":["../src/walletconnect.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web3-tools-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Model Context Protocol server for blockchain
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Model Context Protocol server for blockchain data and transaction signing — sign in a browser wallet or on your phone, with simulation and ERC-7730 clear signing before you approve",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"README.md",
|
|
14
|
+
"CHANGELOG.md",
|
|
14
15
|
"LICENSE",
|
|
15
16
|
"src"
|
|
16
17
|
],
|
|
@@ -19,14 +20,15 @@
|
|
|
19
20
|
},
|
|
20
21
|
"scripts": {
|
|
21
22
|
"clean": "rm -rf dist",
|
|
22
|
-
"build": "npm run clean &&
|
|
23
|
+
"build": "npm run clean && tsc && cp src/erc7730-index.json.gz dist/",
|
|
23
24
|
"watch": "tsc --watch",
|
|
24
|
-
"
|
|
25
|
-
"prepare": "npm run build",
|
|
25
|
+
"typecheck": "tsc -p tsconfig.test.json",
|
|
26
26
|
"test": "vitest run",
|
|
27
27
|
"test:watch": "vitest",
|
|
28
28
|
"test:ui": "vitest --ui",
|
|
29
|
-
"sync:registry": "node scripts/build-erc7730-index.mjs /tmp/erc7730/registry src/erc7730-index.json.gz"
|
|
29
|
+
"sync:registry": "node scripts/build-erc7730-index.mjs /tmp/erc7730/registry src/erc7730-index.json.gz",
|
|
30
|
+
"start": "node dist/index.js",
|
|
31
|
+
"prepack": "npm run build && cp ../README.md ../CHANGELOG.md ../LICENSE ."
|
|
30
32
|
},
|
|
31
33
|
"keywords": [
|
|
32
34
|
"mcp",
|
|
@@ -37,13 +39,18 @@
|
|
|
37
39
|
"ens",
|
|
38
40
|
"ethereum-name-service",
|
|
39
41
|
"model-context-protocol",
|
|
40
|
-
"ai"
|
|
42
|
+
"ai",
|
|
43
|
+
"walletconnect",
|
|
44
|
+
"clear-signing",
|
|
45
|
+
"erc-7730",
|
|
46
|
+
"transaction-signing"
|
|
41
47
|
],
|
|
42
48
|
"author": "Benjamin Hajnal <hajnalbenjamin@gmail.com>",
|
|
43
49
|
"license": "MIT",
|
|
44
50
|
"repository": {
|
|
45
51
|
"type": "git",
|
|
46
|
-
"url": "git+https://github.com/hajnalben/web3-tools-mcp.git"
|
|
52
|
+
"url": "git+https://github.com/hajnalben/web3-tools-mcp.git",
|
|
53
|
+
"directory": "mcp"
|
|
47
54
|
},
|
|
48
55
|
"bugs": {
|
|
49
56
|
"url": "https://github.com/hajnalben/web3-tools-mcp/issues"
|
|
@@ -53,13 +60,18 @@
|
|
|
53
60
|
"@envio-dev/hypersync-client": "^0.6.5",
|
|
54
61
|
"@modelcontextprotocol/sdk": "^1.20.1",
|
|
55
62
|
"@shazow/whatsabi": "^0.29.0",
|
|
63
|
+
"@walletconnect/sign-client": "^2.25.0",
|
|
64
|
+
"express": "^5.2.1",
|
|
65
|
+
"qrcode-terminal": "^0.12.0",
|
|
56
66
|
"viem": "^2.31.4",
|
|
57
|
-
"web3-wallet-relay": "^1.
|
|
67
|
+
"web3-wallet-relay": "^1.2.0",
|
|
58
68
|
"ws": "^8.18.0",
|
|
59
69
|
"zod": "^3.25.76"
|
|
60
70
|
},
|
|
61
71
|
"devDependencies": {
|
|
72
|
+
"@types/express": "^5.0.6",
|
|
62
73
|
"@types/node": "^20.19.23",
|
|
74
|
+
"@types/qrcode-terminal": "^0.12.2",
|
|
63
75
|
"@types/ws": "^8.5.13",
|
|
64
76
|
"@vitest/ui": "^3.2.4",
|
|
65
77
|
"dotenv": "^17.2.3",
|
package/src/anvil.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { type ChildProcess, spawn } from 'node:child_process'
|
|
2
2
|
import { createPublicClient, http } from 'viem'
|
|
3
3
|
import { mainnet } from 'viem/chains'
|
|
4
4
|
|
|
@@ -18,16 +18,16 @@ export interface AnvilConfig {
|
|
|
18
18
|
|
|
19
19
|
// Check if anvil is installed
|
|
20
20
|
export async function isAnvilInstalled(): Promise<boolean> {
|
|
21
|
-
return new Promise(resolve => {
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
22
|
const proc = spawn('anvil', ['--version'], { shell: true })
|
|
23
|
-
proc.on('close', code => resolve(code === 0))
|
|
23
|
+
proc.on('close', (code) => resolve(code === 0))
|
|
24
24
|
proc.on('error', () => resolve(false))
|
|
25
25
|
})
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
// Find an available port
|
|
29
29
|
async function findAvailablePort(startPort: number = 8545): Promise<number> {
|
|
30
|
-
const net = await import('net')
|
|
30
|
+
const net = await import('node:net')
|
|
31
31
|
return new Promise((resolve, reject) => {
|
|
32
32
|
const server = net.createServer()
|
|
33
33
|
server.listen(startPort, () => {
|
|
@@ -40,7 +40,9 @@ async function findAvailablePort(startPort: number = 8545): Promise<number> {
|
|
|
40
40
|
})
|
|
41
41
|
server.on('error', () => {
|
|
42
42
|
// Port in use, try next one
|
|
43
|
-
findAvailablePort(startPort + 1)
|
|
43
|
+
findAvailablePort(startPort + 1)
|
|
44
|
+
.then(resolve)
|
|
45
|
+
.catch(reject)
|
|
44
46
|
})
|
|
45
47
|
})
|
|
46
48
|
}
|
|
@@ -49,9 +51,7 @@ async function findAvailablePort(startPort: number = 8545): Promise<number> {
|
|
|
49
51
|
export async function startAnvil(config: AnvilConfig): Promise<AnvilInstance> {
|
|
50
52
|
const installed = await isAnvilInstalled()
|
|
51
53
|
if (!installed) {
|
|
52
|
-
throw new Error(
|
|
53
|
-
'Anvil is not installed. Please install Foundry: https://book.getfoundry.sh/getting-started/installation'
|
|
54
|
-
)
|
|
54
|
+
throw new Error('Anvil is not installed. Please install Foundry: https://book.getfoundry.sh/getting-started/installation')
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const port = config.port ?? (await findAvailablePort())
|
|
@@ -74,15 +74,15 @@ export async function startAnvil(config: AnvilConfig): Promise<AnvilInstance> {
|
|
|
74
74
|
})
|
|
75
75
|
|
|
76
76
|
let stderr = ''
|
|
77
|
-
proc.stderr?.on('data', data => {
|
|
77
|
+
proc.stderr?.on('data', (data) => {
|
|
78
78
|
stderr += data.toString()
|
|
79
79
|
})
|
|
80
80
|
|
|
81
|
-
proc.on('error', err => {
|
|
81
|
+
proc.on('error', (err) => {
|
|
82
82
|
reject(new Error(`Failed to start Anvil: ${err.message}`))
|
|
83
83
|
})
|
|
84
84
|
|
|
85
|
-
proc.on('close', code => {
|
|
85
|
+
proc.on('close', (code) => {
|
|
86
86
|
if (code !== 0 && code !== null) {
|
|
87
87
|
reject(new Error(`Anvil exited with code ${code}: ${stderr}`))
|
|
88
88
|
}
|
|
@@ -107,7 +107,7 @@ export async function startAnvil(config: AnvilConfig): Promise<AnvilInstance> {
|
|
|
107
107
|
})
|
|
108
108
|
if (response.ok) {
|
|
109
109
|
// Add small delay to ensure all RPC methods are ready (including debug_*)
|
|
110
|
-
await new Promise(r => setTimeout(r, 100))
|
|
110
|
+
await new Promise((r) => setTimeout(r, 100))
|
|
111
111
|
const client = createPublicClient({
|
|
112
112
|
chain: mainnet, // Chain is overridden by fork anyway
|
|
113
113
|
transport: http(rpcUrl)
|
|
@@ -142,10 +142,7 @@ export function stopAnvil(instance: AnvilInstance): void {
|
|
|
142
142
|
}
|
|
143
143
|
|
|
144
144
|
// Helper to run a function with a temporary Anvil instance
|
|
145
|
-
export async function withAnvil<T>(
|
|
146
|
-
config: AnvilConfig,
|
|
147
|
-
fn: (instance: AnvilInstance) => Promise<T>
|
|
148
|
-
): Promise<T> {
|
|
145
|
+
export async function withAnvil<T>(config: AnvilConfig, fn: (instance: AnvilInstance) => Promise<T>): Promise<T> {
|
|
149
146
|
const instance = await startAnvil(config)
|
|
150
147
|
try {
|
|
151
148
|
return await fn(instance)
|
|
@@ -193,7 +190,7 @@ export async function traceTransactionWithAnvil(
|
|
|
193
190
|
forkUrl: archiveUrl,
|
|
194
191
|
forkBlockNumber: forkBlock
|
|
195
192
|
},
|
|
196
|
-
async instance => {
|
|
193
|
+
async (instance) => {
|
|
197
194
|
// Use debug_traceCall to simulate and trace the transaction
|
|
198
195
|
// Note: This replays the tx against the post-block state, which may differ slightly
|
|
199
196
|
// from the original execution if state changed within the block
|
|
@@ -243,7 +240,7 @@ export async function simulateCallWithTrace(
|
|
|
243
240
|
forkUrl,
|
|
244
241
|
forkBlockNumber: blockNumber
|
|
245
242
|
},
|
|
246
|
-
async instance => {
|
|
243
|
+
async (instance) => {
|
|
247
244
|
// First, try a regular call to get the result
|
|
248
245
|
let result = '0x'
|
|
249
246
|
let success = true
|
package/src/chain-meta.ts
CHANGED
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
* human labels for addresses. Kept apart from the preview and the clear-signing registry
|
|
4
4
|
* so both can use it without importing each other.
|
|
5
5
|
*/
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
import { whatsabi } from '@shazow/whatsabi'
|
|
8
|
+
import { type Abi, type Address, parseAbiItem } from 'viem'
|
|
8
9
|
import { getClientManager } from './client.js'
|
|
9
10
|
import type { ChainName } from './types.js'
|
|
10
11
|
|
|
@@ -26,7 +27,9 @@ export async function loadAbi(chain: ChainName, address: string) {
|
|
|
26
27
|
const client = clientManager.getClient(chain)
|
|
27
28
|
const etherscanApiKey = clientManager.getConfig().etherscanApiKey
|
|
28
29
|
|
|
29
|
-
const loaders: whatsabi.loaders.ABILoader[] = [
|
|
30
|
+
const loaders: whatsabi.loaders.ABILoader[] = [
|
|
31
|
+
new whatsabi.loaders.SourcifyABILoader({ chainId: clientManager.getChainId(chain) })
|
|
32
|
+
]
|
|
30
33
|
if (etherscanApiKey) {
|
|
31
34
|
loaders.push(new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: etherscanApiKey, chainId: clientManager.getChainId(chain) }))
|
|
32
35
|
}
|
|
@@ -83,7 +86,9 @@ export async function addressLabel(chain: ChainName, address: string): Promise<s
|
|
|
83
86
|
|
|
84
87
|
let label: string | undefined
|
|
85
88
|
try {
|
|
86
|
-
const code = await getClientManager()
|
|
89
|
+
const code = await getClientManager()
|
|
90
|
+
.getClient(chain)
|
|
91
|
+
.getBytecode({ address: address as Address })
|
|
87
92
|
if (code && code !== '0x') {
|
|
88
93
|
label = (await tokenMeta(chain, address).catch(() => ({ symbol: undefined }))).symbol
|
|
89
94
|
if (!label) label = (await loadAbi(chain, address)).name
|
|
@@ -96,7 +101,6 @@ export async function addressLabel(chain: ChainName, address: string): Promise<s
|
|
|
96
101
|
return label
|
|
97
102
|
}
|
|
98
103
|
|
|
99
|
-
|
|
100
104
|
/** uint256 max and anything near it means "infinite" in an approval. */
|
|
101
105
|
export const MAX_UINT256 = (1n << 256n) - 1n
|
|
102
106
|
export const UNLIMITED_THRESHOLD = MAX_UINT256 / 2n
|
package/src/chains.ts
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Chain } from 'viem'
|
|
2
|
+
import {
|
|
3
|
+
arbitrum,
|
|
4
|
+
avalanche,
|
|
5
|
+
base,
|
|
6
|
+
bsc,
|
|
7
|
+
gnosis,
|
|
8
|
+
linea,
|
|
9
|
+
localhost,
|
|
10
|
+
mainnet,
|
|
11
|
+
optimism,
|
|
12
|
+
polygon,
|
|
13
|
+
sonic,
|
|
14
|
+
unichain,
|
|
15
|
+
zksync
|
|
16
|
+
} from 'viem/chains'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Every chain this server knows, in one table. Adding one here is the whole change: the
|
|
20
|
+
* `ChainName` union, the tool schemas, RPC selection, explorer links, Hypersync routing and
|
|
21
|
+
* the `--help` listing are all derived from it.
|
|
22
|
+
*/
|
|
23
|
+
interface ChainInfo {
|
|
24
|
+
chain: Chain
|
|
25
|
+
/** Subdomain under g.alchemy.com; absent where Alchemy has no endpoint. */
|
|
26
|
+
alchemy?: string
|
|
27
|
+
/** Subdomain under infura.io; absent where Infura has no endpoint. */
|
|
28
|
+
infura?: string
|
|
29
|
+
/** Used when no provider key is configured. */
|
|
30
|
+
fallback: string
|
|
31
|
+
/** Block explorer domain, for links. Absent where there is nothing to link to. */
|
|
32
|
+
explorer?: string
|
|
33
|
+
hypersync?: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const TABLE = {
|
|
37
|
+
mainnet: {
|
|
38
|
+
chain: mainnet,
|
|
39
|
+
alchemy: 'eth-mainnet',
|
|
40
|
+
infura: 'mainnet',
|
|
41
|
+
fallback: 'https://ethereum-rpc.publicnode.com',
|
|
42
|
+
explorer: 'etherscan.io',
|
|
43
|
+
hypersync: 'https://eth.hypersync.xyz'
|
|
44
|
+
},
|
|
45
|
+
arbitrum: {
|
|
46
|
+
chain: arbitrum,
|
|
47
|
+
alchemy: 'arb-mainnet',
|
|
48
|
+
infura: 'arbitrum-mainnet',
|
|
49
|
+
fallback: 'https://arb1.arbitrum.io/rpc',
|
|
50
|
+
explorer: 'arbiscan.io',
|
|
51
|
+
hypersync: 'https://arbitrum.hypersync.xyz'
|
|
52
|
+
},
|
|
53
|
+
avalanche: {
|
|
54
|
+
chain: avalanche,
|
|
55
|
+
alchemy: 'avax-mainnet',
|
|
56
|
+
infura: 'avalanche-mainnet',
|
|
57
|
+
fallback: 'https://api.avax.network/ext/bc/C/rpc',
|
|
58
|
+
explorer: 'snowtrace.io',
|
|
59
|
+
hypersync: 'https://avalanche.hypersync.xyz'
|
|
60
|
+
},
|
|
61
|
+
base: {
|
|
62
|
+
chain: base,
|
|
63
|
+
alchemy: 'base-mainnet',
|
|
64
|
+
// publicnode refuses receipts and anything else it calls an archive request
|
|
65
|
+
fallback: 'https://mainnet.base.org',
|
|
66
|
+
explorer: 'basescan.org',
|
|
67
|
+
hypersync: 'https://base.hypersync.xyz'
|
|
68
|
+
},
|
|
69
|
+
bnb: {
|
|
70
|
+
chain: bsc,
|
|
71
|
+
alchemy: 'bnb-mainnet',
|
|
72
|
+
infura: 'bsc-mainnet',
|
|
73
|
+
fallback: 'https://bsc-dataseed.bnbchain.org',
|
|
74
|
+
explorer: 'bscscan.com',
|
|
75
|
+
hypersync: 'https://bsc.hypersync.xyz'
|
|
76
|
+
},
|
|
77
|
+
gnosis: {
|
|
78
|
+
chain: gnosis,
|
|
79
|
+
alchemy: 'gnosis-mainnet',
|
|
80
|
+
fallback: 'https://rpc.gnosischain.com',
|
|
81
|
+
explorer: 'gnosisscan.io',
|
|
82
|
+
hypersync: 'https://gnosis.hypersync.xyz'
|
|
83
|
+
},
|
|
84
|
+
sonic: {
|
|
85
|
+
chain: sonic,
|
|
86
|
+
alchemy: 'sonic-mainnet',
|
|
87
|
+
fallback: 'https://rpc.soniclabs.com',
|
|
88
|
+
explorer: 'sonicscan.org',
|
|
89
|
+
hypersync: 'https://sonic.hypersync.xyz'
|
|
90
|
+
},
|
|
91
|
+
optimism: {
|
|
92
|
+
chain: optimism,
|
|
93
|
+
alchemy: 'opt-mainnet',
|
|
94
|
+
infura: 'optimism-mainnet',
|
|
95
|
+
fallback: 'https://mainnet.optimism.io',
|
|
96
|
+
explorer: 'optimistic.etherscan.io',
|
|
97
|
+
hypersync: 'https://optimism.hypersync.xyz'
|
|
98
|
+
},
|
|
99
|
+
polygon: {
|
|
100
|
+
chain: polygon,
|
|
101
|
+
alchemy: 'polygon-mainnet',
|
|
102
|
+
infura: 'polygon-mainnet',
|
|
103
|
+
// polygon-rpc.com now answers 401 "API key disabled" to anonymous callers
|
|
104
|
+
fallback: 'https://polygon-bor-rpc.publicnode.com',
|
|
105
|
+
explorer: 'polygonscan.com',
|
|
106
|
+
hypersync: 'https://polygon.hypersync.xyz'
|
|
107
|
+
},
|
|
108
|
+
zksync: {
|
|
109
|
+
chain: zksync,
|
|
110
|
+
alchemy: 'zksync-mainnet',
|
|
111
|
+
infura: 'zksync-mainnet',
|
|
112
|
+
fallback: 'https://mainnet.era.zksync.io',
|
|
113
|
+
explorer: 'era.zksync.network',
|
|
114
|
+
hypersync: 'https://zksync.hypersync.xyz'
|
|
115
|
+
},
|
|
116
|
+
linea: {
|
|
117
|
+
chain: linea,
|
|
118
|
+
alchemy: 'linea-mainnet',
|
|
119
|
+
infura: 'linea-mainnet',
|
|
120
|
+
fallback: 'https://rpc.linea.build',
|
|
121
|
+
explorer: 'lineascan.build',
|
|
122
|
+
hypersync: 'https://linea.hypersync.xyz'
|
|
123
|
+
},
|
|
124
|
+
unichain: {
|
|
125
|
+
chain: unichain,
|
|
126
|
+
alchemy: 'unichain-mainnet',
|
|
127
|
+
infura: 'unichain-mainnet',
|
|
128
|
+
fallback: 'https://mainnet.unichain.org',
|
|
129
|
+
explorer: 'uniscan.xyz',
|
|
130
|
+
hypersync: 'https://unichain.hypersync.xyz'
|
|
131
|
+
},
|
|
132
|
+
localhost: {
|
|
133
|
+
chain: localhost,
|
|
134
|
+
fallback: 'http://localhost:8545'
|
|
135
|
+
}
|
|
136
|
+
} as const satisfies Record<string, ChainInfo>
|
|
137
|
+
|
|
138
|
+
export type ChainName = keyof typeof TABLE
|
|
139
|
+
|
|
140
|
+
// Widened: `as const` above is only there to infer ChainName from the keys, and reading a
|
|
141
|
+
// literal entry would otherwise lose the fields that not every chain sets.
|
|
142
|
+
export const CHAINS: Record<ChainName, ChainInfo> = TABLE
|
|
143
|
+
|
|
144
|
+
/** Tuple rather than array, because `z.enum` needs a non-empty literal list. */
|
|
145
|
+
export const SUPPORTED_CHAINS = Object.keys(CHAINS) as [ChainName, ...ChainName[]]
|