web3-tools-mcp 1.3.4 → 1.5.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 +63 -5
- package/dist/anvil.d.ts +34 -0
- package/dist/anvil.d.ts.map +1 -0
- package/dist/anvil.js +254 -0
- package/dist/anvil.js.map +1 -0
- package/dist/chain-meta.d.ts +27 -0
- package/dist/chain-meta.d.ts.map +1 -0
- package/dist/chain-meta.js +92 -0
- package/dist/chain-meta.js.map +1 -0
- package/dist/clear-signing.d.ts +62 -0
- package/dist/clear-signing.d.ts.map +1 -0
- package/dist/clear-signing.js +139 -0
- package/dist/clear-signing.js.map +1 -0
- package/dist/client.d.ts +3 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +11 -0
- package/dist/client.js.map +1 -1
- package/dist/erc7730-index.json.gz +0 -0
- package/dist/index.js +25 -5
- package/dist/index.js.map +1 -1
- package/dist/preview.d.ts +58 -0
- package/dist/preview.d.ts.map +1 -0
- package/dist/preview.js +182 -0
- package/dist/preview.js.map +1 -0
- package/dist/tools/advanced.d.ts +67 -9
- package/dist/tools/advanced.d.ts.map +1 -1
- package/dist/tools/advanced.js +206 -67
- package/dist/tools/advanced.js.map +1 -1
- package/dist/tools/balance.d.ts +5 -5
- package/dist/tools/contract-info.d.ts +9 -9
- package/dist/tools/contract.d.ts +8 -8
- package/dist/tools/ens.d.ts +15 -15
- package/dist/tools/gas.d.ts +18 -18
- package/dist/tools/logs.d.ts +3 -3
- package/dist/tools/transactions.d.ts +11 -11
- package/dist/tools/transactions.d.ts.map +1 -1
- package/dist/tools/transactions.js +92 -113
- package/dist/tools/transactions.js.map +1 -1
- package/dist/utils.d.ts +13 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +76 -0
- package/dist/utils.js.map +1 -1
- package/dist/wallet-client.d.ts +83 -0
- package/dist/wallet-client.d.ts.map +1 -0
- package/dist/wallet-client.js +357 -0
- package/dist/wallet-client.js.map +1 -0
- package/package.json +11 -9
- package/src/anvil.ts +323 -0
- package/src/chain-meta.ts +102 -0
- package/src/clear-signing.ts +206 -0
- package/src/client.ts +14 -0
- package/src/erc7730-index.json.gz +0 -0
- package/src/index.ts +26 -5
- package/src/preview.ts +265 -0
- package/src/tools/advanced.ts +239 -70
- package/src/tools/transactions.ts +98 -125
- package/src/utils.ts +95 -0
- package/src/wallet-client.ts +380 -0
- package/dist/wallet-server.d.ts +0 -35
- package/dist/wallet-server.d.ts.map +0 -1
- package/dist/wallet-server.js +0 -232
- package/dist/wallet-server.js.map +0 -1
- package/public/wallet-app.js +0 -677
- package/public/wallet.html +0 -723
- package/src/wallet-server.ts +0 -283
package/README.md
CHANGED
|
@@ -6,6 +6,7 @@ A Model Context Protocol (MCP) server for blockchain interactions using [viem](h
|
|
|
6
6
|
|
|
7
7
|
- Multi-chain support (Ethereum, Arbitrum, Avalanche, Base, BNB Chain, Gnosis, Sonic, Optimism, Polygon, zkSync Era, Linea, Unichain)
|
|
8
8
|
- **🔐 Transaction Signing via Browser Wallet** (MetaMask, Rabby, Coinbase Wallet)
|
|
9
|
+
- Clear signing: transactions are decoded, named and simulated before you approve them
|
|
9
10
|
- Smart contract interactions (read & write functions, ABI retrieval, source code)
|
|
10
11
|
- Contract simulation & gas estimation (simulate transactions, estimate costs)
|
|
11
12
|
- Real-time gas price tracking (legacy & EIP-1559)
|
|
@@ -46,6 +47,10 @@ All API keys are optional. The server uses public RPCs by default. Add keys to u
|
|
|
46
47
|
- `--infura-api-key` or `INFURA_API_KEY` - Additional RPC provider
|
|
47
48
|
- `--custom-rpc` - Custom RPC URLs as JSON
|
|
48
49
|
|
|
50
|
+
**Wallet relay (all optional):**
|
|
51
|
+
- `WALLET_SERVER_URL` + `WALLET_TOKEN` - Use a hosted relay instead of a local one
|
|
52
|
+
- `PORT`, `HOST`, `WALLET_PUBLIC_URL` - Read by the relay itself when you host it
|
|
53
|
+
|
|
49
54
|
**Get free API keys:**
|
|
50
55
|
- Etherscan: [etherscan.io/apis](https://etherscan.io/apis)
|
|
51
56
|
- Hypersync: [hypersync.xyz](https://hypersync.xyz)
|
|
@@ -79,7 +84,7 @@ npx web3-tools-mcp
|
|
|
79
84
|
| zkSync Era | 324 | ✅ |
|
|
80
85
|
| Linea | 59144 | ✅ |
|
|
81
86
|
| Unichain | 130 | ✅ |
|
|
82
|
-
| Localhost |
|
|
87
|
+
| Localhost | 1337 | ❌ |
|
|
83
88
|
|
|
84
89
|
## 🔐 Browser Wallet Integration
|
|
85
90
|
|
|
@@ -88,12 +93,63 @@ The server includes a built-in wallet interface for secure transaction signing w
|
|
|
88
93
|
### How It Works
|
|
89
94
|
|
|
90
95
|
1. When you use a transaction tool (e.g., `send_native_token`), the server automatically:
|
|
91
|
-
- Starts a local
|
|
96
|
+
- Starts a local wallet relay on `http://127.0.0.1:3456` (the next free port up to 3460)
|
|
92
97
|
- Opens your browser to connect your wallet (MetaMask, Rabby, Coinbase Wallet, etc.)
|
|
93
98
|
2. You connect your wallet once in the browser
|
|
94
|
-
3.
|
|
99
|
+
3. The transaction is decoded and simulated, then the request appears in the browser for approval
|
|
95
100
|
4. Sign or reject transactions directly in your wallet
|
|
96
101
|
|
|
102
|
+
The browser page and the MCP server are both clients of the relay, authenticated with a
|
|
103
|
+
pairing token that travels in the URL fragment (`#t=…`) of the link the server prints.
|
|
104
|
+
|
|
105
|
+
**One relay per machine.** Every local MCP process looks for an existing relay on ports
|
|
106
|
+
3456-3460 before starting one, so all your editor sessions share a single wallet page —
|
|
107
|
+
connect once, and a transaction from any session lands in that tab. They authenticate with
|
|
108
|
+
a token kept in `~/.config/web3-tools-mcp/relay-token` (mode 0600); delete it to rotate.
|
|
109
|
+
|
|
110
|
+
### Before You Sign
|
|
111
|
+
|
|
112
|
+
Each request shows what the transaction actually does, not just calldata:
|
|
113
|
+
|
|
114
|
+
- **What it means** — for contracts in the [Ledger ERC-7730 registry](https://github.com/LedgerHQ/clear-signing-erc7730-registry)
|
|
115
|
+
(651 contracts, 3253 selectors), the protocol's own intent and field labels: *Supply ·
|
|
116
|
+
Aave · Amount to supply: 250 USDC*, rather than `supply(address,uint256,address,uint16)`.
|
|
117
|
+
The index ships gzipped (~60KB) and a weekly workflow opens a PR when it changes.
|
|
118
|
+
- **Decoded call** — otherwise the function name and named arguments, from the verified ABI
|
|
119
|
+
(Sourcify / Etherscan) or, for unverified contracts, from selectors recovered from bytecode
|
|
120
|
+
via [WhatsABI](https://github.com/shazow/whatsabi). Proxies resolve to their implementation.
|
|
121
|
+
- **Token amounts** — formatted with on-chain decimals and symbol; unlimited approvals are
|
|
122
|
+
flagged explicitly.
|
|
123
|
+
- **Simulation** — `eth_simulateV1` (falling back to `eth_call` + `estimateGas`) reports the
|
|
124
|
+
gas estimate and every ERC20 transfer the transaction would cause, marked in/out for your
|
|
125
|
+
account. A reverting transaction is shown as such before you can approve it.
|
|
126
|
+
|
|
127
|
+
### Hosting the Wallet Page
|
|
128
|
+
|
|
129
|
+
The relay lives in its own workspace, [`wallet/`](wallet), and depends only on express, cors
|
|
130
|
+
and ws (~4MB installed, against ~180MB for the MCP server). It deploys on its own to any host
|
|
131
|
+
that keeps a Node process alive and supports WebSockets — Render, Railway, Fly:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# On the host, from wallet/ — PORT is provided by the platform
|
|
135
|
+
WALLET_TOKEN=<long random string> WALLET_PUBLIC_URL=https://wallet.example.com npm start
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then point the MCP server at it:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
export WALLET_SERVER_URL=https://wallet.example.com
|
|
142
|
+
export WALLET_TOKEN=<the same token>
|
|
143
|
+
npx web3-tools-mcp
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
`render.yaml` deploys the relay as-is (`rootDir: wallet`, so only its dependencies are
|
|
147
|
+
installed). Anyone holding `WALLET_TOKEN` can send your browser transactions to sign, so
|
|
148
|
+
treat it like a password and always serve the page over HTTPS.
|
|
149
|
+
|
|
150
|
+
Locally nothing changes: the MCP server embeds the same relay and `npm run start:wallet`
|
|
151
|
+
runs it from the workspace.
|
|
152
|
+
|
|
97
153
|
### Supported Wallets
|
|
98
154
|
|
|
99
155
|
- MetaMask
|
|
@@ -103,12 +159,12 @@ The server includes a built-in wallet interface for secure transaction signing w
|
|
|
103
159
|
|
|
104
160
|
### Manual Access
|
|
105
161
|
|
|
106
|
-
Visit `
|
|
162
|
+
Visit the wallet URL printed by the server (`wallet_status` also returns it) anytime to:
|
|
107
163
|
- Check wallet connection status
|
|
108
164
|
- See pending transactions
|
|
109
165
|
- View transaction history
|
|
110
166
|
|
|
111
|
-
**Note:** The
|
|
167
|
+
**Note:** The relay starts automatically with the MCP server. No additional setup required.
|
|
112
168
|
|
|
113
169
|
## Available Tools
|
|
114
170
|
|
|
@@ -116,6 +172,7 @@ Visit `http://localhost:3456` anytime to:
|
|
|
116
172
|
- `get_function_signature` - Generate 4-byte function selectors
|
|
117
173
|
- `get_event_signature` - Generate 32-byte event topic0 hashes
|
|
118
174
|
- `get_error_signature` - Generate 4-byte error selectors
|
|
175
|
+
- `encode_function_data` - Encode a call's calldata from its ABI and arguments
|
|
119
176
|
|
|
120
177
|
### Contract Info
|
|
121
178
|
- `get_contract_abi` - Get ABI with proxy detection and verification status
|
|
@@ -155,6 +212,7 @@ Visit `http://localhost:3456` anytime to:
|
|
|
155
212
|
- `get_storage_at` - Read storage slots with type decoding
|
|
156
213
|
- `get_block_info` - Get block data (timestamp, hash, etc.)
|
|
157
214
|
- `trace_transaction` - Trace execution (call tree, VM, state diff)
|
|
215
|
+
- `debug_call` - Trace a call without broadcasting it, falling back to a local anvil fork
|
|
158
216
|
|
|
159
217
|
## Advanced Configuration
|
|
160
218
|
|
package/dist/anvil.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ChildProcess } from 'child_process';
|
|
2
|
+
import { createPublicClient } from 'viem';
|
|
3
|
+
export interface AnvilInstance {
|
|
4
|
+
process: ChildProcess;
|
|
5
|
+
port: number;
|
|
6
|
+
rpcUrl: string;
|
|
7
|
+
client: ReturnType<typeof createPublicClient>;
|
|
8
|
+
}
|
|
9
|
+
export interface AnvilConfig {
|
|
10
|
+
forkUrl: string;
|
|
11
|
+
forkBlockNumber?: bigint;
|
|
12
|
+
port?: number;
|
|
13
|
+
chainId?: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function isAnvilInstalled(): Promise<boolean>;
|
|
16
|
+
export declare function startAnvil(config: AnvilConfig): Promise<AnvilInstance>;
|
|
17
|
+
export declare function stopAnvil(instance: AnvilInstance): void;
|
|
18
|
+
export declare function withAnvil<T>(config: AnvilConfig, fn: (instance: AnvilInstance) => Promise<T>): Promise<T>;
|
|
19
|
+
export declare function traceTransactionWithAnvil(forkUrl: string, transactionHash: string, blockNumber: bigint, tracer?: 'callTracer' | 'prestateTracer' | 'stateDiffTracer', chainName?: string): Promise<unknown>;
|
|
20
|
+
export interface SimulateCallParams {
|
|
21
|
+
to: string;
|
|
22
|
+
data?: string;
|
|
23
|
+
from?: string;
|
|
24
|
+
value?: bigint;
|
|
25
|
+
gas?: bigint;
|
|
26
|
+
}
|
|
27
|
+
export declare function simulateCallWithTrace(forkUrl: string, params: SimulateCallParams, blockNumber?: bigint, tracer?: 'callTracer' | 'prestateTracer'): Promise<{
|
|
28
|
+
result: string;
|
|
29
|
+
trace: unknown;
|
|
30
|
+
gasUsed: bigint;
|
|
31
|
+
success: boolean;
|
|
32
|
+
revertReason?: string;
|
|
33
|
+
}>;
|
|
34
|
+
//# sourceMappingURL=anvil.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anvil.d.ts","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,YAAY,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,kBAAkB,EAAQ,MAAM,MAAM,CAAA;AAG/C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,YAAY,CAAA;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAA;CAC9C;AAED,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAA;IACf,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB;AAGD,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,OAAO,CAAC,CAMzD;AAuBD,wBAAsB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC,CAsF5E;AAGD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,IAAI,CAIvD;AAGD,wBAAsB,SAAS,CAAC,CAAC,EAC/B,MAAM,EAAE,WAAW,EACnB,EAAE,EAAE,CAAC,QAAQ,EAAE,aAAa,KAAK,OAAO,CAAC,CAAC,CAAC,GAC1C,OAAO,CAAC,CAAC,CAAC,CAOZ;AAQD,wBAAsB,yBAAyB,CAC7C,OAAO,EAAE,MAAM,EACf,eAAe,EAAE,MAAM,EACvB,WAAW,EAAE,MAAM,EACnB,MAAM,GAAE,YAAY,GAAG,gBAAgB,GAAG,iBAAgC,EAC1E,SAAS,CAAC,EAAE,MAAM,GACjB,OAAO,CAAC,OAAO,CAAC,CAiDlB;AAGD,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,kBAAkB,EAC1B,WAAW,CAAC,EAAE,MAAM,EACpB,MAAM,GAAE,YAAY,GAAG,gBAA+B,GACrD,OAAO,CAAC;IACT,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,OAAO,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,OAAO,CAAA;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAC,CAmFD"}
|
package/dist/anvil.js
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { createPublicClient, http } from 'viem';
|
|
3
|
+
import { mainnet } from 'viem/chains';
|
|
4
|
+
// Check if anvil is installed
|
|
5
|
+
export async function isAnvilInstalled() {
|
|
6
|
+
return new Promise(resolve => {
|
|
7
|
+
const proc = spawn('anvil', ['--version'], { shell: true });
|
|
8
|
+
proc.on('close', code => resolve(code === 0));
|
|
9
|
+
proc.on('error', () => resolve(false));
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
// Find an available port
|
|
13
|
+
async function findAvailablePort(startPort = 8545) {
|
|
14
|
+
const net = await import('net');
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const server = net.createServer();
|
|
17
|
+
server.listen(startPort, () => {
|
|
18
|
+
const address = server.address();
|
|
19
|
+
if (address && typeof address === 'object') {
|
|
20
|
+
server.close(() => resolve(address.port));
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
server.close(() => reject(new Error('Could not get port')));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
server.on('error', () => {
|
|
27
|
+
// Port in use, try next one
|
|
28
|
+
findAvailablePort(startPort + 1).then(resolve).catch(reject);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Start an Anvil instance
|
|
33
|
+
export async function startAnvil(config) {
|
|
34
|
+
const installed = await isAnvilInstalled();
|
|
35
|
+
if (!installed) {
|
|
36
|
+
throw new Error('Anvil is not installed. Please install Foundry: https://book.getfoundry.sh/getting-started/installation');
|
|
37
|
+
}
|
|
38
|
+
const port = config.port ?? (await findAvailablePort());
|
|
39
|
+
const rpcUrl = `http://127.0.0.1:${port}`;
|
|
40
|
+
const args = ['--fork-url', config.forkUrl, '--port', port.toString(), '--silent'];
|
|
41
|
+
if (config.forkBlockNumber !== undefined) {
|
|
42
|
+
args.push('--fork-block-number', config.forkBlockNumber.toString());
|
|
43
|
+
}
|
|
44
|
+
if (config.chainId !== undefined) {
|
|
45
|
+
args.push('--chain-id', config.chainId.toString());
|
|
46
|
+
}
|
|
47
|
+
return new Promise((resolve, reject) => {
|
|
48
|
+
const proc = spawn('anvil', args, {
|
|
49
|
+
shell: true,
|
|
50
|
+
stdio: ['ignore', 'pipe', 'pipe']
|
|
51
|
+
});
|
|
52
|
+
let stderr = '';
|
|
53
|
+
proc.stderr?.on('data', data => {
|
|
54
|
+
stderr += data.toString();
|
|
55
|
+
});
|
|
56
|
+
proc.on('error', err => {
|
|
57
|
+
reject(new Error(`Failed to start Anvil: ${err.message}`));
|
|
58
|
+
});
|
|
59
|
+
proc.on('close', code => {
|
|
60
|
+
if (code !== 0 && code !== null) {
|
|
61
|
+
reject(new Error(`Anvil exited with code ${code}: ${stderr}`));
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
// Wait for Anvil to be ready by polling the RPC
|
|
65
|
+
const maxAttempts = 50; // 5 seconds
|
|
66
|
+
let attempts = 0;
|
|
67
|
+
const checkReady = async () => {
|
|
68
|
+
attempts++;
|
|
69
|
+
try {
|
|
70
|
+
const response = await fetch(rpcUrl, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
jsonrpc: '2.0',
|
|
75
|
+
method: 'eth_blockNumber',
|
|
76
|
+
params: [],
|
|
77
|
+
id: 1
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
if (response.ok) {
|
|
81
|
+
// Add small delay to ensure all RPC methods are ready (including debug_*)
|
|
82
|
+
await new Promise(r => setTimeout(r, 100));
|
|
83
|
+
const client = createPublicClient({
|
|
84
|
+
chain: mainnet, // Chain is overridden by fork anyway
|
|
85
|
+
transport: http(rpcUrl)
|
|
86
|
+
});
|
|
87
|
+
resolve({ process: proc, port, rpcUrl, client });
|
|
88
|
+
}
|
|
89
|
+
else if (attempts < maxAttempts) {
|
|
90
|
+
setTimeout(checkReady, 100);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
proc.kill();
|
|
94
|
+
reject(new Error('Anvil failed to start within timeout'));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
if (attempts < maxAttempts) {
|
|
99
|
+
setTimeout(checkReady, 100);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
proc.kill();
|
|
103
|
+
reject(new Error('Anvil failed to start within timeout'));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
// Start checking after a small delay
|
|
108
|
+
setTimeout(checkReady, 200);
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
// Stop an Anvil instance
|
|
112
|
+
export function stopAnvil(instance) {
|
|
113
|
+
if (instance.process && !instance.process.killed) {
|
|
114
|
+
instance.process.kill('SIGTERM');
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Helper to run a function with a temporary Anvil instance
|
|
118
|
+
export async function withAnvil(config, fn) {
|
|
119
|
+
const instance = await startAnvil(config);
|
|
120
|
+
try {
|
|
121
|
+
return await fn(instance);
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
stopAnvil(instance);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Archive RPC URLs for chains that need historical state access
|
|
128
|
+
const ARCHIVE_RPCS = {
|
|
129
|
+
arbitrum: 'https://arbitrum-one.arpc.x.superfluid.dev'
|
|
130
|
+
};
|
|
131
|
+
// Trace a transaction using Anvil fork
|
|
132
|
+
export async function traceTransactionWithAnvil(forkUrl, transactionHash, blockNumber, tracer = 'callTracer', chainName) {
|
|
133
|
+
// Use archive RPC if available for better historical state access
|
|
134
|
+
const archiveUrl = chainName && ARCHIVE_RPCS[chainName] ? ARCHIVE_RPCS[chainName] : forkUrl;
|
|
135
|
+
// First, fetch the original transaction from the RPC
|
|
136
|
+
const fetchClient = createPublicClient({
|
|
137
|
+
chain: mainnet,
|
|
138
|
+
transport: http(forkUrl)
|
|
139
|
+
});
|
|
140
|
+
const tx = await fetchClient.getTransaction({
|
|
141
|
+
hash: transactionHash
|
|
142
|
+
});
|
|
143
|
+
if (!tx) {
|
|
144
|
+
throw new Error(`Transaction ${transactionHash} not found`);
|
|
145
|
+
}
|
|
146
|
+
// Fork at the block BEFORE the transaction to replay it accurately
|
|
147
|
+
// Note: This requires an archival RPC that has historical state
|
|
148
|
+
const forkBlock = blockNumber > 0n ? blockNumber - 1n : 0n;
|
|
149
|
+
return withAnvil({
|
|
150
|
+
forkUrl: archiveUrl,
|
|
151
|
+
forkBlockNumber: forkBlock
|
|
152
|
+
}, async (instance) => {
|
|
153
|
+
// Use debug_traceCall to simulate and trace the transaction
|
|
154
|
+
// Note: This replays the tx against the post-block state, which may differ slightly
|
|
155
|
+
// from the original execution if state changed within the block
|
|
156
|
+
const trace = await instance.client.request({
|
|
157
|
+
method: 'debug_traceCall',
|
|
158
|
+
params: [
|
|
159
|
+
{
|
|
160
|
+
from: tx.from,
|
|
161
|
+
to: tx.to,
|
|
162
|
+
data: tx.input,
|
|
163
|
+
value: tx.value ? `0x${tx.value.toString(16)}` : '0x0',
|
|
164
|
+
gas: tx.gas ? `0x${tx.gas.toString(16)}` : undefined
|
|
165
|
+
},
|
|
166
|
+
'latest',
|
|
167
|
+
{ tracer }
|
|
168
|
+
]
|
|
169
|
+
});
|
|
170
|
+
return trace;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
export async function simulateCallWithTrace(forkUrl, params, blockNumber, tracer = 'callTracer') {
|
|
174
|
+
return withAnvil({
|
|
175
|
+
forkUrl,
|
|
176
|
+
forkBlockNumber: blockNumber
|
|
177
|
+
}, async (instance) => {
|
|
178
|
+
// First, try a regular call to get the result
|
|
179
|
+
let result = '0x';
|
|
180
|
+
let success = true;
|
|
181
|
+
let revertReason;
|
|
182
|
+
let gasUsed = 0n;
|
|
183
|
+
try {
|
|
184
|
+
// Use eth_call to get the result
|
|
185
|
+
result = (await instance.client.request({
|
|
186
|
+
method: 'eth_call',
|
|
187
|
+
params: [
|
|
188
|
+
{
|
|
189
|
+
to: params.to,
|
|
190
|
+
data: params.data,
|
|
191
|
+
from: params.from ?? '0x0000000000000000000000000000000000000000',
|
|
192
|
+
value: params.value ? `0x${params.value.toString(16)}` : undefined,
|
|
193
|
+
gas: params.gas ? `0x${params.gas.toString(16)}` : undefined
|
|
194
|
+
},
|
|
195
|
+
'latest'
|
|
196
|
+
]
|
|
197
|
+
}));
|
|
198
|
+
// Estimate gas
|
|
199
|
+
try {
|
|
200
|
+
gasUsed = await instance.client.estimateGas({
|
|
201
|
+
to: params.to,
|
|
202
|
+
data: params.data,
|
|
203
|
+
account: params.from,
|
|
204
|
+
value: params.value
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Ignore gas estimation errors
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
success = false;
|
|
213
|
+
const errorMessage = error.message;
|
|
214
|
+
// Try to extract revert reason
|
|
215
|
+
const revertMatch = errorMessage.match(/revert(?:ed)?[:\s]*(.+?)(?:\n|$)/i);
|
|
216
|
+
if (revertMatch) {
|
|
217
|
+
revertReason = revertMatch[1].trim();
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
revertReason = errorMessage;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Get the trace using debug_traceCall
|
|
224
|
+
let trace = null;
|
|
225
|
+
try {
|
|
226
|
+
trace = await instance.client.request({
|
|
227
|
+
method: 'debug_traceCall',
|
|
228
|
+
params: [
|
|
229
|
+
{
|
|
230
|
+
to: params.to,
|
|
231
|
+
data: params.data,
|
|
232
|
+
from: params.from ?? '0x0000000000000000000000000000000000000000',
|
|
233
|
+
value: params.value ? `0x${params.value.toString(16)}` : undefined,
|
|
234
|
+
gas: params.gas ? `0x${params.gas.toString(16)}` : undefined
|
|
235
|
+
},
|
|
236
|
+
'latest',
|
|
237
|
+
{ tracer }
|
|
238
|
+
]
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (traceError) {
|
|
242
|
+
// If tracing fails, include the error
|
|
243
|
+
trace = { error: traceError.message };
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
result,
|
|
247
|
+
trace,
|
|
248
|
+
gasUsed,
|
|
249
|
+
success,
|
|
250
|
+
revertReason
|
|
251
|
+
};
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
//# sourceMappingURL=anvil.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"anvil.js","sourceRoot":"","sources":["../src/anvil.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-chain metadata every layer of the preview needs: contract ABIs, token symbols and
|
|
3
|
+
* human labels for addresses. Kept apart from the preview and the clear-signing registry
|
|
4
|
+
* so both can use it without importing each other.
|
|
5
|
+
*/
|
|
6
|
+
import { type Abi } from 'viem';
|
|
7
|
+
import type { ChainName } from './types.js';
|
|
8
|
+
export declare function loadAbi(chain: ChainName, address: string): Promise<{
|
|
9
|
+
abi: Abi;
|
|
10
|
+
source: "verified" | "guessed";
|
|
11
|
+
proxy?: string;
|
|
12
|
+
name?: string;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function tokenMeta(chain: ChainName, token: string): Promise<{
|
|
15
|
+
symbol?: string;
|
|
16
|
+
decimals?: number;
|
|
17
|
+
}>;
|
|
18
|
+
/**
|
|
19
|
+
* Human label for an address: token symbol first (more recognisable than the contract
|
|
20
|
+
* name — "USDC" beats "FiatTokenProxy"), then the verified contract name. EOAs get none,
|
|
21
|
+
* and the bytecode check keeps us from asking explorers about plain wallets.
|
|
22
|
+
*/
|
|
23
|
+
export declare function addressLabel(chain: ChainName, address: string): Promise<string | undefined>;
|
|
24
|
+
/** uint256 max and anything near it means "infinite" in an approval. */
|
|
25
|
+
export declare const MAX_UINT256: bigint;
|
|
26
|
+
export declare const UNLIMITED_THRESHOLD: bigint;
|
|
27
|
+
//# sourceMappingURL=chain-meta.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-meta.d.ts","sourceRoot":"","sources":["../src/chain-meta.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,KAAK,GAAG,EAA8B,MAAM,MAAM,CAAA;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAW3C,wBAAsB,OAAO,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM;SAJvB,GAAG;YAAU,UAAU,GAAG,SAAS;YAAU,MAAM;WAAS,MAAM;GAmCzG;AAED,wBAAsB,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM;aAnClB,MAAM;eAAa,MAAM;GAuDrE;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAiBjG;AAGD,wEAAwE;AACxE,eAAO,MAAM,WAAW,QAAoB,CAAA;AAC5C,eAAO,MAAM,mBAAmB,QAAmB,CAAA"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-chain metadata every layer of the preview needs: contract ABIs, token symbols and
|
|
3
|
+
* human labels for addresses. Kept apart from the preview and the clear-signing registry
|
|
4
|
+
* so both can use it without importing each other.
|
|
5
|
+
*/
|
|
6
|
+
import { parseAbiItem } from 'viem';
|
|
7
|
+
import { whatsabi } from '@shazow/whatsabi';
|
|
8
|
+
import { getClientManager } from './client.js';
|
|
9
|
+
const ERC20_META_ABI = [
|
|
10
|
+
parseAbiItem('function decimals() view returns (uint8)'),
|
|
11
|
+
parseAbiItem('function symbol() view returns (string)')
|
|
12
|
+
];
|
|
13
|
+
const abiCache = new Map();
|
|
14
|
+
const labelCache = new Map();
|
|
15
|
+
const metaCache = new Map();
|
|
16
|
+
export async function loadAbi(chain, address) {
|
|
17
|
+
const key = `${chain}:${address.toLowerCase()}`;
|
|
18
|
+
const cached = abiCache.get(key);
|
|
19
|
+
if (cached)
|
|
20
|
+
return cached;
|
|
21
|
+
const clientManager = getClientManager();
|
|
22
|
+
const client = clientManager.getClient(chain);
|
|
23
|
+
const etherscanApiKey = clientManager.getConfig().etherscanApiKey;
|
|
24
|
+
const loaders = [new whatsabi.loaders.SourcifyABILoader({ chainId: clientManager.getChainId(chain) })];
|
|
25
|
+
if (etherscanApiKey) {
|
|
26
|
+
loaders.push(new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: etherscanApiKey, chainId: clientManager.getChainId(chain) }));
|
|
27
|
+
}
|
|
28
|
+
const result = await whatsabi.autoload(address, {
|
|
29
|
+
provider: client,
|
|
30
|
+
abiLoader: new whatsabi.loaders.MultiABILoader(loaders),
|
|
31
|
+
signatureLookup: new whatsabi.loaders.OpenChainSignatureLookup(),
|
|
32
|
+
followProxies: true
|
|
33
|
+
});
|
|
34
|
+
// Bytecode-guessed ABIs carry no argument names; verified ones do.
|
|
35
|
+
const source = result.abi.some((item) => item.type === 'function' && item.inputs?.some((i) => i.name)) ? 'verified' : 'guessed';
|
|
36
|
+
const loaded = {
|
|
37
|
+
abi: result.abi,
|
|
38
|
+
source: source,
|
|
39
|
+
proxy: result.address !== address ? result.address : undefined,
|
|
40
|
+
name: result.contractResult?.name ?? undefined
|
|
41
|
+
};
|
|
42
|
+
abiCache.set(key, loaded);
|
|
43
|
+
return loaded;
|
|
44
|
+
}
|
|
45
|
+
export async function tokenMeta(chain, token) {
|
|
46
|
+
const key = `${chain}:${token.toLowerCase()}`;
|
|
47
|
+
const cached = metaCache.get(key);
|
|
48
|
+
if (cached)
|
|
49
|
+
return cached;
|
|
50
|
+
const client = getClientManager().getClient(chain);
|
|
51
|
+
const [decimals, symbol] = await client.multicall({
|
|
52
|
+
contracts: [
|
|
53
|
+
{ address: token, abi: ERC20_META_ABI, functionName: 'decimals' },
|
|
54
|
+
{ address: token, abi: ERC20_META_ABI, functionName: 'symbol' }
|
|
55
|
+
],
|
|
56
|
+
...(chain === 'localhost' && { deployless: true })
|
|
57
|
+
});
|
|
58
|
+
const meta = {
|
|
59
|
+
decimals: decimals.status === 'success' ? Number(decimals.result) : undefined,
|
|
60
|
+
symbol: symbol.status === 'success' ? symbol.result : undefined
|
|
61
|
+
};
|
|
62
|
+
metaCache.set(key, meta);
|
|
63
|
+
return meta;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Human label for an address: token symbol first (more recognisable than the contract
|
|
67
|
+
* name — "USDC" beats "FiatTokenProxy"), then the verified contract name. EOAs get none,
|
|
68
|
+
* and the bytecode check keeps us from asking explorers about plain wallets.
|
|
69
|
+
*/
|
|
70
|
+
export async function addressLabel(chain, address) {
|
|
71
|
+
const key = `${chain}:${address.toLowerCase()}`;
|
|
72
|
+
if (labelCache.has(key))
|
|
73
|
+
return labelCache.get(key);
|
|
74
|
+
let label;
|
|
75
|
+
try {
|
|
76
|
+
const code = await getClientManager().getClient(chain).getBytecode({ address: address });
|
|
77
|
+
if (code && code !== '0x') {
|
|
78
|
+
label = (await tokenMeta(chain, address).catch(() => ({ symbol: undefined }))).symbol;
|
|
79
|
+
if (!label)
|
|
80
|
+
label = (await loadAbi(chain, address)).name;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Unknown address — show it bare rather than failing the preview.
|
|
85
|
+
}
|
|
86
|
+
labelCache.set(key, label);
|
|
87
|
+
return label;
|
|
88
|
+
}
|
|
89
|
+
/** uint256 max and anything near it means "infinite" in an approval. */
|
|
90
|
+
export const MAX_UINT256 = (1n << 256n) - 1n;
|
|
91
|
+
export const UNLIMITED_THRESHOLD = MAX_UINT256 / 2n;
|
|
92
|
+
//# sourceMappingURL=chain-meta.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chain-meta.js","sourceRoot":"","sources":["../src/chain-meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { ChainName } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Clear signing from the Ledger ERC-7730 registry: for a known contract and selector it
|
|
4
|
+
* says what the transaction *means* ("Swap", "Approve spending") and labels each field the
|
|
5
|
+
* way the protocol intends, instead of showing raw ABI argument names.
|
|
6
|
+
*
|
|
7
|
+
* The index is built from the registry by scripts/build-erc7730-index.mjs.
|
|
8
|
+
*/
|
|
9
|
+
interface IndexField {
|
|
10
|
+
label?: string;
|
|
11
|
+
path?: string;
|
|
12
|
+
format?: string;
|
|
13
|
+
/** tokenAmount: `tokenPath` points at the field holding the token address. */
|
|
14
|
+
params?: {
|
|
15
|
+
tokenPath?: string;
|
|
16
|
+
tokenAddress?: string;
|
|
17
|
+
[k: string]: unknown;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
interface IndexSelector {
|
|
21
|
+
name: string;
|
|
22
|
+
intent: string;
|
|
23
|
+
/** Full signature, needed to ABI-decode the calldata these paths address. */
|
|
24
|
+
signature?: string;
|
|
25
|
+
fields: IndexField[];
|
|
26
|
+
}
|
|
27
|
+
interface IndexEntry {
|
|
28
|
+
protocol: string;
|
|
29
|
+
chainIds: number[];
|
|
30
|
+
selectors: Record<string, IndexSelector>;
|
|
31
|
+
}
|
|
32
|
+
export interface ClearSigningField {
|
|
33
|
+
label: string;
|
|
34
|
+
format: string;
|
|
35
|
+
value: string;
|
|
36
|
+
address?: string;
|
|
37
|
+
name?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface ClearSigningInfo {
|
|
40
|
+
protocol: string;
|
|
41
|
+
intent: string;
|
|
42
|
+
functionName: string;
|
|
43
|
+
fields: ClearSigningField[];
|
|
44
|
+
}
|
|
45
|
+
export declare function protocolLabel(slug: string): string;
|
|
46
|
+
interface TxContext {
|
|
47
|
+
to: string;
|
|
48
|
+
value?: string;
|
|
49
|
+
from?: string;
|
|
50
|
+
}
|
|
51
|
+
/** Registry entry for a contract, if it is listed for this chain. */
|
|
52
|
+
export declare function lookupContract(chain: ChainName, address: string): IndexEntry | undefined;
|
|
53
|
+
/**
|
|
54
|
+
* Clear-signing info for a transaction, or null when the contract/selector is unlisted.
|
|
55
|
+
* `decodedArgs` is supplied by the caller, which has already decoded the calldata against
|
|
56
|
+
* the contract's real ABI — the registry's own signature is only a fallback for naming.
|
|
57
|
+
*/
|
|
58
|
+
export declare function resolveClearSigning(chain: ChainName, tx: TxContext & {
|
|
59
|
+
data?: string;
|
|
60
|
+
}, decodedArgs: Record<string, unknown>): Promise<ClearSigningInfo | null>;
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=clear-signing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear-signing.d.ts","sourceRoot":"","sources":["../src/clear-signing.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C;;;;;;GAMG;AAEH,UAAU,UAAU;IAElB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,8EAA8E;IAC9E,MAAM,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAA;CAC7E;AAED,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAED,UAAU,UAAU;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,iBAAiB,EAAE,CAAA;CAC5B;AAkCD,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAiBD,UAAU,SAAS;IACjB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AA+DD,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAKxF;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,SAAS,EAChB,EAAE,EAAE,SAAS,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,EACjC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GACnC,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAiBlC"}
|