Files
explorer-monorepo/frontend/public/index.html

1201 lines
48 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chain 138 Explorer | d-bis.org | Bridge Monitoring & WETH Utilities</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.ethers.io/lib/ethers-5.7.2.umd.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
:root {
--primary: #667eea;
--secondary: #764ba2;
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--bridge-blue: #3b82f6;
--dark: #1f2937;
--light: #f9fafb;
--border: #e5e7eb;
--text: #111827;
--text-light: #6b7280;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: var(--light);
color: var(--text);
line-height: 1.6;
}
.navbar {
background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
color: white;
padding: 1rem 2rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
position: sticky;
top: 0;
z-index: 1000;
}
.nav-container {
max-width: 1400px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: bold;
display: flex;
align-items: center;
gap: 0.5rem;
}
.nav-links {
display: flex;
gap: 2rem;
list-style: none;
}
.nav-links a {
color: white;
text-decoration: none;
transition: opacity 0.2s;
}
.nav-links a:hover { opacity: 0.8; }
.search-box {
flex: 1;
max-width: 600px;
margin: 0 2rem;
}
.search-input {
width: 100%;
padding: 0.75rem 1rem;
border: none;
border-radius: 8px;
font-size: 1rem;
background: rgba(255,255,255,0.2);
color: white;
backdrop-filter: blur(10px);
}
.search-input::placeholder { color: rgba(255,255,255,0.7); }
.search-input:focus {
outline: none;
background: rgba(255,255,255,0.3);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 2rem;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.stat-card {
background: white;
padding: 1.5rem;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}
.stat-card.bridge-card {
border-left: 4px solid var(--bridge-blue);
}
.stat-label {
color: var(--text-light);
font-size: 0.875rem;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 0.5rem;
}
.stat-value {
font-size: 2rem;
font-weight: bold;
color: var(--primary);
}
.stat-value.bridge-value {
color: var(--bridge-blue);
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
padding: 2rem;
margin-bottom: 2rem;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
padding-bottom: 1rem;
border-bottom: 2px solid var(--border);
}
.card-title {
font-size: 1.5rem;
font-weight: bold;
color: var(--text);
}
.tabs {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
border-bottom: 2px solid var(--border);
flex-wrap: wrap;
}
.tab {
padding: 1rem 1.5rem;
background: none;
border: none;
cursor: pointer;
font-size: 1rem;
color: var(--text-light);
border-bottom: 3px solid transparent;
transition: all 0.2s;
}
.tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
font-weight: 600;
}
.bridge-tab.active {
color: var(--bridge-blue);
border-bottom-color: var(--bridge-blue);
}
.weth-tab.active {
color: var(--success);
border-bottom-color: var(--success);
}
.table {
width: 100%;
border-collapse: collapse;
}
.table th {
text-align: left;
padding: 1rem;
background: var(--light);
font-weight: 600;
color: var(--text);
border-bottom: 2px solid var(--border);
}
.table td {
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.table tr:hover { background: var(--light); }
.hash {
font-family: 'Courier New', monospace;
font-size: 0.875rem;
color: var(--primary);
word-break: break-all;
}
.hash:hover { text-decoration: underline; cursor: pointer; }
.badge {
display: inline-block;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 600;
}
.badge-success { background: #d1fae5; color: var(--success); }
.badge-warning { background: #fef3c7; color: var(--warning); }
.badge-danger { background: #fee2e2; color: var(--danger); }
.badge-chain {
background: #dbeafe;
color: var(--bridge-blue);
}
.loading {
text-align: center;
padding: 3rem;
color: var(--text-light);
}
.loading i {
font-size: 2rem;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.error {
background: #fee2e2;
color: var(--danger);
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
.success {
background: #d1fae5;
color: var(--success);
padding: 1rem;
border-radius: 8px;
margin: 1rem 0;
}
.bridge-chain-card {
background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 1rem;
}
.weth-card {
background: linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%);
padding: 1.5rem;
border-radius: 12px;
margin-bottom: 1.5rem;
}
.weth-form {
background: white;
padding: 1.5rem;
border-radius: 8px;
margin-top: 1rem;
}
.form-group {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
color: var(--text);
}
.form-input {
width: 100%;
padding: 0.75rem;
border: 2px solid var(--border);
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s;
}
.form-input:focus {
outline: none;
border-color: var(--primary);
}
.form-input-group {
display: flex;
gap: 0.5rem;
}
.form-input-group .form-input {
flex: 1;
}
.btn {
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.btn-primary {
background: var(--primary);
color: white;
}
.btn-primary:hover { background: var(--secondary); }
.btn-bridge {
background: var(--bridge-blue);
color: white;
}
.btn-bridge:hover { background: #2563eb; }
.btn-success {
background: var(--success);
color: white;
}
.btn-success:hover { background: #059669; }
.btn-warning {
background: var(--warning);
color: white;
}
.btn-warning:hover { background: #d97706; }
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.balance-display {
background: var(--light);
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
}
.balance-row {
display: flex;
justify-content: space-between;
padding: 0.5rem 0;
border-bottom: 1px solid var(--border);
}
.balance-row:last-child {
border-bottom: none;
}
.balance-label {
color: var(--text-light);
}
.balance-value {
font-weight: bold;
color: var(--text);
}
.chain-name {
font-size: 1.25rem;
font-weight: bold;
color: var(--bridge-blue);
margin-bottom: 0.5rem;
}
.chain-info {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.chain-stat {
font-size: 0.875rem;
}
.chain-stat-label {
color: var(--text-light);
}
.chain-stat-value {
font-weight: bold;
color: var(--text);
margin-top: 0.25rem;
}
.detail-view {
display: none;
}
.detail-view.active { display: block; }
.info-row {
display: flex;
padding: 1rem;
border-bottom: 1px solid var(--border);
}
.info-label {
font-weight: 600;
min-width: 200px;
color: var(--text-light);
}
.info-value {
flex: 1;
word-break: break-all;
}
.metamask-status {
padding: 1rem;
border-radius: 8px;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.metamask-status.connected {
background: #d1fae5;
color: var(--success);
}
.metamask-status.disconnected {
background: #fee2e2;
color: var(--danger);
}
@media (max-width: 768px) {
.nav-container { flex-direction: column; gap: 1rem; }
.search-box { max-width: 100%; margin: 0; }
.nav-links { flex-wrap: wrap; justify-content: center; }
}
</style>
</head>
<body>
<nav class="navbar">
<div class="nav-container">
<div class="logo">
<i class="fas fa-cube"></i>
<span>Chain 138 Explorer</span>
</div>
<div class="search-box">
<input type="text" class="search-input" id="searchInput" placeholder="Search by address, transaction hash, or block number...">
</div>
<ul class="nav-links">
<li><a href="#" onclick="showHome(); return false;"><i class="fas fa-home"></i> Home</a></li>
<li><a href="#" onclick="showBlocks(); return false;"><i class="fas fa-cubes"></i> Blocks</a></li>
<li><a href="#" onclick="showTransactions(); return false;"><i class="fas fa-exchange-alt"></i> Transactions</a></li>
<li><a href="#" onclick="showBridgeMonitoring(); return false;"><i class="fas fa-bridge"></i> Bridge</a></li>
<li><a href="#" onclick="showWETHUtilities(); return false;"><i class="fas fa-coins"></i> WETH</a></li>
</ul>
</div>
</nav>
<div class="container" id="mainContent">
<!-- Home View -->
<div id="homeView">
<div class="stats-grid" id="statsGrid">
<!-- Stats loaded dynamically -->
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Latest Blocks</h2>
<button class="btn btn-primary" onclick="showBlocks()">View All</button>
</div>
<div id="latestBlocks">
<div class="loading"><i class="fas fa-spinner"></i> Loading blocks...</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h2 class="card-title">Latest Transactions</h2>
<button class="btn btn-primary" onclick="showTransactions()">View All</button>
</div>
<div id="latestTransactions">
<div class="loading"><i class="fas fa-spinner"></i> Loading transactions...</div>
</div>
</div>
</div>
<!-- WETH Utilities View -->
<div id="wethView" class="detail-view">
<div class="card">
<div class="card-header">
<h2 class="card-title"><i class="fas fa-coins"></i> WETH9 & WETH10 Utilities</h2>
<button class="btn btn-success" onclick="refreshWETHBalances()"><i class="fas fa-sync-alt"></i> Refresh</button>
</div>
<!-- MetaMask Connection Status -->
<div id="metamaskStatus" class="metamask-status disconnected">
<i class="fas fa-wallet"></i>
<span>MetaMask not connected</span>
<button class="btn btn-success" onclick="connectMetaMask()" style="margin-left: auto;">Connect MetaMask</button>
</div>
<div class="tabs">
<button class="tab weth-tab active" onclick="showWETHTab('weth9')">WETH9</button>
<button class="tab weth-tab" onclick="showWETHTab('weth10')">WETH10</button>
<button class="tab weth-tab" onclick="showWETHTab('info')">Information</button>
</div>
<!-- WETH9 Tab -->
<div id="weth9Tab" class="weth-tab-content">
<div class="weth-card">
<div class="chain-name">WETH9 Token</div>
<div style="color: var(--text-light); margin-bottom: 1rem;">
Contract: <span class="hash" onclick="showAddressDetail('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2')" style="cursor: pointer;">0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2</span>
</div>
<div class="balance-display" id="weth9Balance">
<div class="balance-row">
<span class="balance-label">ETH Balance:</span>
<span class="balance-value" id="weth9EthBalance">-</span>
</div>
<div class="balance-row">
<span class="balance-label">WETH9 Balance:</span>
<span class="balance-value" id="weth9TokenBalance">-</span>
</div>
</div>
<div class="weth-form">
<h3 style="margin-bottom: 1rem;">Wrap ETH → WETH9</h3>
<div class="form-group">
<label class="form-label">Amount (ETH)</label>
<div class="form-input-group">
<input type="number" class="form-input" id="weth9WrapAmount" placeholder="0.0" step="0.000001" min="0">
<button class="btn btn-primary" onclick="setMaxWETH9('wrap')">MAX</button>
</div>
</div>
<button class="btn btn-success" onclick="wrapWETH9()" id="weth9WrapBtn" disabled>
<i class="fas fa-arrow-right"></i> Wrap ETH to WETH9
</button>
</div>
<div class="weth-form">
<h3 style="margin-bottom: 1rem;">Unwrap WETH9 → ETH</h3>
<div class="form-group">
<label class="form-label">Amount (WETH9)</label>
<div class="form-input-group">
<input type="number" class="form-input" id="weth9UnwrapAmount" placeholder="0.0" step="0.000001" min="0">
<button class="btn btn-primary" onclick="setMaxWETH9('unwrap')">MAX</button>
</div>
</div>
<button class="btn btn-warning" onclick="unwrapWETH9()" id="weth9UnwrapBtn" disabled>
<i class="fas fa-arrow-left"></i> Unwrap WETH9 to ETH
</button>
</div>
</div>
</div>
<!-- WETH10 Tab -->
<div id="weth10Tab" class="weth-tab-content" style="display: none;">
<div class="weth-card">
<div class="chain-name">WETH10 Token</div>
<div style="color: var(--text-light); margin-bottom: 1rem;">
Contract: <span class="hash" onclick="showAddressDetail('0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f')" style="cursor: pointer;">0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f</span>
</div>
<div class="balance-display" id="weth10Balance">
<div class="balance-row">
<span class="balance-label">ETH Balance:</span>
<span class="balance-value" id="weth10EthBalance">-</span>
</div>
<div class="balance-row">
<span class="balance-label">WETH10 Balance:</span>
<span class="balance-value" id="weth10TokenBalance">-</span>
</div>
</div>
<div class="weth-form">
<h3 style="margin-bottom: 1rem;">Wrap ETH → WETH10</h3>
<div class="form-group">
<label class="form-label">Amount (ETH)</label>
<div class="form-input-group">
<input type="number" class="form-input" id="weth10WrapAmount" placeholder="0.0" step="0.000001" min="0">
<button class="btn btn-primary" onclick="setMaxWETH10('wrap')">MAX</button>
</div>
</div>
<button class="btn btn-success" onclick="wrapWETH10()" id="weth10WrapBtn" disabled>
<i class="fas fa-arrow-right"></i> Wrap ETH to WETH10
</button>
</div>
<div class="weth-form">
<h3 style="margin-bottom: 1rem;">Unwrap WETH10 → ETH</h3>
<div class="form-group">
<label class="form-label">Amount (WETH10)</label>
<div class="form-input-group">
<input type="number" class="form-input" id="weth10UnwrapAmount" placeholder="0.0" step="0.000001" min="0">
<button class="btn btn-primary" onclick="setMaxWETH10('unwrap')">MAX</button>
</div>
</div>
<button class="btn btn-warning" onclick="unwrapWETH10()" id="weth10UnwrapBtn" disabled>
<i class="fas fa-arrow-left"></i> Unwrap WETH10 to ETH
</button>
</div>
</div>
</div>
<!-- Information Tab -->
<div id="wethInfoTab" class="weth-tab-content" style="display: none;">
<div class="card">
<h3>About WETH9 and WETH10</h3>
<div style="margin-top: 1rem; line-height: 1.8;">
<p><strong>WETH9</strong> and <strong>WETH10</strong> are wrapped versions of native ETH (Ether) that allow you to use ETH in smart contracts and DeFi protocols.</p>
<h4 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">What is Wrapping?</h4>
<p>Wrapping ETH converts your native ETH into an ERC-20 token (WETH9 or WETH10) that can be used in DeFi applications, smart contracts, and cross-chain bridging.</p>
<h4 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">Contract Addresses</h4>
<ul style="margin-left: 2rem; margin-top: 0.5rem;">
<li><strong>WETH9:</strong> <span class="hash">0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2</span></li>
<li><strong>WETH10:</strong> <span class="hash">0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f</span></li>
</ul>
<h4 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">How to Use</h4>
<ol style="margin-left: 2rem; margin-top: 0.5rem;">
<li>Connect your MetaMask wallet</li>
<li>Select WETH9 or WETH10 tab</li>
<li>Enter the amount to wrap or unwrap</li>
<li>Confirm the transaction in MetaMask</li>
</ol>
<h4 style="margin-top: 1.5rem; margin-bottom: 0.5rem;">Cross-Chain Bridging</h4>
<p>Both WETH9 and WETH10 can be bridged to other chains using the CCIP bridge contracts:</p>
<ul style="margin-left: 2rem; margin-top: 0.5rem;">
<li><strong>WETH9 Bridge:</strong> <span class="hash">0x89dd12025bfCD38A168455A44B400e913ED33BE2</span></li>
<li><strong>WETH10 Bridge:</strong> <span class="hash">0xe0E93247376aa097dB308B92e6Ba36bA015535D0</span></li>
</ul>
</div>
</div>
</div>
</div>
</div>
<!-- Bridge Monitoring View and other views (keep existing code) -->
<div id="bridgeView" class="detail-view">
<!-- Bridge monitoring content (from previous implementation) -->
</div>
<!-- Other views -->
<div id="blocksView" class="detail-view">
<div class="card">
<div class="card-header">
<h2 class="card-title">All Blocks</h2>
</div>
<div id="blocksList">
<div class="loading"><i class="fas fa-spinner"></i> Loading blocks...</div>
</div>
</div>
</div>
<div id="transactionsView" class="detail-view">
<div class="card">
<div class="card-header">
<h2 class="card-title">All Transactions</h2>
</div>
<div id="transactionsList">
<div class="loading"><i class="fas fa-spinner"></i> Loading transactions...</div>
</div>
</div>
</div>
<div id="blockDetailView" class="detail-view">
<div class="card">
<div class="card-header">
<button class="btn btn-secondary" onclick="showBlocks()"><i class="fas fa-arrow-left"></i> Back</button>
<h2 class="card-title">Block Details</h2>
</div>
<div id="blockDetail"></div>
</div>
</div>
<div id="transactionDetailView" class="detail-view">
<div class="card">
<div class="card-header">
<button class="btn btn-secondary" onclick="showTransactions()"><i class="fas fa-arrow-left"></i> Back</button>
<h2 class="card-title">Transaction Details</h2>
</div>
<div id="transactionDetail"></div>
</div>
</div>
<div id="addressDetailView" class="detail-view">
<div class="card">
<div class="card-header">
<button class="btn btn-secondary" onclick="showHome()"><i class="fas fa-arrow-left"></i> Back</button>
<h2 class="card-title">Address Details</h2>
</div>
<div id="addressDetail"></div>
</div>
</div>
</div>
<script>
const API_BASE = '/api';
const RPC_URL = 'https://rpc-core.d-bis.org'; // Chain 138 RPC
let currentView = 'home';
let provider = null;
let signer = null;
let userAddress = null;
// WETH Contract Addresses
const WETH9_ADDRESS = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const WETH10_ADDRESS = '0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f';
// WETH ABI (Standard ERC-20 + WETH functions)
const WETH_ABI = [
"function deposit() payable",
"function withdraw(uint256 wad)",
"function balanceOf(address account) view returns (uint256)",
"function transfer(address to, uint256 amount) returns (bool)",
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
"function totalSupply() view returns (uint256)",
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"event Deposit(address indexed dst, uint256 wad)",
"event Withdrawal(address indexed src, uint256 wad)"
];
// Initialize
document.addEventListener('DOMContentLoaded', () => {
checkMetaMaskConnection();
loadStats();
loadLatestBlocks();
});
// MetaMask Connection
async function checkMetaMaskConnection() {
if (typeof window.ethereum !== 'undefined') {
try {
const accounts = await window.ethereum.request({ method: 'eth_accounts' });
if (accounts.length > 0) {
await connectMetaMask();
}
} catch (error) {
console.error('Error checking MetaMask:', error);
}
}
}
async function connectMetaMask() {
if (typeof window.ethereum === 'undefined') {
alert('MetaMask is not installed! Please install MetaMask to use WETH utilities.');
return;
}
try {
// Request account access
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
userAddress = accounts[0];
// Connect to Chain 138
await switchToChain138();
// Setup provider and signer
provider = new ethers.providers.Web3Provider(window.ethereum);
signer = provider.getSigner();
// Update UI
const statusEl = document.getElementById('metamaskStatus');
statusEl.className = 'metamask-status connected';
statusEl.innerHTML = `
<i class="fas fa-check-circle"></i>
<span>Connected: ${shortenHash(userAddress)}</span>
<button class="btn btn-warning" onclick="disconnectMetaMask()" style="margin-left: auto;">Disconnect</button>
`;
// Enable buttons
document.getElementById('weth9WrapBtn').disabled = false;
document.getElementById('weth9UnwrapBtn').disabled = false;
document.getElementById('weth10WrapBtn').disabled = false;
document.getElementById('weth10UnwrapBtn').disabled = false;
// Load balances
await refreshWETHBalances();
// Listen for account changes
window.ethereum.on('accountsChanged', (accounts) => {
if (accounts.length === 0) {
disconnectMetaMask();
} else {
connectMetaMask();
}
});
// Listen for chain changes
window.ethereum.on('chainChanged', () => {
switchToChain138();
});
} catch (error) {
console.error('Error connecting MetaMask:', error);
alert('Failed to connect MetaMask: ' + error.message);
}
}
async function switchToChain138() {
const chainId = '0x8A'; // 138 in hex
try {
await window.ethereum.request({
method: 'wallet_switchEthereumChain',
params: [{ chainId }],
});
} catch (switchError) {
// If chain doesn't exist, add it
if (switchError.code === 4902) {
try {
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId,
chainName: 'Chain 138',
nativeCurrency: {
name: 'ETH',
symbol: 'ETH',
decimals: 18
},
rpcUrls: [RPC_URL],
blockExplorerUrls: ['https://explorer.d-bis.org']
}],
});
} catch (addError) {
console.error('Error adding chain:', addError);
throw addError;
}
} else {
throw switchError;
}
}
}
function disconnectMetaMask() {
provider = null;
signer = null;
userAddress = null;
const statusEl = document.getElementById('metamaskStatus');
statusEl.className = 'metamask-status disconnected';
statusEl.innerHTML = `
<i class="fas fa-wallet"></i>
<span>MetaMask not connected</span>
<button class="btn btn-success" onclick="connectMetaMask()" style="margin-left: auto;">Connect MetaMask</button>
`;
document.getElementById('weth9WrapBtn').disabled = true;
document.getElementById('weth9UnwrapBtn').disabled = true;
document.getElementById('weth10WrapBtn').disabled = true;
document.getElementById('weth10UnwrapBtn').disabled = true;
}
async function refreshWETHBalances() {
if (!userAddress) return;
try {
// Get ETH balance
const ethBalance = await provider.getBalance(userAddress);
const ethBalanceFormatted = formatEther(ethBalance);
// Get WETH9 balance
const weth9Contract = new ethers.Contract(WETH9_ADDRESS, WETH_ABI, provider);
const weth9Balance = await weth9Contract.balanceOf(userAddress);
const weth9BalanceFormatted = formatEther(weth9Balance);
// Get WETH10 balance
const weth10Contract = new ethers.Contract(WETH10_ADDRESS, WETH_ABI, provider);
const weth10Balance = await weth10Contract.balanceOf(userAddress);
const weth10BalanceFormatted = formatEther(weth10Balance);
// Update UI
document.getElementById('weth9EthBalance').textContent = ethBalanceFormatted + ' ETH';
document.getElementById('weth9TokenBalance').textContent = weth9BalanceFormatted + ' WETH9';
document.getElementById('weth10EthBalance').textContent = ethBalanceFormatted + ' ETH';
document.getElementById('weth10TokenBalance').textContent = weth10BalanceFormatted + ' WETH10';
} catch (error) {
console.error('Error refreshing balances:', error);
}
}
function setMaxWETH9(type) {
if (type === 'wrap') {
const ethBalance = document.getElementById('weth9EthBalance').textContent.replace(' ETH', '');
document.getElementById('weth9WrapAmount').value = parseFloat(ethBalance).toFixed(6);
} else {
const wethBalance = document.getElementById('weth9TokenBalance').textContent.replace(' WETH9', '');
document.getElementById('weth9UnwrapAmount').value = parseFloat(wethBalance).toFixed(6);
}
}
function setMaxWETH10(type) {
if (type === 'wrap') {
const ethBalance = document.getElementById('weth10EthBalance').textContent.replace(' ETH', '');
document.getElementById('weth10WrapAmount').value = parseFloat(ethBalance).toFixed(6);
} else {
const wethBalance = document.getElementById('weth10TokenBalance').textContent.replace(' WETH10', '');
document.getElementById('weth10UnwrapAmount').value = parseFloat(wethBalance).toFixed(6);
}
}
async function wrapWETH9() {
const amount = document.getElementById('weth9WrapAmount').value;
if (!amount || parseFloat(amount) <= 0) {
alert('Please enter a valid amount');
return;
}
if (!signer) {
alert('Please connect MetaMask first');
return;
}
try {
const weth9Contract = new ethers.Contract(WETH9_ADDRESS, WETH_ABI, signer);
const amountWei = ethers.utils.parseEther(amount);
const btn = document.getElementById('weth9WrapBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
const tx = await weth9Contract.deposit({ value: amountWei });
const receipt = await tx.wait();
btn.innerHTML = '<i class="fas fa-check"></i> Success!';
document.getElementById('weth9WrapAmount').value = '';
await refreshWETHBalances();
setTimeout(() => {
btn.innerHTML = '<i class="fas fa-arrow-right"></i> Wrap ETH to WETH9';
btn.disabled = false;
}, 3000);
} catch (error) {
console.error('Error wrapping WETH9:', error);
alert('Failed to wrap WETH9: ' + error.message);
document.getElementById('weth9WrapBtn').innerHTML = '<i class="fas fa-arrow-right"></i> Wrap ETH to WETH9';
document.getElementById('weth9WrapBtn').disabled = false;
}
}
async function unwrapWETH9() {
const amount = document.getElementById('weth9UnwrapAmount').value;
if (!amount || parseFloat(amount) <= 0) {
alert('Please enter a valid amount');
return;
}
if (!signer) {
alert('Please connect MetaMask first');
return;
}
try {
const weth9Contract = new ethers.Contract(WETH9_ADDRESS, WETH_ABI, signer);
const amountWei = ethers.utils.parseEther(amount);
const btn = document.getElementById('weth9UnwrapBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
const tx = await weth9Contract.withdraw(amountWei);
const receipt = await tx.wait();
btn.innerHTML = '<i class="fas fa-check"></i> Success!';
document.getElementById('weth9UnwrapAmount').value = '';
await refreshWETHBalances();
setTimeout(() => {
btn.innerHTML = '<i class="fas fa-arrow-left"></i> Unwrap WETH9 to ETH';
btn.disabled = false;
}, 3000);
} catch (error) {
console.error('Error unwrapping WETH9:', error);
alert('Failed to unwrap WETH9: ' + error.message);
document.getElementById('weth9UnwrapBtn').innerHTML = '<i class="fas fa-arrow-left"></i> Unwrap WETH9 to ETH';
document.getElementById('weth9UnwrapBtn').disabled = false;
}
}
async function wrapWETH10() {
const amount = document.getElementById('weth10WrapAmount').value;
if (!amount || parseFloat(amount) <= 0) {
alert('Please enter a valid amount');
return;
}
if (!signer) {
alert('Please connect MetaMask first');
return;
}
try {
const weth10Contract = new ethers.Contract(WETH10_ADDRESS, WETH_ABI, signer);
const amountWei = ethers.utils.parseEther(amount);
const btn = document.getElementById('weth10WrapBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
const tx = await weth10Contract.deposit({ value: amountWei });
const receipt = await tx.wait();
btn.innerHTML = '<i class="fas fa-check"></i> Success!';
document.getElementById('weth10WrapAmount').value = '';
await refreshWETHBalances();
setTimeout(() => {
btn.innerHTML = '<i class="fas fa-arrow-right"></i> Wrap ETH to WETH10';
btn.disabled = false;
}, 3000);
} catch (error) {
console.error('Error wrapping WETH10:', error);
alert('Failed to wrap WETH10: ' + error.message);
document.getElementById('weth10WrapBtn').innerHTML = '<i class="fas fa-arrow-right"></i> Wrap ETH to WETH10';
document.getElementById('weth10WrapBtn').disabled = false;
}
}
async function unwrapWETH10() {
const amount = document.getElementById('weth10UnwrapAmount').value;
if (!amount || parseFloat(amount) <= 0) {
alert('Please enter a valid amount');
return;
}
if (!signer) {
alert('Please connect MetaMask first');
return;
}
try {
const weth10Contract = new ethers.Contract(WETH10_ADDRESS, WETH_ABI, signer);
const amountWei = ethers.utils.parseEther(amount);
const btn = document.getElementById('weth10UnwrapBtn');
btn.disabled = true;
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Processing...';
const tx = await weth10Contract.withdraw(amountWei);
const receipt = await tx.wait();
btn.innerHTML = '<i class="fas fa-check"></i> Success!';
document.getElementById('weth10UnwrapAmount').value = '';
await refreshWETHBalances();
setTimeout(() => {
btn.innerHTML = '<i class="fas fa-arrow-left"></i> Unwrap WETH10 to ETH';
btn.disabled = false;
}, 3000);
} catch (error) {
console.error('Error unwrapping WETH10:', error);
alert('Failed to unwrap WETH10: ' + error.message);
document.getElementById('weth10UnwrapBtn').innerHTML = '<i class="fas fa-arrow-left"></i> Unwrap WETH10 to ETH';
document.getElementById('weth10UnwrapBtn').disabled = false;
}
}
function showWETHTab(tab) {
document.querySelectorAll('.weth-tab-content').forEach(el => el.style.display = 'none');
document.querySelectorAll('.weth-tab').forEach(el => el.classList.remove('active'));
document.getElementById(`${tab}Tab`).style.display = 'block';
event.target.classList.add('active');
}
function showWETHUtilities() {
showView('weth');
if (userAddress) {
refreshWETHBalances();
}
}
function showBridgeMonitoring() {
showView('bridge');
}
function showHome() {
showView('home');
loadStats();
loadLatestBlocks();
}
function showBlocks() {
showView('blocks');
}
function showTransactions() {
showView('transactions');
}
function showView(viewName) {
currentView = viewName;
document.querySelectorAll('.detail-view').forEach(v => v.classList.remove('active'));
document.getElementById('homeView').style.display = viewName === 'home' ? 'block' : 'none';
if (viewName !== 'home') {
document.getElementById(`${viewName}View`).classList.add('active');
}
}
async function fetchAPI(url) {
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return await response.json();
} catch (error) {
console.error('API Error:', error);
throw error;
}
}
async function loadStats() {
try {
const stats = await fetchAPI(`${API_BASE}/v2/stats`);
const statsGrid = document.getElementById('statsGrid');
if (statsGrid) {
statsGrid.innerHTML = `
<div class="stat-card">
<div class="stat-label">Total Blocks</div>
<div class="stat-value">${formatNumber(stats.total_blocks)}</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Transactions</div>
<div class="stat-value">${formatNumber(stats.total_transactions)}</div>
</div>
<div class="stat-card">
<div class="stat-label">Total Addresses</div>
<div class="stat-value">${formatNumber(stats.total_addresses)}</div>
</div>
<div class="stat-card bridge-card">
<div class="stat-label">Bridge Contracts</div>
<div class="stat-value bridge-value">2 Active</div>
</div>
`;
}
} catch (error) {
console.error('Failed to load stats:', error);
}
}
async function loadLatestBlocks() {
const container = document.getElementById('latestBlocks');
if (!container) return;
try {
const blockData = await fetchAPI(`${API_BASE}?module=block&action=eth_block_number`);
const latestBlock = parseInt(blockData.result, 16);
let html = '<table class="table"><thead><tr><th>Block</th><th>Hash</th><th>Transactions</th><th>Timestamp</th></tr></thead><tbody>';
for (let i = 0; i < 10 && latestBlock - i >= 0; i++) {
const blockNum = latestBlock - i;
try {
const block = await fetchAPI(`${API_BASE}?module=block&action=eth_get_block_by_number&tag=0x${blockNum.toString(16)}&boolean=false`);
if (block.result) {
const timestamp = new Date(parseInt(block.result.timestamp, 16) * 1000).toLocaleString();
const txCount = block.result.transactions.length;
html += `<tr onclick="showBlockDetail('${blockNum}')" style="cursor: pointer;">
<td>${blockNum}</td>
<td class="hash">${shortenHash(block.result.hash)}</td>
<td>${txCount}</td>
<td>${timestamp}</td>
</tr>`;
}
} catch (e) {}
}
html += '</tbody></table>';
container.innerHTML = html;
} catch (error) {
container.innerHTML = `<div class="error">Failed to load blocks: ${error.message}</div>`;
}
}
function showBlockDetail(blockNumber) {
alert(`Block ${blockNumber} detail view - to be implemented`);
}
function showAddressDetail(address) {
showView('addressDetail');
}
function handleSearch(query) {
query = query.trim();
if (!query) return;
if (/^0x[a-fA-F0-9]{40}$/.test(query)) {
showAddressDetail(query);
} else if (/^0x[a-fA-F0-9]{64}$/.test(query)) {
alert(`Transaction ${query} - to be implemented`);
} else if (/^\d+$/.test(query)) {
showBlockDetail(query);
} else {
alert('Invalid search. Enter an address, transaction hash, or block number.');
}
}
function formatNumber(num) {
return parseInt(num || 0).toLocaleString();
}
function shortenHash(hash, length = 10) {
if (!hash || hash.length <= length * 2 + 2) return hash;
return hash.substring(0, length + 2) + '...' + hash.substring(hash.length - length);
}
function formatEther(wei, unit = 'ether') {
if (typeof wei === 'string' && wei.startsWith('0x')) {
wei = BigInt(wei);
}
const weiNum = typeof wei === 'bigint' ? Number(wei) : parseFloat(wei);
const ether = weiNum / Math.pow(10, unit === 'gwei' ? 9 : 18);
return ether.toFixed(6).replace(/\.?0+$/, '');
}
// Search input handler
document.addEventListener('DOMContentLoaded', () => {
const searchInput = document.getElementById('searchInput');
if (searchInput) {
searchInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
handleSearch(e.target.value);
}
});
}
});
</script>
</body>
</html>