Skip to content

Commit

Permalink
✨ Implement ZkSync Ignite incentives program (#2305)
Browse files Browse the repository at this point in the history
Co-authored-by: Nandy Bâ <[email protected]>
  • Loading branch information
MartinGbz and NandyBa authored Jan 6, 2025
1 parent 8b89cec commit 5147f80
Show file tree
Hide file tree
Showing 22 changed files with 419 additions and 133 deletions.
1 change: 1 addition & 0 deletions public/icons/other/zksync-ignite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions src/components/incentives/IncentivesButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DotsHorizontalIcon } from '@heroicons/react/solid';
import { Box, SvgIcon, Typography } from '@mui/material';
import { useState } from 'react';
import { useMeritIncentives } from 'src/hooks/useMeritIncentives';
import { useZkSyncIgniteIncentives } from 'src/hooks/useZkSyncIgniteIncentives';
import { useRootStore } from 'src/store/root';
import { DASHBOARD } from 'src/utils/mixPanelEvents';

Expand All @@ -13,6 +14,7 @@ import { FormattedNumber } from '../primitives/FormattedNumber';
import { TokenIcon } from '../primitives/TokenIcon';
import { getSymbolMap, IncentivesTooltipContent } from './IncentivesTooltipContent';
import { MeritIncentivesTooltipContent } from './MeritIncentivesTooltipContent';
import { ZkSyncIgniteIncentivesTooltipContent } from './ZkSyncIgniteIncentivesTooltipContent';

