#!/usr/bin/env node /** * WETH → USDT Bridge Verification using thirdweb SDK * Verifies bytecode, ERC-20 compliance, and bridge route availability */ const { ThirdwebSDK } = require("@thirdweb-dev/sdk"); const { ethers } = require("ethers"); // Configuration const CHAIN138_RPC = process.env.RPC_URL_138 || "https://rpc-http-pub.d-bis.org"; const CHAIN138_ID = 138; const ETHEREUM_MAINNET_ID = 1; // Token addresses const WETH_CANONICAL = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; const WETH_CHAIN138 = "0x3304b747E565a97ec8AC220b0B6A1f6ffDB837e6"; // Actual deployed address const USDT_MAINNET = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; // ERC-20 ABI (minimal for verification) const ERC20_ABI = [ "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function totalSupply() view returns (uint256)" ]; // Colors for console output const colors = { reset: '\x1b[0m', bright: '\x1b[1m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m' }; function log(message, color = 'reset') { console.log(`${colors[color]}${message}${colors.reset}`); } function logSection(title) { console.log(`\n${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}`); console.log(`${colors.cyan}${title}${colors.reset}`); console.log(`${colors.cyan}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${colors.reset}\n`); } async function checkBytecode(provider, address, label) { log(`Checking bytecode at ${label}: ${address}`, 'blue'); try { const code = await provider.getCode(address); if (code === "0x" || code === "0x0") { log(` ❌ No bytecode found`, 'red'); return false; } else { const codeLength = (code.length - 2) / 2; // Subtract "0x" and convert hex to bytes log(` ✅ Bytecode exists (${codeLength} bytes)`, 'green'); return true; } } catch (error) { log(` ❌ Error checking bytecode: ${error.message}`, 'red'); return false; } } async function verifyERC20(provider, address) { log(`Verifying ERC-20 compliance at: ${address}`, 'blue'); const contract = new ethers.Contract(address, ERC20_ABI, provider); const results = { symbol: null, decimals: null, totalSupply: null, valid: false }; try { // Check symbol() try { results.symbol = await contract.symbol(); log(` ✅ symbol() = ${results.symbol}`, 'green'); } catch (error) { log(` ❌ symbol() failed: ${error.message}`, 'red'); } // Check decimals() try { results.decimals = await contract.decimals(); const expected = 18; if (results.decimals === expected) { log(` ✅ decimals() = ${results.decimals} (expected: ${expected})`, 'green'); } else { log(` ⚠ decimals() = ${results.decimals} (expected: ${expected})`, 'yellow'); } } catch (error) { log(` ❌ decimals() failed: ${error.message}`, 'red'); } // Check totalSupply() try { results.totalSupply = await contract.totalSupply(); const formatted = ethers.formatEther(results.totalSupply); log(` ✅ totalSupply() = ${formatted} WETH`, 'green'); } catch (error) { log(` ❌ totalSupply() failed: ${error.message}`, 'red'); } // Determine validity if (results.symbol && results.decimals !== null && results.totalSupply !== null) { results.valid = true; log(` ✅ Contract behaves as valid ERC-20`, 'green'); } else { log(` ⚠ Some ERC-20 functions failed`, 'yellow'); } } catch (error) { log(` ❌ ERC-20 verification error: ${error.message}`, 'red'); } return results; } async function checkThirdwebBridgeRoute(fromChainId, toChainId, fromToken, toToken) { logSection("Step 3: Checking thirdweb Bridge Route"); log(`Checking route: ChainID ${fromChainId} (WETH) → ChainID ${toChainId} (USDT)`, 'blue'); log(`WETH Address: ${fromToken}`, 'blue'); log(`USDT Address: ${toToken}`, 'blue'); log(``, 'blue'); try { // Initialize thirdweb SDK log(`Initializing thirdweb SDK...`, 'blue'); const sdk = new ThirdwebSDK(new ethers.JsonRpcProvider(CHAIN138_RPC), { chainId: fromChainId }); // Try to get bridge quote // Note: thirdweb Bridge API may not be directly accessible via SDK // This is a placeholder - actual implementation may vary log(`Attempting to get bridge quote...`, 'blue'); log(`⚠ Note: thirdweb Bridge may require specific setup or may not support ChainID 138`, 'yellow'); // For now, we'll indicate that manual verification is needed log(`⚠ Bridge route verification requires manual testing with thirdweb Bridge UI or API`, 'yellow'); log(` Recommendation: Use thirdweb Bridge dashboard or contact thirdweb support`, 'blue'); return { available: false, reason: "Manual verification required - thirdweb Bridge API access needed" }; } catch (error) { log(`❌ Error checking bridge route: ${error.message}`, 'red'); return { available: false, reason: error.message }; } } async function main() { logSection("WETH → USDT Bridge Verification (ChainID 138 → Ethereum Mainnet)"); log(`IMPORTANT: WETH9 is NOT at canonical address on ChainID 138`, 'yellow'); log(`Canonical address (mainnet): ${WETH_CANONICAL}`, 'blue'); log(`Actual deployed address (ChainID 138): ${WETH_CHAIN138}`, 'blue'); log(`Reason: Cannot recreate CREATE-deployed contracts with CREATE2`, 'blue'); log(``, 'blue'); // Initialize provider const provider = new ethers.JsonRpcProvider(CHAIN138_RPC); // Step 1: Check bytecode logSection("Step 1: Checking Bytecode"); const canonicalHasBytecode = await checkBytecode(provider, WETH_CANONICAL, "Canonical Address"); log(``, 'blue'); const actualHasBytecode = await checkBytecode(provider, WETH_CHAIN138, "Actual Deployed Address"); if (!canonicalHasBytecode) { log(`⚠ No bytecode at canonical address (expected - cannot recreate CREATE-deployed contracts)`, 'yellow'); } // Step 2: Verify ERC-20 logSection("Step 2: Verifying ERC-20 Compliance"); let erc20Valid = false; if (actualHasBytecode) { const erc20Results = await verifyERC20(provider, WETH_CHAIN138); erc20Valid = erc20Results.valid; } else { log(`⚠ Skipping ERC-20 verification (no bytecode found)`, 'yellow'); } // Step 3: Check bridge route const bridgeRoute = await checkThirdwebBridgeRoute( CHAIN138_ID, ETHEREUM_MAINNET_ID, WETH_CHAIN138, USDT_MAINNET ); // Final verdict logSection("Final Verdict: GO / NO-GO"); log(`Verification Summary:`, 'blue'); log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'blue'); log(``, 'blue'); if (actualHasBytecode) { log(`✓ Bytecode exists at actual WETH address on ChainID 138`, 'green'); log(` Address: ${WETH_CHAIN138}`, 'blue'); } else { log(`✗ No bytecode at actual WETH address on ChainID 138`, 'red'); } if (erc20Valid) { log(`✓ Contract behaves as valid ERC-20`, 'green'); } else { log(`⚠ ERC-20 verification incomplete or failed`, 'yellow'); } if (bridgeRoute.available) { log(`✓ Valid route available via thirdweb Bridge`, 'green'); } else { log(`⚠ Route availability inconclusive: ${bridgeRoute.reason}`, 'yellow'); } log(``, 'blue'); log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'blue'); log(``, 'blue'); // Determine GO/NO-GO if (actualHasBytecode && erc20Valid && bridgeRoute.available) { log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'green'); log(`✅ GO: Bridge route is viable`, 'green'); log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'green'); process.exit(0); } else if (actualHasBytecode && erc20Valid) { log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'yellow'); log(`⚠ CONDITIONAL GO: Contract valid but route unverified`, 'yellow'); log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'yellow'); log(``, 'blue'); log(`Contract verification passed, but bridge route needs manual verification.`, 'blue'); log(`CRITICAL: WETH is NOT at canonical address on ChainID 138`, 'yellow'); log(` • Canonical: ${WETH_CANONICAL} (no bytecode)`, 'blue'); log(` • Actual: ${WETH_CHAIN138} (has bytecode)`, 'blue'); log(``, 'blue'); log(`Recommendation: Use CCIP Bridge instead (supports ChainID 138)`, 'blue'); log(` CCIPWETH9Bridge: 0x89dd12025bfCD38A168455A44B400e913ED33BE2`, 'blue'); process.exit(1); } else { log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'red'); log(`❌ NO-GO: Bridge route is NOT viable`, 'red'); log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`, 'red'); log(``, 'blue'); log(`Recommendation: Use CCIP Bridge or fix contract deployment issues`, 'blue'); process.exit(2); } } // Run verification main().catch(error => { log(`Fatal error: ${error.message}`, 'red'); console.error(error); process.exit(1); });