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

Feat/implement zk ignite program #6

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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.
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
Loading