interface IncentivesButtonProps {
symbol: string;
Expand Down Expand Up @@ -61,6 +63,35 @@ export const MeritIncentivesButton = (params: {
);
};

export const ZkIgniteIncentivesButton = (params: {
market: string;
rewardedAsset?: string;
protocolAction?: ProtocolAction;
}) => {
const [open, setOpen] = useState(false);
const { data: zkSyncIgniteIncentives } = useZkSyncIgniteIncentives(params);

if (!zkSyncIgniteIncentives) {
return null;
}

return (
<ContentWithTooltip
tooltipContent={
<ZkSyncIgniteIncentivesTooltipContent zkSyncIgniteIncentives={zkSyncIgniteIncentives} />
}
withoutHover
setOpen={setOpen}
open={open}
>
<Content
incentives={[zkSyncIgniteIncentives]}
incentivesNetAPR={+zkSyncIgniteIncentives.incentiveAPR}
/>
</ContentWithTooltip>
);
};

export const IncentivesButton = ({ incentives, symbol, displayBlank }: IncentivesButtonProps) => {
const [open, setOpen] = useState(false);

Expand Down
39 changes: 35 additions & 4 deletions src/components/incentives/IncentivesCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,44 @@
import { ProtocolAction } from '@aave/contract-helpers';
import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
import { Box } from '@mui/material';
import { Box, useMediaQuery } from '@mui/material';
import { ReactNode } from 'react';

import { FormattedNumber } from '../primitives/FormattedNumber';
import { NoData } from '../primitives/NoData';
import { IncentivesButton } from './IncentivesButton';
import {
IncentivesButton,
MeritIncentivesButton,
ZkIgniteIncentivesButton,
} from './IncentivesButton';

interface IncentivesCardProps {
symbol: string;
value: string | number;
incentives?: ReserveIncentiveResponse[];
address?: string;
variant?: 'main14' | 'main16' | 'secondary14';
symbolsVariant?: 'secondary14' | 'secondary16';
align?: 'center' | 'flex-end';
color?: string;
tooltip?: ReactNode;
market: string;
protocolAction?: ProtocolAction;
}

export const IncentivesCard = ({
symbol,
value,
incentives,
address,
variant = 'secondary14',
symbolsVariant,
align,
color,
tooltip,
market,
protocolAction,
}: IncentivesCardProps) => {
const isTableChangedToCards = useMediaQuery('(max-width:1125px)');
return (
<Box
sx={{
Expand All @@ -53,8 +65,27 @@ export const IncentivesCard = ({
) : (
<NoData variant={variant} color={color || 'text.secondary'} />
)}

<IncentivesButton incentives={incentives} symbol={symbol} />
<Box
sx={
isTableChangedToCards
? { display: 'flex', flexDirection: 'column', justifyContent: 'center', gap: '4px' }
: {
display: 'flex',
justifyContent: 'center',
gap: '4px',
flexWrap: 'wrap',
flex: '0 0 50%', // 2 items per row
}
}
>
<IncentivesButton incentives={incentives} symbol={symbol} />
<MeritIncentivesButton symbol={symbol} market={market} protocolAction={protocolAction} />
<ZkIgniteIncentivesButton
market={market}
rewardedAsset={address}
protocolAction={protocolAction}
/>
</Box>
</Box>
);
};
105 changes: 105 additions & 0 deletions src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { Trans } from '@lingui/macro';
import { Box, Typography } from '@mui/material';
import { ExtendedReserveIncentiveResponse } from 'src/hooks/useMeritIncentives';

import { FormattedNumber } from '../primitives/FormattedNumber';
import { Link } from '../primitives/Link';
import { Row } from '../primitives/Row';
import { TokenIcon } from '../primitives/TokenIcon';
import { getSymbolMap } from './IncentivesTooltipContent';

export const ZkSyncIgniteIncentivesTooltipContent = ({
zkSyncIgniteIncentives,
}: {
zkSyncIgniteIncentives: ExtendedReserveIncentiveResponse;
}) => {
const typographyVariant = 'secondary12';

const zkSyncIgniteIncentivesFormatted = getSymbolMap(zkSyncIgniteIncentives);

return (
<Box
sx={{
display: 'flex',
justifyContent: 'center',
alignItems: 'start',
flexDirection: 'column',
}}
>
<img src={`/icons/other/zksync-ignite.svg`} width="100px" height="40px" alt="" />

<Typography variant="caption" color="text.primary" mb={3}>
<Trans>Eligible for the ZKSync Ignite program.</Trans>
</Typography>

<Typography variant="caption" color="text.secondary" mb={3}>
<Trans>
This is a program initiated and implemented by the decentralised ZKSync community. Aave
Labs does not guarantee the program and accepts no liability.
</Trans>{' '}
<Link
href={'https://zksyncignite.xyz/'}
sx={{ textDecoration: 'underline' }}
variant="caption"
color="text.secondary"
>
Learn more
</Link>
</Typography>

<Typography variant="caption" color="text.secondary" mb={3}>
<Trans>ZKSync Ignite Program rewards are claimed through the</Trans>{' '}
<Link
href="https://app.zksyncignite.xyz/users/"
sx={{ textDecoration: 'underline' }}
variant="caption"
color="text.secondary"
>
official app
</Link>
{'.'}
</Typography>
{zkSyncIgniteIncentives.customMessage ? (
<Typography variant="caption" color="text.strong" mb={3}>
<Trans>{zkSyncIgniteIncentives.customMessage}</Trans>
</Typography>
) : null}

<Box sx={{ width: '100%' }}>
<Row
height={32}
caption={
<Box
sx={{
display: 'flex',
alignItems: 'center',
mb: 0,
}}
>
<TokenIcon
aToken={zkSyncIgniteIncentivesFormatted.aToken}
symbol={zkSyncIgniteIncentivesFormatted.tokenIconSymbol}
sx={{ fontSize: '20px', mr: 1 }}
/>
<Typography variant={typographyVariant}>
{zkSyncIgniteIncentivesFormatted.symbol}
</Typography>
</Box>
}
width="100%"
>
<Box sx={{ display: 'inline-flex', alignItems: 'center' }}>
<FormattedNumber
value={+zkSyncIgniteIncentivesFormatted.incentiveAPR}
percent
variant={typographyVariant}
/>
<Typography variant={typographyVariant} sx={{ ml: 1 }}>
<Trans>APR</Trans>
</Typography>
</Box>
</Row>
</Box>
</Box>
);
};
120 changes: 120 additions & 0 deletions src/hooks/useZkSyncIgniteIncentives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { ProtocolAction } from '@aave/contract-helpers';
import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
import { AaveV3ZkSync } from '@bgd-labs/aave-address-book';
import { useQuery } from '@tanstack/react-query';
import { CustomMarket } from 'src/ui-config/marketsConfig';
import { Address } from 'viem';

enum OpportunityAction {
LEND = 'LEND',
BORROW = 'BORROW',
}

enum OpportunityStatus {
LIVE = 'LIVE',
PAST = 'PAST',
UPCOMING = 'UPCOMING',
}

type MerklOpportunity = {
chainId: number;
type: string;
identifier: Address;
name: string;
status: OpportunityStatus;
action: OpportunityAction;
tvl: number;
apr: number;
dailyRewards: number;
tags: [];
id: string;
tokens: [
{
id: string;
name: string;
chainId: number;
address: Address;
decimals: number;
icon: string;
verified: boolean;
isTest: boolean;
price: number;
symbol: string;
}
];
};

export type ExtendedReserveIncentiveResponse = ReserveIncentiveResponse & {
customMessage: string;
customForumLink: string;
};

const url = 'https://api.merkl.xyz/v4/opportunities?tags=zksync&mainProtocolId=aave'; // Merkl API for ZK Ignite opportunities

const rewardToken = AaveV3ZkSync.ASSETS.ZK.UNDERLYING;
const rewardTokenSymbol = 'ZK';

const checkOpportunityAction = (
opportunityAction: OpportunityAction,
protocolAction: ProtocolAction
) => {
switch (opportunityAction) {
case OpportunityAction.LEND:
return protocolAction === ProtocolAction.supply;
case OpportunityAction.BORROW:
return protocolAction === ProtocolAction.borrow;
default:
return false;
}
};

export const useZkSyncIgniteIncentives = ({
market,
rewardedAsset,
protocolAction,
}: {
market: string;
rewardedAsset?: string;
protocolAction?: ProtocolAction;
}) => {
return useQuery({
queryFn: async () => {
if (market === CustomMarket.proto_zksync_v3) {
const response = await fetch(url);
const merklOpportunities: MerklOpportunity[] = await response.json();
return merklOpportunities;
} else {
return [];
}
},
queryKey: ['zkIgniteIncentives', market],
staleTime: 1000 * 60 * 5,
select: (merklOpportunities) => {
const opportunities = merklOpportunities.filter(
(opportunitiy) =>
rewardedAsset &&
opportunitiy.identifier.toLowerCase() === rewardedAsset.toLowerCase() &&
protocolAction &&
checkOpportunityAction(opportunitiy.action, protocolAction)
);

if (opportunities.length === 0) {
return null;
}

const opportunity = opportunities[0];

if (opportunity.status !== OpportunityStatus.LIVE) {
return null;
}

const apr = opportunity.apr / 100;

return {
incentiveAPR: apr.toString(),
rewardTokenAddress: rewardToken,
rewardTokenSymbol: rewardTokenSymbol,
} as ExtendedReserveIncentiveResponse;
},
});
};
2 changes: 1 addition & 1 deletion src/locales/el/messages.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/locales/en/messages.js

Large diffs are not rendered by default.

Loading

1 comment on commit 5147f80

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit was deployed on ipfs

Please sign in to comment.