UNPKG

caravan-x

Version:

A terminal-based utility for managing Caravan multisig wallets in regtest mode. This tool simplifies development and testing with Caravan by providing an easy-to-use interface

1,867 lines (1,617 loc) 81.2 kB
/** * BlockchainWorld - Extends the VoxelEngine with a more immersive blockchain world */ class BlockchainWorld { constructor(container) { console.log("it works"); // Initialize the base voxel engine this.engine = new VoxelEngine(container); // Initialize enhanced systems this.mempoolSystem = new MempoolSystem(this.engine); this.blockchainTower = new BlockchainTower(this.engine); this.minerSystem = new MinerSystem(this.engine); this.npcSystem = new NPCSystem(this.engine); this.simulationSystem = new SimulationSystem(this.engine); // World settings this.worldSettings = { centerX: 0, centerZ: 0, mempoolRadius: 30, towerDistance: 50, minerHouseCount: 5, minerHouseRadius: 70, }; // Player settings this.playerInventory = { bitcoin: 0, computingPower: 10, tools: { pickaxe: { level: 1, durability: 100 }, verifier: { level: 1, durability: 100 }, signKey: { level: 1, durability: 100 }, }, }; // Initialize UI elements this.initializeUI(); // Connect to blockchain data service this.connectToBlockchainService(); // Generate the world this.generateWorld(); } /** * Initialize specialized UI elements for the enhanced world */ initializeUI() { // Create mempool status panel this.mempoolStatusUI = document.createElement("div"); this.mempoolStatusUI.className = "mempool-status-ui"; this.mempoolStatusUI.innerHTML = ` <h3>Mempool Status</h3> <div class="mempool-stats"> <div>Transactions: <span id="mempool-tx-count">0</span></div> <div>Avg Fee Rate: <span id="mempool-avg-fee-rate">0</span> sat/vB</div> <div>Size: <span id="mempool-size">0</span> KB</div> </div> <div class="fee-rate-tiers"> <div class="tier high-priority">High: <span id="high-fee-rate">0</span> sat/vB</div> <div class="tier medium-priority">Medium: <span id="medium-fee-rate">0</span> sat/vB</div> <div class="tier low-priority">Low: <span id="low-fee-rate">0</span> sat/vB</div> </div> `; document.body.appendChild(this.mempoolStatusUI); // Create blockchain tower status this.blockchainStatusUI = document.createElement("div"); this.blockchainStatusUI.className = "blockchain-status-ui"; this.blockchainStatusUI.innerHTML = ` <h3>Blockchain Tower</h3> <div class="blockchain-stats"> <div>Height: <span id="blockchain-height">0</span> blocks</div> <div>Latest Hash: <span id="latest-hash">0000...</span></div> <div>Difficulty: <span id="blockchain-difficulty">0</span></div> </div> `; document.body.appendChild(this.blockchainStatusUI); // Create mining control UI this.miningControlUI = document.createElement("div"); this.miningControlUI.className = "mining-control-ui"; this.miningControlUI.innerHTML = ` <h3>Mining Controls</h3> <div class="mining-controls"> <button id="start-mining-btn">Start Mining</button> <button id="create-transaction-btn">Create Transaction</button> <div class="mining-power"> <label>Mining Power: <span id="mining-power-value">10</span></label> <input type="range" id="mining-power-slider" min="1" max="100" value="10"> </div> </div> `; document.body.appendChild(this.miningControlUI); // Add event listeners for UI controls this.setupUIEventListeners(); } /** * Set up event listeners for UI controls */ setupUIEventListeners() { // Mining button document .getElementById("start-mining-btn") .addEventListener("click", () => { this.simulationSystem.startMining(this.playerInventory.computingPower); }); // Create transaction button document .getElementById("create-transaction-btn") .addEventListener("click", () => { this.openCreateTransactionDialog(); }); // Mining power slider document .getElementById("mining-power-slider") .addEventListener("input", (e) => { const power = parseInt(e.target.value); this.playerInventory.computingPower = power; document.getElementById("mining-power-value").textContent = power; }); } /** * Connect to the blockchain data service */ connectToBlockchainService() { // Setup socket connection this.socket = io(); // Handle blockchain updates this.socket.on("blockchain_update", (data) => { this.updateWorldFromBlockchainData(data); }); // Handle new blocks this.socket.on("new_block", (block) => { this.blockchainTower.addNewBlock(block); this.updateBlockchainStatus(); // Show celebration effects this.showBlockMinedEffects(block); }); // Handle new transactions this.socket.on("new_transaction", (tx) => { this.mempoolSystem.addTransaction(tx); this.updateMempoolStatus(); }); } /** * Generate the enhanced blockchain world */ generateWorld() { // Clear existing world this.engine.clearWorld(); // Setup basic environment (sky, ground, lighting) this.setupEnvironment(); // Create central mempool this.createMempool(); // Create blockchain tower this.createBlockchainTower(); // Create miner houses this.createMinerHouses(); // Add NPCs this.populateWorldWithNPCs(); // Create player spawn point this.createPlayerSpawnPoint(); // Fetch initial blockchain data this.fetchInitialBlockchainData(); } /** * Setup the environment (sky, ground, lighting) */ setupEnvironment() { // Create sky dome with day/night cycle this.engine.createSkyDome(); // Create stylized terrain with hills and valleys this.createTerrain(); // Add dynamic lighting this.engine.setupEnhancedLighting(); // Add ambient sounds this.engine.setupAmbientSounds(); // Add weather effects this.engine.setupWeatherEffects(); } /** * Create stylized terrain for the world */ createTerrain() { // Create a large flat area for the central hub this.engine.createFlatArea( this.worldSettings.centerX, this.worldSettings.centerZ, 100, ); // Create hills around the perimeter for (let i = 0; i < 12; i++) { const angle = (i / 12) * Math.PI * 2; const distance = 120; const x = Math.cos(angle) * distance; const z = Math.sin(angle) * distance; this.engine.createHill( x, z, 20 + Math.random() * 20, 30 + Math.random() * 20, ); } // Create paths connecting landmarks this.createPaths(); } /** * Create paths connecting the main landmarks */ createPaths() { // Path from spawn to mempool this.engine.createPath( this.worldSettings.centerX + 10, this.worldSettings.centerZ + 10, this.worldSettings.centerX, this.worldSettings.centerZ, 3, // width "stone_path", ); // Path from mempool to blockchain tower this.engine.createPath( this.worldSettings.centerX, this.worldSettings.centerZ, this.worldSettings.centerX + this.worldSettings.towerDistance, this.worldSettings.centerZ, 5, // width "gold_path", ); // Paths to each miner house for (let i = 0; i < this.worldSettings.minerHouseCount; i++) { const angle = (i / this.worldSettings.minerHouseCount) * Math.PI * 2; const x = Math.cos(angle) * this.worldSettings.minerHouseRadius; const z = Math.sin(angle) * this.worldSettings.minerHouseRadius; this.engine.createPath( this.worldSettings.centerX, this.worldSettings.centerZ, x, z, 2, // width "stone_path", ); } } /** * Create the central mempool area */ createMempool() { // Create a circular pool structure this.mempoolSystem.createMempoolStructure( this.worldSettings.centerX, 0, // y position at ground level this.worldSettings.centerZ, this.worldSettings.mempoolRadius, ); // Create transaction sorting areas (fee rate tiers) this.mempoolSystem.createFeeTiers(); // Add decorative elements this.mempoolSystem.addDecorativeElements(); // Add the initial transactions (will be populated with real data later) this.mempoolSystem.createPlaceholderTransactions(); } /** * Create the blockchain tower */ createBlockchainTower() { // Position the tower a distance away from the mempool const towerX = this.worldSettings.centerX + this.worldSettings.towerDistance; const towerZ = this.worldSettings.centerZ; // Create the tower foundation this.blockchainTower.createTowerBase(towerX, 0, towerZ); // Create the initial tower structure (will grow with new blocks) this.blockchainTower.initializeTowerStructure(); // Add decorative elements this.blockchainTower.addDecorativeElements(); // Create placeholders for the initial blocks (will be populated with real data) this.blockchainTower.createPlaceholderBlocks(); } /** * Create miner houses around the mempool */ createMinerHouses() { // Create houses in a circle around the mempool for (let i = 0; i < this.worldSettings.minerHouseCount; i++) { const angle = (i / this.worldSettings.minerHouseCount) * Math.PI * 2; const x = this.worldSettings.centerX + Math.cos(angle) * this.worldSettings.minerHouseRadius; const z = this.worldSettings.centerZ + Math.sin(angle) * this.worldSettings.minerHouseRadius; // Each miner has different characteristics const minerType = this.getMinerTypeForIndex(i); const minerName = this.getMinerNameForIndex(i); const minerPower = 10 + i * 5; // Different mining powers // Create the miner house this.minerSystem.createMinerHouse( x, 0, z, minerType, minerName, minerPower, ); } } /** * Get miner type based on index */ getMinerTypeForIndex(index) { const types = ["small", "medium", "large", "pool", "industrial"]; return types[index % types.length]; } /** * Get miner name based on index */ getMinerNameForIndex(index) { const names = [ "BitDigger", "BlockForge", "HashPower", "ChainMaster", "CryptoMine", ]; return names[index % names.length]; } /** * Populate the world with NPCs */ populateWorldWithNPCs() { // Add miners NPCs this.addMinerNPCs(); // Add trader NPCs this.addTraderNPCs(); // Add node operator NPCs this.addNodeOperatorNPCs(); // Add user NPCs this.addUserNPCs(); } /** * Add miner NPCs to the world */ addMinerNPCs() { // Place NPCs at each miner house for (let i = 0; i < this.worldSettings.minerHouseCount; i++) { const house = this.minerSystem.minerHouses[i]; if (!house) continue; // Create miner NPC const npc = this.npcSystem.createMinerNPC( house.position.x + 3, // Offset from house 1, // y position (standing on ground) house.position.z + 2, // Offset from house house.minerName, house.minerPower, ); // Set NPC behavior to walk between house and mempool this.npcSystem.setWalkBetweenPointsBehavior( npc, [ new THREE.Vector3(house.position.x + 3, 1, house.position.z + 2), // Near house new THREE.Vector3( this.worldSettings.centerX + 5, 1, this.worldSettings.centerZ + 5, ), // Near mempool ], 5000, // Delay between walks ); } } /** * Add trader NPCs to the world */ addTraderNPCs() { // Create traders at the mempool edges for (let i = 0; i < 3; i++) { const angle = (i / 3) * Math.PI * 2; const x = this.worldSettings.centerX + Math.cos(angle) * (this.worldSettings.mempoolRadius + 5); const z = this.worldSettings.centerZ + Math.sin(angle) * (this.worldSettings.mempoolRadius + 5); // Create trader NPC const npc = this.npcSystem.createTraderNPC( x, 1, z, `Fee Trader ${i + 1}`, ["low", "medium", "high"][i % 3], // Different fee strategies ); // Set trading behavior this.npcSystem.setTradingBehavior(npc); } } /** * Add node operator NPCs */ addNodeOperatorNPCs() { // Create node operators near the blockchain tower const towerX = this.worldSettings.centerX + this.worldSettings.towerDistance; const towerZ = this.worldSettings.centerZ; for (let i = 0; i < 2; i++) { const x = towerX + (i === 0 ? -10 : 10); const z = towerZ + 5; // Create node operator NPC const npc = this.npcSystem.createNodeOperatorNPC( x, 1, z, `Node Operator ${i + 1}`, i === 0 ? "full_node" : "pruned_node", ); // Set verification behavior this.npcSystem.setVerificationBehavior(npc); } } /** * Add user NPCs that create transactions */ addUserNPCs() { // Create users at different locations const locations = [ { x: this.worldSettings.centerX + 20, z: this.worldSettings.centerZ - 20, }, { x: this.worldSettings.centerX - 15, z: this.worldSettings.centerZ + 25, }, { x: this.worldSettings.centerX - 25, z: this.worldSettings.centerZ - 15, }, ]; for (let i = 0; i < locations.length; i++) { // Create user NPC const npc = this.npcSystem.createUserNPC( locations[i].x, 1, locations[i].z, `User ${i + 1}`, Math.random() * 0.5 + 0.2, // Random transaction frequency ); // Set transaction creation behavior this.npcSystem.setTransactionCreationBehavior(npc); } } /** * Create the player spawn point */ createPlayerSpawnPoint() { // Create a small platform for the player to spawn on const spawnX = this.worldSettings.centerX + 20; const spawnZ = this.worldSettings.centerZ + 20; // Create a recognizable spawn platform this.engine.createSpawnPlatform(spawnX, 0, spawnZ); // Set player position this.engine.camera.position.set(spawnX, 5, spawnZ); this.engine.controls.getObject().position.set(spawnX, 5, spawnZ); } /** * Fetch initial blockchain data */ async fetchInitialBlockchainData() { try { const response = await fetch("/api/blockchain"); const data = await response.json(); // Update the world with the blockchain data this.updateWorldFromBlockchainData(data); } catch (error) { console.error("Error fetching blockchain data:", error); } } /** * Update the world based on blockchain data */ updateWorldFromBlockchainData(data) { // Update mempool if (data.mempool) { this.mempoolSystem.updateFromBlockchainData(data.mempool); this.updateMempoolStatus(); } // Update blockchain tower if (data.blocks) { this.blockchainTower.updateFromBlockchainData(data.blocks); this.updateBlockchainStatus(); } // Update mining difficulty for miners if (data.chainInfo && data.chainInfo.difficulty) { this.minerSystem.updateDifficulty(data.chainInfo.difficulty); } } /** * Update the mempool status UI */ updateMempoolStatus() { const mempoolStats = this.mempoolSystem.getStats(); document.getElementById("mempool-tx-count").textContent = mempoolStats.txCount; document.getElementById("mempool-avg-fee-rate").textContent = mempoolStats.avgFeeRate.toFixed(2); document.getElementById("mempool-size").textContent = mempoolStats.size.toFixed(2); document.getElementById("high-fee-rate").textContent = mempoolStats.highFeeRate.toFixed(2); document.getElementById("medium-fee-rate").textContent = mempoolStats.mediumFeeRate.toFixed(2); document.getElementById("low-fee-rate").textContent = mempoolStats.lowFeeRate.toFixed(2); } /** * Update the blockchain status UI */ updateBlockchainStatus() { const blockchainStats = this.blockchainTower.getStats(); document.getElementById("blockchain-height").textContent = blockchainStats.height; document.getElementById("latest-hash").textContent = blockchainStats.latestHash.substring(0, 8) + "..."; document.getElementById("blockchain-difficulty").textContent = blockchainStats.difficulty.toFixed(2); } /** * Show visual effects when a block is mined */ showBlockMinedEffects(block) { // Create fireworks effect at the tower const towerPosition = this.blockchainTower.getTowerPosition(); this.engine.createFireworksEffect( towerPosition.x, towerPosition.y + block.height * 3, towerPosition.z, ); // Create celebration text this.engine.createFloatingText( towerPosition.x, towerPosition.y + block.height * 3 + 10, towerPosition.z, `Block #${block.height} Mined!`, 0xffaa00, ); // Play celebration sound this.engine.playSound("block_mined"); } /** * Open dialog to create a new transaction */ openCreateTransactionDialog() { // Create and show the transaction dialog const dialog = document.createElement("div"); dialog.className = "transaction-dialog"; dialog.innerHTML = ` <div class="dialog-header"> <h3>Create Transaction</h3> <button class="dialog-close-btn">×</button> </div> <div class="dialog-content"> <div class="input-group"> <label for="tx-recipient">Recipient Address:</label> <input type="text" id="tx-recipient" value="mxDuAYQUaT3ytdR5E7QKnKLPXXhhqPKTdZ"> </div> <div class="input-group"> <label for="tx-amount">Amount (BTC):</label> <input type="number" id="tx-amount" value="0.001" min="0.00001" step="0.00001"> </div> <div class="input-group"> <label for="tx-fee-rate">Fee Rate (sat/vB):</label> <input type="number" id="tx-fee-rate" value="5" min="1" step="1"> </div> <div class="input-group"> <label for="tx-type">Transaction Type:</label> <select id="tx-type"> <option value="regular">Regular</option> <option value="rbf">Replace-By-Fee (RBF)</option> <option value="cpfp">Child-Pays-For-Parent (CPFP)</option> </select> </div> </div> <div class="dialog-footer"> <button id="send-tx-btn" class="primary-btn">Send Transaction</button> <button id="cancel-tx-btn" class="secondary-btn">Cancel</button> </div> `; document.body.appendChild(dialog); // Add event listeners dialog.querySelector(".dialog-close-btn").addEventListener("click", () => { document.body.removeChild(dialog); }); dialog.querySelector("#cancel-tx-btn").addEventListener("click", () => { document.body.removeChild(dialog); }); dialog.querySelector("#send-tx-btn").addEventListener("click", () => { // Get transaction details const recipient = document.getElementById("tx-recipient").value; const amount = parseFloat(document.getElementById("tx-amount").value); const feeRate = parseInt(document.getElementById("tx-fee-rate").value); const txType = document.getElementById("tx-type").value; // Create transaction this.createTransaction(recipient, amount, feeRate, txType); // Close dialog document.body.removeChild(dialog); }); } /** * Create a new transaction */ createTransaction(recipient, amount, feeRate, txType) { // Create transaction with the specified parameters this.socket.emit("create_transaction", { fromWallet: "player_wallet", toAddress: recipient, amount: amount, feeRate: feeRate, type: txType, }); // Deduct BTC from player's inventory this.playerInventory.bitcoin -= amount; // Create visual effect of transaction moving to mempool const playerPosition = this.engine.controls.getObject().position; // Create transaction entity const txEntity = this.engine.createTransactionEntity( playerPosition.x, playerPosition.y - 1, playerPosition.z, feeRate, txType, ); // Animate transaction to mempool this.engine.animateEntityToPosition( txEntity, this.worldSettings.centerX, 1, this.worldSettings.centerZ, 2000, // Duration in milliseconds () => { // Add to mempool once animation completes this.mempoolSystem.addLocalTransaction({ txid: this.generateRandomTxid(), amount: amount, feeRate: feeRate, type: txType, }); }, ); } /** * Generate a random transaction ID for local transactions */ generateRandomTxid() { return [...Array(64)] .map(() => Math.floor(Math.random() * 16).toString(16)) .join(""); } /** * Start the simulation */ start() { // Start the game loop this.engine.startGameLoop(); // Start NPC behaviors this.npcSystem.startBehaviors(); // Start simulation this.simulationSystem.startSimulation(); } } /** * MempoolSystem - Manages the mempool visualization */ class MempoolSystem { constructor(engine) { this.engine = engine; this.transactions = []; this.mempoolEntities = []; this.feeTiers = { high: { min: 20, color: 0xff5555, height: 2 }, medium: { min: 5, color: 0xffaa00, height: 1 }, low: { min: 0, color: 0x55ff55, height: 0 }, }; // Mempool stats this.stats = { txCount: 0, avgFeeRate: 0, size: 0, highFeeRate: 30, mediumFeeRate: 10, lowFeeRate: 3, }; } /** * Create the mempool structure */ createMempoolStructure(x, y, z, radius) { // Create water pool const pool = this.engine.createPool(x, y - 3, z, radius, 0x0088ff); // Create outer rim const rim = this.engine.createRing( x, y, z, radius, radius + 2, "stone_brick", ); // Create fee tier sections in the pool this.createFeeTiers(x, y, z, radius); // Create a bridge to cross the mempool this.createBridge(x, y, z, radius); // Create a sorting mechanism visualized as hoppers above the pool this.createTransactionSorter(x, y, z); // Add fee rate indicator signs this.createFeeRateIndicators(x, y, z, radius); // Create vortex effect in the center this.engine.createVortexEffect(x, y - 2, z, 5, 0x0088ff); } /** * Create fee tier sections in the pool */ createFeeTiers(x, y, z, radius) { // High fee section (inner circle) this.engine.createDiskSection( x, y - 2.5, z, radius * 0.3, this.feeTiers.high.color, "high_fee_section", ); // Medium fee section (middle ring) this.engine.createRingSection( x, y - 2.5, z, radius * 0.3, radius * 0.6, this.feeTiers.medium.color, "medium_fee_section", ); // Low fee section (outer ring) this.engine.createRingSection( x, y - 2.5, z, radius * 0.6, radius * 0.9, this.feeTiers.low.color, "low_fee_section", ); } /** * Create a bridge across the mempool */ createBridge(x, y, z, radius) { // Create bridge from one side to the other this.engine.createBridge( x - radius, y, z, x + radius, y, z, 3, // width "oak_planks", ); // Create bridge support pillars this.engine.createPillar(x - radius * 0.5, y - 5, z, y, "oak_log"); this.engine.createPillar(x, y - 5, z, y, "oak_log"); this.engine.createPillar(x + radius * 0.5, y - 5, z, y, "oak_log"); // Create bridge railings this.engine.createRailing( x - radius, y, z - 1.5, x + radius, y, z - 1.5, 1, // height "oak_fence", ); this.engine.createRailing( x - radius, y, z + 1.5, x + radius, y, z + 1.5, 1, // height "oak_fence", ); } /** * Create transaction sorter mechanism */ createTransactionSorter(x, y, z) { // Create a hopper-like structure above the pool const hopperY = y + 10; // Main funnel this.engine.createFunnel(x, hopperY, z, 5, 10, "iron_block"); // Input conveyor belt this.engine.createConveyorBelt( x - 15, hopperY, z, x - 5, hopperY, z, 2, // width "conveyor_belt", ); // Sorting arms for different fee tiers this.createSortingArm(x, hopperY - 2, z, 0, this.feeTiers.high.color); this.createSortingArm(x, hopperY - 4, z, 120, this.feeTiers.medium.color); this.createSortingArm(x, hopperY - 6, z, 240, this.feeTiers.low.color); // Add animated elements this.engine.createAnimatedMechanism(x, hopperY, z); } /** * Create a sorting arm for a fee tier */ createSortingArm(x, y, z, rotation, color) { this.engine.createSortingArm(x, y, z, rotation, color); } /** * Create fee rate indicator signs */ createFeeRateIndicators(x, y, z, radius) { // High fee sign this.engine.createSign( x + radius * 0.15, y + 1, z + radius * 0.15, "High Fee Rate", "20+ sat/vB", this.feeTiers.high.color, ); // Medium fee sign this.engine.createSign( x - radius * 0.45, y + 1, z + radius * 0.45, "Medium Fee Rate", "5-20 sat/vB", this.feeTiers.medium.color, ); // Low fee sign this.engine.createSign( x - radius * 0.75, y + 1, z - radius * 0.45, "Low Fee Rate", "1-5 sat/vB", this.feeTiers.low.color, ); } /** * Add decorative elements to the mempool */ addDecorativeElements() { const x = this.engine.worldSettings.centerX; const y = 0; const z = this.engine.worldSettings.centerZ; const radius = this.engine.worldSettings.mempoolRadius; // Add lanterns around the edge for (let i = 0; i < 12; i++) { const angle = (i / 12) * Math.PI * 2; const lx = x + Math.cos(angle) * (radius + 1); const lz = z + Math.sin(angle) * (radius + 1); this.engine.createLantern(lx, y + 3, lz); } // Add floating bubbles in the pool for (let i = 0; i < 20; i++) { const angle = Math.random() * Math.PI * 2; const distance = Math.random() * radius * 0.9; const bx = x + Math.cos(angle) * distance; const bz = z + Math.sin(angle) * distance; this.engine.createBubble(bx, y - 2 + Math.random(), bz); } // Add mempool explanation sign this.engine.createInfoSign(x - radius - 5, y + 1, z, "The Memory Pool", [ "Unconfirmed transactions wait here", "Sorted by fee rate (sat/vB)", "Higher fee = faster confirmation", "Watch miners select from the pool", ]); } /** * Create placeholder transactions in the mempool */ createPlaceholderTransactions() { const x = this.engine.worldSettings.centerX; const y = 0; const z = this.engine.worldSettings.centerZ; // Create 20 random placeholder transactions for (let i = 0; i < 20; i++) { // Determine fee tier const tierName = i < 5 ? "high" : i < 12 ? "medium" : "low"; const tier = this.feeTiers[tierName]; // Determine position based on tier const angle = Math.random() * Math.PI * 2; const radius = this.engine.worldSettings.mempoolRadius; let distance; if (tierName === "high") { distance = radius * Math.random() * 0.3; } else if (tierName === "medium") { distance = radius * (0.3 + Math.random() * 0.3); } else { distance = radius * (0.6 + Math.random() * 0.3); } const tx = { id: `placeholder_tx_${i}`, feeRate: tier.min + Math.random() * 10, type: Math.random() < 0.2 ? Math.random() < 0.5 ? "RBF" : "CPFP" : "regular", }; // Create transaction entity const txEntity = this.createTransactionEntity( x + Math.cos(angle) * distance, y - 2 + tier.height, z + Math.sin(angle) * distance, tx, ); // Store reference this.mempoolEntities.push(txEntity); } } /** * Create a transaction entity in the mempool */ createTransactionEntity(x, y, z, tx) { // Determine color based on fee rate let color; if (tx.feeRate >= this.feeTiers.high.min) { color = this.feeTiers.high.color; } else if (tx.feeRate >= this.feeTiers.medium.min) { color = this.feeTiers.medium.color; } else { color = this.feeTiers.low.color; } // Create different shapes for special transaction types let shape; if (tx.type === "RBF") { shape = "diamond"; } else if (tx.type === "CPFP") { shape = "star"; } else { shape = "cube"; } // Create the entity const entity = this.engine.createCustomEntity(x, y, z, shape, color); // Add floating animation this.engine.addFloatingAnimation(entity, 0.1, 2000); // Add transaction data to entity entity.userData = { ...tx }; // Add interaction entity.onClick = () => { this.showTransactionDetails(tx); }; return entity; } /** * Show transaction details when clicked */ showTransactionDetails(tx) { // Create a dialog showing transaction details const dialog = document.createElement("div"); dialog.className = "transaction-details-dialog"; dialog.innerHTML = ` <div class="dialog-header"> <h3>Transaction Details</h3> <button class="dialog-close-btn">×</button> </div> <div class="dialog-content"> <div class="tx-detail"> <span class="label">ID:</span> <span class="value">${tx.id}</span> </div> <div class="tx-detail"> <span class="label">Fee Rate:</span> <span class="value">${tx.feeRate.toFixed(2)} sat/vB</span> </div> <div class="tx-detail"> <span class="label">Type:</span> <span class="value">${tx.type}</span> </div> ${ tx.type === "RBF" ? ` <div class="tx-detail"> <span class="label">Replaces:</span> <span class="value">${tx.replaces || "Unknown"}</span> </div> ` : "" } ${ tx.type === "CPFP" ? ` <div class="tx-detail"> <span class="label">Parent:</span> <span class="value">${tx.parent || "Unknown"}</span> </div> ` : "" } </div> `; document.body.appendChild(dialog); // Add close button event dialog.querySelector(".dialog-close-btn").addEventListener("click", () => { document.body.removeChild(dialog); }); } /** * Update mempool from blockchain data */ updateFromBlockchainData(mempoolData) { // Store transaction data this.transactions = mempoolData.txids.map((txid) => { return { id: txid, feeRate: Math.random() * 30, // Would come from actual tx data type: Math.random() < 0.2 ? Math.random() < 0.5 ? "RBF" : "CPFP" : "regular", }; }); // Update mempool statistics this.updateStats(mempoolData); // Update visual representation this.updateVisualization(); } /** * Update mempool statistics */ updateStats(mempoolData) { // Calculate statistics based on mempool data this.stats.txCount = mempoolData.txCount || mempoolData.txids.length; this.stats.size = mempoolData.size / 1024; // KB // Calculate fee rates from transactions if (this.transactions.length > 0) { // Calculate average fee rate const totalFeeRate = this.transactions.reduce( (sum, tx) => sum + tx.feeRate, 0, ); this.stats.avgFeeRate = totalFeeRate / this.transactions.length; // Calculate tier fee rates const sortedRates = [...this.transactions] .sort((a, b) => b.feeRate - a.feeRate) .map((tx) => tx.feeRate); const highIndex = Math.floor(sortedRates.length * 0.2); const mediumIndex = Math.floor(sortedRates.length * 0.5); this.stats.highFeeRate = sortedRates[highIndex] || 20; this.stats.mediumFeeRate = sortedRates[mediumIndex] || 10; this.stats.lowFeeRate = sortedRates[sortedRates.length - 1] || 1; } } /** * Update the visual representation of the mempool */ updateVisualization() { // Clear existing entities this.mempoolEntities.forEach((entity) => { this.engine.removeEntity(entity); }); this.mempoolEntities = []; // Create new entities for transactions const x = this.engine.worldSettings.centerX; const y = 0; const z = this.engine.worldSettings.centerZ; const radius = this.engine.worldSettings.mempoolRadius; // Only visualize up to 50 transactions const visualTxCount = Math.min(this.transactions.length, 50); for (let i = 0; i < visualTxCount; i++) { const tx = this.transactions[i]; // Determine tier let tier; if (tx.feeRate >= this.stats.highFeeRate) { tier = "high"; } else if (tx.feeRate >= this.stats.mediumFeeRate) { tier = "medium"; } else { tier = "low"; } // Determine position based on tier const angle = (i / visualTxCount) * Math.PI * 2; let distance; if (tier === "high") { distance = radius * Math.random() * 0.3; } else if (tier === "medium") { distance = radius * (0.3 + Math.random() * 0.3); } else { distance = radius * (0.6 + Math.random() * 0.3); } // Create transaction entity const txEntity = this.createTransactionEntity( x + Math.cos(angle) * distance, y - 2 + this.feeTiers[tier].height, z + Math.sin(angle) * distance, tx, ); // Store reference this.mempoolEntities.push(txEntity); } } /** * Add a new transaction to the mempool */ addTransaction(tx) { // Create transaction data const transactionData = { id: tx.txid, feeRate: tx.feeRate || Math.random() * 30, type: tx.type || "regular", }; // Add to transactions list this.transactions.push(transactionData); // Update stats this.stats.txCount++; // Determine tier based on fee rate let tier; if (transactionData.feeRate >= this.feeTiers.high.min) { tier = "high"; } else if (transactionData.feeRate >= this.feeTiers.medium.min) { tier = "medium"; } else { tier = "low"; } // Determine position for the new transaction const x = this.engine.worldSettings.centerX; const y = 0; const z = this.engine.worldSettings.centerZ; const radius = this.engine.worldSettings.mempoolRadius; // Random position within tier const angle = Math.random() * Math.PI * 2; let distance; if (tier === "high") { distance = radius * Math.random() * 0.3; } else if (tier === "medium") { distance = radius * (0.3 + Math.random() * 0.3); } else { distance = radius * (0.6 + Math.random() * 0.3); } // Create transaction entity with animation const txEntity = this.createTransactionEntity( x + Math.cos(angle) * distance, y + 10, // Start high above the pool z + Math.sin(angle) * distance, transactionData, ); // Animate falling into the pool this.engine.animateEntityToPosition( txEntity, x + Math.cos(angle) * distance, y - 2 + this.feeTiers[tier].height, // Final height in the pool z + Math.sin(angle) * distance, 1500, // Duration () => { // Create splash effect when transaction lands this.engine.createSplashEffect( txEntity.position.x, y - 2, txEntity.position.z, 0x0088ff, ); }, ); // Store reference this.mempoolEntities.push(txEntity); // Play transaction sound this.engine.playSound("transaction_added"); } /** * Add a locally created transaction */ addLocalTransaction(tx) { this.addTransaction(tx); } /** * Remove a transaction from the mempool (e.g., when mined into a block) */ removeTransaction(txId) { // Find the transaction const txIndex = this.transactions.findIndex((tx) => tx.id === txId); if (txIndex === -1) return; // Remove from transactions list this.transactions.splice(txIndex, 1); // Find corresponding entity const entityIndex = this.mempoolEntities.findIndex( (entity) => entity.userData.id === txId, ); if (entityIndex === -1) return; const entity = this.mempoolEntities[entityIndex]; // Animate entity rising from the pool this.engine.animateEntityToPosition( entity, entity.position.x, entity.position.y + 15, entity.position.z, 1000, // Duration () => { // Remove entity when animation completes this.engine.removeEntity(entity); this.mempoolEntities.splice(entityIndex, 1); }, ); // Update stats this.stats.txCount--; } /** * Get current mempool statistics */ getStats() { return { ...this.stats }; } } /** * BlockchainTower - Manages the blockchain tower visualization */ class BlockchainTower { constructor(engine) { this.engine = engine; this.blocks = []; this.blockEntities = []; this.basePosition = { x: 0, y: 0, z: 0 }; this.towerHeight = 0; // Block stats this.stats = { height: 0, latestHash: "0000000000000000000000000000000000000000000000000000000000000000", difficulty: 0, }; } /** * Create the tower base */ createTowerBase(x, y, z) { // Store the base position this.basePosition = { x, y, z }; // Create a large platform this.engine.createPlatform(x, y, z, 20, 20, "stone_brick"); // Create foundation pillars for (let i = 0; i < 4; i++) { const angle = (i / 4) * Math.PI * 2; const px = x + Math.cos(angle) * 8; const pz = z + Math.sin(angle) * 8; this.engine.createPillar(px, y, pz, y + 10, "quartz_pillar"); } // Create decorative elements this.createDecorativeBase(x, y, z); // Create blockchain info sign this.engine.createInfoSign(x - 15, y + 1, z, "The Blockchain Tower", [ "Each block contains transactions", "Blocks are stacked chronologically", "The chain grows upward over time", "Miners compete to add new blocks", ]); } /** * Create decorative elements for the base */ createDecorativeBase(x, y, z) { // Create steps leading up to the platform this.engine.createSteps( x - 10, y, z, x - 5, y, z, 5, 5, "stone_brick_stairs", ); // Create torches around the base for (let i = 0; i < 8; i++) { const angle = (i / 8) * Math.PI * 2; const tx = x + Math.cos(angle) * 10; const tz = z + Math.sin(angle) * 10; this.engine.createTorch(tx, y + 1, tz); } // Create a beacon in the center this.engine.createBeacon(x, y + 1, z); } /** * Initialize the tower structure */ initializeTowerStructure() { const { x, y, z } = this.basePosition; // Create the central tower structure this.engine.createColumn(x, y, z, 0, 2, "glass"); // Create corner supports for (let i = 0; i < 4; i++) { const angle = (i / 4) * Math.PI * 2 + Math.PI / 4; const sx = x + Math.cos(angle) * 5; const sz = z + Math.sin(angle) * 5; this.engine.createColumn(sx, y, sz, 0, 1, "quartz_pillar"); } } /** * Add decorative elements to the tower */ addDecorativeElements() { const { x, y, z } = this.basePosition; // Create a golden Bitcoin symbol at the base this.engine.createBitcoinSymbol(x, y + 5, z, 3, 0xffaa00); // Create floating particles rising up the tower this.engine.createRisingParticles(x, y, z, 0xffaa00, 50); } /** * Create placeholder blocks */ createPlaceholderBlocks() { // Create 10 placeholder blocks for (let i = 0; i < 10; i++) { const block = { height: i, hash: `placeholder_block_${i}`, time: Date.now() / 1000 - (10 - i) * 600, txCount: Math.floor(Math.random() * 10) + 1, size: Math.random() * 900000 + 100000, difficulty: 1 + i * 0.1, }; this.addBlockToTower(block); } } /** * Add a block to the tower */ addBlockToTower(block) { // Add to blocks list this.blocks.push(block); // Update stats this.stats.height = Math.max(this.stats.height, block.height); this.stats.latestHash = block.hash; this.stats.difficulty = block.difficulty; // Determine block position const { x, y, z } = this.basePosition; const blockY = y + 10 + block.height * 3; // Determine block properties based on its data const size = 0.5 + block.txCount / 20; // Scale with transaction count const color = this.getBlockColor(block); // Create block entity const blockEntity = this.engine.createBlockEntity( x, blockY, z, size, color, block, ); // Add block height number above the block this.engine.createFloatingText( x, blockY + size + 0.5, z, `#${block.height}`, 0xffffff, ); // Connect to previous block with a beam if (this.blockEntities.length > 0) { const prevBlock = this.blockEntities[this.blockEntities.length - 1]; this.engine.createBeam( prevBlock.position.x, prevBlock.position.y, prevBlock.position.z, x, blockY, z, 0xffaa00, ); } // Update tower height this.towerHeight = Math.max(this.towerHeight, blockY + size); // Store entity reference this.blockEntities.push(blockEntity); // Add click handler blockEntity.onClick = () => { this.showBlockDetails(block); }; return blockEntity; } /** * Get color for a block based on its properties */ getBlockColor(block) { // Newer blocks are brighter const age = Date.now() / 1000 - block.time; const ageColor = Math.max(0, 1 - age / 86400); // Fade over a day // More transactions = more golden const txColor = Math.min(1, block.txCount / 20); // Create a color gradient from blue to gold based on age and tx count const r = 0.3 + 0.7 * txColor; const g = 0.2 + 0.6 * txColor; const b = 0.8 - 0.6 * txColor; // Apply brightness based on age const brightness = 0.5 + 0.5 * ageColor; // Convert to hex color return ( Math.floor(r * brightness * 255) * 65536 + Math.floor(g * brightness * 255) * 256 + Math.floor(b * brightness * 255) ); } /** * Show block details */ showBlockDetails(block) { // Create a dialog showing block details const dialog = document.createElement("div"); dialog.className = "block-details-dialog"; dialog.innerHTML = ` <div class="dialog-header"> <h3>Block #${block.height}</h3> <button class="dialog-close-btn">×</button> </div> <div class="dialog-content"> <div class="block-detail"> <span class="label">Hash:</span> <span class="value">${block.hash.substring(0, 20)}...</span> </div> <div class="block-detail"> <span class="label">Time:</span> <span class="value">${new Date(block.time * 1000).toLocaleString()}</span> </div> <div class="block-detail"> <span class="label">Transactions:</span> <span class="value">${block.txCount}</span> </div> <div class="block-detail"> <span class="label">Size:</span> <span class="value">${(block.size / 1024).toFixed(2)} KB</span> </div> <div class="block-detail"> <span class="label">Difficulty:</span> <span class="value">${block.difficulty.toFixed(8)}</span> </div> </div> <div class="dialog-footer"> <button id="explore-block-btn" class="primary-btn">Explore Transactions</button> </div> `; document.body.appendChild(dialog); // Add close button event dialog.querySelector(".dialog-close-btn").addEventListener("click", () => { document.body.removeChild(dialog); }); // Add explore button event dialog.querySelector("#explore-block-btn").addEventListener("click", () => { this.showBlockTransactions(block); document.body.removeChild(dialog); }); } /** * Show block transactions */ showBlockTransactions(block) { // Create a dialog showing block transactions const dialog = document.createElement("div"); dialog.className = "block-transactions-dialog"; dialog.innerHTML = ` <div class="dialog-header"> <h3>Block #${block.height} Transactions</h3> <button class="dialog-close-btn">×</button> </div> <div class="dialog-content"> <div class="transactions-list"> ${Array(block.txCount) .fill(0) .map( (_, i) => ` <div class="transaction-item"> <div class="tx-icon ${i === 0 ? "coinbase" : ""}"></div> <div class="tx-info"> <div class="tx-id">TX #${i}: ${this.generateRandomTxid().substring(0, 12)}...</div> <div class="tx-details"> ${i === 0 ? "Coinbase (Block Reward)" : `${Math.floor(Math.random() * 3) + 1} inputs, ${Math.floor(Math.random() * 5) + 1} outputs`} </div> </div> <div class="tx-amount">${i === 0 ? "6.25 BTC" : `${(Math.random() * 2).toFixed(8)} BTC`}</div> </div> `, ) .join("")} </div> </div> `; document.body.appendChild(dialog); // Add close button event dialog.querySelector(".dialog-close-btn").addEventListener("click", () => { document.body.removeChild(dialog); }); } /** * Generate a random transaction ID */ generateRandomTxid() { return [...Array(64)] .map(() => Math.floor(Math.random() * 16).toString(16)) .join(""); } /** * Add a new block to the tower */ addNewBlock(block) { // Play mining sound this.engine.playSound("block_mined"); // Create celebration effects this.createBlockMinedEffect(); // Add block to tower const blockEntity = this.addBlockToTower(block); // Animate the block dropping into place const finalPosition = blockEntity.position.clone(); blockEntity.position.y += 30; // Start higher this.engine.animateEntityToPosition( blockEntity, finalPosition.x, finalPosition.y, finalPosition.z, 2000, // Duration () => { // Create impact effect when block lands this.engine.createImpactEffect( finalPosition.x, finalPosition.y, finalPosition.z, 0xffaa00, ); }, ); } /** * Create special effects when a block is mined */ createBlockMinedEffect() { const { x, y, z } = this.basePosition; const blockY = y + 10 + this.blocks.length * 3; // Create fireworks effect this.engine.createFireworksEffect(x, blockY, z); // Create celebration text this.engine.createFloatingText( x, blockY + 10, z, "New Block Mined!", 0xffaa00, ); // Create shockwave effect this.engine.createShockwaveEffect(x, blockY, z, 0xffaa00, 20); } /** * Update from blockchain data */ updateFromBlockchainData(blocks) { // Find blocks that are not yet in the tower const existingHashes = this.blocks.map((block) => block.hash); const newBlocks = blocks.filter( (block) => !existingHashes.includes(block.hash), ); // Add new blocks to the tower for (const block of newBlocks) { this.addBlockToTower(block); } // Update tower appearance if needed this.updateTowerAppearance(); } /** * Update the tower's appearance based on current state */ updateTowerAppearance() { // Update the height of the central column this.engine.updateColumn( this.basePosition.x, this.basePosition.y, this.basePosition.z, this.towerHeight, ); // Update the rising particles this.engine.updateRisingParticles( this.basePosition.x, this.basePosition.y, this.basePosition.z, this.towerHeight, ); } /** * Get the tower position */ getTowerPosition() { return { ...this.basePosition }; } /** * Get blockchain statistics */ getStats() { return { ...this.stats }; } } /** * MinerSystem - Manages miner houses and mining simulation */ class MinerSystem { constructor(engine) { this.engine = engine; this.minerHouses = []; this.difficulty = 1; this.activeMinerCount = 0; } /** * Create a miner house */ createMinerHouse(x, y, z, minerType, minerName,