/ 2.html
2.html
1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Ethereum Transaction Lifecycle Visualizer</title> 7 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.min.js"></script> 8 <style> 9 body, html { 10 margin: 0; 11 padding: 0; 12 width: 100%; 13 height: 100%; 14 overflow: hidden; 15 font-family: Arial, sans-serif; 16 } 17 #visualization { 18 width: 100%; 19 height: 100%; 20 } 21 #controls { 22 position: fixed; 23 bottom: 20px; 24 right: 20px; 25 width: 150px; 26 height: 150px; 27 } 28 .control-button { 29 position: absolute; 30 width: 60px; 31 height: 30px; 32 background-color: rgba(255, 255, 255, 0.7); 33 border: 1px solid #999; 34 border-radius: 5px; 35 } 36 #upBtn { top: 0; left: 45px; } 37 #leftBtn { top: 60px; left: 0; } 38 #rightBtn { top: 60px; right: 0; } 39 #downBtn { bottom: 0; left: 45px; } 40 #metadata, #addNodeForm { 41 position: fixed; 42 background-color: rgba(255, 255, 255, 0.9); 43 padding: 10px; 44 border-radius: 5px; 45 max-width: 300px; 46 max-height: 80%; 47 overflow-y: auto; 48 } 49 #metadata { 50 bottom: 20px; 51 left: 20px; 52 } 53 #addNodeForm { 54 top: 20px; 55 left: 20px; 56 display: none; /* Initially hidden */ 57 } 58 #metadata input, #metadata textarea, #addNodeForm input, #addNodeForm select { 59 width: 100%; 60 margin-bottom: 10px; 61 } 62 #metadata button, #addNodeForm button { 63 margin-right: 10px; 64 } 65 #editToggle { 66 position: fixed; 67 top: 20px; 68 right: 20px; 69 } 70 </style> 71 </head> 72 <body onload="onLoadPopulateNodesFromURl()"> 73 <div id="Title", style="padding-left: 10px; padding-right: 10px;"> 74 <h1>Preconfirmations</h1> 75 </div> 76 <div id="Subtitle", style="padding-left: 10px; padding-right: 10px;"> 77 <h2>Fast, credible promises of inclusion</h2> 78 </div> 79 <div id="Content", style="padding-left: 10px; padding-right: 10px;"> 80 <p>Preconfirmations insert a new stage to this progression of confidence, between submission and inclusion.</p> 81 82 <p>It does this by enabling parties involved in the transaction lifecyle to provide a stake-backed promise that the transaction will be included within a stupulated time, or even included with the surety of a specific 83 post-inclusion state (the ENS is succesfully registered, or the Uniswap transaction is placed at the top of the block, for example). We will get to who these parties are, both on the supply/submission 84 and demand/preconfirmation side in due course. The parties providing the preconfirmations will henceforth be called <strong>preconfers</strong>.</p> 85 86 <p>So from a user's perspective, a transaction now goes through the following stages: Submission > <strong>Preconfirmation</strong> > Inclusion > Finalization </p> 87 88 <p>These preconfirmations can be provided at a much smaller interval than the 12 seconds per slot, depending on the specific design of the preconfirmation supply chain.</p> 89 <span style="float: right;"><a href="1.html"> <<prev<< </a> <a href="3.html"> >>Next>> </a></span> 90 </div> 91 <div id="visualization"></div> 92 <div id="controls"> 93 <button id="upBtn" class="control-button">Up</button> 94 <button id="leftBtn" class="control-button">Previous</button> 95 <button id="rightBtn" class="control-button">Next</button> 96 <button id="downBtn" class="control-button">Down</button> 97 </div> 98 <div id="metadata"></div> 99 <div id="addNodeForm"> 100 <h3>Add New Node</h3> 101 <input type="text" id="newNodeName" placeholder="Node Name"> 102 <select id="newNodeDirection"> 103 <option value="next">Next</option> 104 <option value="up">Up</option> 105 <option value="down">Down</option> 106 </select> 107 <button onclick="addNewNode()">Add Node</button> 108 </div> 109 <button id="editToggle" onclick="toggleEditMode()">Edit Mode</button> 110 111 <script> 112 let nodeIds = ["User"]; 113 let connectionMatrix = { 114 "User": {} 115 }; 116 let nodeMetadata = { 117 "User": { 118 "content": "Initiates and signs the transaction", 119 "url": "https://ethereum.org/en/developers/docs/transactions/" 120 } 121 }; 122 123 const directionToGridDelta = { 124 "next": [1, 0], 125 "previous": [-1, 0], 126 "up": [0, -1], 127 "down": [0, 1] 128 }; 129 130 let isEditMode = false; 131 132 function assignGridPositions(startNode) { 133 const gridPositions = {}; 134 const queue = [[startNode, 0, 0]]; 135 const visited = new Set(); 136 137 while (queue.length > 0) { 138 const [node, x, y] = queue.shift(); 139 if (visited.has(node)) continue; 140 visited.add(node); 141 gridPositions[node] = [x, y]; 142 143 for (let neighbor in connectionMatrix[node]) { 144 const connection = connectionMatrix[node][neighbor]; 145 if (connection) { 146 const [dx, dy] = directionToGridDelta[connection[0]]; 147 queue.push([neighbor, x + dx, y + dy]); 148 } 149 } 150 } 151 152 return gridPositions; 153 } 154 155 function createVisualization() { 156 const width = window.innerWidth; 157 const height = window.innerHeight; 158 const nodeRadius = Math.min(width, height) * 0.05; 159 const transactionRadius = nodeRadius * 0.3; 160 const gridSize = Math.min(width, height) * 0.2; 161 162 d3.select("#visualization").selectAll("*").remove(); 163 164 const gridPositions = assignGridPositions(nodeIds[0]); 165 166 let nodes = nodeIds.map(id => ({ 167 id: id, 168 x: (gridPositions[id][0] + 2) * gridSize, 169 y: (gridPositions[id][1] + 2) * gridSize 170 })); 171 172 const links = []; 173 for (let source in connectionMatrix) { 174 for (let target in connectionMatrix[source]) { 175 if (connectionMatrix[source][target]) { 176 links.push({ 177 source: nodes.find(n => n.id === source), 178 target: nodes.find(n => n.id === target), 179 type: connectionMatrix[source][target][0] 180 }); 181 } 182 } 183 } 184 185 const svg = d3.select("#visualization") 186 .append("svg") 187 .attr("width", width) 188 .attr("height", height); 189 190 const link = svg.append("g") 191 .selectAll("line") 192 .data(links) 193 .join("line") 194 .attr("stroke", "#999") 195 .attr("stroke-opacity", 0.6) 196 .attr("x1", d => d.source.x) 197 .attr("y1", d => d.source.y) 198 .attr("x2", d => d.target.x) 199 .attr("y2", d => d.target.y); 200 201 const node = svg.append("g") 202 .selectAll("circle") 203 .data(nodes) 204 .join("circle") 205 .attr("r", nodeRadius) 206 .attr("fill", "lightblue") 207 .attr("cx", d => d.x) 208 .attr("cy", d => d.y); 209 210 const label = svg.append("g") 211 .selectAll("text") 212 .data(nodes) 213 .join("text") 214 .text(d => d.id) 215 .attr("font-size", nodeRadius * 0.3) 216 .attr("text-anchor", "middle") 217 .attr("dominant-baseline", "central") 218 .attr("x", d => d.x) 219 .attr("y", d => d.y); 220 221 const transaction = svg.append("circle") 222 .attr("r", transactionRadius) 223 .attr("fill", "red"); 224 225 return { nodes, transaction }; 226 } 227 228 let { nodes, transaction } = createVisualization(); 229 let currentNode = nodes[0]; 230 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 231 232 const directionMapping = { 233 "up": "up", 234 "down": "down", 235 "next": "right", 236 "previous": "left" 237 }; 238 239 function updateMetadataDisplay(nodeId) { 240 const metadata = nodeMetadata[nodeId]; 241 let html = `<h3><a href="${metadata.url}" target="_blank">${nodeId}</a></h3>`; 242 if (isEditMode) { 243 html += ` 244 <form id="metadataForm"> 245 <input type="text" id="nodeNameInput" value="${nodeId}"> 246 <textarea id="content" name="content" placeholder="Content">${metadata.content}</textarea> 247 <input type="url" id="url" name="url" value="${metadata.url}" placeholder="URL"> 248 <button type="button" onclick="saveMetadata('${nodeId}')">Save</button> 249 <button type="button" onclick="deleteNode('${nodeId}')">Delete</button> 250 <button type="button" onclick="uploadNodeData()">Upload</button> 251 <button type="button" onclick="downloadNodeData()">Download</button> 252 </form>`; 253 } else { 254 html += `<p>${metadata.content}</p>`; 255 } 256 document.getElementById("metadata").innerHTML = html; 257 } 258 259 function saveMetadata(oldNodeId) { 260 const newNodeId = document.getElementById("nodeNameInput").value; 261 const content = document.getElementById("content").value; 262 const url = document.getElementById("url").value; 263 264 const newMetadata = { 265 content: content, 266 url: url 267 }; 268 269 if (oldNodeId !== newNodeId) { 270 nodeMetadata[newNodeId] = newMetadata; 271 delete nodeMetadata[oldNodeId]; 272 nodeIds = nodeIds.map(id => id === oldNodeId ? newNodeId : id); 273 274 // Update connectionMatrix 275 connectionMatrix[newNodeId] = connectionMatrix[oldNodeId]; 276 delete connectionMatrix[oldNodeId]; 277 for (let node in connectionMatrix) { 278 if (connectionMatrix[node][oldNodeId]) { 279 connectionMatrix[node][newNodeId] = connectionMatrix[node][oldNodeId]; 280 delete connectionMatrix[node][oldNodeId]; 281 } 282 } 283 284 currentNode = { id: newNodeId, x: currentNode.x, y: currentNode.y }; 285 //Update the nodes object 286 nodes = nodes.map(n => n.id === oldNodeId ? currentNode : n); 287 console.log(nodes); 288 } else { 289 nodeMetadata[newNodeId] = newMetadata; 290 } 291 292 ({ nodes, transaction } = createVisualization()); 293 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 294 updateButtons(); 295 updateMetadataDisplay(newNodeId); 296 } 297 298 function resetMetadata(nodeId) { 299 nodeMetadata[nodeId] = { 300 "content": "", 301 "url": "" 302 }; 303 updateMetadataDisplay(nodeId); 304 } 305 306 function moveTransaction(direction) { 307 for (let targetId in connectionMatrix[currentNode.id]) { 308 const connection = connectionMatrix[currentNode.id][targetId]; 309 if (connection && connection[0] === direction) { 310 const targetNode = nodes.find(n => n.id === targetId); 311 if (targetNode) { 312 currentNode = targetNode; 313 transaction.transition() 314 .duration(500) 315 .attr("cx", currentNode.x) 316 .attr("cy", currentNode.y); 317 updateButtons(); 318 updateMetadataDisplay(currentNode.id); 319 break; 320 } 321 } 322 } 323 } 324 325 function updateButtons() { 326 for (let direction in directionMapping) { 327 const buttonId = `#${directionMapping[direction]}Btn`; 328 let isEnabled = false; 329 for (let targetId in connectionMatrix[currentNode.id]) { 330 const connection = connectionMatrix[currentNode.id][targetId]; 331 if (connection && connection[0] === direction) { 332 isEnabled = true; 333 break; 334 } 335 } 336 d3.select(buttonId).property("disabled", !isEnabled); 337 } 338 } 339 340 function toggleEditMode() { 341 isEditMode = !isEditMode; 342 updateMetadataDisplay(currentNode.id); 343 document.getElementById("editToggle").textContent = isEditMode ? "View Mode" : "Edit Mode"; 344 document.getElementById("addNodeForm").style.display = isEditMode ? "block" : "none"; 345 } 346 347 function addNewNode() { 348 const newNodeName = document.getElementById("newNodeName").value; 349 const direction = document.getElementById("newNodeDirection").value; 350 351 if (!newNodeName || nodeIds.includes(newNodeName)) { 352 alert("Please enter a unique node name."); 353 return; 354 } 355 356 nodeIds.push(newNodeName); 357 connectionMatrix[newNodeName] = {}; 358 nodeMetadata[newNodeName] = { 359 "content": "", 360 "url": "" 361 }; 362 363 connectionMatrix[currentNode.id][newNodeName] = [direction, directionToGridDelta[direction]]; 364 const oppositeDirection = direction === "next" ? "previous" : (direction === "up" ? "down" : "up"); 365 connectionMatrix[newNodeName][currentNode.id] = [oppositeDirection, directionToGridDelta[oppositeDirection]]; 366 367 ({ nodes, transaction } = createVisualization()); 368 369 // Move the transaction to the new node 370 currentNode = nodes.find(n => n.id === newNodeName); 371 transaction.transition() 372 .duration(500) 373 .attr("cx", currentNode.x) 374 .attr("cy", currentNode.y); 375 376 updateButtons(); 377 updateMetadataDisplay(currentNode.id); 378 379 // Clear the input field 380 document.getElementById("newNodeName").value = ""; 381 } 382 383 d3.select("#upBtn").on("click", () => moveTransaction("up")); 384 d3.select("#downBtn").on("click", () => moveTransaction("down")); 385 d3.select("#leftBtn").on("click", () => moveTransaction("previous")); 386 d3.select("#rightBtn").on("click", () => moveTransaction("next")); 387 388 updateButtons(); 389 updateMetadataDisplay(currentNode.id); 390 391 window.addEventListener('resize', () => { 392 ({ nodes, transaction } = createVisualization()); 393 currentNode = nodes.find(n => n.id === currentNode.id); 394 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 395 updateButtons(); 396 updateMetadataDisplay(currentNode.id); 397 }); 398 399 function deleteNode(nodeId) { 400 if (nodeId === "User") { 401 alert("Cannot delete the initial node."); 402 return; 403 } 404 405 delete nodeMetadata[nodeId]; 406 delete connectionMatrix[nodeId]; 407 nodeIds = nodeIds.filter(id => id !== nodeId); 408 for (let node in connectionMatrix) { 409 delete connectionMatrix[node][nodeId]; 410 } 411 412 ({ nodes, transaction } = createVisualization()); 413 currentNode = nodes[0]; 414 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 415 updateButtons(); 416 updateMetadataDisplay(currentNode.id); 417 } 418 419 function downloadNodeData() { 420 const data = { 421 nodeIds: nodeIds, 422 connectionMatrix: connectionMatrix, 423 nodeMetadata: nodeMetadata 424 }; 425 const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); 426 const url = URL.createObjectURL(blob); 427 const a = document.createElement("a"); 428 a.href = url; 429 a.download = "preconfirmations.json"; 430 a.click(); 431 } 432 433 function uploadNodeData() { 434 const input = document.createElement("input"); 435 input.type = "file"; 436 input.accept = ".json"; 437 input.onchange = e => { 438 const file = e.target.files[0]; 439 const reader = new FileReader(); 440 reader.onload = e => { 441 const data = JSON.parse(e.target.result); 442 nodeIds = data.nodeIds; 443 connectionMatrix = data.connectionMatrix; 444 nodeMetadata = data.nodeMetadata; 445 ({ nodes, transaction } = createVisualization()); 446 currentNode = nodes[0]; 447 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 448 updateButtons(); 449 updateMetadataDisplay(currentNode.id); 450 }; 451 reader.readAsText(file); 452 }; 453 input.click(); 454 } 455 456 function downloadNodeData() { 457 const data = { 458 nodeIds: nodeIds, 459 connectionMatrix: connectionMatrix, 460 nodeMetadata: nodeMetadata 461 }; 462 const blob = new Blob([JSON.stringify(data)], { type: "application/json" }); 463 const url = URL.createObjectURL(blob); 464 const a = document.createElement("a"); 465 a.href = url; 466 a.download = "preconfirmations.json"; 467 a.click(); 468 } 469 470 function onLoadPopulateNodesFromURl() { 471 //Fetch json file from relative URL, check if it exists, if so populate the nodes 472 fetch('2.json') 473 .then(response => response.json()) 474 .then(data => { 475 nodeIds = data.nodeIds; 476 connectionMatrix = data.connectionMatrix; 477 nodeMetadata = data.nodeMetadata; 478 ({ nodes, transaction } = createVisualization()); 479 currentNode = nodes[0]; 480 transaction.attr("cx", currentNode.x).attr("cy", currentNode.y); 481 updateButtons(); 482 updateMetadataDisplay(currentNode.id); 483 }) 484 .catch((error) => { 485 console.error('Error:', error); 486 }); 487 } 488 489 </script> 490 </body> 491 </html>