Skip to content

Commit

Permalink
Add promoted operators (#203)
Browse files Browse the repository at this point in the history
  • Loading branch information
vincenthongzy authored Sep 5, 2024
1 parent 7f9bcc0 commit 1cf3f74
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 25 deletions.
25 changes: 25 additions & 0 deletions src/@services/RestakingDashboardService.js
Original file line number Diff line number Diff line change
@@ -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);
}
}
4 changes: 3 additions & 1 deletion src/@services/ServiceContext.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -21,5 +22,6 @@ const services = {
avsService: new AVSService(),
lrtService: new LRTService(),
operatorService: new OperatorService(),
eigenlayerService: new EigenLayerService()
eigenlayerService: new EigenLayerService(),
rdService: new RestakingDashboardService()
};
87 changes: 78 additions & 9 deletions src/avs/AVSDetailsOperatorsTab.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand All @@ -234,6 +251,48 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) {
[dispatch]
);

const renderPromotedOperators = useCallback(() => {
return state.promotedOperators?.map((operator, i) => (
<TableRow
className="size-16 cursor-pointer border-t border-outline bg-black/100 transition-colors hover:bg-default"
key={`promoted-operator-item-${i}`}
onClick={() =>
navigate(`/operators/${operator.address}`, {
state: { operator }
})
}
>
<TableCell className="p-4">
<div className="flex items-center gap-x-3">
<div className="flex size-5 items-center justify-center rounded-md bg-foreground-2 p-1 text-center text-xs text-content1">
ad
</div>
<ThirdPartyLogo
className="size-8 min-w-8"
url={operator.metadata?.logo}
/>
<span className="truncate">
{operator.metadata?.name || operator.address}
</span>
</div>
</TableCell>
<TableCell className="pe-8 text-end">
{tvl === 0
? 'N/A'
: `${((operator.strategiesTotal / tvl) * 100).toFixed(2)}%`}
</TableCell>
<TableCell className="pe-8 text-end">
<div>
{formatUSD(operator.strategiesTotal * state.promotedOperatorsRate)}
</div>
<div className="text-xs text-foreground-2">
{formatETH(operator.strategiesTotal)}
</div>
</TableCell>
</TableRow>
));
}, [navigate, state.promotedOperators, state.promotedOperatorsRate, tvl]);

return (
<div className="rd-box text-sm">
<div className="flex flex-col justify-between gap-y-4 p-4 lg:flex-row lg:items-center">
Expand Down Expand Up @@ -301,15 +360,9 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) {
TVL
</TableColumn>
</TableHeader>
<TableBody
emptyContent={
<div className="flex h-[40rem] flex-col items-center justify-center">
<span className="text-lg text-foreground-2">
No result found for {truncate(state.search ?? '')}
</span>
</div>
}
>
<TableBody>
{searchParams.get('page') === '1' && renderPromotedOperators()}

{(state.isTableLoading || isAVSLoading) &&
[...new Array(10)].map((_, i) => (
<TableRow className="border-t border-outline" key={i}>
Expand Down Expand Up @@ -372,6 +425,22 @@ function AVSOperatorsList({ address, avsError, isAVSLoading, tvl }) {
</TableRow>
))}

{!state.isTableLoading &&
!isAVSLoading &&
state.operators.length === 0 && (
<TableRow>
<TableCell colSpan={3}>
<div className="flex h-[40rem] flex-col items-center justify-center">
<span className="text-lg text-foreground-2">
No result found for {truncate(state.search ?? '')}
</span>
</div>
</TableCell>
<TableCell hidden />
<TableCell hidden />
</TableRow>
)}

{!state.isTableLoading &&
!isAVSLoading &&
state.operators.length > 0 &&
Expand Down
98 changes: 83 additions & 15 deletions src/operators/OperatorList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 });
Expand Down Expand Up @@ -145,6 +161,46 @@ export default function OperatorList() {
}
}, [dispatch, debouncedSearchTerm]);

const renderPromotedOperators = useCallback(() => {
return state.promotedOperators?.map((operator, i) => (
<TableRow
className="size-16 cursor-pointer border-t border-outline bg-black/100 transition-colors hover:bg-default"
key={`promoted-operator-item-${i}`}
onClick={() =>
navigate(`/operators/${operator.address}`, {
state: { operator }
})
}
>
<TableCell className="p-4">
<div className="flex items-center gap-x-3">
<div className="flex size-5 items-center justify-center rounded-md bg-foreground-2 p-1 text-center text-xs text-content1">
ad
</div>
<ThirdPartyLogo
className="size-8 min-w-8"
url={operator.metadata?.logo}
/>
<span className="truncate">
{operator.metadata?.name || operator.address}
</span>
</div>
</TableCell>
<TableCell className="pe-8 text-end">
{formatNumber(operator.stakerCount)}
</TableCell>
<TableCell className="pe-8 text-end">
<div>
{formatUSD(operator.strategiesTotal * state.promotedOperatorsRate)}
</div>
<div className="text-xs text-foreground-2">
{formatETH(operator.strategiesTotal)}
</div>
</TableCell>
</TableRow>
));
}, [navigate, state.promotedOperators, state.promotedOperatorsRate]);

return (
<div className="flex h-full flex-col">
<div className="font-display text-3xl font-medium text-foreground-1">
Expand Down Expand Up @@ -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
Expand All @@ -212,19 +272,9 @@ export default function OperatorList() {
</TableColumn>
)}
</TableHeader>
<TableBody
emptyContent={
<div className="flex flex-col items-center justify-center">
<span className="text-lg text-foreground-2">
No operator found for &quot;
{debouncedSearchTerm.length > 42
? `${debouncedSearchTerm.substring(0, 42)}...`
: debouncedSearchTerm}
&quot;
</span>
</div>
}
>
<TableBody>
{searchParams.get('page') === '1' && renderPromotedOperators()}

{state.isFetchingData
? [...Array(10)].map((_, i) => (
<TableRow className="border-t border-outline" key={i}>
Expand Down Expand Up @@ -276,6 +326,24 @@ export default function OperatorList() {
</TableCell>
</TableRow>
))}

{!state.isFetchingData && state.operators.length === 0 && (
<TableRow>
<TableCell colSpan={3}>
<div className="flex flex-col items-center justify-center">
<span className="text-lg text-foreground-2">
No operator found for &quot;
{debouncedSearchTerm.length > 42
? `${debouncedSearchTerm.substring(0, 42)}...`
: debouncedSearchTerm}
&quot;
</span>
</div>
</TableCell>
<TableCell hidden />
<TableCell hidden />
</TableRow>
)}
</TableBody>
</Table>
{state.totalPages > 1 && (
Expand Down

0 comments on commit 1cf3f74

Please sign in to comment.