import type { MinterData, MintWrapperData, QuarryData, RegistryData, RewarderData, } from "@quarryprotocol/quarry-sdk"; import { useToken } from "@saberhq/sail"; import { isNotNull, mapSome } from "@saberhq/solana-contrib"; import type { ProgramAccount, Token } from "@saberhq/token-utils"; import { TokenAmount } from "@saberhq/token-utils"; import { useSolana } from "@saberhq/use-solana"; import { PublicKey } from "@solana/web3.js"; import { groupBy } from "lodash-es"; import { useMemo } from "react"; import { useQuery } from "react-query"; import { createContainer } from "unstated-next"; import type { ParsedQuarryMetaWithReplicas } from "../.."; import { createMinterAddressQuery, createRegistryAddressQuery, createRewarderQuarryAddressesQuery, useBatchedQuarries, useMinterData, useMintWrapperData, useRegistryData, useRewarderConfig, useRewarderData, } from "../.."; import { getQuarryOrigin } from "../../utils/url"; /** * Information about a Quarry. */ export interface QuarryInfo { key: PublicKey; quarry: ProgramAccount; quarryMeta?: ParsedQuarryMetaWithReplicas; quarryAppDepositLink: string | null; } export interface UseRewarderArgs { /** * Key of the rewarder. */ rewarderKey?: PublicKey; /** * Keys of known staked tokens, in case one is not using the registry. * * If this is specified, this is merged with the registry. */ stakedTokenMints?: PublicKey[]; } export interface UseRewarder { /** * Key of the rewarder. */ rewarderKey: PublicKey; rewarder?: ProgramAccount | null; mintWrapper?: ProgramAccount | null; rewardToken?: Token | null; quarries?: readonly QuarryInfo[]; annualRewardsRate: TokenAmount | null; minter: ProgramAccount | null | undefined; registry: ProgramAccount | null | undefined; /** * True if the quarries are still loading. */ quarriesLoading: boolean; /** * Loading status of every element. */ isLoading: { registry: boolean; rewarder: boolean; quarries: boolean; }; /** * List of the mints of all staked tokens. */ allStakedTokenMints?: PublicKey[]; } const useRewarderInternal = (args: UseRewarderArgs = {}): UseRewarder => { const { rewarderKey, stakedTokenMints } = args; if (!rewarderKey) { throw new Error("no rewarder key"); } const { data: rewarderConfig } = useRewarderConfig(rewarderKey.toString()); const { data: registryKey } = useQuery( createRegistryAddressQuery(rewarderKey) ); const { data: registry, isLoading: registryIsLoading } = useRegistryData(registryKey); const { data: rewarder, isLoading: rewarderIsLoading } = useRewarderData(rewarderKey); const { data: rewardToken } = useToken( mapSome(rewarder, (r) => r.account.rewardsTokenMint) ); const allStakedTokenMints = useMemo(() => { // wait for registry to load if (registry === undefined || rewarderConfig === undefined) { return undefined; } const configMints = rewarderConfig?.quarries.map((q) => q.stakedToken.mint); const mintsMinusConfig = [ ...(stakedTokenMints ?? []), ...(registry?.account.tokens ?? []).filter( (pk) => !pk.equals(PublicKey.default) && !stakedTokenMints?.find((m) => m.equals(pk)) ), ]; return [ ...mintsMinusConfig, ...(configMints ?? []).filter( (m) => !mintsMinusConfig.find((k) => k.equals(m)) ), ]; }, [registry, rewarderConfig, stakedTokenMints]); const { data: quarryKeys } = useQuery( createRewarderQuarryAddressesQuery(rewarderKey, allStakedTokenMints ?? []) ); const { data: quarriesData, isLoading: quarriesIsLoading } = useBatchedQuarries(quarryKeys); const rewarderListQuarriesByStakedTokenMint = useMemo( () => mapSome(rewarderConfig, (cfg) => groupBy(cfg.quarries ?? [], (quarry) => quarry.stakedToken.mint) ), [rewarderConfig] ); const { network } = useSolana(); const quarries = useMemo(() => { return quarriesData ?.map((quarryData): QuarryInfo | null => { if (!quarryData) { return null; } const quarryMeta = rewarderListQuarriesByStakedTokenMint?.[ quarryData.account.tokenMintKey.toString() ]?.[0]; const quarryAppDepositLink = quarryMeta ? `${getQuarryOrigin( network )}/#/rewarders/${rewarderKey.toString()}/quarries/${quarryMeta.quarry.toString()}/deposit` : null; return { key: quarryData.publicKey, quarry: quarryData, quarryMeta, quarryAppDepositLink, }; }) .filter(isNotNull); }, [ network, quarriesData, rewarderKey, rewarderListQuarriesByStakedTokenMint, ]); const annualRewardsRate = useMemo(() => { if (!rewardToken || !rewarder) { return null; } return new TokenAmount(rewardToken, rewarder.account.annualRewardsRate); }, [rewardToken, rewarder]); const mintWrapper = useMintWrapperData(rewarder?.account.mintWrapper); const { data: minterKey } = useQuery( createMinterAddressQuery( rewarder ? rewarder.account.mintWrapper : null, rewarder ? rewarder.publicKey : null ) ); const { data: minterData, isLoading: minterIsLoading } = useMinterData(minterKey); const quarriesLoading = registryIsLoading || rewarderIsLoading || quarriesIsLoading; const isLoading = { registry: registryIsLoading, rewarder: rewarderIsLoading, quarries: quarriesIsLoading, minter: minterIsLoading, }; return { rewarderKey, rewarder, rewardToken, allStakedTokenMints, quarries, annualRewardsRate, mintWrapper: mintWrapper.data, minter: minterData, registry, quarriesLoading, isLoading, }; }; export const { Provider: RewarderProvider, useContainer: useRewarder } = createContainer(useRewarderInternal);