Skip to content

Commit e733f1d

Browse files
authored
Load TEE registry through API route (#10)
1 parent a496362 commit e733f1d

10 files changed

Lines changed: 164 additions & 129 deletions

File tree

lib/opengradient/contracts/teeRegistry.ts

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,14 @@
11
import { ethers } from 'ethers';
22
import type { Address } from 'viem';
33

4+
import type { TEENodeWithStatus, TEEInfo, TEERegistryOverview, TEETypeInfo, TEETypeSummary } from 'lib/opengradient/teeRegistry';
5+
import { TEE_REGISTRY_ADDRESS } from 'lib/opengradient/teeRegistry';
6+
47
import TEERegistryAbi from './abi/TEERegistry.json';
58
import { ethDevnetProvider } from './providers';
69

7-
export const TEE_REGISTRY_ADDRESS = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248';
8-
910
const contract = new ethers.Contract(TEE_REGISTRY_ADDRESS, TEERegistryAbi, ethDevnetProvider);
1011

11-
export interface TEETypeInfo {
12-
typeId: number;
13-
name: string;
14-
addedAt: bigint;
15-
}
16-
17-
export interface TEEInfo {
18-
teeId: string;
19-
owner: Address;
20-
paymentAddress: Address;
21-
endpoint: string;
22-
publicKey: string;
23-
tlsCertificate: string;
24-
pcrHash: string;
25-
teeType: number;
26-
enabled: boolean;
27-
registeredAt: bigint;
28-
lastHeartbeatAt: bigint;
29-
}
30-
31-
export interface TEENodeWithStatus extends TEEInfo {
32-
isActive: boolean;
33-
}
34-
35-
export interface TEETypeSummary {
36-
typeId: number;
37-
name: string;
38-
totalNodes: number;
39-
enabledNodes: number;
40-
activeNodes: number;
41-
approvedPCRs: number;
42-
addedAt: bigint;
43-
}
44-
45-
export interface TEERegistryStats {
46-
totalTypes: number;
47-
totalNodes: number;
48-
activeNodes: number;
49-
enabledNodes: number;
50-
approvedPCRs: number;
51-
}
52-
5312
export const getTEETypes = async(): Promise<Array<TEETypeInfo>> => {
5413
const [ typeIds, infos ] = await contract.getTEETypes();
5514

@@ -109,11 +68,7 @@ export const getHeartbeatMaxAge = async(): Promise<bigint> => {
10968
/**
11069
* Fetch full registry overview: types, nodes per type with status, and global stats.
11170
*/
112-
export const getTEERegistryOverview = async(): Promise<{
113-
types: Array<TEETypeSummary>;
114-
stats: TEERegistryStats;
115-
nodesByType: Record<number, Array<TEENodeWithStatus>>;
116-
}> => {
71+
export const getTEERegistryOverviewFromContract = async(): Promise<TEERegistryOverview> => {
11772
// 1. Get all TEE types
11873
const types = await getTEETypes();
11974

@@ -186,5 +141,3 @@ export const getTEERegistryOverview = async(): Promise<{
186141
nodesByType,
187142
};
188143
};
189-
190-
export const TEE_REGISTRY_QUERY_KEY = [ 'opengradient', 'teeRegistry' ];

lib/opengradient/teeRegistry.ts

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { Address } from 'viem';
2+
3+
export const TEE_REGISTRY_ADDRESS = '0x4e72238852f3c918f4E4e57AeC9280dDB0c80248';
4+
5+
export interface TEETypeInfo {
6+
typeId: number;
7+
name: string;
8+
addedAt: bigint;
9+
}
10+
11+
export interface TEEInfo {
12+
teeId: string;
13+
owner: Address;
14+
paymentAddress: Address;
15+
endpoint: string;
16+
publicKey: string;
17+
tlsCertificate: string;
18+
pcrHash: string;
19+
teeType: number;
20+
enabled: boolean;
21+
registeredAt: bigint;
22+
lastHeartbeatAt: bigint;
23+
}
24+
25+
export interface TEENodeWithStatus extends TEEInfo {
26+
isActive: boolean;
27+
}
28+
29+
export interface TEETypeSummary {
30+
typeId: number;
31+
name: string;
32+
totalNodes: number;
33+
enabledNodes: number;
34+
activeNodes: number;
35+
approvedPCRs: number;
36+
addedAt: bigint;
37+
}
38+
39+
export interface TEERegistryStats {
40+
totalTypes: number;
41+
totalNodes: number;
42+
activeNodes: number;
43+
enabledNodes: number;
44+
approvedPCRs: number;
45+
}
46+
47+
export type TEERegistryOverview = {
48+
types: Array<TEETypeSummary>;
49+
stats: TEERegistryStats;
50+
nodesByType: Record<number, Array<TEENodeWithStatus>>;
51+
};
52+
53+
export type SerializedTEERegistryOverview = {
54+
types: Array<Omit<TEETypeSummary, 'addedAt'> & { addedAt: string }>;
55+
stats: TEERegistryStats;
56+
nodesByType: Record<string, Array<Omit<TEENodeWithStatus, 'registeredAt' | 'lastHeartbeatAt'> & {
57+
registeredAt: string;
58+
lastHeartbeatAt: string;
59+
}>>;
60+
};
61+
62+
export const serializeTEERegistryOverview = (overview: TEERegistryOverview): SerializedTEERegistryOverview => ({
63+
types: overview.types.map((type) => ({
64+
...type,
65+
addedAt: type.addedAt.toString(),
66+
})),
67+
stats: overview.stats,
68+
nodesByType: Object.fromEntries(
69+
Object.entries(overview.nodesByType).map(([ typeId, nodes ]) => [
70+
typeId,
71+
nodes.map((node) => ({
72+
...node,
73+
registeredAt: node.registeredAt.toString(),
74+
lastHeartbeatAt: node.lastHeartbeatAt.toString(),
75+
})),
76+
]),
77+
),
78+
});
79+
80+
export const parseTEERegistryOverview = (overview: SerializedTEERegistryOverview): TEERegistryOverview => ({
81+
types: overview.types.map((type) => ({
82+
...type,
83+
addedAt: BigInt(type.addedAt),
84+
})),
85+
stats: overview.stats,
86+
nodesByType: Object.fromEntries(
87+
Object.entries(overview.nodesByType).map(([ typeId, nodes ]) => [
88+
Number(typeId),
89+
nodes.map((node) => ({
90+
...node,
91+
registeredAt: BigInt(node.registeredAt),
92+
lastHeartbeatAt: BigInt(node.lastHeartbeatAt),
93+
})),
94+
]),
95+
),
96+
});
97+
98+
export const getTEERegistryOverview = async(): Promise<TEERegistryOverview> => {
99+
const response = await fetch('/api/opengradient/tee-registry');
100+
if (!response.ok) {
101+
throw new Error(`Failed to load TEE registry overview: ${ response.status }`);
102+
}
103+
104+
return parseTEERegistryOverview(await response.json() as SerializedTEERegistryOverview);
105+
};
106+
107+
export const TEE_REGISTRY_QUERY_KEY = [ 'opengradient', 'teeRegistry' ];
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { NextApiRequest, NextApiResponse } from 'next';
2+
3+
import { getTEERegistryOverviewFromContract } from 'lib/opengradient/contracts/teeRegistry';
4+
import { serializeTEERegistryOverview } from 'lib/opengradient/teeRegistry';
5+
6+
export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
7+
try {
8+
const overview = await getTEERegistryOverviewFromContract();
9+
res.status(200).json(serializeTEERegistryOverview(overview));
10+
} catch (error) {
11+
res.status(500).json({
12+
error: error instanceof Error ? error.message : 'Failed to load TEE registry overview',
13+
});
14+
}
15+
}

ui/home/HeroBanner.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React from 'react';
55
import { route } from 'nextjs-routes';
66

77
import useApiQuery from 'lib/api/useApiQuery';
8-
import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY } from 'lib/opengradient/contracts/teeRegistry';
8+
import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY } from 'lib/opengradient/teeRegistry';
99
import { HOMEPAGE_STATS, HOMEPAGE_STATS_MICROSERVICE } from 'stubs/stats';
1010
import { LinkBox, LinkOverlay } from 'toolkit/chakra/link';
1111
import { Skeleton } from 'toolkit/chakra/skeleton';
@@ -319,7 +319,7 @@ const HeroBanner = () => {
319319
label="TEE Operators"
320320
iconName="nft_shield"
321321
loading={ teeRegistryQuery.isPlaceholderData }
322-
value={ `${ teeStats.activeNodes }/${ teeStats.enabledNodes }` }
322+
value={ teeStats.activeNodes.toLocaleString() }
323323
/>
324324
<MetricCard
325325
href={ route({ pathname: '/address/[hash]', query: { hash: settlementContractAddress } }) }

ui/home/TrustedExecution.tsx

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import React from 'react';
55
import { route } from 'nextjs-routes';
66

77
import dayjs from 'lib/date/dayjs';
8-
import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS, type TEENodeWithStatus } from 'lib/opengradient/contracts/teeRegistry';
8+
import { getTEERegistryOverview, TEE_REGISTRY_QUERY_KEY, TEE_REGISTRY_ADDRESS, type TEENodeWithStatus } from 'lib/opengradient/teeRegistry';
99
import { Link } from 'toolkit/chakra/link';
1010
import { Skeleton } from 'toolkit/chakra/skeleton';
1111
import { PLACEHOLDER_TEE_REGISTRY_STATS, PLACEHOLDER_TEE_TYPES } from 'ui/opengradient/teeRegistry/placeholders';
@@ -138,10 +138,11 @@ const TrustedExecution = () => {
138138
const types = query.data?.types ?? PLACEHOLDER_TEE_TYPES;
139139
const nodes = React.useMemo(() => {
140140
const nodesByType = query.data?.nodesByType ?? {};
141-
return Object.values(nodesByType).flat().sort((a, b) => Number(b.lastHeartbeatAt - a.lastHeartbeatAt));
141+
return Object.values(nodesByType).flat()
142+
.filter((node) => node.isActive)
143+
.sort((a, b) => Number(b.lastHeartbeatAt - a.lastHeartbeatAt));
142144
}, [ query.data?.nodesByType ]);
143-
const primaryType = types[0] ?? PLACEHOLDER_TEE_TYPES[0];
144-
const visibleNodes = nodes.slice(0, 3);
145+
const primaryType = types[0];
145146
const isLoading = query.isPlaceholderData;
146147

147148
return (
@@ -239,13 +240,13 @@ const TrustedExecution = () => {
239240
<Grid templateColumns={{ base: '1fr', md: 'repeat(3, minmax(0, 1fr))' }} gap={ 3 } mb={ 4 }>
240241
<RegistryMetric
241242
label="Active TEEs"
242-
value={ `${ stats.activeNodes }/${ stats.enabledNodes }` }
243+
value={ stats.activeNodes.toLocaleString() }
243244
helper="Heartbeat-valid operators"
244245
loading={ isLoading }
245246
/>
246247
<RegistryMetric
247248
label="Execution type"
248-
value={ primaryType.name }
249+
value={ primaryType?.name ?? 'Loading' }
249250
helper="Registered AI workload class"
250251
loading={ isLoading }
251252
/>
@@ -270,32 +271,23 @@ const TrustedExecution = () => {
270271
</Text>
271272
<Skeleton loading={ isLoading } w="fit-content">
272273
<Text mt={ 3 } fontFamily={ fonts.sans } fontSize="18px" fontWeight={ 600 } color={ text.primary }>
273-
{ primaryType.name }
274+
{ primaryType?.name ?? 'Loading' }
274275
</Text>
275276
</Skeleton>
276277
<Grid templateColumns="repeat(3, minmax(0, 1fr))" gap={ 3 } mt={ 4 }>
277278
<Box>
278279
<Text fontFamily={ fonts.mono } fontSize="9px" color={ text.muted } textTransform="uppercase" letterSpacing="0.08em">Active</Text>
279-
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType.activeNodes }/{ primaryType.totalNodes }</Text>
280+
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType?.activeNodes.toLocaleString() ?? '0' }</Text>
280281
</Box>
281282
<Box>
282283
<Text fontFamily={ fonts.mono } fontSize="9px" color={ text.muted } textTransform="uppercase" letterSpacing="0.08em">Enabled</Text>
283-
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType.enabledNodes }</Text>
284+
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType?.enabledNodes.toLocaleString() ?? '0' }</Text>
284285
</Box>
285286
<Box>
286287
<Text fontFamily={ fonts.mono } fontSize="9px" color={ text.muted } textTransform="uppercase" letterSpacing="0.08em">PCRs</Text>
287-
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType.approvedPCRs }</Text>
288+
<Text mt={ 1 } fontFamily={ fonts.mono } fontSize="14px" color={ text.primary }>{ primaryType?.approvedPCRs.toLocaleString() ?? '0' }</Text>
288289
</Box>
289290
</Grid>
290-
<Box mt={ 4 } h="3px" borderRadius="2px" bg={{ _light: 'rgba(36, 188, 227, 0.12)', _dark: 'rgba(36, 188, 227, 0.12)' }} overflow="hidden">
291-
<Box
292-
h="100%"
293-
w={ primaryType.totalNodes > 0 ? `${ Math.round((primaryType.activeNodes / primaryType.totalNodes) * 100) }%` : '0' }
294-
minW={ primaryType.activeNodes > 0 ? '18px' : '0' }
295-
bg={ colors.cyan }
296-
borderRadius="2px"
297-
/>
298-
</Box>
299291
</Box>
300292

