From ec258343fbb5dc0fe2ebe6a3885bde4b67035eb7 Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Sun, 20 Feb 2022 01:35:33 +0800 Subject: [PATCH 1/8] feat: update whitelist check function when opening maskbox --- .../src/plugins/MaskBox/SNSAdaptor/index.tsx | 3 +- .../mask/src/plugins/MaskBox/apis/index.ts | 1 + .../src/plugins/MaskBox/apis/merkleProof.ts | 19 +++++++++ .../mask/src/plugins/MaskBox/constants.ts | 2 + .../src/plugins/MaskBox/hooks/useContext.ts | 19 ++++++++- .../plugins/MaskBox/hooks/useGetRootHash.ts | 16 ++++++++ .../plugins/MaskBox/hooks/useIsWhitelisted.ts | 4 +- .../MaskBox/hooks/useOpenBoxTransaction.ts | 3 +- packages/mask/src/plugins/MaskBox/type.ts | 2 + packages/web3-contracts/abis/MaskBox.json | 41 +++++++++++++++++++ 10 files changed, 104 insertions(+), 6 deletions(-) create mode 100644 packages/mask/src/plugins/MaskBox/apis/merkleProof.ts create mode 100644 packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx index bcdc72943447..f40422d28ea9 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx @@ -32,6 +32,7 @@ export default sns function Renderer(props: React.PropsWithChildren<{ url: string }>) { const [, chainId] = props.url.match(/chain=(\d+)/i) ?? [] const [, boxId] = props.url.match(/box=(\d+)/i) ?? [] + const [, qualification] = props.url.match(/qualification=([\dA-Za-z]+)/) ?? [] const shouldNotRender = !chainId || !boxId usePluginWrapper(!shouldNotRender) @@ -39,7 +40,7 @@ function Renderer(props: React.PropsWithChildren<{ url: string }>) { return ( - + diff --git a/packages/mask/src/plugins/MaskBox/apis/index.ts b/packages/mask/src/plugins/MaskBox/apis/index.ts index d6a8c7357c73..137f34bb9057 100644 --- a/packages/mask/src/plugins/MaskBox/apis/index.ts +++ b/packages/mask/src/plugins/MaskBox/apis/index.ts @@ -1 +1,2 @@ export * from './rss3' +export * from './merkleProof' diff --git a/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts new file mode 100644 index 000000000000..5df0bdf146bd --- /dev/null +++ b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts @@ -0,0 +1,19 @@ +import { MERKLE_PROOF_ENDPOINT } from '../constants' + +export const getHashRoot = async (leaf: string, root: string) => { + let response: { proof?: string[]; message?: string; module?: string } = {} + let ok = false + try { + const res = await fetch(`${MERKLE_PROOF_ENDPOINT}?leaf=${leaf}&root=${root}`) + response = await res.json() + ok = res.ok + } catch (err: any) { + console.log('error', err) + } + + if (!ok) { + throw new Error(response?.message) + } + + return response +} diff --git a/packages/mask/src/plugins/MaskBox/constants.ts b/packages/mask/src/plugins/MaskBox/constants.ts index 989e33b9d1f4..49e7183f3fed 100644 --- a/packages/mask/src/plugins/MaskBox/constants.ts +++ b/packages/mask/src/plugins/MaskBox/constants.ts @@ -2,3 +2,5 @@ import { PluginId } from '@masknet/plugin-infra' export const PLUGIN_ID = PluginId.MaskBox export const RSS3_ENDPOINT = 'https://hub.pass3.me' +export const MERKLE_PROOF_ENDPOINT = + 'https://lf8d031acj.execute-api.ap-east-1.amazonaws.com/api/v1/merkle_tree/leaf_exists' diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index 1587d1ef676d..dc44398c25a4 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -1,5 +1,6 @@ import { useEffect, useMemo, useState, useCallback } from 'react' import { useAsyncRetry } from 'react-use' +import Web3 from 'web3' import fromUnixTime from 'date-fns/fromUnixTime' import addDays from 'date-fns/addDays' import subDays from 'date-fns/subDays' @@ -28,6 +29,7 @@ import { import type { NonPayableTx } from '@masknet/web3-contracts/types/types' import { BoxInfo, BoxState } from '../type' import { useMaskBoxInfo } from './useMaskBoxInfo' +import { useGetRootHash } from './useGetRootHash' import { useMaskBoxStatus } from './useMaskBoxStatus' import { useMaskBoxCreationSuccessEvent } from './useMaskBoxCreationSuccessEvent' import { useMaskBoxTokensForSale } from './useMaskBoxTokensForSale' @@ -38,7 +40,7 @@ import { useMaskBoxMetadata } from './useMaskBoxMetadata' import { useIsWhitelisted } from './useIsWhitelisted' import { isGreaterThanOrEqualTo, isLessThanOrEqualTo, isZero, multipliedBy, useBeat } from '@masknet/web3-shared-base' -function useContext(initialState?: { boxId: string }) { +function useContext(initialState?: { boxId: string; qualification: string }) { const now = new Date() const beat = useBeat() const account = useAccount() @@ -47,6 +49,7 @@ function useContext(initialState?: { boxId: string }) { const { MASK_BOX_CONTRACT_ADDRESS } = useMaskBoxConstants() const [boxId, setBoxId] = useState(initialState?.boxId ?? '') + const qualification = initialState?.qualification?.replace(/0x/, '') const [paymentTokenAddress, setPaymentTokenAddress] = useState('') // #region the box info @@ -56,6 +59,14 @@ function useContext(initialState?: { boxId: string }) { loading: loadingMaskBoxInfo, retry: retryMaskBoxInfo, } = useMaskBoxInfo(boxId) + + // #get hashroot + const { value, error: errorRootHash } = useGetRootHash( + qualification || '0x0000000000000000000000000000000000000000000000000000000000000000', + ) + const web3 = new Web3() + const hashroot = value ? web3.eth.abi.encodeParameters(['bytes32[]'], [value?.proof?.map((p) => '0x' + p)]) : '0x00' + const { value: maskBoxStatus = null, error: errorMaskBoxStatus, @@ -135,6 +146,7 @@ function useContext(initialState?: { boxId: string }) { ]) const boxState = useMemo(() => { + if (errorRootHash?.message === 'leaf not found') return BoxState.NOT_IN_WHITELIST if (errorMaskBoxInfo || errorMaskBoxStatus || errorBoxInfo) return BoxState.ERROR if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo) { if (!maskBoxInfo && !boxInfo) return BoxState.UNKNOWN @@ -149,7 +161,7 @@ function useContext(initialState?: { boxId: string }) { return BoxState.READY }, [boxInfo, loadingBoxInfo, errorBoxInfo, maskBoxInfo, loadingMaskBoxInfo, errorMaskBoxInfo, beat]) - const isWhitelisted = useIsWhitelisted(boxInfo?.qualificationAddress, account) + const isWhitelisted = useIsWhitelisted(boxInfo?.qualificationAddress, account, hashroot) const isQualifiedByContract = boxInfo?.qualificationAddress && !isZeroAddress(boxInfo?.qualificationAddress) ? isWhitelisted : true @@ -179,6 +191,8 @@ function useContext(initialState?: { boxId: string }) { return countdown ? `Start sale in ${countdown}` : 'Loading...' case BoxState.SOLD_OUT: return 'Sold Out' + case BoxState.NOT_IN_WHITELIST: + return 'You are not in whitelist' case BoxState.DRAWED_OUT: return 'Purchase limit exceeded' case BoxState.ERROR: @@ -255,6 +269,7 @@ function useContext(initialState?: { boxId: string }) { paymentTokenIndex, paymentTokenPrice, paymentTokenDetailed, + hashroot, openBoxTransactionOverrides, ) const { value: erc20Allowance, retry: retryAllowance } = useERC20TokenAllowance( diff --git a/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts b/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts new file mode 100644 index 000000000000..510f82690032 --- /dev/null +++ b/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts @@ -0,0 +1,16 @@ +import { useAsyncRetry } from 'react-use' +import { useWallet } from '@masknet/web3-shared-evm' +import { getHashRoot } from '../apis' + +export function useGetRootHash(qualification: string) { + const wallet = useWallet() + qualification?.replace(/0x/, '') + const leafArray = wallet?.address + ?.replace(/0x/, '') + ?.match(/.{1,2}/g) + ?.map((byte) => Number.parseInt(byte, 16)) + const leaf = encodeURIComponent(Buffer.from(new Uint8Array(leafArray as number[])).toString('base64')) + return useAsyncRetry(async () => { + return getHashRoot(leaf, qualification) + }, [qualification]) +} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts b/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts index 022e73935bda..2839679b853d 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts @@ -2,11 +2,11 @@ import { useMemo } from 'react' import { useAsyncRetry } from 'react-use' import { useWhitelistContract } from './useWhitelistContract' -export function useIsWhitelisted(address?: string, account?: string) { +export function useIsWhitelisted(address?: string, account?: string, qualification?: string) { const contract = useWhitelistContract(address) const result = useAsyncRetry(async () => { if (!contract || !account) return null - return contract.methods.is_qualified(account, '0x00').call() + return contract.methods.is_qualified(account, qualification || '0x00').call() }, [account, contract]) return useMemo(() => result.value ?? { qualified: false, error_msg: '' }, [result.value]) diff --git a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts index 91f772589fe8..f11b6a2eafaf 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts @@ -10,6 +10,7 @@ export function useOpenBoxTransaction( paymentTokenIndex: number, paymentTokenPrice: string, paymentTokenDetailed: FungibleTokenDetailed | null, + qualification: string, overrides?: NonPayableTx | null, ) { const account = useAccount() @@ -25,7 +26,7 @@ export function useOpenBoxTransaction( ? multipliedBy(paymentTokenPrice, amount).toFixed() : undefined, }, - method: maskBoxContract.methods.openBox(boxId, amount, paymentTokenIndex, '0x0'), + method: maskBoxContract.methods.openBox(boxId, amount, paymentTokenIndex, qualification || '0x00'), } }, [account, amount, boxId, maskBoxContract, paymentTokenIndex, paymentTokenPrice, paymentTokenDetailed, overrides]) } diff --git a/packages/mask/src/plugins/MaskBox/type.ts b/packages/mask/src/plugins/MaskBox/type.ts index 3e0bb2b64368..59af90155b15 100644 --- a/packages/mask/src/plugins/MaskBox/type.ts +++ b/packages/mask/src/plugins/MaskBox/type.ts @@ -20,6 +20,8 @@ export enum BoxState { ERROR = 7, /** 404 */ NOT_FOUND = 8, + /** leaf not found */ + NOT_IN_WHITELIST = 9, } export interface PaymentOption { diff --git a/packages/web3-contracts/abis/MaskBox.json b/packages/web3-contracts/abis/MaskBox.json index c2edd9713d53..f70d7f424344 100644 --- a/packages/web3-contracts/abis/MaskBox.json +++ b/packages/web3-contracts/abis/MaskBox.json @@ -311,6 +311,11 @@ "internalType": "uint256", "name": "holder_min_token_amount", "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "qualification_data", + "type": "bytes32" } ], "name": "createBox", @@ -362,6 +367,11 @@ "internalType": "uint256", "name": "holder_min_token_amount", "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "qualification_data", + "type": "bytes32" } ], "stateMutability": "view", @@ -529,6 +539,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addrs", + "type": "address[]" + } + ], + "name": "removeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -549,6 +572,24 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "box_id", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "qualification_data", + "type": "bytes32" + } + ], + "name": "setQualificationData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { From 24efc316eb2169859101ab4a7f490468e43a0e82 Mon Sep 17 00:00:00 2001 From: guanbinrui <52657989+guanbinrui@users.noreply.github.com> Date: Mon, 21 Feb 2022 14:23:19 +0800 Subject: [PATCH 2/8] refactor: adjust naming & code style (#5691) * refactor: adjust naming & code style * fix: fetch the proof * refactor: maskbox qualification * fix: lint errors * refactor: no more depend on proof API * fix: the type of proof * refactor: more safer code Co-authored-by: UncleBill Co-authored-by: UncleBill --- cspell.json | 3 +- .../SNSAdaptor/components/PreviewCard.tsx | 9 +- .../src/plugins/MaskBox/SNSAdaptor/index.tsx | 4 +- .../src/plugins/MaskBox/apis/merkleProof.ts | 25 ++- .../src/plugins/MaskBox/hooks/useContext.ts | 108 ++++++++----- .../plugins/MaskBox/hooks/useGetRootHash.ts | 16 -- .../plugins/MaskBox/hooks/useIsWhitelisted.ts | 13 -- .../hooks/useMaskBoxQualificationContract.ts | 8 + .../plugins/MaskBox/hooks/useMerkleProof.ts | 16 ++ .../MaskBox/hooks/useOpenBoxTransaction.ts | 4 +- .../plugins/MaskBox/hooks/useQualification.ts | 13 ++ .../MaskBox/hooks/useWhitelistContract.ts | 8 - packages/mask/src/plugins/MaskBox/type.ts | 4 + .../abis/MaskBoxQualification.json | 23 +++ .../web3-contracts/abis/MaskBoxWhiteList.json | 148 ------------------ packages/web3-contracts/types/MaskBox.d.ts | 10 ++ .../types/MaskBoxQualification.d.ts | 43 +++++ .../types/MaskBoxWhiteList.d.ts | 70 --------- 18 files changed, 202 insertions(+), 323 deletions(-) delete mode 100644 packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts delete mode 100644 packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts create mode 100644 packages/mask/src/plugins/MaskBox/hooks/useMaskBoxQualificationContract.ts create mode 100644 packages/mask/src/plugins/MaskBox/hooks/useMerkleProof.ts create mode 100644 packages/mask/src/plugins/MaskBox/hooks/useQualification.ts delete mode 100644 packages/mask/src/plugins/MaskBox/hooks/useWhitelistContract.ts create mode 100644 packages/web3-contracts/abis/MaskBoxQualification.json delete mode 100644 packages/web3-contracts/abis/MaskBoxWhiteList.json create mode 100644 packages/web3-contracts/types/MaskBoxQualification.d.ts delete mode 100644 packages/web3-contracts/types/MaskBoxWhiteList.d.ts diff --git a/cspell.json b/cspell.json index 5bdfcc6afe7e..5d841b2f8f2b 100644 --- a/cspell.json +++ b/cspell.json @@ -258,7 +258,8 @@ "xlarge", "xlink", "zerion", - "zubin" + "zubin", + "merkel" ], "ignoreWords": [ "aicanft", diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/PreviewCard.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/PreviewCard.tsx index fa95b82762d4..d1bb7ccf4ae8 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/PreviewCard.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/PreviewCard.tsx @@ -51,8 +51,6 @@ export function PreviewCard(props: PreviewCardProps) { const { boxId, boxState, - isQualified, - holderToken, boxStateMessage, boxInfo, boxMetadata, @@ -198,14 +196,9 @@ export function PreviewCard(props: PreviewCardProps) { size="medium" fullWidth variant="contained" - disabled={boxState !== BoxState.READY || !isQualified} + disabled={boxState !== BoxState.READY} onClick={() => setOpenDrawDialog(true)}> {(() => { - if (!isQualified) { - const { symbol, decimals } = holderToken ?? {} - const tokenPrice = `${formatBalance(boxInfo?.holderMinTokenAmount, decimals)} $${symbol}` - return `You must hold at least ${tokenPrice}` - } return boxState === BoxState.READY && paymentTokenAddress ? ( <> {boxStateMessage} ( diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx index f40422d28ea9..0de20484d5f4 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx @@ -32,7 +32,7 @@ export default sns function Renderer(props: React.PropsWithChildren<{ url: string }>) { const [, chainId] = props.url.match(/chain=(\d+)/i) ?? [] const [, boxId] = props.url.match(/box=(\d+)/i) ?? [] - const [, qualification] = props.url.match(/qualification=([\dA-Za-z]+)/) ?? [] + const [, hashRoot] = props.url.match(/qualification=([\dA-Za-z]+)/) ?? [] const shouldNotRender = !chainId || !boxId usePluginWrapper(!shouldNotRender) @@ -40,7 +40,7 @@ function Renderer(props: React.PropsWithChildren<{ url: string }>) { return ( - + diff --git a/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts index 5df0bdf146bd..cf50645aa79b 100644 --- a/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts +++ b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts @@ -1,19 +1,12 @@ +import urlcat from 'urlcat' import { MERKLE_PROOF_ENDPOINT } from '../constants' -export const getHashRoot = async (leaf: string, root: string) => { - let response: { proof?: string[]; message?: string; module?: string } = {} - let ok = false - try { - const res = await fetch(`${MERKLE_PROOF_ENDPOINT}?leaf=${leaf}&root=${root}`) - response = await res.json() - ok = res.ok - } catch (err: any) { - console.log('error', err) - } - - if (!ok) { - throw new Error(response?.message) - } - - return response +export async function getMerkleProof(leaf: string, root: string) { + const response = await fetch( + urlcat(MERKLE_PROOF_ENDPOINT, { + leaf, + root: root.replace(/^0x/, ''), + }), + ) + return (await response.json()) as { proof?: string[]; message?: string; module?: string } } diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index dc44398c25a4..d6bfa467027d 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState, useCallback } from 'react' +import * as ABICoder from 'web3-eth-abi' import { useAsyncRetry } from 'react-use' -import Web3 from 'web3' import fromUnixTime from 'date-fns/fromUnixTime' import addDays from 'date-fns/addDays' import subDays from 'date-fns/subDays' @@ -25,11 +25,12 @@ import { ZERO_ADDRESS, isZeroAddress, isNativeTokenAddress, + formatBalance, } from '@masknet/web3-shared-evm' import type { NonPayableTx } from '@masknet/web3-contracts/types/types' import { BoxInfo, BoxState } from '../type' import { useMaskBoxInfo } from './useMaskBoxInfo' -import { useGetRootHash } from './useGetRootHash' +import { useMerkelProof } from './useMerkleProof' import { useMaskBoxStatus } from './useMaskBoxStatus' import { useMaskBoxCreationSuccessEvent } from './useMaskBoxCreationSuccessEvent' import { useMaskBoxTokensForSale } from './useMaskBoxTokensForSale' @@ -37,19 +38,27 @@ import { useMaskBoxPurchasedTokens } from './useMaskBoxPurchasedTokens' import { formatCountdown } from '../helpers/formatCountdown' import { useOpenBoxTransaction } from './useOpenBoxTransaction' import { useMaskBoxMetadata } from './useMaskBoxMetadata' -import { useIsWhitelisted } from './useIsWhitelisted' -import { isGreaterThanOrEqualTo, isLessThanOrEqualTo, isZero, multipliedBy, useBeat } from '@masknet/web3-shared-base' - -function useContext(initialState?: { boxId: string; qualification: string }) { +import { useQualification } from './useQualification' +import { + isGreaterThan, + isGreaterThanOrEqualTo, + isLessThanOrEqualTo, + isZero, + multipliedBy, + useBeat, +} from '@masknet/web3-shared-base' + +function useContext(initialState?: { boxId: string; hashRoot: string }) { const now = new Date() const beat = useBeat() const account = useAccount() const chainId = useChainId() const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(ChainId.Mainnet) const { MASK_BOX_CONTRACT_ADDRESS } = useMaskBoxConstants() + const coder = ABICoder as unknown as ABICoder.AbiCoder const [boxId, setBoxId] = useState(initialState?.boxId ?? '') - const qualification = initialState?.qualification?.replace(/0x/, '') + const [hashRoot, setHashRoot] = useState(initialState?.hashRoot || '') const [paymentTokenAddress, setPaymentTokenAddress] = useState('') // #region the box info @@ -60,13 +69,6 @@ function useContext(initialState?: { boxId: string; qualification: string }) { retry: retryMaskBoxInfo, } = useMaskBoxInfo(boxId) - // #get hashroot - const { value, error: errorRootHash } = useGetRootHash( - qualification || '0x0000000000000000000000000000000000000000000000000000000000000000', - ) - const web3 = new Web3() - const hashroot = value ? web3.eth.abi.encodeParameters(['bytes32[]'], [value?.proof?.map((p) => '0x' + p)]) : '0x00' - const { value: maskBoxStatus = null, error: errorMaskBoxStatus, @@ -144,11 +146,38 @@ function useContext(initialState?: { boxId: string; qualification: string }) { maskBoxStatus, maskBoxCreationSuccessEvent, ]) + // #endregion + + // #region qualification + const { value, error: errorProof, loading: loadingProof } = useMerkelProof(hashRoot) + const proofBytes = value?.proof + ? coder.encodeParameters(['bytes32[]'], [value?.proof?.map((p) => `0x${p}`) ?? []]) + : undefined + const qualification = useQualification( + boxInfo?.qualificationAddress, + account, + value?.proof ? coder.encodeParameters(['bytes', 'bytes32'], [proofBytes, hashRoot]) : undefined, + ) + + // not in whitelist + const notInWhiteList = value?.message === 'leaf not found' + + // at least hold token amount + const { value: holderToken } = useERC20TokenDetailed(boxInfo?.holderTokenAddress) + const { value: holderTokenBalance = '0' } = useERC20TokenBalance(holderToken?.address) + const holderMinTokenAmountBN = new BigNumber(boxInfo?.holderMinTokenAmount ?? 0) + const insufficientHolderToken = + isGreaterThan(holderMinTokenAmountBN, 0) && + holderMinTokenAmountBN.lte(holderTokenBalance) && + !qualification?.qualified + // #endregion const boxState = useMemo(() => { - if (errorRootHash?.message === 'leaf not found') return BoxState.NOT_IN_WHITELIST + if (notInWhiteList) return BoxState.NOT_IN_WHITELIST + if (insufficientHolderToken) return BoxState.INSUFFICIENT_HOLDER_TOKEN + if (qualification?.error_msg) return BoxState.NOT_QUALIFIED if (errorMaskBoxInfo || errorMaskBoxStatus || errorBoxInfo) return BoxState.ERROR - if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo) { + if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo || loadingProof) { if (!maskBoxInfo && !boxInfo) return BoxState.UNKNOWN } if (maskBoxInfo && !boxInfo) return BoxState.UNKNOWN @@ -159,19 +188,19 @@ function useContext(initialState?: { boxId: string; qualification: string }) { if (boxInfo.startAt > now || !boxInfo.started) return BoxState.NOT_READY if (boxInfo.endAt < now || maskBoxStatus?.expired) return BoxState.EXPIRED return BoxState.READY - }, [boxInfo, loadingBoxInfo, errorBoxInfo, maskBoxInfo, loadingMaskBoxInfo, errorMaskBoxInfo, beat]) - - const isWhitelisted = useIsWhitelisted(boxInfo?.qualificationAddress, account, hashroot) - const isQualifiedByContract = - boxInfo?.qualificationAddress && !isZeroAddress(boxInfo?.qualificationAddress) ? isWhitelisted : true - - // #region check holder min token - const { value: holderToken } = useERC20TokenDetailed(boxInfo?.holderTokenAddress) - const { value: holderTokenBalance = '0' } = useERC20TokenBalance(holderToken?.address) - const holderMinTokenAmountBN = new BigNumber(boxInfo?.holderMinTokenAmount ?? 0) - const isQualified = - (isZero(holderMinTokenAmountBN) || holderMinTokenAmountBN.lte(holderTokenBalance)) && isQualifiedByContract - // #endregion + }, [ + boxInfo, + loadingBoxInfo, + errorBoxInfo, + maskBoxInfo, + loadingMaskBoxInfo, + loadingProof, + errorMaskBoxInfo, + qualification, + notInWhiteList, + insufficientHolderToken, + beat, + ]) const boxStateMessage = useMemo(() => { switch (boxState) { @@ -192,9 +221,15 @@ function useContext(initialState?: { boxId: string; qualification: string }) { case BoxState.SOLD_OUT: return 'Sold Out' case BoxState.NOT_IN_WHITELIST: - return 'You are not in whitelist' + return 'You are not in the whitelist.' + case BoxState.INSUFFICIENT_HOLDER_TOKEN: + const { symbol, decimals } = holderToken ?? {} + const tokenPrice = `${formatBalance(boxInfo?.holderMinTokenAmount, decimals)} $${symbol}` + return `You must hold at least ${tokenPrice}.` + case BoxState.NOT_QUALIFIED: + return qualification?.error_msg ?? 'Not qualified.' case BoxState.DRAWED_OUT: - return 'Purchase limit exceeded' + return 'Purchase limit exceeded.' case BoxState.ERROR: return 'Something went wrong.' case BoxState.NOT_FOUND: @@ -202,7 +237,7 @@ function useContext(initialState?: { boxId: string; qualification: string }) { default: unreachable(boxState) } - }, [boxState, boxInfo?.startAt, beat]) + }, [holderToken, boxState, boxInfo?.startAt, qualification, beat]) useEffect(() => { if (!boxInfo || boxInfo.started) return @@ -212,8 +247,6 @@ function useContext(initialState?: { boxId: string; qualification: string }) { } }, [boxInfo, beat]) - // #endregion - // #region the box metadata const { value: boxMetadata, retry: retryBoxMetadata } = useMaskBoxMetadata(boxId, boxInfo?.creator ?? '') // #endregion @@ -269,14 +302,14 @@ function useContext(initialState?: { boxId: string; qualification: string }) { paymentTokenIndex, paymentTokenPrice, paymentTokenDetailed, - hashroot, + proofBytes, openBoxTransactionOverrides, ) const { value: erc20Allowance, retry: retryAllowance } = useERC20TokenAllowance( isNativeToken ? undefined : paymentTokenAddress, MASK_BOX_CONTRACT_ADDRESS, ) - const canPurchase = !isBalanceInsufficient && isQualified && !!boxInfo?.personalRemaining + const canPurchase = !isBalanceInsufficient && !!boxInfo?.personalRemaining const allowToPurchase = boxState === BoxState.READY const isAllowanceEnough = isNativeToken ? true : costAmount.lte(erc20Allowance ?? '0') const { value: openBoxTransactionGasLimit } = useAsyncRetry(async () => { @@ -291,9 +324,6 @@ function useContext(initialState?: { boxId: string; qualification: string }) { boxId, setBoxId, - isQualified, - holderToken, - // box info & metadata boxInfo, boxMetadata, diff --git a/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts b/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts deleted file mode 100644 index 510f82690032..000000000000 --- a/packages/mask/src/plugins/MaskBox/hooks/useGetRootHash.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { useAsyncRetry } from 'react-use' -import { useWallet } from '@masknet/web3-shared-evm' -import { getHashRoot } from '../apis' - -export function useGetRootHash(qualification: string) { - const wallet = useWallet() - qualification?.replace(/0x/, '') - const leafArray = wallet?.address - ?.replace(/0x/, '') - ?.match(/.{1,2}/g) - ?.map((byte) => Number.parseInt(byte, 16)) - const leaf = encodeURIComponent(Buffer.from(new Uint8Array(leafArray as number[])).toString('base64')) - return useAsyncRetry(async () => { - return getHashRoot(leaf, qualification) - }, [qualification]) -} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts b/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts deleted file mode 100644 index 2839679b853d..000000000000 --- a/packages/mask/src/plugins/MaskBox/hooks/useIsWhitelisted.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useMemo } from 'react' -import { useAsyncRetry } from 'react-use' -import { useWhitelistContract } from './useWhitelistContract' - -export function useIsWhitelisted(address?: string, account?: string, qualification?: string) { - const contract = useWhitelistContract(address) - const result = useAsyncRetry(async () => { - if (!contract || !account) return null - return contract.methods.is_qualified(account, qualification || '0x00').call() - }, [account, contract]) - - return useMemo(() => result.value ?? { qualified: false, error_msg: '' }, [result.value]) -} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useMaskBoxQualificationContract.ts b/packages/mask/src/plugins/MaskBox/hooks/useMaskBoxQualificationContract.ts new file mode 100644 index 000000000000..b013cb1f3d64 --- /dev/null +++ b/packages/mask/src/plugins/MaskBox/hooks/useMaskBoxQualificationContract.ts @@ -0,0 +1,8 @@ +import type { AbiItem } from 'web3-utils' +import { useContract } from '@masknet/web3-shared-evm' +import type { MaskBoxQualification } from '@masknet/web3-contracts/types/MaskBoxQualification' +import MASK_BOX_QUALIFICATION_CONTRACT from '@masknet/web3-contracts/abis/MaskBoxQualification.json' + +export function useMaskBoxQualificationContract(address?: string) { + return useContract(address, MASK_BOX_QUALIFICATION_CONTRACT as AbiItem[]) +} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useMerkleProof.ts b/packages/mask/src/plugins/MaskBox/hooks/useMerkleProof.ts new file mode 100644 index 000000000000..3d4c0b08e22b --- /dev/null +++ b/packages/mask/src/plugins/MaskBox/hooks/useMerkleProof.ts @@ -0,0 +1,16 @@ +import { useAsyncRetry } from 'react-use' +import { useAccount } from '@masknet/web3-shared-evm' +import { getMerkleProof } from '../apis' + +export function useMerkelProof(root?: string) { + const account = useAccount() + return useAsyncRetry(async () => { + if (!root) return + + const leaf = Buffer.from( + (account.replace(/0x/, '').match(/.{2}/g) ?? []).map((x) => Number.parseInt(x, 16)), + ).toString('base64') + + return getMerkleProof(leaf, root) + }, [account, root]) +} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts index f11b6a2eafaf..d6bb0d4482ce 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts @@ -10,7 +10,7 @@ export function useOpenBoxTransaction( paymentTokenIndex: number, paymentTokenPrice: string, paymentTokenDetailed: FungibleTokenDetailed | null, - qualification: string, + proof?: string, overrides?: NonPayableTx | null, ) { const account = useAccount() @@ -26,7 +26,7 @@ export function useOpenBoxTransaction( ? multipliedBy(paymentTokenPrice, amount).toFixed() : undefined, }, - method: maskBoxContract.methods.openBox(boxId, amount, paymentTokenIndex, qualification || '0x00'), + method: maskBoxContract.methods.openBox(boxId, amount, paymentTokenIndex, proof ?? '0x00'), } }, [account, amount, boxId, maskBoxContract, paymentTokenIndex, paymentTokenPrice, paymentTokenDetailed, overrides]) } diff --git a/packages/mask/src/plugins/MaskBox/hooks/useQualification.ts b/packages/mask/src/plugins/MaskBox/hooks/useQualification.ts new file mode 100644 index 000000000000..c409f937d8f2 --- /dev/null +++ b/packages/mask/src/plugins/MaskBox/hooks/useQualification.ts @@ -0,0 +1,13 @@ +import { useAsyncRetry } from 'react-use' +import { useMaskBoxQualificationContract } from './useMaskBoxQualificationContract' + +export function useQualification(address?: string, account?: string, proof?: string) { + const qualificationContract = useMaskBoxQualificationContract(address) + const { value: qualification = { qualified: false, error_msg: '' } } = useAsyncRetry(async () => { + if (!qualificationContract || !account) return null + return qualificationContract.methods.is_qualified(account, proof ?? '0x00').call({ + from: account, + }) + }, [account, qualificationContract, proof]) + return qualification +} diff --git a/packages/mask/src/plugins/MaskBox/hooks/useWhitelistContract.ts b/packages/mask/src/plugins/MaskBox/hooks/useWhitelistContract.ts deleted file mode 100644 index 87423cc30cfd..000000000000 --- a/packages/mask/src/plugins/MaskBox/hooks/useWhitelistContract.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { AbiItem } from 'web3-utils' -import { useContract } from '@masknet/web3-shared-evm' -import type { MaskBoxWhiteList } from '@masknet/web3-contracts/types/MaskBoxWhiteList' -import MASK_BOX_WHITELIST_ABI from '@masknet/web3-contracts/abis/MaskBoxWhiteList.json' - -export function useWhitelistContract(address?: string) { - return useContract(address, MASK_BOX_WHITELIST_ABI as AbiItem[]) -} diff --git a/packages/mask/src/plugins/MaskBox/type.ts b/packages/mask/src/plugins/MaskBox/type.ts index 59af90155b15..ac12d96c65a3 100644 --- a/packages/mask/src/plugins/MaskBox/type.ts +++ b/packages/mask/src/plugins/MaskBox/type.ts @@ -22,6 +22,10 @@ export enum BoxState { NOT_FOUND = 8, /** leaf not found */ NOT_IN_WHITELIST = 9, + /** insufficient holder token */ + INSUFFICIENT_HOLDER_TOKEN = 10, + /** not qualified */ + NOT_QUALIFIED = 11, } export interface PaymentOption { diff --git a/packages/web3-contracts/abis/MaskBoxQualification.json b/packages/web3-contracts/abis/MaskBoxQualification.json new file mode 100644 index 000000000000..c63ad4479e30 --- /dev/null +++ b/packages/web3-contracts/abis/MaskBoxQualification.json @@ -0,0 +1,23 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "bytes", "name": "proof", "type": "bytes" } + ], + "name": "is_qualified", + "outputs": [ + { "internalType": "bool", "name": "qualified", "type": "bool" }, + { "internalType": "string", "name": "error_msg", "type": "string" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }], + "stateMutability": "view", + "type": "function" + } +] diff --git a/packages/web3-contracts/abis/MaskBoxWhiteList.json b/packages/web3-contracts/abis/MaskBoxWhiteList.json deleted file mode 100644 index 61a76e7558d6..000000000000 --- a/packages/web3-contracts/abis/MaskBoxWhiteList.json +++ /dev/null @@ -1,148 +0,0 @@ -[ - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "addrs", - "type": "address[]" - } - ], - "name": "add_white_list", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "initialize", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "name": "is_qualified", - "outputs": [ - { - "internalType": "bool", - "name": "qualified", - "type": "bool" - }, - { - "internalType": "string", - "name": "error_msg", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address[]", - "name": "addrs", - "type": "address[]" - } - ], - "name": "remove_white_list", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "uint32", - "name": "", - "type": "uint32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "white_list", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } -] diff --git a/packages/web3-contracts/types/MaskBox.d.ts b/packages/web3-contracts/types/MaskBox.d.ts index ab54e03fa2c7..6b284cf8995b 100644 --- a/packages/web3-contracts/types/MaskBox.d.ts +++ b/packages/web3-contracts/types/MaskBox.d.ts @@ -103,6 +103,7 @@ export interface MaskBox extends BaseContract { qualification: string, holder_token_addr: string, holder_min_token_amount: number | string | BN, + qualification_data: string | number[], ): NonPayableTransactionObject getBoxInfo(box_id: number | string | BN): NonPayableTransactionObject<{ @@ -113,6 +114,7 @@ export interface MaskBox extends BaseContract { qualification: string holder_token_addr: string holder_min_token_amount: string + qualification_data: string 0: string 1: string 2: string @@ -120,6 +122,7 @@ export interface MaskBox extends BaseContract { 4: string 5: string 6: string + 7: string }> getBoxStatus(box_id: number | string | BN): NonPayableTransactionObject<{ @@ -156,10 +159,17 @@ export interface MaskBox extends BaseContract { owner(): NonPayableTransactionObject + removeAdmin(addrs: string[]): NonPayableTransactionObject + removeWhitelist(addrs: string[]): NonPayableTransactionObject renounceOwnership(): NonPayableTransactionObject + setQualificationData( + box_id: number | string | BN, + qualification_data: string | number[], + ): NonPayableTransactionObject + transferOwnership(newOwner: string): NonPayableTransactionObject whitelist(arg0: string): NonPayableTransactionObject diff --git a/packages/web3-contracts/types/MaskBoxQualification.d.ts b/packages/web3-contracts/types/MaskBoxQualification.d.ts new file mode 100644 index 000000000000..b99d14ee4331 --- /dev/null +++ b/packages/web3-contracts/types/MaskBoxQualification.d.ts @@ -0,0 +1,43 @@ +/* Autogenerated file. Do not edit manually. */ +/* tslint:disable */ +/* eslint-disable */ + +import BN from 'bn.js' +import { ContractOptions } from 'web3-eth-contract' +import { EventLog } from 'web3-core' +import { EventEmitter } from 'events' +import { + Callback, + PayableTransactionObject, + NonPayableTransactionObject, + BlockType, + ContractEventLog, + BaseContract, +} from './types' + +interface EventOptions { + filter?: object + fromBlock?: BlockType + topics?: string[] +} + +export interface MaskBoxQualification extends BaseContract { + constructor(jsonInterface: any[], address?: string, options?: ContractOptions): MaskBoxQualification + clone(): MaskBoxQualification + methods: { + is_qualified( + account: string, + proof: string | number[], + ): NonPayableTransactionObject<{ + qualified: boolean + error_msg: string + 0: boolean + 1: string + }> + + version(): NonPayableTransactionObject + } + events: { + allEvents(options?: EventOptions, cb?: Callback): EventEmitter + } +} diff --git a/packages/web3-contracts/types/MaskBoxWhiteList.d.ts b/packages/web3-contracts/types/MaskBoxWhiteList.d.ts deleted file mode 100644 index 705870d77875..000000000000 --- a/packages/web3-contracts/types/MaskBoxWhiteList.d.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ - -import BN from 'bn.js' -import { ContractOptions } from 'web3-eth-contract' -import { EventLog } from 'web3-core' -import { EventEmitter } from 'events' -import { - Callback, - PayableTransactionObject, - NonPayableTransactionObject, - BlockType, - ContractEventLog, - BaseContract, -} from './types' - -interface EventOptions { - filter?: object - fromBlock?: BlockType - topics?: string[] -} - -export type OwnershipTransferred = ContractEventLog<{ - previousOwner: string - newOwner: string - 0: string - 1: string -}> - -export interface MaskBoxWhiteList extends BaseContract { - constructor(jsonInterface: any[], address?: string, options?: ContractOptions): MaskBoxWhiteList - clone(): MaskBoxWhiteList - methods: { - add_white_list(addrs: string[]): NonPayableTransactionObject - - initialize(): NonPayableTransactionObject - - is_qualified( - account: string, - arg1: string | number[], - ): NonPayableTransactionObject<{ - qualified: boolean - error_msg: string - 0: boolean - 1: string - }> - - owner(): NonPayableTransactionObject - - remove_white_list(addrs: string[]): NonPayableTransactionObject - - renounceOwnership(): NonPayableTransactionObject - - transferOwnership(newOwner: string): NonPayableTransactionObject - - version(): NonPayableTransactionObject - - white_list(arg0: string): NonPayableTransactionObject - } - events: { - OwnershipTransferred(cb?: Callback): EventEmitter - OwnershipTransferred(options?: EventOptions, cb?: Callback): EventEmitter - - allEvents(options?: EventOptions, cb?: Callback): EventEmitter - } - - once(event: 'OwnershipTransferred', cb: Callback): void - once(event: 'OwnershipTransferred', options: EventOptions, cb: Callback): void -} From 4ee76deaf29ded2b91b3b51df871894ac0626ac2 Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Mon, 21 Feb 2022 14:42:44 +0800 Subject: [PATCH 3/8] feat: change qualification to rootHash in URL --- packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx index 0de20484d5f4..3e31f89f3edc 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx @@ -32,7 +32,7 @@ export default sns function Renderer(props: React.PropsWithChildren<{ url: string }>) { const [, chainId] = props.url.match(/chain=(\d+)/i) ?? [] const [, boxId] = props.url.match(/box=(\d+)/i) ?? [] - const [, hashRoot] = props.url.match(/qualification=([\dA-Za-z]+)/) ?? [] + const [, hashRoot] = props.url.match(/rootHash=([\dA-Za-z]+)/) ?? [] const shouldNotRender = !chainId || !boxId usePluginWrapper(!shouldNotRender) From ea2e16b07d07ede9897bccc562bf1c2cd0e16cbe Mon Sep 17 00:00:00 2001 From: guanbinrui <52657989+guanbinrui@users.noreply.github.com> Date: Mon, 21 Feb 2022 15:45:12 +0800 Subject: [PATCH 4/8] fix: failed to open box (#5694) --- .../mask/src/plugins/MaskBox/hooks/useContext.ts | 15 +++++++++------ .../MaskBox/hooks/useOpenBoxTransaction.ts | 12 +++++++++++- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index d6bfa467027d..95e8607d03b9 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -58,7 +58,7 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { const coder = ABICoder as unknown as ABICoder.AbiCoder const [boxId, setBoxId] = useState(initialState?.boxId ?? '') - const [hashRoot, setHashRoot] = useState(initialState?.hashRoot || '') + const [rootHash, setRootHash] = useState(initialState?.hashRoot || '') const [paymentTokenAddress, setPaymentTokenAddress] = useState('') // #region the box info @@ -149,14 +149,14 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { // #endregion // #region qualification - const { value, error: errorProof, loading: loadingProof } = useMerkelProof(hashRoot) + const { value, error: errorProof, loading: loadingProof } = useMerkelProof(rootHash) const proofBytes = value?.proof ? coder.encodeParameters(['bytes32[]'], [value?.proof?.map((p) => `0x${p}`) ?? []]) : undefined const qualification = useQualification( boxInfo?.qualificationAddress, account, - value?.proof ? coder.encodeParameters(['bytes', 'bytes32'], [proofBytes, hashRoot]) : undefined, + value?.proof ? coder.encodeParameters(['bytes', 'bytes32'], [proofBytes, rootHash]) : undefined, ) // not in whitelist @@ -176,8 +176,9 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { if (notInWhiteList) return BoxState.NOT_IN_WHITELIST if (insufficientHolderToken) return BoxState.INSUFFICIENT_HOLDER_TOKEN if (qualification?.error_msg) return BoxState.NOT_QUALIFIED - if (errorMaskBoxInfo || errorMaskBoxStatus || errorBoxInfo) return BoxState.ERROR - if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo || loadingProof) { + if (errorMaskBoxInfo || errorMaskBoxStatus || errorBoxInfo || (rootHash ? errorProof : false)) + return BoxState.ERROR + if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo || (rootHash ? loadingProof : false)) { if (!maskBoxInfo && !boxInfo) return BoxState.UNKNOWN } if (maskBoxInfo && !boxInfo) return BoxState.UNKNOWN @@ -194,9 +195,11 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { errorBoxInfo, maskBoxInfo, loadingMaskBoxInfo, - loadingProof, errorMaskBoxInfo, qualification, + loadingProof, + errorProof, + rootHash, notInWhiteList, insufficientHolderToken, beat, diff --git a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts index d6bb0d4482ce..2a59d7016ae9 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useOpenBoxTransaction.ts @@ -28,5 +28,15 @@ export function useOpenBoxTransaction( }, method: maskBoxContract.methods.openBox(boxId, amount, paymentTokenIndex, proof ?? '0x00'), } - }, [account, amount, boxId, maskBoxContract, paymentTokenIndex, paymentTokenPrice, paymentTokenDetailed, overrides]) + }, [ + account, + amount, + boxId, + maskBoxContract, + paymentTokenIndex, + paymentTokenPrice, + paymentTokenDetailed, + proof, + overrides, + ]) } From 57872804b300fdb361a496d979afc2b0b3eafe92 Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Mon, 21 Feb 2022 17:49:53 +0800 Subject: [PATCH 5/8] feat: catch error when call merkleProof api --- .../src/plugins/MaskBox/apis/merkleProof.ts | 18 +++++++++++------- .../src/plugins/MaskBox/hooks/useContext.ts | 1 - 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts index cf50645aa79b..4a8f6637c231 100644 --- a/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts +++ b/packages/mask/src/plugins/MaskBox/apis/merkleProof.ts @@ -2,11 +2,15 @@ import urlcat from 'urlcat' import { MERKLE_PROOF_ENDPOINT } from '../constants' export async function getMerkleProof(leaf: string, root: string) { - const response = await fetch( - urlcat(MERKLE_PROOF_ENDPOINT, { - leaf, - root: root.replace(/^0x/, ''), - }), - ) - return (await response.json()) as { proof?: string[]; message?: string; module?: string } + try { + const response = await fetch( + urlcat(MERKLE_PROOF_ENDPOINT, { + leaf, + root: root.replace(/^0x/, ''), + }), + ) + return (await response.json()) as { proof?: string[]; message?: string; module?: string } + } catch (err: any) { + throw new Error(err?.message) + } } diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index 95e8607d03b9..c72802b44503 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -43,7 +43,6 @@ import { isGreaterThan, isGreaterThanOrEqualTo, isLessThanOrEqualTo, - isZero, multipliedBy, useBeat, } from '@masknet/web3-shared-base' From f14de6d731cbd521e152105c15be88ac2a975516 Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Mon, 21 Feb 2022 18:49:37 +0800 Subject: [PATCH 6/8] feat: change holderTokenAmount judgement --- packages/mask/src/plugins/MaskBox/hooks/useContext.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index c72802b44503..85f7dca63328 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -161,14 +161,13 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { // not in whitelist const notInWhiteList = value?.message === 'leaf not found' + console.log('qualification', qualification?.qualified) // at least hold token amount const { value: holderToken } = useERC20TokenDetailed(boxInfo?.holderTokenAddress) const { value: holderTokenBalance = '0' } = useERC20TokenBalance(holderToken?.address) const holderMinTokenAmountBN = new BigNumber(boxInfo?.holderMinTokenAmount ?? 0) const insufficientHolderToken = - isGreaterThan(holderMinTokenAmountBN, 0) && - holderMinTokenAmountBN.lte(holderTokenBalance) && - !qualification?.qualified + isGreaterThan(holderMinTokenAmountBN, 0) && !holderMinTokenAmountBN.lte(holderTokenBalance) // #endregion const boxState = useMemo(() => { From 33991b8a5211be714057a534f2eaa02d32fa14ec Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Tue, 22 Feb 2022 14:03:23 +0800 Subject: [PATCH 7/8] chore: delete console.log --- packages/mask/src/plugins/MaskBox/hooks/useContext.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index 85f7dca63328..51579f1c95fa 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -161,7 +161,6 @@ function useContext(initialState?: { boxId: string; hashRoot: string }) { // not in whitelist const notInWhiteList = value?.message === 'leaf not found' - console.log('qualification', qualification?.qualified) // at least hold token amount const { value: holderToken } = useERC20TokenDetailed(boxInfo?.holderTokenAddress) const { value: holderTokenBalance = '0' } = useERC20TokenBalance(holderToken?.address) From a0070926d217efb2da45e526386a705c0f725027 Mon Sep 17 00:00:00 2001 From: Randolph Chen Date: Tue, 22 Feb 2022 14:42:13 +0800 Subject: [PATCH 8/8] feat: change nft style when sharing claim success --- .../DashboardComponents/CollectibleList/CollectibleCard.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx index 7d7ca7f8f172..f3f07d716d5e 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx @@ -55,6 +55,7 @@ const useStyles = makeStyles()((theme) => ({ }, linkWrapper: { position: 'relative', + display: 'block', width: 172, height: 172, },