Skip to content

Commit

Permalink
Merge pull request #76 from chainbound/feat/rebuild-demo
Browse files Browse the repository at this point in the history
feat: update demo
  • Loading branch information
merklefruit authored Jun 9, 2024
2 parents e6ef7d7 + a9796ff commit eea2d17
Show file tree
Hide file tree
Showing 14 changed files with 239 additions and 113 deletions.
22 changes: 22 additions & 0 deletions bolt-sidecar/src/json_rpc/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ impl CommitmentsRpc for JsonRpcApi {

// TODO: check if there is enough time left in the current slot

// Web demo: push an event to the demo server to notify the frontend
emit_bolt_demo_event("commitment request accepted");

// Forward the constraints to mev-boost's builder API
self.mevboost_client
.post_constraints(&signed_constraints)
Expand All @@ -162,6 +165,25 @@ impl CommitmentsRpc for JsonRpcApi {
}
}

fn emit_bolt_demo_event<T: Into<String>>(message: T) {
let msg = message.into();
tokio::spawn(async move {
let client = reqwest::Client::new();
client
.post("http://host.docker.internal:3001/events")
.header("Content-Type", "application/json")
.body(
serde_json::to_string(
&serde_json::json!({"message": format!("BOLT-SIDECAR: {}", msg)}),
)
.unwrap(),
)
.send()
.await
.expect("failed to send event to demo server");
});
}