301293
<Box
@@ -316,7 +308,7 @@ const TrustedExecution = () => {
316308
</Text>
317309
</Flex>
318310
<VStack align="stretch" gap={ 0 }>
319-
{ visibleNodes.length > 0 ? visibleNodes.map((node) => (
311+
{ nodes.length > 0 ? nodes.map((node) => (
320312
<NodePreview key={ node.teeId } node={ node } loading={ isLoading }/>
321313
)) : (
322314
<Text py={ 5 } fontSize="13px" color={ text.muted }>

ui/opengradient/teeRegistry/TEENodeDetailDrawer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Box, Flex, Text, VStack } from '@chakra-ui/react';
22
import React from 'react';
33

44
import dayjs from 'lib/date/dayjs';
5-
import type { TEENodeWithStatus } from 'lib/opengradient/contracts/teeRegistry';
5+
import type { TEENodeWithStatus } from 'lib/opengradient/teeRegistry';
66
import { DrawerBackdrop, DrawerBody, DrawerCloseTrigger, DrawerContent, DrawerHeader, DrawerRoot, DrawerTitle } from 'toolkit/chakra/drawer';
77
import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand';
88
import AddressEntity from 'ui/shared/entities/address/AddressEntity';

ui/opengradient/teeRegistry/TEENodesTable.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Box, Flex, Text } from '@chakra-ui/react';
22
import React from 'react';
33

44
import dayjs from 'lib/date/dayjs';
5-
import type { TEENodeWithStatus, TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry';
5+
import type { TEENodeWithStatus, TEETypeSummary } from 'lib/opengradient/teeRegistry';
66
import { Skeleton } from 'toolkit/chakra/skeleton';
77
import { TableBody, TableCell, TableColumnHeader, TableHeaderSticky, TableRoot, TableRow } from 'toolkit/chakra/table';
88
import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand';

ui/opengradient/teeRegistry/TEETypeCard.tsx

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Box, Flex, Grid, Text } from '@chakra-ui/react';
22
import React from 'react';
33

4-
import type { TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry';
4+
import type { TEETypeSummary } from 'lib/opengradient/teeRegistry';
55
import { Skeleton } from 'toolkit/chakra/skeleton';
66
import { OPENGRADIENT_BRAND } from 'ui/opengradient/brand';
77

@@ -46,8 +46,6 @@ const TEETypeCard = ({ type, isSelected, isLoading, onClick }: Props) => {
4646
onClick(type.typeId);
4747
}, [ onClick, type.typeId ]);
4848

49-
const activePct = type.totalNodes > 0 ? Math.round((type.activeNodes / type.totalNodes) * 100) : 0;
50-
5149
return (
5250
<Flex
5351
as="button"
@@ -101,26 +99,10 @@ const TEETypeCard = ({ type, isSelected, isLoading, onClick }: Props) => {
10199
</Flex>
102100

103101
<Grid templateColumns="repeat(3, minmax(0, 1fr))" gap={ 3 } mb={ 3 }>
104-
<Metric label="Active" value={ `${ type.activeNodes }/${ type.totalNodes }` } isLoading={ isLoading }/>
102+
<Metric label="Active" value={ type.activeNodes.toLocaleString() } isLoading={ isLoading }/>
105103
<Metric label="Enabled" value={ type.enabledNodes.toLocaleString() } isLoading={ isLoading }/>
106104
<Metric label="PCRs" value={ type.approvedPCRs.toLocaleString() } isLoading={ isLoading }/>
107105
</Grid>
108-
109-
<Box
110-
h="3px"
111-
borderRadius="2px"
112-
bg={{ _light: 'rgba(36, 188, 227, 0.12)', _dark: 'rgba(36, 188, 227, 0.12)' }}
113-
overflow="hidden"
114-
>
115-
<Box
116-
h="100%"
117-
w={ `${ activePct }%` }
118-
minW={ activePct > 0 ? '18px' : '0' }
119-
bg={ colors.cyan }
120-
borderRadius="2px"
121-
transition="width 0.2s ease"
122-
/>
123-
</Box>
124106
</Flex>
125107
);
126108
};
Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
1-
import type { TEERegistryStats, TEETypeSummary } from 'lib/opengradient/contracts/teeRegistry';
1+
import type { TEERegistryStats, TEETypeSummary } from 'lib/opengradient/teeRegistry';
22

33
export const PLACEHOLDER_TEE_REGISTRY_STATS: TEERegistryStats = {
4-
totalTypes: 3,
5-
totalNodes: 12,
6-
activeNodes: 8,
7-
enabledNodes: 10,
8-
approvedPCRs: 5,
4+
totalTypes: 0,
5+
totalNodes: 0,
6+
activeNodes: 0,
7+
enabledNodes: 0,
8+
approvedPCRs: 0,
99
};
1010

11-
export const PLACEHOLDER_TEE_TYPES: Array<TEETypeSummary> = [
12-
{ typeId: 0, name: 'LLM Inference', totalNodes: 5, enabledNodes: 4, activeNodes: 3, approvedPCRs: 2, addedAt: BigInt(0) },
13-
{ typeId: 1, name: 'Agent Execution', totalNodes: 4, enabledNodes: 3, activeNodes: 3, approvedPCRs: 2, addedAt: BigInt(0) },
14-
{ typeId: 2, name: 'Model Training', totalNodes: 3, enabledNodes: 3, activeNodes: 2, approvedPCRs: 1, addedAt: BigInt(0) },
15-
];
11+
export const PLACEHOLDER_TEE_TYPES: Array<TEETypeSummary> = [];

0 commit comments

Comments
 (0)