Skip to content

Instantly share code, notes, and snippets.

@hsnsbhshsh
Created March 1, 2026 00:33
Show Gist options
  • Select an option

  • Save hsnsbhshsh/e8fc2eaa950726eb57f6fb6145b70eab to your computer and use it in GitHub Desktop.

Select an option

Save hsnsbhshsh/e8fc2eaa950726eb57f6fb6145b70eab to your computer and use it in GitHub Desktop.
Untitled
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bitcoin Explorer Pro - Live Analytics</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
:root {
--primary: #0a0a0a;
--secondary: #121212;
--accent: #1a1a1a;
--text: #ffffff;
--text-secondary: #b0b0b0;
--highlight: #00ff9d;
--highlight-secondary: #00ccff;
--card-bg: rgba(30, 30, 30, 0.8);
--glow: 0 0 10px rgba(0, 255, 157, 0.3);
--border: rgba(255, 255, 255, 0.1);
--positive: #00ff9d;
--negative: #ff4d4d;
--sender: #ff4d4d;
--receiver: #00ff9d;
--neutral: #b0b0b0;
}
body {
background-color: var(--primary);
color: var(--text);
min-height: 100vh;
overflow-x: hidden;
background-image:
radial-gradient(circle at 10% 20%, rgba(0, 255, 157, 0.03) 0%, transparent 20%),
radial-gradient(circle at 90% 40%, rgba(0, 255, 157, 0.03) 0%, transparent 20%),
radial-gradient(circle at 50% 80%, rgba(0, 255, 157, 0.03) 0%, transparent 20%);
}
.container {
max-width: 1400px;
margin: 0 auto;
padding: 15px;
display: grid;
grid-template-columns: 1fr;
gap: 15px;
}
/* Header */
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid var(--border);
margin-bottom: 15px;
flex-wrap: wrap;
}
.logo {
display: flex;
align-items: center;
gap: 10px;
}
.logo-icon {
width: 35px;
height: 35px;
background: linear-gradient(135deg, var(--highlight), var(--highlight-secondary));
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
box-shadow: var(--glow);
animation: pulse 3s infinite;
}
@keyframes pulse {
0% { transform: scale(1); box-shadow: var(--glow); }
50% { transform: scale(1.05); box-shadow: 0 0 15px rgba(0, 255, 157, 0.5); }
100% { transform: scale(1); box-shadow: var(--glow); }
}
.logo h1 {
font-size: 20px;
font-weight: 700;
background: linear-gradient(to right, var(--highlight), var(--highlight-secondary));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
letter-spacing: 1px;
}
.mobile-menu-btn {
display: none;
background: none;
border: none;
color: var(--text);
font-size: 20px;
cursor: pointer;
}
.search-bar {
display: flex;
align-items: center;
background: var(--secondary);
border-radius: 10px;
padding: 8px 12px;
width: 100%;
border: 1px solid var(--border);
margin: 10px 0;
order: 3;
transition: all 0.3s ease;
}
.search-bar:focus-within {
border-color: var(--highlight);
box-shadow: var(--glow);
}
.search-bar input {
background: transparent;
border: none;
color: var(--text);
width: 100%;
margin-left: 8px;
outline: none;
font-size: 14px;
letter-spacing: 0.5px;
}
.search-bar input::placeholder {
color: var(--text-secondary);
}
.user-actions {
display: flex;
gap: 10px;
align-items: center;
}
.notification {
position: relative;
cursor: pointer;
}
.notification-badge {
position: absolute;
top: -5px;
right: -5px;
background: var(--negative);
color: white;
border-radius: 50%;
width: 16px;
height: 16px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.user-profile {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
}
.avatar {
width: 35px;
height: 35px;
border-radius: 50%;
background: linear-gradient(135deg, var(--highlight), var(--highlight-secondary));
display: flex;
align-items: center;
justify-content: center;
}
/* Sidebar */
.sidebar {
background: var(--secondary);
border-radius: 16px;
padding: 15px;
border: 1px solid var(--border);
display: none;
margin-bottom: 15px;
}
.sidebar.active {
display: block;
}
.sidebar-menu {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.sidebar-menu li {
padding: 10px 12px;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 10px;
font-weight: 500;
letter-spacing: 0.3px;
}
.sidebar-menu li.active {
background: rgba(0, 255, 157, 0.1);
color: var(--highlight);
}
.sidebar-menu li:hover:not(.active) {
background: rgba(255, 255, 255, 0.05);
}
/* Main Content */
.main-content {
display: flex;
flex-direction: column;
gap: 15px;
}
.tab-container {
background: var(--secondary);
border-radius: 16px;
overflow: hidden;
border: 1px solid var(--border);
}
.tab-header {
display: flex;
border-bottom: 1px solid var(--border);
background: var(--accent);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.tab {
padding: 12px 20px;
cursor: pointer;
transition: all 0.3s ease;
font-weight: 600;
position: relative;
white-space: nowrap;
flex-shrink: 0;
letter-spacing: 0.5px;
}
.tab.active {
color: var(--highlight);
}
.tab.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: var(--highlight);
}
.tab:hover:not(.active) {
background: rgba(255, 255, 255, 0.05);
}
.tab-content {
padding: 20px;
display: none;
}
.tab-content.active {
display: block;
animation: fadeIn 0.5s ease;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* Cards */
.dashboard-cards {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
margin-bottom: 15px;
}
.card {
background: var(--card-bg);
border-radius: 16px;
padding: 15px;
backdrop-filter: blur(10px);
border: 1px solid var(--border);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(to right, var(--highlight), var(--highlight-secondary));
}
.card:hover {
transform: translateY(-3px);
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.3);
}
.card h2 {
font-size: 16px;
margin-bottom: 12px;
color: var(--text-secondary);
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
letter-spacing: 0.5px;
}
.balance {
font-size: 24px;
font-weight: 700;
margin-bottom: 8px;
letter-spacing: 0.5px;
}
.address-stats {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-top: 15px;
}
.stat-item {
display: flex;
flex-direction: column;
padding: 12px;
background: rgba(255, 255, 255, 0.05);
border-radius: 10px;
transition: all 0.3s ease;
}
.stat-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}
.stat-label {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 5px;
font-weight: 500;
}
.stat-value {
font-size: 18px;
font-weight: 700;
letter-spacing: 0.3px;
}
.positive { color: var(--positive) !important; }
.negative { color: var(--negative) !important; }
.sender { color: var(--sender) !important; }
.receiver { color: var(--receiver) !important; }
.neutral { color: var(--neutral) !important; }
/* Charts */
.chart-container {
height: 200px;
margin-top: 15px;
position: relative;
}
/* Tables */
.market-table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
margin-bottom: 20px;
}
.market-table {
width: 100%;
border-collapse: collapse;
min-width: 600px;
}
.market-table th {
text-align: left;
padding: 12px;
font-weight: 600;
color: var(--text-secondary);
border-bottom: 1px solid var(--border);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 1px;
}
.market-table td {
padding: 12px;
border-bottom: 1px solid var(--border);
font-size: 13px;
letter-spacing: 0.3px;
}
.market-table tr:last-child td {
border-bottom: none;
}
.market-table tr:hover {
background: rgba(255, 255, 255, 0.03);
}
/* Address Display */
.address-link {
color: var(--highlight);
cursor: pointer;
text-decoration: none;
font-family: 'Courier New', monospace;
font-size: 12px;
transition: all 0.3s ease;
font-weight: 500;
}
.address-link:hover {
text-decoration: underline;
color: var(--highlight-secondary);
}
.address-full {
font-family: 'Courier New', monospace;
background: rgba(0, 255, 157, 0.1);
padding: 8px 12px;
border-radius: 6px;
font-size: 12px;
margin-bottom: 5px;
display: block;
cursor: pointer;
transition: all 0.3s ease;
word-break: break-all;
line-height: 1.4;
}
.address-full:hover {
background: rgba(0, 255, 157, 0.2);
transform: translateY(-1px);
}
.address-short {
font-family: 'Courier New', monospace;
background: rgba(0, 255, 157, 0.1);
padding: 2px 6px;
border-radius: 4px;
font-size: 11px;
margin-bottom: 2px;
display: inline-block;
cursor: pointer;
transition: all 0.3s ease;
}
.address-short:hover {
background: rgba(0, 255, 157, 0.2);
transform: translateY(-1px);
}
.transaction-row {
transition: all 0.3s ease;
}
.transaction-row:hover {
background: rgba(0, 255, 157, 0.05) !important;
}
/* Pagination */
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 20px;
padding: 15px 0;
border-top: 1px solid var(--border);
}
.pagination-btn {
padding: 8px 16px;
background: var(--accent);
border: 1px solid var(--border);
color: var(--text);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 13px;
font-weight: 500;
letter-spacing: 0.3px;
}
.pagination-btn:hover:not(:disabled) {
background: var(--card-bg);
border-color: var(--highlight);
}
.pagination-btn.active {
background: var(--highlight);
color: var(--primary);
border-color: var(--highlight);
font-weight: 600;
}
.pagination-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.pagination-info {
color: var(--text-secondary);
font-size: 13px;
margin: 0 10px;
letter-spacing: 0.3px;
}
/* Live Transactions */
.live-transaction {
animation: slideIn 0.5s ease;
position: relative;
}
@keyframes slideIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
.live-indicator {
width: 8px;
height: 8px;
background: var(--positive);
border-radius: 50%;
display: inline-block;
margin-right: 8px;
animation: blink 1.5s infinite;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Bitcoin Stats */
.bitcoin-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.bitcoin-stat {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
transition: all 0.3s ease;
}
.bitcoin-stat:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}
.bitcoin-stat i {
font-size: 24px;
margin-bottom: 10px;
color: var(--highlight);
}
.bitcoin-stat-value {
font-size: 20px;
font-weight: 700;
margin-bottom: 5px;
letter-spacing: 0.5px;
}
.bitcoin-stat-label {
font-size: 12px;
color: var(--text-secondary);
font-weight: 500;
}
/* Saved Addresses */
.saved-addresses {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 15px;
}
.saved-address {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 12px;
border: 1px solid var(--border);
transition: all 0.3s ease;
cursor: pointer;
}
.saved-address:hover {
background: rgba(0, 255, 157, 0.05);
transform: translateY(-2px);
}
.saved-address-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.saved-address-name {
font-weight: 600;
color: var(--highlight);
font-size: 14px;
}
.saved-address-actions {
display: flex;
gap: 8px;
}
.saved-address-value {
font-family: 'Courier New', monospace;
font-size: 12px;
color: var(--text-secondary);
word-break: break-all;
margin-bottom: 10px;
line-height: 1.4;
}
/* Form Elements */
.form-group {
margin-bottom: 12px;
}
.form-group label {
display: block;
margin-bottom: 6px;
font-size: 14px;
color: var(--text-secondary);
font-weight: 500;
letter-spacing: 0.3px;
}
.form-control {
width: 100%;
padding: 10px 12px;
background: var(--accent);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
outline: none;
transition: all 0.3s ease;
font-size: 14px;
letter-spacing: 0.3px;
}
.form-control:focus {
border-color: var(--highlight);
box-shadow: var(--glow);
}
.btn {
padding: 12px 24px;
border-radius: 10px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
border: none;
font-size: 14px;
letter-spacing: 0.5px;
}
.btn-primary {
background: linear-gradient(135deg, var(--highlight), var(--highlight-secondary));
color: var(--primary);
font-weight: 700;
}
.btn-secondary {
background: rgba(255, 255, 255, 0.1);
color: var(--text);
font-weight: 600;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: var(--glow);
}
.btn-small {
padding: 6px 12px;
font-size: 12px;
}
/* Loading Spinner */
.loading {
display: flex;
justify-content: center;
align-items: center;
padding: 40px;
flex-direction: column;
gap: 15px;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid rgba(255, 255, 255, 0.1);
border-left: 4px solid var(--highlight);
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* Status Badges */
.status-badge {
padding: 4px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.status-confirmed {
background: rgba(0, 255, 157, 0.2);
color: var(--positive);
}
.status-pending {
background: rgba(255, 204, 0, 0.2);
color: #ffcc00;
}
/* Alert */
.alert {
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
animation: fadeIn 0.5s ease;
}
.alert-success {
background: rgba(0, 255, 157, 0.1);
border-left: 4px solid var(--positive);
}
.alert-error {
background: rgba(255, 77, 77, 0.1);
border-left: 4px solid var(--negative);
}
.alert-warning {
background: rgba(255, 204, 0, 0.1);
border-left: 4px solid #ffcc00;
}
/* Binance Stats Card */
.binance-stats {
background: rgba(0, 255, 157, 0.1);
border-radius: 12px;
padding: 15px;
margin-top: 15px;
border: 1px solid rgba(0, 255, 157, 0.2);
}
.binance-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 10px;
margin-top: 10px;
}
.binance-stat-item {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.binance-stat-value {
font-size: 16px;
font-weight: 700;
color: var(--highlight);
margin-bottom: 4px;
}
.binance-stat-label {
font-size: 11px;
color: var(--text-secondary);
font-weight: 500;
}
/* API Status */
.api-status {
display: flex;
align-items: center;
gap: 10px;
margin-top: 10px;
font-size: 12px;
}
.api-status-indicator {
width: 10px;
height: 10px;
border-radius: 50%;
}
.api-status-active {
background: var(--positive);
animation: blink 2s infinite;
}
.api-status-fallback {
background: #ffcc00;
}
/* Transaction Type Switcher */
.tx-type-switcher {
display: flex;
gap: 10px;
margin-bottom: 15px;
background: var(--accent);
padding: 8px;
border-radius: 10px;
border: 1px solid var(--border);
}
.tx-type-btn {
flex: 1;
padding: 8px 16px;
background: transparent;
border: 1px solid transparent;
color: var(--text-secondary);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 13px;
font-weight: 500;
text-align: center;
}
.tx-type-btn.active {
background: rgba(0, 255, 157, 0.2);
color: var(--highlight);
border-color: var(--highlight);
}
.tx-type-btn:hover:not(.active) {
background: rgba(255, 255, 255, 0.05);
}
/* Block Info Cards */
.block-info-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.block-info-item {
background: rgba(255, 255, 255, 0.05);
padding: 15px;
border-radius: 12px;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
}
.block-info-item:hover {
background: rgba(255, 255, 255, 0.08);
transform: translateY(-2px);
}
.block-info-label {
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 5px;
font-weight: 500;
}
.block-info-value {
font-size: 16px;
font-weight: 600;
word-break: break-all;
}
.block-hash {
font-family: 'Courier New', monospace;
font-size: 14px;
color: var(--highlight);
}
/* Transaction Details */
.tx-details {
background: rgba(255, 255, 255, 0.05);
border-radius: 12px;
padding: 15px;
margin-top: 15px;
border: 1px solid var(--border);
}
.tx-details-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid var(--border);
}
.tx-details-title {
font-size: 16px;
font-weight: 600;
color: var(--highlight);
}
/* Footer */
footer {
text-align: center;
padding: 15px 0;
border-top: 1px solid var(--border);
color: var(--text-secondary);
font-size: 12px;
margin-top: 15px;
letter-spacing: 0.5px;
}
/* Desktop Styles */
@media (min-width: 768px) {
.container {
grid-template-columns: 250px 1fr;
gap: 20px;
}
header {
grid-column: 1 / -1;
flex-wrap: nowrap;
padding: 15px 0;
margin-bottom: 20px;
}
.logo h1 {
font-size: 24px;
}
.mobile-menu-btn {
display: none;
}
.search-bar {
width: 400px;
margin: 0;
order: 0;
}
.sidebar {
display: block;
margin-bottom: 0;
}
.dashboard-cards {
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.card {
padding: 20px;
}
.balance {
font-size: 28px;
}
.chart-container {
height: 250px;
margin-top: 20px;
}
.address-stats {
grid-template-columns: repeat(4, 1fr);
}
footer {
grid-column: 1 / -1;
padding: 20px 0;
font-size: 14px;
margin-top: 20px;
}
}
@media (min-width: 1200px) {
.container {
grid-template-columns: 250px 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<header>
<button class="mobile-menu-btn" id="mobileMenuBtn">
<i class="fas fa-bars"></i>
</button>
<div class="logo">
<div class="logo-icon">
<i class="fab fa-bitcoin"></i>
</div>
<h1>BITCOIN EXPLORER PRO</h1>
</div>
<div class="search-bar">
<i class="fas fa-search"></i>
<input type="text" id="searchInput" placeholder="Enter Bitcoin address, transaction hash, or block height...">
</div>
<div class="user-actions">
<div class="notification">
<i class="far fa-bell fa-lg"></i>
<div class="notification-badge" id="notificationCount">0</div>
</div>
<div class="user-profile">
<div class="avatar">
<i class="fas fa-user"></i>
</div>
<span class="user-name">EXPLORER</span>
</div>
</div>
</header>
<aside class="sidebar" id="sidebar">
<ul class="sidebar-menu">
<li class="active" data-tab="address-explorer"><i class="fas fa-search"></i> ADDRESS EXPLORER</li>
<li data-tab="live-stream"><i class="fas fa-broadcast-tower"></i> LIVE TRANSACTIONS</li>
<li data-tab="large-tx"><i class="fas fa-exclamation-triangle"></i> LARGE TX ALERTS</li>
<li data-tab="bitcoin-stats"><i class="fas fa-chart-bar"></i> BITCOIN STATS</li>
<li data-tab="block-explorer"><i class="fas fa-cube"></i> BLOCK EXPLORER</li>
<li data-tab="binance-stats"><i class="fas fa-exchange-alt"></i> BINANCE STATS</li>
<li data-tab="saved-addresses"><i class="fas fa-bookmark"></i> SAVED ADDRESSES</li>
</ul>
</aside>
<main class="main-content">
<div class="tab-container">
<div class="tab-header">
<div class="tab active" data-tab="address-explorer">ADDRESS EXPLORER</div>
<div class="tab" data-tab="live-stream">LIVE STREAM</div>
<div class="tab" data-tab="large-tx">LARGE TX ALERTS</div>
<div class="tab" data-tab="bitcoin-stats">BITCOIN STATS</div>
<div class="tab" data-tab="block-explorer">BLOCK EXPLORER</div>
<div class="tab" data-tab="binance-stats">BINANCE STATS</div>
<div class="tab" data-tab="saved-addresses">SAVED ADDRESSES</div>
</div>
<!-- Address Explorer Tab -->
<div class="tab-content active" id="address-explorer">
<div class="card">
<h2><i class="fas fa-search"></i> BITCOIN ADDRESS EXPLORER</h2>
<div class="form-group">
<label for="address-input">ENTER BITCOIN ADDRESS</label>
<div style="display: flex; gap: 10px;">
<input type="text" id="address-input" class="form-control" placeholder="e.g., 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa">
<button class="btn btn-primary" id="explore-btn">EXPLORE</button>
<button class="btn btn-secondary" id="save-address-btn">SAVE ADDRESS</button>
</div>
<div class="api-status">
<div class="api-status-indicator api-status-active" id="apiStatusIndicator"></div>
<span id="apiStatusText">Using Mempool.space API</span>
</div>
</div>
</div>
<div class="dashboard-cards">
<div class="card">
<h2><i class="fas fa-wallet"></i> ADDRESS BALANCE</h2>
<div class="balance" id="address-balance">0.00000000 BTC</div>
<div class="balance" id="address-balance-usd">$0.00</div>
<div class="address-stats">
<div class="stat-item">
<span class="stat-label">TOTAL RECEIVED</span>
<span class="stat-value positive" id="total-received">0 BTC</span>
</div>
<div class="stat-item">
<span class="stat-label">TOTAL SENT</span>
<span class="stat-value negative" id="total-sent">0 BTC</span>
</div>
<div class="stat-item">
<span class="stat-label">TRANSACTION COUNT</span>
<span class="stat-value neutral" id="tx-count">0</span>
</div>
<div class="stat-item">
<span class="stat-label">UNCONFIRMED</span>
<span class="stat-value" id="unconfirmed-balance">0 BTC</span>
</div>
</div>
</div>
<div class="card">
<h2><i class="fas fa-chart-line"></i> BALANCE HISTORY</h2>
<div class="chart-container">
<canvas id="balanceHistoryChart"></canvas>
</div>
</div>
</div>
<div class="card">
<h2><i class="fas fa-exchange-alt"></i> TRANSACTION HISTORY</h2>
<div class="tx-type-switcher">
<button class="tx-type-btn active" data-type="all">ALL TRANSACTIONS</button>
<button class="tx-type-btn" data-type="sent">SENT</button>
<button class="tx-type-btn" data-type="received">RECEIVED</button>
</div>
<div class="market-table-container">
<table class="market-table">
<thead>
<tr>
<th>TRANSACTION HASH</th>
<th>DATE</th>
<th>FROM</th>
<th>TO</th>
<th>AMOUNT (BTC)</th>
<th>FEE</th>
<th>STATUS</th>
</tr>
</thead>
<tbody id="address-transactions">
<tr>
<td colspan="7" class="loading">
<div class="spinner"></div>
<div>Enter a Bitcoin address to explore transactions</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div class="pagination" id="pagination-controls" style="display: none;">
<button class="pagination-btn" id="pagination-prev" disabled>PREV</button>
<span class="pagination-info" id="pagination-info">Page 1 of 1</span>
<button class="pagination-btn" id="pagination-next" disabled>NEXT</button>
</div>
</div>
</div>
<!-- Live Stream Tab -->
<div class="tab-content" id="live-stream">
<div class="card">
<h2><i class="fas fa-broadcast-tower"></i> LIVE BITCOIN TRANSACTIONS</h2>
<div class="alert alert-success">
<i class="fas fa-circle-notch fa-spin"></i>
<span>Streaming live transactions from the Bitcoin network. Updates every 10 seconds.</span>
</div>
<div class="market-table-container">
<table class="market-table">
<thead>
<tr>
<th>TIME</th>
<th>TRANSACTION HASH</th>
<th>FROM</th>
<th>TO</th>
<th>AMOUNT (BTC)</th>
<th>FEE (BTC)</th>
<th>CONFIRMATIONS</th>
</tr>
</thead>
<tbody id="live-transactions">
<tr>
<td colspan="7" class="loading">
<div class="spinner"></div>
<div>Loading live transactions...</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Large Transactions Alert Tab -->
<div class="tab-content" id="large-tx">
<div class="card">
<h2><i class="fas fa-exclamation-triangle"></i> LARGE TRANSACTION MONITOR (>500 BTC)</h2>
<div class="alert alert-warning">
<i class="fas fa-exclamation-circle"></i>
<span>Monitoring for transactions exceeding 500 BTC. Found: <span id="large-tx-count">0</span> transactions</span>
</div>
<div class="market-table-container">
<table class="market-table">
<thead>
<tr>
<th>TIME</th>
<th>TRANSACTION HASH</th>
<th>FROM</th>
<th>TO</th>
<th>AMOUNT (BTC)</th>
<th>USD VALUE</th>
<th>ACTION</th>
</tr>
</thead>
<tbody id="large-transactions">
<tr>
<td colspan="7" class="loading">
<div class="spinner"></div>
<div>Monitoring for large transactions...</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- Bitcoin Stats Tab -->
<div class="tab-content" id="bitcoin-stats">
<div class="card">
<h2><i class="fab fa-bitcoin"></i> BITCOIN NETWORK STATISTICS</h2>
<div class="bitcoin-stats-grid">
<div class="bitcoin-stat">
<i class="fas fa-chart-line"></i>
<div class="bitcoin-stat-value" id="btc-price">$0.00</div>
<div class="bitcoin-stat-label">BITCOIN PRICE</div>
</div>
<div class="bitcoin-stat">
<i class="fas fa-cube"></i>
<div class="bitcoin-stat-value" id="block-height">0</div>
<div class="bitcoin-stat-label">BLOCK HEIGHT</div>
</div>
<div class="bitcoin-stat">
<i class="fas fa-bolt"></i>
<div class="bitcoin-stat-value" id="hash-rate">0 EH/s</div>
<div class="bitcoin-stat-label">HASH RATE</div>
</div>
<div class="bitcoin-stat">
<i class="fas fa-users"></i>
<div class="bitcoin-stat-value" id="market-cap">$0.00B</div>
<div class="bitcoin-stat-label">MARKET CAP</div>
</div>
</div>
<div class="chart-container">
<canvas id="networkChart"></canvas>
</div>
</div>
</div>
<!-- Block Explorer Tab -->
<div class="tab-content" id="block-explorer">
<div class="card">
<h2><i class="fas fa-cube"></i> BLOCK EXPLORER</h2>
<div class="form-group">
<label for="block-input">ENTER BLOCK HEIGHT OR HASH</label>
<div style="display: flex; gap: 10px;">
<input type="text" id="block-input" class="form-control" placeholder="e.g., 840000 or 0000000000000000000...">
<button class="btn btn-primary" id="explore-block-btn">EXPLORE BLOCK</button>
</div>
</div>
</div>
<div class="card" id="block-info-container" style="display: none;">
<h2><i class="fas fa-info-circle"></i> BLOCK INFORMATION</h2>
<div id="block-info-content">
<div class="block-info-grid" id="block-info-grid"></div>
</div>
</div>
<div class="card" id="block-transactions-container" style="display: none;">
<h2><i class="fas fa-exchange-alt"></i> BLOCK TRANSACTIONS</h2>
<div class="market-table-container">
<table class="market-table">
<thead>
<tr>
<th>TRANSACTION HASH</th>
<th>FROM</th>
<th>TO</th>
<th>AMOUNT (BTC)</th>
<th>FEE (BTC)</th>
<th>CONFIRMATIONS</th>
</tr>
</thead>
<tbody id="block-transactions">
</tbody>
</table>
</div>
<!-- Block Transactions Pagination -->
<div class="pagination" id="block-pagination-controls" style="display: none;">
<button class="pagination-btn" id="block-pagination-prev" disabled>PREV</button>
<span class="pagination-info" id="block-pagination-info">Page 1 of 1</span>
<button class="pagination-btn" id="block-pagination-next" disabled>NEXT</button>
</div>
</div>
</div>
<!-- Binance Stats Tab -->
<div class="tab-content" id="binance-stats">
<div class="card">
<h2><i class="fab fa-bitcoin"></i> BINANCE BITCOIN STATISTICS</h2>
<div class="alert alert-success">
<i class="fas fa-sync-alt fa-spin"></i>
<span>Live data from Binance API. Updates every 30 seconds.</span>
</div>
<div class="binance-stats">
<div style="text-align: center; margin-bottom: 15px;">
<div class="balance" id="binance-price">$0.00</div>
<div style="display: flex; justify-content: center; gap: 20px; margin-top: 10px;">
<div>
<div class="binance-stat-label">24H CHANGE</div>
<div class="binance-stat-value positive" id="binance-change">0.00%</div>
</div>
<div>
<div class="binance-stat-label">VOLUME</div>
<div class="binance-stat-value" id="binance-volume">0 BTC</div>
</div>
</div>
</div>
<div class="binance-stats-grid">
<div class="binance-stat-item">
<div class="binance-stat-value" id="binance-high">$0.00</div>
<div class="binance-stat-label">24H HIGH</div>
</div>
<div class="binance-stat-item">
<div class="binance-stat-value" id="binance-low">$0.00</div>
<div class="binance-stat-label">24H LOW</div>
</div>
<div class="binance-stat-item">
<div class="binance-stat-value" id="binance-open">$0.00</div>
<div class="binance-stat-label">OPEN PRICE</div>
</div>
<div class="binance-stat-item">
<div class="binance-stat-value" id="binance-close">$0.00</div>
<div class="binance-stat-label">LAST PRICE</div>
</div>
</div>
</div>
<div class="chart-container">
<canvas id="binanceChart"></canvas>
</div>
</div>
</div>
<!-- Saved Addresses Tab -->
<div class="tab-content" id="saved-addresses">
<div class="card">
<h2><i class="fas fa-bookmark"></i> SAVED BITCOIN ADDRESSES</h2>
<div id="saved-addresses-list" class="saved-addresses">
<div class="loading">
<div class="spinner"></div>
<div>Loading saved addresses...</div>
</div>
</div>
</div>
</div>
</div>
</main>
<footer>
<p>© 2023 BITCOIN EXPLORER PRO. ALL RIGHTS RESERVED. REAL-TIME BITCOIN DATA ANALYSIS.</p>
<p>Powered by Mempool.space API & Binance API</p>
</footer>
</div>
<script>
// ============================================
// CONFIGURATION - UPDATED APIs THAT WORK
// ============================================
const MEMPOOL_API = 'https://mempool.space/api';
const BLOCKCYPHER_API = 'https://api.blockcypher.com/v1/btc/main';
const BLOCKCHAIN_API = 'https://blockchain.info';
const BINANCE_API = 'https://api.binance.com/api/v3';
// ============================================
// GLOBAL STATE
// ============================================
let currentAddress = '';
let currentBitcoinPrice = 0;
let savedAddresses = [];
let liveTransactions = [];
let largeTransactions = [];
let currentTxPage = 1;
let totalTxPages = 1;
let currentTransactions = [];
let filteredTransactions = [];
let transactionsPerPage = 10;
let binanceData = null;
let binanceChartInstance = null;
let networkChartInstance = null;
let balanceChart = null;
let currentApiSource = 'mempool';
let currentTxFilter = 'all';
let currentBlock = null;
let currentBlockPage = 1;
let blockTransactionsPerPage = 10;
// ============================================
// DOM ELEMENTS
// ============================================
const mobileMenuBtn = document.getElementById('mobileMenuBtn');
const sidebar = document.getElementById('sidebar');
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
const searchInput = document.getElementById('searchInput');
const addressInput = document.getElementById('address-input');
const exploreBtn = document.getElementById('explore-btn');
const saveAddressBtn = document.getElementById('save-address-btn');
const notificationCount = document.getElementById('notificationCount');
const paginationControls = document.getElementById('pagination-controls');
const paginationPrev = document.getElementById('pagination-prev');
const paginationNext = document.getElementById('pagination-next');
const paginationInfo = document.getElementById('pagination-info');
const apiStatusIndicator = document.getElementById('apiStatusIndicator');
const apiStatusText = document.getElementById('apiStatusText');
const blockInput = document.getElementById('block-input');
const exploreBlockBtn = document.getElementById('explore-block-btn');
const blockInfoContainer = document.getElementById('block-info-container');
const blockTransactionsContainer = document.getElementById('block-transactions-container');
const blockPaginationControls = document.getElementById('block-pagination-controls');
const blockPaginationPrev = document.getElementById('block-pagination-prev');
const blockPaginationNext = document.getElementById('block-pagination-next');
const blockPaginationInfo = document.getElementById('block-pagination-info');
// ============================================
// INITIALIZATION
// ============================================
document.addEventListener('DOMContentLoaded', function() {
initEventListeners();
loadBitcoinStats();
loadBinanceData();
loadSavedAddresses();
startLiveStream();
startLargeTransactionMonitor();
setInterval(loadBitcoinStats, 30000);
setInterval(loadBinanceData, 30000);
setInterval(fetchLiveTransactions, 10000);
initializeSampleData();
});
// ============================================
// EVENT LISTENERS
// ============================================
function initEventListeners() {
mobileMenuBtn.addEventListener('click', function() {
sidebar.classList.toggle('active');
});
document.querySelectorAll('.sidebar-menu li').forEach(item => {
item.addEventListener('click', function() {
document.querySelectorAll('.sidebar-menu li').forEach(i => i.classList.remove('active'));
this.classList.add('active');
const tabId = this.dataset.tab;
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
document.querySelector(`.tab[data-tab="${tabId}"]`).classList.add('active');
document.getElementById(tabId).classList.add('active');
if (tabId === 'binance-stats') {
loadBinanceData();
} else if (tabId === 'bitcoin-stats') {
loadBitcoinStats();
} else if (tabId === 'block-explorer') {
// Reset block explorer
blockInfoContainer.style.display = 'none';
blockTransactionsContainer.style.display = 'none';
}
});
});
tabs.forEach(tab => {
tab.addEventListener('click', () => {
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(c => c.classList.remove('active'));
tab.classList.add('active');
document.getElementById(tab.dataset.tab).classList.add('active');
if (tab.dataset.tab === 'binance-stats') {
loadBinanceData();
} else if (tab.dataset.tab === 'bitcoin-stats') {
loadBitcoinStats();
} else if (tab.dataset.tab === 'block-explorer') {
// Reset block explorer
blockInfoContainer.style.display = 'none';
blockTransactionsContainer.style.display = 'none';
}
});
});
exploreBtn.addEventListener('click', exploreAddress);
addressInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') exploreAddress();
});
saveAddressBtn.addEventListener('click', saveCurrentAddress);
searchInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter' && this.value.trim()) {
const value = this.value.trim();
// Check if it's a block number (only digits) or address/hash
if (/^\d+$/.test(value)) {
// It's a block number
blockInput.value = value;
exploreBlock();
} else if (value.length === 64) {
// It's likely a transaction hash
showAlert(`Transaction hash detected. Use Block Explorer for details.`, 'info');
} else {
// Assume it's an address
addressInput.value = value;
exploreAddress();
}
}
});
// Transaction type switcher
document.querySelectorAll('.tx-type-btn').forEach(btn => {
btn.addEventListener('click', function() {
document.querySelectorAll('.tx-type-btn').forEach(b => b.classList.remove('active'));
this.classList.add('active');
currentTxFilter = this.dataset.type;
filterTransactionsByType(currentTxFilter);
});
});
paginationPrev.addEventListener('click', () => {
if (currentTxPage > 1) {
currentTxPage--;
updateTransactionsPage();
}
});
paginationNext.addEventListener('click', () => {
if (currentTxPage < totalTxPages) {
currentTxPage++;
updateTransactionsPage();
}
});
// Block explorer
exploreBlockBtn.addEventListener('click', exploreBlock);
blockInput.addEventListener('keypress', function(e) {
if (e.key === 'Enter') exploreBlock();
});
blockPaginationPrev.addEventListener('click', () => {
if (currentBlockPage > 1) {
currentBlockPage--;
updateBlockTransactionsPage();
}
});
blockPaginationNext.addEventListener('click', () => {
const totalBlockPages = Math.ceil((currentBlock?.tx_count || 0) / blockTransactionsPerPage);
if (currentBlockPage < totalBlockPages) {
currentBlockPage++;
updateBlockTransactionsPage();
}
});
document.addEventListener('click', function(e) {
if (e.target.closest('.address-link, .address-short, .address-full')) {
const element = e.target.closest('.address-link, .address-short, .address-full');
const address = element.getAttribute('data-value') || element.textContent;
const type = element.getAttribute('data-type') || 'address';
if (type === 'address' && address && address.length > 20) {
addressInput.value = address;
exploreAddress();
} else if (type === 'tx') {
showAlert(`Transaction details for: ${address.substring(0, 20)}...`, 'info');
} else if (type === 'block') {
blockInput.value = address;
exploreBlock();
}
}
});
}
function initializeSampleData() {
if (!localStorage.getItem('bitcoinExplorerFirstRun')) {
localStorage.setItem('bitcoinExplorerFirstRun', 'true');
localStorage.setItem('bitcoinExplorerSavedAddresses', JSON.stringify([
{
name: 'Satoshi Nakamoto (First Bitcoin)',
address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
balance: '0.00000000',
note: 'The first Bitcoin address'
},
{
name: 'Bitcoin Pizza Address',
address: '1CZpmqVcLwM1zqRwpeC6hqRwUZHLB8w2pK',
balance: '0.00000000',
note: 'Famous 10,000 BTC pizza transaction'
},
{
name: 'Binance Cold Wallet',
address: '34xp4vRoCGJym3xR7yCVPFHoCNxv4Twseo',
balance: '0.00000000',
note: 'One of the largest Bitcoin addresses'
}
]));
}
addressInput.placeholder = 'e.g., 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa';
searchInput.placeholder = 'Try: 1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa or 840000';
setTimeout(() => {
showAlert('Welcome to Bitcoin Explorer Pro! Enter a Bitcoin address or block height to begin.', 'success');
}, 1000);
}
// ============================================
// API STATUS
// ============================================
function updateApiStatus(source, status) {
apiStatusText.textContent = `Using ${source} API`;
if (status === 'active') {
apiStatusIndicator.className = 'api-status-indicator api-status-active';
} else if (status === 'fallback') {
apiStatusIndicator.className = 'api-status-indicator api-status-fallback';
}
}
// ============================================
// BITCOIN STATISTICS
// ============================================
async function loadBitcoinStats() {
try {
const binanceResponse = await fetch(`${BINANCE_API}/ticker/price?symbol=BTCUSDT`);
if (binanceResponse.ok) {
const binanceData = await binanceResponse.json();
currentBitcoinPrice = parseFloat(binanceData.price);
}
document.getElementById('btc-price').textContent = `$${currentBitcoinPrice.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
try {
const blockHeightResponse = await fetch(`${MEMPOOL_API}/blocks/tip/height`);
const blockHeight = await blockHeightResponse.text();
document.getElementById('block-height').textContent = parseInt(blockHeight).toLocaleString('en-US');
} catch (error) {
document.getElementById('block-height').textContent = 'N/A';
}
document.getElementById('hash-rate').textContent = `305.42 EH/s`;
const marketCap = currentBitcoinPrice * 19400000;
document.getElementById('market-cap').textContent = `$${(marketCap / 1e9).toFixed(1)}B`;
updateNetworkChart();
} catch (error) {
console.error('Error loading Bitcoin stats:', error);
}
}
function updateNetworkChart() {
const ctx = document.getElementById('networkChart').getContext('2d');
if (networkChartInstance) {
networkChartInstance.destroy();
}
const hours = ['-6h', '-5h', '-4h', '-3h', '-2h', '-1h', 'Now'];
const difficultyData = [85.3, 86.1, 87.2, 86.8, 88.5, 89.2, 90.1];
const txCountData = [2450, 2580, 2670, 2410, 2820, 2950, 2740];
networkChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: hours,
datasets: [
{
label: 'Difficulty (T)',
data: difficultyData,
borderColor: '#00ccff',
backgroundColor: 'rgba(0, 204, 255, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4,
yAxisID: 'y'
},
{
label: 'Transactions/Hour',
data: txCountData,
borderColor: '#00ff9d',
backgroundColor: 'rgba(0, 255, 157, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4,
yAxisID: 'y1'
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: {
mode: 'index',
intersect: false
},
plugins: {
legend: {
display: true,
position: 'top'
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'var(--text-secondary)'
}
},
y: {
type: 'linear',
display: true,
position: 'left',
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'var(--text-secondary)'
}
},
y1: {
type: 'linear',
display: true,
position: 'right',
grid: {
drawOnChartArea: false
},
ticks: {
color: 'var(--text-secondary)'
}
}
}
}
});
}
// ============================================
// BINANCE DATA
// ============================================
async function loadBinanceData() {
try {
const response = await fetch(`${BINANCE_API}/ticker/24hr?symbol=BTCUSDT`);
const data = await response.json();
if (data && data.lastPrice) {
binanceData = data;
updateBinanceDisplay();
createBinanceChart();
}
} catch (error) {
console.error('Error loading Binance data:', error);
}
}
function updateBinanceDisplay() {
if (!binanceData) return;
const price = parseFloat(binanceData.lastPrice);
document.getElementById('binance-price').textContent = `$${price.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
const change = parseFloat(binanceData.priceChangePercent);
const changeElement = document.getElementById('binance-change');
changeElement.textContent = `${change.toFixed(2)}%`;
changeElement.className = change >= 0 ? 'binance-stat-value positive' : 'binance-stat-value negative';
document.getElementById('binance-high').textContent = `$${parseFloat(binanceData.highPrice).toLocaleString('en-US', { minimumFractionDigits: 2 })}`;
document.getElementById('binance-low').textContent = `$${parseFloat(binanceData.lowPrice).toLocaleString('en-US', { minimumFractionDigits: 2 })}`;
document.getElementById('binance-open').textContent = `$${parseFloat(binanceData.openPrice).toLocaleString('en-US', { minimumFractionDigits: 2 })}`;
document.getElementById('binance-close').textContent = `$${parseFloat(binanceData.lastPrice).toLocaleString('en-US', { minimumFractionDigits: 2 })}`;
document.getElementById('binance-volume').textContent = `${parseFloat(binanceData.volume).toLocaleString('en-US', { minimumFractionDigits: 4 })} BTC`;
}
function createBinanceChart() {
if (!binanceData) return;
const ctx = document.getElementById('binanceChart').getContext('2d');
if (binanceChartInstance) {
binanceChartInstance.destroy();
}
const basePrice = parseFloat(binanceData.openPrice);
const currentPrice = parseFloat(binanceData.lastPrice);
const highPrice = parseFloat(binanceData.highPrice);
const lowPrice = parseFloat(binanceData.lowPrice);
const hours = [];
const prices = [];
for (let i = 0; i < 24; i++) {
hours.push(`${i}:00`);
let simulatedPrice;
if (i === 0) {
simulatedPrice = basePrice;
} else if (i === 23) {
simulatedPrice = currentPrice;
} else {
const volatility = (highPrice - lowPrice) / basePrice;
const randomFactor = 0.5 + Math.random() * volatility;
const direction = currentPrice > basePrice ? 1 : -1;
const progressFactor = Math.sin((i/24) * Math.PI) * volatility;
simulatedPrice = basePrice + (currentPrice - basePrice) * (i/24) * randomFactor + progressFactor * direction;
simulatedPrice = Math.max(lowPrice * 0.99, Math.min(highPrice * 1.01, simulatedPrice));
}
prices.push(simulatedPrice);
}
const chartColor = currentPrice >= basePrice ? 'rgba(0, 255, 157, 0.3)' : 'rgba(255, 77, 77, 0.3)';
const borderColor = currentPrice >= basePrice ? '#00ff9d' : '#ff4d4d';
binanceChartInstance = new Chart(ctx, {
type: 'line',
data: {
labels: hours,
datasets: [{
label: 'BTC Price (USD)',
data: prices,
borderColor: borderColor,
backgroundColor: chartColor,
borderWidth: 2,
fill: true,
tension: 0.4,
pointRadius: 2,
pointHoverRadius: 6
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `Price: $${context.parsed.y.toLocaleString('en-US', { minimumFractionDigits: 2 })}`;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'var(--text-secondary)',
maxTicksLimit: 8
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.1)'
},
ticks: {
color: 'var(--text-secondary)',
callback: function(value) {
return '$' + value.toLocaleString('en-US', { maximumFractionDigits: 0 });
}
}
}
}
}
});
}
// ============================================
// CORE FUNCTION - EXPLORE ADDRESS
// ============================================
async function exploreAddress() {
const address = addressInput.value.trim();
if (!address || address.length < 25) {
showAlert('Please enter a valid Bitcoin address', 'error');
return;
}
currentAddress = address;
showAlert(`Exploring address: ${address.substring(0, 20)}...`, 'success');
const container = document.getElementById('address-transactions');
container.innerHTML = `
<tr>
<td colspan="7" class="loading">
<div class="spinner"></div>
<div>Fetching address data from Bitcoin network...</div>
</td>
</tr>
`;
currentTxPage = 1;
paginationControls.style.display = 'none';
try {
await tryMempoolAPI(address);
} catch (mempoolError) {
console.log('Mempool API failed, trying BlockCypher...');
try {
await tryBlockCypherAPI(address);
} catch (blockcypherError) {
console.log('BlockCypher API failed, trying Blockchain.info...');
try {
await tryBlockchainAPI(address);
} catch (blockchainError) {
console.log('All APIs failed, showing sample data');
showSampleData();
}
}
}
}
// ============================================
// TRY MEMPOOL.SPACE API (PRIMARY)
// ============================================
async function tryMempoolAPI(address) {
try {
updateApiStatus('Mempool.space', 'active');
currentApiSource = 'mempool';
const addressInfoUrl = `${MEMPOOL_API}/address/${address}`;
const addressInfoResponse = await fetch(addressInfoUrl);
if (!addressInfoResponse.ok) {
throw new Error(`Mempool API failed: ${addressInfoResponse.status}`);
}
const addressInfo = await addressInfoResponse.json();
const transactionsUrl = `${MEMPOOL_API}/address/${address}/txs`;
const transactionsResponse = await fetch(transactionsUrl);
const transactions = await transactionsResponse.json();
currentTransactions = transactions || [];
filteredTransactions = [...currentTransactions];
totalTxPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
updateAddressInfoFromMempool(addressInfo);
filterTransactionsByType(currentTxFilter);
if (currentTransactions.length > 0) {
generateBalanceHistoryFromMempool(transactions);
}
return true;
} catch (error) {
console.error('Mempool API error:', error);
throw error;
}
}
// ============================================
// TRY BLOCKCYPHER API (FALLBACK 1)
// ============================================
async function tryBlockCypherAPI(address) {
try {
updateApiStatus('BlockCypher', 'fallback');
currentApiSource = 'blockcypher';
const response = await fetch(`${BLOCKCYPHER_API}/addrs/${address}?limit=50`);
if (!response.ok) {
throw new Error(`BlockCypher API failed: ${response.status}`);
}
const data = await response.json();
currentTransactions = data.txrefs || data.unconfirmed_txrefs || [];
filteredTransactions = [...currentTransactions];
totalTxPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
updateAddressInfoFromBlockCypher(data);
filterTransactionsByType(currentTxFilter);
if (currentTransactions.length > 0) {
generateBalanceHistoryFromBlockCypher(data.txrefs || []);
}
return true;
} catch (error) {
console.error('BlockCypher API error:', error);
throw error;
}
}
// ============================================
// TRY BLOCKCHAIN.INFO API (FALLBACK 2)
// ============================================
async function tryBlockchainAPI(address) {
try {
updateApiStatus('Blockchain.info', 'fallback');
currentApiSource = 'blockchain';
const response = await fetch(`${BLOCKCHAIN_API}/rawaddr/${address}?limit=50`);
if (!response.ok) {
throw new Error(`Blockchain.info API failed: ${response.status}`);
}
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
currentTransactions = data.txs || [];
filteredTransactions = [...currentTransactions];
totalTxPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
updateAddressInfoFromBlockchain(data);
filterTransactionsByType(currentTxFilter);
if (currentTransactions.length > 0) {
generateBalanceHistoryFromBlockchain(data.txs);
}
return true;
} catch (error) {
console.error('Blockchain.info API error:', error);
throw error;
}
}
// ============================================
// SHOW SAMPLE DATA (FINAL FALLBACK)
// ============================================
function showSampleData() {
updateApiStatus('Sample Data', 'fallback');
currentApiSource = 'sample';
const sampleData = generateSampleData(currentAddress);
updateAddressInfoSample(sampleData);
filterTransactionsByType(currentTxFilter);
if (sampleData.transactions.length > 0) {
generateBalanceHistorySample(sampleData.transactions);
}
showAlert('Using sample data (APIs unavailable)', 'warning');
}
// ============================================
// UPDATE ADDRESS INFO FROM DIFFERENT SOURCES
// ============================================
function updateAddressInfoFromMempool(data) {
const satToBtc = sat => (sat / 100000000).toFixed(8);
const chainStats = data.chain_stats || {};
const mempoolStats = data.mempool_stats || {};
const totalReceived = satToBtc(chainStats.funded_txo_sum || 0);
const totalSent = satToBtc(chainStats.spent_txo_sum || 0);
const confirmedBalance = satToBtc((chainStats.funded_txo_sum || 0) - (chainStats.spent_txo_sum || 0));
const unconfirmedBalance = satToBtc((mempoolStats.funded_txo_sum || 0) - (mempoolStats.spent_txo_sum || 0));
const txCount = (chainStats.tx_count || 0) + (mempoolStats.tx_count || 0);
const totalBalance = parseFloat(confirmedBalance) + parseFloat(unconfirmedBalance);
document.getElementById('address-balance').textContent = `${totalBalance.toFixed(8)} BTC`;
document.getElementById('total-received').textContent = `${totalReceived} BTC`;
document.getElementById('total-sent').textContent = `${totalSent} BTC`;
document.getElementById('tx-count').textContent = txCount;
document.getElementById('unconfirmed-balance').textContent = `${unconfirmedBalance} BTC`;
if (currentBitcoinPrice > 0) {
const usdValue = (totalBalance * currentBitcoinPrice).toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
document.getElementById('address-balance-usd').textContent = usdValue;
}
}
function updateAddressInfoFromBlockCypher(data) {
const satToBtc = sat => (sat / 100000000).toFixed(8);
const balance = satToBtc(data.final_balance || data.balance || 0);
const totalReceived = satToBtc(data.total_received || 0);
const totalSent = satToBtc(data.total_sent || 0);
const unconfirmedBalance = satToBtc(data.unconfirmed_balance || 0);
const txCount = data.n_tx || data.final_n_tx || 0;
document.getElementById('address-balance').textContent = `${balance} BTC`;
document.getElementById('total-received').textContent = `${totalReceived} BTC`;
document.getElementById('total-sent').textContent = `${totalSent} BTC`;
document.getElementById('tx-count').textContent = txCount;
document.getElementById('unconfirmed-balance').textContent = `${unconfirmedBalance} BTC`;
if (currentBitcoinPrice > 0) {
const usdValue = (parseFloat(balance) * currentBitcoinPrice).toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
});
document.getElementById('address-balance-usd').textContent = usdValue;
}
}
function updateAddressInfoFromBlockchain(data) {
const satToBtc = sat => (sat / 100000000).toFixed(8);
const balance = satToBtc(data.final_balance || 0);
const totalReceived = satToBtc(data.total_received || 0);
const totalSent = satToBtc(data.total_sent || 0);
const unconfirmedBalance = satToBtc(data.unconfirmed_balance || 0);
const txCount = data.n_tx || 0;
document.getElementById('address-balance').textContent = `${balance} BTC`;
document.getElementById('total-received').textContent = `${totalReceived} BTC`;
document.getElementById('total-sent').textContent = `${totalSent} BTC`;
document.getElementById('tx-count').textContent = txCount;
document.getElementById('unconfirmed-balance').textContent = `${unconfirmedBalance} BTC`;
if (currentBitcoinPrice > 0) {
const usdValue = (parseFloat(balance) * currentBitcoinPrice).toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
});
document.getElementById('address-balance-usd').textContent = usdValue;
}
}
function updateAddressInfoSample(sampleData) {
document.getElementById('address-balance').textContent = `${sampleData.balance.toFixed(8)} BTC`;
document.getElementById('total-received').textContent = `${sampleData.totalReceived.toFixed(8)} BTC`;
document.getElementById('total-sent').textContent = `${sampleData.totalSent.toFixed(8)} BTC`;
document.getElementById('tx-count').textContent = sampleData.txCount;
document.getElementById('unconfirmed-balance').textContent = '0.00000000 BTC';
if (currentBitcoinPrice > 0) {
const usdValue = (sampleData.balance * currentBitcoinPrice).toLocaleString('en-US', {
style: 'currency',
currency: 'USD'
});
document.getElementById('address-balance-usd').textContent = usdValue;
}
}
// ============================================
// FILTER TRANSACTIONS BY TYPE
// ============================================
function filterTransactionsByType(type) {
currentTxFilter = type;
if (!currentTransactions || currentTransactions.length === 0) {
return;
}
switch(type) {
case 'all':
filteredTransactions = [...currentTransactions];
break;
case 'sent':
filteredTransactions = currentTransactions.filter(tx => {
if (currentApiSource === 'mempool' || currentApiSource === 'blockchain') {
const amount = currentApiSource === 'mempool'
? calculateTransactionAmount(tx, currentAddress)
: calculateTransactionAmountBlockchain(tx, currentAddress);
return amount < 0;
} else if (currentApiSource === 'blockcypher') {
return tx.tx_input_n !== undefined;
} else {
return tx.amount < 0;
}
});
break;
case 'received':
filteredTransactions = currentTransactions.filter(tx => {
if (currentApiSource === 'mempool' || currentApiSource === 'blockchain') {
const amount = currentApiSource === 'mempool'
? calculateTransactionAmount(tx, currentAddress)
: calculateTransactionAmountBlockchain(tx, currentAddress);
return amount > 0;
} else if (currentApiSource === 'blockcypher') {
return tx.tx_output_n !== undefined;
} else {
return tx.amount > 0;
}
});
break;
}
currentTxPage = 1;
totalTxPages = Math.ceil(filteredTransactions.length / transactionsPerPage);
updateTransactionsPage();
}
// ============================================
// TRANSACTIONS PAGES FROM DIFFERENT SOURCES
// ============================================
function updateTransactionsPage() {
const container = document.getElementById('address-transactions');
if (!filteredTransactions || filteredTransactions.length === 0) {
container.innerHTML = `
<tr>
<td colspan="7" style="text-align: center; padding: 40px;">
<i class="fas fa-inbox" style="font-size: 48px; color: var(--text-secondary); margin-bottom: 15px;"></i>
<div>No ${currentTxFilter === 'all' ? '' : currentTxFilter + ' '}transactions found for this address</div>
</td>
</tr>
`;
paginationControls.style.display = 'none';
return;
}
const startIndex = (currentTxPage - 1) * transactionsPerPage;
const endIndex = Math.min(startIndex + transactionsPerPage, filteredTransactions.length);
const pageTransactions = filteredTransactions.slice(startIndex, endIndex);
container.innerHTML = '';
pageTransactions.forEach((tx, index) => {
displayTransaction(tx, container, startIndex + index);
});
updatePaginationControls();
}
function displayTransaction(tx, container, globalIndex) {
try {
let date, time, hash, amount, fee, status, fromAddresses = [], toAddresses = [];
switch(currentApiSource) {
case 'mempool':
date = new Date((tx.status?.block_time || Date.now() / 1000) * 1000).toLocaleDateString();
time = new Date((tx.status?.block_time || Date.now() / 1000) * 1000).toLocaleTimeString();
hash = tx.txid || '';
amount = calculateTransactionAmount(tx, currentAddress);
fee = tx.fee ? (tx.fee / 100000000).toFixed(8) : '0.00000000';
status = tx.status?.confirmed ? 'Confirmed' : 'Pending';
fromAddresses = extractFromAddressesMempool(tx);
toAddresses = extractToAddressesMempool(tx);
break;
case 'blockcypher':
date = new Date(tx.confirmed || tx.received).toLocaleDateString();
time = new Date(tx.confirmed || tx.received).toLocaleTimeString();
hash = tx.tx_hash || '';
amount = Math.abs(tx.value) / 100000000 * (tx.tx_input_n !== undefined ? -1 : 1);
fee = 'N/A';
status = tx.confirmed ? 'Confirmed' : 'Pending';
fromAddresses = [tx.tx_input_n ? 'Multiple Inputs' : 'Unknown'];
toAddresses = [tx.tx_output_n ? 'Multiple Outputs' : 'Unknown'];
break;
case 'blockchain':
date = new Date(tx.time * 1000).toLocaleDateString();
time = new Date(tx.time * 1000).toLocaleTimeString();
hash = tx.hash || '';
amount = calculateTransactionAmountBlockchain(tx, currentAddress);
fee = tx.fee ? (tx.fee / 100000000).toFixed(8) : '0.00000000';
status = tx.block_height ? 'Confirmed' : 'Pending';
fromAddresses = extractFromAddressesBlockchain(tx);
toAddresses = extractToAddressesBlockchain(tx);
break;
case 'sample':
date = new Date(tx.timestamp).toLocaleDateString();
time = new Date(tx.timestamp).toLocaleTimeString();
hash = tx.hash || `sample_${globalIndex}`;
amount = tx.amount;
fee = tx.fee || '0.00001000';
status = tx.confirmed ? 'Confirmed' : 'Pending';
fromAddresses = tx.from || ['Sample Sender'];
toAddresses = tx.to || ['Sample Receiver'];
break;
}
const btcAmount = typeof amount === 'number' ? Math.abs(amount) : 0;
const formattedAmount = btcAmount.toFixed(8);
let amountColorClass = 'neutral';
if (typeof amount === 'number') {
if (amount > 0) amountColorClass = 'receiver';
else if (amount < 0) amountColorClass = 'sender';
}
const row = document.createElement('tr');
row.className = 'transaction-row';
// Display full addresses for from and to
const fromDisplay = fromAddresses.length > 0
? fromAddresses.slice(0, 2).map(addr =>
`<div class="address-full" title="${addr}" data-type="address" data-value="${addr}">${addr}</div>`
).join('')
: '<div class="address-short">Unknown</div>';
const toDisplay = toAddresses.length > 0
? toAddresses.slice(0, 2).map(addr =>
`<div class="address-full" title="${addr}" data-type="address" data-value="${addr}">${addr}</div>`
).join('')
: '<div class="address-short">Unknown</div>';
row.innerHTML = `
<td>
<span class="address-link" title="${hash}" data-type="tx" data-value="${hash}">
${hash.substring(0, 16)}...
</span>
</td>
<td>${date}<br><small>${time}</small></td>
<td>${fromDisplay}</td>
<td>${toDisplay}</td>
<td class="${amountColorClass}">${formattedAmount} BTC</td>
<td>${fee}</td>
<td>
<span class="status-badge ${status === 'Confirmed' ? 'status-confirmed' : 'status-pending'}">
${status}
</span>
</td>
`;
container.appendChild(row);
} catch (error) {
console.error('Error displaying transaction:', error);
}
}
// ============================================
// HELPER FUNCTIONS FOR TRANSACTION PROCESSING
// ============================================
function calculateTransactionAmount(tx, address) {
if (!tx || !tx.vout || !address) return 0;
let totalReceived = 0;
let totalSent = 0;
tx.vout.forEach(output => {
if (output.scriptpubkey_address === address) {
totalReceived += (output.value || 0) / 100000000;
}
});
if (tx.vin) {
tx.vin.forEach(input => {
if (input.prevout && input.prevout.scriptpubkey_address === address) {
totalSent += (input.prevout.value || 0) / 100000000;
}
});
}
return totalReceived - totalSent;
}
function extractFromAddressesMempool(tx) {
const addresses = new Set();
if (tx.vin) {
tx.vin.forEach(input => {
if (input.prevout && input.prevout.scriptpubkey_address) {
addresses.add(input.prevout.scriptpubkey_address);
} else if (input.is_coinbase) {
addresses.add('Coinbase (Newly Minted)');
} else {
addresses.add('Unknown');
}
});
}
return Array.from(addresses);
}
function extractToAddressesMempool(tx) {
const addresses = new Set();
if (tx.vout) {
tx.vout.forEach(output => {
if (output.scriptpubkey_address) {
addresses.add(output.scriptpubkey_address);
}
});
}
return Array.from(addresses);
}
function calculateTransactionAmountBlockchain(tx, address) {
if (!tx || !tx.out || !address) return 0;
let totalReceived = 0;
let totalSent = 0;
tx.out.forEach(output => {
if (output.addr === address) {
totalReceived += (output.value || 0) / 100000000;
}
});
if (tx.inputs) {
tx.inputs.forEach(input => {
if (input.prev_out && input.prev_out.addr === address) {
totalSent += (input.prev_out.value || 0) / 100000000;
}
});
}
return totalReceived - totalSent;
}
function extractFromAddressesBlockchain(tx) {
const addresses = new Set();
if (tx.inputs) {
tx.inputs.forEach(input => {
if (input.prev_out && input.prev_out.addr) {
addresses.add(input.prev_out.addr);
} else {
addresses.add('Unknown');
}
});
}
return Array.from(addresses);
}
function extractToAddressesBlockchain(tx) {
const addresses = new Set();
if (tx.out) {
tx.out.forEach(output => {
if (output.addr) {
addresses.add(output.addr);
}
});
}
return Array.from(addresses);
}
// ============================================
// GENERATE SAMPLE DATA
// ============================================
function generateSampleData(address) {
const transactions = [];
const now = Date.now();
const baseBalance = Math.random() * 10;
for (let i = 0; i < 15; i++) {
const isIncoming = Math.random() > 0.5;
const amount = (Math.random() * 2 + 0.001).toFixed(8);
const timestamp = new Date(now - (i * 86400000)).toISOString();
transactions.push({
hash: `sample_tx_${i}_${Math.random().toString(36).substring(7)}`,
timestamp: timestamp,
amount: parseFloat(isIncoming ? amount : -amount),
fee: (Math.random() * 0.001).toFixed(8),
confirmed: Math.random() > 0.1,
from: isIncoming ? [`1${Math.random().toString(36).substring(2, 34)}`] : [address],
to: isIncoming ? [address] : [`1${Math.random().toString(36).substring(2, 34)}`]
});
}
transactions.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
let totalReceived = 0;
let totalSent = 0;
let balance = baseBalance;
transactions.forEach(tx => {
if (tx.amount > 0) {
totalReceived += tx.amount;
balance += tx.amount;
} else {
totalSent += Math.abs(tx.amount);
balance += tx.amount;
}
});
return {
balance: balance,
totalReceived: totalReceived,
totalSent: totalSent,
txCount: transactions.length,
transactions: transactions
};
}
// ============================================
// UPDATE PAGINATION CONTROLS
// ============================================
function updatePaginationControls() {
if (filteredTransactions.length <= transactionsPerPage) {
paginationControls.style.display = 'none';
return;
}
paginationControls.style.display = 'flex';
paginationInfo.textContent = `Page ${currentTxPage} of ${totalTxPages}`;
paginationPrev.disabled = currentTxPage === 1;
paginationNext.disabled = currentTxPage === totalTxPages;
}
// ============================================
// BALANCE HISTORY CHARTS
// ============================================
function generateBalanceHistoryFromMempool(transactions) {
if (!transactions || transactions.length === 0) {
createEmptyBalanceChart();
return;
}
const sortedTx = [...transactions].sort((a, b) => (a.status?.block_time || 0) - (b.status?.block_time || 0));
const balanceData = [];
const labels = [];
let runningBalance = 0;
const recentTx = sortedTx.slice(-20);
recentTx.forEach((tx, index) => {
const amount = calculateTransactionAmount(tx, currentAddress);
runningBalance += amount;
const date = tx.status?.block_time ? new Date(tx.status.block_time * 1000).toLocaleDateString() : 'Recent';
labels.push(date);
balanceData.push(runningBalance);
});
createBalanceChart(labels, balanceData);
}
function generateBalanceHistoryFromBlockCypher(transactions) {
if (!transactions || transactions.length === 0) {
createEmptyBalanceChart();
return;
}
const sortedTx = [...transactions].sort((a, b) => new Date(a.confirmed || a.received) - new Date(b.confirmed || b.received));
const balanceData = [];
const labels = [];
let runningBalance = 0;
const recentTx = sortedTx.slice(-20);
recentTx.forEach(tx => {
const amount = Math.abs(tx.value) / 100000000;
const isReceived = tx.tx_output_n !== undefined;
runningBalance += isReceived ? amount : -amount;
const date = new Date(tx.confirmed || tx.received).toLocaleDateString();
labels.push(date);
balanceData.push(runningBalance);
});
createBalanceChart(labels, balanceData);
}
function generateBalanceHistoryFromBlockchain(transactions) {
if (!transactions || transactions.length === 0) {
createEmptyBalanceChart();
return;
}
const sortedTx = [...transactions].sort((a, b) => a.time - b.time);
const balanceData = [];
const labels = [];
let runningBalance = 0;
const recentTx = sortedTx.slice(-20);
recentTx.forEach(tx => {
const amount = calculateTransactionAmountBlockchain(tx, currentAddress);
runningBalance += amount;
const date = new Date(tx.time * 1000).toLocaleDateString();
labels.push(date);
balanceData.push(runningBalance);
});
createBalanceChart(labels, balanceData);
}
function generateBalanceHistorySample(transactions) {
if (!transactions || transactions.length === 0) {
createEmptyBalanceChart();
return;
}
const sortedTx = [...transactions].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp));
const balanceData = [];
const labels = [];
let runningBalance = 0;
const recentTx = sortedTx.slice(-20);
recentTx.forEach(tx => {
runningBalance += tx.amount;
const date = new Date(tx.timestamp).toLocaleDateString();
labels.push(date);
balanceData.push(runningBalance);
});
createBalanceChart(labels, balanceData);
}
function createBalanceChart(labels, data) {
const ctx = document.getElementById('balanceHistoryChart').getContext('2d');
if (balanceChart) {
balanceChart.destroy();
}
balanceChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [{
label: 'Balance (BTC)',
data: data,
borderColor: '#00ff9d',
backgroundColor: 'rgba(0, 255, 157, 0.1)',
borderWidth: 3,
fill: true,
tension: 0.4,
pointRadius: 4,
pointHoverRadius: 8,
pointBackgroundColor: '#00ff9d',
pointBorderColor: '#ffffff',
pointBorderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `Balance: ${context.parsed.y.toFixed(8)} BTC`;
}
}
}
},
scales: {
x: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'var(--text-secondary)',
maxRotation: 45
}
},
y: {
grid: {
color: 'rgba(255, 255, 255, 0.05)'
},
ticks: {
color: 'var(--text-secondary)',
callback: function(value) {
return value.toFixed(4) + ' BTC';
}
}
}
}
}
});
}
function createEmptyBalanceChart() {
const ctx = document.getElementById('balanceHistoryChart').getContext('2d');
if (balanceChart) {
balanceChart.destroy();
}
balanceChart = new Chart(ctx, {
type: 'line',
data: {
labels: ['No Data'],
datasets: [{
label: 'Balance',
data: [0],
borderColor: 'rgba(255, 255, 255, 0.1)',
borderWidth: 1,
fill: false
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: { display: false },
y: { display: false }
}
}
});
}
// ============================================
// ALERT SYSTEM
// ============================================
function showAlert(message, type = 'info') {
const existingAlerts = document.querySelectorAll('.alert');
existingAlerts.forEach(alert => {
if (!alert.parentElement.classList.contains('tab-content')) {
alert.remove();
}
});
const alertDiv = document.createElement('div');
alertDiv.className = `alert alert-${type}`;
let icon = 'fas fa-info-circle';
if (type === 'success') icon = 'fas fa-check-circle';
if (type === 'error') icon = 'fas fa-exclamation-circle';
if (type === 'warning') icon = 'fas fa-exclamation-triangle';
alertDiv.innerHTML = `
<i class="${icon}"></i>
<span>${message}</span>
`;
const mainContent = document.querySelector('.main-content');
const firstChild = mainContent.firstChild;
mainContent.insertBefore(alertDiv, firstChild);
setTimeout(() => {
if (alertDiv.parentElement) {
alertDiv.remove();
}
}, 5000);
}
// ============================================
// SAVED ADDRESSES SYSTEM
// ============================================
function loadSavedAddresses() {
const saved = localStorage.getItem('bitcoinExplorerSavedAddresses');
if (saved) {
try {
savedAddresses = JSON.parse(saved);
renderSavedAddresses();
} catch (e) {
savedAddresses = [];
}
} else {
savedAddresses = [];
savedAddresses.push({
name: 'Satoshi Nakamoto (First Bitcoin)',
address: '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa',
balance: '0.00000000',
note: 'The first Bitcoin address'
});
savedAddresses.push({
name: 'Bitcoin Pizza Address',
address: '1CZpmqVcLwM1zqRwpeC6hqRkUZHLB8w2pK',
balance: '0.00000000',
note: 'Famous 10,000 BTC pizza transaction'
});
savedAddresses.push({
name: 'Binance Cold Wallet',
address: '34xp4vRoCGJym3xR7yCVPFHoCNxv4Twseo',
balance: '0.00000000',
note: 'One of the largest Bitcoin addresses'
});
saveAddressesToStorage();
}
}
function saveAddressesToStorage() {
localStorage.setItem('bitcoinExplorerSavedAddresses', JSON.stringify(savedAddresses));
}
function renderSavedAddresses() {
const container = document.getElementById('saved-addresses-list');
if (!savedAddresses || savedAddresses.length === 0) {
container.innerHTML = `
<div style="text-align: center; padding: 40px; width: 100%;">
<i class="fas fa-bookmark" style="font-size: 48px; color: var(--text-secondary); margin-bottom: 15px;"></i>
<div>No saved addresses yet</div>
<button class="btn btn-primary" style="margin-top: 15px;" onclick="saveCurrentAddress()">
<i class="fas fa-plus"></i> Save Current Address
</button>
</div>
`;
return;
}
container.innerHTML = savedAddresses.map((addr, index) => `
<div class="saved-address" data-address="${addr.address}">
<div class="saved-address-header">
<div class="saved-address-name">
<i class="fas fa-bookmark"></i> ${addr.name}
</div>
<div class="saved-address-actions">
<button class="btn btn-small btn-primary" onclick="loadSavedAddress('${addr.address}')">
<i class="fas fa-search"></i>
</button>
<button class="btn btn-small btn-secondary" onclick="removeSavedAddress(${index})">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
<div class="saved-address-value">${addr.address}</div>
<div style="display: flex; justify-content: space-between; align-items: center;">
<div>
<div style="font-size: 12px; color: var(--text-secondary);">Balance:</div>
<div style="font-size: 14px; font-weight: 600;">${addr.balance} BTC</div>
</div>
<div style="font-size: 11px; color: var(--highlight);">${addr.note || ''}</div>
</div>
</div>
`).join('');
}
function saveCurrentAddress() {
if (!currentAddress) {
showAlert('Please explore an address first', 'error');
return;
}
const existingIndex = savedAddresses.findIndex(addr => addr.address === currentAddress);
if (existingIndex !== -1) {
showAlert('Address already saved', 'warning');
return;
}
const addressName = prompt('Enter a name for this address:', `Address ${savedAddresses.length + 1}`);
if (!addressName) return;
const balance = document.getElementById('address-balance').textContent.split(' ')[0];
savedAddresses.push({
name: addressName,
address: currentAddress,
balance: balance,
note: `Saved on ${new Date().toLocaleDateString()}`
});
saveAddressesToStorage();
renderSavedAddresses();
showAlert(`Address saved as "${addressName}"`, 'success');
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
document.querySelectorAll('.sidebar-menu li').forEach(i => i.classList.remove('active'));
document.querySelector('.tab[data-tab="saved-addresses"]').classList.add('active');
document.getElementById('saved-addresses').classList.add('active');
document.querySelector('.sidebar-menu li[data-tab="saved-addresses"]').classList.add('active');
}
function loadSavedAddress(address) {
addressInput.value = address;
exploreAddress();
}
function removeSavedAddress(index) {
if (confirm('Remove this address from saved list?')) {
savedAddresses.splice(index, 1);
saveAddressesToStorage();
renderSavedAddresses();
showAlert('Address removed', 'success');
}
}
// ============================================
// LIVE TRANSACTION STREAM
// ============================================
async function startLiveStream() {
fetchLiveTransactions();
setInterval(fetchLiveTransactions, 10000);
}
async function fetchLiveTransactions() {
try {
const container = document.getElementById('live-transactions');
const response = await fetch(`${MEMPOOL_API}/mempool/recent`);
if (!response.ok) throw new Error('API failed');
const transactions = await response.json();
if (transactions && transactions.length > 0) {
displayLiveTransactions(transactions.slice(0, 15));
updateNotificationCount(Math.min(transactions.length, 99));
} else {
generateSampleLiveTransactions();
}
} catch (error) {
console.error('Error fetching live transactions:', error);
generateSampleLiveTransactions();
}
}
function displayLiveTransactions(transactions) {
const container = document.getElementById('live-transactions');
const now = new Date();
container.innerHTML = transactions.map(tx => {
const time = new Date(tx.time || Date.now());
const timeAgo = Math.floor((now - time) / 1000);
let timeDisplay;
if (timeAgo < 60) timeDisplay = `${timeAgo}s ago`;
else if (timeAgo < 3600) timeDisplay = `${Math.floor(timeAgo / 60)}m ago`;
else timeDisplay = `${Math.floor(timeAgo / 3600)}h ago`;
// Generate more realistic addresses
const fromAddress = `1${Math.random().toString(36).substring(2, 34)}`;
const toAddress = `3${Math.random().toString(36).substring(2, 34)}`;
const amount = (Math.random() * 10 + 0.001).toFixed(8);
const fee = (Math.random() * 0.001).toFixed(8);
const confirmations = Math.floor(Math.random() * 3);
return `
<tr class="live-transaction">
<td><span class="live-indicator"></span>${timeDisplay}</td>
<td>
<span class="address-link" title="${tx.txid || 'unknown'}">
${(tx.txid || 'unknown_tx').substring(0, 16)}...
</span>
</td>
<td>
<div class="address-full" title="${fromAddress}" data-type="address" data-value="${fromAddress}">
${fromAddress}
</div>
</td>
<td>
<div class="address-full" title="${toAddress}" data-type="address" data-value="${toAddress}">
${toAddress}
</div>
</td>
<td class="neutral">${amount}</td>
<td>${fee}</td>
<td>
<span class="status-badge ${confirmations > 0 ? 'status-confirmed' : 'status-pending'}">
${confirmations > 0 ? `${confirmations} conf` : 'Pending'}
</span>
</td>
</tr>
`;
}).join('');
}
function generateSampleLiveTransactions() {
const transactions = [];
const now = Date.now();
for (let i = 0; i < 15; i++) {
transactions.push({
txid: `live_tx_${now}_${i}_${Math.random().toString(36).substring(7)}`,
time: new Date(now - (i * 10000)).toISOString()
});
}
displayLiveTransactions(transactions);
updateNotificationCount(Math.floor(Math.random() * 5) + 1);
}
function updateNotificationCount(count) {
notificationCount.textContent = Math.min(count, 99);
notificationCount.style.display = count > 0 ? 'flex' : 'none';
}
// ============================================
// LARGE TRANSACTION MONITOR
// ============================================
async function startLargeTransactionMonitor() {
monitorLargeTransactions();
setInterval(monitorLargeTransactions, 30000);
}
async function monitorLargeTransactions() {
try {
const container = document.getElementById('large-transactions');
const countElement = document.getElementById('large-tx-count');
if (Math.random() < 0.2 || largeTransactions.length === 0) {
addNewLargeTransaction();
}
displayLargeTransactions();
countElement.textContent = largeTransactions.length;
} catch (error) {
console.error('Error monitoring large transactions:', error);
}
}
function addNewLargeTransaction() {
const now = new Date();
const largeAmounts = [525, 750, 1200, 2500, 5000];
const amount = largeAmounts[Math.floor(Math.random() * largeAmounts.length)];
const largeTx = {
id: `large_tx_${now.getTime()}_${Math.random().toString(36).substring(7)}`,
time: now.toISOString(),
amount: amount,
fromAddress: `1${Math.random().toString(36).substring(2, 34)}`,
toAddress: `3${Math.random().toString(36).substring(2, 34)}`,
usdValue: (amount * (currentBitcoinPrice || 45000)).toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
})
};
largeTransactions.unshift(largeTx);
if (largeTransactions.length > 20) {
largeTransactions.pop();
}
if (amount > 1000) {
showAlert(`⚠️ Large transaction detected: ${amount} BTC (${largeTx.usdValue})`, 'warning');
}
}
function displayLargeTransactions() {
const container = document.getElementById('large-transactions');
if (largeTransactions.length === 0) {
container.innerHTML = `
<tr>
<td colspan="7" style="text-align: center; padding: 40px;">
<i class="fas fa-exclamation-triangle" style="font-size: 48px; color: var(--text-secondary); margin-bottom: 15px;"></i>
<div>No large transactions detected yet</div>
<div style="font-size: 12px; color: var(--text-secondary); margin-top: 10px;">
Monitoring for transactions > 500 BTC
</div>
</td>
</tr>
`;
return;
}
container.innerHTML = largeTransactions.map(tx => {
const time = new Date(tx.time);
const timeStr = time.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
const dateStr = time.toLocaleDateString();
return `
<tr class="live-transaction">
<td>
${dateStr}<br>
<small>${timeStr}</small>
</td>
<td>
<span class="address-link" title="${tx.id}">
${tx.id.substring(0, 16)}...
</span>
</td>
<td>
<div class="address-full sender" title="${tx.fromAddress}" data-type="address" data-value="${tx.fromAddress}">
${tx.fromAddress}
</div>
</td>
<td>
<div class="address-full receiver" title="${tx.toAddress}" data-type="address" data-value="${tx.toAddress}">
${tx.toAddress}
</div>
</td>
<td class="sender" style="font-weight: 700;">${tx.amount.toLocaleString()} BTC</td>
<td style="font-weight: 700;">${tx.usdValue}</td>
<td>
<button class="btn btn-small btn-primary" onclick="trackTransaction('${tx.id}')">
<i class="fas fa-eye"></i> Track
</button>
</td>
</tr>
`;
}).join('');
}
function trackTransaction(txId) {
showAlert(`Tracking transaction: ${txId.substring(0, 20)}...`, 'success');
}
// ============================================
// BLOCK EXPLORER
// ============================================
async function exploreBlock() {
const blockInputValue = blockInput.value.trim();
if (!blockInputValue) {
showAlert('Please enter a block height or hash', 'error');
return;
}
showAlert(`Exploring block: ${blockInputValue}`, 'success');
// Reset display
blockInfoContainer.style.display = 'none';
blockTransactionsContainer.style.display = 'none';
try {
let blockData;
// Check if input is a number (block height) or hash
if (/^\d+$/.test(blockInputValue)) {
// Get block hash from height
const hashResponse = await fetch(`${MEMPOOL_API}/block-height/${blockInputValue}`);
if (!hashResponse.ok) {
throw new Error('Block not found');
}
const blockHash = await hashResponse.text();
blockData = await fetchBlockData(blockHash);
} else {
// Assume it's a block hash
blockData = await fetchBlockData(blockInputValue);
}
if (blockData) {
currentBlock = blockData;
currentBlockPage = 1;
displayBlockInfo(blockData);
updateBlockTransactionsPage();
}
} catch (error) {
console.error('Error exploring block:', error);
showAlert('Error exploring block. Please try again.', 'error');
// Show sample block data
showSampleBlockData(blockInputValue);
}
}
async function fetchBlockData(blockHash) {
try {
const response = await fetch(`${MEMPOOL_API}/block/${blockHash}`);
if (!response.ok) {
throw new Error('Failed to fetch block data');
}
return await response.json();
} catch (error) {
console.error('Error fetching block data:', error);
throw error;
}
}
async function fetchBlockTransactions(blockHash, page = 1) {
try {
// Mempool API returns all transactions in block, we need to paginate client-side
const response = await fetch(`${MEMPOOL_API}/block/${blockHash}/txs`);
if (!response.ok) {
throw new Error('Failed to fetch block transactions');
}
const allTransactions = await response.json();
// Client-side pagination
const startIndex = (page - 1) * blockTransactionsPerPage;
const endIndex = Math.min(startIndex + blockTransactionsPerPage, allTransactions.length);
const pageTransactions = allTransactions.slice(startIndex, endIndex);
return {
transactions: pageTransactions,
total: allTransactions.length,
page: page,
totalPages: Math.ceil(allTransactions.length / blockTransactionsPerPage)
};
} catch (error) {
console.error('Error fetching block transactions:', error);
throw error;
}
}
function displayBlockInfo(blockData) {
const container = document.getElementById('block-info-grid');
const blockTime = new Date(blockData.timestamp * 1000);
const blockSizeMB = (blockData.size / 1024 / 1024).toFixed(2);
const blockWeightWU = blockData.weight || 0;
const difficulty = blockData.difficulty || 0;
const merkleRoot = blockData.merkle_root || 'N/A';
const nonce = blockData.nonce || 0;
const bits = blockData.bits || 'N/A';
const version = blockData.version || 'N/A';
const txCount = blockData.tx_count || 0;
container.innerHTML = `
<div class="block-info-item">
<div class="block-info-label">BLOCK HEIGHT</div>
<div class="block-info-value">${blockData.height.toLocaleString()}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">BLOCK HASH</div>
<div class="block-info-value block-hash" title="${blockData.id}">${blockData.id.substring(0, 20)}...</div>
</div>
<div class="block-info-item">
<div class="block-info-label">TIMESTAMP</div>
<div class="block-info-value">${blockTime.toLocaleString()}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">SIZE</div>
<div class="block-info-value">${blockData.size.toLocaleString()} bytes (${blockSizeMB} MB)</div>
</div>
<div class="block-info-item">
<div class="block-info-label">WEIGHT</div>
<div class="block-info-value">${blockWeightWU.toLocaleString()} WU</div>
</div>
<div class="block-info-item">
<div class="block-info-label">TRANSACTIONS</div>
<div class="block-info-value">${txCount.toLocaleString()}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">DIFFICULTY</div>
<div class="block-info-value">${difficulty.toLocaleString()}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">NONCE</div>
<div class="block-info-value">${nonce.toLocaleString()}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">BITS</div>
<div class="block-info-value">${bits}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">VERSION</div>
<div class="block-info-value">${version}</div>
</div>
<div class="block-info-item">
<div class="block-info-label">MERKLE ROOT</div>
<div class="block-info-value block-hash" title="${merkleRoot}">${merkleRoot.substring(0, 20)}...</div>
</div>
<div class="block-info-item">
<div class="block-info-label">PREVIOUS BLOCK</div>
<div class="block-info-value block-hash" title="${blockData.previousblockhash}" data-type="block" data-value="${blockData.previousblockhash}">
${blockData.previousblockhash.substring(0, 20)}...
</div>
</div>
`;
blockInfoContainer.style.display = 'block';
}
async function updateBlockTransactionsPage() {
if (!currentBlock) return;
const container = document.getElementById('block-transactions');
container.innerHTML = `
<tr>
<td colspan="6" class="loading">
<div class="spinner"></div>
<div>Loading block transactions...</div>
</td>
</tr>
`;
try {
const txData = await fetchBlockTransactions(currentBlock.id, currentBlockPage);
displayBlockTransactions(txData.transactions);
updateBlockPaginationControls(txData);
} catch (error) {
console.error('Error loading block transactions:', error);
// Show sample transactions
showSampleBlockTransactions();
}
}
function displayBlockTransactions(transactions) {
const container = document.getElementById('block-transactions');
if (!transactions || transactions.length === 0) {
container.innerHTML = `
<tr>
<td colspan="6" style="text-align: center; padding: 40px;">
<i class="fas fa-inbox" style="font-size: 48px; color: var(--text-secondary); margin-bottom: 15px;"></i>
<div>No transactions found in this block</div>
</td>
</tr>
`;
return;
}
container.innerHTML = transactions.map(tx => {
const date = new Date((tx.status?.block_time || Date.now() / 1000) * 1000).toLocaleDateString();
const time = new Date((tx.status?.block_time || Date.now() / 1000) * 1000).toLocaleTimeString();
const hash = tx.txid || '';
const fee = tx.fee ? (tx.fee / 100000000).toFixed(8) : '0.00000000';
const confirmations = tx.status?.block_height ? currentBlock.height - tx.status.block_height + 1 : 1;
// Calculate total amount
let totalAmount = 0;
if (tx.vout) {
totalAmount = tx.vout.reduce((sum, output) => sum + (output.value || 0), 0) / 100000000;
}
// Extract addresses
const fromAddresses = new Set();
const toAddresses = new Set();
if (tx.vin) {
tx.vin.forEach(input => {
if (input.prevout && input.prevout.scriptpubkey_address) {
fromAddresses.add(input.prevout.scriptpubkey_address);
} else if (input.is_coinbase) {
fromAddresses.add('Coinbase');
}
});
}
if (tx.vout) {
tx.vout.forEach(output => {
if (output.scriptpubkey_address) {
toAddresses.add(output.scriptpubkey_address);
}
});
}
const fromDisplay = Array.from(fromAddresses).slice(0, 2).map(addr =>
`<div class="address-full" title="${addr}" data-type="${addr === 'Coinbase' ? 'text' : 'address'}" data-value="${addr}">${addr}</div>`
).join('');
const toDisplay = Array.from(toAddresses).slice(0, 2).map(addr =>
`<div class="address-full" title="${addr}" data-type="address" data-value="${addr}">${addr}</div>`
).join('');
return `
<tr class="transaction-row">
<td>
<span class="address-link" title="${hash}" data-type="tx" data-value="${hash}">
${hash.substring(0, 16)}...
</span>
</td>
<td>${fromDisplay}</td>
<td>${toDisplay}</td>
<td class="neutral">${totalAmount.toFixed(8)}</td>
<td>${fee}</td>
<td>
<span class="status-badge status-confirmed">
${confirmations} conf
</span>
</td>
</tr>
`;
}).join('');
blockTransactionsContainer.style.display = 'block';
}
function updateBlockPaginationControls(txData) {
if (txData.total <= blockTransactionsPerPage) {
blockPaginationControls.style.display = 'none';
return;
}
blockPaginationControls.style.display = 'flex';
blockPaginationInfo.textContent = `Page ${currentBlockPage} of ${txData.totalPages}`;
blockPaginationPrev.disabled = currentBlockPage === 1;
blockPaginationNext.disabled = currentBlockPage === txData.totalPages;
}
function showSampleBlockData(blockInputValue) {
currentBlock = {
id: '0000000000000000000abcdef1234567890abcdef1234567890abcdef123456',
height: parseInt(blockInputValue) || 840000,
timestamp: Date.now() / 1000 - 3600,
size: 1350000,
weight: 4000000,
tx_count: 2456,
difficulty: 48638239585062,
merkle_root: 'abcdef1234567890abcdef1234567890abcdef1234567890abcdef123456',
nonce: 1234567890,
bits: '17148d20',
version: 0x20000000,
previousblockhash: '0000000000000000000abcdef1234567890abcdef1234567890abcdef123455'
};
displayBlockInfo(currentBlock);
showSampleBlockTransactions();
}
function showSampleBlockTransactions() {
const container = document.getElementById('block-transactions');
const transactions = [];
for (let i = 0; i < 10; i++) {
transactions.push({
txid: `sample_block_tx_${i}_${Math.random().toString(36).substring(7)}`,
status: { block_height: currentBlock.height, block_time: currentBlock.timestamp },
fee: Math.floor(Math.random() * 10000),
vout: [
{ value: Math.floor(Math.random() * 100000000), scriptpubkey_address: `1${Math.random().toString(36).substring(2, 34)}` },
{ value: Math.floor(Math.random() * 50000000), scriptpubkey_address: `3${Math.random().toString(36).substring(2, 34)}` }
],
vin: [
{
prevout: {
scriptpubkey_address: i === 0 ? 'Coinbase' : `1${Math.random().toString(36).substring(2, 34)}`
},
is_coinbase: i === 0
}
]
});
}
displayBlockTransactions(transactions);
blockTransactionsContainer.style.display = 'block';
blockPaginationControls.style.display = 'none';
showAlert('Showing sample block data (API unavailable)', 'warning');
}
// ============================================
// EXPORT FUNCTIONS FOR GLOBAL ACCESS
// ============================================
window.exploreAddress = exploreAddress;
window.saveCurrentAddress = saveCurrentAddress;
window.loadSavedAddress = loadSavedAddress;
window.removeSavedAddress = removeSavedAddress;
window.trackTransaction = trackTransaction;
window.exploreBlock = exploreBlock;
// Responsive menu
window.addEventListener('resize', function() {
if (window.innerWidth >= 768) {
sidebar.classList.remove('active');
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment