Skip to content

Commit

Permalink
Add pricing to claims window (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
gpxl-dev authored Sep 20, 2023
1 parent 6e24058 commit 4d5faa6
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 98 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"@radix-ui/react-checkbox": "^1.0.4",
"@react-hookz/web": "^23.1.0",
"@tanstack/react-query": "^4.32.0",
"axios": "^1.5.0",
"bignumber.js": "^9.1.1",
"framer-motion": "^10.15.1",
"graphql": "^16.7.1",
Expand Down
65 changes: 41 additions & 24 deletions src/features/claims/ClaimForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,25 @@ import BigNumber from "bignumber.js";
import { useState } from "react";
import { MdClose } from "react-icons/md";
import { zeroAddress } from "viem";
import { useAccount, useContractWrite, usePrepareContractWrite } from "wagmi";
import {
useAccount,
useChainId,
useContractWrite,
usePrepareContractWrite,
} from "wagmi";
import { ContractTypes } from "../../config/ContractAddresses";
import { useContractAddresses } from "../../config/hooks/useContractAddress";
import { poolAbi } from "../../contracts/poolAbi";
import { Button } from "../common/Button";
import { useClaimSelectionStore } from "../votes/store/useClaimSelectionStore";
import { ClaimableTokensLineItem } from "./ClaimableTokensLineItem";
import { useClaimableAmounts } from "./hooks/useClaimableAmounts";
import { useResetClaimStatus } from "./hooks/useResetClaimStatus";

// FIXME: this should not be the source - probably a json file instead,
// with goerli and mainnet.
const stakerTokens: `0x${string}`[] = [
"0x326c977e6efc84e512bb9c30f76e30c160ed06fb", // link
"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984", // uni
"0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6", // weth
];

export const ClaimForm = ({}: {}) => {
const [pool] = useContractAddresses([ContractTypes.AirSwapPool], {});
const { address: connectedAccount } = useAccount();
const chainId = useChainId();

const [
pointsClaimableByEpoch,
Expand Down Expand Up @@ -53,6 +52,8 @@ export const ClaimForm = ({}: {}) => {
0,
);

const claimable = useClaimableAmounts(pointsSelected || totalPointsClaimable);

const [selection, setSelection] = useState<{
index: number;
tokenAddress: `0x${string}`;
Expand Down Expand Up @@ -104,27 +105,43 @@ export const ClaimForm = ({}: {}) => {

<hr className="border-gray-800 -mx-6 my-6" />

{/* TODO: needs a loading state */}
<div
className="grid items-center gap-x-5 gap-y-4"
style={{
gridTemplateColumns: "auto 1fr auto",
}}
>
{stakerTokens.map((stakerToken, i) => (
<ClaimableTokensLineItem
isSelected={selection?.index === i}
onSelect={(amount) =>
setSelection({
amount,
index: i,
tokenAddress: stakerTokens[i],
})
}
numPoints={pointsSelected}
tokenAddress={stakerToken}
key={stakerToken}
/>
))}
{claimable
.filter(
({ claimableAmount, price, tokenInfo }) =>
tokenInfo?.decimals &&
claimableAmount &&
price &&
tokenInfo.symbol,
)
.map((claimOption, i) => (
<ClaimableTokensLineItem
isSelected={selection?.index === i}
onSelect={() => {
if (
!claimOption.claimableAmount ||
!claimOption.tokenInfo?.address
)
return;
setSelection({
amount: claimOption.claimableAmount,
index: i,
tokenAddress: claimOption.tokenInfo.address,
});
}}
amount={claimOption.claimableAmount || 0n}
decimals={claimOption.tokenInfo?.decimals || 18}
symbol={claimOption.tokenInfo?.symbol || "N/A"}
value={claimOption.claimableValue || 0}
key={claimOption.tokenInfo?.address || i}
/>
))}
</div>

<Button color="primary" rounded={false} className="mt-7" onClick={write}>
Expand Down
47 changes: 23 additions & 24 deletions src/features/claims/ClaimableTokensLineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,45 +1,44 @@
import { format } from "@greypixel_/nicenumbers";
import { twJoin } from "tailwind-merge";
import { Checkbox } from "../common/Checkbox";
import { useClaimableAmount } from "./hooks/useClaimableAmount";

export const ClaimableTokensLineItem = ({
tokenAddress,
numPoints,
symbol,
amount,
decimals,
value,
isSelected,
onSelect,
}: {
tokenAddress: `0x${string}`;
numPoints: number;
symbol: string;
decimals: number;
amount: bigint;
value: number;
isSelected: boolean;
onSelect: (amount: bigint) => void;
onSelect: () => void;
}) => {
const { isLoading, data } = useClaimableAmount({
points: numPoints,
tokenAddress,
});

if (isLoading || !data.claimableAmount || !data.tokenInfo) {
return null;
}

return (
<>
<Checkbox
isOptionButton
checked={isSelected}
onCheckedChange={(state) =>
state === true &&
data.claimableAmount &&
onSelect(data.claimableAmount)
}
onCheckedChange={(state) => state === true && onSelect()}
/>
<span className="text-gray-400">
{format(data.claimableAmount, {
tokenDecimals: data.tokenInfo.decimals,
{format(amount, {
tokenDecimals: decimals,
})}{" "}
{data.tokenInfo.symbol}
{symbol}
</span>
<span
className={twJoin(
"text-white font-medium",
amount && symbol ? "opacity-100" : "opacity-0",
"transition-opacity",
)}
>
${value.toFixed(2)}
</span>
<span className="text-white font-medium">$XXX</span>
</>
);
};
36 changes: 36 additions & 0 deletions src/features/claims/config/claimableTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
type MainnetClaimableToken = {
address: `0x${string}`;
mainnetEquivalentAddress?: undefined;
};

type TestnetClaimableToken = {
address: `0x${string}`;
mainnetEquivalentAddress: `0x${string}`;
};
type ClaimableTokensNetworkMapping = {
[index: number]: TestnetClaimableToken[];
} & {
mainnet: MainnetClaimableToken[];
};

export const claimableTokens: ClaimableTokensNetworkMapping = {
// TODO: populate.
mainnet: [],
5: [
// link
{
address: "0x326c977e6efc84e512bb9c30f76e30c160ed06fb",
mainnetEquivalentAddress: "0x514910771AF9Ca656af840dff83E8264EcF986CA",
},
// uni
{
address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
mainnetEquivalentAddress: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
},
// weth
{
address: "0xb4fbf271143f4fbf7b91a5ded31805e42b2208d6",
mainnetEquivalentAddress: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
},
],
};
43 changes: 43 additions & 0 deletions src/features/claims/hooks/useClaimCalculations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useChainId, useQuery } from "wagmi";
import { multicall } from "wagmi/actions";
import { ContractTypes } from "../../../config/ContractAddresses";
import { useContractAddresses } from "../../../config/hooks/useContractAddress";
import { poolAbi } from "../../../contracts/poolAbi";

export const useClaimCalculations = (
points: number,
claimableTokens: `0x${string}`[],
) => {
const chainId = useChainId();
const [poolContract] = useContractAddresses([ContractTypes.AirSwapPool], {
useDefaultAsFallback: false,
alwaysUseDefault: false,
});
const _points = BigInt(points) * BigInt(10 ** 4);

const fetch = async () => {
if (!poolContract.address) return;
const multicallResponse = await multicall({
chainId,
contracts: claimableTokens.map((tokenAddress) => ({
address: poolContract.address!,
abi: poolAbi,
functionName: "calculate",
args: [_points, tokenAddress],
})),
});

// return just the results
return multicallResponse.map((response) => response.result || 0n);
};

return useQuery(["claimCalculations", chainId, points], fetch, {
enabled: Boolean(
_points > 0 && poolContract.address && claimableTokens.length,
),
// 1 minute
cacheTime: 60_000,
staleTime: 30_000,
refetchInterval: 30_000,
});
};
49 changes: 0 additions & 49 deletions src/features/claims/hooks/useClaimableAmount.tsx

This file was deleted.

54 changes: 54 additions & 0 deletions src/features/claims/hooks/useClaimableAmounts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import BigNumber from "bignumber.js";
import { useMemo } from "react";
import { useChainId } from "wagmi";
import { useMultipleTokenInfo } from "../../common/hooks/useMultipleTokenInfo";
import { claimableTokens } from "../config/claimableTokens";
import { useClaimCalculations } from "./useClaimCalculations";
import { useDefiLlamaBatchPrices } from "./useDefillamaBatchPrices";

const empty: string[] = [];

export const useClaimableAmounts = (points: number) => {
const chainId = useChainId();
const tokenList =
claimableTokens[chainId === 1 ? "mainnet" : chainId] || empty;

const { data: claimableAmounts } = useClaimCalculations(
points,
tokenList.map((token) => token.address),
);

const tokenInfoResults = useMultipleTokenInfo({
chainId,
tokenAddresses: tokenList.map((token) => token.address),
});

const { data: prices } = useDefiLlamaBatchPrices(
tokenList.map((token) => token.mainnetEquivalentAddress || token.address),
);

return useMemo(() => {
return tokenList
.map((_, index) => {
const tokenInfo = tokenInfoResults[index].data;
const price = prices?.[index].price;
const claimableAmount = claimableAmounts?.[index];
const claimableValue =
tokenInfo?.decimals &&
price &&
claimableAmount &&
new BigNumber(claimableAmount.toString())
.dividedBy(10 ** tokenInfo.decimals)
.multipliedBy(price)
.toNumber();

return {
claimableAmount,
tokenInfo,
price,
claimableValue,
};
})
.sort((a, b) => (b.claimableValue || 0) - (a.claimableValue || 0));
}, [prices, tokenInfoResults, claimableAmounts, tokenList]);
};
Loading

0 comments on commit 4d5faa6

Please sign in to comment.