From 52b416fd4b9ab8d48d39c50a7b868e326cd5f82c Mon Sep 17 00:00:00 2001 From: Leandro Boscariol Date: Thu, 23 Dec 2021 02:26:26 -0800 Subject: [PATCH] Claim hooks part 0 (#2032) * Added V_COW Token objects (only rinkeby for now) * Added GNO Token objects (will be used later) * Modded initial set of claim hooks * Restored and commented out original claim hooks on mod file * Added utils for checking what type of user claims are Not yet in use, will be useful later * Temporary hardcoded address for loading the claims in the UI * Removed unecessary `@src` import * Using the chain id enum rather than magic numbers Co-authored-by: Leandro Boscariol --- src/components/claim/ClaimModal.tsx | 4 +- src/custom/constants/tokens/index.ts | 53 +++++++++++++++- src/custom/state/claim/hooks/hooksMod.ts | 54 +++++++--------- src/custom/state/claim/hooks/index.ts | 79 +++++++++++++++++++++++- src/custom/state/claim/hooks/utils.ts | 19 ++++++ 5 files changed, 175 insertions(+), 34 deletions(-) create mode 100644 src/custom/state/claim/hooks/utils.ts diff --git a/src/components/claim/ClaimModal.tsx b/src/components/claim/ClaimModal.tsx index 7055b2653..7a2dce1be 100644 --- a/src/components/claim/ClaimModal.tsx +++ b/src/components/claim/ClaimModal.tsx @@ -9,7 +9,7 @@ import tokenLogo from '../../assets/images/token-logo.png' import { useActiveWeb3React } from '../../hooks/web3' import { ApplicationModal } from '../../state/application/actions' import { useModalOpen, useToggleSelfClaimModal } from '../../state/application/hooks' -import { useClaimCallback, useUserClaimData, useUserUnclaimedAmount } from '../../state/claim/hooks' +import { useClaimCallback, useUserClaimData, useUserUnclaimedAmount } from 'state/claim/hooks' import { useUserHasSubmittedClaim } from '../../state/transactions/hooks' import { CloseIcon, CustomLightSpinner, ExternalLink, TYPE, UniTokenAnimated } from '../../theme' import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' @@ -60,7 +60,7 @@ export default function ClaimModal() { const userClaimData = useUserClaimData(account) // monitor the status of the claim from contracts and txns - const { claimCallback } = useClaimCallback(account) + const { claimCallback } = useClaimCallback('0x82850293A348107E798E5D366c10D1b566145Cd5') // TODO: remove me, hard coded only for testing const unclaimedAmount: CurrencyAmount | undefined = useUserUnclaimedAmount(account) const { claimSubmitted, claimTxn } = useUserHasSubmittedClaim(account ?? undefined) const claimConfirmed = Boolean(claimTxn?.receipt) diff --git a/src/custom/constants/tokens/index.ts b/src/custom/constants/tokens/index.ts index 2a3745e4a..3071dfe68 100644 --- a/src/custom/constants/tokens/index.ts +++ b/src/custom/constants/tokens/index.ts @@ -1,8 +1,10 @@ import { ChainId } from '@uniswap/sdk' -import { WETH9 } from '@uniswap/sdk-core' +import { WETH9, Token } from '@uniswap/sdk-core' import { DAI_RINKEBY, USDC_RINKEBY, USDT_RINKEBY, WBTC_RINKEBY } from 'utils/rinkeby/constants' import { DAI, USDC, USDT, WBTC } from 'constants/tokens' import { USDC_XDAI, /*USDT_XDAI,*/ WBTC_XDAI, WETH_XDAI, WXDAI } from 'utils/xdai/constants' +import { SupportedChainId } from 'constants/chains' +import { V_COW_CONTRACT_ADDRESS } from 'constants/index' export * from './tokensMod' @@ -28,3 +30,52 @@ export const ADDRESS_IMAGE_OVERRIDE = { 'https://raw.githubusercontent.com/1Hive/default-token-list/master/src/assets/xdai/0xe91d153e0b41518a2ce8dd3d7944fa863463a97d/logo.png', [WETH_XDAI.address]: getTrustImage(WETH_ADDRESS_MAINNET), } + +export const V_COW: Record = { + // TODO: enable once contract addresses are added + // [SupportedChainId.MAINNET]: new Token( + // SupportedChainId.MAINNET, + // V_COW_CONTRACT_ADDRESS[SupportedChainId.MAINNET] || '', + // 18, + // 'vCOW', + // 'Virtual CowSwap Token' + // ), + // [SupportedChainId.XDAI]: new Token( + // SupportedChainId.XDAI, + // V_COW_CONTRACT_ADDRESS[SupportedChainId.XDAI] || '', + // 18, + // 'vCOW', + // 'Virtual CowSwap Token' + // ), + [SupportedChainId.RINKEBY]: new Token( + SupportedChainId.RINKEBY, + V_COW_CONTRACT_ADDRESS[SupportedChainId.RINKEBY] || '', + 18, + 'vCOW', + 'Virtual CowSwap Token' + ), +} + +export const GNO: Record = { + [SupportedChainId.MAINNET]: new Token( + SupportedChainId.MAINNET, + '0x6810e776880c02933d47db1b9fc05908e5386b96', + 18, + 'GNO', + 'Gnosis' + ), + [SupportedChainId.XDAI]: new Token( + SupportedChainId.XDAI, + '0x9c58bacc331c9aa871afd802db6379a98e80cedb', + 18, + 'GNO', + 'Gnosis' + ), + [SupportedChainId.RINKEBY]: new Token( + SupportedChainId.RINKEBY, + '0xd0dab4e640d95e9e8a47545598c33e31bdb53c7c', + 18, + 'GNO', + 'Gnosis' + ), +} diff --git a/src/custom/state/claim/hooks/hooksMod.ts b/src/custom/state/claim/hooks/hooksMod.ts index dbe52ae9d..4dd8eb3f1 100644 --- a/src/custom/state/claim/hooks/hooksMod.ts +++ b/src/custom/state/claim/hooks/hooksMod.ts @@ -1,15 +1,17 @@ -import JSBI from 'jsbi' +// import JSBI from 'jsbi' import { CurrencyAmount, Token } from '@uniswap/sdk-core' import { TransactionResponse } from '@ethersproject/providers' // import { useEffect, useState } from 'react' -import { UNI } from 'constants/tokens' +// import { UNI } from 'constants/tokens' import { useActiveWeb3React } from 'hooks/web3' import { useMerkleDistributorContract } from 'hooks/useContract' import { calculateGasMargin } from 'utils/calculateGasMargin' // import { useSingleCallResult } from 'state/multicall/hooks' import { isAddress } from 'utils/index' import { useTransactionAdder } from 'state/enhancedTransactions/hooks' -import { UserClaims } from '.' +import { UserClaims, useUserUnclaimedAmount } from '.' +// import { useSingleCallResult } from '@src/state/multicall/hooks' +export { useUserClaimData } from '@src/state/claim/hooks' // interface UserClaimData { // index: number @@ -158,34 +160,26 @@ export function useUserClaims(account: string | null | undefined): UserClaims | } // check if user is in blob and has not yet claimed UNI -export function useUserHasAvailableClaim(account: string | null | undefined): boolean { - const userClaims = useUserClaims(account) - // const distributorContract = useMerkleDistributorContract() - - // TODO: Go claiming by claiming, and check if claimed or not - // TODO: Should we do a multicall instead, or the contract allows to check multiple claimings at once? - const isClaimedResult = { loading: false, result: [false] } //useSingleCallResult(distributorContract, 'isClaimed', [userClaimData?.index]) - - // user is in blob and contract marks as unclaimed - return Boolean(userClaims && !isClaimedResult.loading && isClaimedResult.result?.[0] === false) -} - -export function useUserUnclaimedAmount(account: string | null | undefined): CurrencyAmount | undefined { - const { chainId } = useActiveWeb3React() - const claims = useUserClaims(account) - const canClaim = useUserHasAvailableClaim(account) - - const uni = chainId ? UNI[chainId] : undefined - if (!uni) return undefined - if (!canClaim || !claims) { - return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(0)) - } - const totalAmount = claims.reduce((acc, claim) => { - return JSBI.add(acc, JSBI.BigInt(claim.amount)) - }, JSBI.BigInt('0')) +// export function useUserHasAvailableClaim(account: string | null | undefined): boolean { +// const userClaimData = useUserClaimData(account) +// const distributorContract = useMerkleDistributorContract() +// const isClaimedResult = useSingleCallResult(distributorContract, 'isClaimed', [userClaimData?.index]) +// // user is in blob and contract marks as unclaimed +// return Boolean(userClaimData && !isClaimedResult.loading && isClaimedResult.result?.[0] === false) +// } - return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(totalAmount)) -} +// export function useUserUnclaimedAmount(account: string | null | undefined): CurrencyAmount | undefined { +// const { chainId } = useActiveWeb3React() +// const userClaimData = useUserClaimData(account) +// const canClaim = useUserHasAvailableClaim(account) +// +// const uni = chainId ? UNI[chainId] : undefined +// if (!uni) return undefined +// if (!canClaim || !userClaimData) { +// return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(0)) +// } +// return CurrencyAmount.fromRawAmount(uni, JSBI.BigInt(userClaimData.amount)) +// } export function useClaimCallback(account: string | null | undefined): { claimCallback: () => Promise diff --git a/src/custom/state/claim/hooks/index.ts b/src/custom/state/claim/hooks/index.ts index 8b6ea1ac4..b8d58d9b8 100644 --- a/src/custom/state/claim/hooks/index.ts +++ b/src/custom/state/claim/hooks/index.ts @@ -1,3 +1,13 @@ +import { useMemo } from 'react' +import JSBI from 'jsbi' +import { CurrencyAmount, Token } from '@uniswap/sdk-core' + +import { useUserClaims } from 'state/claim/hooks/hooksMod' +import { useVCowContract } from 'hooks/useContract' +import { useSingleContractMultipleData } from 'state/multicall/hooks' +import { useActiveWeb3React } from 'hooks/web3' +import { V_COW } from 'constants/tokens' + export * from './hooksMod' export type ClaimType = @@ -8,12 +18,79 @@ export type ClaimType = | 'UserOption' // paid, with vesting, must use Native currency, can be available on both mainnet and gchain | 'Investor' // paid, with vesting, must use USDC, only on mainnet +export const FREE_CLAIM_TYPES: ClaimType[] = ['Airdrop', 'Team', 'Advisor'] +export const PAID_CLAIM_TYPES: ClaimType[] = ['GnoOption', 'UserOption', 'Investor'] + export interface UserClaimData { index: number amount: string proof: string[] type: ClaimType - // TODO: Either add the missing fields, or add https://github.com/gnosis/gp-v2-token type } +type Account = string | null | undefined + export type UserClaims = UserClaimData[] + +/** + * Gets an array of available claim + * + * @param account + */ +export function useUserAvailableClaims(account: Account): UserClaims { + const userClaims = useUserClaims(account) + const contract = useVCowContract() + + // build list of parameters, with the claim index + const claimIndexes = userClaims?.map(({ index }) => [index]) || [] + + const results = useSingleContractMultipleData(contract, 'isClaimed', claimIndexes) + + console.log(`useUserAvailableClaims::re-render`, userClaims, claimIndexes, results) + + return useMemo(() => { + if (!userClaims || userClaims.length === 0) { + // user has no claims + return [] + } + + return results.reduce((acc, result, index) => { + if ( + result.valid && // result is valid + !result.loading && // result is not loading + result.result?.[0] === false // result is false, meaning not claimed + ) { + acc.push(userClaims[index]) // get the claim not yet claimed + } + return acc + }, []) + }, [results, userClaims]) +} + +/** + * Returns whether the user has any available claim + * Syntactic sugar on top of `useUserAvailableClaims` + * + * @param account + */ +export function useUserHasAvailableClaim(account: Account): boolean { + const availableClaims = useUserAvailableClaims(account) + + return availableClaims.length > 0 +} + +export function useUserUnclaimedAmount(account: string | null | undefined): CurrencyAmount | undefined { + const { chainId } = useActiveWeb3React() + const claims = useUserAvailableClaims(account) + + const vCow = chainId ? V_COW[chainId] : undefined + if (!vCow) return undefined + if (!claims || claims.length === 0) { + return CurrencyAmount.fromRawAmount(vCow, JSBI.BigInt(0)) + } + const totalAmount = claims.reduce((acc, claim) => { + return JSBI.add(acc, JSBI.BigInt(claim.amount)) + }, JSBI.BigInt('0')) + + return CurrencyAmount.fromRawAmount(vCow, JSBI.BigInt(totalAmount)) +} diff --git a/src/custom/state/claim/hooks/utils.ts b/src/custom/state/claim/hooks/utils.ts new file mode 100644 index 000000000..4b5e2e0e8 --- /dev/null +++ b/src/custom/state/claim/hooks/utils.ts @@ -0,0 +1,19 @@ +import { FREE_CLAIM_TYPES, PAID_CLAIM_TYPES, UserClaims } from 'state/claim/hooks/index' + +/** + * Helper function to check whether any claim is an investment option + * + * @param claims + */ +export function hasPaidClaim(claims: UserClaims | null): boolean { + return claims?.some((claim) => claim.type in PAID_CLAIM_TYPES) || false +} + +/** + * Helper function to check whether any claim is an airdrop option + * + * @param claims + */ +export function hasFreeClaim(claims: UserClaims | null): boolean { + return claims?.some((claim) => claim.type in FREE_CLAIM_TYPES) || false +}