83 lines
3.0 KiB
JavaScript
83 lines
3.0 KiB
JavaScript
#!/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); });
|