Files
proxmox/token-lists/scripts/verify-on-chain.js
defiQUG cb47cce074 Complete markdown files cleanup and organization
- Organized 252 files across project
- Root directory: 187 → 2 files (98.9% reduction)
- Moved configuration guides to docs/04-configuration/
- Moved troubleshooting guides to docs/09-troubleshooting/
- Moved quick start guides to docs/01-getting-started/
- Moved reports to reports/ directory
- Archived temporary files
- Generated comprehensive reports and documentation
- Created maintenance scripts and guides

All files organized according to established standards.
2026-01-06 01:46:25 -08:00

285 lines
8.6 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* On-Chain Verification Script
* Verifies token list entries against on-chain contracts using RPC calls
*
* RPC endpoints (fallback order):
* 1. https://rpc-http-pub.d-bis.org (primary)
* 2. https://rpc-core.d-bis.org (fallback)
*/
import { readFileSync } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, resolve } from 'path';
import { ethers } from 'ethers';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const RPC_ENDPOINTS = [
'https://rpc-http-pub.d-bis.org',
'https://rpc-core.d-bis.org'
];
const REQUIRED_CHAIN_ID = 138;
const REQUIRED_CHAIN_ID_HEX = '0x8a';
// ERC-20 ABI (minimal)
const ERC20_ABI = [
'function decimals() view returns (uint8)',
'function symbol() view returns (string)',
'function name() view returns (string)',
'function totalSupply() view returns (uint256)'
];
// Oracle ABI (Chainlink-compatible)
const ORACLE_ABI = [
'function latestRoundData() view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)',
'function decimals() view returns (uint8)',
'function description() view returns (string)'
];
async function getProvider() {
let lastError;
for (const rpcUrl of RPC_ENDPOINTS) {
try {
const provider = new ethers.JsonRpcProvider(rpcUrl);
// Verify chain ID
const network = await provider.getNetwork();
const chainId = Number(network.chainId);
if (chainId !== REQUIRED_CHAIN_ID) {
throw new Error(`Chain ID mismatch: expected ${REQUIRED_CHAIN_ID}, got ${chainId}`);
}
// Test connection
await provider.getBlockNumber();
console.log(`✅ Connected to RPC: ${rpcUrl} (Chain ID: ${chainId})\n`);
return provider;
} catch (error) {
lastError = error;
console.log(`⚠️ Failed to connect to ${rpcUrl}: ${error.message}`);
continue;
}
}
throw new Error(`Failed to connect to any RPC endpoint. Last error: ${lastError?.message}`);
}
async function verifyERC20Token(provider, token, index) {
const results = [];
const prefix = `Token[${index}] ${token.symbol || token.name}`;
try {
// Check if contract exists
const code = await provider.getCode(token.address);
if (code === '0x') {
results.push({ type: 'error', message: `${prefix}: No contract code at address ${token.address}` });
return results;
}
const contract = new ethers.Contract(token.address, ERC20_ABI, provider);
// Verify decimals
try {
const onChainDecimals = await contract.decimals();
if (Number(onChainDecimals) !== token.decimals) {
results.push({
type: 'error',
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
});
} else {
results.push({ type: 'success', message: `${prefix}: Decimals verified (${token.decimals})` });
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read decimals: ${error.message}` });
}
// Verify symbol (warn if different)
try {
const onChainSymbol = await contract.symbol();
if (onChainSymbol !== token.symbol) {
results.push({
type: 'warning',
message: `${prefix}: Symbol mismatch - list: "${token.symbol}", on-chain: "${onChainSymbol}"`
});
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read symbol: ${error.message}` });
}
// Verify name (warn if different)
try {
const onChainName = await contract.name();
if (onChainName !== token.name) {
results.push({
type: 'warning',
message: `${prefix}: Name mismatch - list: "${token.name}", on-chain: "${onChainName}"`
});
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read name: ${error.message}` });
}
// Verify totalSupply (optional)
try {
await contract.totalSupply();
results.push({ type: 'success', message: `${prefix}: totalSupply() callable` });
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: totalSupply() failed: ${error.message}` });
}
} catch (error) {
results.push({ type: 'error', message: `${prefix}: Verification failed: ${error.message}` });
}
return results;
}
async function verifyOracleToken(provider, token, index) {
const results = [];
const prefix = `Token[${index}] ${token.symbol || token.name} (Oracle)`;
try {
// Check if contract exists
const code = await provider.getCode(token.address);
if (code === '0x') {
results.push({ type: 'error', message: `${prefix}: No contract code at address ${token.address}` });
return results;
}
const contract = new ethers.Contract(token.address, ORACLE_ABI, provider);
// Verify latestRoundData
try {
await contract.latestRoundData();
results.push({ type: 'success', message: `${prefix}: latestRoundData() callable` });
} catch (error) {
results.push({ type: 'error', message: `${prefix}: latestRoundData() failed: ${error.message}` });
}
// Verify decimals
try {
const onChainDecimals = await contract.decimals();
if (Number(onChainDecimals) !== token.decimals) {
results.push({
type: 'error',
message: `${prefix}: Decimals mismatch - list: ${token.decimals}, on-chain: ${onChainDecimals}`
});
} else {
results.push({ type: 'success', message: `${prefix}: Decimals verified (${token.decimals})` });
}
} catch (error) {
results.push({ type: 'warning', message: `${prefix}: Failed to read decimals: ${error.message}` });
}
} catch (error) {
results.push({ type: 'error', message: `${prefix}: Verification failed: ${error.message}` });
}
return results;
}
function isOracleToken(token) {
return token.tags && (token.tags.includes('oracle') || token.tags.includes('pricefeed'));
}
async function verifyOnChain(filePath, required = false) {
console.log(`\n🔗 Verifying on-chain contracts: ${filePath}\n`);
// Read token list file
let tokenList;
try {
const fileContent = readFileSync(filePath, 'utf-8');
tokenList = JSON.parse(fileContent);
} catch (error) {
console.error('❌ Error reading or parsing token list file:');
console.error(` ${error.message}`);
process.exit(1);
}
let provider;
try {
provider = await getProvider();
} catch (error) {
if (required) {
console.error(`${error.message}`);
console.error('On-chain verification is required but RPC connection failed.');
process.exit(1);
} else {
console.log(`⚠️ ${error.message}`);
console.log('Skipping on-chain verification (optional mode)\n');
return 0;
}
}
if (!tokenList.tokens || !Array.isArray(tokenList.tokens)) {
console.log('No tokens to verify.\n');
return 0;
}
const allResults = [];
for (const [index, token] of tokenList.tokens.entries()) {
let results;
if (isOracleToken(token)) {
results = await verifyOracleToken(provider, token, index);
} else {
results = await verifyERC20Token(provider, token, index);
}
allResults.push(...results);
}
// Report results
const errors = allResults.filter(r => r.type === 'error');
const warnings = allResults.filter(r => r.type === 'warning');
const successes = allResults.filter(r => r.type === 'success');
if (errors.length > 0) {
console.log('❌ Errors:');
errors.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (warnings.length > 0) {
console.log('⚠️ Warnings:');
warnings.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (successes.length > 0) {
console.log('✅ Verified:');
successes.forEach(r => console.log(` ${r.message}`));
console.log('');
}
if (errors.length > 0) {
console.log(`❌ Verification failed with ${errors.length} error(s)\n`);
return 1;
}
console.log('✅ All on-chain verifications passed!\n');
return 0;
}
// Main
const args = process.argv.slice(2);
const filePath = args.find(arg => !arg.startsWith('--')) || resolve(__dirname, '../lists/dbis-138.tokenlist.json');
const required = args.includes('--required');
if (!filePath) {
console.error('Usage: node verify-on-chain.js [path/to/token-list.json] [--required]');
process.exit(1);
}
verifyOnChain(filePath, required).then(exitCode => {
process.exit(exitCode);
}).catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
});