import { useMemo } from "react"; import { assocPath, range } from "ramda"; import { useRecoilState } from "recoil"; import { recentlyTransferredAndBurnedPositionsState, UserPosition, userPositionsState, } from "../../../state/userPositions"; import { useContracts } from "../../useContracts/useContracts"; import { useWeb3Provider } from "../../useWeb3Provider/useWeb3Provider"; import { bigNumAsInt, weiBigNumAsFloat } from "../../../utils/parse/parse"; import { config } from "../../../config"; import { BigNumber } from "ethers"; import { ParentRewarderABI__factory } from "digit-shared/src/types/generatedContractTypes"; import { PositionInfoStructOutput } from "digit-shared/src/types/generatedContractTypes/ReliquaryABI"; const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" export const useUserPositions = () => { const [userPositionsUnfiltered, setUserPositions] = useRecoilState(userPositionsState); const [recentPositionEvent] = useRecoilState( recentlyTransferredAndBurnedPositionsState ); const { account, signer } = useWeb3Provider(); const { reliquary } = useContracts(); const fetchPendingTokens = async (positionId: number, poolId: number) => { const [primaryRewardTokenAddress, rewarderContractAddress, pendingReward] = await Promise.all([ reliquary.rewardToken(), reliquary.rewarder(poolId), reliquary.pendingReward(positionId), ]); if (rewarderContractAddress == ZERO_ADDRESS) return [ { tokenAddress: primaryRewardTokenAddress, amount: pendingReward, } ] const rewarderContract = ParentRewarderABI__factory.connect( rewarderContractAddress, signer! ); const pendingTokens = await rewarderContract.pendingTokens( positionId, pendingReward ); return [ { tokenAddress: primaryRewardTokenAddress, amount: pendingReward, }, ...pendingTokens.rewardTokens.map((tokenAddress: string, i: number) => ({ tokenAddress, amount: pendingTokens.rewardAmounts[i], })), ]; }; const fetchUserPosition = async ( positionId: number ): Promise => { // if (userPositions[positionId]) return userPositions[positionId]; let tokenUri: string; let positionInfo: PositionInfoStructOutput; let nextLevel: BigNumber; let balanceInUnderlyingBig: BigNumber[]; let pendingTokens: { tokenAddress: string; amount: BigNumber; }[]; try { [tokenUri, positionInfo, nextLevel] = await Promise.all([ reliquary.tokenURI(positionId), reliquary.getPositionForId(positionId), reliquary.levelOnUpdate(positionId), ]); const poolId = bigNumAsInt(positionInfo.poolId); const lpInterface = new config!.poolConfig[poolId].type( config!.poolConfig[poolId]!.lpAddress, signer!, config!.poolConfig[poolId] ); [balanceInUnderlyingBig, pendingTokens] = await Promise.all([ lpInterface.convertToAssets(positionInfo.amount), fetchPendingTokens(positionId, poolId), ]); } catch (error: any) { console.error( `Error fetching data for positionId: ${positionId}. Details: ${error.message}` ); return undefined; } const poolConfig = config!.poolConfig[bigNumAsInt(positionInfo.poolId)]; const balanceInUnderlying = balanceInUnderlyingBig.map( (bal: BigNumber, index: number) => weiBigNumAsFloat(bal, poolConfig!.underlyingFarmAsset[index].decimals) ); const isMultiAssetPool = poolConfig!.underlyingFarmAsset.length > 1; let underlyingAssetName = poolConfig!.underlyingFarmAsset .map((a) => a.name) .join("/"); if (isMultiAssetPool) underlyingAssetName += " LP"; const position: UserPosition = { ...JSON.parse(atob(tokenUri.split(",")[1])), amount: weiBigNumAsFloat( positionInfo.amount, poolConfig!.lpDecimalPlaces ), amountAsString: positionInfo.amount.toString(), amountInUnderlying: balanceInUnderlying, amountInUnderlyingBig: balanceInUnderlyingBig, underlyingAssetName, canLevelUp: bigNumAsInt(nextLevel) > bigNumAsInt(positionInfo.level), entry: bigNumAsInt(positionInfo.entry), level: bigNumAsInt(positionInfo.level), nextLevel: bigNumAsInt(nextLevel), tokenId: positionId, tokenUri, pendingRewards: pendingTokens, isMultiAssetPool, poolId: positionInfo.poolId, }; setUserPositions(assocPath([positionId], position)); return position; }; const fetchUserPositions = async () => { const numberOfPositions = await reliquary.balanceOf(account!); const ownerTokenIds: BigNumber[] = await Promise.all( range(0, bigNumAsInt(numberOfPositions)).map((i) => reliquary.tokenOfOwnerByIndex(account!, i) ) ); const userPositions = await Promise.all( ownerTokenIds.map((t) => fetchUserPosition(bigNumAsInt(t))) ); //remove undefined positions return userPositions.filter((p) => p); }; const userPositions = useMemo(() => { const filteredUserPositions = userPositionsUnfiltered; if (recentPositionEvent.length === 0) return filteredUserPositions; recentPositionEvent.forEach((item) => { item?.id && delete filteredUserPositions[item.id]; }); return filteredUserPositions; }, [userPositionsUnfiltered, recentPositionEvent]); const userPositionsArray = useMemo( () => Object.values(userPositions), [userPositions] ); return { fetchUserPosition, fetchUserPositions, setUserPositions, userPositions, userPositionsArray, }; };