diff --git a/src/app/api/ibc/clients/route.ts b/src/app/api/ibc/clients/route.ts new file mode 100644 index 0000000..efd01dd --- /dev/null +++ b/src/app/api/ibc/clients/route.ts @@ -0,0 +1,52 @@ +import db from "@/lib/db"; +import { type NextRequest } from "next/server"; + +export async function GET(req: NextRequest) { + console.log("SUCCESS: GET /api/ibc/clients"); + try { + console.log("Querying indexer for IBC clients."); + const clients = await db.events.findMany({ + select: { + tx_results: { + select: { + tx_hash: true, + }, + }, + blocks: { + select: { + created_at: true, + height: true, + }, + }, + attributes: { + select: { + key: true, + value: true, + }, + }, + }, + where: { + type: { + equals: "create_client", + }, + }, + }); + + console.log("Successfully queried for IBC Clients.", clients); + + // NOTE: As of now, cannot completely decode the Protobuf data for an IBC client related transaction + // due to ibc.core.client.v1.MsgCreateClient not having a defined URL protobuf schema that can be resolved. + // What data that can be returned by decoding from TxResult and Transaction is not all that useful. + // const clientTx = clients.at(0)?.tx_results?.tx_result; + // if (clientTx) { + // const [tx, ibcClient] = ibcEventFromBytes(clientTx); + // console.log("Successfully extracted IBC data from txResult data.", tx, ibcClient); + // } + + return new Response(JSON.stringify(clients)); + + } catch (error) { + console.error("GET request failed.", error); + return new Response("Could not query IBC Clients.", { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/ibc/channels/page.tsx b/src/app/ibc/channels/page.tsx new file mode 100644 index 0000000..f566e14 --- /dev/null +++ b/src/app/ibc/channels/page.tsx @@ -0,0 +1,9 @@ +const Page = () => { + return ( +
+

IBC Channels are not yet implemented...

+
+ ); +}; + +export default Page; diff --git a/src/app/ibc/clients/page.tsx b/src/app/ibc/clients/page.tsx new file mode 100644 index 0000000..f8ef542 --- /dev/null +++ b/src/app/ibc/clients/page.tsx @@ -0,0 +1,57 @@ +"use client"; +import ClientsTable from "@/components/ibc/clients/ClientsTable"; +import { useQuery } from "@tanstack/react-query"; +import axios from "axios"; + +const Page = () => { + const { data , isFetched, isError } = useQuery({ + queryFn: async () => { + console.log("Fetching: GET /api/ibc/clients"); + const { data } = await axios.get("/api/ibc/clients"); + console.log("Fetched result:", data); + return data; + // const result = SearchResultValidator.safeParse(data); + // if (result.success) { + // console.log(result.data); + // return result.data; + // } else { + // throw new Error(result.error.message); + // } + }, + queryKey: ["IBCClients"], + retry: false, + meta: { + errorMessage: "Failed to query for IBC Clients. Please try again.", + }, + }); + + if (isError) { + return ( +
+

No results found.

+
+ ); + } + + return ( +
+ {isFetched ? ( +
+

IBC Clients

+ {// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions + data ? ( +
+ +
+ ) : ( +

No results

+ )} +
+ ) : ( +

loading...

+ )} +
+ ); +}; + +export default Page; \ No newline at end of file diff --git a/src/app/ibc/connections/page.tsx b/src/app/ibc/connections/page.tsx new file mode 100644 index 0000000..1f0d717 --- /dev/null +++ b/src/app/ibc/connections/page.tsx @@ -0,0 +1,9 @@ +const Page = () => { + return ( +
+

IBC Connections are not yet implemented...

+
+ ); +}; + +export default Page; diff --git a/src/app/ibc/page.tsx b/src/app/ibc/page.tsx new file mode 100644 index 0000000..f4da06f --- /dev/null +++ b/src/app/ibc/page.tsx @@ -0,0 +1,28 @@ +import Link from "next/link"; + +const Page = () => { + return ( +
+

Available IBC data to explore

+
+

+ + IBC Clients + +

+

+ + IBC Channels + +

+

+ + IBC Connections + +

+
+
+ ); +}; + +export default Page; \ No newline at end of file diff --git a/src/app/page.tsx b/src/app/page.tsx index 9e8ea16..4170559 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,6 +16,11 @@ export default async function Home() { Recent Blocks

+

+ + IBC Clients + +

); diff --git a/src/components/Providers/index.tsx b/src/components/Providers/index.tsx index 2324134..33e7610 100644 --- a/src/components/Providers/index.tsx +++ b/src/components/Providers/index.tsx @@ -16,6 +16,11 @@ const Providers = ({ children } : { children: React.ReactNode }) => { }, queryCache: new QueryCache({ onError: (error, query) => { + // TODO: Overall model is fine but need to change how meta is used. + // Idea: Add a `errorTitle` field instead that can be used in place of "Error" below. This gives a top level, succinct explanation. + // `description` becomes whatever value we store inside our error value. This is what needs to be refactored to make all queries play nicely. + // This allows each error to clearly signal its nature while also giving detail where appropriate. The issue of that detail is delegated to the useQuery callsite + // and any component/route that throws errors. // There may be a more elegant way of expressing this but the general typing of onError's `error` and `query` arguments requires some amount of refinement for safety. // https://tanstack.com/query/latest/docs/react/reference/QueryCache let errorMessage = ""; diff --git a/src/components/ibc/clients/ClientsTable.tsx b/src/components/ibc/clients/ClientsTable.tsx new file mode 100644 index 0000000..0c869f1 --- /dev/null +++ b/src/components/ibc/clients/ClientsTable.tsx @@ -0,0 +1,30 @@ +import { columns } from "./columns"; +import { DataTable } from "../../ui/data-table"; +// import { type QueryKind } from "@/lib/validators/search"; +import { type FC } from "react"; + +interface Props { + data: Array<{ + tx_results: { + tx_hash: string | null, + }, + blocks: { + created_at: string, + height: bigint, + }, + attributes: Array<{ + key: string, + value: string, + }>, + }>, +} + +const ClientsTable : FC = ({ data }) => { + return ( +
+ +
+ ); +}; + +export default ClientsTable; \ No newline at end of file diff --git a/src/components/ibc/clients/columns.tsx b/src/components/ibc/clients/columns.tsx new file mode 100644 index 0000000..48a297d --- /dev/null +++ b/src/components/ibc/clients/columns.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { type ColumnDef } from "@tanstack/react-table"; +import Link from "next/link"; + +export interface ClientsColumns { + tx_results: { + tx_hash: string | null, + }, + blocks: { + created_at: string, + height: bigint, + }, + attributes: Array<{ + key: string, + value: string, + }>, +}; + +// TODO formating, styling, etc +export const columns : Array> = [ + { + accessorKey: "blocks.height", + header: () =>
Height
, + cell: ({ getValue }) => { + const ht: bigint = getValue() as bigint; + return {ht.toString()}; + }, + }, + { + accessorKey: "blocks.created_at", + header: () =>
Timestamp
, + cell: ({ getValue }) => { + const timestamp : string = getValue() as string; + return

{timestamp}

; + }, + }, + { + accessorKey: "tx_results", + header: () =>
Hash
, + cell: ({ getValue }) => { + const tx = getValue() as { tx_hash : string | null }; + if (tx.tx_hash !== null) { + return

{tx.tx_hash}

; + } + }, + }, + { + accessorKey: "attributes", + header: () =>
info
, + cell: ({ getValue }) => { + const tx = getValue() as Array<{ key: string, value: string | null }>; + // TODO: besides styling itself, do better re: null value case for value + return ( + + ); + }, + }, +]; \ No newline at end of file diff --git a/src/lib/protobuf.ts b/src/lib/protobuf.ts index e095c9b..13d1a72 100644 --- a/src/lib/protobuf.ts +++ b/src/lib/protobuf.ts @@ -1,7 +1,19 @@ import { TxResult } from "@buf/cosmos_cosmos-sdk.bufbuild_es/tendermint/abci/types_pb"; +// import { MsgCreateClient } from "@buf/cosmos_ibc.bufbuild_es/ibc/core/client/v1/tx_pb"; import { Transaction } from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1alpha1/transaction_pb"; +// import { IbcRelay } from "@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/ibc/v1alpha1/ibc_pb"; + export const transactionFromBytes = (txBytes : Buffer) => { const txResult = TxResult.fromBinary(txBytes); return Transaction.fromBinary(txResult.tx); +}; + +// NOTE: As of now, cannot completely decode the Protobuf data for an IBC client related transaction +// due to ibc.core.client.v1.MsgCreateClient not having a defined URL protobuf schema that can be resolved. +// What data that can be returned by decoding from TxResult and Transaction is not all that useful. +export const ibcEventFromBytes = (txBytes : Buffer) : [Transaction, TxResult] => { + const ibcEvent = TxResult.fromBinary(txBytes); + const tx = Transaction.fromBinary(ibcEvent.tx); + return [tx, ibcEvent]; }; \ No newline at end of file