Skip to content

Commit

Permalink
add leo unclaimed rewards to displayed amounts
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-d committed Oct 9, 2024
1 parent b1ab128 commit 8aaac9f
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 22 deletions.
112 changes: 99 additions & 13 deletions web/src/app/stake/MyPositions.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useCallback, useMemo, useRef, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import List from "@/assets/icons/list.svg";
import Grid from "@/assets/icons/grid.svg";
Expand All @@ -26,6 +26,10 @@ import { usePositions } from "@/hooks/usePostions";
import { LoaderIcon } from "lucide-react";
import { useTokens, type Token, getTokenFromAddress } from "@/config/tokens";
import { useContracts } from "@/config/contracts";
import { simulateContract } from "wagmi/actions";
import config from "@/config";
import { getFormattedPriceFromUnscaledAmount } from "@/lib/amounts";
import { CampaignPrices } from "./pool/page";

export const MyPositions = () => {
const [displayMode, setDisplayMode] = useState<"list" | "grid">("list");
Expand Down Expand Up @@ -147,26 +151,108 @@ export const MyPositions = () => {
args: collectSeawaterArgs,
});

const { data: unclaimedLeoRewardsData } = useSimulateContract({
address: leoContract.address,
abi: leoContract.abi,
functionName: "collect",
args: [vestedPositions, campaignIds],
});

// campaignTokenPrices is the price of each token used in a campaign for this position
const [campaignTokenPrices, setCampaignTokenPrices] =
useState<CampaignPrices>({});
useEffect(() => {
(async () => {
const prices: CampaignPrices = {};
// no campaign rewards
if (!unclaimedLeoRewardsData) return prices;
for (const reward of unclaimedLeoRewardsData.result.campaignRewards) {
// looking at a seawater reward, not a leo reward
if (!("campaignToken" in reward)) continue;
const campaignToken =
reward.campaignToken.toLowerCase() as `0x${string}`;
// already seen this token
if (campaignToken in prices) continue;
// find token details
const token = getTokenFromAddress(chainId, campaignToken);
if (!token) {
console.warn("Token not found, skipping!", campaignToken);
continue;
}
// look up price
const { result: poolSqrtPriceX96 } = await simulateContract(
config.wagmiConfig,
{
address: ammContract.address,
abi: ammContract.abi,
functionName: "sqrtPriceX967B8F5FC5",
args: [token.address],
},
);
// store converted price
const tokenPrice = sqrtPriceX96ToPrice(
poolSqrtPriceX96,
token.decimals,
);
prices[campaignToken] = { decimals: token.decimals, tokenPrice };
}
setCampaignTokenPrices(prices);
})();
}, [
unclaimedLeoRewardsData,
setCampaignTokenPrices,
ammContract.abi,
ammContract.address,
chainId,
]);

const unclaimedRewards = useMemo(() => {
if (!unclaimedRewardsData) return "$0.00";
if (!(unclaimedRewardsData || unclaimedLeoRewardsData)) return "$0.00";

// Sum all Leo rewards, scaled by the price of their token
const campaignRewards =
unclaimedLeoRewardsData?.result.campaignRewards.reduce(
(acc, campaignReward) => {
if (!("campaignToken" in campaignReward)) return acc;
const campaignToken =
campaignReward.campaignToken.toLowerCase() as `0x${string}`;
if (!(campaignToken in campaignTokenPrices)) return acc;
const tokenDetails = campaignTokenPrices[campaignToken];
const reward = getFormattedPriceFromUnscaledAmount(
campaignReward.rewards,
tokenDetails.decimals,
tokenDetails.tokenPrice,
fUSDC.decimals,
);
return acc + reward;
},
0,
) ?? 0;

const rewards = unclaimedRewardsData.result.reduce((p, c, i) => {
const token = getTokenFromAddress(chainId, nonVestedPositions[i].id);
// this should never happen as nonVestedPositions is passed to collect
if (!token) return 0;
const token0AmountScaled =
(Number(c.amount0) * Number(tokenPrice)) /
10 ** (token.decimals + fUSDC.decimals);
const token1AmountScaled = Number(c.amount1) / 10 ** fUSDC.decimals;
return p + token0AmountScaled + token1AmountScaled;
}, 0);
return usdFormat(rewards);
// Sum regular rewards
const rewards =
unclaimedRewardsData?.result.reduce((p, c, i) => {
const token = getTokenFromAddress(chainId, nonVestedPositions[i].id);
// this should never happen as nonVestedPositions is passed to collect
if (!token) return 0;
const token0AmountScaled = getFormattedPriceFromUnscaledAmount(
c.amount0,
token.decimals,
tokenPrice,
fUSDC.decimals,
);
const token1AmountScaled = Number(c.amount1) / 10 ** fUSDC.decimals;
return p + token0AmountScaled + token1AmountScaled;
}, 0) ?? 0;
return usdFormat(rewards + campaignRewards);
}, [
unclaimedRewardsData,
unclaimedLeoRewardsData,
tokenPrice,
chainId,
fUSDC.decimals,
nonVestedPositions,
campaignTokenPrices,
]);

