web3-tools-mcp 1.4.0 โ†’ 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 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 | 31337 | โŒ |
87
+ | Localhost | 1337 | โŒ |
83
88
 
84
89
  ## ๐Ÿ” Browser Wallet Integration
85
90
 
@@ -88,7 +93,7 @@ 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 wallet relay on `http://localhost:3456`
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
99
  3. The transaction is decoded and simulated, then the request appears in the browser for approval
@@ -106,9 +111,13 @@ a token kept in `~/.config/web3-tools-mcp/relay-token` (mode 0600); delete it to
106
111
 
107
112
  Each request shows what the transaction actually does, not just calldata:
108
113
 
109
- - **Decoded call** โ€” function name and named arguments, from the verified ABI (Sourcify /
110
- Etherscan) or, for unverified contracts, from selectors recovered from bytecode via
111
- [WhatsABI](https://github.com/shazow/whatsabi). Proxies are resolved to their implementation.
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.
112
121
  - **Token amounts** โ€” formatted with on-chain decimals and symbol; unlimited approvals are
113
122
  flagged explicitly.
114
123
  - **Simulation** โ€” `eth_simulateV1` (falling back to `eth_call` + `estimateGas`) reports the
@@ -163,6 +172,7 @@ Visit the wallet URL printed by the server (`wallet_status` also returns it) any
163
172
  - `get_function_signature` - Generate 4-byte function selectors
164
173
  - `get_event_signature` - Generate 32-byte event topic0 hashes
165
174
  - `get_error_signature` - Generate 4-byte error selectors
175
+ - `encode_function_data` - Encode a call's calldata from its ABI and arguments
166
176
 
167
177
  ### Contract Info
168
178
  - `get_contract_abi` - Get ABI with proxy detection and verification status
@@ -202,6 +212,7 @@ Visit the wallet URL printed by the server (`wallet_status` also returns it) any
202
212
  - `get_storage_at` - Read storage slots with type decoding
203
213
  - `get_block_info` - Get block data (timestamp, hash, etc.)
204
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
205
216
 
206
217
  ## Advanced Configuration
207
218
 
@@ -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"}
@@ -0,0 +1,139 @@
1
+ import { createRequire } from 'node:module';
2
+ import { gunzipSync } from 'node:zlib';
3
+ import { formatUnits, getAddress, isAddress } from 'viem';
4
+ import { addressLabel, tokenMeta, UNLIMITED_THRESHOLD } from './chain-meta.js';
5
+ import { getClientManager } from './client.js';
6
+ // Prettier names for registry directory slugs; unknown ones read fine as-is.
7
+ const PROTOCOL_LABELS = {
8
+ lifi: 'LI.FI',
9
+ '1inch': '1inch',
10
+ uniswap: 'Uniswap',
11
+ paraswap: 'ParaSwap',
12
+ cowswap: 'CoW Swap',
13
+ '0x': '0x Protocol',
14
+ aave: 'Aave',
15
+ morpho: 'Morpho',
16
+ safe: 'Safe',
17
+ lido: 'Lido',
18
+ eigenlayer: 'EigenLayer',
19
+ ens: 'ENS'
20
+ };
21
+ const require = createRequire(import.meta.url);
22
+ let index;
23
+ /** Loaded on first use: ~1.5MB of JSON, shipped gzipped at ~60KB. */
24
+ function registry() {
25
+ if (!index) {
26
+ try {
27
+ const path = require.resolve('./erc7730-index.json.gz');
28
+ index = JSON.parse(gunzipSync(require('node:fs').readFileSync(path)).toString());
29
+ }
30
+ catch {
31
+ index = {};
32
+ }
33
+ }
34
+ return index;
35
+ }
36
+ export function protocolLabel(slug) {
37
+ return PROTOCOL_LABELS[slug] ?? slug;
38
+ }
39
+ /** Resolve a registry path ("params.amountIn", "#.to", "[0].value") against decoded args. */
40
+ function getByPath(root, path) {
41
+ let current = root;
42
+ for (const part of path.replace(/^[#@]\./, '').split('.')) {
43
+ if (current == null)
44
+ return undefined;
45
+ const arrayIndex = part.match(/^\[(\d*)\]$/);
46
+ if (arrayIndex) {
47
+ current = Array.isArray(current) ? current[arrayIndex[1] === '' ? 0 : Number(arrayIndex[1])] : undefined;
48
+ }
49
+ else {
50
+ current = current[part];
51
+ }
52
+ }
53
+ return current;
54
+ }
55
+ function resolvePath(root, tx, path) {
56
+ if (path.startsWith('@.')) {
57
+ if (path.slice(2) === 'to')
58
+ return tx.to;
59
+ if (path.slice(2) === 'value')
60
+ return tx.value;
61
+ if (path.slice(2) === 'from')
62
+ return tx.from;
63
+ return undefined;
64
+ }
65
+ return getByPath(root, path);
66
+ }
67
+ async function formatField(chain, field, root, tx) {
68
+ // A missing format with a token reference still means an amount.
69
+ const format = field.format ?? (field.params?.tokenPath || field.params?.tokenAddress ? 'tokenAmount' : 'raw');
70
+ const label = field.label ?? field.path ?? format;
71
+ const raw = field.path ? resolvePath(root, tx, field.path) : undefined;
72
+ const out = { label, format, value: '' };
73
+ // Unresolved: show the descriptor's path rather than an empty row.
74
+ if (raw == null) {
75
+ out.value = field.path ?? '';
76
+ return out;
77
+ }
78
+ switch (format) {
79
+ case 'tokenAmount':
80
+ case 'amount': {
81
+ const amount = typeof raw === 'bigint' ? raw : BigInt(raw);
82
+ const token = field.params?.tokenAddress ?? (field.params?.tokenPath ? resolvePath(root, tx, field.params.tokenPath) : undefined);
83
+ const meta = typeof token === 'string' && isAddress(token)
84
+ ? await tokenMeta(chain, token).catch(() => ({ symbol: undefined, decimals: undefined }))
85
+ : { symbol: undefined, decimals: undefined };
86
+ const symbol = meta.symbol ? ` ${meta.symbol}` : '';
87
+ out.value = amount > UNLIMITED_THRESHOLD ? `Unlimited${symbol}` : `${formatUnits(amount, meta.decimals ?? 18)}${symbol}`;
88
+ return out;
89
+ }
90
+ case 'addressName':
91
+ case 'address':
92
+ case 'tokenName': {
93
+ if (!isAddress(raw)) {
94
+ out.value = String(raw);
95
+ return out;
96
+ }
97
+ const address = getAddress(raw);
98
+ const name = registry()[address.toLowerCase()]?.protocol;
99
+ out.address = address;
100
+ out.name = name ? protocolLabel(name) : await addressLabel(chain, address).catch(() => undefined);
101
+ out.value = out.name ?? address;
102
+ return out;
103
+ }
104
+ default:
105
+ out.value = typeof raw === 'bigint' ? raw.toString() : String(raw);
106
+ return out;
107
+ }
108
+ }
109
+ /** Registry entry for a contract, if it is listed for this chain. */
110
+ export function lookupContract(chain, address) {
111
+ const entry = registry()[address.toLowerCase()];
112
+ if (!entry)
113
+ return undefined;
114
+ const chainId = getClientManager().getChainId(chain);
115
+ return entry.chainIds.includes(chainId) ? entry : undefined;
116
+ }
117
+ /**
118
+ * Clear-signing info for a transaction, or null when the contract/selector is unlisted.
119
+ * `decodedArgs` is supplied by the caller, which has already decoded the calldata against
120
+ * the contract's real ABI โ€” the registry's own signature is only a fallback for naming.
121
+ */
122
+ export async function resolveClearSigning(chain, tx, decodedArgs) {
123
+ if (!tx.data || tx.data.length < 10)
124
+ return null;
125
+ const entry = lookupContract(chain, tx.to);
126
+ const format = entry?.selectors[tx.data.slice(0, 10).toLowerCase()];
127
+ if (!entry || !format)
128
+ return null;
129
+ // Fields with neither a path nor a label carry nothing a signer could read.
130
+ const usable = format.fields.filter((field) => field.path || field.label);
131
+ const fields = await Promise.all(usable.map((field) => formatField(chain, field, decodedArgs, tx)));
132
+ return {
133
+ protocol: protocolLabel(entry.protocol),
134
+ intent: format.intent,
135
+ functionName: format.name,
136
+ fields
137
+ };
138
+ }
139
+ //# sourceMappingURL=clear-signing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clear-signing.js","sourceRoot":"","sources":["../src/clear-signing.ts"],"names":[],"mappings":"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"}
Binary file
package/dist/preview.d.ts CHANGED
@@ -30,6 +30,9 @@ export interface TxPreview {
30
30
  functionName: string;
31
31
  signature: string;
32
32
  fields: PreviewField[];
33
+ /** From the ERC-7730 registry: what this call means, and whose contract it is. */
34
+ intent?: string;
35
+ protocol?: string;
33
36
  /** 'verified' = ABI from Sourcify/Etherscan, 'guessed' = selector lookup on bytecode */
34
37
  source: 'verified' | 'guessed';
35
38
  proxy?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE;QACR,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,YAAY,EAAE,CAAA;QACtB,wFAAwF;QACxF,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,OAAO,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,YAAY,EAAE,WAAW,EAAE,CAAA;KAC5B,CAAA;CACF;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAuOD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAmBnG"}
1
+ {"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAE3C,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,yEAAyE;IACzE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,EAAE,EAAE,MAAM,CAAA;IACV,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE;QACR,YAAY,EAAE,MAAM,CAAA;QACpB,SAAS,EAAE,MAAM,CAAA;QACjB,MAAM,EAAE,YAAY,EAAE,CAAA;QACtB,kFAAkF;QAClF,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,wFAAwF;QACxF,MAAM,EAAE,UAAU,GAAG,SAAS,CAAA;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;IACD,UAAU,CAAC,EAAE;QACX,OAAO,EAAE,OAAO,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,YAAY,EAAE,WAAW,EAAE,CAAA;KAC5B,CAAA;CACF;AAED,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAyKD;;;;GAIG;AACH,wBAAsB,cAAc,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAsBnG"}
package/dist/preview.js CHANGED
@@ -1,96 +1,15 @@
1
- import { decodeFunctionData, formatEther, formatUnits, parseAbiItem, toFunctionSelector } from 'viem';
1
+ import { decodeFunctionData, formatEther, formatUnits, toFunctionSelector } from 'viem';
2
2
  import { simulateBlocks } from 'viem/actions';
3
- import { whatsabi } from '@shazow/whatsabi';
4
3
  import { getClientManager } from './client.js';
5
- const ERC20_META_ABI = [
6
- parseAbiItem('function decimals() view returns (uint8)'),
7
- parseAbiItem('function symbol() view returns (string)')
8
- ];
4
+ import { addressLabel, loadAbi, tokenMeta, UNLIMITED_THRESHOLD } from './chain-meta.js';
5
+ import { lookupContract, protocolLabel, resolveClearSigning } from './clear-signing.js';
9
6
  const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
10
- const MAX_UINT256 = (1n << 256n) - 1n;
11
- const UNLIMITED_THRESHOLD = MAX_UINT256 / 2n;
12
7
  // Selectors whose amount argument is denominated in the token at tx.to.
13
8
  const ERC20_AMOUNT_ARGS = {
14
9
  [toFunctionSelector('function approve(address,uint256)')]: { arg: 'amount', approval: true },
15
10
  [toFunctionSelector('function transfer(address,uint256)')]: { arg: 'amount' },
16
11
  [toFunctionSelector('function transferFrom(address,address,uint256)')]: { arg: 'amount' }
17
12
  };
18
- const abiCache = new Map();
19
- const labelCache = new Map();
20
- const metaCache = new Map();
21
- async function loadAbi(chain, address) {
22
- const key = `${chain}:${address.toLowerCase()}`;
23
- const cached = abiCache.get(key);
24
- if (cached)
25
- return cached;
26
- const clientManager = getClientManager();
27
- const client = clientManager.getClient(chain);
28
- const etherscanApiKey = clientManager.getConfig().etherscanApiKey;
29
- const loaders = [new whatsabi.loaders.SourcifyABILoader({ chainId: clientManager.getChainId(chain) })];
30
- if (etherscanApiKey) {
31
- loaders.push(new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: etherscanApiKey, chainId: clientManager.getChainId(chain) }));
32
- }
33
- const result = await whatsabi.autoload(address, {
34
- provider: client,
35
- abiLoader: new whatsabi.loaders.MultiABILoader(loaders),
36
- signatureLookup: new whatsabi.loaders.OpenChainSignatureLookup(),
37
- followProxies: true
38
- });
39
- // Bytecode-guessed ABIs carry no argument names; verified ones do.
40
- const source = result.abi.some((item) => item.type === 'function' && item.inputs?.some((i) => i.name)) ? 'verified' : 'guessed';
41
- const loaded = {
42
- abi: result.abi,
43
- source: source,
44
- proxy: result.address !== address ? result.address : undefined,
45
- name: result.contractResult?.name ?? undefined
46
- };
47
- abiCache.set(key, loaded);
48
- return loaded;
49
- }
50
- async function tokenMeta(chain, token) {
51
- const key = `${chain}:${token.toLowerCase()}`;
52
- const cached = metaCache.get(key);
53
- if (cached)
54
- return cached;
55
- const client = getClientManager().getClient(chain);
56
- const [decimals, symbol] = await client.multicall({
57
- contracts: [
58
- { address: token, abi: ERC20_META_ABI, functionName: 'decimals' },
59
- { address: token, abi: ERC20_META_ABI, functionName: 'symbol' }
60
- ],
61
- ...(chain === 'localhost' && { deployless: true })
62
- });
63
- const meta = {
64
- decimals: decimals.status === 'success' ? Number(decimals.result) : undefined,
65
- symbol: symbol.status === 'success' ? symbol.result : undefined
66
- };
67
- metaCache.set(key, meta);
68
- return meta;
69
- }
70
- /**
71
- * Human label for an address: token symbol first (more recognisable than the contract
72
- * name โ€” "USDC" beats "FiatTokenProxy"), then the verified contract name. EOAs get none,
73
- * and the bytecode check keeps us from asking explorers about plain wallets.
74
- */
75
- async function addressLabel(chain, address) {
76
- const key = `${chain}:${address.toLowerCase()}`;
77
- if (labelCache.has(key))
78
- return labelCache.get(key);
79
- let label;
80
- try {
81
- const code = await getClientManager().getClient(chain).getBytecode({ address: address });
82
- if (code && code !== '0x') {
83
- label = (await tokenMeta(chain, address).catch(() => ({ symbol: undefined }))).symbol;
84
- if (!label)
85
- label = (await loadAbi(chain, address)).name;
86
- }
87
- }
88
- catch {
89
- // Unknown address โ€” show it bare rather than failing the preview.
90
- }
91
- labelCache.set(key, label);
92
- return label;
93
- }
94
13
  function stringify(value) {
95
14
  if (typeof value === 'bigint')
96
15
  return value.toString();
@@ -143,6 +62,33 @@ async function decodeCalldata(chain, tx) {
143
62
  const signature = abiItem
144
63
  ? `${functionName}(${inputs.map((i) => `${i.type}${i.name ? ` ${i.name}` : ''}`).join(', ')})`
145
64
  : functionName;
65
+ // Map positional args to their parameter names so registry field paths resolve.
66
+ const named = {};
67
+ inputs.forEach((input, i) => {
68
+ if (input.name)
69
+ named[input.name] = (args ?? [])[i];
70
+ });
71
+ // A registry descriptor says what the call means and how the protocol wants each field
72
+ // labelled, which beats raw ABI argument names. Fall back to those when it has none.
73
+ const clearSigning = await resolveClearSigning(chain, tx, named).catch(() => null);
74
+ if (clearSigning) {
75
+ return {
76
+ functionName,
77
+ signature,
78
+ source,
79
+ proxy,
80
+ intent: clearSigning.intent,
81
+ protocol: clearSigning.protocol,
82
+ fields: clearSigning.fields.map((field) => ({
83
+ name: field.label,
84
+ type: field.format,
85
+ value: field.value,
86
+ ...(field.address && { address: field.address }),
87
+ ...(field.name && { label: field.name }),
88
+ ...(field.value.startsWith('Unlimited') && { warning: 'Unlimited spending approval' })
89
+ }))
90
+ };
91
+ }
146
92
  return { functionName, signature, fields, source, proxy };
147
93
  }
148
94
  function topicToAddress(topic) {
@@ -217,7 +163,10 @@ export async function buildTxPreview(chain, tx, from) {
217
163
  const [decoded, simulation, toLabel] = await Promise.all([
218
164
  decodeCalldata(chain, tx).catch(() => undefined),
219
165
  from ? simulate(chain, tx, from).catch(() => undefined) : Promise.resolve(undefined),
220
- addressLabel(chain, tx.to).catch(() => undefined)
166
+ // The registry's protocol name beats anything on-chain: "Aave", not a proxy's class name.
167
+ Promise.resolve(lookupContract(chain, tx.to))
168
+ .then((entry) => (entry ? protocolLabel(entry.protocol) : addressLabel(chain, tx.to)))
169
+ .catch(() => undefined)
221
170
  ]);
222
171
  return {
223
172
  chain,
@@ -1 +1 @@
1
- {"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAClB,WAAW,EACX,WAAW,EAEX,kBAAkB,EACnB,MAAM,MAAM,CAAA;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,SAAS,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACvF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAyDvF,MAAM,cAAc,GAAG,oEAAoE,CAAA;AAE3F,wEAAwE;AACxE,MAAM,iBAAiB,GAAwD;IAC7E,CAAC,kBAAkB,CAAC,mCAAmC,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;IAC5F,CAAC,kBAAkB,CAAC,oCAAoC,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;IAC7E,CAAC,kBAAkB,CAAC,gDAAgD,CAAC,CAAC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE;CAC1F,CAAA;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC,QAAQ,EAAE,CAAA;IACtD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAA;IACvE,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,OAAO,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC;aAC7B,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;aACxC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAA;IAClB,CAAC;IACD,OAAO,MAAM,CAAC,KAAK,CAAC,CAAA;AACtB,CAAC;AAED;;;GAGG;AACH,KAAK,UAAU,cAAc,CAAC,KAAgB,EAAE,EAAS;IACvD,IAAI,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IAElD,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAA;IAC1D,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,kBAAkB,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,CAAC,IAAqB,EAAE,CAAC,CAAA;IAE1F,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAuB,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,IAAI,KAAK,YAAY,CAAC,CAAA;IAC/G,MAAM,MAAM,GAAG,OAAO,EAAE,MAAM,IAAI,EAAE,CAAA;IACpC,MAAM,KAAK,GAAG,iBAAiB,CAAC,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IACrD,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAExH,MAAM,MAAM,GAAmB,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC3D,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAC,CAAA;QACvB,MAAM,IAAI,GAAG,KAAK,EAAE,IAAI,IAAI,MAAM,CAAC,EAAE,CAAA;QACrC,MAAM,KAAK,GAAiB,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,IAAI,SAAS,EAAE,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC,EAAE,CAAA;QAE7F,IAAI,KAAK,EAAE,IAAI,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAA;QAEjF,qEAAqE;QACrE,IAAI,KAAK,IAAI,CAAC,KAAM,IAA2B,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YACxF,IAAI,IAAI,EAAE,QAAQ,KAAK,SAAS,EAAE,CAAC;gBACjC,KAAK,CAAC,KAAK,GAAG,GAAG,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;YAC7F,CAAC;YACD,IAAI,KAAK,CAAC,QAAQ,IAAI,KAAK,GAAG,mBAAmB,EAAE,CAAC;gBAClD,KAAK,CAAC,KAAK,GAAG,YAAY,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;gBACjE,KAAK,CAAC,OAAO,GAAG,6BAA6B,CAAA;YAC/C,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAA;IACd,CAAC,CAAC,CAAA;IAEF,uEAAuE;IACvE,MAAM,OAAO,CAAC,GAAG,CACf,MAAM;SACH,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC;SAChC,GAAG,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QACnB,KAAK,CAAC,KAAK,GAAG,MAAM,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,OAAiB,CAAC,CAAA;IAClE,CAAC,CAAC,CACL,CAAA;IAED,MAAM,SAAS,GAAG,OAAO;QACvB,CAAC,CAAC,GAAG,YAAY,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;QAC9F,CAAC,CAAC,YAAY,CAAA;IAEhB,gFAAgF;IAChF,MAAM,KAAK,GAA4B,EAAE,CAAA;IACzC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,KAAK,CAAC,IAAI;YAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;IACrD,CAAC,CAAC,CAAA;IAEF,uFAAuF;IACvF,qFAAqF;IACrF,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,KAAK,EAAE,EAAE,EAAE,KAAK,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;IAClF,IAAI,YAAY,EAAE,CAAC;QACjB,OAAO;YACL,YAAY;YACZ,SAAS;YACT,MAAM;YACN,KAAK;YACL,MAAM,EAAE,YAAY,CAAC,MAAM;YAC3B,QAAQ,EAAE,YAAY,CAAC,QAAQ;YAC/B,MAAM,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;gBAC1C,IAAI,EAAE,KAAK,CAAC,KAAK;gBACjB,IAAI,EAAE,KAAK,CAAC,MAAM;gBAClB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;gBAChD,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;gBACxC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,6BAA6B,EAAE,CAAC;aACvF,CAAC,CAAC;SACJ,CAAA;IACH,CAAC;IAED,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,CAAA;AAC3D,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,OAAO,KAAK,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,EAAE,CAAA;AAChC,CAAC;AAED,KAAK,UAAU,eAAe,CAAC,KAAgB,EAAE,IAA6E;IAC5H,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,KAAK,cAAc,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAA;IAEjH,OAAO,OAAO,CAAC,GAAG,CAChB,SAAS,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,EAAwB,EAAE;QAChD,MAAM,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,GAAG,CAAA;QAChF,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,CAAA;QAC1G,OAAO;YACL,KAAK,EAAE,GAAG,CAAC,OAAO;YAClB,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,IAAI,EAAE,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC;YACpC,EAAE,EAAE,cAAc,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAE,CAAC;YAClC,MAAM;YACN,WAAW,EAAE,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;SAClG,CAAA;IACH,CAAC,CAAC,CACH,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,KAAK,UAAU,QAAQ,CAAC,KAAgB,EAAE,EAAS,EAAE,IAAa;IAChE,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAClD,MAAM,IAAI,GAAG;QACX,OAAO,EAAE,IAAI;QACb,EAAE,EAAE,EAAE,CAAC,EAAa;QACpB,GAAG,CAAC,EAAE,CAAC,IAAI,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,IAAqB,EAAE,CAAC;QAClD,GAAG,CAAC,EAAE,CAAC,KAAK,IAAI,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC;KACtE,CAAA;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,cAAc,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,CAAC,CAAA;QACrH,MAAM,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,CAAA;QACtC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,KAAK,GAAG,MAAM,CAAC,KAAgE,CAAA;YACrF,OAAO;gBACL,OAAO,EAAE,MAAM,CAAC,MAAM,KAAK,SAAS;gBACpC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,QAAQ,EAAE;gBACvC,KAAK,EAAE,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,YAAY,IAAI,KAAK,EAAE,OAAO,IAAI,oBAAoB,CAAC;gBAChH,YAAY,EAAE,MAAM,eAAe,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,IAAI,IAAI,EAAE,CAAU,CAAC;aACzE,CAAA;QACH,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,0CAA0C;IAC5C,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACvB,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAA;QAC1C,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,CAAC,QAAQ,EAAE,EAAE,YAAY,EAAE,EAAE,EAAE,CAAA;IACzE,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;YAC3E,YAAY,EAAE,EAAE;SACjB,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAgB,EAAE,EAAS,EAAE,IAAa;IAC7E,MAAM,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;IAEhE,MAAM,CAAC,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACvD,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;QAChD,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,EAAE,EAAE,EAAE,IAAe,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC;QAC/F,0FAA0F;QAC1F,OAAO,CAAC,OAAO,CAAC,cAAc,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC;aAC1C,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;aACrF,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KAC1B,CAAC,CAAA;IAEF,OAAO;QACL,KAAK;QACL,EAAE,EAAE,EAAE,CAAC,EAAE;QACT,OAAO;QACP,QAAQ,EAAE,WAAW,gBAAgB,EAAE,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE;QACnE,KAAK;QACL,cAAc,EAAE,KAAK,IAAI,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS;QAC/E,OAAO;QACP,UAAU;KACX,CAAA;AACH,CAAC"}
@@ -38,8 +38,8 @@ declare const _default: {
38
38
  }, "strip", z.ZodTypeAny, {
39
39
  chain: "arbitrum" | "mainnet" | "avalanche" | "base" | "bnb" | "gnosis" | "sonic" | "optimism" | "polygon" | "zksync" | "linea" | "unichain" | "localhost";
40
40
  to: string;
41
- tokenAddress: string;
42
41
  decimals: number;
42
+ tokenAddress: string;
43
43
  amount: string;
44
44
  }, {
45
45
  chain: "arbitrum" | "mainnet" | "avalanche" | "base" | "bnb" | "gnosis" | "sonic" | "optimism" | "polygon" | "zksync" | "linea" | "unichain" | "localhost";
@@ -51,8 +51,8 @@ declare const _default: {
51
51
  handler: (args: {
52
52
  chain: "arbitrum" | "mainnet" | "avalanche" | "base" | "bnb" | "gnosis" | "sonic" | "optimism" | "polygon" | "zksync" | "linea" | "unichain" | "localhost";
53
53
  to: string;
54
- tokenAddress: string;
55
54
  decimals: number;
55
+ tokenAddress: string;
56
56
  amount: string;
57
57
  }) => Promise<import("../types.js").ToolResult>;
58
58
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "web3-tools-mcp",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Model Context Protocol server for blockchain interactions using viem, Etherscan, and Hypersync",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -19,13 +19,14 @@
19
19
  },
20
20
  "scripts": {
21
21
  "clean": "rm -rf dist",
22
- "build": "npm run clean && npm run build -w web3-wallet-relay && tsc",
22
+ "build": "npm run clean && npm run build -w web3-wallet-relay && tsc && cp src/erc7730-index.json.gz dist/",
23
23
  "watch": "tsc --watch",
24
24
  "start:wallet": "npm start -w web3-wallet-relay",
25
25
  "prepare": "npm run build",
26
26
  "test": "vitest run",
27
27
  "test:watch": "vitest",
28
- "test:ui": "vitest --ui"
28
+ "test:ui": "vitest --ui",
29
+ "sync:registry": "node scripts/build-erc7730-index.mjs /tmp/erc7730/registry src/erc7730-index.json.gz"
29
30
  },
30
31
  "keywords": [
31
32
  "mcp",
@@ -0,0 +1,102 @@
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, type Address, parseAbiItem } from 'viem'
7
+ import { whatsabi } from '@shazow/whatsabi'
8
+ import { getClientManager } from './client.js'
9
+ import type { ChainName } from './types.js'
10
+
11
+ const ERC20_META_ABI = [
12
+ parseAbiItem('function decimals() view returns (uint8)'),
13
+ parseAbiItem('function symbol() view returns (string)')
14
+ ]
15
+
16
+ const abiCache = new Map<string, { abi: Abi; source: 'verified' | 'guessed'; proxy?: string; name?: string }>()
17
+ const labelCache = new Map<string, string | undefined>()
18
+ const metaCache = new Map<string, { symbol?: string; decimals?: number }>()
19
+
20
+ export async function loadAbi(chain: ChainName, address: string) {
21
+ const key = `${chain}:${address.toLowerCase()}`
22
+ const cached = abiCache.get(key)
23
+ if (cached) return cached
24
+
25
+ const clientManager = getClientManager()
26
+ const client = clientManager.getClient(chain)
27
+ const etherscanApiKey = clientManager.getConfig().etherscanApiKey
28
+
29
+ const loaders: whatsabi.loaders.ABILoader[] = [new whatsabi.loaders.SourcifyABILoader({ chainId: clientManager.getChainId(chain) })]
30
+ if (etherscanApiKey) {
31
+ loaders.push(new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: etherscanApiKey, chainId: clientManager.getChainId(chain) }))
32
+ }
33
+
34
+ const result = await whatsabi.autoload(address as Address, {
35
+ provider: client,
36
+ abiLoader: new whatsabi.loaders.MultiABILoader(loaders),
37
+ signatureLookup: new whatsabi.loaders.OpenChainSignatureLookup(),
38
+ followProxies: true
39
+ })
40
+
41
+ // Bytecode-guessed ABIs carry no argument names; verified ones do.
42
+ const source = result.abi.some((item) => item.type === 'function' && item.inputs?.some((i) => i.name)) ? 'verified' : 'guessed'
43
+ const loaded = {
44
+ abi: result.abi as Abi,
45
+ source: source as 'verified' | 'guessed',
46
+ proxy: result.address !== address ? result.address : undefined,
47
+ name: result.contractResult?.name ?? undefined
48
+ }
49
+ abiCache.set(key, loaded)
50
+ return loaded
51
+ }
52
+
53
+ export async function tokenMeta(chain: ChainName, token: string) {
54
+ const key = `${chain}:${token.toLowerCase()}`
55
+ const cached = metaCache.get(key)
56
+ if (cached) return cached
57
+
58
+ const client = getClientManager().getClient(chain)
59
+ const [decimals, symbol] = await client.multicall({
60
+ contracts: [
61
+ { address: token as Address, abi: ERC20_META_ABI, functionName: 'decimals' },
62
+ { address: token as Address, abi: ERC20_META_ABI, functionName: 'symbol' }
63
+ ],
64
+ ...(chain === 'localhost' && { deployless: true })
65
+ })
66
+
67
+ const meta = {
68
+ decimals: decimals.status === 'success' ? Number(decimals.result) : undefined,
69
+ symbol: symbol.status === 'success' ? (symbol.result as string) : undefined
70
+ }
71
+ metaCache.set(key, meta)
72
+ return meta
73
+ }
74
+
75
+ /**
76
+ * Human label for an address: token symbol first (more recognisable than the contract
77
+ * name โ€” "USDC" beats "FiatTokenProxy"), then the verified contract name. EOAs get none,
78
+ * and the bytecode check keeps us from asking explorers about plain wallets.
79
+ */
80
+ export async function addressLabel(chain: ChainName, address: string): Promise<string | undefined> {
81
+ const key = `${chain}:${address.toLowerCase()}`
82
+ if (labelCache.has(key)) return labelCache.get(key)
83
+
84
+ let label: string | undefined
85
+ try {
86
+ const code = await getClientManager().getClient(chain).getBytecode({ address: address as Address })
87
+ if (code && code !== '0x') {
88
+ label = (await tokenMeta(chain, address).catch(() => ({ symbol: undefined }))).symbol
89
+ if (!label) label = (await loadAbi(chain, address)).name
90
+ }
91
+ } catch {
92
+ // Unknown address โ€” show it bare rather than failing the preview.
93
+ }
94
+
95
+ labelCache.set(key, label)
96
+ return label
97
+ }
98
+
99
+
100
+ /** uint256 max and anything near it means "infinite" in an approval. */
101
+ export const MAX_UINT256 = (1n << 256n) - 1n
102
+ export const UNLIMITED_THRESHOLD = MAX_UINT256 / 2n
@@ -0,0 +1,206 @@
1
+ import { createRequire } from 'node:module'
2
+ import { gunzipSync } from 'node:zlib'
3
+ import { formatUnits, getAddress, isAddress } from 'viem'
4
+ import { addressLabel, tokenMeta, UNLIMITED_THRESHOLD } from './chain-meta.js'
5
+ import { getClientManager } from './client.js'
6
+ import type { ChainName } from './types.js'
7
+
8
+ /**
9
+ * Clear signing from the Ledger ERC-7730 registry: for a known contract and selector it
10
+ * says what the transaction *means* ("Swap", "Approve spending") and labels each field the
11
+ * way the protocol intends, instead of showing raw ABI argument names.
12
+ *
13
+ * The index is built from the registry by scripts/build-erc7730-index.mjs.
14
+ */
15
+
16
+ interface IndexField {
17
+ // A handful of registry descriptors omit these, so nothing here is guaranteed.
18
+ label?: string
19
+ path?: string
20
+ format?: string
21
+ /** tokenAmount: `tokenPath` points at the field holding the token address. */
22
+ params?: { tokenPath?: string; tokenAddress?: string; [k: string]: unknown }
23
+ }
24
+
25
+ interface IndexSelector {
26
+ name: string
27
+ intent: string
28
+ /** Full signature, needed to ABI-decode the calldata these paths address. */
29
+ signature?: string
30
+ fields: IndexField[]
31
+ }
32
+
33
+ interface IndexEntry {
34
+ protocol: string
35
+ chainIds: number[]
36
+ selectors: Record<string, IndexSelector>
37
+ }
38
+
39
+ export interface ClearSigningField {
40
+ label: string
41
+ format: string
42
+ value: string
43
+ address?: string
44
+ name?: string
45
+ }
46
+
47
+ export interface ClearSigningInfo {
48
+ protocol: string
49
+ intent: string
50
+ functionName: string
51
+ fields: ClearSigningField[]
52
+ }
53
+
54
+ // Prettier names for registry directory slugs; unknown ones read fine as-is.
55
+ const PROTOCOL_LABELS: Record<string, string> = {
56
+ lifi: 'LI.FI',
57
+ '1inch': '1inch',
58
+ uniswap: 'Uniswap',
59
+ paraswap: 'ParaSwap',
60
+ cowswap: 'CoW Swap',
61
+ '0x': '0x Protocol',
62
+ aave: 'Aave',
63
+ morpho: 'Morpho',
64
+ safe: 'Safe',
65
+ lido: 'Lido',
66
+ eigenlayer: 'EigenLayer',
67
+ ens: 'ENS'
68
+ }
69
+
70
+ const require = createRequire(import.meta.url)
71
+ let index: Record<string, IndexEntry> | undefined
72
+
73
+ /** Loaded on first use: ~1.5MB of JSON, shipped gzipped at ~60KB. */
74
+ function registry(): Record<string, IndexEntry> {
75
+ if (!index) {
76
+ try {
77
+ const path = require.resolve('./erc7730-index.json.gz')
78
+ index = JSON.parse(gunzipSync(require('node:fs').readFileSync(path)).toString())
79
+ } catch {
80
+ index = {}
81
+ }
82
+ }
83
+ return index as Record<string, IndexEntry>
84
+ }
85
+
86
+ export function protocolLabel(slug: string): string {
87
+ return PROTOCOL_LABELS[slug] ?? slug
88
+ }
89
+
90
+ /** Resolve a registry path ("params.amountIn", "#.to", "[0].value") against decoded args. */
91
+ function getByPath(root: unknown, path: string): unknown {
92
+ let current: unknown = root
93
+ for (const part of path.replace(/^[#@]\./, '').split('.')) {
94
+ if (current == null) return undefined
95
+ const arrayIndex = part.match(/^\[(\d*)\]$/)
96
+ if (arrayIndex) {
97
+ current = Array.isArray(current) ? current[arrayIndex[1] === '' ? 0 : Number(arrayIndex[1])] : undefined
98
+ } else {
99
+ current = (current as Record<string, unknown>)[part]
100
+ }
101
+ }
102
+ return current
103
+ }
104
+
105
+ interface TxContext {
106
+ to: string
107
+ value?: string
108
+ from?: string
109
+ }
110
+
111
+ function resolvePath(root: unknown, tx: TxContext, path: string): unknown {
112
+ if (path.startsWith('@.')) {
113
+ if (path.slice(2) === 'to') return tx.to
114
+ if (path.slice(2) === 'value') return tx.value
115
+ if (path.slice(2) === 'from') return tx.from
116
+ return undefined
117
+ }
118
+ return getByPath(root, path)
119
+ }
120
+
121
+ async function formatField(chain: ChainName, field: IndexField, root: unknown, tx: TxContext): Promise<ClearSigningField> {
122
+ // A missing format with a token reference still means an amount.
123
+ const format = field.format ?? (field.params?.tokenPath || field.params?.tokenAddress ? 'tokenAmount' : 'raw')
124
+ const label = field.label ?? field.path ?? format
125
+ const raw = field.path ? resolvePath(root, tx, field.path) : undefined
126
+ const out: ClearSigningField = { label, format, value: '' }
127
+
128
+ // Unresolved: show the descriptor's path rather than an empty row.
129
+ if (raw == null) {
130
+ out.value = field.path ?? ''
131
+ return out
132
+ }
133
+
134
+ switch (format) {
135
+ case 'tokenAmount':
136
+ case 'amount': {
137
+ const amount = typeof raw === 'bigint' ? raw : BigInt(raw as string | number)
138
+ const token =
139
+ field.params?.tokenAddress ?? (field.params?.tokenPath ? resolvePath(root, tx, field.params.tokenPath) : undefined)
140
+
141
+ const meta =
142
+ typeof token === 'string' && isAddress(token)
143
+ ? await tokenMeta(chain, token).catch(() => ({ symbol: undefined, decimals: undefined }))
144
+ : { symbol: undefined, decimals: undefined }
145
+
146
+ const symbol = meta.symbol ? ` ${meta.symbol}` : ''
147
+ out.value = amount > UNLIMITED_THRESHOLD ? `Unlimited${symbol}` : `${formatUnits(amount, meta.decimals ?? 18)}${symbol}`
148
+ return out
149
+ }
150
+
151
+ case 'addressName':
152
+ case 'address':
153
+ case 'tokenName': {
154
+ if (!isAddress(raw as string)) {
155
+ out.value = String(raw)
156
+ return out
157
+ }
158
+ const address = getAddress(raw as string)
159
+ const name = registry()[address.toLowerCase()]?.protocol
160
+ out.address = address
161
+ out.name = name ? protocolLabel(name) : await addressLabel(chain, address).catch(() => undefined)
162
+ out.value = out.name ?? address
163
+ return out
164
+ }
165
+
166
+ default:
167
+ out.value = typeof raw === 'bigint' ? raw.toString() : String(raw)
168
+ return out
169
+ }
170
+ }
171
+
172
+ /** Registry entry for a contract, if it is listed for this chain. */
173
+ export function lookupContract(chain: ChainName, address: string): IndexEntry | undefined {
174
+ const entry = registry()[address.toLowerCase()]
175
+ if (!entry) return undefined
176
+ const chainId = getClientManager().getChainId(chain)
177
+ return entry.chainIds.includes(chainId) ? entry : undefined
178
+ }
179
+
180
+ /**
181
+ * Clear-signing info for a transaction, or null when the contract/selector is unlisted.
182
+ * `decodedArgs` is supplied by the caller, which has already decoded the calldata against
183
+ * the contract's real ABI โ€” the registry's own signature is only a fallback for naming.
184
+ */
185
+ export async function resolveClearSigning(
186
+ chain: ChainName,
187
+ tx: TxContext & { data?: string },
188
+ decodedArgs: Record<string, unknown>
189
+ ): Promise<ClearSigningInfo | null> {
190
+ if (!tx.data || tx.data.length < 10) return null
191
+
192
+ const entry = lookupContract(chain, tx.to)
193
+ const format = entry?.selectors[tx.data.slice(0, 10).toLowerCase()]
194
+ if (!entry || !format) return null
195
+
196
+ // Fields with neither a path nor a label carry nothing a signer could read.
197
+ const usable = format.fields.filter((field) => field.path || field.label)
198
+ const fields = await Promise.all(usable.map((field) => formatField(chain, field, decodedArgs, tx)))
199
+
200
+ return {
201
+ protocol: protocolLabel(entry.protocol),
202
+ intent: format.intent,
203
+ functionName: format.name,
204
+ fields
205
+ }
206
+ }
Binary file
package/src/preview.ts CHANGED
@@ -9,8 +9,9 @@ import {
9
9
  toFunctionSelector
10
10
  } from 'viem'
11
11
  import { simulateBlocks } from 'viem/actions'
12
- import { whatsabi } from '@shazow/whatsabi'
13
12
  import { getClientManager } from './client.js'
13
+ import { addressLabel, loadAbi, tokenMeta, UNLIMITED_THRESHOLD } from './chain-meta.js'
14
+ import { lookupContract, protocolLabel, resolveClearSigning } from './clear-signing.js'
14
15
  import type { ChainName } from './types.js'
15
16
 
16
17
  export interface PreviewField {
@@ -46,6 +47,9 @@ export interface TxPreview {
46
47
  functionName: string
47
48
  signature: string
48
49
  fields: PreviewField[]
50
+ /** From the ERC-7730 registry: what this call means, and whose contract it is. */
51
+ intent?: string
52
+ protocol?: string
49
53
  /** 'verified' = ABI from Sourcify/Etherscan, 'guessed' = selector lookup on bytecode */
50
54
  source: 'verified' | 'guessed'
51
55
  proxy?: string
@@ -64,14 +68,7 @@ export interface RawTx {
64
68
  value?: string
65
69
  }
66
70
 
67
- const ERC20_META_ABI = [
68
- parseAbiItem('function decimals() view returns (uint8)'),
69
- parseAbiItem('function symbol() view returns (string)')
70
- ]
71
-
72
71
  const TRANSFER_TOPIC = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'
73
- const MAX_UINT256 = (1n << 256n) - 1n
74
- const UNLIMITED_THRESHOLD = MAX_UINT256 / 2n
75
72
 
76
73
  // Selectors whose amount argument is denominated in the token at tx.to.
77
74
  const ERC20_AMOUNT_ARGS: Record<string, { arg: string; approval?: boolean }> = {
@@ -80,89 +77,6 @@ const ERC20_AMOUNT_ARGS: Record<string, { arg: string; approval?: boolean }> = {
80
77
  [toFunctionSelector('function transferFrom(address,address,uint256)')]: { arg: 'amount' }
81
78
  }
82
79
 
83
- const abiCache = new Map<string, { abi: Abi; source: 'verified' | 'guessed'; proxy?: string; name?: string }>()
84
- const labelCache = new Map<string, string | undefined>()
85
- const metaCache = new Map<string, { symbol?: string; decimals?: number }>()
86
-
87
- async function loadAbi(chain: ChainName, address: string) {
88
- const key = `${chain}:${address.toLowerCase()}`
89
- const cached = abiCache.get(key)
90
- if (cached) return cached
91
-
92
- const clientManager = getClientManager()
93
- const client = clientManager.getClient(chain)
94
- const etherscanApiKey = clientManager.getConfig().etherscanApiKey
95
-
96
- const loaders: whatsabi.loaders.ABILoader[] = [new whatsabi.loaders.SourcifyABILoader({ chainId: clientManager.getChainId(chain) })]
97
- if (etherscanApiKey) {
98
- loaders.push(new whatsabi.loaders.EtherscanV2ABILoader({ apiKey: etherscanApiKey, chainId: clientManager.getChainId(chain) }))
99
- }
100
-
101
- const result = await whatsabi.autoload(address as Address, {
102
- provider: client,
103
- abiLoader: new whatsabi.loaders.MultiABILoader(loaders),
104
- signatureLookup: new whatsabi.loaders.OpenChainSignatureLookup(),
105
- followProxies: true
106
- })
107
-
108
- // Bytecode-guessed ABIs carry no argument names; verified ones do.
109
- const source = result.abi.some((item) => item.type === 'function' && item.inputs?.some((i) => i.name)) ? 'verified' : 'guessed'
110
- const loaded = {
111
- abi: result.abi as Abi,
112
- source: source as 'verified' | 'guessed',
113
- proxy: result.address !== address ? result.address : undefined,
114
- name: result.contractResult?.name ?? undefined
115
- }
116
- abiCache.set(key, loaded)
117
- return loaded
118
- }
119
-
120
- async function tokenMeta(chain: ChainName, token: string) {
121
- const key = `${chain}:${token.toLowerCase()}`
122
- const cached = metaCache.get(key)
123
- if (cached) return cached
124
-
125
- const client = getClientManager().getClient(chain)
126
- const [decimals, symbol] = await client.multicall({
127
- contracts: [
128
- { address: token as Address, abi: ERC20_META_ABI, functionName: 'decimals' },
129
- { address: token as Address, abi: ERC20_META_ABI, functionName: 'symbol' }
130
- ],
131
- ...(chain === 'localhost' && { deployless: true })
132
- })
133
-
134
- const meta = {
135
- decimals: decimals.status === 'success' ? Number(decimals.result) : undefined,
136
- symbol: symbol.status === 'success' ? (symbol.result as string) : undefined
137
- }
138
- metaCache.set(key, meta)
139
- return meta
140
- }
141
-
142
- /**
143
- * Human label for an address: token symbol first (more recognisable than the contract
144
- * name โ€” "USDC" beats "FiatTokenProxy"), then the verified contract name. EOAs get none,
145
- * and the bytecode check keeps us from asking explorers about plain wallets.
146
- */
147
- async function addressLabel(chain: ChainName, address: string): Promise<string | undefined> {
148
- const key = `${chain}:${address.toLowerCase()}`
149
- if (labelCache.has(key)) return labelCache.get(key)
150
-
151
- let label: string | undefined
152
- try {
153
- const code = await getClientManager().getClient(chain).getBytecode({ address: address as Address })
154
- if (code && code !== '0x') {
155
- label = (await tokenMeta(chain, address).catch(() => ({ symbol: undefined }))).symbol
156
- if (!label) label = (await loadAbi(chain, address)).name
157
- }
158
- } catch {
159
- // Unknown address โ€” show it bare rather than failing the preview.
160
- }
161
-
162
- labelCache.set(key, label)
163
- return label
164
- }
165
-
166
80
  function stringify(value: unknown): string {
167
81
  if (typeof value === 'bigint') return value.toString()
168
82
  if (Array.isArray(value)) return `[${value.map(stringify).join(', ')}]`
@@ -222,6 +136,34 @@ async function decodeCalldata(chain: ChainName, tx: RawTx): Promise<TxPreview['d
222
136
  ? `${functionName}(${inputs.map((i) => `${i.type}${i.name ? ` ${i.name}` : ''}`).join(', ')})`
223
137
  : functionName
224
138
 
139
+ // Map positional args to their parameter names so registry field paths resolve.
140
+ const named: Record<string, unknown> = {}
141
+ inputs.forEach((input, i) => {
142
+ if (input.name) named[input.name] = (args ?? [])[i]
143
+ })
144
+
145
+ // A registry descriptor says what the call means and how the protocol wants each field
146
+ // labelled, which beats raw ABI argument names. Fall back to those when it has none.
147
+ const clearSigning = await resolveClearSigning(chain, tx, named).catch(() => null)
148
+ if (clearSigning) {
149
+ return {
150
+ functionName,
151
+ signature,
152
+ source,
153
+ proxy,
154
+ intent: clearSigning.intent,
155
+ protocol: clearSigning.protocol,
156
+ fields: clearSigning.fields.map((field) => ({
157
+ name: field.label,
158
+ type: field.format,
159
+ value: field.value,
160
+ ...(field.address && { address: field.address }),
161
+ ...(field.name && { label: field.name }),
162
+ ...(field.value.startsWith('Unlimited') && { warning: 'Unlimited spending approval' })
163
+ }))
164
+ }
165
+ }
166
+
225
167
  return { functionName, signature, fields, source, proxy }
226
168
  }
227
169
 
@@ -304,7 +246,10 @@ export async function buildTxPreview(chain: ChainName, tx: RawTx, from?: string)
304
246
  const [decoded, simulation, toLabel] = await Promise.all([
305
247
  decodeCalldata(chain, tx).catch(() => undefined),
306
248
  from ? simulate(chain, tx, from as Address).catch(() => undefined) : Promise.resolve(undefined),
307
- addressLabel(chain, tx.to).catch(() => undefined)
249
+ // The registry's protocol name beats anything on-chain: "Aave", not a proxy's class name.
250
+ Promise.resolve(lookupContract(chain, tx.to))
251
+ .then((entry) => (entry ? protocolLabel(entry.protocol) : addressLabel(chain, tx.to)))
252
+ .catch(() => undefined)
308
253
  ])
309
254
 
310
255
  return {