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 +16 -5
- 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/erc7730-index.json.gz +0 -0
- package/dist/preview.d.ts +3 -0
- package/dist/preview.d.ts.map +1 -1
- package/dist/preview.js +34 -85
- package/dist/preview.js.map +1 -1
- package/dist/tools/transactions.d.ts +2 -2
- package/package.json +4 -3
- package/src/chain-meta.ts +102 -0
- package/src/clear-signing.ts +206 -0
- package/src/erc7730-index.json.gz +0 -0
- package/src/preview.ts +37 -92
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,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://
|
|
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
|
-
- **
|
|
110
|
-
|
|
111
|
-
|
|
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;
|
package/dist/preview.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.d.ts","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"
|
|
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,
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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,
|
package/dist/preview.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preview.js","sourceRoot":"","sources":["../src/preview.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,kBAAkB,EAClB,WAAW,EACX,WAAW,EACX,YAAY,EACZ,kBAAkB,EACnB,MAAM,MAAM,CAAA;AACb,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAsD9C,MAAM,cAAc,GAAG;IACrB,YAAY,CAAC,0CAA0C,CAAC;IACxD,YAAY,CAAC,yCAAyC,CAAC;CACxD,CAAA;AAED,MAAM,cAAc,GAAG,oEAAoE,CAAA;AAC3F,MAAM,WAAW,GAAG,CAAC,EAAE,IAAI,IAAI,CAAC,GAAG,EAAE,CAAA;AACrC,MAAM,mBAAmB,GAAG,WAAW,GAAG,EAAE,CAAA;AAE5C,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,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuF,CAAA;AAC/G,MAAM,UAAU,GAAG,IAAI,GAAG,EAA8B,CAAA;AACxD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkD,CAAA;AAE3E,KAAK,UAAU,OAAO,CAAC,KAAgB,EAAE,OAAe;IACtD,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,EAAE,CAAA;IAC/C,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAChC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IAEzB,MAAM,aAAa,GAAG,gBAAgB,EAAE,CAAA;IACxC,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAC7C,MAAM,eAAe,GAAG,aAAa,CAAC,SAAS,EAAE,CAAC,eAAe,CAAA;IAEjE,MAAM,OAAO,GAAiC,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IACpI,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,OAAO,CAAC,oBAAoB,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,OAAO,EAAE,aAAa,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAA;IAChI,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,OAAkB,EAAE;QACzD,QAAQ,EAAE,MAAM;QAChB,SAAS,EAAE,IAAI,QAAQ,CAAC,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC;QACvD,eAAe,EAAE,IAAI,QAAQ,CAAC,OAAO,CAAC,wBAAwB,EAAE;QAChE,aAAa,EAAE,IAAI;KACpB,CAAC,CAAA;IAEF,mEAAmE;IACnE,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,UAAU,IAAI,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAA;IAC/H,MAAM,MAAM,GAAG;QACb,GAAG,EAAE,MAAM,CAAC,GAAU;QACtB,MAAM,EAAE,MAAgC;QACxC,KAAK,EAAE,MAAM,CAAC,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC9D,IAAI,EAAE,MAAM,CAAC,cAAc,EAAE,IAAI,IAAI,SAAS;KAC/C,CAAA;IACD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACzB,OAAO,MAAM,CAAA;AACf,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,KAAgB,EAAE,KAAa;IACtD,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,KAAK,CAAC,WAAW,EAAE,EAAE,CAAA;IAC7C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IACjC,IAAI,MAAM;QAAE,OAAO,MAAM,CAAA;IAEzB,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAA;IAClD,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC;QAChD,SAAS,EAAE;YACT,EAAE,OAAO,EAAE,KAAgB,EAAE,GAAG,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE;YAC5E,EAAE,OAAO,EAAE,KAAgB,EAAE,GAAG,EAAE,cAAc,EAAE,YAAY,EAAE,QAAQ,EAAE;SAC3E;QACD,GAAG,CAAC,KAAK,KAAK,WAAW,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;KACnD,CAAC,CAAA;IAEF,MAAM,IAAI,GAAG;QACX,QAAQ,EAAE,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS;QAC7E,MAAM,EAAE,MAAM,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAE,MAAM,CAAC,MAAiB,CAAC,CAAC,CAAC,SAAS;KAC5E,CAAA;IACD,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;IACxB,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;GAIG;AACH,KAAK,UAAU,YAAY,CAAC,KAAgB,EAAE,OAAe;IAC3D,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,EAAE,CAAA;IAC/C,IAAI,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC;QAAE,OAAO,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAEnD,IAAI,KAAyB,CAAA;IAC7B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,MAAM,gBAAgB,EAAE,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,OAAkB,EAAE,CAAC,CAAA;QACnG,IAAI,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAC1B,KAAK,GAAG,CAAC,MAAM,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;YACrF,IAAI,CAAC,KAAK;gBAAE,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;QAC1D,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,kEAAkE;IACpE,CAAC;IAED,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;IAC1B,OAAO,KAAK,CAAA;AACd,CAAC;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,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,YAAY,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC;KAClD,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"}
|
|
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.
|
|
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
|
-
|
|
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 {
|