diff --git a/apps/namadillo/src/App/Common/SyncIndicator.tsx b/apps/namadillo/src/App/Common/SyncIndicator.tsx
index e9b659e0e3..55877e0c8f 100644
--- a/apps/namadillo/src/App/Common/SyncIndicator.tsx
+++ b/apps/namadillo/src/App/Common/SyncIndicator.tsx
@@ -1,10 +1,12 @@
import { Tooltip } from "@namada/components";
import { syncStatusAtom } from "atoms/syncStatus/atoms";
+import { useChainStatus } from "hooks/useChainStatus";
import { useAtomValue } from "jotai";
import { twMerge } from "tailwind-merge";
export const SyncIndicator = (): JSX.Element => {
const syncStatus = useAtomValue(syncStatusAtom);
+ const { data } = useChainStatus();
return (
@@ -21,7 +23,7 @@ export const SyncIndicator = (): JSX.Element => {
"Syncing"
: syncStatus.isError ?
"Error syncing"
- : "Fully synced"}
+ : `Fully synced: height ${data?.height}, epoch ${data?.epoch}`}
);
diff --git a/apps/namadillo/src/hooks/useChainStatus.tsx b/apps/namadillo/src/hooks/useChainStatus.tsx
new file mode 100644
index 0000000000..f262807baf
--- /dev/null
+++ b/apps/namadillo/src/hooks/useChainStatus.tsx
@@ -0,0 +1,18 @@
+import { useSSE } from "@namada/hooks";
+import { indexerUrlAtom } from "atoms/settings";
+import { useAtomValue } from "jotai";
+
+type Status = {
+ height: number;
+ epoch: number;
+};
+
+export const useChainStatus = (): {
+ data?: Status;
+ error?: Event;
+ closeFn?: () => void;
+} => {
+ const url = useAtomValue(indexerUrlAtom);
+
+ return useSSE(`${url}/api/v1/chain/status`);
+};
diff --git a/packages/hooks/src/index.ts b/packages/hooks/src/index.ts
index 8a1f36dcd6..f36f9fb1cc 100644
--- a/packages/hooks/src/index.ts
+++ b/packages/hooks/src/index.ts
@@ -1,6 +1,7 @@
export * from "./useDebounce";
export * from "./useEffectSkipFirstRender";
export * from "./useEvent";
+export * from "./useSSE";
export * from "./useSanitizedLocation";
export * from "./useSanitizedParams";
export * from "./useUntil";
diff --git a/packages/hooks/src/useSSE.ts b/packages/hooks/src/useSSE.ts
new file mode 100644
index 0000000000..8d8ca35a1e
--- /dev/null
+++ b/packages/hooks/src/useSSE.ts
@@ -0,0 +1,32 @@
+import { useEffect, useState } from "react";
+
+export function useSSE(
+ url: string,
+ options?: EventSourceInit
+): { data?: T; error?: Event; closeFn?: () => void } {
+ const [data, setData] = useState();
+ const [error, setError] = useState();
+ const [closeFn, setCloseFn] = useState<() => void>();
+
+ useEffect(() => {
+ const eventSource = new EventSource(url, options);
+ setCloseFn(() => eventSource.close.bind(eventSource));
+
+ eventSource.onmessage = (event) => {
+ setData((old) =>
+ event.data === JSON.stringify(old) ? old : JSON.parse(event.data)
+ );
+ };
+
+ eventSource.onerror = (err) => {
+ setError(err);
+ eventSource.close();
+ };
+
+ return () => {
+ eventSource.close();
+ };
+ }, [url]);
+
+ return { data, error, closeFn };
+}