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