Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update demo #76

Merged
merged 3 commits into from
Jun 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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