import { useEffect, useMemo } from 'react' import BigNumber from 'bignumber.js' import { useWeb3React } from '@web3-react/core' import { useSelector } from 'react-redux' import { ethers } from 'ethers' import { minBy, orderBy } from 'lodash' import { useAppDispatch } from 'state' import { Team } from 'config/constants/types' import Nfts from 'config/constants/nfts' import { farmsConfig } from 'config/constants' import { simpleRpcProvider } from 'utils/providers' import { getBalanceAmount } from 'utils/formatBalance' import { BIG_ZERO } from 'utils/bigNumber' import useRefresh from 'hooks/useRefresh' import { filterFarmsByQuoteToken } from 'utils/farmsPriceHelpers' import { fetchFarmsPublicDataAsync, fetchPoolsPublicDataAsync, fetchPoolsUserDataAsync, fetchCakeVaultPublicData, fetchCakeVaultUserData, fetchCakeVaultFees, setBlock, } from './actions' import { State, Farm, Pool, ProfileState, TeamsState, AchievementState, FarmsState, NodeRound, ReduxNodeLedger, NodeLedger, ReduxNodeRound, Boards, } from './types' import { fetchProfile } from './profile' import { fetchTeam, fetchTeams } from './teams' import { fetchAchievements } from './achievements' import { fetchWalletNfts } from './collectibles' import { parseBigNumberObj } from './predictions/helpers' import { transformPool } from './pools/helpers' import { fetchPoolsStakingLimitsAsync } from './pools' import { fetchFarmUserDataAsync, nonArchivedFarms } from './farms' import { fetchCurrentLotteryId, fetchCurrentLottery, fetchUserTicketsAndLotteries, fetchPublicLotteries, } from './lottery' import { useProcessLotteryResponse } from './lottery/helpers' export const usePollFarmsData = (includeArchive = false) => { const dispatch = useAppDispatch() const { slowRefresh } = useRefresh() const { account } = useWeb3React() useEffect(() => { const farmsToFetch = includeArchive ? farmsConfig : nonArchivedFarms const pids = farmsToFetch.map((farmToFetch) => farmToFetch.pid) dispatch(fetchFarmsPublicDataAsync(pids)) if (account) { dispatch(fetchFarmUserDataAsync({ account, pids })) } }, [includeArchive, dispatch, slowRefresh, account]) } /** * Fetches the "core" farm data used globally * 251 = CAKE-BNB LP * 252 = BUSD-BNB LP */ export const usePollCoreFarmData = () => { const dispatch = useAppDispatch() const { fastRefresh } = useRefresh() useEffect(() => { dispatch(fetchFarmsPublicDataAsync([251, 252])) }, [dispatch, fastRefresh]) } export const usePollBlockNumber = () => { const dispatch = useAppDispatch() useEffect(() => { const interval = setInterval(async () => { const blockNumber = await simpleRpcProvider.getBlockNumber() dispatch(setBlock(blockNumber)) }, 6000) return () => clearInterval(interval) }, [dispatch]) } // Farms export const useFarms = (): FarmsState => { const farms = useSelector((state: State) => state.farms) return farms } export const useFarmFromPid = (pid): Farm => { const farm = useSelector((state: State) => state.farms.data.find((f) => f.pid === pid)) return farm } export const useFarmFromLpSymbol = (lpSymbol: string): Farm => { const farm = useSelector((state: State) => state.farms.data.find((f) => f.lpSymbol === lpSymbol)) return farm } export const useFarmUser = (pid) => { const farm = useFarmFromPid(pid) return { allowance: farm.userData ? new BigNumber(farm.userData.allowance) : BIG_ZERO, tokenBalance: farm.userData ? new BigNumber(farm.userData.tokenBalance) : BIG_ZERO, stakedBalance: farm.userData ? new BigNumber(farm.userData.stakedBalance) : BIG_ZERO, earnings: farm.userData ? new BigNumber(farm.userData.earnings) : BIG_ZERO, } } // Return a farm for a given token symbol. The farm is filtered based on attempting to return a farm with a quote token from an array of preferred quote tokens export const useFarmFromTokenSymbol = (tokenSymbol: string, preferredQuoteTokens?: string[]): Farm => { const farms = useSelector((state: State) => state.farms.data.filter((farm) => farm.token.symbol === tokenSymbol)) const filteredFarm = filterFarmsByQuoteToken(farms, preferredQuoteTokens) return filteredFarm } // Return the base token price for a farm, from a given pid export const useBusdPriceFromPid = (pid: number): BigNumber => { const farm = useFarmFromPid(pid) return farm && new BigNumber(farm.token.busdPrice) } export const useBusdPriceFromToken = (tokenSymbol: string): BigNumber => { const tokenFarm = useFarmFromTokenSymbol(tokenSymbol) const tokenPrice = useBusdPriceFromPid(tokenFarm?.pid) return tokenPrice } export const useLpTokenPrice = (symbol: string) => { const farm = useFarmFromLpSymbol(symbol) const farmTokenPriceInUsd = useBusdPriceFromPid(farm.pid) let lpTokenPrice = BIG_ZERO if (farm.lpTotalSupply && farm.lpTotalInQuoteToken) { // Total value of base token in LP const valueOfBaseTokenInFarm = farmTokenPriceInUsd.times(farm.tokenAmountTotal) // Double it to get overall value in LP const overallValueOfAllTokensInFarm = valueOfBaseTokenInFarm.times(2) // Divide total value of all tokens, by the number of LP tokens const totalLpTokens = getBalanceAmount(new BigNumber(farm.lpTotalSupply)) lpTokenPrice = overallValueOfAllTokensInFarm.div(totalLpTokens) } return lpTokenPrice } // Pools export const useFetchPublicPoolsData = () => { const dispatch = useAppDispatch() const { slowRefresh } = useRefresh() useEffect(() => { const fetchPoolsPublicData = async () => { const blockNumber = await simpleRpcProvider.getBlockNumber() dispatch(fetchPoolsPublicDataAsync(blockNumber)) } fetchPoolsPublicData() dispatch(fetchPoolsStakingLimitsAsync()) }, [dispatch, slowRefresh]) } export const usePools = (account): { pools: Pool[]; userDataLoaded: boolean } => { const { fastRefresh } = useRefresh() const dispatch = useAppDispatch() useEffect(() => { if (account) { dispatch(fetchPoolsUserDataAsync(account)) } }, [account, dispatch, fastRefresh]) const { pools, userDataLoaded } = useSelector((state: State) => ({ pools: state.pools.data, userDataLoaded: state.pools.userDataLoaded, })) return { pools: pools.map(transformPool), userDataLoaded } } export const usePoolFromPid = (sousId: number): Pool => { const pool = useSelector((state: State) => state.pools.data.find((p) => p.sousId === sousId)) return transformPool(pool) } // board // boards export const useBoards = (): Boards[] => { const boards = useSelector((state: State) => state.boards.data) return boards } export const useBoardsFromPid = (pid): Boards => { const node = useSelector((state: State) => state.boards.data.find((p) => p.pid === pid)) return node } export const useNodeUser = (pid) => { const node = useBoardsFromPid(pid) return { allowance: node.userData ? new BigNumber(node.userData.allowance) : new BigNumber(0), tokenBalance: node.userData ? new BigNumber(node.userData.tokenBalance) : new BigNumber(0), stakedBalance: node.userData ? new BigNumber(node.userData.stakedBalance) : new BigNumber(0), unlockTime: node.userData ? node.userData.unlockTime : 0, xCandyBalance: node.userData ? node.userData.xCandyBalance : 0, } } // cake export const useFetchCakeVault = () => { const { account } = useWeb3React() const { fastRefresh } = useRefresh() const dispatch = useAppDispatch() useEffect(() => { dispatch(fetchCakeVaultPublicData()) }, [dispatch, fastRefresh]) useEffect(() => { dispatch(fetchCakeVaultUserData({ account })) }, [dispatch, fastRefresh, account]) useEffect(() => { dispatch(fetchCakeVaultFees()) }, [dispatch]) } export const useCakeVault = () => { const { totalShares: totalSharesAsString, pricePerFullShare: pricePerFullShareAsString, totalCakeInVault: totalCakeInVaultAsString, estimatedCakeBountyReward: estimatedCakeBountyRewardAsString, totalPendingCakeHarvest: totalPendingCakeHarvestAsString, fees: { performanceFee, callFee, withdrawalFee, withdrawalFeePeriod }, userData: { isLoading, userShares: userSharesAsString, cakeAtLastUserAction: cakeAtLastUserActionAsString, lastDepositedTime, lastUserActionTime, }, } = useSelector((state: State) => state.pools.cakeVault) const estimatedCakeBountyReward = useMemo(() => { return new BigNumber(estimatedCakeBountyRewardAsString) }, [estimatedCakeBountyRewardAsString]) const totalPendingCakeHarvest = useMemo(() => { return new BigNumber(totalPendingCakeHarvestAsString) }, [totalPendingCakeHarvestAsString]) const totalShares = useMemo(() => { return new BigNumber(totalSharesAsString) }, [totalSharesAsString]) const pricePerFullShare = useMemo(() => { return new BigNumber(pricePerFullShareAsString) }, [pricePerFullShareAsString]) const totalCakeInVault = useMemo(() => { return new BigNumber(totalCakeInVaultAsString) }, [totalCakeInVaultAsString]) const userShares = useMemo(() => { return new BigNumber(userSharesAsString) }, [userSharesAsString]) const cakeAtLastUserAction = useMemo(() => { return new BigNumber(cakeAtLastUserActionAsString) }, [cakeAtLastUserActionAsString]) return { totalShares, pricePerFullShare, totalCakeInVault, estimatedCakeBountyReward, totalPendingCakeHarvest, fees: { performanceFee, callFee, withdrawalFee, withdrawalFeePeriod, }, userData: { isLoading, userShares, cakeAtLastUserAction, lastDepositedTime, lastUserActionTime, }, } } // Profile export const useFetchProfile = () => { const { account } = useWeb3React() const dispatch = useAppDispatch() useEffect(() => { dispatch(fetchProfile(account)) }, [account, dispatch]) } export const useProfile = () => { const { isInitialized, isLoading, data, hasRegistered }: ProfileState = useSelector((state: State) => state.profile) return { profile: data, hasProfile: isInitialized && hasRegistered, isInitialized, isLoading } } // Teams export const useTeam = (id: number) => { const team: Team = useSelector((state: State) => state.teams.data[id]) const dispatch = useAppDispatch() useEffect(() => { dispatch(fetchTeam(id)) }, [id, dispatch]) return team } export const useTeams = () => { const { isInitialized, isLoading, data }: TeamsState = useSelector((state: State) => state.teams) const dispatch = useAppDispatch() useEffect(() => { dispatch(fetchTeams()) }, [dispatch]) return { teams: data, isInitialized, isLoading } } // Achievements export const useFetchAchievements = () => { const { account } = useWeb3React() const dispatch = useAppDispatch() useEffect(() => { if (account) { dispatch(fetchAchievements(account)) } }, [account, dispatch]) } export const useAchievements = () => { const achievements: AchievementState['data'] = useSelector((state: State) => state.achievements.data) return achievements } export const usePriceBnbBusd = (): BigNumber => { const bnbBusdFarm = useFarmFromPid(252) return new BigNumber(bnbBusdFarm.quoteToken.busdPrice) } export const usePriceCakeBusd = (): BigNumber => { const cakeBnbFarm = useFarmFromPid(251) return new BigNumber(cakeBnbFarm.token.busdPrice) } // Block export const useBlock = () => { return useSelector((state: State) => state.block) } export const useInitialBlock = () => { return useSelector((state: State) => state.block.initialBlock) } // Predictions export const useGetRounds = () => { const rounds = useSelector((state: State) => state.predictions.rounds) return Object.keys(rounds).reduce((accum, epoch) => { return { ...accum, [epoch]: parseBigNumberObj(rounds[epoch]), } }, {}) as { [key: string]: NodeRound } } export const useGetRound = (epoch: number) => { const round = useSelector((state: State) => state.predictions.rounds[epoch]) return parseBigNumberObj(round) } export const useGetSortedRounds = () => { const roundData = useGetRounds() return orderBy(Object.values(roundData), ['epoch'], ['asc']) } export const useGetBetByEpoch = (account: string, epoch: number) => { const bets = useSelector((state: State) => state.predictions.ledgers) if (!bets[account]) { return null } if (!bets[account][epoch]) { return null } return parseBigNumberObj(bets[account][epoch]) } export const useGetIsClaimable = (epoch) => { const claimableStatuses = useSelector((state: State) => state.predictions.claimableStatuses) return claimableStatuses[epoch] || false } /** * Used to get the range of rounds to poll for */ export const useGetEarliestEpoch = () => { return useSelector((state: State) => { const earliestRound = minBy(Object.values(state.predictions.rounds), 'epoch') return earliestRound?.epoch }) } export const useIsHistoryPaneOpen = () => { return useSelector((state: State) => state.predictions.isHistoryPaneOpen) } export const useIsChartPaneOpen = () => { return useSelector((state: State) => state.predictions.isChartPaneOpen) } export const useGetCurrentEpoch = () => { return useSelector((state: State) => state.predictions.currentEpoch) } export const useGetIntervalBlocks = () => { return useSelector((state: State) => state.predictions.intervalBlocks) } export const useGetBufferBlocks = () => { return useSelector((state: State) => state.predictions.bufferBlocks) } export const useGetTotalIntervalBlocks = () => { const intervalBlocks = useGetIntervalBlocks() const bufferBlocks = useGetBufferBlocks() return intervalBlocks + bufferBlocks } export const useGetCurrentRound = () => { const currentEpoch = useGetCurrentEpoch() const rounds = useGetSortedRounds() return rounds.find((round) => round.epoch === currentEpoch) } export const useGetPredictionsStatus = () => { return useSelector((state: State) => state.predictions.status) } export const useGetHistoryFilter = () => { return useSelector((state: State) => state.predictions.historyFilter) } export const useGetCurrentRoundBlockNumber = () => { return useSelector((state: State) => state.predictions.currentRoundStartBlockNumber) } export const useGetMinBetAmount = () => { const minBetAmount = useSelector((state: State) => state.predictions.minBetAmount) return useMemo(() => ethers.BigNumber.from(minBetAmount), [minBetAmount]) } export const useGetRewardRate = () => { const rewardRate = useSelector((state: State) => state.predictions.rewardRate) return rewardRate / 100 } export const useGetIsFetchingHistory = () => { return useSelector((state: State) => state.predictions.isFetchingHistory) } export const useGetHistory = () => { return useSelector((state: State) => state.predictions.history) } export const useGetHistoryByAccount = (account: string) => { const bets = useGetHistory() return bets ? bets[account] : [] } export const useGetLedgerByRoundId = (account: string, roundId: string) => { const ledgers = useSelector((state: State) => state.predictions.ledgers) if (!ledgers[account]) { return null } if (!ledgers[account][roundId]) { return null } return ledgers[account][roundId] } export const useGetLastOraclePrice = () => { const lastOraclePrice = useSelector((state: State) => state.predictions.lastOraclePrice) return useMemo(() => { return ethers.BigNumber.from(lastOraclePrice) }, [lastOraclePrice]) } // Collectibles export const useGetCollectibles = () => { const { account } = useWeb3React() const dispatch = useAppDispatch() const { isInitialized, isLoading, data } = useSelector((state: State) => state.collectibles) const identifiers = Object.keys(data) useEffect(() => { // Fetch nfts only if we have not done so already if (!isInitialized) { dispatch(fetchWalletNfts(account)) } }, [isInitialized, account, dispatch]) return { isInitialized, isLoading, tokenIds: data, nftsInWallet: Nfts.filter((nft) => identifiers.includes(nft.identifier)), } } // Voting export const useGetProposals = () => { const proposals = useSelector((state: State) => state.voting.proposals) return Object.values(proposals) } export const useGetProposal = (proposalId: string) => { const proposal = useSelector((state: State) => state.voting.proposals[proposalId]) return proposal } export const useGetVotes = (proposalId: string) => { const votes = useSelector((state: State) => state.voting.votes[proposalId]) return votes ? votes.filter((vote) => vote._inValid !== true) : [] } export const useGetVotingStateLoadingStatus = () => { const votingStatus = useSelector((state: State) => state.voting.voteLoadingStatus) return votingStatus } export const useGetProposalLoadingStatus = () => { const votingStatus = useSelector((state: State) => state.voting.proposalLoadingStatus) return votingStatus } // Lottery export const useGetCurrentLotteryId = () => { return useSelector((state: State) => state.lottery.currentLotteryId) } export const useGetUserLotteriesGraphData = () => { return useSelector((state: State) => state.lottery.userLotteryData) } export const useGetUserLotteryGraphRoundById = (lotteryId: string) => { const userLotteriesData = useGetUserLotteriesGraphData() return userLotteriesData.rounds.find((userRound) => userRound.lotteryId === lotteryId) } export const useGetLotteriesGraphData = () => { return useSelector((state: State) => state.lottery.lotteriesData) } export const useGetLotteryGraphDataById = (lotteryId: string) => { const lotteriesData = useGetLotteriesGraphData() return lotteriesData.find((lottery) => lottery.id === lotteryId) } export const useFetchLottery = () => { const { account } = useWeb3React() const { fastRefresh } = useRefresh() const dispatch = useAppDispatch() const currentLotteryId = useGetCurrentLotteryId() useEffect(() => { // get current lottery ID & max ticket buy dispatch(fetchCurrentLotteryId()) }, [dispatch]) useEffect(() => { if (currentLotteryId) { // Get historical lottery data from boards + subgraph dispatch(fetchPublicLotteries({ currentLotteryId })) // get public data for current lottery dispatch(fetchCurrentLottery({ currentLotteryId })) } }, [dispatch, currentLotteryId, fastRefresh]) useEffect(() => { // get user tickets for current lottery, and user lottery subgraph data if (account && currentLotteryId) { dispatch(fetchUserTicketsAndLotteries({ account, currentLotteryId })) } }, [dispatch, currentLotteryId, account]) } export const useLottery = () => { const currentRound = useSelector((state: State) => state.lottery.currentRound) const processedCurrentRound = useProcessLotteryResponse(currentRound) const isTransitioning = useSelector((state: State) => state.lottery.isTransitioning) const currentLotteryId = useGetCurrentLotteryId() const userLotteryData = useGetUserLotteriesGraphData() const lotteriesData = useGetLotteriesGraphData() const maxNumberTicketsPerBuyOrClaimAsString = useSelector( (state: State) => state.lottery.maxNumberTicketsPerBuyOrClaim, ) const maxNumberTicketsPerBuyOrClaim = useMemo(() => { return new BigNumber(maxNumberTicketsPerBuyOrClaimAsString) }, [maxNumberTicketsPerBuyOrClaimAsString]) return { currentLotteryId, maxNumberTicketsPerBuyOrClaim, isTransitioning, userLotteryData, lotteriesData, currentRound: processedCurrentRound, } }