const collectAll = useCallback(() => {
Expand Down
102 changes: 93 additions & 9 deletions web/src/app/stake/pool/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Badge } from "@/components/ui/badge";
import { Line } from "rc-progress";
import { motion } from "framer-motion";
import Link from "next/link";
import { useCallback, useEffect, useMemo } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import { graphql, useFragment } from "@/gql";
import { useGraphqlGlobal } from "@/hooks/useGraphql";
import { useFeatureFlag } from "@/hooks/useFeatureFlag";
Expand All @@ -21,7 +21,10 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { getFormattedPriceFromTick } from "@/lib/amounts";
import {
getFormattedPriceFromTick,
getFormattedPriceFromUnscaledAmount,
} from "@/lib/amounts";
import { useStakeStore } from "@/stores/useStakeStore";
import { useSwapStore } from "@/stores/useSwapStore";
import {
Expand All @@ -44,6 +47,12 @@ import {
} from "@/config/tokens";
import { useContracts } from "@/config/contracts";
import { superpositionTestnet } from "@/config/chains";
import { simulateContract } from "wagmi/actions";
import config from "@/config";

export type CampaignPrices = {
[k: `0x${string}`]: { decimals: number; tokenPrice: bigint };
};

const ManagePoolFragment = graphql(`
fragment ManagePoolFragment on SeawaterPool {
Expand Down Expand Up @@ -237,23 +246,98 @@ export default function PoolPage() {
],
});

const unclaimedRewards = useMemo(() => {
if (!unclaimedRewardsData || !positionId) return "$0.00";
// campaignTokenPrices is the price of each token used in a campaign for this position
const [campaignTokenPrices, setCampaignTokenPrices] =
useState<CampaignPrices>({});
useEffect(() => {
(async () => {
const prices: CampaignPrices = {};
// no campaign rewards
if (!unclaimedLeoRewardsData) return prices;
for (const reward of unclaimedLeoRewardsData.result.campaignRewards) {
// looking at a seawater reward, not a leo reward
if (!("campaignToken" in reward)) continue;
const campaignToken =
reward.campaignToken.toLowerCase() as `0x${string}`;
// already seen this token
if (campaignToken in prices) continue;
// find token details
const token = getTokenFromAddress(chainId, campaignToken);
if (!token) {
console.warn("Token not found, skipping!", campaignToken);
continue;
}
// look up price
const { result: poolSqrtPriceX96 } = await simulateContract(
config.wagmiConfig,
{
address: ammContract.address,
abi: ammContract.abi,
functionName: "sqrtPriceX967B8F5FC5",
args: [token.address],
},
);
// store converted price
const tokenPrice = sqrtPriceX96ToPrice(
poolSqrtPriceX96,
token.decimals,
);
prices[campaignToken] = { decimals: token.decimals, tokenPrice };
}
setCampaignTokenPrices(prices);
})();
}, [
unclaimedLeoRewardsData,
setCampaignTokenPrices,
ammContract.abi,
ammContract.address,
chainId,
]);

const [{ amount0, amount1 }] = unclaimedRewardsData.result || [
const unclaimedRewards = useMemo(() => {
if (!(unclaimedRewardsData || unclaimedLeoRewardsData) || !positionId)
return "$0.00";

// Sum all Leo rewards, scaled by the price of their token
const campaignRewards =
unclaimedLeoRewardsData?.result.campaignRewards.reduce(
(acc, campaignReward) => {
if (!("campaignToken" in campaignReward)) return acc;
const campaignToken =
campaignReward.campaignToken.toLowerCase() as `0x${string}`;
if (!(campaignToken in campaignTokenPrices)) return acc;
const tokenDetails = campaignTokenPrices[campaignToken];
const reward = getFormattedPriceFromUnscaledAmount(
campaignReward.rewards,
tokenDetails.decimals,
tokenDetails.tokenPrice,
fUSDC.decimals,
);
return acc + reward;
},
0,
) ?? 0;

// Sum regular rewards
const [{ amount0, amount1 }] = unclaimedRewardsData?.result || [
{ amount0: 0n, amount1: 0n },
];
const token0AmountScaled =
(Number(amount0) * Number(tokenPrice)) /
10 ** (token0.decimals + fUSDC.decimals);
const token0AmountScaled = getFormattedPriceFromUnscaledAmount(
amount0,
token0.decimals,
tokenPrice,
fUSDC.decimals,
);
const token1AmountScaled = Number(amount1) / 10 ** fUSDC.decimals;
return usdFormat(token0AmountScaled + token1AmountScaled);
return usdFormat(token0AmountScaled + token1AmountScaled + campaignRewards);
}, [
unclaimedRewardsData,
unclaimedLeoRewardsData,
fUSDC.decimals,
positionId,
token0.decimals,
tokenPrice,
campaignTokenPrices,
]);

const collect = useCallback(
Expand Down
17 changes: 17 additions & 0 deletions web/src/lib/amounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,22 @@ const getFormattedPriceFromAmount = (
decimalsFusdc: number,
): number => (Number(amount) * Number(price)) / 10 ** decimalsFusdc;

/**
* @description scale an unscaled amount by the price of the pool
* @param amount - unscaled token amount
* @param decimals - the decimals of the token
* @param price - the pool price as a regular number, scaled up by fUSDC decimals
* @param decimalsFusdc - the decimals of fUSDC
* @returns the scaled price amount in USD
*/
const getFormattedPriceFromUnscaledAmount = (
amount: bigint | number,
decimals: number,
price: bigint | number,
decimalsFusdc: number,
): number =>
(Number(amount) * Number(price)) / 10 ** (decimals + decimalsFusdc);

// convert a tick to a formatted price, scaled by decimals
const getFormattedPriceFromTick = (
tick: number,
Expand Down Expand Up @@ -162,6 +178,7 @@ export {
snapAmountToDecimals,
getTokenAmountFromFormattedString,
getFormattedPriceFromAmount,
getFormattedPriceFromUnscaledAmount,
getFormattedPriceFromTick,
getUsdTokenAmountsForPosition,
};

0 comments on commit 8aaac9f

Please sign in to comment.