feat: explorer API, wallet, CCIP scripts, and config refresh
- Backend REST/gateway/track routes, analytics, Blockscout proxy paths. - Frontend wallet and liquidity surfaces; MetaMask token list alignment. - Deployment docs, verification scripts, address inventory updates. Check: go build ./... under backend/ (pass). Made-with: Cursor
This commit is contained in:
@@ -67,7 +67,7 @@ fi
|
||||
# Check Router
|
||||
log_info ""
|
||||
log_info "2. CCIP Router"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
ROUTER_BYTECODE=$(cast code "$ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$ROUTER_BYTECODE" ] && [ "$ROUTER_BYTECODE" != "0x" ]; then
|
||||
check_healthy "Router deployed and accessible"
|
||||
@@ -89,7 +89,7 @@ fi
|
||||
# Check Bridge Contracts
|
||||
log_info ""
|
||||
log_info "4. Bridge Contracts"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
WETH9_BRIDGE_BYTECODE=$(cast code "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
@@ -26,7 +26,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
declare -A CHAIN_SELECTORS=()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Check database connection and credentials
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
@@ -17,17 +17,55 @@ echo ""
|
||||
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
|
||||
sql_scalar() {
|
||||
local sql="$1"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -Atc "$sql" 2>/dev/null | tr -d ' '
|
||||
}
|
||||
|
||||
is_shared_blockscout_db() {
|
||||
sql_scalar "
|
||||
SELECT CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses' AND column_name = 'address'
|
||||
)
|
||||
THEN 'yes'
|
||||
ELSE 'no'
|
||||
END;
|
||||
"
|
||||
}
|
||||
|
||||
# Test connection
|
||||
echo -n "Testing connection... "
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo "✅ Connected"
|
||||
|
||||
# Check if migration tables exist
|
||||
echo -n "Checking for track schema tables... "
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT EXISTS(SELECT 1 FROM information_schema.tables WHERE table_name = 'addresses');" -t | grep -q "t"; then
|
||||
echo "✅ Tables exist"
|
||||
|
||||
if [ "$(is_shared_blockscout_db || echo no)" = "yes" ]; then
|
||||
echo "Mode: shared Blockscout DB detected"
|
||||
REQUIRED_TABLES=(operator_events operator_ip_whitelist operator_roles wallet_nonces)
|
||||
else
|
||||
echo "⚠️ Tables not found - migration needed"
|
||||
echo "Mode: standalone explorer DB detected"
|
||||
REQUIRED_TABLES=(addresses token_transfers wallet_nonces operator_roles)
|
||||
fi
|
||||
|
||||
echo -n "Checking required tables... "
|
||||
table_list=""
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
if [ -n "$table_list" ]; then
|
||||
table_list+=", "
|
||||
fi
|
||||
table_list+="'$table'"
|
||||
done
|
||||
TABLE_COUNT=$(sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN (${table_list});")
|
||||
if [ "${TABLE_COUNT:-0}" -ge "${#REQUIRED_TABLES[@]}" ]; then
|
||||
echo "✅ Ready (${TABLE_COUNT}/${#REQUIRED_TABLES[@]})"
|
||||
else
|
||||
echo "⚠️ Missing tables (${TABLE_COUNT:-0}/${#REQUIRED_TABLES[@]}) - run bash scripts/run-migration-0010.sh"
|
||||
fi
|
||||
else
|
||||
echo "❌ Connection failed"
|
||||
@@ -37,7 +75,10 @@ else
|
||||
echo "2. Verify credentials in database config"
|
||||
echo "3. Check pg_hba.conf for authentication method"
|
||||
echo "4. Try connecting manually: psql -h $DB_HOST -U $DB_USER -d $DB_NAME"
|
||||
echo ""
|
||||
echo "Use the credentials for your deployment mode:"
|
||||
echo " - standalone explorer DB: explorer / explorer"
|
||||
echo " - shared Blockscout DB: blockscout / blockscout"
|
||||
fi
|
||||
|
||||
unset PGPASSWORD
|
||||
|
||||
|
||||
@@ -7,19 +7,24 @@ BASE_URL="${1:-https://explorer.d-bis.org}"
|
||||
python3 - "$BASE_URL" <<'PY'
|
||||
import re
|
||||
import sys
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
base = sys.argv[1].rstrip("/")
|
||||
session = requests.Session()
|
||||
session.headers.update({"User-Agent": "ExplorerHealthCheck/1.0"})
|
||||
session.headers.update({"User-Agent": "ExplorerHealthCheck/2.0"})
|
||||
|
||||
checks = [
|
||||
failed = False
|
||||
|
||||
html_checks = [
|
||||
"/",
|
||||
"/home",
|
||||
"/blocks",
|
||||
"/transactions",
|
||||
"/addresses",
|
||||
"/bridge",
|
||||
"/routes",
|
||||
"/weth",
|
||||
"/tokens",
|
||||
"/pools",
|
||||
@@ -27,42 +32,149 @@ checks = [
|
||||
"/more",
|
||||
"/analytics",
|
||||
"/operator",
|
||||
"/system",
|
||||
"/liquidity",
|
||||
"/wallet",
|
||||
"/snap/",
|
||||
"/docs.html",
|
||||
"/privacy.html",
|
||||
"/terms.html",
|
||||
"/acknowledgments.html",
|
||||
]
|
||||
|
||||
json_checks = [
|
||||
"/api/v2/stats",
|
||||
"/api/config/token-list",
|
||||
"/api/config/networks",
|
||||
"/api/config/capabilities",
|
||||
"/config/CHAIN138_RPC_CAPABILITIES.json",
|
||||
"/config/topology-graph.json",
|
||||
"/config/mission-control-verify.example.json",
|
||||
"/explorer-api/v1/features",
|
||||
"/explorer-api/v1/ai/context?q=cUSDT",
|
||||
"/explorer-api/v1/track1/bridge/status",
|
||||
"/explorer-api/v1/mission-control/liquidity/token/0x93E66202A11B1772E55407B32B44e5Cd8eda7f22/pools",
|
||||
"/token-aggregation/api/v1/routes/tree?chainId=138&tokenIn=0x93E66202A11B1772E55407B32B44e5Cd8eda7f22&tokenOut=0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1&amountIn=1000000",
|
||||
"/token-aggregation/api/v1/routes/matrix",
|
||||
"/token-aggregation/api/v1/routes/ingestion?fromChainId=138&routeType=swap",
|
||||
"/token-aggregation/api/v1/routes/partner-payloads?partner=0x&amount=1000000&includeUnsupported=true",
|
||||
]
|
||||
|
||||
failed = False
|
||||
asset_checks = [
|
||||
"/token-icons/cUSDC.png",
|
||||
"/token-icons/cUSDT.png",
|
||||
"/token-icons/cXAUC.png",
|
||||
"/token-icons/cXAUT.png",
|
||||
]
|
||||
|
||||
print("== Core routes ==")
|
||||
for path in checks:
|
||||
|
||||
def mark_failure(message):
|
||||
global failed
|
||||
failed = True
|
||||
print(message)
|
||||
|
||||
|
||||
print("== Public routes ==")
|
||||
for path in html_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:50]}]")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
failed = True
|
||||
print(f"ERR {path} [{exc}]")
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== JSON and API surfaces ==")
|
||||
for path in json_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
continue
|
||||
if "json" not in ctype:
|
||||
failed = True
|
||||
print(f" expected JSON content-type, got: {ctype}")
|
||||
continue
|
||||
payload = resp.json()
|
||||
if path == "/api/config/capabilities":
|
||||
if payload.get("chainId") != 138:
|
||||
mark_failure(" capabilities JSON does not report chainId 138")
|
||||
wallet_support = payload.get("walletSupport", {})
|
||||
if wallet_support.get("walletWatchAsset") is not True:
|
||||
mark_failure(" capabilities JSON does not advertise walletWatchAsset")
|
||||
elif path == "/api/config/token-list":
|
||||
tokens = payload.get("tokens", [])
|
||||
if not tokens:
|
||||
mark_failure(" token list is empty")
|
||||
elif path == "/api/config/networks":
|
||||
chains = payload.get("chains", [])
|
||||
if not chains:
|
||||
mark_failure(" networks payload does not include any chains")
|
||||
elif path == "/explorer-api/v1/features":
|
||||
if "features" not in payload:
|
||||
mark_failure(" features payload is missing the features key")
|
||||
elif path == "/explorer-api/v1/track1/bridge/status":
|
||||
data = payload.get("data", {})
|
||||
relays = data.get("ccip_relays", {})
|
||||
expected_relays = {
|
||||
"avax",
|
||||
"avax_cw",
|
||||
"avax_to_138",
|
||||
"bsc",
|
||||
"mainnet_cw",
|
||||
"mainnet_weth",
|
||||
}
|
||||
missing = sorted(expected_relays - set(relays))
|
||||
if missing:
|
||||
mark_failure(f" bridge status is missing relay keys: {', '.join(missing)}")
|
||||
if data.get("status") not in {"operational", "paused", "degraded"}:
|
||||
mark_failure(" bridge status payload does not include a recognized overall status")
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== Static assets ==")
|
||||
for path in asset_checks:
|
||||
url = base + path
|
||||
try:
|
||||
resp = session.get(url, timeout=20, allow_redirects=True)
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} {path} [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR {path} [{exc}]")
|
||||
|
||||
print("\n== Mission-control SSE ==")
|
||||
stream_url = base + "/explorer-api/v1/mission-control/stream"
|
||||
try:
|
||||
with session.get(stream_url, timeout=(20, 20), stream=True) as resp:
|
||||
ctype = resp.headers.get("content-type", "")
|
||||
print(f"{resp.status_code:>3} /explorer-api/v1/mission-control/stream [{ctype[:60]}]")
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
else:
|
||||
lines = []
|
||||
for raw in resp.iter_lines(decode_unicode=True):
|
||||
if raw:
|
||||
lines.append(raw)
|
||||
if len(lines) >= 2:
|
||||
break
|
||||
if not any(line.startswith("event:") for line in lines):
|
||||
mark_failure(" mission-control stream did not emit an event line")
|
||||
if not any(line.startswith("data:") for line in lines):
|
||||
mark_failure(" mission-control stream did not emit a data line")
|
||||
except Exception as exc:
|
||||
mark_failure(f"ERR /explorer-api/v1/mission-control/stream [{exc}]")
|
||||
|
||||
print("\n== Internal href targets from homepage ==")
|
||||
try:
|
||||
home = session.get(base + "/", timeout=20).text
|
||||
hrefs = sorted(set(re.findall(r'href="([^"]+)"', home)))
|
||||
hrefs = sorted(set(re.findall(r'href=\"([^\"]+)\"', home)))
|
||||
for href in hrefs:
|
||||
if href.startswith("/") and not href.startswith("//"):
|
||||
resp = session.get(base + href, timeout=20, allow_redirects=True)
|
||||
@@ -70,10 +182,9 @@ try:
|
||||
if resp.status_code >= 400:
|
||||
failed = True
|
||||
except Exception as exc:
|
||||
failed = True
|
||||
print(f"ERR homepage href sweep failed: {exc}")
|
||||
mark_failure(f"ERR homepage href sweep failed: {exc}")
|
||||
|
||||
print("\n== Static explorer domains referenced by bridge page ==")
|
||||
print("\n== External explorer roots referenced by bridge surfaces ==")
|
||||
external_roots = [
|
||||
"https://etherscan.io/",
|
||||
"https://bscscan.com/",
|
||||
|
||||
@@ -36,7 +36,7 @@ fi
|
||||
|
||||
# Contract addresses
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x326C977E6efc84E512bB9C30f76E30c160eD06FB)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
# Get account
|
||||
|
||||
@@ -11,7 +11,7 @@ load_explorer_runtime_env
|
||||
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN "")"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
ACCOUNT=$(cast wallet address "$PRIVATE_KEY")
|
||||
|
||||
|
||||
@@ -102,7 +102,7 @@ WETH9_CONFIGURED=0
|
||||
WETH10_CONFIGURED=0
|
||||
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
TOTAL_DESTINATIONS=$(ccip_destination_count)
|
||||
|
||||
@@ -138,6 +138,12 @@ server {
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
||||
}
|
||||
location = /api/config/capabilities {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json;
|
||||
}
|
||||
|
||||
location = / {
|
||||
root /var/www/html;
|
||||
|
||||
@@ -13,7 +13,7 @@ load_explorer_runtime_env
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN_138 0x514910771AF9Ca656af840dff83E8264EcF986CA)"
|
||||
CHAIN_ID=138
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
echo "║ COMPLETE LINK TOKEN SETUP ║"
|
||||
@@ -126,7 +126,7 @@ echo ""
|
||||
# Step 4: Check bridge balances (if token exists)
|
||||
if [ "${TOKEN_EXISTS:-false}" = "true" ]; then
|
||||
echo "=== Step 4: Checking Bridge LINK Balances ==="
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
WETH9_LINK=$(cast call "$LINK_TOKEN" "balanceOf(address)" "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "0")
|
||||
|
||||
@@ -27,7 +27,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
@@ -28,7 +28,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
# Check PRIVATE_KEY
|
||||
|
||||
@@ -45,7 +45,7 @@ WETH9_MAINNET_BRIDGE="0x2A0840e5117683b11682ac46f5CF5621E67269E3"
|
||||
WETH10_MAINNET_BRIDGE="0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
|
||||
# Bridge addresses on ChainID 138
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH9_BRIDGE="0xcacfd227A040002e49e2e01626363071324f820a"
|
||||
WETH10_BRIDGE="0xe0E93247376aa097dB308B92e6Ba36bA015535D0"
|
||||
|
||||
log_info "========================================="
|
||||
|
||||
@@ -43,7 +43,7 @@ WETH9_MAINNET_BRIDGE="0x2A0840e5117683b11682ac46f5CF5621E67269E3"
|
||||
WETH10_MAINNET_BRIDGE="0xb7721dD53A8c629d9f1Ba31a5819AFe250002b03"
|
||||
|
||||
# Bridge addresses on ChainID 138
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
log_info "========================================="
|
||||
|
||||
@@ -12,7 +12,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Ethereum Mainnet canonical LINK token address
|
||||
LINK_TOKEN_MAINNET="0x514910771AF9Ca656af840dff83E8264EcF986CA"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
CHAIN_ID=138
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════╗"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Complete deployment and testing script
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
@@ -16,6 +16,14 @@ YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
extract_error_message() {
|
||||
local body="${1:-}"
|
||||
if [ -z "$body" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "$body" | jq -r '.error.message // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Step 1: Verify components
|
||||
echo -e "${BLUE}Step 1: Verifying components...${NC}"
|
||||
bash "$SCRIPT_DIR/verify-tiered-architecture.sh"
|
||||
@@ -41,14 +49,16 @@ if bash "$SCRIPT_DIR/check-database-connection.sh" 2>&1 | grep -q "✅ Connected
|
||||
|
||||
# Try migration
|
||||
echo -e "${BLUE}Running migration...${NC}"
|
||||
if bash "$SCRIPT_DIR/run-migration-0010.sh" 2>&1 | tail -5; then
|
||||
if bash "$SCRIPT_DIR/run-migration-0010.sh"; then
|
||||
echo -e "${GREEN}✅ Migration completed${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Migration may have already been run or failed${NC}"
|
||||
echo -e "${RED}❌ Migration failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
DB_READY=false
|
||||
echo -e "${YELLOW}⚠️ Database not accessible - Track 1 endpoints will work, Track 2-4 require database${NC}"
|
||||
echo " Wallet connect will stay unavailable until DB access is fixed and migration 0010 is applied."
|
||||
echo " To fix: Set DB_PASSWORD environment variable or fix database credentials"
|
||||
fi
|
||||
echo ""
|
||||
@@ -143,14 +153,28 @@ fi
|
||||
|
||||
# Test auth endpoints
|
||||
echo -n "Testing /api/v1/auth/nonce... "
|
||||
NONCE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "http://localhost:8080/api/v1/auth/nonce" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"address":"0x1234567890123456789012345678901234567890"}' 2>&1)
|
||||
NONCE_CODE=$(echo "$NONCE_RESPONSE" | tail -n1)
|
||||
if [ "$NONCE_CODE" = "200" ] || [ "$NONCE_CODE" = "500" ]; then
|
||||
echo -e "${GREEN}✅${NC} (HTTP $NONCE_CODE)"
|
||||
if [ "$DB_READY" = true ]; then
|
||||
NONCE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "http://localhost:8080/api/v1/auth/nonce" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"address":"0x1234567890123456789012345678901234567890"}' 2>&1)
|
||||
NONCE_CODE=$(echo "$NONCE_RESPONSE" | tail -n1)
|
||||
NONCE_BODY=$(echo "$NONCE_RESPONSE" | sed '$d')
|
||||
NONCE_ERROR=$(extract_error_message "$NONCE_BODY")
|
||||
if [ "$NONCE_CODE" = "200" ]; then
|
||||
echo -e "${GREEN}✅${NC} (HTTP $NONCE_CODE)"
|
||||
elif [ "$NONCE_CODE" = "503" ]; then
|
||||
echo -e "${RED}❌${NC} (HTTP $NONCE_CODE)"
|
||||
echo " Wallet auth storage is not ready: ${NONCE_ERROR:-service unavailable}"
|
||||
echo " Fix: bash scripts/run-migration-0010.sh, then restart the backend."
|
||||
exit 1
|
||||
else
|
||||
echo -e "${YELLOW}⚠️${NC} (HTTP $NONCE_CODE)"
|
||||
if [ -n "$NONCE_ERROR" ]; then
|
||||
echo " Response: $NONCE_ERROR"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "${YELLOW}⚠️${NC} (HTTP $NONCE_CODE)"
|
||||
echo -e "${YELLOW}⚠️${NC} skipped (database unavailable; wallet auth cannot work yet)"
|
||||
fi
|
||||
|
||||
# Test Track 2 (should require auth)
|
||||
@@ -214,4 +238,3 @@ echo ""
|
||||
|
||||
echo -e "${GREEN}✅ Deployment and testing complete!${NC}"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -1,23 +1,94 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy explorer config (token list, networks) to VMID 5000 for /api/config/* endpoints.
|
||||
# Deploy explorer config (token list, networks, capabilities) to VMID 5000 for /api/config/* endpoints.
|
||||
# Run from repo root. Requires Proxmox host access (pct exec) or SSH to explorer container.
|
||||
#
|
||||
# Usage:
|
||||
# From Proxmox host: pct exec 5000 -- bash -c 'mkdir -p /var/www/html/config'
|
||||
# Then: ./scripts/deploy-explorer-config-to-vmid5000.sh
|
||||
#
|
||||
# From a workstation that can SSH to the Proxmox host:
|
||||
# PROXMOX_HOST=192.168.11.12 ./scripts/deploy-explorer-config-to-vmid5000.sh
|
||||
#
|
||||
# Or run inside VMID 5000:
|
||||
# pct push 5000 /path/to/DUAL_CHAIN_TOKEN_LIST.tokenlist.json /var/www/html/config/
|
||||
# pct push 5000 /path/to/DUAL_CHAIN_NETWORKS.json /var/www/html/config/
|
||||
# pct push 5000 /path/to/CHAIN138_RPC_CAPABILITIES.json /var/www/html/config/
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
CONFIG_SRC="$REPO_ROOT/explorer-monorepo/backend/api/rest/config/metamask"
|
||||
TOPOLOGY_SRC="$REPO_ROOT/explorer-monorepo/frontend/public/config/topology-graph.json"
|
||||
VERIFY_EXAMPLE_SRC="$REPO_ROOT/explorer-monorepo/frontend/public/config/mission-control-verify.example.json"
|
||||
TOKEN_ICONS_SRC="$REPO_ROOT/explorer-monorepo/frontend/public/token-icons"
|
||||
VMID="${EXPLORER_VMID:-5000}"
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.12}"
|
||||
EXEC_MODE="${EXEC_MODE:-pct}"
|
||||
SSH_OPTS=(-o BatchMode=yes -o StrictHostKeyChecking=no)
|
||||
|
||||
deploy_via_pct() {
|
||||
pct exec "$VMID" -- mkdir -p /var/www/html/config /var/www/html/token-icons
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" /var/www/html/config/DUAL_CHAIN_NETWORKS.json
|
||||
pct push "$VMID" "$CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json" /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then
|
||||
pct push "$VMID" "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" /var/www/html/config/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json
|
||||
fi
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then
|
||||
pct push "$VMID" "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" /var/www/html/config/GRU_V2_DEPLOYMENT_QUEUE.json
|
||||
fi
|
||||
if [ -f "$TOPOLOGY_SRC" ]; then
|
||||
pct push "$VMID" "$TOPOLOGY_SRC" /var/www/html/config/topology-graph.json
|
||||
fi
|
||||
if [ -f "$VERIFY_EXAMPLE_SRC" ]; then
|
||||
pct push "$VMID" "$VERIFY_EXAMPLE_SRC" /var/www/html/config/mission-control-verify.example.json
|
||||
fi
|
||||
if [ -d "$TOKEN_ICONS_SRC" ]; then
|
||||
for icon in "$TOKEN_ICONS_SRC"/*.png; do
|
||||
[ -f "$icon" ] || continue
|
||||
pct push "$VMID" "$icon" "/var/www/html/token-icons/$(basename "$icon")"
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
deploy_via_remote_pct() {
|
||||
local remote_tmp
|
||||
remote_tmp="/tmp/explorer-config-${VMID}-$$"
|
||||
ssh "${SSH_OPTS[@]}" "root@$PROXMOX_HOST" "mkdir -p '$remote_tmp'"
|
||||
scp "${SSH_OPTS[@]}" "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" "root@$PROXMOX_HOST:$remote_tmp/DUAL_CHAIN_TOKEN_LIST.tokenlist.json"
|
||||
scp "${SSH_OPTS[@]}" "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" "root@$PROXMOX_HOST:$remote_tmp/DUAL_CHAIN_NETWORKS.json"
|
||||
scp "${SSH_OPTS[@]}" "$CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json" "root@$PROXMOX_HOST:$remote_tmp/CHAIN138_RPC_CAPABILITIES.json"
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then
|
||||
scp "${SSH_OPTS[@]}" "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" "root@$PROXMOX_HOST:$remote_tmp/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json"
|
||||
fi
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then
|
||||
scp "${SSH_OPTS[@]}" "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" "root@$PROXMOX_HOST:$remote_tmp/GRU_V2_DEPLOYMENT_QUEUE.json"
|
||||
fi
|
||||
if [ -f "$TOPOLOGY_SRC" ]; then
|
||||
scp "${SSH_OPTS[@]}" "$TOPOLOGY_SRC" "root@$PROXMOX_HOST:$remote_tmp/topology-graph.json"
|
||||
fi
|
||||
if [ -f "$VERIFY_EXAMPLE_SRC" ]; then
|
||||
scp "${SSH_OPTS[@]}" "$VERIFY_EXAMPLE_SRC" "root@$PROXMOX_HOST:$remote_tmp/mission-control-verify.example.json"
|
||||
fi
|
||||
if [ -d "$TOKEN_ICONS_SRC" ]; then
|
||||
for icon in "$TOKEN_ICONS_SRC"/*.png; do
|
||||
[ -f "$icon" ] || continue
|
||||
scp "${SSH_OPTS[@]}" "$icon" "root@$PROXMOX_HOST:$remote_tmp/$(basename "$icon")"
|
||||
done
|
||||
fi
|
||||
ssh "${SSH_OPTS[@]}" "root@$PROXMOX_HOST" "\
|
||||
pct exec '$VMID' -- mkdir -p /var/www/html/config /var/www/html/token-icons && \
|
||||
pct push '$VMID' '$remote_tmp/DUAL_CHAIN_TOKEN_LIST.tokenlist.json' /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json && \
|
||||
pct push '$VMID' '$remote_tmp/DUAL_CHAIN_NETWORKS.json' /var/www/html/config/DUAL_CHAIN_NETWORKS.json && \
|
||||
pct push '$VMID' '$remote_tmp/CHAIN138_RPC_CAPABILITIES.json' /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json \
|
||||
$(if [ -f "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then printf "%s" "&& pct push '$VMID' '$remote_tmp/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json' /var/www/html/config/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json "; fi) \
|
||||
$(if [ -f "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then printf "%s" "&& pct push '$VMID' '$remote_tmp/GRU_V2_DEPLOYMENT_QUEUE.json' /var/www/html/config/GRU_V2_DEPLOYMENT_QUEUE.json "; fi) \
|
||||
$(if [ -f "$TOPOLOGY_SRC" ]; then printf "%s" "&& pct push '$VMID' '$remote_tmp/topology-graph.json' /var/www/html/config/topology-graph.json "; fi) \
|
||||
$(if [ -f "$VERIFY_EXAMPLE_SRC" ]; then printf "%s" "&& pct push '$VMID' '$remote_tmp/mission-control-verify.example.json' /var/www/html/config/mission-control-verify.example.json "; fi) \
|
||||
$(if [ -d "$TOKEN_ICONS_SRC" ]; then for icon in "$TOKEN_ICONS_SRC"/*.png; do [ -f "$icon" ] || continue; printf "%s" "&& pct push '$VMID' '$remote_tmp/$(basename "$icon")' /var/www/html/token-icons/$(basename "$icon") "; done; fi) && \
|
||||
rm -rf '$remote_tmp'"
|
||||
}
|
||||
|
||||
if [ ! -f "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" ]; then
|
||||
echo "Error: Token list not found at $CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" >&2
|
||||
@@ -27,23 +98,38 @@ if [ ! -f "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" ]; then
|
||||
echo "Error: Networks config not found at $CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json" ]; then
|
||||
echo "Error: Capabilities config not found at $CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deploying explorer config to VMID $VMID..."
|
||||
echo " Token list: $CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json"
|
||||
echo " Networks: $CONFIG_SRC/DUAL_CHAIN_NETWORKS.json"
|
||||
echo " Capabilities: $CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json"
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then
|
||||
echo " GRU status: $CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json"
|
||||
fi
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then
|
||||
echo " GRU queue: $CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json"
|
||||
fi
|
||||
if [ -d "$TOKEN_ICONS_SRC" ]; then
|
||||
echo " Token icons: $TOKEN_ICONS_SRC/*.png"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
case "$EXEC_MODE" in
|
||||
pct)
|
||||
if command -v pct &>/dev/null; then
|
||||
pct exec "$VMID" -- mkdir -p /var/www/html/config
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" /var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json
|
||||
pct push "$VMID" "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" /var/www/html/config/DUAL_CHAIN_NETWORKS.json
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/token-list | jq '.tokens | length'"
|
||||
deploy_via_pct
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/capabilities | jq '.chainId'"
|
||||
elif ssh "${SSH_OPTS[@]}" "root@$PROXMOX_HOST" "command -v pct" >/dev/null 2>&1; then
|
||||
deploy_via_remote_pct
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/capabilities | jq '.chainId'"
|
||||
else
|
||||
echo "pct not available. Use EXEC_MODE=ssh or run manually inside VMID 5000:"
|
||||
echo " mkdir -p /var/www/html/config"
|
||||
echo " # Copy DUAL_CHAIN_TOKEN_LIST.tokenlist.json and DUAL_CHAIN_NETWORKS.json to /var/www/html/config/"
|
||||
echo "pct not available locally, and remote pct on $PROXMOX_HOST was not reachable."
|
||||
echo "Use EXEC_MODE=ssh or run from the Proxmox host:"
|
||||
echo " ssh root@$PROXMOX_HOST 'pct exec $VMID -- mkdir -p /var/www/html/config'"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
@@ -53,12 +139,42 @@ case "$EXEC_MODE" in
|
||||
trap "rm -rf $TMP_DIR" EXIT
|
||||
cp "$CONFIG_SRC/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" "$TMP_DIR/"
|
||||
cp "$CONFIG_SRC/DUAL_CHAIN_NETWORKS.json" "$TMP_DIR/"
|
||||
cp "$CONFIG_SRC/CHAIN138_RPC_CAPABILITIES.json" "$TMP_DIR/"
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then
|
||||
cp "$CONFIG_SRC/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" "$TMP_DIR/"
|
||||
fi
|
||||
if [ -f "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then
|
||||
cp "$CONFIG_SRC/GRU_V2_DEPLOYMENT_QUEUE.json" "$TMP_DIR/"
|
||||
fi
|
||||
if [ -d "$TOKEN_ICONS_SRC" ]; then
|
||||
cp "$TOKEN_ICONS_SRC"/*.png "$TMP_DIR/" 2>/dev/null || true
|
||||
fi
|
||||
ssh "root@$CONTAINER_IP" "mkdir -p /var/www/html/config /var/www/html/token-icons"
|
||||
scp "$TMP_DIR/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" "root@$CONTAINER_IP:/var/www/html/config/" 2>/dev/null || {
|
||||
echo "SSH to $CONTAINER_IP failed. Ensure config dir exists: ssh root@$CONTAINER_IP 'mkdir -p /var/www/html/config'"
|
||||
exit 1
|
||||
}
|
||||
scp "$TMP_DIR/DUAL_CHAIN_NETWORKS.json" "root@$CONTAINER_IP:/var/www/html/config/"
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/token-list | jq '.tokens | length'"
|
||||
scp "$TMP_DIR/CHAIN138_RPC_CAPABILITIES.json" "root@$CONTAINER_IP:/var/www/html/config/"
|
||||
if [ -f "$TMP_DIR/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" ]; then
|
||||
scp "$TMP_DIR/GRU_V2_PUBLIC_DEPLOYMENT_STATUS.json" "root@$CONTAINER_IP:/var/www/html/config/"
|
||||
fi
|
||||
if [ -f "$TMP_DIR/GRU_V2_DEPLOYMENT_QUEUE.json" ]; then
|
||||
scp "$TMP_DIR/GRU_V2_DEPLOYMENT_QUEUE.json" "root@$CONTAINER_IP:/var/www/html/config/"
|
||||
fi
|
||||
if [ -f "$TOPOLOGY_SRC" ]; then
|
||||
scp "$TOPOLOGY_SRC" "root@$CONTAINER_IP:/var/www/html/config/topology-graph.json"
|
||||
fi
|
||||
if [ -f "$VERIFY_EXAMPLE_SRC" ]; then
|
||||
scp "$VERIFY_EXAMPLE_SRC" "root@$CONTAINER_IP:/var/www/html/config/mission-control-verify.example.json"
|
||||
fi
|
||||
if [ -d "$TOKEN_ICONS_SRC" ]; then
|
||||
for icon in "$TMP_DIR"/*.png; do
|
||||
[ -f "$icon" ] || continue
|
||||
scp "$icon" "root@$CONTAINER_IP:/var/www/html/token-icons/"
|
||||
done
|
||||
fi
|
||||
echo "Done. Verify: curl -s https://explorer.d-bis.org/api/config/capabilities | jq '.chainId'"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown EXEC_MODE=$EXEC_MODE. Use pct or ssh." >&2
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Deploy custom explorer frontend to VMID 5000
|
||||
# This script copies the frontend to /var/www/html/ and updates nginx
|
||||
# Deploy legacy static explorer frontend to VMID 5000
|
||||
# This copies the old SPA assets into /var/www/html/.
|
||||
# For the current Next.js frontend, use ./scripts/deploy-next-frontend-to-vmid5000.sh
|
||||
#
|
||||
# Optional: for air-gapped Mermaid on chain138-command-center.html, run:
|
||||
# bash explorer-monorepo/scripts/vendor-mermaid-for-command-center.sh
|
||||
# then switch the script src in chain138-command-center.html to /thirdparty/mermaid.min.js
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -15,7 +20,7 @@ FRONTEND_PUBLIC="$(dirname "$FRONTEND_SOURCE")"
|
||||
PROXMOX_R630_02="${PROXMOX_HOST_R630_02:-192.168.11.12}"
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploying Custom Explorer Frontend"
|
||||
echo "Deploying Legacy Static Explorer Frontend"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
@@ -80,7 +85,7 @@ echo ""
|
||||
|
||||
# Step 4b: Deploy favicon and apple-touch-icon
|
||||
echo "=== Step 4b: Deploying icons ==="
|
||||
for ASSET in explorer-spa.js apple-touch-icon.png favicon.ico; do
|
||||
for ASSET in explorer-spa.js chain138-command-center.html apple-touch-icon.png favicon.ico; do
|
||||
SRC="${FRONTEND_PUBLIC}/${ASSET}"
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "⚠️ Skip $ASSET (not found)"
|
||||
@@ -100,6 +105,54 @@ for ASSET in explorer-spa.js apple-touch-icon.png favicon.ico; do
|
||||
echo "✅ $ASSET deployed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Optional local Mermaid (~3 MB) for command center when jsDelivr/CSP is blocked
|
||||
MERMAID_SRC="${FRONTEND_PUBLIC}/thirdparty/mermaid.min.js"
|
||||
if [ -f "$MERMAID_SRC" ]; then
|
||||
echo "=== Step 4b2: Deploying thirdparty/mermaid.min.js (local vendored) ==="
|
||||
if [ "$DEPLOY_METHOD" = "direct" ]; then
|
||||
mkdir -p /var/www/html/thirdparty
|
||||
cp "$MERMAID_SRC" /var/www/html/thirdparty/mermaid.min.js
|
||||
chown www-data:www-data /var/www/html/thirdparty/mermaid.min.js 2>/dev/null || true
|
||||
echo "✅ thirdparty/mermaid.min.js deployed"
|
||||
elif [ "$DEPLOY_METHOD" = "remote" ]; then
|
||||
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$MERMAID_SRC" root@${PROXMOX_R630_02}:/tmp/mermaid.min.js
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@${PROXMOX_R630_02} "pct exec $VMID -- mkdir -p /var/www/html/thirdparty && pct push $VMID /tmp/mermaid.min.js /var/www/html/thirdparty/mermaid.min.js --perms 0644 && pct exec $VMID -- chown www-data:www-data /var/www/html/thirdparty/mermaid.min.js"
|
||||
echo "✅ thirdparty/mermaid.min.js deployed via $PROXMOX_R630_02"
|
||||
else
|
||||
$EXEC_PREFIX mkdir -p /var/www/html/thirdparty
|
||||
pct push $VMID "$MERMAID_SRC" /var/www/html/thirdparty/mermaid.min.js
|
||||
$EXEC_PREFIX chown www-data:www-data /var/www/html/thirdparty/mermaid.min.js 2>/dev/null || true
|
||||
echo "✅ thirdparty/mermaid.min.js deployed"
|
||||
fi
|
||||
echo ""
|
||||
else
|
||||
echo "ℹ️ Skip thirdparty/mermaid.min.js (run scripts/vendor-mermaid-for-command-center.sh if CSP/offline needs local Mermaid)"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo "=== Step 4c: Deploying /config JSON (topology, verify example) ==="
|
||||
run_in_vm "mkdir -p /var/www/html/config"
|
||||
for CFG in topology-graph.json mission-control-verify.example.json; do
|
||||
CFG_SRC="${FRONTEND_PUBLIC}/config/${CFG}"
|
||||
if [ ! -f "$CFG_SRC" ]; then
|
||||
echo "⚠️ Skip config/$CFG (not found)"
|
||||
continue
|
||||
fi
|
||||
if [ "$DEPLOY_METHOD" = "direct" ]; then
|
||||
cp "$CFG_SRC" "/var/www/html/config/$CFG"
|
||||
chown www-data:www-data "/var/www/html/config/$CFG" 2>/dev/null || true
|
||||
echo "✅ config/$CFG deployed"
|
||||
elif [ "$DEPLOY_METHOD" = "remote" ]; then
|
||||
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$CFG_SRC" root@${PROXMOX_R630_02}:/tmp/explorer-cfg-"$CFG"
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no root@${PROXMOX_R630_02} "pct push $VMID /tmp/explorer-cfg-$CFG /var/www/html/config/$CFG --perms 0644 && pct exec $VMID -- chown www-data:www-data /var/www/html/config/$CFG"
|
||||
echo "✅ config/$CFG deployed via $PROXMOX_R630_02"
|
||||
else
|
||||
pct push $VMID "$CFG_SRC" "/var/www/html/config/$CFG"
|
||||
$EXEC_PREFIX chown www-data:www-data "/var/www/html/config/$CFG" 2>/dev/null || true
|
||||
echo "✅ config/$CFG deployed"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
|
||||
# Step 5 (remote): Apply nginx config so /favicon.ico and /apple-touch-icon.png are served
|
||||
@@ -211,6 +264,10 @@ echo "=========================================="
|
||||
echo "Deployment Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
echo "Note: this is the legacy static SPA deployment path."
|
||||
echo "For the current Next.js frontend, use:"
|
||||
echo " ./scripts/deploy-next-frontend-to-vmid5000.sh"
|
||||
echo ""
|
||||
echo "Frontend should now be accessible at:"
|
||||
echo " - http://$VM_IP/"
|
||||
echo " - https://explorer.d-bis.org/"
|
||||
@@ -219,4 +276,3 @@ echo "To view logs:"
|
||||
echo " tail -f /var/log/nginx/blockscout-access.log"
|
||||
echo " tail -f /var/log/nginx/blockscout-error.log"
|
||||
echo ""
|
||||
|
||||
|
||||
151
scripts/deploy-next-frontend-to-vmid5000.sh
Executable file
151
scripts/deploy-next-frontend-to-vmid5000.sh
Executable file
@@ -0,0 +1,151 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Deploy the current Next.js standalone frontend to VMID 5000.
|
||||
# This is the canonical deployment path for the current SolaceScanScout frontend.
|
||||
# It builds the local frontend, uploads the standalone bundle, installs a systemd
|
||||
# service, and starts the Node server on 127.0.0.1:3000 inside the container.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
VMID="${VMID:-5000}"
|
||||
FRONTEND_PORT="${FRONTEND_PORT:-3000}"
|
||||
SERVICE_NAME="solacescanscout-frontend"
|
||||
APP_ROOT="/opt/solacescanscout/frontend"
|
||||
PROXMOX_R630_02="${PROXMOX_HOST_R630_02:-192.168.11.12}"
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
WORKSPACE_ROOT="$(cd "$REPO_ROOT/.." && pwd)"
|
||||
FRONTEND_ROOT="${REPO_ROOT}/frontend"
|
||||
SERVICE_TEMPLATE="${REPO_ROOT}/deployment/systemd/solacescanscout-frontend.service"
|
||||
NGINX_SNIPPET="${REPO_ROOT}/deployment/common/nginx-next-frontend-proxy.conf"
|
||||
VERIFY_SCRIPT="${WORKSPACE_ROOT}/scripts/verify/check-explorer-e2e.sh"
|
||||
RELEASE_ID="$(date +%Y%m%d_%H%M%S)"
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
ARCHIVE_NAME="solacescanscout-next-${RELEASE_ID}.tar"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$TMP_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
if [[ ! -f "$SERVICE_TEMPLATE" ]]; then
|
||||
echo "Missing service template: $SERVICE_TEMPLATE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
push_into_vmid() {
|
||||
local source_path="$1"
|
||||
local destination_path="$2"
|
||||
local perms="${3:-0644}"
|
||||
|
||||
if [[ -f /proc/1/cgroup ]] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
|
||||
install -D -m "$perms" "$source_path" "$destination_path"
|
||||
elif command -v pct >/dev/null 2>&1; then
|
||||
pct push "$VMID" "$source_path" "$destination_path" --perms "$perms"
|
||||
else
|
||||
local remote_tmp="/tmp/$(basename "$source_path").$$"
|
||||
scp -o ConnectTimeout=10 -o StrictHostKeyChecking=no "$source_path" "root@${PROXMOX_R630_02}:${remote_tmp}"
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "root@${PROXMOX_R630_02}" \
|
||||
"pct push ${VMID} ${remote_tmp} ${destination_path} --perms ${perms} && rm -f ${remote_tmp}"
|
||||
fi
|
||||
}
|
||||
|
||||
run_in_vmid() {
|
||||
local command="$1"
|
||||
|
||||
if [[ -f /proc/1/cgroup ]] && grep -q "lxc" /proc/1/cgroup 2>/dev/null; then
|
||||
bash -lc "$command"
|
||||
elif command -v pct >/dev/null 2>&1; then
|
||||
pct exec "$VMID" -- bash -lc "$command"
|
||||
else
|
||||
ssh -o ConnectTimeout=10 -o StrictHostKeyChecking=no "root@${PROXMOX_R630_02}" \
|
||||
"pct exec ${VMID} -- bash -lc $(printf '%q' "$command")"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "=========================================="
|
||||
echo "Deploying Next SolaceScanScout Frontend"
|
||||
echo "=========================================="
|
||||
echo "VMID: $VMID"
|
||||
echo "Frontend root: $FRONTEND_ROOT"
|
||||
echo "Release: $RELEASE_ID"
|
||||
echo ""
|
||||
|
||||
if [[ "${SKIP_BUILD:-0}" != "1" ]]; then
|
||||
echo "== Building frontend =="
|
||||
(cd "$FRONTEND_ROOT" && npm run build)
|
||||
echo ""
|
||||
fi
|
||||
|
||||
if [[ ! -f "${FRONTEND_ROOT}/.next/standalone/server.js" ]]; then
|
||||
echo "Missing standalone server build. Run \`npm run build\` in ${FRONTEND_ROOT} first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
STAGE_DIR="${TMP_DIR}/stage"
|
||||
mkdir -p "${STAGE_DIR}/.next"
|
||||
cp -R "${FRONTEND_ROOT}/.next/standalone/." "$STAGE_DIR/"
|
||||
cp -R "${FRONTEND_ROOT}/.next/static" "${STAGE_DIR}/.next/static"
|
||||
cp -R "${FRONTEND_ROOT}/public" "${STAGE_DIR}/public"
|
||||
tar -C "$STAGE_DIR" -cf "${TMP_DIR}/${ARCHIVE_NAME}" .
|
||||
|
||||
cp "$SERVICE_TEMPLATE" "${TMP_DIR}/${SERVICE_NAME}.service"
|
||||
sed -i "s|/opt/solacescanscout/frontend/current|${APP_ROOT}/current|g" "${TMP_DIR}/${SERVICE_NAME}.service"
|
||||
sed -i "s|Environment=PORT=3000|Environment=PORT=${FRONTEND_PORT}|g" "${TMP_DIR}/${SERVICE_NAME}.service"
|
||||
|
||||
cat > "${TMP_DIR}/install-next-frontend.sh" <<EOF
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
APP_ROOT="${APP_ROOT}"
|
||||
RELEASE_DIR="\${APP_ROOT}/releases/${RELEASE_ID}"
|
||||
SERVICE_NAME="${SERVICE_NAME}"
|
||||
FRONTEND_PORT="${FRONTEND_PORT}"
|
||||
|
||||
mkdir -p "\${APP_ROOT}/releases"
|
||||
mkdir -p "\${RELEASE_DIR}"
|
||||
tar -xf "/tmp/${ARCHIVE_NAME}" -C "\${RELEASE_DIR}"
|
||||
ln -sfn "\${RELEASE_DIR}" "\${APP_ROOT}/current"
|
||||
chown -R www-data:www-data "\${APP_ROOT}"
|
||||
|
||||
install -m 0644 "/tmp/${SERVICE_NAME}.service" "/etc/systemd/system/\${SERVICE_NAME}.service"
|
||||
systemctl daemon-reload
|
||||
systemctl enable "\${SERVICE_NAME}.service" >/dev/null
|
||||
systemctl restart "\${SERVICE_NAME}.service"
|
||||
|
||||
for attempt in \$(seq 1 20); do
|
||||
if curl -fsS --max-time 5 "http://127.0.0.1:\${FRONTEND_PORT}/" > /tmp/\${SERVICE_NAME}-health.out; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
grep -qi "SolaceScanScout" /tmp/\${SERVICE_NAME}-health.out
|
||||
EOF
|
||||
chmod +x "${TMP_DIR}/install-next-frontend.sh"
|
||||
|
||||
echo "== Uploading release bundle =="
|
||||
push_into_vmid "${TMP_DIR}/${ARCHIVE_NAME}" "/tmp/${ARCHIVE_NAME}" 0644
|
||||
push_into_vmid "${TMP_DIR}/${SERVICE_NAME}.service" "/tmp/${SERVICE_NAME}.service" 0644
|
||||
push_into_vmid "${TMP_DIR}/install-next-frontend.sh" "/tmp/install-next-frontend.sh" 0755
|
||||
echo ""
|
||||
|
||||
echo "== Installing release and restarting service =="
|
||||
run_in_vmid "/tmp/install-next-frontend.sh"
|
||||
echo ""
|
||||
|
||||
echo "== Verification =="
|
||||
run_in_vmid "systemctl is-active ${SERVICE_NAME}.service"
|
||||
run_in_vmid "curl -fsS --max-time 5 http://127.0.0.1:${FRONTEND_PORT}/ | grep -qi SolaceScanScout"
|
||||
echo "Service ${SERVICE_NAME} is running on 127.0.0.1:${FRONTEND_PORT}"
|
||||
echo ""
|
||||
echo "Nginx follow-up:"
|
||||
echo " Switch the explorer server block to proxy / and /_next/ to 127.0.0.1:${FRONTEND_PORT}"
|
||||
echo " while preserving /api/, /api/config/*, /explorer-api/v1/, /token-aggregation/api/v1/, /snap/, and /health."
|
||||
echo " Snippet: ${NGINX_SNIPPET}"
|
||||
if [[ -f "${VERIFY_SCRIPT}" ]]; then
|
||||
echo " After nginx/NPMplus cutover, verify with:"
|
||||
echo " bash ${VERIFY_SCRIPT} https://explorer.d-bis.org"
|
||||
fi
|
||||
echo ""
|
||||
echo "Next frontend deployment complete."
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deploy Explorer to Production
|
||||
# Copies frontend files to the Blockscout container
|
||||
# Deploy the legacy static explorer frontend to production
|
||||
# For the current Next.js frontend deployment, use scripts/deploy-next-frontend-to-vmid5000.sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
@@ -27,9 +27,12 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo "Deploy Chain 138 Explorer to Production"
|
||||
echo "Deploy Legacy Chain 138 Explorer Frontend"
|
||||
echo "════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
log_warn "This script deploys the legacy static SPA."
|
||||
log_warn "For the current Next.js frontend, use ./scripts/deploy-next-frontend-to-vmid5000.sh"
|
||||
echo ""
|
||||
|
||||
# Check if files exist
|
||||
if [ ! -f "$REPO_ROOT/frontend/public/index.html" ]; then
|
||||
@@ -67,4 +70,3 @@ echo ""
|
||||
log_info "Explorer URL: https://$DOMAIN/"
|
||||
log_info "To rollback: ssh root@$IP 'cp /var/www/html/index.html.backup.* /var/www/html/index.html'"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ echo ""
|
||||
|
||||
# Check CCIP Router for fee token
|
||||
echo "=== Checking CCIP Router for Fee Token ==="
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
ROUTER_CODE=$(cast code "$CCIP_ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$ROUTER_CODE" ] && [ "$ROUTER_CODE" != "0x" ]; then
|
||||
echo "✓ CCIP Router exists"
|
||||
|
||||
@@ -29,7 +29,7 @@ load_explorer_runtime_env
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_ADDRESS="$(resolve_address_value WETH9_ADDRESS WETH9_ADDRESS 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
ETHEREUM_MAINNET_SELECTOR="${ETHEREUM_MAINNET_SELECTOR:-$(ccip_destination_selector_by_name "Ethereum Mainnet")}"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
@@ -1,87 +1,150 @@
|
||||
/**
|
||||
* Explorer Frontend E2E Tests
|
||||
* Tests live SPA links, route coverage, and detail-page navigation on explorer.d-bis.org.
|
||||
* Run: npx playwright test explorer-monorepo/scripts/e2e-explorer-frontend.spec.ts --project=chromium
|
||||
*/
|
||||
|
||||
import { expect, test } from '@playwright/test'
|
||||
import { expect, test, type Page } from '@playwright/test'
|
||||
|
||||
const EXPLORER_URL = process.env.EXPLORER_URL || 'https://explorer.d-bis.org'
|
||||
const ADDRESS_TEST = '0x99b3511a2d315a497c8112c1fdd8d508d4b1e506'
|
||||
|
||||
async function expectBodyToContain(page: Parameters<typeof test>[0], pattern: RegExp) {
|
||||
const bodyText = await page.locator('body').textContent().catch(() => '') || ''
|
||||
expect(bodyText).toMatch(pattern)
|
||||
async function expectHeading(page: Page, name: RegExp) {
|
||||
await expect(page.getByRole('heading', { name })).toBeVisible({ timeout: 10000 })
|
||||
}
|
||||
|
||||
test.describe('Explorer Frontend - Path Coverage', () => {
|
||||
function collectUnexpectedConsoleErrors(page: Page, allowlist: RegExp[] = []) {
|
||||
const unexpectedErrors: string[] = []
|
||||
|
||||
page.on('console', (message) => {
|
||||
if (message.type() !== 'error') {
|
||||
return
|
||||
}
|
||||
|
||||
const text = message.text()
|
||||
if (allowlist.some((pattern) => pattern.test(text))) {
|
||||
return
|
||||
}
|
||||
|
||||
unexpectedErrors.push(text)
|
||||
})
|
||||
|
||||
return async () => {
|
||||
expect(unexpectedErrors).toEqual([])
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('Explorer Frontend - Route Coverage', () => {
|
||||
for (const route of [
|
||||
{ path: '/', matcher: /SolaceScanScout|Latest Blocks|Explorer/i },
|
||||
{ path: '/blocks', matcher: /Blocks|Block/i },
|
||||
{ path: '/transactions', matcher: /Transactions|Transaction/i },
|
||||
{ path: '/addresses', matcher: /Addresses|Address/i },
|
||||
{ path: '/watchlist', matcher: /Watchlist|Explorer/i },
|
||||
{ path: '/pools', matcher: /Pools|Liquidity/i },
|
||||
{ path: '/liquidity', matcher: /Liquidity|Route|Pool/i },
|
||||
{ path: '/bridge', matcher: /Bridge|Explorer/i },
|
||||
{ path: '/weth', matcher: /WETH|Explorer/i },
|
||||
{ path: '/', heading: /SolaceScanScout/i },
|
||||
{ path: '/blocks', heading: /^Blocks$/i },
|
||||
{ path: '/transactions', heading: /^Transactions$/i },
|
||||
{ path: '/addresses', heading: /^Addresses$/i },
|
||||
{ path: '/watchlist', heading: /^Watchlist$/i },
|
||||
{ path: '/pools', heading: /^Pools$/i },
|
||||
{ path: '/liquidity', heading: /Public liquidity, route discovery, and execution access points/i },
|
||||
{ path: '/wallet', heading: /Wallet & MetaMask/i },
|
||||
{ path: '/tokens', heading: /^Tokens$/i },
|
||||
{ path: '/search', heading: /^Search$/i },
|
||||
]) {
|
||||
test(`${route.path} loads`, async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}${route.path}`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
await page.goto(`${EXPLORER_URL}${route.path}`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page).toHaveURL(new RegExp(route.path === '/' ? '/?$' : route.path.replace('/', '\\/')), { timeout: 8000 })
|
||||
await expectBodyToContain(page, route.matcher)
|
||||
await expectHeading(page, route.heading)
|
||||
})
|
||||
}
|
||||
|
||||
test('/address/:address loads address detail', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/address/${ADDRESS_TEST}`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
await page.waitForSelector('#addressDetailBreadcrumb', { state: 'attached', timeout: 15000 })
|
||||
await expectBodyToContain(page, /Address|Balance|Transaction|Explorer/i)
|
||||
test('/addresses/:address loads address detail', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/addresses/${ADDRESS_TEST}`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expectHeading(page, /Address/i)
|
||||
await expect(page.getByText(/Back to addresses/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Explorer Frontend - Nav and Detail Links', () => {
|
||||
test('MetaMask Snap link is present', async ({ page }) => {
|
||||
await page.goto(EXPLORER_URL, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
const snapLink = page.locator('a[href="/snap/"]').first()
|
||||
await expect(snapLink).toBeVisible({ timeout: 8000 })
|
||||
await expect(snapLink).toHaveAttribute('href', '/snap/')
|
||||
test.describe('Explorer Frontend - Current Navigation', () => {
|
||||
test('global shell is present on both app and pages routes', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/wallet`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page.getByRole('link', { name: /Go to explorer home/i })).toBeVisible({ timeout: 10000 })
|
||||
await expect(page.getByText(/Support:/i)).toBeVisible({ timeout: 10000 })
|
||||
|
||||
await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page.getByRole('link', { name: /Go to explorer home/i })).toBeVisible({ timeout: 10000 })
|
||||
await expect(page.getByText(/Support:/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
test('Address breadcrumb home link returns to root', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/address/${ADDRESS_TEST}`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
await page.waitForSelector('#addressDetailView.active #addressDetailBreadcrumb', { state: 'visible', timeout: 15000 })
|
||||
const homeLink = page.locator('#addressDetailView.active #addressDetailBreadcrumb a[href="/"]').first()
|
||||
await expect(homeLink).toBeVisible({ timeout: 8000 })
|
||||
await homeLink.click()
|
||||
await expect(page).toHaveURL(new RegExp(`${EXPLORER_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/?$`), { timeout: 8000 })
|
||||
test('wallet page exposes the current Chain 138 Snap action', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/wallet`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page.getByRole('button', { name: /Install Open Snap/i })).toBeVisible({ timeout: 10000 })
|
||||
await expect(page.getByText(/Chain 138 Open Snap/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
test('Blocks list opens block detail view', async ({ page }) => {
|
||||
test('blocks list links to a current block detail route', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/blocks`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
const blockLink = page.locator('#blocksList tbody tr:first-child td:first-child a').first()
|
||||
await expect(blockLink).toBeVisible({ timeout: 8000 })
|
||||
const blockLink = page.locator('a[href^="/blocks/"]').first()
|
||||
await expect(blockLink).toBeVisible({ timeout: 10000 })
|
||||
await blockLink.click()
|
||||
await expect(page).toHaveURL(/\/block\/\d+/, { timeout: 8000 })
|
||||
await expect(page.locator('#blockDetailView.active #blockDetailBreadcrumb')).toBeVisible({ timeout: 8000 })
|
||||
await expect(page).toHaveURL(/\/blocks\/\d+$/, { timeout: 10000 })
|
||||
await expect(page.getByText(/Back to blocks/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
test('Transactions list opens transaction detail view', async ({ page }) => {
|
||||
test('transactions list links to a current transaction detail route', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/transactions`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
const transactionLink = page.locator('#transactionsList tbody tr:first-child td:first-child a').first()
|
||||
await expect(transactionLink).toBeVisible({ timeout: 8000 })
|
||||
await transactionLink.click()
|
||||
await expect(page).toHaveURL(/\/tx\/0x[a-f0-9]+/i, { timeout: 8000 })
|
||||
await expect(page.locator('#transactionDetailView.active #transactionDetailBreadcrumb')).toBeVisible({ timeout: 8000 })
|
||||
const transactionLinks = page.locator('a[href^="/transactions/"]')
|
||||
const transactionCount = await transactionLinks.count()
|
||||
|
||||
if (transactionCount === 0) {
|
||||
await expect(page.getByText(/Recent transactions are unavailable right now/i)).toBeVisible({ timeout: 10000 })
|
||||
await expect(page.getByRole('button', { name: /^Next$/i })).toBeDisabled()
|
||||
return
|
||||
}
|
||||
|
||||
const href = await transactionLinks.first().getAttribute('href')
|
||||
expect(href).toMatch(/^\/transactions\/0x[a-f0-9]+$/i)
|
||||
await page.goto(`${EXPLORER_URL}${href}`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page).toHaveURL(/\/transactions\/0x[a-f0-9]+$/i, { timeout: 10000 })
|
||||
await expect(page.getByText(/Back to transactions/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
test('Addresses list opens address detail view', async ({ page }) => {
|
||||
test('addresses page opens a current address detail route', async ({ page }) => {
|
||||
await page.goto(`${EXPLORER_URL}/addresses`, { waitUntil: 'networkidle', timeout: 20000 })
|
||||
const addressLink = page.locator('#addressesList tbody tr:first-child td:first-child a').first()
|
||||
await expect(addressLink).toBeVisible({ timeout: 8000 })
|
||||
await expect(page.locator('#addressesList tbody tr').first()).not.toContainText('N/A')
|
||||
await addressLink.click()
|
||||
await expect(page).toHaveURL(/\/address\/0x[a-f0-9]+/i, { timeout: 8000 })
|
||||
await expect(page.locator('#addressDetailView.active #addressDetailBreadcrumb')).toBeVisible({ timeout: 8000 })
|
||||
await page.getByPlaceholder('0x...').fill(ADDRESS_TEST)
|
||||
await page.getByRole('button', { name: /Open address/i }).click()
|
||||
await expect(page).toHaveURL(new RegExp(`/addresses/${ADDRESS_TEST}$`, 'i'), { timeout: 10000 })
|
||||
await expect(page.getByText(/Back to addresses/i)).toBeVisible({ timeout: 10000 })
|
||||
})
|
||||
|
||||
test('homepage keeps recent blocks visible when stats are temporarily unavailable', async ({ page }) => {
|
||||
const assertConsole = collectUnexpectedConsoleErrors(page, [
|
||||
/api\/v2\/stats/i,
|
||||
/503/i,
|
||||
/service unavailable/i,
|
||||
])
|
||||
|
||||
await page.route(`${EXPLORER_URL}/api/v2/stats`, async (route) => {
|
||||
await route.fulfill({
|
||||
status: 503,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({ error: 'service_unavailable' }),
|
||||
})
|
||||
})
|
||||
|
||||
await page.route(new RegExp(`${EXPLORER_URL.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}/api/v2/blocks\\?.*`), async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{
|
||||
hash: '0xabc',
|
||||
height: 4321,
|
||||
timestamp: '2026-04-05T00:00:00.000Z',
|
||||
miner: { hash: '0xdef' },
|
||||
transaction_count: 7,
|
||||
gas_used: 21000,
|
||||
gas_limit: 30000000,
|
||||
},
|
||||
],
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
await page.goto(`${EXPLORER_URL}/`, { waitUntil: 'domcontentloaded', timeout: 20000 })
|
||||
await expect(page.getByText(/Live network stats are temporarily unavailable/i)).toBeVisible({ timeout: 10000 })
|
||||
await expect(page.getByRole('link', { name: /Block #4321/i })).toBeVisible({ timeout: 10000 })
|
||||
await assertConsole()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -15,6 +15,7 @@ echo "This script fixes:"
|
||||
echo " 1. Database migrations (creates missing tables)"
|
||||
echo " 2. Static assets (builds and digests assets)"
|
||||
echo " 3. Proper Blockscout startup command"
|
||||
echo " 4. Public API stability under indexer load"
|
||||
echo ""
|
||||
|
||||
cat << 'COMMANDS'
|
||||
@@ -117,15 +118,43 @@ if [ -f "$BLOCKSCOUT_DIR/docker-compose.yml" ]; then
|
||||
# Backup original
|
||||
cp "$BLOCKSCOUT_DIR/docker-compose.yml" "$BLOCKSCOUT_DIR/docker-compose.yml.backup"
|
||||
|
||||
# Update command to explicitly start Blockscout
|
||||
# Update command to explicitly start Blockscout, use the local Docker-network
|
||||
# Postgres path, and apply safer DB pool defaults.
|
||||
sed -i 's|command:.*|command: bin/blockscout start|g' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/blockscout:/a\ command: bin/blockscout start' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
|
||||
grep -q 'DATABASE_URL=' "$BLOCKSCOUT_DIR/docker-compose.yml" && \
|
||||
sed -i 's#DATABASE_URL=.*#DATABASE_URL=postgresql://blockscout:blockscout@postgres:5432/blockscout?sslmode=disable#' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/environment:/a\ - DATABASE_URL=postgresql://blockscout:blockscout@postgres:5432/blockscout?sslmode=disable' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
grep -q 'POOL_SIZE=' "$BLOCKSCOUT_DIR/docker-compose.yml" && \
|
||||
sed -i 's/POOL_SIZE=.*/POOL_SIZE=50/' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/SECRET_KEY_BASE=/a\ - POOL_SIZE=50' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
grep -q 'POOL_SIZE_API=' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/POOL_SIZE=50/a\ - POOL_SIZE_API=50' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
grep -q 'DATABASE_QUEUE_TARGET=' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/POOL_SIZE_API=50/a\ - DATABASE_QUEUE_TARGET=5s' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
grep -q 'INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/DATABASE_QUEUE_TARGET=5s/a\ - INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=true' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
grep -q 'ECTO_USE_SSL=' "$BLOCKSCOUT_DIR/docker-compose.yml" && \
|
||||
sed -i 's/ECTO_USE_SSL=.*/ECTO_USE_SSL=false/' "$BLOCKSCOUT_DIR/docker-compose.yml" || \
|
||||
sed -i '/INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=true/a\ - ECTO_USE_SSL=false' "$BLOCKSCOUT_DIR/docker-compose.yml"
|
||||
|
||||
echo "✅ Updated docker-compose.yml"
|
||||
else
|
||||
echo "⚠️ docker-compose.yml not found at $BLOCKSCOUT_DIR"
|
||||
echo "You may need to update it manually to include:"
|
||||
echo " command: bin/blockscout start"
|
||||
echo " POOL_SIZE=50"
|
||||
echo " POOL_SIZE_API=50"
|
||||
echo " DATABASE_QUEUE_TARGET=5s"
|
||||
echo " INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=true"
|
||||
echo " ECTO_USE_SSL=false"
|
||||
echo " DATABASE_URL=postgresql://blockscout:blockscout@postgres:5432/blockscout?sslmode=disable"
|
||||
fi
|
||||
|
||||
# ============================================================
|
||||
@@ -142,14 +171,28 @@ docker rm $BLOCKSCOUT_CONTAINER 2>/dev/null || true
|
||||
# Restart using docker-compose (if available)
|
||||
if [ -f "$BLOCKSCOUT_DIR/docker-compose.yml" ]; then
|
||||
cd "$BLOCKSCOUT_DIR"
|
||||
docker compose up -d blockscout
|
||||
docker compose up -d blockscout || docker-compose up -d blockscout
|
||||
else
|
||||
# Manual start with correct command
|
||||
echo "Starting Blockscout manually..."
|
||||
EXTRA_ENV_FILE=()
|
||||
if [ -f "$BLOCKSCOUT_DIR/.env" ]; then
|
||||
EXTRA_ENV_FILE+=(--env-file "$BLOCKSCOUT_DIR/.env")
|
||||
else
|
||||
echo "⚠️ $BLOCKSCOUT_DIR/.env not found; relying on explicit fallback env only"
|
||||
fi
|
||||
docker run -d \
|
||||
--name blockscout \
|
||||
--env-file "$BLOCKSCOUT_DIR/.env" \
|
||||
--restart unless-stopped \
|
||||
--network blockscout_blockscout-network \
|
||||
"${EXTRA_ENV_FILE[@]}" \
|
||||
-p 4000:4000 \
|
||||
-e DATABASE_URL=postgresql://blockscout:blockscout@postgres:5432/blockscout?sslmode=disable \
|
||||
-e POOL_SIZE=50 \
|
||||
-e POOL_SIZE_API=50 \
|
||||
-e DATABASE_QUEUE_TARGET=5s \
|
||||
-e INDEXER_DISABLE_PENDING_TRANSACTIONS_FETCHER=true \
|
||||
-e ECTO_USE_SSL=false \
|
||||
blockscout/blockscout:latest \
|
||||
bin/blockscout start
|
||||
fi
|
||||
@@ -231,4 +274,3 @@ echo "If you just need to run migrations quickly:"
|
||||
echo ""
|
||||
echo "pct exec 5000 -- bash -c 'BLOCKSCOUT_CONTAINER=\$(docker ps -a | grep blockscout | grep -v postgres | awk \"{print \\\$1}\" | head -1); docker exec -it \$BLOCKSCOUT_CONTAINER bin/blockscout eval \"Explorer.Release.migrate()\"'"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
ETHEREUM_MAINNET_SELECTOR="${ETHEREUM_MAINNET_SELECTOR:-$(ccip_destination_selector_by_name "Ethereum Mainnet")}"
|
||||
|
||||
|
||||
@@ -1,17 +1,21 @@
|
||||
#!/bin/bash
|
||||
# Fix database connection and run migration
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
echo "=== Database Connection Fix ==="
|
||||
echo ""
|
||||
|
||||
# Database credentials for custom explorer backend
|
||||
# Database credentials for the selected deployment mode
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
DB_USER="${DB_USER:-explorer}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-L@ker\$2010}"
|
||||
DB_NAME="${DB_NAME:-explorer}"
|
||||
if [ -z "${DB_PASSWORD:-}" ]; then
|
||||
echo "❌ DB_PASSWORD is required"
|
||||
echo " Export DB_PASSWORD before running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Testing connection with:"
|
||||
echo " Host: $DB_HOST:$DB_PORT"
|
||||
@@ -21,38 +25,77 @@ echo ""
|
||||
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
|
||||
sql_scalar() {
|
||||
local sql="$1"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -Atc "$sql" 2>/dev/null | tr -d ' '
|
||||
}
|
||||
|
||||
is_shared_blockscout_db() {
|
||||
sql_scalar "
|
||||
SELECT CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses' AND column_name = 'address'
|
||||
)
|
||||
THEN 'yes'
|
||||
ELSE 'no'
|
||||
END;
|
||||
"
|
||||
}
|
||||
|
||||
# Test connection
|
||||
echo -n "Testing connection... "
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo "✅ Connected"
|
||||
|
||||
# Check if migration tables exist
|
||||
echo -n "Checking for track schema tables... "
|
||||
TABLE_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('wallet_nonces', 'operator_roles', 'addresses', 'token_transfers');" -t 2>/dev/null | tr -d ' ')
|
||||
|
||||
if [ "$TABLE_COUNT" -ge "4" ]; then
|
||||
echo "✅ All tables exist ($TABLE_COUNT/4)"
|
||||
|
||||
if [ "$(is_shared_blockscout_db || echo no)" = "yes" ]; then
|
||||
echo "Mode: shared Blockscout DB detected"
|
||||
REQUIRED_TABLES=(operator_events operator_ip_whitelist operator_roles wallet_nonces)
|
||||
else
|
||||
echo "Mode: standalone explorer DB detected"
|
||||
REQUIRED_TABLES=(wallet_nonces operator_roles addresses token_transfers)
|
||||
fi
|
||||
|
||||
echo -n "Checking required tables... "
|
||||
table_list=""
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
if [ -n "$table_list" ]; then
|
||||
table_list+=", "
|
||||
fi
|
||||
table_list+="'$table'"
|
||||
done
|
||||
TABLE_COUNT=$(sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN (${table_list});")
|
||||
|
||||
if [ "${TABLE_COUNT:-0}" -ge "${#REQUIRED_TABLES[@]}" ]; then
|
||||
echo "✅ All required tables exist (${TABLE_COUNT}/${#REQUIRED_TABLES[@]})"
|
||||
echo ""
|
||||
echo "✅ Database is ready!"
|
||||
else
|
||||
echo "⚠️ Tables missing ($TABLE_COUNT/4 found)"
|
||||
echo "⚠️ Tables missing (${TABLE_COUNT:-0}/${#REQUIRED_TABLES[@]} found)"
|
||||
echo ""
|
||||
echo "Running migration..."
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MIGRATION_FILE="$PROJECT_ROOT/backend/database/migrations/0010_track_schema.up.sql"
|
||||
|
||||
if [ -f "$MIGRATION_FILE" ]; then
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_FILE" 2>&1 | tail -10; then
|
||||
|
||||
if [ -f "$PROJECT_ROOT/scripts/run-migration-0010.sh" ]; then
|
||||
if DB_HOST="$DB_HOST" DB_PORT="$DB_PORT" DB_USER="$DB_USER" DB_PASSWORD="$DB_PASSWORD" DB_NAME="$DB_NAME" \
|
||||
bash "$PROJECT_ROOT/scripts/run-migration-0010.sh"; then
|
||||
echo ""
|
||||
echo "✅ Migration completed"
|
||||
else
|
||||
echo ""
|
||||
echo "⚠️ Migration may have partially completed or tables already exist"
|
||||
echo "❌ Migration failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "❌ Migration file not found: $MIGRATION_FILE"
|
||||
echo "❌ Migration helper not found: $PROJECT_ROOT/scripts/run-migration-0010.sh"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
@@ -66,7 +109,9 @@ else
|
||||
echo " PGPASSWORD='postgres-password' psql -h $DB_HOST -U postgres -c '\l'"
|
||||
echo "4. Verify password is correct"
|
||||
echo ""
|
||||
echo "Note: Custom explorer backend uses 'explorer' user, not 'blockscout'"
|
||||
echo "Use the credentials for your deployment mode:"
|
||||
echo " - standalone explorer DB: explorer / explorer"
|
||||
echo " - shared Blockscout DB: blockscout / blockscout"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -75,10 +120,10 @@ unset PGPASSWORD
|
||||
echo ""
|
||||
echo "=== Next Steps ==="
|
||||
echo "1. Restart API server with database password:"
|
||||
echo " export DB_PASSWORD='$DB_PASSWORD'"
|
||||
echo " export DB_PASSWORD='<your explorer DB password>'"
|
||||
echo " # Set DB_USER / DB_NAME too if you are using the shared Blockscout DB"
|
||||
echo " cd backend && ./bin/api-server"
|
||||
echo ""
|
||||
echo "2. Test health endpoint:"
|
||||
echo " curl http://localhost:8080/health"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -141,7 +141,7 @@ server {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
|
||||
# Explorer config API (token list, networks) - serve from /var/www/html/config/
|
||||
# Explorer config API (token list, networks, capabilities) - serve from /var/www/html/config/
|
||||
# Deploy files with: ./scripts/deploy-explorer-config-to-vmid5000.sh
|
||||
location = /api/config/token-list {
|
||||
default_type application/json;
|
||||
@@ -155,6 +155,12 @@ server {
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
||||
}
|
||||
location = /api/config/capabilities {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json;
|
||||
}
|
||||
|
||||
# API endpoint (for Blockscout API)
|
||||
location /api/ {
|
||||
@@ -190,13 +196,13 @@ EOF
|
||||
echo "✅ Clean configuration created: $CONFIG_FILE"
|
||||
echo ""
|
||||
|
||||
# Step 5.5: Ensure config directory exists for /api/config/token-list and /api/config/networks
|
||||
# Step 5.5: Ensure config directory exists for /api/config/token-list, /api/config/networks, and /api/config/capabilities
|
||||
echo "=== Step 5.5: Config Directory for Token List ==="
|
||||
mkdir -p /var/www/html/config
|
||||
if [ -f "/var/www/html/config/DUAL_CHAIN_TOKEN_LIST.tokenlist.json" ]; then
|
||||
echo "Config files already present in /var/www/html/config/"
|
||||
else
|
||||
echo "Note: Run deploy-explorer-config-to-vmid5000.sh from repo root to deploy token list. /api/config/* will 404 until then."
|
||||
echo "Note: Run deploy-explorer-config-to-vmid5000.sh from repo root to deploy token list, networks, and capabilities. /api/config/* will 404 until then."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
@@ -273,4 +279,3 @@ echo "To view logs:"
|
||||
echo " tail -f /var/log/nginx/blockscout-access.log"
|
||||
echo " tail -f /var/log/nginx/blockscout-error.log"
|
||||
echo ""
|
||||
|
||||
|
||||
@@ -75,7 +75,7 @@ server {
|
||||
add_header Access-Control-Allow-Headers "Content-Type";
|
||||
}
|
||||
|
||||
# Explorer config API (token list, networks) - serve from /var/www/html/config/
|
||||
# Explorer config API (token list, networks, capabilities) - serve from /var/www/html/config/
|
||||
location = /api/config/token-list {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
@@ -88,6 +88,12 @@ server {
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
||||
}
|
||||
location = /api/config/capabilities {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json;
|
||||
}
|
||||
|
||||
location /health {
|
||||
access_log off;
|
||||
@@ -136,8 +142,16 @@ server {
|
||||
add_header Cache-Control "public, immutable";
|
||||
}
|
||||
|
||||
# Static JSON under /config/ (topology graph, optional operator verify snapshot)
|
||||
location /config/ {
|
||||
alias /var/www/html/config/;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
default_type application/json;
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
}
|
||||
|
||||
# SPA paths on HTTP (for internal/LAN tests) - serve index.html before redirect
|
||||
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator)(/|$) {
|
||||
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator|system|routes|pools|more)(/|$) {
|
||||
root /var/www/html;
|
||||
try_files /index.html =404;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
@@ -239,7 +253,7 @@ server {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
}
|
||||
|
||||
# Explorer config API (token list, networks) - serve from /var/www/html/config/
|
||||
# Explorer config API (token list, networks, capabilities) - serve from /var/www/html/config/
|
||||
location = /api/config/token-list {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
@@ -252,6 +266,12 @@ server {
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/DUAL_CHAIN_NETWORKS.json;
|
||||
}
|
||||
location = /api/config/capabilities {
|
||||
default_type application/json;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Cache-Control "public, max-age=3600";
|
||||
alias /var/www/html/config/CHAIN138_RPC_CAPABILITIES.json;
|
||||
}
|
||||
|
||||
# API endpoint (for Blockscout API)
|
||||
location /api/ {
|
||||
@@ -287,9 +307,17 @@ server {
|
||||
proxy_connect_timeout 75s;
|
||||
}
|
||||
|
||||
# SPA paths: /address, /tx, /block, /token, /tokens, /blocks, /transactions, /bridge, /weth, /liquidity, /watchlist, /nft, /home, /analytics, /operator
|
||||
# Static JSON: topology-graph.json, optional mission-control-verify.json
|
||||
location /config/ {
|
||||
alias /var/www/html/config/;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
default_type application/json;
|
||||
add_header Cache-Control "public, max-age=300";
|
||||
}
|
||||
|
||||
# SPA paths: path-based routing for explorer-spa.js
|
||||
# Must serve index.html so path-based routing works (regex takes precedence over proxy)
|
||||
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator)(/|$) {
|
||||
location ~ ^/(address|tx|block|token|tokens|blocks|transactions|bridge|weth|liquidity|watchlist|nft|home|analytics|operator|system|routes|pools|more)(/|$) {
|
||||
root /var/www/html;
|
||||
try_files /index.html =404;
|
||||
add_header Cache-Control "no-store, no-cache, must-revalidate";
|
||||
|
||||
@@ -29,7 +29,7 @@ RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x73ADaF7dBa95221c080db5631466d2bC54f6a76B)"
|
||||
WETH9="$(resolve_address_value WETH9_ADDRESS WETH9_ADDRESS 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)"
|
||||
WETH10="$(resolve_address_value WETH10_ADDRESS WETH10_ADDRESS 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
PASSED=0
|
||||
|
||||
@@ -26,7 +26,7 @@ load_explorer_runtime_env
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x326C977E6efc84E512bB9C30f76E30c160eD06FB)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
AMOUNT_PER_BRIDGE="${1:-10}" # Default 10 LINK per bridge
|
||||
|
||||
@@ -31,7 +31,7 @@ OUTPUT_FILE="${1:-docs/CCIP_STATUS_REPORT_$(date +%Y%m%d_%H%M%S).md}"
|
||||
|
||||
# Router status
|
||||
echo "### CCIP Router"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
ROUTER_BYTECODE=$(cast code "$ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$ROUTER_BYTECODE" ] && [ "$ROUTER_BYTECODE" != "0x" ]; then
|
||||
echo "- **Status**: ✅ Deployed"
|
||||
@@ -55,7 +55,7 @@ OUTPUT_FILE="${1:-docs/CCIP_STATUS_REPORT_$(date +%Y%m%d_%H%M%S).md}"
|
||||
|
||||
# Bridge status
|
||||
echo "### Bridge Contracts"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
WETH9_BRIDGE_BYTECODE=$(cast code "$WETH9_BRIDGE" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
53
scripts/generate-topology-graph.sh
Executable file
53
scripts/generate-topology-graph.sh
Executable file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env bash
|
||||
# Emit explorer-monorepo/frontend/public/config/topology-graph.json from
|
||||
# repo-root config/smart-contracts-master.json (Chain 138 contracts as nodes).
|
||||
# Run from any cwd; prefers monorepo ../../config when invoked from scripts/.
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
MONO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
OUT="$SCRIPT_DIR/../frontend/public/config/topology-graph.json"
|
||||
MASTER="$MONO_ROOT/config/smart-contracts-master.json"
|
||||
EXPLORER_PUBLIC_BASE="${EXPLORER_PUBLIC_BASE:-https://explorer.d-bis.org}"
|
||||
BASE="${EXPLORER_PUBLIC_BASE%/}"
|
||||
|
||||
if [[ ! -f "$MASTER" ]]; then
|
||||
echo "warning: smart-contracts-master.json not found; leaving existing topology-graph.json unchanged" >&2
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
|
||||
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
||||
|
||||
LIQ_JSON="null"
|
||||
if [[ -n "${TOKEN_AGGREGATION_BASE_URL:-}" ]]; then
|
||||
CUSDT="0x93E66202A11B1772E55407B32B44e5Cd8eda7f22"
|
||||
TA_URL="${TOKEN_AGGREGATION_BASE_URL%/}/api/v1/tokens/${CUSDT}/pools?chainId=138"
|
||||
code=$(curl -sS -o /tmp/ta-topology-pools.json -w "%{http_code}" -m 20 "$TA_URL" || echo "000")
|
||||
if [[ "$code" == "200" ]] && [[ -s /tmp/ta-topology-pools.json ]]; then
|
||||
pc=""
|
||||
pc=$(jq '(.pools // .data.pools // []) | length' /tmp/ta-topology-pools.json 2>/dev/null || true)
|
||||
if [[ "$pc" =~ ^[0-9]+$ ]]; then
|
||||
LIQ_JSON=$(jq -n --argjson poolCount "$pc" --arg token "$CUSDT" '{chainId:"138",sampleToken:$token,poolCount:$poolCount}')
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
jq --arg ts "$ts" --arg base "$BASE" --argjson liquiditySample "$LIQ_JSON" '
|
||||
.chains["138"].contracts // {} | to_entries
|
||||
| map(select(.value | type == "string"))
|
||||
| map(select(.value | test("^0x[a-fA-F0-9]{40}$")))
|
||||
| .[:40]
|
||||
| . as $pairs
|
||||
| ($pairs | map({data: {id: (.value | ascii_downcase), label: (.key + " (" + (.value | .[0:10]) + "…)"), href: ($base + "/address/" + (.value | ascii_downcase))}})) as $nodes
|
||||
| ($pairs | map(select(.key | test("CCIP|Router|Bridge|UniversalCCIP")))) as $bridgeish
|
||||
| ($pairs | map(select(.key | test("DODO|PMM|Pool_cUS")))) as $pmmish
|
||||
| ($nodes)
|
||||
+ [{data: {id: "stack_rpc", label: "Chain 138 RPC (logical)"}}]
|
||||
+ ($bridgeish | map(.value | ascii_downcase) | unique | map({data: {source: "stack_rpc", target: ., label: "settlement"}}))
|
||||
+ ($pmmish | map(.value | ascii_downcase) | unique | map({data: {source: "stack_rpc", target: ., label: "liquidity"}}))
|
||||
| {generatedAt: $ts, description: "Auto-generated from config/smart-contracts-master.json (subset)", liquiditySample: $liquiditySample, elements: .}
|
||||
' "$MASTER" >"$OUT.tmp"
|
||||
mv "$OUT.tmp" "$OUT"
|
||||
echo "wrote $OUT"
|
||||
@@ -30,7 +30,7 @@ RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x326C977E6efc84E512bB9C30f76E30c160eD06FB)"
|
||||
WETH9="$(resolve_address_value WETH9_ADDRESS WETH9_ADDRESS 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)"
|
||||
WETH10="$(resolve_address_value WETH10_ADDRESS WETH10_ADDRESS 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
if [ -z "${PRIVATE_KEY:-}" ]; then
|
||||
|
||||
@@ -28,7 +28,7 @@ load_explorer_runtime_env
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x326C977E6efc84E512bB9C30f76E30c160eD06FB)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
if [ -z "${PRIVATE_KEY:-}" ]; then
|
||||
|
||||
@@ -25,7 +25,7 @@ load_explorer_runtime_env
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
LINK_TOKEN="$(resolve_address_value LINK_TOKEN LINK_TOKEN 0x326C977E6efc84E512bB9C30f76E30c160eD06FB)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
ALERT_THRESHOLD="${1:-1.0}" # Default 1 LINK
|
||||
|
||||
|
||||
@@ -127,9 +127,9 @@ fi
|
||||
log_info ""
|
||||
log_info "5. Contract Deployments"
|
||||
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
SENDER="0x105F8A15b819948a89153505762444Ee9f324684"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
ROUTER_BYTECODE=$(cast code "$ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Run all deployment steps
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
@@ -10,15 +10,28 @@ echo "=== Running All Deployment Steps ==="
|
||||
echo ""
|
||||
|
||||
# Database credentials
|
||||
export DB_PASSWORD='L@ker$2010'
|
||||
export DB_HOST="${DB_HOST:-localhost}"
|
||||
export DB_PORT="${DB_PORT:-5432}"
|
||||
export DB_USER="${DB_USER:-explorer}"
|
||||
export DB_NAME="${DB_NAME:-explorer}"
|
||||
if [ -z "${DB_PASSWORD:-}" ]; then
|
||||
echo "❌ DB_PASSWORD is required"
|
||||
echo " Export DB_PASSWORD before running this script."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
extract_error_message() {
|
||||
local body="${1:-}"
|
||||
if [ -z "$body" ]; then
|
||||
return 0
|
||||
fi
|
||||
echo "$body" | jq -r '.error.message // empty' 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Step 1: Test database connection
|
||||
echo "Step 1: Testing database connection..."
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
if psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT 1;" > /dev/null 2>&1; then
|
||||
echo "✅ Database connection successful"
|
||||
else
|
||||
echo "❌ Database connection failed"
|
||||
@@ -29,18 +42,18 @@ echo ""
|
||||
|
||||
# Step 2: Check existing tables
|
||||
echo "Step 2: Checking for existing tables..."
|
||||
TABLE_COUNT=$(psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('wallet_nonces', 'operator_roles', 'addresses', 'token_transfers');" -t 2>/dev/null | tr -d ' ')
|
||||
TABLE_COUNT=$(psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('wallet_nonces', 'operator_roles', 'addresses', 'token_transfers');" -t 2>/dev/null | tr -d ' ')
|
||||
echo "Found $TABLE_COUNT/4 track schema tables"
|
||||
echo ""
|
||||
|
||||
# Step 3: Run migration if needed
|
||||
if [ "$TABLE_COUNT" -lt "4" ]; then
|
||||
echo "Step 3: Running database migration..."
|
||||
MIGRATION_FILE="$PROJECT_ROOT/backend/database/migrations/0010_track_schema.up.sql"
|
||||
if psql -h "$DB_HOST" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_FILE" > /dev/null 2>&1; then
|
||||
if bash "$SCRIPT_DIR/run-migration-0010.sh" > /dev/null; then
|
||||
echo "✅ Migration completed"
|
||||
else
|
||||
echo "⚠️ Migration may have partially completed (some tables may already exist)"
|
||||
echo "❌ Migration failed"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Step 3: Migration already complete (tables exist)"
|
||||
@@ -111,13 +124,21 @@ else
|
||||
fi
|
||||
|
||||
echo -n " Auth nonce... "
|
||||
NONCE_CODE=$(curl -s -w "%{http_code}" -o /dev/null -X POST "http://localhost:8080/api/v1/auth/nonce" \
|
||||
NONCE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "http://localhost:8080/api/v1/auth/nonce" \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"address":"0x1234567890123456789012345678901234567890"}')
|
||||
NONCE_CODE=$(echo "$NONCE_RESPONSE" | tail -n1)
|
||||
NONCE_BODY=$(echo "$NONCE_RESPONSE" | sed '$d')
|
||||
NONCE_ERROR=$(extract_error_message "$NONCE_BODY")
|
||||
if [ "$NONCE_CODE" = "200" ]; then
|
||||
echo "✅"
|
||||
else
|
||||
echo "⚠️ (HTTP $NONCE_CODE)"
|
||||
echo "❌ (HTTP $NONCE_CODE)"
|
||||
if [ -n "$NONCE_ERROR" ]; then
|
||||
echo " $NONCE_ERROR"
|
||||
fi
|
||||
echo " Wallet auth is not healthy; stop here until migration and DB access are fixed."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo -n " Track 2 auth check... "
|
||||
@@ -155,4 +176,3 @@ echo " 3. Monitor: tail -f backend/logs/api-server.log"
|
||||
echo ""
|
||||
|
||||
unset PGPASSWORD
|
||||
|
||||
|
||||
127
scripts/run-migration-0010.sh
Executable file → Normal file
127
scripts/run-migration-0010.sh
Executable file → Normal file
@@ -1,19 +1,30 @@
|
||||
#!/bin/bash
|
||||
# Run migration 0010_track_schema.up.sql
|
||||
# Run migration 0010_track_schema with shared-DB detection.
|
||||
#
|
||||
# - Standalone explorer DB: applies the full Track 2-4 schema.
|
||||
# - Shared Blockscout DB: applies only explorer-owned auth/operator tables to
|
||||
# avoid colliding with Blockscout's existing addresses/token_transfers schema.
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MIGRATION_DIR="$PROJECT_ROOT/backend/database/migrations"
|
||||
MIGRATION_FILE="$MIGRATION_DIR/0010_track_schema.up.sql"
|
||||
FULL_MIGRATION_FILE="$MIGRATION_DIR/0010_track_schema.up.sql"
|
||||
AUTH_ONLY_MIGRATION_FILE="$MIGRATION_DIR/0010_track_schema.auth_only.sql"
|
||||
MIGRATION_FILE="$FULL_MIGRATION_FILE"
|
||||
MIGRATION_MODE="full"
|
||||
REQUIRED_TABLES=(addresses token_transfers operator_roles wallet_nonces)
|
||||
|
||||
echo "=== Running Track Schema Migration (0010) ==="
|
||||
echo ""
|
||||
|
||||
# Check if migration file exists
|
||||
if [ ! -f "$MIGRATION_FILE" ]; then
|
||||
echo "❌ Migration file not found: $MIGRATION_FILE"
|
||||
if [ ! -f "$FULL_MIGRATION_FILE" ]; then
|
||||
echo "❌ Migration file not found: $FULL_MIGRATION_FILE"
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$AUTH_ONLY_MIGRATION_FILE" ]; then
|
||||
echo "❌ Migration file not found: $AUTH_ONLY_MIGRATION_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
@@ -24,29 +35,100 @@ DB_USER="${DB_USER:-explorer}"
|
||||
DB_PASSWORD="${DB_PASSWORD:-changeme}"
|
||||
DB_NAME="${DB_NAME:-explorer}"
|
||||
|
||||
sql_scalar() {
|
||||
local sql="$1"
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -Atc "$sql" 2>/dev/null | tr -d ' '
|
||||
}
|
||||
|
||||
is_shared_blockscout_db() {
|
||||
sql_scalar "
|
||||
SELECT CASE
|
||||
WHEN EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses'
|
||||
) AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM information_schema.columns
|
||||
WHERE table_schema = 'public' AND table_name = 'addresses' AND column_name = 'address'
|
||||
)
|
||||
THEN 'yes'
|
||||
ELSE 'no'
|
||||
END;
|
||||
"
|
||||
}
|
||||
|
||||
count_required_tables() {
|
||||
local table_list=""
|
||||
local table
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
if [ -n "$table_list" ]; then
|
||||
table_list+=", "
|
||||
fi
|
||||
table_list+="'$table'"
|
||||
done
|
||||
sql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN (${table_list});"
|
||||
}
|
||||
|
||||
if [ "$(is_shared_blockscout_db || echo no)" = "yes" ]; then
|
||||
MIGRATION_MODE="auth-only"
|
||||
MIGRATION_FILE="$AUTH_ONLY_MIGRATION_FILE"
|
||||
REQUIRED_TABLES=(operator_events operator_ip_whitelist operator_roles wallet_nonces)
|
||||
fi
|
||||
|
||||
echo "Database: $DB_NAME@$DB_HOST:$DB_PORT"
|
||||
echo "User: $DB_USER"
|
||||
if [ "$MIGRATION_MODE" = "auth-only" ]; then
|
||||
echo "Mode: shared Blockscout DB detected; applying explorer auth/operator tables only"
|
||||
else
|
||||
echo "Mode: standalone explorer DB; applying full Track 2-4 schema"
|
||||
fi
|
||||
echo ""
|
||||
|
||||
# Run migration using psql
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
TABLE_COUNT="$(count_required_tables || echo 0)"
|
||||
if [ "${TABLE_COUNT:-0}" -ge "${#REQUIRED_TABLES[@]}" ]; then
|
||||
echo "✅ Migration already applied (${TABLE_COUNT}/${#REQUIRED_TABLES[@]} required tables present)"
|
||||
echo ""
|
||||
echo "Verified tables:"
|
||||
for table in "${REQUIRED_TABLES[@]}"; do
|
||||
echo " - $table"
|
||||
done
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export PGPASSWORD="$DB_PASSWORD"
|
||||
if psql -v ON_ERROR_STOP=1 -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_FILE"; then
|
||||
TABLE_COUNT="$(count_required_tables || echo 0)"
|
||||
if [ "${TABLE_COUNT:-0}" -lt "${#REQUIRED_TABLES[@]}" ]; then
|
||||
echo ""
|
||||
echo "❌ Migration finished but required tables are still missing (${TABLE_COUNT}/${#REQUIRED_TABLES[@]})"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_FILE"; then
|
||||
echo ""
|
||||
echo "✅ Migration 0010_track_schema.up.sql completed successfully"
|
||||
echo "✅ Migration ${MIGRATION_MODE} completed successfully"
|
||||
echo ""
|
||||
echo "Created tables:"
|
||||
echo " - addresses (Track 2)"
|
||||
echo " - token_transfers (Track 2)"
|
||||
echo " - token_balances (Track 2)"
|
||||
echo " - internal_transactions (Track 2)"
|
||||
echo " - analytics_flows (Track 3)"
|
||||
echo " - analytics_bridge_history (Track 3)"
|
||||
echo " - token_distribution (Track 3 - materialized view)"
|
||||
echo " - operator_events (Track 4)"
|
||||
echo " - operator_ip_whitelist (Track 4)"
|
||||
echo " - operator_roles (Track 4)"
|
||||
echo " - wallet_nonces (Authentication)"
|
||||
if [ "$MIGRATION_MODE" = "auth-only" ]; then
|
||||
echo "Created or verified tables:"
|
||||
echo " - operator_events (Track 4)"
|
||||
echo " - operator_ip_whitelist (Track 4)"
|
||||
echo " - operator_roles (Track 4)"
|
||||
echo " - wallet_nonces (Authentication)"
|
||||
else
|
||||
echo "Created tables:"
|
||||
echo " - addresses (Track 2)"
|
||||
echo " - token_transfers (Track 2)"
|
||||
echo " - token_balances (Track 2)"
|
||||
echo " - internal_transactions (Track 2)"
|
||||
echo " - analytics_flows (Track 3)"
|
||||
echo " - analytics_bridge_history (Track 3)"
|
||||
echo " - token_distribution (Track 3 - materialized view)"
|
||||
echo " - operator_events (Track 4)"
|
||||
echo " - operator_ip_whitelist (Track 4)"
|
||||
echo " - operator_roles (Track 4)"
|
||||
echo " - wallet_nonces (Authentication)"
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Migration failed"
|
||||
@@ -54,4 +136,3 @@ else
|
||||
fi
|
||||
|
||||
unset PGPASSWORD
|
||||
|
||||
|
||||
@@ -57,7 +57,7 @@ log_success " RPC accessible (block: $BLOCK_NUMBER)"
|
||||
|
||||
# Check bridge configuration
|
||||
log_info " Checking bridge configuration..."
|
||||
WETH9_BRIDGE="0x971cD9D156f193df8051E48043C476e53ECd4693"
|
||||
WETH9_BRIDGE="0xcacfd227A040002e49e2e01626363071324f820a"
|
||||
DEST=$(cast call "$WETH9_BRIDGE" "destinations(uint64)" "$DESTINATION_SELECTOR" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
DEST_CLEAN=$(echo "$DEST" | grep -oE "^0x[0-9a-fA-F]{40}$" | head -1 || echo "")
|
||||
if [ -z "$DEST_CLEAN" ] || echo "$DEST_CLEAN" | grep -qE "^0x0+$"; then
|
||||
@@ -111,7 +111,7 @@ log_info ""
|
||||
log_info "Step 4: Verify Fee Calculation"
|
||||
log_info ""
|
||||
|
||||
CCIP_ROUTER="0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e"
|
||||
CCIP_ROUTER="0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
|
||||
FEE_RESULT=$(cast call "$CCIP_ROUTER" "getFee(uint64,bytes)" "$DESTINATION_SELECTOR" "0x" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$FEE_RESULT" ] && [ "$FEE_RESULT" != "0x" ]; then
|
||||
FEE_WEI=$(echo "$FEE_RESULT" | grep -oE "[0-9]+" | head -1 || echo "")
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#!/bin/bash
|
||||
# Complete test suite for tiered architecture deployment
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${API_BASE_URL:-http://localhost:8080}"
|
||||
DB_HOST="${DB_HOST:-localhost}"
|
||||
DB_PORT="${DB_PORT:-5432}"
|
||||
DB_USER="${DB_USER:-explorer}"
|
||||
DB_NAME="${DB_NAME:-explorer}"
|
||||
echo "=== Full Deployment Test Suite ==="
|
||||
echo "Base URL: $BASE_URL"
|
||||
echo ""
|
||||
@@ -18,6 +22,15 @@ YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
print_auth_hint_if_needed() {
|
||||
local body="${1:-}"
|
||||
local message
|
||||
message=$(echo "$body" | jq -r '.error.message // empty' 2>/dev/null || true)
|
||||
if [[ "$message" == *wallet_nonces* ]] || [[ "$message" == *"storage is not initialized"* ]]; then
|
||||
echo " Hint: wallet auth storage is missing. Run bash scripts/run-migration-0010.sh and restart the backend."
|
||||
fi
|
||||
}
|
||||
|
||||
test_endpoint() {
|
||||
local method=$1
|
||||
local endpoint=$2
|
||||
@@ -62,6 +75,7 @@ test_endpoint() {
|
||||
if [ ${#body} -lt 200 ]; then
|
||||
echo " Response: $body"
|
||||
fi
|
||||
print_auth_hint_if_needed "$body"
|
||||
((FAILED++))
|
||||
return 1
|
||||
fi
|
||||
@@ -89,13 +103,13 @@ test_endpoint "GET" "/api/v1/track4/operator/bridge/events" "401" "Track 4: Requ
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}=== Database Verification ===${NC}"
|
||||
if export PGPASSWORD="${DB_PASSWORD:-changeme}" && psql -h "${DB_HOST:-localhost}" -U "${DB_USER:-explorer}" -d "${DB_NAME:-explorer}" -c "SELECT COUNT(*) FROM wallet_nonces;" -t > /dev/null 2>&1; then
|
||||
if export PGPASSWORD="${DB_PASSWORD:-changeme}" && psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COUNT(*) FROM wallet_nonces;" -t > /dev/null 2>&1; then
|
||||
echo -e "${GREEN}✓ Database: Connected${NC}"
|
||||
((PASSED++))
|
||||
|
||||
# Check tables
|
||||
echo -n "Checking track schema tables... "
|
||||
TABLE_COUNT=$(export PGPASSWORD="${DB_PASSWORD:-changeme}" && psql -h "${DB_HOST:-localhost}" -U "${DB_USER:-explorer}" -d "${DB_NAME:-explorer}" -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('addresses', 'token_transfers', 'wallet_nonces', 'operator_roles');" -t 2>/dev/null | tr -d ' ')
|
||||
TABLE_COUNT=$(export PGPASSWORD="${DB_PASSWORD:-changeme}" && psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -c "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = 'public' AND table_name IN ('addresses', 'token_transfers', 'wallet_nonces', 'operator_roles');" -t 2>/dev/null | tr -d ' ')
|
||||
if [ "$TABLE_COUNT" -ge "4" ]; then
|
||||
echo -e "${GREEN}✓ All tables exist${NC}"
|
||||
((PASSED++))
|
||||
@@ -121,4 +135,3 @@ else
|
||||
echo -e "${YELLOW}⚠️ Some tests failed or need attention${NC}"
|
||||
exit 0 # Don't fail deployment, just report
|
||||
fi
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Test tiered architecture API endpoints
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
BASE_URL="${API_BASE_URL:-http://localhost:8080}"
|
||||
echo "=== Testing Tiered Architecture API ==="
|
||||
@@ -17,6 +17,15 @@ NC='\033[0m' # No Color
|
||||
PASSED=0
|
||||
FAILED=0
|
||||
|
||||
print_auth_hint_if_needed() {
|
||||
local body="${1:-}"
|
||||
local message
|
||||
message=$(echo "$body" | jq -r '.error.message // empty' 2>/dev/null || true)
|
||||
if [[ "$message" == *wallet_nonces* ]] || [[ "$message" == *"storage is not initialized"* ]]; then
|
||||
echo " Hint: wallet auth storage is missing. Run bash scripts/run-migration-0010.sh and restart the backend."
|
||||
fi
|
||||
}
|
||||
|
||||
# Test function
|
||||
test_endpoint() {
|
||||
local method=$1
|
||||
@@ -46,6 +55,7 @@ test_endpoint() {
|
||||
echo -e "${RED}✗ FAIL${NC} (Expected $expected_status, got $http_code)"
|
||||
echo " Response: $body" | head -c 200
|
||||
echo ""
|
||||
print_auth_hint_if_needed "$body"
|
||||
((FAILED++))
|
||||
return 1
|
||||
fi
|
||||
@@ -91,4 +101,3 @@ else
|
||||
echo -e "${RED}❌ Some tests failed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
12
scripts/vendor-mermaid-for-command-center.sh
Executable file
12
scripts/vendor-mermaid-for-command-center.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
# Download Mermaid 10 bundle for offline / CSP-strict explorer hosts.
|
||||
# Output: explorer-monorepo/frontend/public/thirdparty/mermaid.min.js (gitignored)
|
||||
set -euo pipefail
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
OUT_DIR="$(cd "$SCRIPT_DIR/../frontend/public/thirdparty" && pwd)"
|
||||
mkdir -p "$OUT_DIR"
|
||||
URL="${MERMAID_CDN_URL:-https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js}"
|
||||
echo "Fetching Mermaid → $OUT_DIR/mermaid.min.js"
|
||||
curl -fsSL -o "$OUT_DIR/mermaid.min.js" "$URL"
|
||||
wc -c "$OUT_DIR/mermaid.min.js"
|
||||
echo "Done. Point chain138-command-center.html at /thirdparty/mermaid.min.js and redeploy."
|
||||
@@ -29,7 +29,8 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
CCIP_ROUTER="0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e"
|
||||
# Canonical Chain 138 router (relay path); legacy direct router: 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e
|
||||
CCIP_ROUTER="0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
|
||||
|
||||
log_info "========================================="
|
||||
log_info "CCIP Router Verification"
|
||||
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
CCIP_SENDER="0x105F8A15b819948a89153505762444Ee9f324684"
|
||||
CCIP_ROUTER="0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e"
|
||||
CCIP_ROUTER="0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
|
||||
|
||||
log_info "========================================="
|
||||
log_info "CCIP Sender Verification"
|
||||
|
||||
@@ -67,7 +67,7 @@ log_info ""
|
||||
|
||||
# A.1: Router Deployment
|
||||
log_info "A.1: Router Deployment"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
ROUTER_BYTECODE=$(cast code "$ROUTER" --rpc-url "$RPC_URL" 2>/dev/null || echo "")
|
||||
if [ -n "$ROUTER_BYTECODE" ] && [ "$ROUTER_BYTECODE" != "0x" ]; then
|
||||
check_pass "Router contract deployed: $ROUTER"
|
||||
@@ -89,7 +89,7 @@ fi
|
||||
# A.3: Bridge Destinations
|
||||
log_info ""
|
||||
log_info "A.3: Bridge Destination Configuration"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
WETH9_CONFIGURED=0
|
||||
|
||||
@@ -26,7 +26,7 @@ load_explorer_runtime_env
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
WETH10_BRIDGE="$(resolve_address_value CCIPWETH10_BRIDGE CCIPWETH10_BRIDGE 0xe0E93247376aa097dB308B92e6Ba36bA015535D0)"
|
||||
|
||||
declare -A CHAIN_SELECTORS=()
|
||||
|
||||
@@ -8,6 +8,8 @@ set -euo pipefail
|
||||
|
||||
BASE_URL="${1:-https://explorer.d-bis.org}"
|
||||
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
MISSION_CONTROL_SAMPLE_TX="${MISSION_CONTROL_SAMPLE_TX:-0x2f31d4f9a97be754b800f4af1a9eedf3b107d353bfa1a19e81417497a76c05c2}"
|
||||
MISSION_CONTROL_SAMPLE_TOKEN="${MISSION_CONTROL_SAMPLE_TOKEN:-0x93E66202A11B1772E55407B32B44e5Cd8eda7f22}"
|
||||
|
||||
# Detect execution context: inside LXC (VMID 5000) or on host
|
||||
RUN_LOCAL=false
|
||||
@@ -133,13 +135,13 @@ else
|
||||
fi
|
||||
|
||||
# 8) /snap/ response contains Snap app content (skip if 301 — redirect may not include body)
|
||||
if echo "$SNAP_BODY" | head -c 8192 | grep -qE 'Connect|template-snap|Snap|MetaMask'; then
|
||||
if echo "$SNAP_BODY" | grep -qE 'Connect|template-snap|Chain 138 Snap|Install MetaMask Flask|Snap|MetaMask'; then
|
||||
echo "✅ $BASE_URL/snap/ contains Snap app content"
|
||||
((PASS++)) || true
|
||||
elif [ "$SNAP_CODE" = "301" ]; then
|
||||
echo "⏭ $BASE_URL/snap/ returned 301 (redirect); content check skipped"
|
||||
else
|
||||
echo "❌ $BASE_URL/snap/ response missing expected content (Connect|Snap|MetaMask)"
|
||||
echo "❌ $BASE_URL/snap/ response missing expected content (Connect|Chain 138 Snap|MetaMask)"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
|
||||
@@ -156,6 +158,77 @@ else
|
||||
echo "⏭ Skipping nginx /snap/ check (not inside VM and pct not used)"
|
||||
fi
|
||||
|
||||
# 10) Visual Command Center returns 200
|
||||
HTTP_CODE="$(curl -sS -o /tmp/chain138-command-center.verify.$$ -w "%{http_code}" --connect-timeout 10 "$BASE_URL/chain138-command-center.html" 2>/dev/null || echo 000)"
|
||||
if [ "$HTTP_CODE" = "200" ]; then
|
||||
echo "✅ $BASE_URL/chain138-command-center.html returns 200"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "❌ $BASE_URL/chain138-command-center.html returned $HTTP_CODE (expected 200)"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
|
||||
# 11) Visual Command Center contains expected explorer architecture content
|
||||
if grep -qE 'Visual Command Center|Mission Control|mainnet cW mint corridor' /tmp/chain138-command-center.verify.$$ 2>/dev/null; then
|
||||
echo "✅ $BASE_URL/chain138-command-center.html contains command-center content"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "❌ $BASE_URL/chain138-command-center.html missing expected command-center content"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
rm -f /tmp/chain138-command-center.verify.$$
|
||||
|
||||
# 12) Mission Control SSE stream returns 200 with text/event-stream
|
||||
MC_STREAM_HEADERS="/tmp/mission-control-stream.headers.$$"
|
||||
MC_STREAM_BODY="/tmp/mission-control-stream.body.$$"
|
||||
MC_STREAM_EXIT=0
|
||||
if ! curl -sS -N -D "$MC_STREAM_HEADERS" -o "$MC_STREAM_BODY" --connect-timeout 10 --max-time 8 \
|
||||
"$BASE_URL/explorer-api/v1/mission-control/stream" >/dev/null 2>&1; then
|
||||
MC_STREAM_EXIT=$?
|
||||
fi
|
||||
if grep -qE '^HTTP/[0-9.]+ 200' "$MC_STREAM_HEADERS" 2>/dev/null \
|
||||
&& grep -qi '^content-type: text/event-stream' "$MC_STREAM_HEADERS" 2>/dev/null \
|
||||
&& { [ "$MC_STREAM_EXIT" -eq 0 ] || [ "$MC_STREAM_EXIT" -eq 28 ]; } \
|
||||
&& grep -qE '^(event|data):' "$MC_STREAM_BODY" 2>/dev/null; then
|
||||
echo "✅ $BASE_URL/explorer-api/v1/mission-control/stream returns Mission Control SSE"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "❌ $BASE_URL/explorer-api/v1/mission-control/stream missing expected SSE response"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
rm -f "$MC_STREAM_HEADERS" "$MC_STREAM_BODY"
|
||||
|
||||
# 13) Mission Control bridge trace returns labeled JSON
|
||||
MC_TRACE_BODY="/tmp/mission-control-trace.body.$$"
|
||||
HTTP_CODE="$(curl -sS -o "$MC_TRACE_BODY" -w "%{http_code}" --connect-timeout 10 \
|
||||
"$BASE_URL/explorer-api/v1/mission-control/bridge/trace?tx=$MISSION_CONTROL_SAMPLE_TX" 2>/dev/null || echo 000)"
|
||||
if [ "$HTTP_CODE" = "200" ] \
|
||||
&& grep -q '"tx_hash"' "$MC_TRACE_BODY" 2>/dev/null \
|
||||
&& grep -q '"from_registry"' "$MC_TRACE_BODY" 2>/dev/null \
|
||||
&& grep -q '"to_registry"' "$MC_TRACE_BODY" 2>/dev/null; then
|
||||
echo "✅ $BASE_URL/explorer-api/v1/mission-control/bridge/trace returns labeled trace JSON"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "❌ $BASE_URL/explorer-api/v1/mission-control/bridge/trace failed verification"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
rm -f "$MC_TRACE_BODY"
|
||||
|
||||
# 14) Mission Control liquidity proxy returns pool JSON
|
||||
MC_LIQUIDITY_BODY="/tmp/mission-control-liquidity.body.$$"
|
||||
HTTP_CODE="$(curl -sS -o "$MC_LIQUIDITY_BODY" -w "%{http_code}" --connect-timeout 10 \
|
||||
"$BASE_URL/explorer-api/v1/mission-control/liquidity/token/$MISSION_CONTROL_SAMPLE_TOKEN/pools" 2>/dev/null || echo 000)"
|
||||
if [ "$HTTP_CODE" = "200" ] \
|
||||
&& grep -q '"pools"' "$MC_LIQUIDITY_BODY" 2>/dev/null \
|
||||
&& grep -q '"dex"' "$MC_LIQUIDITY_BODY" 2>/dev/null; then
|
||||
echo "✅ $BASE_URL/explorer-api/v1/mission-control/liquidity/token/.../pools returns pool JSON"
|
||||
((PASS++)) || true
|
||||
else
|
||||
echo "❌ $BASE_URL/explorer-api/v1/mission-control/liquidity/token/.../pools failed verification"
|
||||
((FAIL++)) || true
|
||||
fi
|
||||
rm -f "$MC_LIQUIDITY_BODY"
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo "Result: $PASS passed, $FAIL failed"
|
||||
|
||||
@@ -30,7 +30,7 @@ fi
|
||||
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
CCIP_ROUTER="0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e"
|
||||
CCIP_ROUTER="0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817"
|
||||
LINK_TOKEN="0x514910771AF9Ca656af840dff83E8264EcF986CA"
|
||||
|
||||
# Default values
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
# Verify tiered architecture implementation
|
||||
|
||||
set -e
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
@@ -12,9 +12,10 @@ echo ""
|
||||
ERRORS=0
|
||||
WARNINGS=0
|
||||
|
||||
# Check 1: Database migration file exists
|
||||
echo -n "Checking migration file... "
|
||||
if [ -f "$PROJECT_ROOT/backend/database/migrations/0010_track_schema.up.sql" ]; then
|
||||
# Check 1: Database migration files exist
|
||||
echo -n "Checking migration files... "
|
||||
if [ -f "$PROJECT_ROOT/backend/database/migrations/0010_track_schema.up.sql" ] && \
|
||||
[ -f "$PROJECT_ROOT/backend/database/migrations/0010_track_schema.auth_only.sql" ]; then
|
||||
echo "✅"
|
||||
else
|
||||
echo "❌"
|
||||
@@ -142,12 +143,13 @@ if [ $ERRORS -eq 0 ]; then
|
||||
echo ""
|
||||
echo "Next steps:"
|
||||
echo "1. Run: bash scripts/setup-tiered-architecture.sh"
|
||||
echo "2. Set JWT_SECRET environment variable"
|
||||
echo "3. Start the API server"
|
||||
echo "2. Export DB_PASSWORD and run: bash scripts/run-migration-0010.sh"
|
||||
echo " (auto-detects standalone explorer DB vs shared Blockscout DB)"
|
||||
echo "3. Set JWT_SECRET environment variable"
|
||||
echo "4. Start the API server"
|
||||
exit 0
|
||||
else
|
||||
echo ""
|
||||
echo "❌ Some components are missing. Please review errors above."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ load_explorer_runtime_env
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_ADDRESS="$(resolve_address_value WETH9_ADDRESS WETH9_ADDRESS 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)"
|
||||
WETH10_ADDRESS="$(resolve_address_value WETH10_ADDRESS WETH10_ADDRESS 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9f)"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x8078A09637e47Fa5Ed34F626046Ea2094a5CDE5e)"
|
||||
CCIP_ROUTER="$(resolve_address_value CCIP_ROUTER_ADDRESS CCIP_ROUTER_ADDRESS 0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817)"
|
||||
|
||||
log_info "========================================="
|
||||
log_info "TokenAdminRegistry Verification"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Run all VMID 5000 deployment checks: explorer frontend, Blockscout API, Snap site.
|
||||
# Run all VMID 5000 deployment checks: explorer frontend, Blockscout API, Snap site,
|
||||
# Visual Command Center, and Mission Control endpoints.
|
||||
# Usage: ./verify-vmid5000-all.sh [BASE_URL]
|
||||
# BASE_URL defaults to https://explorer.d-bis.org
|
||||
|
||||
@@ -9,7 +10,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
BASE_URL="${1:-https://explorer.d-bis.org}"
|
||||
|
||||
echo "=============================================="
|
||||
echo "VMID 5000 – full verification"
|
||||
echo "VMID 5000 – full explorer verification"
|
||||
echo "BASE_URL=$BASE_URL"
|
||||
echo "=============================================="
|
||||
echo ""
|
||||
|
||||
@@ -28,7 +28,7 @@ load_explorer_runtime_env
|
||||
# Configuration
|
||||
RPC_URL="${RPC_URL_138:-http://192.168.11.250:8545}"
|
||||
WETH9_ADDRESS="$(resolve_address_value WETH9_ADDRESS WETH9_ADDRESS 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0x971cD9D156f193df8051E48043C476e53ECd4693)"
|
||||
WETH9_BRIDGE="$(resolve_address_value CCIPWETH9_BRIDGE CCIPWETH9_BRIDGE 0xcacfd227A040002e49e2e01626363071324f820a)"
|
||||
ETHEREUM_MAINNET_SELECTOR="${ETHEREUM_MAINNET_SELECTOR:-$(ccip_destination_selector_by_name "Ethereum Mainnet")}"
|
||||
|
||||
# Parse arguments
|
||||
|
||||
Reference in New Issue
Block a user