2026-01-06 01:46:25 -08:00
#!/usr/bin/env bash
# Add WETH9 and WETH10 Wrap/Unwrap Utilities to Explorer
# Enables users to wrap/unwrap native ETH directly from the explorer
set -euo pipefail
2026-02-12 15:46:57 -08:00
# Load IP configuration
SCRIPT_DIR = " $( cd " $( dirname " ${ BASH_SOURCE [0] } " ) " && pwd ) "
PROJECT_ROOT = " $( cd " $SCRIPT_DIR /.. " && pwd ) "
source " ${ PROJECT_ROOT } /config/ip-addresses.conf " 2>/dev/null || true
IP = " ${ IP :- ${ IP_DEVICE_14 :- ${ IP_DEVICE_14 :- ${ IP_DEVICE_14 :- ${ IP_DEVICE_14 :- ${ IP_DEVICE_14 :- 192 .168.11.14 } } } } } 0 } "
2026-01-06 01:46:25 -08:00
DOMAIN = " ${ DOMAIN :- explorer .d-bis.org } "
PASSWORD = " ${ PASSWORD :- L @kers2010 } "
# Colors
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
CYAN = '\033[0;36m'
NC = '\033[0m'
log_info( ) { echo -e " ${ BLUE } [INFO] ${ NC } $1 " ; }
log_success( ) { echo -e " ${ GREEN } [✓] ${ NC } $1 " ; }
log_warn( ) { echo -e " ${ YELLOW } [WARN] ${ NC } $1 " ; }
log_error( ) { echo -e " ${ RED } [ERROR] ${ NC } $1 " ; }
log_step( ) { echo -e " ${ CYAN } [STEP] ${ NC } $1 " ; }
exec_container( ) {
local cmd = " $1 "
sshpass -p " $PASSWORD " ssh -o StrictHostKeyChecking = no root@" $IP " " bash -c ' $cmd ' " 2>& 1
}
echo "════════════════════════════════════════════════════════"
echo "Add WETH9/WETH10 Wrap/Unwrap Utilities to Explorer"
echo "════════════════════════════════════════════════════════"
echo ""
# Step 1: Read current explorer HTML
log_step "Step 1: Reading current explorer interface..."
sshpass -p " $PASSWORD " scp -o StrictHostKeyChecking = no root@" $IP " :/var/www/html/index.html /tmp/blockscout-current.html
log_success "Current explorer interface backed up"
# Step 2: Create enhanced explorer with wrap/unwrap utilities
log_step "Step 2: Creating enhanced explorer with WETH utilities..."
cat > /tmp/blockscout-with-weth-utilities.html <<'WETH_HTML_EOF'
<!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;" >
2026-02-12 15:46:57 -08:00
<li><strong>WETH9 Bridge:</strong> <span class = "hash" >0x971cD9D156f193df8051E48043C476e53ECd4693</span></li>
2026-01-06 01:46:25 -08:00
<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>
WETH_HTML_EOF
# Step 3: Upload enhanced explorer
log_step "Step 3: Uploading enhanced explorer with WETH utilities..."
sshpass -p " $PASSWORD " scp -o StrictHostKeyChecking = no /tmp/blockscout-with-weth-utilities.html root@" $IP " :/var/www/html/index.html
log_success "Enhanced explorer with WETH utilities uploaded"
echo ""
log_success "WETH9/WETH10 Wrap/Unwrap Utilities added!"
echo ""
log_info "Features added:"
log_info " ✅ WETH9 Wrap/Unwrap Interface"
log_info " ✅ WETH10 Wrap/Unwrap Interface"
log_info " ✅ MetaMask Integration"
log_info " ✅ Real-time Balance Display"
log_info " ✅ MAX Button for Quick Selection"
log_info " ✅ Transaction Status Feedback"
log_info " ✅ Automatic Balance Refresh"
log_info " ✅ Chain 138 Network Detection"
log_info " ✅ User-friendly Forms"
log_info " ✅ Information Tab with Instructions"
echo ""
log_info "Contract Addresses:"
log_info " WETH9: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
log_info " WETH10: 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f"
echo ""
log_info "Access: https://explorer.d-bis.org/"
log_info "Click 'WETH' in the navigation to access wrap/unwrap utilities"
echo ""