#!/usr/bin/env python3 """ Ethereum Mainnet contract verification helper. - Checks verification status via Etherscan API V2. - Optionally submits Standard JSON Input bundles when a source bundle path is configured locally. - Can be used as a status-only inventory check for historical deployments whose source bundle is documented separately. """ import json import sys import time import urllib.parse import urllib.request from pathlib import Path # Colors for terminal output class Colors: RED = '\033[0;31m' GREEN = '\033[0;32m' YELLOW = '\033[1;33m' BLUE = '\033[0;34m' CYAN = '\033[0;36m' NC = '\033[0m' # No Color def log_info(msg): print(f"{Colors.BLUE}[INFO]{Colors.NC} {msg}") def log_success(msg): print(f"{Colors.GREEN}[✓]{Colors.NC} {msg}") def log_warn(msg): print(f"{Colors.YELLOW}[⚠]{Colors.NC} {msg}") def log_error(msg): print(f"{Colors.RED}[✗]{Colors.NC} {msg}") def log_step(msg): print(f"{Colors.CYAN}[STEP]{Colors.NC} {msg}") # Contract configuration CONTRACTS = { "0x89dd12025bfCD38A168455A44B400e913ED33BE2": { "name": "CCIPWETH9Bridge", "constructor_args": "0x00000000000000000000000080226fc0ee2b096224eeac085bb9a8cba1146f7d000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca", "standard_json_file": "docs/CCIPWETH9Bridge_standard_json.json" }, "0xa9F284eD010f4F7d7F8F201742b49b9f58e29b84": { "name": "DODOPMMIntegration", "verification_note": "docs/03-deployment/ETHEREUM_MAINNET_DODOPMMINTEGRATION_VERIFICATION.md" } } API_URL = "https://api.etherscan.io/v2/api" CHAIN_ID = "1" def load_env_file(env_path): """Load environment variables from .env file""" env_vars = {} try: with open(env_path, 'r') as f: for line in f: line = line.strip() if line and not line.startswith('#') and '=' in line: key, value = line.split('=', 1) env_vars[key.strip()] = value.strip().strip('"').strip("'") except FileNotFoundError: log_error(f".env file not found: {env_path}") sys.exit(1) return env_vars def check_verification_status(address, api_key): """Check if contract is already verified on Etherscan""" url = ( f"{API_URL}?chainid={CHAIN_ID}&module=contract&action=getsourcecode" f"&address={address}&apikey={api_key}" ) try: with urllib.request.urlopen(url) as response: data = json.loads(response.read().decode()) if data.get('status') == '1' and data.get('result'): result = data['result'][0] source_code = result.get('SourceCode', '') # Check if source code exists (verified) if source_code and source_code not in ['', '{{']: return True return False except Exception as e: log_warn(f"Could not check verification status: {e}") return False def check_submission_status(guid, api_key): """Check the status of a submitted verification job.""" url = ( f"{API_URL}?chainid={CHAIN_ID}&module=contract&action=checkverifystatus" f"&guid={guid}&apikey={api_key}" ) with urllib.request.urlopen(url) as response: data = json.loads(response.read().decode()) return data.get('status'), data.get('result', '') def verify_contract(address, contract_info, api_key): """Verify contract using Standard JSON Input via Etherscan API""" contract_name = contract_info['name'] constructor_args = contract_info.get('constructor_args', '') standard_json_file = contract_info.get('standard_json_file') if not standard_json_file: log_warn("No Standard JSON bundle configured for automated submission.") note = contract_info.get('verification_note') if note: log_info(f"See verification note: {note}") return None # Load Standard JSON file project_root = Path(__file__).parent.parent json_path = project_root / standard_json_file if not json_path.exists(): log_error(f"Standard JSON file not found: {json_path}") return False log_step(f"Loading Standard JSON from {json_path}") try: with open(json_path, 'r') as f: standard_json = json.load(f) except Exception as e: log_error(f"Failed to load Standard JSON: {e}") return False # Convert to compact JSON string json_str = json.dumps(standard_json, separators=(',', ':')) params = { 'chainid': CHAIN_ID, 'module': 'contract', 'action': 'verifysourcecode', 'apikey': api_key, 'contractaddress': address, 'codeformat': 'solidity-standard-json-input', 'contractname': contract_name, 'compilerversion': 'v0.8.20+commit.a1b79de6', 'optimizationUsed': '1', 'runs': '200', 'constructorArguments': constructor_args, 'sourceCode': json_str } # Encode parameters data = urllib.parse.urlencode(params).encode('utf-8') log_step("Submitting verification request to Etherscan...") try: req = urllib.request.Request(API_URL, data=data) with urllib.request.urlopen(req, timeout=30) as response: result = json.loads(response.read().decode()) if result.get('status') == '1': guid = result.get('result', '') if guid and guid != 'null': log_success(f"Verification submitted successfully!") log_info(f"GUID: {guid}") log_info(f"Check status: https://etherscan.io/address/{address}#code") return guid else: log_error(f"Verification failed: {result.get('result', 'Unknown error')}") return None else: error_msg = result.get('result', 'Unknown error') log_error(f"Verification failed: {error_msg}") return None except urllib.error.HTTPError as e: log_error(f"HTTP Error: {e}") try: error_body = e.read().decode() log_info(f"Error details: {error_body}") except: pass return None except Exception as e: log_error(f"Request failed: {e}") return None def monitor_verification(address, guid, api_key, max_attempts=12): """Monitor verification status after submission""" log_info("Waiting for verification to complete...") for attempt in range(1, max_attempts + 1): time.sleep(5) try: status, result = check_submission_status(guid, api_key) except Exception as e: log_warn(f"Could not query verification job status: {e}") status, result = None, '' if status == '1': log_success("Contract is now verified!") return True if result: log_info(f"Attempt {attempt}/{max_attempts}: {result}") else: log_info(f"Attempt {attempt}/{max_attempts}: Still processing...") log_warn("Verification may still be processing. Check Etherscan manually.") return False def main(): log_info("=" * 50) log_info("Ethereum Mainnet Contract Verification") log_info("Using Standard JSON Input Method") log_info("=" * 50) log_info("") # Load environment variables project_root = Path(__file__).parent.parent source_project = project_root.parent / "smom-dbis-138" env_path = source_project / ".env" if not env_path.exists(): log_error(f".env file not found: {env_path}") sys.exit(1) env_vars = load_env_file(env_path) api_key = env_vars.get('ETHERSCAN_API_KEY') if not api_key: log_error("ETHERSCAN_API_KEY not found in .env file") sys.exit(1) # Process each contract verified_count = 0 already_verified_count = 0 failed_count = 0 for address, contract_info in CONTRACTS.items(): log_info("") log_info("=" * 50) log_info(f"Verifying: {contract_info['name']}") log_info(f"Address: {address}") log_info("=" * 50) log_info("") # Check if already verified log_step("Checking current verification status...") if check_verification_status(address, api_key): log_success("Contract is already verified on Etherscan!") log_info(f"View contract: https://etherscan.io/address/{address}#code") already_verified_count += 1 continue log_info("Contract is not yet verified") # Verify contract log_step("Submitting verification request...") guid = verify_contract(address, contract_info, api_key) if guid: log_info("") log_step("Monitoring verification status...") if monitor_verification(address, guid, api_key): verified_count += 1 else: log_warn("Verification submitted but status unclear. Check Etherscan manually.") elif contract_info.get('standard_json_file') or contract_info.get('verification_note'): failed_count += 1 else: failed_count += 1 log_info("") log_info("Manual verification steps:") log_info(f"1. Go to: https://etherscan.io/address/{address}#code") log_info("2. Click 'Contract' tab → 'Verify and Publish'") log_info("3. Select 'Standard JSON Input'") log_info(f"4. Upload: {project_root / contract_info['standard_json_file']}") log_info(f"5. Enter constructor args: {contract_info['constructor_args']}") log_info("6. Submit") # Summary log_info("") log_info("=" * 50) log_info("Verification Summary") log_info("=" * 50) log_info("") log_success(f"Already Verified: {already_verified_count}") log_success(f"Newly Verified: {verified_count}") log_error(f"Failed: {failed_count}") log_info("") total = already_verified_count + verified_count + failed_count log_info(f"Total Contracts: {total}") if failed_count == 0: log_success("All contracts processed successfully!") else: log_warn("Some contracts require manual verification.") if __name__ == "__main__": main()