Files
smom-dbis-138/scripts/treasury/verify-weth9-fingerprint.js
2026-03-02 12:14:09 -08:00

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); });