wormhole-explorer/web/src/components/Governance.tsx

178 lines
4.7 KiB
TypeScript

import { ChevronRight } from "@mui/icons-material";
import { Card, IconButton, Typography } from "@mui/material";
import { Box } from "@mui/system";
import {
createColumnHelper,
getCoreRowModel,
getExpandedRowModel,
Row,
useReactTable,
} from "@tanstack/react-table";
import { ReactElement, useMemo } from "react";
import useLatestObservations, {
ObservationsResponse,
} from "../hooks/useLatestObservations";
import useLatestVAAs, { VAAsResponse } from "../hooks/useLatestVAAs";
import * as vaa from "../utils/vaa";
import Table from "./Table";
const columnHelper = createColumnHelper<VAAsResponse>();
// TODO: parse once
const columns = [
columnHelper.display({
id: "_expand",
cell: ({ row }) =>
row.getCanExpand() ? (
<IconButton
size="small"
{...{
onClick: row.getToggleExpandedHandler(),
style: { cursor: "pointer" },
}}
>
<ChevronRight
sx={{
transition: ".2s",
transform: row.getIsExpanded() ? "rotate(90deg)" : undefined,
}}
/>
</IconButton>
) : null,
}),
columnHelper.accessor("_id", {
id: "sequence",
header: () => "Sequence",
cell: (info) => info.getValue().split("/")[2],
}),
columnHelper.accessor("vaas", {
id: "type",
header: () => "Type",
cell: (info) =>
vaa.parse(Buffer.from(info.getValue(), "base64")).payload.type,
}),
columnHelper.accessor("vaas", {
id: "chain",
header: () => "Chain",
cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.chain || "",
}),
columnHelper.accessor("vaas", {
id: "address",
header: () => "Address",
cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.address || "",
}),
columnHelper.accessor("vaas", {
id: "module",
header: () => "Module",
cell: (info) =>
(vaa.parse(Buffer.from(info.getValue(), "base64")).payload as any)
.module || "",
}),
columnHelper.accessor("updatedAt", {
header: () => "Observed At",
cell: (info) => new Date(info.getValue()).toLocaleString(),
}),
];
const obsColumnHelper = createColumnHelper<CollatedObservation>();
const obsColumns = [
obsColumnHelper.accessor("_id", {
header: () => "Sequence",
cell: (info) => info.getValue().split("/")[2],
}),
obsColumnHelper.accessor("count", {
header: () => "Count",
cell: (info) => info.getValue(),
}),
];
function VAADetails({ row }: { row: Row<VAAsResponse> }): ReactElement {
return (
<Typography variant="body2" sx={{ wordBreak: "break-all" }}>
{Buffer.from(row.original.vaas, "base64").toString("hex")}
</Typography>
);
}
type CollatedObservation = {
_id: string;
count: number;
observations: ObservationsResponse[];
};
function Governance() {
const vaas = useLatestVAAs(
"1/0000000000000000000000000000000000000000000000000000000000000004"
);
const obs = useLatestObservations(
"1/0000000000000000000000000000000000000000000000000000000000000004"
);
const collatedObservations: CollatedObservation[] = useMemo(
() =>
// NOTE: this ignores differing digests
Object.entries(
obs.reduce((obvsById, o) => {
if (!obvsById[o.messageId]) {
obvsById[o.messageId] = [];
}
obvsById[o.messageId].push(o);
return obvsById;
}, {} as any)
).map(([key, val]: [string, any]) => ({
_id: key,
count: val.length,
observations: val,
})),
[obs]
);
const table = useReactTable({
columns,
data: vaas,
getRowId: (vaa) => vaa._id,
getRowCanExpand: () => true,
getCoreRowModel: getCoreRowModel(),
getExpandedRowModel: getExpandedRowModel(),
enableSorting: false,
});
const obsTable = useReactTable({
columns: obsColumns,
data: collatedObservations,
getRowId: (o) => o._id,
getCoreRowModel: getCoreRowModel(),
enableSorting: false,
});
return (
<>
<Box m={2}>
<Card>
<Box m={2}>
<Typography variant="h5">Latest Governance VAAs</Typography>
<Table<VAAsResponse>
table={table}
renderSubComponent={VAADetails}
/>
</Box>
</Card>
</Box>
<Box m={2}>
<Card>
<Box m={2}>
<Typography variant="h5">Latest Governance Observations</Typography>
<Typography variant="caption">
Collated from the latest 100 governance observations
</Typography>
<Table<CollatedObservation> table={obsTable} />
</Box>
</Card>
</Box>
</>
);
}
export default Governance;