/ 4.html
4.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="3.html"> &lt;&lt;prev&lt;&lt; </a>&nbsp;<a href="5.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 = "nodedata.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 onLoadPopulateNodesFromURl() {
469              //Fetch json file from relative URL, check if it exists, if so populate the nodes
470              fetch('3.json')
471                  .then(response => response.json())
472                  .then(data => {
473                      nodeIds = data.nodeIds;
474                      connectionMatrix = data.connectionMatrix;
475                      nodeMetadata = data.nodeMetadata;
476                      ({ nodes, transaction } = createVisualization());
477                      currentNode = nodes[0];
478                      transaction.attr("cx", currentNode.x).attr("cy", currentNode.y);
479                      updateButtons();
480                      updateMetadataDisplay(currentNode.id);
481                  })
482                  .catch((error) => {
483                      console.error('Error:', error);
484                  });
485          }
486  
487      </script>
488  </body>
489  </html>