Skip to content

Commit

Permalink
WIP custom tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
gpxl-dev committed Mar 27, 2024
1 parent f5602b5 commit a8509d5
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 100 deletions.
66 changes: 66 additions & 0 deletions src/features/claims/AddCustomTokenForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useState } from "react";
import { Address, isAddress } from "viem";
import { useChainId } from "wagmi";
import { useMultipleTokenInfo } from "../common/hooks/useMultipleTokenInfo";
import { useClaimSelectionStore } from "../votes/store/useClaimSelectionStore";
import { useClaimableAmounts } from "./hooks/useClaimableAmounts";

export const AddCustomTokenForm = ({ className }: { className?: string }) => {
const [address, setAddress] = useState<string>("");
const chainId = useChainId();

const isValidAddress = isAddress(address || "");

const [{ data: tokenInfo, isError }] = useMultipleTokenInfo({
chainId,
tokenAddresses: [address as Address],
enabled: isValidAddress,
});

const { addCustomToken, selectedClaims, pointsClaimableByEpoch } =
useClaimSelectionStore((state) => ({
addCustomToken: state.addCustomToken,
selectedClaims: state.selectedClaims,
pointsClaimableByEpoch: state.pointsClaimableByEpoch,
}));

const totalPointsClaimable = Object.values(pointsClaimableByEpoch).reduce(
(acc, epoch) => acc + epoch,
0,
);

const pointsSelected =
selectedClaims.reduce((acc, claim) => acc + claim.value, 0) ||
totalPointsClaimable;

const { refetch: refetchClaimable } = useClaimableAmounts(pointsSelected);

return (
<div className="flex flex-col">
<input
value={address}
onChange={(e) => setAddress(e.target.value)}
className="text-black"
/>
{!isValidAddress && <div className="text-white">Invalid address</div>}
<div>
{tokenInfo && (
<div className="text-white">
<div>{tokenInfo.name}</div>
<div>{tokenInfo.symbol}</div>
<div>{tokenInfo.decimals}</div>
<button
onClick={() => {
addCustomToken(chainId, address as Address);
refetchClaimable();
setAddress("");
}}
>
Add
</button>
</div>
)}
</div>
</div>
);
};
23 changes: 18 additions & 5 deletions src/features/claims/ClaimForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Button } from "../common/Button";
import { TransactionTracker } from "../common/TransactionTracker";
import { formatNumber } from "../common/utils/formatNumber";
import { useClaimSelectionStore } from "../votes/store/useClaimSelectionStore";
import { AddCustomTokenForm } from "./AddCustomTokenForm";
import {
ClaimableTokensLineItem,
ClaimableTokensLineItemLoading,
Expand All @@ -25,6 +26,7 @@ import { useClaimableAmounts } from "./hooks/useClaimableAmounts";
import { useResetClaimStatus } from "./hooks/useResetClaimStatus";

export const ClaimForm = ({}: {}) => {
const [newCustomTokenAddress, setNewCustomTokenAddress] = useState<string>();
const [pool] = useContractAddresses([ContractTypes.AirSwapPool], {});
const { address: connectedAccount } = useAccount();

Expand Down Expand Up @@ -210,12 +212,21 @@ export const ClaimForm = ({}: {}) => {
gridTemplateColumns: "auto 1fr auto",
}}
>
<div className="col-span-full">
<AddCustomTokenForm />
</div>
{claimable.map(
({ claimableAmount, claimableValue, price, tokenInfo }, i) => {
const isLoaded =
tokenInfo?.decimals &&
claimableAmount != null &&
price;
(
{
claimableAmount,
claimableValue,
price,
tokenInfo,
isCustomToken,
},
i,
) => {
const isLoaded = tokenInfo?.decimals && claimableAmount != null;

return isLoaded ? (
<ClaimableTokensLineItem
Expand All @@ -235,6 +246,8 @@ export const ClaimForm = ({}: {}) => {
symbol={tokenInfo?.symbol || "???"}
value={claimableValue || 0}
key={tokenInfo?.address || i}
isCustomToken={isCustomToken}
address={tokenInfo?.address}
/>
) : (
<ClaimableTokensLineItemLoading key={i + "-loading"} />
Expand Down
20 changes: 19 additions & 1 deletion src/features/claims/ClaimableTokensLineItem.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { IoMdClose } from "react-icons/io";
import { twJoin } from "tailwind-merge";
import { Address } from "viem";
import { useChainId } from "wagmi";
import { Checkbox } from "../common/Checkbox";
import { formatNumber } from "../common/utils/formatNumber";
import { useClaimSelectionStore } from "../votes/store/useClaimSelectionStore";

export const ClaimableTokensLineItem = ({
symbol,
Expand All @@ -9,23 +13,37 @@ export const ClaimableTokensLineItem = ({
value,
isSelected,
onSelect,
isCustomToken = false,
address,
}: {
symbol: string;
decimals: number;
amount: bigint;
value: number;
isSelected: boolean;
onSelect: () => void;
isCustomToken?: boolean;
address: Address;
}) => {
const chainId = useChainId();
const removeCustomToken = useClaimSelectionStore(
(state) => state.removeCustomToken,
);

return (
<>
<Checkbox
isOptionButton
checked={isSelected}
onCheckedChange={(state) => state === true && onSelect()}
/>
<span className="text-gray-400">
<span className="text-gray-400 inline-flex gap-2 items-center">
{formatNumber(amount, decimals)} {symbol}
{isCustomToken && (
<button onClick={() => removeCustomToken(chainId, address)}>
<IoMdClose className="text-red-500" />
</button>
)}
</span>
<span
className={twJoin(
Expand Down
38 changes: 32 additions & 6 deletions src/features/claims/hooks/useClaimCalculations.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BigNumber } from "bignumber.js";
import { hashMessage } from "viem";
import { useNetwork, useQuery } from "wagmi";
import { multicall } from "wagmi/actions";
import { ContractTypes } from "../../../config/ContractAddresses";
Expand All @@ -8,6 +9,7 @@ import { poolAbi } from "../../../contracts/poolAbi";
export const useClaimCalculations = (
points: number,
claimableTokens: `0x${string}`[],
enabled: boolean = true,
) => {
const { chain } = useNetwork();
const [poolContract] = useContractAddresses([ContractTypes.AirSwapPool], {
Expand Down Expand Up @@ -35,13 +37,37 @@ export const useClaimCalculations = (
return multicallResponse.map((response) => response.result || 0n);
};

return useQuery(["claimCalculations", chain!.id, points], fetch, {
const claimableTokenAddressesHash = hashMessage(claimableTokens.join(","));

console.log({
enabled: Boolean(
_points > 0 && poolContract.address && claimableTokens.length && chain,
enabled &&
_points > 0 &&
poolContract.address &&
claimableTokens.length &&
chain,
),
// 1 minute
cacheTime: 60_000,
staleTime: 30_000,
refetchInterval: 30_000,
enabledb: enabled,
_points,
pooladdr: poolContract.address,
claimableTokens: claimableTokens.length,
chain,
});
return useQuery(
["claimCalculations", chain!.id, claimableTokenAddressesHash, points],
fetch,
{
enabled: Boolean(
enabled &&
_points > 0 &&
poolContract.address &&
claimableTokens.length &&
chain,
),
// 1 minute
cacheTime: 60_000,
staleTime: 30_000,
refetchInterval: 30_000,
},
);
};
79 changes: 66 additions & 13 deletions src/features/claims/hooks/useClaimableAmounts.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import BigNumber from "bignumber.js";
import { useMemo } from "react";
import { getAddress } from "viem";
import { Address, useChainId } from "wagmi";
import { useMultipleTokenInfo } from "../../common/hooks/useMultipleTokenInfo";
import { useClaimSelectionStore } from "../../votes/store/useClaimSelectionStore";
import {
TestnetClaimableToken,
claimableTokens,
testnetClaimableTokens,
} from "../config/claimableTokens";
Expand All @@ -18,15 +19,45 @@ const testnets = Object.keys(testnetClaimableTokens).map((t) => parseInt(t));
export const useClaimableAmounts = (points: number) => {
const chainId = useChainId() as SupportedChainId;

const isTestnet = testnets.includes(chainId);
const savedCustomTokens = useClaimSelectionStore(
(state) => state.customTokens,
);

const { tokenList, tokenAddresses, customTokens } = useMemo(() => {
const isTestnet = testnets.includes(chainId);
const tokens = isTestnet
? testnetClaimableTokens[chainId]
: claimableTokens[chainId];

if (isTestnet) {
return {
tokenList: tokens,
tokenAddresses: tokens as Address[],
customTokens: [] as Address[],
};
}

const defaultTokens: Address[] = tokens as Address[];

const tokenList = isTestnet
? testnetClaimableTokens[chainId]
: claimableTokens[chainId];
// filter out custom tokens that are already in the tokenList.
const customTokensForChain = savedCustomTokens[chainId] || [];
const checksummedTokenList = defaultTokens.map((token) =>
getAddress(token),
);
const customTokensNotInDefaults = customTokensForChain.filter(
(customToken) => !checksummedTokenList.includes(customToken),
);

const tokenAddresses = isTestnet
? (tokenList as TestnetClaimableToken[]).map((t) => t.address)
: (tokenList as Address[]);
return {
tokenList: customTokensNotInDefaults.length
? [...defaultTokens, ...customTokensNotInDefaults]
: (defaultTokens as Address[]),
tokenAddresses: customTokensNotInDefaults.length
? [...defaultTokens, ...customTokensNotInDefaults]
: (defaultTokens as Address[]),
customTokens: customTokensNotInDefaults,
};
}, [savedCustomTokens, chainId]);

const { data: claimableAmounts, refetch } = useClaimCalculations(
points,
Expand All @@ -40,17 +71,31 @@ export const useClaimableAmounts = (points: number) => {

const { data: prices } = useDefiLlamaBatchPrices({
chainId,
tokenAddresses: tokenList.map((token) =>
typeof token === "object" ? token.mainnetEquivalentAddress : token,
),
tokenAddresses: tokenList
.map((token) => {
const addr =
typeof token === "object" ? token.mainnetEquivalentAddress : token;
return typeof token === "object"
? token.mainnetEquivalentAddress
: token;
})
.concat(chainId === 1 ? customTokens : []),
});

return useMemo(() => {
const data = tokenList
.map((_, index) => {
.map((token, index) => {
const address = typeof token === "object" ? token.address : token;
const tokenInfo = tokenInfoResults[index].data;
const price = prices?.[index].price;
const claimableAmount = claimableAmounts?.[index];
console.log(
"symbol",
tokenInfo?.symbol,
claimableAmount,
"price",
price,
);
const claimableValue =
tokenInfo?.decimals &&
price &&
Expand All @@ -65,9 +110,17 @@ export const useClaimableAmounts = (points: number) => {
tokenInfo,
price,
claimableValue,
isCustomToken: customTokens.includes(getAddress(address)),
};
})
.sort((a, b) => (b.claimableValue || 0) - (a.claimableValue || 0));
return { data, refetch };
}, [prices, tokenInfoResults, claimableAmounts, tokenList, refetch]);
}, [
prices,
tokenInfoResults,
claimableAmounts,
tokenList,
refetch,
customTokens,
]);
};
33 changes: 21 additions & 12 deletions src/features/claims/hooks/useDefillamaBatchPrices.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import axios from "axios";
import { hashMessage } from "viem";
import { useQuery } from "wagmi";
import { claimableTokens } from "../config/claimableTokens";

Expand Down Expand Up @@ -27,17 +28,25 @@ type CurrentPricesResponse = {
};

const fetch = async (chainName: string, addresses: `0x${string}`[]) => {
const response = await axios.get<CurrentPricesResponse>(
`${path}/${addresses
.map((address) => `${chainName}:${address}`)
.join(",")}`,
);
const results = response.data.coins;
// return an array of {address: price} objects, where `price` is results[address].price
return addresses.map((address) => ({
address,
price: results[`${chainName}:${address}`].price,
}));
console.log("fetching prices for", addresses);
if (addresses.length === 37) debugger;
try {
const response = await axios.get<CurrentPricesResponse>(
`${path}/${addresses
.map((address) => `${chainName}:${address}`)
.join(",")}`,
);
const results = response.data.coins;

// return an array of {address: price} objects, where `price` is results[address].price
return addresses.map((address) => ({
address,
price: results[`${chainName}:${address}`]?.price || 0,
}));
} catch (e) {
console.log(e);
throw e;
}
};

export const useDefiLlamaBatchPrices = ({
Expand All @@ -48,7 +57,7 @@ export const useDefiLlamaBatchPrices = ({
tokenAddresses: `0x${string}`[];
}) => {
return useQuery(
["defillama", "prices", chainId, tokenAddresses],
["defillama", "prices", chainId, hashMessage(tokenAddresses.join(""))],
() => fetch(DefillamaChainNames[chainId], tokenAddresses),
{
cacheTime: 3_600_000, // 1 hour
Expand Down
Loading

0 comments on commit a8509d5

Please sign in to comment.