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
JavaScript
/**
* 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,