#[cfg(test)]
mod tests {
use serde_json::Value;
Expand Down
35 changes: 19 additions & 16 deletions bolt-web-demo/backend/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,18 @@ app.get("/retry-port-events", (req, res) => {
});

app.get("/latest-slot", (req, res) => {
res.send({ slot: LATEST_SLOT });
if (!DEVNET_ENDPOINTS[EventType.BEACON_CLIENT_URL_FOUND]) {
res.status(500).send({ message: "No beacon client URL found" });
return;
}

getSlot(DEVNET_ENDPOINTS[EventType.BEACON_CLIENT_URL_FOUND]).then((slot) => {
if (slot !== undefined) {
res.send({ slot });
} else {
res.status(500).send("Could not fetch the latest slot");
}
});
});

// Endpoint to send a signed preconfirmation transaction to the BOLT MEV sidecar
Expand All @@ -70,28 +81,20 @@ app.post("/preconfirmation", async (req, res) => {
return;
}

const { signedTx, txHash } = req.body;
if (!signedTx || !txHash) {
res.status(400).send("No signedTx or txHash provided");
const { slot, tx, signature } = req.body;
if (!tx || !signature) {
res.status(400).send("No tx or signature provided");
return;
}

const slot = await getSlot(beaconClientUrl);

const preconfirmationResponse = await fetch(`http://${mevSidecarUrl}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
id: "1",
jsonrpc: "2.0",
method: "eth_requestPreconfirmation",
params: [
{
slot: slot + 2,
txHash,
rawTx: signedTx,
},
],
method: "bolt_inclusionPreconfirmation",
params: [{ slot, tx, signature }],
}),
}).then((response) => response.json());

Expand All @@ -106,12 +109,12 @@ server.listen(SERVER_PORT, () => {
async function sendDevnetEvents() {
waitForPort(
["cl-1-lighthouse-geth", "http"],
EventType.BEACON_CLIENT_URL_FOUND,
EventType.BEACON_CLIENT_URL_FOUND
);

waitForPort(
["el-1-geth-lighthouse", "rpc"],
EventType.JSONRPC_PROVIDER_URL_FOUND,
EventType.JSONRPC_PROVIDER_URL_FOUND
);

waitForPort(["mev-sidecar-api", "api"], EventType.MEV_SIDECAR_URL_FOUND);
Expand Down
Binary file added bolt-web-demo/frontend/public/bolt-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified bolt-web-demo/frontend/src/app/favicon.ico
Binary file not shown.
136 changes: 80 additions & 56 deletions bolt-web-demo/frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
"use client";

import io from "socket.io-client";
import { useState, useEffect, useCallback } from "react";
import Image from "next/image";
import { useState, useEffect } from "react";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Button } from "@/components/ui/button";
import { createAndSignTransaction } from "@/lib/wallet";
import { createPreconfPayload } from "@/lib/wallet";
import { EventType } from "@/lib/types";
import { Progress } from "@/components/ui/progress";

type Event = { message: string; type?: EventType; timestamp: string };
type Event = {
message: string;
type?: EventType;
timestamp: string;
link?: string;
};

export const SERVER_URL = "http://localhost:3001";

export default function Home() {
const [events, setEvents] = useState<Array<Event>>([]);

const [preconfSent, setPreconfSent] = useState<boolean>(false);
const [preconfSlot, setPreconfSlot] = useState<number>(-1);
const [preconfIncluded, setPreconfIncluded] = useState<boolean>(false);

const [timerActive, setTimerActive] = useState<boolean>(false);
const [time, setTime] = useState(0);

Expand All @@ -22,8 +33,6 @@ export default function Home() {
const [providerUrl, setProviderUrl] = useState<string>("");
const [explorerUrl, setExplorerUrl] = useState<string>("");

const SERVER_URL = "http://localhost:3001";

useEffect(() => {
fetch(`${SERVER_URL}/retry-port-events`);
fetch(`${SERVER_URL}/latest-slot`)
Expand All @@ -35,19 +44,23 @@ export default function Home() {
const newSocket = io(SERVER_URL, { autoConnect: true });

newSocket.on("new-event", (event: Event) => {
console.log("Event from server:", event);
console.debug("Event from server:", event);

// If the event has a special type, handle it differently
switch (event.type) {
case EventType.BEACON_CLIENT_URL_FOUND:
console.info("Beacon client URL found:", event.message);
setBeaconClientUrl(event.message);
return;
case EventType.JSONRPC_PROVIDER_URL_FOUND:
console.info("Provider URL found:", event.message);
setProviderUrl(event.message);
return;
case EventType.EXPLORER_URL_FOUND:
console.info("Explorer URL found:", event.message);
setExplorerUrl(event.message);
case EventType.MEV_SIDECAR_URL_FOUND:
console.info("MEV sidecar URL found:", event.message);
return;
case EventType.NEW_SLOT:
setNewSlotNumber(Number(event.message));
Expand All @@ -56,36 +69,28 @@ export default function Home() {
break;
}

setEvents((prev) => [event, ...prev]);

// If the event is a preconfirmation, extract the tx hash and slot number
// and display a message with the explorer URL
if (
event.message
.toLowerCase()
.includes("preconfirmation proof verified for tx hash")
.includes("verified merkle proof for tx_hash")
) {
const txHash = event.message.match(/0x[a-fA-F0-9]{64}/g);
const slot = event.message
.match(/slot \d+/g)
?.toString()
.match(/\d+/g)
?.toString();

new Promise((_) =>
setTimeout(() => {
const event: Event = {
message: `Preconfirmation ${txHash} available here: ${explorerUrl}/slot/${slot}`,
timestamp: new Date().toISOString(),
};
setEvents((prev) => [event, ...prev]);
}, 1000),
);
setPreconfIncluded(true);
dispatchEvent({
message: `Preconfirmation included`,
link: `${explorerUrl}/slot/${preconfSlot}`,
timestamp: new Date().toISOString(),
});
}

setEvents((prev) => [event, ...prev]);
});

return () => {
newSocket.close();
};
}, [explorerUrl]);
}, [explorerUrl, preconfSlot]);

useEffect(() => {
let interval: any = null;
Expand All @@ -101,50 +106,58 @@ export default function Home() {
return () => clearInterval(interval);
}, [timerActive]);

const sendPreconfirmation = useCallback(
async function () {
setEvents([]);
setPreconfSent(true);
try {
const { signedTx, txHash } =
await createAndSignTransaction(providerUrl);

// 1. POST preconfirmation.
// The preconfirmation is considered valid as soon as the server responds with a 200 status code.
setTime(0);
setTimerActive(true);
const res = await fetch("http://localhost:3001/preconfirmation", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ signedTx, txHash }),
});
if (res.status === 200) {
console.log("Preconfirmation successful");
setTimerActive(false);
}
} catch (e) {
console.error(e);
async function sendPreconfirmation() {
// Reset state
setEvents([]);
setPreconfSent(true);
setPreconfIncluded(false);

try {
const { payload, txHash } = await createPreconfPayload(providerUrl);
setPreconfSlot(payload.slot);
dispatchEvent({
message: `Preconfirmation request sent for tx: ${txHash} at slot ${payload.slot}`,
timestamp: new Date().toISOString(),
});

// 1. POST preconfirmation.
// The preconfirmation is considered valid as soon as the server responds with a 200 status code.
setTime(0);
setTimerActive(true);
const res = await fetch(`${SERVER_URL}/preconfirmation`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (res.status === 200) {
console.log("Preconfirmation successful");
setTimerActive(false);
}
},
[providerUrl],
);
} catch (e) {
console.error(e);
}
}

function dispatchEvent(event: Event) {
setEvents((prev) => [event, ...prev]);
}

return (
<main className="flex min-h-screen flex-col items-center p-24">
<div className="w-full max-w-5xl items-center justify-between lg:flex">
<h1 className="font-mono text-2xl font-bold">BOLT</h1>
<Image src="/bolt-logo.png" alt="BOLT" width={100} height={100} />

<p>Your friendly preconfirmation companion.</p>
</div>

{newSlotNumber < 128 ? (
<>
{newSlotNumber === -1 ? (
<div className="w-full max-w-5xl pt-4">
<div className="w-full max-w-6xl pt-4">
<p className="text-center pt-10">Loading...</p>
</div>
) : (
<div className="w-full max-w-5xl pt-4">
<div className="w-full max-w-6xl pt-4">
<div className="grid gap-3 border p-4 border-gray-800">
<p className="text-lg">
MEV Boost is not active yet, please wait
Expand Down Expand Up @@ -212,7 +225,18 @@ export default function Home() {
<li key={index}>
<span>{parseDateToMs(message.timestamp)}</span>
{" | "}
{JSON.stringify(message.message)}
{message.message.toString()}
{message.link && (
<a
href={message.link}
target="_blank"
rel="noreferrer"
className="text-blue-500"
>
{" "}
[link]
</a>
)}
</li>
))}
</ul>
Expand Down
Loading

0 comments on commit eea2d17

Please sign in to comment.