Download index.html from dazman72/interactive-blockchain-explorer: direct link, hf CLI and curl.
- Browser
- Download file 48.6 kB
-
https://huggingface.co/spaces/dazman72/interactive-blockchain-explorer/resolve/main/index.html
- Command line
-
hf download hf://spaces/dazman72/interactive-blockchain-explorer/index.html
-
curl -L -o index.html https://huggingface.co/spaces/dazman72/interactive-blockchain-explorer/resolve/main/index.html
48.6 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Interactive Blockchain Explorer</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/particles.js@2.0.0/particles.min.js"></script> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| .blockchain-node { | |
| transition: all 0.3s ease; | |
| } | |
| .blockchain-node:hover { | |
| transform: scale(1.05); | |
| box-shadow: 0 10px 25px rgba(59, 130, 246, 0.3); | |
| } | |
| .block-highlight { | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0.7); } | |
| 70% { box-shadow: 0 0 0 10px rgba(59, 130, 246, 0); } | |
| 100% { box-shadow: 0 0 0 0 rgba(59, 130, 246, 0); } | |
| } | |
| #particles-js { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| z-index: -1; | |
| background: linear-gradient(135deg, #1e3a8a 0%, #0f172a 100%); | |
| } | |
| .transaction-card { | |
| transition: all 0.3s ease; | |
| } | |
| .transaction-card:hover { | |
| transform: translateY(-5px); | |
| } | |
| .mining-animation { | |
| animation: mining 1.5s infinite; | |
| } | |
| @keyframes mining { | |
| 0% { opacity: 0.5; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.5; } | |
| } | |
| .blockchain-visualization { | |
| perspective: 1000px; | |
| } | |
| .block-3d { | |
| transform-style: preserve-3d; | |
| transition: transform 0.5s; | |
| } | |
| .block-3d:hover { | |
| transform: rotateY(15deg) rotateX(5deg); | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-gray-100 min-h-screen overflow-x-hidden"> | |
| <div id="particles-js"></div> | |
| <div class="container mx-auto px-4 py-8"> | |
| <!-- Header --> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-4xl md:text-6xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-600 mb-4"> | |
| Interactive Blockchain Explorer | |
| </h1> | |
| <p class="text-xl text-blue-200 max-w-3xl mx-auto"> | |
| Visualize how blockchain technology works through this interactive demonstration. Create transactions, mine blocks, and see the decentralized network in action. | |
| </p> | |
| </header> | |
| <!-- Main Dashboard --> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12"> | |
| <!-- Control Panel --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700"> | |
| <h2 class="text-2xl font-bold mb-6 text-blue-400 flex items-center"> | |
| <i class="fas fa-sliders-h mr-3"></i> Control Panel | |
| </h2> | |
| <div class="space-y-6"> | |
| <div> | |
| <h3 class="text-lg font-semibold mb-3 text-blue-300">Create Transaction</h3> | |
| <div class="grid grid-cols-2 gap-3 mb-3"> | |
| <div> | |
| <label class="block text-sm text-gray-300 mb-1">From</label> | |
| <select id="fromAddress" class="w-full bg-gray-700 rounded px-3 py-2 text-sm"> | |
| <option value="Alice">Alice</option> | |
| <option value="Bob">Bob</option> | |
| <option value="Charlie">Charlie</option> | |
| <option value="Diana">Diana</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label class="block text-sm text-gray-300 mb-1">To</label> | |
| <select id="toAddress" class="w-full bg-gray-700 rounded px-3 py-2 text-sm"> | |
| <option value="Bob">Bob</option> | |
| <option value="Alice">Alice</option> | |
| <option value="Charlie">Charlie</option> | |
| <option value="Diana">Diana</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-300 mb-1">Amount</label> | |
| <input type="number" id="transactionAmount" class="w-full bg-gray-700 rounded px-3 py-2 text-sm" placeholder="0.00" min="0.01" step="0.01"> | |
| </div> | |
| <button id="createTransactionBtn" class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-paper-plane mr-2"></i> Create Transaction | |
| </button> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold mb-3 text-blue-300">Mine Block</h3> | |
| <div class="flex items-center mb-3"> | |
| <div class="w-full bg-gray-700 rounded-lg h-2 mr-3"> | |
| <div id="miningProgress" class="bg-yellow-500 h-2 rounded-lg" style="width: 0%"></div> | |
| </div> | |
| <span id="miningDifficulty" class="text-sm text-gray-300">Difficulty: 4</span> | |
| </div> | |
| <button id="mineBlockBtn" class="w-full bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded-lg transition flex items-center justify-center"> | |
| <i class="fas fa-hammer mr-2"></i> Mine Block | |
| </button> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold mb-3 text-blue-300">Network Settings</h3> | |
| <div class="mb-3"> | |
| <label class="flex items-center"> | |
| <input type="checkbox" id="autoMineToggle" class="mr-2"> | |
| <span class="text-sm text-gray-300">Auto-mine transactions</span> | |
| </label> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="block text-sm text-gray-300 mb-1">Network Nodes</label> | |
| <div class="flex flex-wrap gap-2"> | |
| <span class="bg-gray-700 px-3 py-1 rounded-full text-xs flex items-center"> | |
| <span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> Node 1 | |
| </span> | |
| <span class="bg-gray-700 px-3 py-1 rounded-full text-xs flex items-center"> | |
| <span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> Node 2 | |
| </span> | |
| <span class="bg-gray-700 px-3 py-1 rounded-full text-xs flex items-center"> | |
| <span class="w-2 h-2 bg-green-500 rounded-full mr-2"></span> Node 3 | |
| </span> | |
| <button class="bg-gray-700 hover:bg-gray-600 px-3 py-1 rounded-full text-xs flex items-center transition"> | |
| <i class="fas fa-plus mr-1"></i> Add Node | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Blockchain Visualization --> | |
| <div class="lg:col-span-2"> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700 h-full"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-2xl font-bold text-blue-400 flex items-center"> | |
| <i class="fas fa-link mr-3"></i> Blockchain | |
| </h2> | |
| <div class="text-sm bg-gray-700 px-3 py-1 rounded-full"> | |
| <span id="blockCount" class="font-semibold">1</span> Blocks | |
| </div> | |
| </div> | |
| <div class="blockchain-visualization"> | |
| <div id="blockchainContainer" class="space-y-4"> | |
| <!-- Genesis Block --> | |
| <div class="block-3d bg-gray-700 rounded-lg p-5 shadow-lg border-l-4 border-blue-500 relative overflow-hidden"> | |
| <div class="absolute top-0 right-0 bg-blue-600 text-white text-xs px-2 py-1 rounded-bl-lg"> | |
| Genesis Block | |
| </div> | |
| <div class="flex justify-between items-start mb-3"> | |
| <div> | |
| <h3 class="font-bold text-lg">Block #0</h3> | |
| <p class="text-xs text-gray-400">0000000000000000000000000000000000000000</p> | |
| </div> | |
| <div class="text-right"> | |
| <div class="text-xs text-gray-400">Nonce: 0</div> | |
| <div class="text-xs text-gray-400">Timestamp: 0</div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 rounded p-3 mb-3"> | |
| <div class="text-xs text-gray-400 mb-1">Merkle Root:</div> | |
| <div class="text-xs font-mono break-all">00000000000000000000000000000000</div> | |
| </div> | |
| <div class="text-xs text-gray-400">Previous Hash: None</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div id="pendingTransactions" class="mt-8"> | |
| <h3 class="text-lg font-semibold mb-3 text-blue-300 flex items-center"> | |
| <i class="fas fa-clock mr-2"></i> Pending Transactions | |
| <span id="pendingCount" class="ml-2 bg-gray-700 px-2 py-0.5 rounded-full text-xs">0</span> | |
| </h3> | |
| <div id="transactionsContainer" class="space-y-3"> | |
| <div class="text-center py-4 text-gray-500"> | |
| No pending transactions | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Network Visualization --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700 mb-12"> | |
| <h2 class="text-2xl font-bold mb-6 text-blue-400 flex items-center"> | |
| <i class="fas fa-network-wired mr-3"></i> Network Visualization | |
| </h2> | |
| <div class="relative h-64 md:h-96 bg-gray-900 rounded-lg overflow-hidden"> | |
| <canvas id="networkCanvas" class="w-full h-full"></canvas> | |
| <div class="absolute bottom-4 left-4 bg-gray-800 bg-opacity-80 px-3 py-2 rounded-lg text-sm"> | |
| <div class="flex items-center mb-1"> | |
| <div class="w-3 h-3 bg-blue-500 rounded-full mr-2"></div> | |
| <span>Nodes: <span id="nodeCount">4</span></span> | |
| </div> | |
| <div class="flex items-center"> | |
| <div class="w-3 h-3 bg-green-500 rounded-full mr-2"></div> | |
| <span>Connections: <span id="connectionCount">6</span></span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Statistics and Charts --> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mb-12"> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700"> | |
| <h2 class="text-2xl font-bold mb-6 text-blue-400 flex items-center"> | |
| <i class="fas fa-chart-line mr-3"></i> Blockchain Statistics | |
| </h2> | |
| <div class="grid grid-cols-2 gap-4 mb-6"> | |
| <div class="bg-gray-700 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-400 mb-1">Total Blocks</div> | |
| <div class="text-2xl font-bold" id="statsBlocks">1</div> | |
| </div> | |
| <div class="bg-gray-700 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-400 mb-1">Total Transactions</div> | |
| <div class="text-2xl font-bold" id="statsTransactions">0</div> | |
| </div> | |
| <div class="bg-gray-700 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-400 mb-1">Average Block Time</div> | |
| <div class="text-2xl font-bold" id="statsBlockTime">0s</div> | |
| </div> | |
| <div class="bg-gray-700 p-4 rounded-lg"> | |
| <div class="text-sm text-gray-400 mb-1">Network Hashrate</div> | |
| <div class="text-2xl font-bold" id="statsHashrate">0 H/s</div> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold mb-3 text-blue-300">Block Time History</h3> | |
| <canvas id="blockTimeChart" height="200"></canvas> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700"> | |
| <h2 class="text-2xl font-bold mb-6 text-blue-400 flex items-center"> | |
| <i class="fas fa-coins mr-3"></i> Wallet Balances | |
| </h2> | |
| <div class="mb-6"> | |
| <canvas id="balanceChart" height="200"></canvas> | |
| </div> | |
| <div class="overflow-x-auto"> | |
| <table class="w-full"> | |
| <thead> | |
| <tr class="text-left text-sm text-gray-400 border-b border-gray-700"> | |
| <th class="pb-2">Wallet</th> | |
| <th class="pb-2">Balance</th> | |
| <th class="pb-2">Transactions</th> | |
| </tr> | |
| </thead> | |
| <tbody id="walletBalances"> | |
| <tr class="border-b border-gray-700"> | |
| <td class="py-3 flex items-center"> | |
| <div class="w-2 h-2 bg-blue-500 rounded-full mr-2"></div> | |
| Alice | |
| </td> | |
| <td class="py-3">100.00</td> | |
| <td class="py-3">0</td> | |
| </tr> | |
| <tr class="border-b border-gray-700"> | |
| <td class="py-3 flex items-center"> | |
| <div class="w-2 h-2 bg-green-500 rounded-full mr-2"></div> | |
| Bob | |
| </td> | |
| <td class="py-3">100.00</td> | |
| <td class="py-3">0</td> | |
| </tr> | |
| <tr class="border-b border-gray-700"> | |
| <td class="py-3 flex items-center"> | |
| <div class="w-2 h-2 bg-purple-500 rounded-full mr-2"></div> | |
| Charlie | |
| </td> | |
| <td class="py-3">100.00</td> | |
| <td class="py-3">0</td> | |
| </tr> | |
| <tr> | |
| <td class="py-3 flex items-center"> | |
| <div class="w-2 h-2 bg-yellow-500 rounded-full mr-2"></div> | |
| Diana | |
| </td> | |
| <td class="py-3">100.00</td> | |
| <td class="py-3">0</td> | |
| </tr> | |
| </tbody> | |
| </table> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Educational Section --> | |
| <div class="bg-gray-800 rounded-xl p-6 shadow-xl border border-gray-700 mb-12"> | |
| <h2 class="text-2xl font-bold mb-6 text-blue-400 flex items-center"> | |
| <i class="fas fa-graduation-cap mr-3"></i> How Blockchain Works | |
| </h2> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-6"> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-cube"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Blocks</h3> | |
| <p class="text-sm text-gray-300"> | |
| Blocks are containers that hold transactions. Each block is cryptographically linked to the previous one, forming a chain. | |
| </p> | |
| </div> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-lock"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Cryptography</h3> | |
| <p class="text-sm text-gray-300"> | |
| Blockchain uses cryptographic hashes to secure data. Changing any information would require changing all subsequent blocks. | |
| </p> | |
| </div> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-users"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Decentralization</h3> | |
| <p class="text-sm text-gray-300"> | |
| The blockchain is maintained by a network of nodes that all have a copy of the ledger, making it resistant to tampering. | |
| </p> | |
| </div> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-hammer"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Mining</h3> | |
| <p class="text-sm text-gray-300"> | |
| Miners solve complex mathematical problems to add new blocks to the chain and are rewarded with cryptocurrency. | |
| </p> | |
| </div> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-handshake"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Consensus</h3> | |
| <p class="text-sm text-gray-300"> | |
| Blockchain networks use consensus mechanisms like Proof of Work or Proof of Stake to agree on the state of the ledger. | |
| </p> | |
| </div> | |
| <div class="bg-gray-700 p-5 rounded-lg hover:bg-gray-600 transition cursor-pointer"> | |
| <div class="text-blue-400 text-4xl mb-3"> | |
| <i class="fas fa-shield-alt"></i> | |
| </div> | |
| <h3 class="text-lg font-semibold mb-2">Immutability</h3> | |
| <p class="text-sm text-gray-300"> | |
| Once data is recorded in a block and added to the chain, it becomes extremely difficult to alter, creating a permanent record. | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize particles.js | |
| document.addEventListener('DOMContentLoaded', function() { | |
| particlesJS('particles-js', { | |
| "particles": { | |
| "number": { | |
| "value": 80, | |
| "density": { | |
| "enable": true, | |
| "value_area": 800 | |
| } | |
| }, | |
| "color": { | |
| "value": "#3b82f6" | |
| }, | |
| "shape": { | |
| "type": "circle", | |
| "stroke": { | |
| "width": 0, | |
| "color": "#000000" | |
| }, | |
| "polygon": { | |
| "nb_sides": 5 | |
| } | |
| }, | |
| "opacity": { | |
| "value": 0.5, | |
| "random": false, | |
| "anim": { | |
| "enable": false, | |
| "speed": 1, | |
| "opacity_min": 0.1, | |
| "sync": false | |
| } | |
| }, | |
| "size": { | |
| "value": 3, | |
| "random": true, | |
| "anim": { | |
| "enable": false, | |
| "speed": 40, | |
| "size_min": 0.1, | |
| "sync": false | |
| } | |
| }, | |
| "line_linked": { | |
| "enable": true, | |
| "distance": 150, | |
| "color": "#3b82f6", | |
| "opacity": 0.4, | |
| "width": 1 | |
| }, | |
| "move": { | |
| "enable": true, | |
| "speed": 2, | |
| "direction": "none", | |
| "random": false, | |
| "straight": false, | |
| "out_mode": "out", | |
| "bounce": false, | |
| "attract": { | |
| "enable": false, | |
| "rotateX": 600, | |
| "rotateY": 1200 | |
| } | |
| } | |
| }, | |
| "interactivity": { | |
| "detect_on": "canvas", | |
| "events": { | |
| "onhover": { | |
| "enable": true, | |
| "mode": "grab" | |
| }, | |
| "onclick": { | |
| "enable": true, | |
| "mode": "push" | |
| }, | |
| "resize": true | |
| }, | |
| "modes": { | |
| "grab": { | |
| "distance": 140, | |
| "line_linked": { | |
| "opacity": 1 | |
| } | |
| }, | |
| "bubble": { | |
| "distance": 400, | |
| "size": 40, | |
| "duration": 2, | |
| "opacity": 8, | |
| "speed": 3 | |
| }, | |
| "repulse": { | |
| "distance": 200, | |
| "duration": 0.4 | |
| }, | |
| "push": { | |
| "particles_nb": 4 | |
| }, | |
| "remove": { | |
| "particles_nb": 2 | |
| } | |
| } | |
| }, | |
| "retina_detect": true | |
| }); | |
| }); | |
| // Blockchain simulation | |
| class Blockchain { | |
| constructor() { | |
| this.chain = [this.createGenesisBlock()]; | |
| this.pendingTransactions = []; | |
| this.difficulty = 4; | |
| this.miningReward = 10; | |
| this.wallets = { | |
| 'Alice': { balance: 100, transactions: 0 }, | |
| 'Bob': { balance: 100, transactions: 0 }, | |
| 'Charlie': { balance: 100, transactions: 0 }, | |
| 'Diana': { balance: 100, transactions: 0 } | |
| }; | |
| this.blockTimes = []; | |
| this.nodes = 4; | |
| this.connections = 6; | |
| } | |
| createGenesisBlock() { | |
| return { | |
| index: 0, | |
| timestamp: new Date().toISOString(), | |
| transactions: [], | |
| previousHash: '0', | |
| hash: this.calculateHash(0, '0', [], 0), | |
| nonce: 0 | |
| }; | |
| } | |
| getLatestBlock() { | |
| return this.chain[this.chain.length - 1]; | |
| } | |
| createTransaction(from, to, amount) { | |
| if (this.wallets[from].balance < amount) { | |
| alert('Insufficient balance!'); | |
| return false; | |
| } | |
| const transaction = { | |
| from, | |
| to, | |
| amount, | |
| timestamp: new Date().toISOString() | |
| }; | |
| this.pendingTransactions.push(transaction); | |
| return true; | |
| } | |
| minePendingTransactions(miningRewardAddress) { | |
| const block = { | |
| index: this.chain.length, | |
| previousHash: this.getLatestBlock().hash, | |
| transactions: this.pendingTransactions, | |
| timestamp: new Date().toISOString(), | |
| nonce: 0 | |
| }; | |
| // Process transactions | |
| this.pendingTransactions.forEach(tx => { | |
| this.wallets[tx.from].balance -= tx.amount; | |
| this.wallets[tx.to].balance += tx.amount; | |
| this.wallets[tx.from].transactions++; | |
| this.wallets[tx.to].transactions++; | |
| }); | |
| // Add mining reward | |
| this.wallets[miningRewardAddress].balance += this.miningReward; | |
| // Calculate hash with proof of work | |
| block.hash = this.proofOfWork(block); | |
| // Add block to chain | |
| this.chain.push(block); | |
| // Record block time | |
| if (this.chain.length > 1) { | |
| const prevTime = new Date(this.chain[this.chain.length - 2].timestamp); | |
| const currTime = new Date(block.timestamp); | |
| this.blockTimes.push((currTime - prevTime) / 1000); | |
| } | |
| // Reset pending transactions | |
| this.pendingTransactions = []; | |
| } | |
| proofOfWork(block) { | |
| let nonce = 0; | |
| let hash; | |
| const startTime = Date.now(); | |
| do { | |
| nonce++; | |
| hash = this.calculateHash(block.index, block.previousHash, block.transactions, nonce); | |
| // Update UI with mining progress | |
| const progress = Math.min(99, (nonce / Math.pow(10, this.difficulty)) * 100); | |
| document.getElementById('miningProgress').style.width = `${progress}%`; | |
| // Small delay for animation | |
| if (nonce % 1000 === 0) { | |
| return new Promise(resolve => setTimeout(resolve, 0)).then(() => {}); | |
| } | |
| } while (!hash.startsWith('0'.repeat(this.difficulty))); | |
| block.nonce = nonce; | |
| const endTime = Date.now(); | |
| console.log(`Block mined in ${(endTime - startTime)/1000}s with nonce ${nonce}`); | |
| return hash; | |
| } | |
| calculateHash(index, previousHash, transactions, nonce) { | |
| return CryptoJS.SHA256(index + previousHash + JSON.stringify(transactions) + nonce).toString(); | |
| } | |
| isChainValid() { | |
| for (let i = 1; i < this.chain.length; i++) { | |
| const currentBlock = this.chain[i]; | |
| const previousBlock = this.chain[i - 1]; | |
| if (currentBlock.hash !== this.calculateHash( | |
| currentBlock.index, | |
| currentBlock.previousHash, | |
| currentBlock.transactions, | |
| currentBlock.nonce | |
| )) { | |
| return false; | |
| } | |
| if (currentBlock.previousHash !== previousBlock.hash) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| } | |
| // Initialize blockchain | |
| const blockchain = new Blockchain(); | |
| // DOM elements | |
| const createTransactionBtn = document.getElementById('createTransactionBtn'); | |
| const mineBlockBtn = document.getElementById('mineBlockBtn'); | |
| const fromAddress = document.getElementById('fromAddress'); | |
| const toAddress = document.getElementById('toAddress'); | |
| const transactionAmount = document.getElementById('transactionAmount'); | |
| const blockchainContainer = document.getElementById('blockchainContainer'); | |
| const transactionsContainer = document.getElementById('transactionsContainer'); | |
| const pendingCount = document.getElementById('pendingCount'); | |
| const blockCount = document.getElementById('blockCount'); | |
| const statsBlocks = document.getElementById('statsBlocks'); | |
| const statsTransactions = document.getElementById('statsTransactions'); | |
| const statsBlockTime = document.getElementById('statsBlockTime'); | |
| const statsHashrate = document.getElementById('statsHashrate'); | |
| const walletBalances = document.getElementById('walletBalances'); | |
| const nodeCount = document.getElementById('nodeCount'); | |
| const connectionCount = document.getElementById('connectionCount'); | |
| // Initialize charts | |
| const blockTimeCtx = document.getElementById('blockTimeChart').getContext('2d'); | |
| const blockTimeChart = new Chart(blockTimeCtx, { | |
| type: 'line', | |
| data: { | |
| labels: [], | |
| datasets: [{ | |
| label: 'Block Time (seconds)', | |
| data: [], | |
| borderColor: '#3b82f6', | |
| backgroundColor: 'rgba(59, 130, 246, 0.1)', | |
| borderWidth: 2, | |
| tension: 0.4, | |
| fill: true | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| display: false | |
| } | |
| }, | |
| scales: { | |
| y: { | |
| beginAtZero: true, | |
| grid: { | |
| color: 'rgba(255, 255, 255, 0.1)' | |
| }, | |
| ticks: { | |
| color: 'rgba(255, 255, 255, 0.7)' | |
| } | |
| }, | |
| x: { | |
| grid: { | |
| color: 'rgba(255, 255, 255, 0.1)' | |
| }, | |
| ticks: { | |
| color: 'rgba(255, 255, 255, 0.7)' | |
| } | |
| } | |
| } | |
| } | |
| }); | |
| const balanceCtx = document.getElementById('balanceChart').getContext('2d'); | |
| const balanceChart = new Chart(balanceCtx, { | |
| type: 'doughnut', | |
| data: { | |
| labels: ['Alice', 'Bob', 'Charlie', 'Diana'], | |
| datasets: [{ | |
| data: [100, 100, 100, 100], | |
| backgroundColor: [ | |
| '#3b82f6', | |
| '#10b981', | |
| '#8b5cf6', | |
| '#f59e0b' | |
| ], | |
| borderWidth: 0 | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| plugins: { | |
| legend: { | |
| position: 'right', | |
| labels: { | |
| color: 'rgba(255, 255, 255, 0.7)' | |
| } | |
| } | |
| }, | |
| cutout: '70%' | |
| } | |
| }); | |
| // Initialize network visualization | |
| const networkCanvas = document.getElementById('networkCanvas'); | |
| networkCanvas.width = networkCanvas.offsetWidth; | |
| networkCanvas.height = networkCanvas.offsetHeight; | |
| const networkCtx = networkCanvas.getContext('2d'); | |
| function drawNetwork() { | |
| networkCtx.clearRect(0, 0, networkCanvas.width, networkCanvas.height); | |
| // Draw connections | |
| networkCtx.strokeStyle = 'rgba(16, 185, 129, 0.3)'; | |
| networkCtx.lineWidth = 1; | |
| // Draw nodes | |
| const nodePositions = []; | |
| for (let i = 0; i < blockchain.nodes; i++) { | |
| const x = 50 + Math.random() * (networkCanvas.width - 100); | |
| const y = 50 + Math.random() * (networkCanvas.height - 100); | |
| nodePositions.push({x, y}); | |
| // Draw connections | |
| for (let j = 0; j < i; j++) { | |
| if (Math.random() > 0.5) { | |
| networkCtx.beginPath(); | |
| networkCtx.moveTo(x, y); | |
| networkCtx.lineTo(nodePositions[j].x, nodePositions[j].y); | |
| networkCtx.stroke(); | |
| } | |
| } | |
| // Draw node | |
| const gradient = networkCtx.createRadialGradient(x, y, 5, x, y, 15); | |
| gradient.addColorStop(0, '#3b82f6'); | |
| gradient.addColorStop(1, '#1e40af'); | |
| networkCtx.beginPath(); | |
| networkCtx.arc(x, y, 15, 0, Math.PI * 2); | |
| networkCtx.fillStyle = gradient; | |
| networkCtx.fill(); | |
| networkCtx.strokeStyle = '#1e3a8a'; | |
| networkCtx.stroke(); | |
| // Draw node label | |
| networkCtx.fillStyle = 'white'; | |
| networkCtx.font = 'bold 10px Arial'; | |
| networkCtx.textAlign = 'center'; | |
| networkCtx.fillText(`Node ${i+1}`, x, y + 30); | |
| } | |
| // Update connection count | |
| connectionCount.textContent = Math.floor(blockchain.nodes * (blockchain.nodes - 1) / 2 * 0.6); | |
| } | |
| drawNetwork(); | |
| // Event listeners | |
| createTransactionBtn.addEventListener('click', () => { | |
| const from = fromAddress.value; | |
| const to = toAddress.value; | |
| const amount = parseFloat(transactionAmount.value); | |
| if (isNaN(amount) || amount <= 0) { | |
| alert('Please enter a valid amount'); | |
| return; | |
| } | |
| if (from === to) { | |
| alert('Sender and receiver cannot be the same'); | |
| return; | |
| } | |
| if (blockchain.createTransaction(from, to, amount)) { | |
| updatePendingTransactions(); | |
| transactionAmount.value = ''; | |
| // Add transaction to UI | |
| const transactionDiv = document.createElement('div'); | |
| transactionDiv.className = 'transaction-card bg-gray-700 rounded-lg p-3 border-l-4 border-blue-500'; | |
| transactionDiv.innerHTML = ` | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <div class="font-semibold">${from} → ${to}</div> | |
| <div class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</div> | |
| </div> | |
| <div class="text-lg font-bold">${amount.toFixed(2)}</div> | |
| </div> | |
| `; | |
| transactionsContainer.prepend(transactionDiv); | |
| // Animation | |
| setTimeout(() => { | |
| transactionDiv.classList.add('opacity-100'); | |
| }, 10); | |
| } | |
| }); | |
| mineBlockBtn.addEventListener('click', async () => { | |
| if (blockchain.pendingTransactions.length === 0) { | |
| alert('No pending transactions to mine'); | |
| return; | |
| } | |
| mineBlockBtn.disabled = true; | |
| mineBlockBtn.innerHTML = `<i class="fas fa-cog fa-spin mr-2"></i> Mining...`; | |
| // Random miner | |
| const miners = ['Alice', 'Bob', 'Charlie', 'Diana']; | |
| const miner = miners[Math.floor(Math.random() * miners.length)]; | |
| // Simulate mining with progress | |
| const miningInterval = setInterval(() => { | |
| const progress = parseInt(document.getElementById('miningProgress').style.width) || 0; | |
| if (progress < 90) { | |
| document.getElementById('miningProgress').style.width = `${progress + 1}%`; | |
| } | |
| }, 100); | |
| // Mine block (simulated delay for UI) | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| blockchain.minePendingTransactions(miner); | |
| clearInterval(miningInterval); | |
| document.getElementById('miningProgress').style.width = '0%'; | |
| updateBlockchain(); | |
| updatePendingTransactions(); | |
| updateStats(); | |
| updateWalletBalances(); | |
| drawNetwork(); | |
| mineBlockBtn.disabled = false; | |
| mineBlockBtn.innerHTML = `<i class="fas fa-hammer mr-2"></i> Mine Block`; | |
| }); | |
| // Update functions | |
| function updateBlockchain() { | |
| blockchainContainer.innerHTML = ''; | |
| blockchain.chain.forEach((block, index) => { | |
| const isGenesis = index === 0; | |
| const blockDiv = document.createElement('div'); | |
| blockDiv.className = `block-3d bg-gray-700 rounded-lg p-5 shadow-lg border-l-4 ${isGenesis ? 'border-blue-500' : 'border-green-500'} mb-4 relative overflow-hidden`; | |
| if (isGenesis) { | |
| blockDiv.innerHTML += ` | |
| <div class="absolute top-0 right-0 bg-blue-600 text-white text-xs px-2 py-1 rounded-bl-lg"> | |
| Genesis Block | |
| </div> | |
| `; | |
| } else if (index === blockchain.chain.length - 1) { | |
| blockDiv.classList.add('block-highlight'); | |
| } | |
| const transactionsCount = block.transactions.length; | |
| const transactionsSummary = transactionsCount > 0 ? | |
| `${transactionsCount} transaction${transactionsCount !== 1 ? 's' : ''}` : | |
| 'No transactions'; | |
| blockDiv.innerHTML += ` | |
| <div class="flex justify-between items-start mb-3"> | |
| <div> | |
| <h3 class="font-bold text-lg">Block #${block.index}</h3> | |
| <p class="text-xs text-gray-400">${block.hash.substring(0, 20)}...</p> | |
| </div> | |
| <div class="text-right"> | |
| <div class="text-xs text-gray-400">Nonce: ${block.nonce}</div> | |
| <div class="text-xs text-gray-400">${new Date(block.timestamp).toLocaleTimeString()}</div> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 rounded p-3 mb-3"> | |
| <div class="text-xs text-gray-400 mb-1">${transactionsSummary}</div> | |
| ${transactionsCount > 0 ? ` | |
| <div class="text-xs font-mono break-all"> | |
| ${block.transactions[0].from} → ${block.transactions[0].to}: ${block.transactions[0].amount} | |
| ${transactionsCount > 1 ? `<br>+ ${transactionsCount - 1} more...` : ''} | |
| </div> | |
| ` : ''} | |
| </div> | |
| <div class="text-xs text-gray-400">Previous Hash: ${block.previousHash.substring(0, 10)}...</div> | |
| `; | |
| blockchainContainer.appendChild(blockDiv); | |
| }); | |
| blockCount.textContent = blockchain.chain.length; | |
| } | |
| function updatePendingTransactions() { | |
| pendingCount.textContent = blockchain.pendingTransactions.length; | |
| if (blockchain.pendingTransactions.length === 0) { | |
| transactionsContainer.innerHTML = ` | |
| <div class="text-center py-4 text-gray-500"> | |
| No pending transactions | |
| </div> | |
| `; | |
| } | |
| } | |
| function updateStats() { | |
| statsBlocks.textContent = blockchain.chain.length; | |
| statsTransactions.textContent = blockchain.chain.reduce((acc, block) => acc + block.transactions.length, 0); | |
| if (blockchain.blockTimes.length > 0) { | |
| const avgBlockTime = blockchain.blockTimes.reduce((a, b) => a + b, 0) / blockchain.blockTimes.length; | |
| statsBlockTime.textContent = `${avgBlockTime.toFixed(2)}s`; | |
| // Update block time chart | |
| blockTimeChart.data.labels = Array.from({length: blockchain.blockTimes.length}, (_, i) => `Block ${i+1}`); | |
| blockTimeChart.data.datasets[0].data = blockchain.blockTimes; | |
| blockTimeChart.update(); | |
| } | |
| // Simulated hashrate | |
| const hashrate = Math.floor(Math.random() * 1000) + 500; | |
| statsHashrate.textContent = `${hashrate} H/s`; | |
| } | |
| function updateWalletBalances() { | |
| walletBalances.innerHTML = ''; | |
| for (const [name, wallet] of Object.entries(blockchain.wallets)) { | |
| const colors = { | |
| 'Alice': 'blue-500', | |
| 'Bob': 'green-500', | |
| 'Charlie': 'purple-500', | |
| 'Diana': 'yellow-500' | |
| }; | |
| const row = document.createElement('tr'); | |
| row.className = 'border-b border-gray-700'; | |
| row.innerHTML = ` | |
| <td class="py-3 flex items-center"> | |
| <div class="w-2 h-2 bg-${colors[name]} rounded-full mr-2"></div> | |
| ${name} | |
| </td> | |
| <td class="py-3">${wallet.balance.toFixed(2)}</td> | |
| <td class="py-3">${wallet.transactions}</td> | |
| `; | |
| walletBalances.appendChild(row); | |
| } | |
| // Update balance chart | |
| balanceChart.data.datasets[0].data = [ | |
| blockchain.wallets.Alice.balance, | |
| blockchain.wallets.Bob.balance, | |
| blockchain.wallets.Charlie.balance, | |
| blockchain.wallets.Diana.balance | |
| ]; | |
| balanceChart.update(); | |
| } | |
| // Initial updates | |
| updateBlockchain(); | |
| updatePendingTransactions(); | |
| updateStats(); | |
| updateWalletBalances(); | |
| // Add some demo transactions | |
| setTimeout(() => { | |
| blockchain.createTransaction('Alice', 'Bob', 5); | |
| blockchain.createTransaction('Bob', 'Charlie', 3); | |
| blockchain.createTransaction('Diana', 'Alice', 7); | |
| updatePendingTransactions(); | |
| // Add to UI | |
| transactionsContainer.innerHTML = ` | |
| <div class="transaction-card bg-gray-700 rounded-lg p-3 border-l-4 border-blue-500"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <div class="font-semibold">Alice → Bob</div> | |
| <div class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</div> | |
| </div> | |
| <div class="text-lg font-bold">5.00</div> | |
| </div> | |
| </div> | |
| <div class="transaction-card bg-gray-700 rounded-lg p-3 border-l-4 border-green-500"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <div class="font-semibold">Bob → Charlie</div> | |
| <div class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</div> | |
| </div> | |
| <div class="text-lg font-bold">3.00</div> | |
| </div> | |
| </div> | |
| <div class="transaction-card bg-gray-700 rounded-lg p-3 border-l-4 border-purple-500"> | |
| <div class="flex justify-between items-center"> | |
| <div> | |
| <div class="font-semibold">Diana → Alice</div> | |
| <div class="text-xs text-gray-400">${new Date().toLocaleTimeString()}</div> | |
| </div> | |
| <div class="text-lg font-bold">7.00</div> | |
| </div> | |
| </div> | |
| `; | |
| pendingCount.textContent = '3'; | |
| }, 1000); | |
| // Simulate network changes | |
| setInterval(() => { | |
| // Randomly add/remove nodes | |
| if (Math.random() > 0.7) { | |
| blockchain.nodes += Math.random() > 0.5 ? 1 : -1; | |
| blockchain.nodes = Math.max(2, Math.min(8, blockchain.nodes)); | |
| nodeCount.textContent = blockchain.nodes; | |
| drawNetwork(); | |
| } | |
| // Update hashrate | |
| const hashrate = Math.floor(Math.random() * 1000) + 500; | |
| statsHashrate.textContent = `${hashrate} H/s`; | |
| }, 5000); | |
| </script> | |
| <!-- Include CryptoJS for hashing --> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=dazman72/interactive-blockchain-explorer" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |