Skip to content
Open
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 .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ yarn-error.log*
package-lock.json

.eslintcache
.yarn-cache

# IDE specific
.idea
Expand Down
83 changes: 47 additions & 36 deletions src/components/incentives/IncentivesCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ProtocolAction } from '@aave/contract-helpers';
import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
import { Box, Typography } from '@mui/material';
import { useRouter } from 'next/router';
import { ReactNode } from 'react';
import { ReactNode, useMemo } from 'react';
import { ENABLE_SELF_CAMPAIGN, useMeritIncentives } from 'src/hooks/useMeritIncentives';
import { useMerklIncentives } from 'src/hooks/useMerklIncentives';
import { useMerklPointsIncentives } from 'src/hooks/useMerklPointsIncentives';
Expand Down Expand Up @@ -32,6 +32,7 @@ interface IncentivesCardProps {
protocolAction?: ProtocolAction;
align?: 'center' | 'flex-end';
inlineIncentives?: boolean;
displayAPY?: number | 'Infinity';
}

export const IncentivesCard = ({
Expand All @@ -47,21 +48,10 @@ export const IncentivesCard = ({
market,
protocolAction,
inlineIncentives = false,
displayAPY,
}: IncentivesCardProps) => {
const router = useRouter();
const protocolAPY = typeof value === 'string' ? parseFloat(value) : value;

const protocolIncentivesAPR =
incentives?.reduce((sum, inc) => {
if (inc.incentiveAPR === 'Infinity' || sum === 'Infinity') {
return 'Infinity';
}
return sum + +inc.incentiveAPR;
}, 0 as number | 'Infinity') || 0;

const protocolIncentivesAPY = convertAprToApy(
protocolIncentivesAPR === 'Infinity' ? 0 : protocolIncentivesAPR
);
const { data: meritIncentives } = useMeritIncentives({
symbol,
market,
Expand All @@ -86,27 +76,48 @@ export const IncentivesCard = ({
protocolIncentives: incentives || [],
});

const meritIncentivesAPR = meritIncentives?.breakdown?.meritIncentivesAPR || 0;

// TODO: This is a one-off for the Self campaign.
// Remove once the Self incentives are finished.
const selfAPY = ENABLE_SELF_CAMPAIGN ? meritIncentives?.variants?.selfAPY ?? 0 : 0;
const totalMeritAPY = meritIncentivesAPR + selfAPY;

const merklIncentivesAPR = merklPointsIncentives?.breakdown?.points
? merklPointsIncentives.breakdown.merklIncentivesAPR || 0
: merklIncentives?.breakdown?.merklIncentivesAPR || 0;

const isBorrow = protocolAction === ProtocolAction.borrow;

// If any incentive is infinite, the total should be infinite
const hasInfiniteIncentives = protocolIncentivesAPR === 'Infinity';

const displayAPY = hasInfiniteIncentives
? 'Infinity'
: isBorrow
? protocolAPY - (protocolIncentivesAPY as number) - totalMeritAPY - merklIncentivesAPR
: protocolAPY + (protocolIncentivesAPY as number) + totalMeritAPY + merklIncentivesAPR;
const computedDisplayAPY = useMemo(() => {
if (displayAPY !== undefined) {
return displayAPY;
}

const protocolIncentivesAPR =
incentives?.reduce((sum, inc) => {
if (inc.incentiveAPR === 'Infinity' || sum === 'Infinity') {
return 'Infinity';
}
return sum + +inc.incentiveAPR;
}, 0 as number | 'Infinity') || 0;

const protocolIncentivesAPY = convertAprToApy(
protocolIncentivesAPR === 'Infinity' ? 0 : protocolIncentivesAPR
);

const meritIncentivesAPR = meritIncentives?.breakdown?.meritIncentivesAPR || 0;
const selfAPY = ENABLE_SELF_CAMPAIGN ? meritIncentives?.variants?.selfAPY ?? 0 : 0;
const totalMeritAPY = meritIncentivesAPR + selfAPY;

const merklIncentivesAPR = merklPointsIncentives?.breakdown?.points
? merklPointsIncentives.breakdown.merklIncentivesAPR || 0
: merklIncentives?.breakdown?.merklIncentivesAPR || 0;

const isBorrow = protocolAction === ProtocolAction.borrow;
const hasInfiniteIncentives = protocolIncentivesAPR === 'Infinity';

return hasInfiniteIncentives
? 'Infinity'
: isBorrow
? protocolAPY - (protocolIncentivesAPY as number) - totalMeritAPY - merklIncentivesAPR
: protocolAPY + (protocolIncentivesAPY as number) + totalMeritAPY + merklIncentivesAPR;
}, [
displayAPY,
incentives,
meritIncentives,
merklIncentives,
merklPointsIncentives,
protocolAPY,
protocolAction,
]);

const isSghoPage =
typeof router?.asPath === 'string' && router.asPath.toLowerCase().startsWith('/sgho');
Expand Down Expand Up @@ -156,14 +167,14 @@ export const IncentivesCard = ({
>
{value.toString() !== '-1' ? (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{displayAPY === 'Infinity' ? (
{computedDisplayAPY === 'Infinity' ? (
<Typography variant={variant} color={color || 'text.secondary'}>
∞ %
</Typography>
) : (
<FormattedNumber
data-cy={`apy`}
value={displayAPY}
value={computedDisplayAPY}
percent
variant={variant}
symbolsVariant={symbolsVariant}
Expand Down
92 changes: 92 additions & 0 deletions src/hooks/useIncentivizedApy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { ProtocolAction } from '@aave/contract-helpers';
import { ReserveIncentiveResponse } from '@aave/math-utils/dist/esm/formatters/incentive/calculate-reserve-incentives';
import { ENABLE_SELF_CAMPAIGN, useMeritIncentives } from 'src/hooks/useMeritIncentives';
import { convertAprToApy } from 'src/utils/utils';

import { useMerklIncentives } from './useMerklIncentives';
import { useMerklPointsIncentives } from './useMerklPointsIncentives';

interface IncentivizedApyParams {
symbol: string;
market: string;
rewardedAsset: string;
protocolAction?: ProtocolAction;
protocolAPY: number | string;
protocolIncentives?: ReserveIncentiveResponse[];
}
type UseIncentivizedApyResult = {
displayAPY: number | 'Infinity';
hasInfiniteIncentives: boolean;
isLoading: boolean;
};
export const useIncentivizedApy = ({
symbol,
market,
rewardedAsset: address,
protocolAction,
protocolAPY: value,
protocolIncentives: incentives = [],
}: IncentivizedApyParams): UseIncentivizedApyResult => {
const protocolAPY = typeof value === 'string' ? parseFloat(value) : value;

const protocolIncentivesAPR =
incentives?.reduce((sum, inc) => {
if (inc.incentiveAPR === 'Infinity' || sum === 'Infinity') {
return 'Infinity';
}
return sum + +inc.incentiveAPR;
}, 0 as number | 'Infinity') || 0;

const protocolIncentivesAPY = convertAprToApy(
protocolIncentivesAPR === 'Infinity' ? 0 : protocolIncentivesAPR
);
const { data: meritIncentives, isLoading: meritLoading } = useMeritIncentives({
symbol,
market,
protocolAction,
protocolAPY,
protocolIncentives: incentives || [],
});

const { data: merklIncentives, isLoading: merklLoading } = useMerklIncentives({
market,
rewardedAsset: address,
protocolAction,
protocolAPY,
protocolIncentives: incentives || [],
});

const { data: merklPointsIncentives, isLoading: merklPointsLoading } = useMerklPointsIncentives({
market,
rewardedAsset: address,
protocolAction,
protocolAPY,
protocolIncentives: incentives || [],
});

const isLoading = meritLoading || merklLoading || merklPointsLoading;

const meritIncentivesAPR = meritIncentives?.breakdown?.meritIncentivesAPR || 0;

// TODO: This is a one-off for the Self campaign.
// Remove once the Self incentives are finished.
const selfAPY = ENABLE_SELF_CAMPAIGN ? meritIncentives?.variants?.selfAPY ?? 0 : 0;
const totalMeritAPY = meritIncentivesAPR + selfAPY;

const merklIncentivesAPR = merklPointsIncentives?.breakdown?.points
? merklPointsIncentives.breakdown.merklIncentivesAPR || 0
: merklIncentives?.breakdown?.merklIncentivesAPR || 0;

const isBorrow = protocolAction === ProtocolAction.borrow;

// If any incentive is infinite, the total should be infinite
const hasInfiniteIncentives = protocolIncentivesAPR === 'Infinity';

const displayAPY = hasInfiniteIncentives
? 'Infinity'
: isBorrow
? protocolAPY - (protocolIncentivesAPY as number) - totalMeritAPY - merklIncentivesAPR
: protocolAPY + (protocolIncentivesAPY as number) + totalMeritAPY + merklIncentivesAPR;

return { displayAPY, hasInfiniteIncentives, isLoading };
};
68 changes: 63 additions & 5 deletions src/modules/markets/MarketAssetsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Trans } from '@lingui/macro';
import { useMediaQuery } from '@mui/material';
import { useState } from 'react';
import { useCallback, useState } from 'react';
import { mapAaveProtocolIncentives } from 'src/components/incentives/incentives.helper';
import { VariableAPYTooltip } from 'src/components/infoTooltips/VariableAPYTooltip';
import { ListColumn } from 'src/components/lists/ListColumn';
Expand Down Expand Up @@ -46,15 +46,60 @@ type MarketAssetsListProps = {
reserves: ReserveWithId[];
loading: boolean;
};

export type IncentivizedApySide = 'supply' | 'borrow';
export type IncentivizedApyState = Record<
string,
Partial<Record<IncentivizedApySide, number | 'Infinity'>>
>;
export type ApyUpdateHandler = (
reserveId: string,
side: IncentivizedApySide,
value: number | 'Infinity'
) => void;

const getComparableApy = (
storedValue: number | 'Infinity' | undefined,
fallback: number
): number => {
if (storedValue === 'Infinity') {
return Number.POSITIVE_INFINITY;
}
if (typeof storedValue === 'number' && !Number.isNaN(storedValue)) {
return storedValue;
}
return fallback;
};

export type ReserveWithProtocolIncentives = ReserveWithId & {
supplyProtocolIncentives: ReturnType<typeof mapAaveProtocolIncentives>;
borrowProtocolIncentives: ReturnType<typeof mapAaveProtocolIncentives>;
onApyChange: ApyUpdateHandler;
};

export default function MarketAssetsList({ reserves, loading }: MarketAssetsListProps) {
const isTableChangedToCards = useMediaQuery('(max-width:1125px)');
const [sortName, setSortName] = useState('');
const [sortDesc, setSortDesc] = useState(false);
const [incentivizedApys, setIncentivizedApys] = useState<IncentivizedApyState>({});

const handleApyChange = useCallback<ApyUpdateHandler>((reserveId, side, value) => {
setIncentivizedApys((prev) => {
const current = prev[reserveId]?.[side];
if (current === value) {
return prev;
}

return {
...prev,
[reserveId]: {
...prev[reserveId],
[side]: value,
},
};
});
}, []);

const sortedReserves = [...reserves].sort((a, b) => {
if (!sortName) return 0;

Expand All @@ -76,8 +121,14 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList
break;

case 'supplyInfo.apy.value':
aValue = Number(a.supplyInfo.apy.value) || 0;
bValue = Number(b.supplyInfo.apy.value) || 0;
aValue = getComparableApy(
incentivizedApys[a.id]?.supply,
Number(a.supplyInfo.apy.value) || 0
);
bValue = getComparableApy(
incentivizedApys[b.id]?.supply,
Number(b.supplyInfo.apy.value) || 0
);
break;

case 'borrowInfo.total.usd':
Expand All @@ -86,8 +137,14 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList
break;

case 'borrowInfo.apy.value':
aValue = Number(a.borrowInfo?.apy.value) || 0;
bValue = Number(b.borrowInfo?.apy.value) || 0;
aValue = getComparableApy(
incentivizedApys[a.id]?.borrow,
Number(a.borrowInfo?.apy.value) || 0
);
bValue = getComparableApy(
incentivizedApys[b.id]?.borrow,
Number(b.borrowInfo?.apy.value) || 0
);
break;

default:
Expand All @@ -102,6 +159,7 @@ export default function MarketAssetsList({ reserves, loading }: MarketAssetsList
...reserve,
supplyProtocolIncentives: mapAaveProtocolIncentives(reserve.incentives, 'supply'),
borrowProtocolIncentives: mapAaveProtocolIncentives(reserve.incentives, 'borrow'),
onApyChange: handleApyChange,
}));
// Show loading state when loading
if (loading) {
Expand Down
Loading
Loading