diff --git a/lib/opengradient/contracts/teeRegistry.ts b/lib/opengradient/contracts/teeRegistry.ts index 7e8138c52a..c5097fa7c2 100644 --- a/lib/opengradient/contracts/teeRegistry.ts +++ b/lib/opengradient/contracts/teeRegistry.ts @@ -1,14 +1,55 @@ import { ethers } from 'ethers'; import type { Address } from 'viem'; -import type { TEENodeWithStatus, TEEInfo, TEERegistryOverview, TEETypeInfo, TEETypeSummary } from 'lib/opengradient/teeRegistry'; -import { TEE_REGISTRY_ADDRESS } from 'lib/opengradient/teeRegistry'; - import TEERegistryAbi from './abi/TEERegistry.json'; import { ethDevnetProvider } from './providers'; +export const TEE_REGISTRY_ADDRESS = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248'; + const contract = new ethers.Contract(TEE_REGISTRY_ADDRESS, TEERegistryAbi, ethDevnetProvider); +export interface TEETypeInfo { + typeId: number; + name: string; + addedAt: bigint; +} + +export interface TEEInfo { + teeId: string; + owner: Address; + paymentAddress: Address; + endpoint: string; + publicKey: string; + tlsCertificate: string; + pcrHash: string; + teeType: number; + enabled: boolean; + registeredAt: bigint; + lastHeartbeatAt: bigint; +} + +export interface TEENodeWithStatus extends TEEInfo { + isActive: boolean; +} + +export interface TEETypeSummary { + typeId: number; + name: string; + totalNodes: number; + enabledNodes: number; + activeNodes: number; + approvedPCRs: number; + addedAt: bigint; +} + +export interface TEERegistryStats { + totalTypes: number; + totalNodes: number; + activeNodes: number; + enabledNodes: number; + approvedPCRs: number; +} + export const getTEETypes = async(): Promise> => { const [ typeIds, infos ] = await contract.getTEETypes(); @@ -68,7 +109,11 @@ export const getHeartbeatMaxAge = async(): Promise => { /** * Fetch full registry overview: types, nodes per type with status, and global stats. */ -export const getTEERegistryOverviewFromContract = async(): Promise => { +export const getTEERegistryOverview = async(): Promise<{ + types: Array; + stats: TEERegistryStats; + nodesByType: Record>; +}> => { // 1. Get all TEE types const types = await getTEETypes(); @@ -141,3 +186,5 @@ export const getTEERegistryOverviewFromContract = async(): Promise; - stats: TEERegistryStats; - nodesByType: Record>; -}; - -export type SerializedTEERegistryOverview = { - types: Array & { addedAt: string }>; - stats: TEERegistryStats; - nodesByType: Record & { - registeredAt: string; - lastHeartbeatAt: string; - }>>; -}; - -export const serializeTEERegistryOverview = (overview: TEERegistryOverview): SerializedTEERegistryOverview => ({ - types: overview.types.map((type) => ({ - ...type, - addedAt: type.addedAt.toString(), - })), - stats: overview.stats, - nodesByType: Object.fromEntries( - Object.entries(overview.nodesByType).map(([ typeId, nodes ]) => [ - typeId, - nodes.map((node) => ({ - ...node, - registeredAt: node.registeredAt.toString(), - lastHeartbeatAt: node.lastHeartbeatAt.toString(), - })), - ]), - ), -}); - -export const parseTEERegistryOverview = (overview: SerializedTEERegistryOverview): TEERegistryOverview => ({ - types: overview.types.map((type) => ({ - ...type, - addedAt: BigInt(type.addedAt), - })), - stats: overview.stats, - nodesByType: Object.fromEntries( - Object.entries(overview.nodesByType).map(([ typeId, nodes ]) => [ - Number(typeId), - nodes.map((node) => ({ - ...node, - registeredAt: BigInt(node.registeredAt), - lastHeartbeatAt: BigInt(node.lastHeartbeatAt), - })), - ]), - ), -}); - -export const getTEERegistryOverview = async(): Promise => { - const response = await fetch('/api/opengradient/tee-registry'); - if (!response.ok) { - throw new Error(`Failed to load TEE registry overview: ${ response.status }`); - } - - return parseTEERegistryOverview(await response.json() as SerializedTEERegistryOverview); -}; - -export const TEE_REGISTRY_QUERY_KEY = [ 'opengradient', 'teeRegistry' ]; diff --git a/pages/api/opengradient/tee-registry.ts b/pages/api/opengradient/tee-registry.ts deleted file mode 100644 index 3c7c180021..0000000000 --- a/pages/api/opengradient/tee-registry.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next'; - -import { getTEERegistryOverviewFromContract } from 'lib/opengradient/contracts/teeRegistry'; -import { serializeTEERegistryOverview } from 'lib/opengradient/teeRegistry'; - -export default async function handler(_req: NextApiRequest, res: NextApiResponse) { - try { - const overview = await getTEERegistryOverviewFromContract(); - res.status(200).json(serializeTEERegistryOverview(overview)); - } catch (error) { - res.status(500).json({ - error: error instanceof Error ? error.message : 'Failed to load TEE registry overview', - }); - } -} diff --git a/ui/home/HeroBanner.tsx b/ui/home/HeroBanner.tsx index 71b9c8edb3..f5f4a6acf7 100644 --- a/ui/home/HeroBanner.tsx +++ b/ui/home/HeroBanner.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { route } from 'nextjs-routes'; import useApiQuery from 'lib/api/useApiQuery'; -import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY } from 'lib/opengradient/teeRegistry'; +import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY } from 'lib/opengradient/contracts/teeRegistry'; import { HOMEPAGE_STATS, HOMEPAGE_STATS_MICROSERVICE } from 'stubs/stats'; import { LinkBox, LinkOverlay } from 'toolkit/chakra/link'; import { Skeleton } from 'toolkit/chakra/skeleton'; diff --git a/ui/home/TrustedExecution.tsx b/ui/home/TrustedExecution.tsx index 4290eb5665..c7ba13cee7 100644 --- a/ui/home/TrustedExecution.tsx +++ b/ui/home/TrustedExecution.tsx @@ -5,7 +5,7 @@ import React from 'react'; import { route } from 'nextjs-routes'; import dayjs from 'lib/date/dayjs'; -import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS, type TEENodeWithStatus } from 'lib/opengradient/teeRegistry'; +import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS, type TEENodeWithStatus } from 'lib/opengradient/contracts/teeRegistry'; import { Link } from 'toolkit/chakra/link'; import { Skeleton } from 'toolkit/chakra/skeleton'; import { PLACEHOLDER_TEE_REGISTRY_STATS, PLACEHOLDER_TEE_TYPES } from 'ui/opengradient/teeRegistry/placeholders'; @@ -143,6 +143,7 @@ const TrustedExecution = () => { .sort((a, b) => Number(b.lastHeartbeatAt - a.lastHeartbeatAt)); }, [ query.data?.nodesByType ]); const primaryType = types[0]; + const visibleNodes = nodes.slice(0, 3); const isLoading = query.isPlaceholderData; return ( @@ -308,7 +309,7 @@ const TrustedExecution = () => { - { nodes.length > 0 ? nodes.map((node) => ( + { visibleNodes.length > 0 ? visibleNodes.map((node) => ( )) : ( diff --git a/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png b/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png index c0226017d9..d525706ecf 100644 Binary files a/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png and b/ui/home/__screenshots__/HeroBanner.pw.tsx_dark-color-mode_customization-dark-mode-1.png differ diff --git a/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png b/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png index 297071f2df..d525706ecf 100644 Binary files a/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png and b/ui/home/__screenshots__/HeroBanner.pw.tsx_default_customization-dark-mode-1.png differ diff --git a/ui/opengradient/teeRegistry/TEENodeDetailDrawer.tsx b/ui/opengradient/teeRegistry/TEENodeDetailDrawer.tsx index 54304b51ec..9c3c890fb6 100644 --- a/ui/opengradient/teeRegistry/TEENodeDetailDrawer.tsx +++ b/ui/opengradient/teeRegistry/TEENodeDetailDrawer.tsx @@ -2,7 +2,7 @@ import { Box, Flex, Text, VStack } from '@chakra-ui/react'; import React from 'react'; import dayjs from 'lib/date/dayjs'; -import type { TEENodeWithStatus } from 'lib/opengradient/teeRegistry'; +import type { TEENodeWithStatus } from 'lib/opengradient/contracts/teeRegistry'; import { DrawerBackdrop, DrawerBody, DrawerCloseTrigger, DrawerContent, DrawerHeader, DrawerRoot, DrawerTitle } from 'toolkit/chakra/drawer'; import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand'; import AddressEntity from 'ui/shared/entities/address/AddressEntity'; diff --git a/ui/opengradient/teeRegistry/TEENodesTable.tsx b/ui/opengradient/teeRegistry/TEENodesTable.tsx index 1019804210..b039cf44fb 100644 --- a/ui/opengradient/teeRegistry/TEENodesTable.tsx +++ b/ui/opengradient/teeRegistry/TEENodesTable.tsx @@ -2,7 +2,7 @@ import { Box, Flex, Text } from '@chakra-ui/react'; import React from 'react'; import dayjs from 'lib/date/dayjs'; -import type { TEENodeWithStatus, TEETypeSummary } from 'lib/opengradient/teeRegistry'; +import type { TEENodeWithStatus, TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry'; import { Skeleton } from 'toolkit/chakra/skeleton'; import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table'; import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand'; diff --git a/ui/opengradient/teeRegistry/TEETypeCard.tsx b/ui/opengradient/teeRegistry/TEETypeCard.tsx index 177ef2d528..e2acbf8d67 100644 --- a/ui/opengradient/teeRegistry/TEETypeCard.tsx +++ b/ui/opengradient/teeRegistry/TEETypeCard.tsx @@ -1,7 +1,7 @@ import { Box, Flex, Grid, Text } from '@chakra-ui/react'; import React from 'react'; -import type { TEETypeSummary } from 'lib/opengradient/teeRegistry'; +import type { TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry'; import { Skeleton } from 'toolkit/chakra/skeleton'; import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand'; diff --git a/ui/opengradient/teeRegistry/placeholders.ts b/ui/opengradient/teeRegistry/placeholders.ts index 906dec2f1f..df3fe24422 100644 --- a/ui/opengradient/teeRegistry/placeholders.ts +++ b/ui/opengradient/teeRegistry/placeholders.ts @@ -1,4 +1,4 @@ -import type { TEERegistryStats, TEETypeSummary } from 'lib/opengradient/teeRegistry'; +import type { TEERegistryStats, TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry'; export const PLACEHOLDER_TEE_REGISTRY_STATS: TEERegistryStats = { totalTypes: 0, diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-base-view-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-base-view-1.png index 50eb706841..0a87f3254a 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-base-view-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_default-view-screen-xl-base-view-1.png differ diff --git a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png index 41408086a8..bade767e73 100644 Binary files a/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png and b/ui/pages/__screenshots__/Home.pw.tsx_default_mobile-base-view-1.png differ diff --git a/ui/pages/opengradient/TEERegistry.tsx b/ui/pages/opengradient/TEERegistry.tsx index 845b35c62e..8e2ddfe5de 100644 --- a/ui/pages/opengradient/TEERegistry.tsx +++ b/ui/pages/opengradient/TEERegistry.tsx @@ -4,8 +4,8 @@ import React from 'react'; import { route } from 'nextjs-routes'; -import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS } from 'lib/opengradient/teeRegistry'; -import type { TEENodeWithStatus } from 'lib/opengradient/teeRegistry'; +import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS } from 'lib/opengradient/contracts/teeRegistry'; +import type { TEENodeWithStatus } from 'lib/opengradient/contracts/teeRegistry'; import { Checkbox } from 'toolkit/chakra/checkbox'; import { Link } from 'toolkit/chakra/link'; import { Skeleton } from 'toolkit/chakra/skeleton';