#!/usr/bin/env node /** * Verify WETH9_138 fingerprint (symbol, name, decimals, optional bytecode hash). * Bot/executor must run at startup and halt if fingerprint does not match expected. * Usage: node verify-weth9-fingerprint.js [--no-exit] * Exit 0 = match, 1 = mismatch or error. */ const WETH9_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; const RPC_138 = process.env.RPC_URL_138 || 'https://rpc-http-pub.d-bis.org'; const RPC_MAINNET = process.env.ETHEREUM_MAINNET_RPC || 'https://eth.llamarpc.com'; const SELECTORS = { symbol: '0x95d89b41', name: '0x06fdde03', decimals: '0x313ce567' }; async function call(rpcUrl, to, data) { const res = await fetch(rpcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_call', params: [{ to, data }, 'latest'], id: 1 }), }); const d = await res.json(); if (d.error) throw new Error(d.error.message || JSON.stringify(d.error)); return d.result; } async function getCode(rpcUrl, address) { const res = await fetch(rpcUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', method: 'eth_getCode', params: [address, 'latest'], id: 1 }), }); const d = await res.json(); if (d.error) throw new Error(d.error.message || JSON.stringify(d.error)); return d.result; } function decodeString(hex) { if (!hex || hex === '0x') return ''; try { const ethers = require('ethers'); return ethers.AbiCoder.defaultAbiCoder().decode(['string'], hex)[0]; } catch (_) { return hex; } } function decodeUint8(hex) { if (!hex || hex === '0x') return 0; return parseInt(hex.slice(-2), 16); } async function getFingerprint(rpcUrl, address) { const [symbolHex, nameHex, decimalsHex] = await Promise.all([ call(rpcUrl, address, SELECTORS.symbol), call(rpcUrl, address, SELECTORS.name), call(rpcUrl, address, SELECTORS.decimals), ]); const ethers = require('ethers'); const code = await getCode(rpcUrl, address); const codeHash = code && code !== '0x' ? ethers.keccak256(code) : null; return { symbol: decodeString(symbolHex), name: decodeString(nameHex), decimals: decodeUint8(decimalsHex), codeHash, }; } async function main() { const fp138 = await getFingerprint(RPC_138, WETH9_ADDRESS); console.log('Chain 138 fingerprint:', JSON.stringify(fp138, null, 2)); const fpMain = await getFingerprint(RPC_MAINNET, WETH9_ADDRESS); console.log('Ethereum mainnet fingerprint:', JSON.stringify(fpMain, null, 2)); const match = fp138.symbol === fpMain.symbol && fp138.name === fpMain.name && fp138.decimals === fpMain.decimals && (fp138.codeHash == null || fpMain.codeHash == null || fp138.codeHash === fpMain.codeHash); if (match) { console.log('Match: YES — WETH9_138 fingerprint matches expected.'); process.exit(0); } console.error('Halt: WETH9_138 fingerprint mismatch.'); process.exit(1); } main().catch((err) => { console.error(err); process.exit(1); });