diff --git a/src/@services/RestakingDashboardService.js b/src/@services/RestakingDashboardService.js new file mode 100644 index 00000000..0bb26240 --- /dev/null +++ b/src/@services/RestakingDashboardService.js @@ -0,0 +1,25 @@ +import BaseService from './BaseService'; + +export default class RestakingDashboardService extends BaseService { + async getPromotedOperators() { + const response = await BaseService._get('/rd/promotion/operators'); + + if (response.ok) { + return await response.json(); + } + + throw await this._createError(response); + } + + async getAVSPromotedOperators(address) { + const response = await BaseService._get( + `/rd/promotion/avs/${address}/operators` + ); + + if (response.ok) { + return await response.json(); + } + + throw await this._createError(response); + } +} diff --git a/src/@services/ServiceContext.jsx b/src/@services/ServiceContext.jsx index b60c5efc..fc2d3089 100644 --- a/src/@services/ServiceContext.jsx +++ b/src/@services/ServiceContext.jsx @@ -3,6 +3,7 @@ import AVSService from './AVSService'; import EigenLayerService from './EigenLayerService'; import LRTService from './LRTService'; import OperatorService from './OperatorService'; +import RestakingDashboardService from './RestakingDashboardService'; export const ServiceContext = createContext(); @@ -21,5 +22,6 @@ const services = { avsService: new AVSService(), lrtService: new LRTService(), operatorService: new OperatorService(), - eigenlayerService: new EigenLayerService() + eigenlayerService: new EigenLayerService(), + rdService: new RestakingDashboardService() }; diff --git a/src/avs/AVSDetailsOperatorsTab.jsx b/src/avs/AVSDetailsOperatorsTab.jsx index 675dd823..e809bd1b 100644 --- a/src/avs/AVSDetailsOperatorsTab.jsx +++ b/src/avs/AVSDetailsOperatorsTab.jsx @@ -105,11 +105,14 @@ export default function AVSDetailsOperatorsTab({ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) { const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); + const { rdService } = useServices(); const [state, dispatch] = useMutativeReducer(reduceState, { currentRate: 1, error: undefined, operators: [], + promotedOperators: [], + promotedOperatorsRate: 1, isInputTouched: false, isTableLoading: true, totalPages: undefined, @@ -210,6 +213,20 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) { state.sort ]); + useEffect(() => { + (async () => { + try { + const response = await rdService.getAVSPromotedOperators(address); + dispatch({ + promotedOperators: response.results, + promotedOperatorsRate: response.rate + }); + } catch (e) { + // swallow error because we don't need to show error message + } + })(); + }, [address, dispatch, rdService]); + const handleSort = useCallback( e => { let sort = e.column; @@ -234,6 +251,48 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) { [dispatch] ); + const renderPromotedOperators = useCallback(() => { + return state.promotedOperators?.map((operator, i) => ( + + navigate(`/operators/${operator.address}`, { + state: { operator } + }) + } + > + +
+
+ ad +
+ + + {operator.metadata?.name || operator.address} + +
+
+ + {tvl === 0 + ? 'N/A' + : `${((operator.strategiesTotal / tvl) * 100).toFixed(2)}%`} + + +
+ {formatUSD(operator.strategiesTotal * state.promotedOperatorsRate)} +
+
+ {formatETH(operator.strategiesTotal)} +
+
+
+ )); + }, [navigate, state.promotedOperators, state.promotedOperatorsRate, tvl]); + return (
@@ -301,15 +360,9 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) { TVL - - - No result found for {truncate(state.search ?? '')} - -
- } - > + + {searchParams.get('page') === '1' && renderPromotedOperators()} + {(state.isTableLoading || isAVSLoading) && [...new Array(10)].map((_, i) => ( @@ -372,6 +425,22 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) { ))} + {!state.isTableLoading && + !isAVSLoading && + state.operators.length === 0 && ( + + +
+ + No result found for {truncate(state.search ?? '')} + +
+
+
+ )} + {!state.isTableLoading && !isAVSLoading && state.operators.length > 0 && diff --git a/src/operators/OperatorList.jsx b/src/operators/OperatorList.jsx index 82dcc45b..11d1bfa4 100644 --- a/src/operators/OperatorList.jsx +++ b/src/operators/OperatorList.jsx @@ -40,12 +40,14 @@ const columns = [ ]; export default function OperatorList() { - const { operatorService } = useServices(); + const { operatorService, rdService } = useServices(); const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const abortController = useRef(null); const [state, dispatch] = useMutativeReducer(reduceState, { operators: [], + promotedOperators: [], + promotedOperatorsRate: 1, isFetchingData: false, searchTerm: searchParams.get('search'), error: null, @@ -100,6 +102,20 @@ export default function OperatorList() { [operatorService, dispatch] ); + useEffect(() => { + (async () => { + try { + const response = await rdService.getPromotedOperators(); + dispatch({ + promotedOperators: response.results, + promotedOperatorsRate: response.rate + }); + } catch (e) { + // swallow error because we don't need to show error message + } + })(); + }, [dispatch, rdService]); + const handlePageClick = useCallback( page => { setSearchParams({ page }); @@ -145,6 +161,46 @@ export default function OperatorList() { } }, [dispatch, debouncedSearchTerm]); + const renderPromotedOperators = useCallback(() => { + return state.promotedOperators?.map((operator, i) => ( + + navigate(`/operators/${operator.address}`, { + state: { operator } + }) + } + > + +
+
+ ad +
+ + + {operator.metadata?.name || operator.address} + +
+
+ + {formatNumber(operator.stakerCount)} + + +
+ {formatUSD(operator.strategiesTotal * state.promotedOperatorsRate)} +
+
+ {formatETH(operator.strategiesTotal)} +
+
+
+ )); + }, [navigate, state.promotedOperators, state.promotedOperatorsRate]); + return (
@@ -195,7 +251,11 @@ export default function OperatorList() { table: state.operators?.length === 0 ? 'h-full' : null, thead: '[&>tr:last-child]:hidden' }} - hideHeader={!state.isFetchingData && state.operators.length == 0} + hideHeader={ + state.promotedOperators.length === 0 && + !state.isFetchingData && + state.operators.length == 0 + } layout="fixed" onSortChange={e => dispatch({ sortDescriptor: e })} removeWrapper @@ -212,19 +272,9 @@ export default function OperatorList() { )} - - - No operator found for " - {debouncedSearchTerm.length > 42 - ? `${debouncedSearchTerm.substring(0, 42)}...` - : debouncedSearchTerm} - " - -
- } - > + + {searchParams.get('page') === '1' && renderPromotedOperators()} + {state.isFetchingData ? [...Array(10)].map((_, i) => ( @@ -276,6 +326,24 @@ export default function OperatorList() { ))} + + {!state.isFetchingData && state.operators.length === 0 && ( + + +
+ + No operator found for " + {debouncedSearchTerm.length > 42 + ? `${debouncedSearchTerm.substring(0, 42)}...` + : debouncedSearchTerm} + " + +
+
+
+ )}
{state.totalPages > 1 && (