Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Implement ZkSync Ignite incentives program #2305

Merged
merged 9 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
30 changes: 30 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,34 @@ export const MeritIncentivesButton = (params: {
);
};

export const ZkIgniteIncentivesButton = (params: {
asset?: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

I see we are passing the term asset around. Can we make it more explicit like rewardAsset or something similar. This way its easier to understand when we are passing this variable @MartinGbz

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agree with you
Asset here can be the aToken or the vToken (aave asset in any cases). If it was the underlying asset I would set it for underlyingAsset.
I can set it to rewardedAsset what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Pushed a commit: f2dad18

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
38 changes: 34 additions & 4 deletions src/components/incentives/IncentivesCard.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,45 @@
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 { CustomMarket } from 'src/ui-config/marketsConfig';

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 +66,25 @@ 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} />
{market === CustomMarket.proto_zksync_v3 && (
MartinGbz marked this conversation as resolved.
Show resolved Hide resolved
<ZkIgniteIncentivesButton asset={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/"
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>
);
};
113 changes: 113 additions & 0 deletions src/hooks/useZkSyncIgniteIncentives.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
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 { 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 = ({
asset,
protocolAction,
}: {
asset?: string;
protocolAction?: ProtocolAction;
}) => {
return useQuery({
queryFn: async () => {
const response = await fetch(url);
const merklOpportunities: MerklOpportunity[] = await response.json();
return merklOpportunities;
},
queryKey: ['zkIgniteIncentives'],
staleTime: 1000 * 60 * 5,
select: (merklOpportunities) => {
const opportunities = merklOpportunities.filter(
(opportunitiy) =>
asset &&
opportunitiy.identifier.toLowerCase() === asset.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.

14 changes: 14 additions & 0 deletions src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ msgstr "APY, borrow rate"
#: src/components/incentives/IncentivesTooltipContent.tsx
#: src/components/incentives/IncentivesTooltipContent.tsx
#: src/components/incentives/MeritIncentivesTooltipContent.tsx
#: src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
msgid "APR"
msgstr "APR"

Expand Down Expand Up @@ -955,6 +956,10 @@ msgstr "{tooltipText}"
msgid "Rewards can be claimed through"
msgstr "Rewards can be claimed through"

#: src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
msgid "This is a program initiated and implemented by the decentralised ZKSync community. Aave Labs does not guarantee the program and accepts no liability."
msgstr "This is a program initiated and implemented by the decentralised ZKSync community. Aave Labs does not guarantee the program and accepts no liability."

#: src/components/incentives/MeritIncentivesTooltipContent.tsx
msgid "This is a program initiated and implemented by the decentralised Aave community. Aave Labs does not guarantee the program and accepts no liability."
msgstr "This is a program initiated and implemented by the decentralised Aave community. Aave Labs does not guarantee the program and accepts no liability."
Expand Down Expand Up @@ -1015,6 +1020,7 @@ msgid "Liquidation at"
msgstr "Liquidation at"

#: src/components/incentives/MeritIncentivesTooltipContent.tsx
#: src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
#: src/components/MarketSwitcher.tsx
#: src/components/transactions/DelegationTxsWrapper.tsx
#: src/components/transactions/DelegationTxsWrapper.tsx
Expand Down Expand Up @@ -1923,6 +1929,10 @@ msgstr "Eligible for the Merit program."
msgid "Cooldown period warning"
msgstr "Cooldown period warning"

#: src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
msgid "ZKSync Ignite Program rewards are claimed through the"
msgstr "ZKSync Ignite Program rewards are claimed through the"

#: src/components/transactions/Emode/EmodeModalContent.tsx
#: src/modules/bridge/BridgeWrapper.tsx
#: src/modules/dashboard/lists/BorrowAssetsList/BorrowAssetsList.tsx
Expand Down Expand Up @@ -2746,6 +2756,10 @@ msgstr "All borrow positions outside of this category must be closed to enable t
msgid "Available to supply"
msgstr "Available to supply"

#: src/components/incentives/ZkSyncIgniteIncentivesTooltipContent.tsx
msgid "Eligible for the ZKSync Ignite program."
msgstr "Eligible for the ZKSync Ignite program."

#: src/components/transactions/Warnings/MarketWarning.tsx
msgid "Per the community, the {market} has been frozen."
msgstr "Per the community, the {market} has been frozen."
Expand Down
2 changes: 1 addition & 1 deletion src/locales/es/messages.js

Large diffs are not rendered by default.

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

Large diffs are not rendered by default.

Loading