diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx index e05288356c76..496a222f8589 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx @@ -9,7 +9,6 @@ import { formatBalance, formatEthereumAddress, useAccount, - useProviderType, useChainId, useMaskBoxConstants, EthereumTokenType, @@ -115,7 +114,6 @@ export function DrawDialog(props: DrawDialogProps) { const providerDescriptor = useProviderDescriptor() const account = useAccount() const chainId = useChainId() - const providerType = useProviderType() const onCount = useCallback( (step: number) => { @@ -256,7 +254,7 @@ export function DrawDialog(props: DrawDialogProps) { diff --git a/packages/mask/src/plugins/MaskBox/helpers/formatCountdown.ts b/packages/mask/src/plugins/MaskBox/helpers/formatCountdown.ts index 5eccf880a8bb..8822c226016a 100644 --- a/packages/mask/src/plugins/MaskBox/helpers/formatCountdown.ts +++ b/packages/mask/src/plugins/MaskBox/helpers/formatCountdown.ts @@ -1,32 +1,7 @@ -const msPerSecond = 1000 -const msPerMinute = msPerSecond * 60 -const msPerHour = msPerMinute * 60 -const msPerDay = msPerHour * 24 +import formatDuration from 'date-fns/formatDuration' +import intervalToDuration from 'date-fns/intervalToDuration' -const units = [ - ['day', 'days'], - ['hour', 'hours'], - ['minute', 'minutes'], - ['second', 'seconds'], -] - -export function formatCountdown(countdown: number) { - if (countdown <= 0) return '' - - const amounts: number[] = [] - - ;[msPerDay, msPerHour, msPerMinute, msPerSecond].reduce((accumulator, x) => { - amounts.push(Math.floor(accumulator / x)) - return accumulator % x - }, countdown) - - return amounts - .map((x, i) => { - if (x <= 0) return '' - if (x === 1) return `${x} ${units[i][0]}` - return `${x} ${units[i][1]}` - }) - .filter(Boolean) - .join(' ') - .trim() +export function formatCountdown(from: number, to: number) { + const duration = intervalToDuration({ start: from, end: to }) + return formatDuration(duration) } diff --git a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts index 771da1cbee62..6307de000073 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useContext.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useContext.ts @@ -35,12 +35,13 @@ import { useMaskBoxPurchasedTokens } from './useMaskBoxPurchasedTokens' import { formatCountdown } from '../helpers/formatCountdown' import { useOpenBoxTransaction } from './useOpenBoxTransaction' import { useMaskBoxMetadata } from './useMaskBoxMetadata' -import { useHeartBit } from './useHeartBit' +import { useHeartBeat } from './useHeartBeat' import { useIsWhitelisted } from './useIsWhitelisted' import { isGreaterThanOrEqualTo, isLessThanOrEqualTo, isZero, multipliedBy } from '@masknet/web3-shared-base' function useContext(initialState?: { boxId: string }) { - const heartBit = useHeartBit() + const now = new Date() + const heartBeat = useHeartBeat() const account = useAccount() const chainId = useChainId() const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(ChainId.Mainnet) @@ -92,8 +93,8 @@ function useContext(initialState?: { boxId: string }) { const totalComputed = total && remaining && remaining > total ? remaining : total const sold = Math.max(0, totalComputed - remaining) const personalRemaining = Math.max(0, personalLimit - purchasedTokens.length) - const startAt = Number.parseInt(maskBoxCreationSuccessEvent?.returnValues.start_time ?? '0', 10) - const endAt = Number.parseInt(maskBoxCreationSuccessEvent?.returnValues.end_time ?? '0', 10) + const startAt = Number.parseInt(maskBoxCreationSuccessEvent?.returnValues.start_time || '0', 10) + const endAt = Number.parseInt(maskBoxCreationSuccessEvent?.returnValues.end_time || '0', 10) const info: BoxInfo = { boxId, creator: maskBoxInfo.creator, @@ -105,6 +106,7 @@ function useContext(initialState?: { boxId: string }) { availableAmount: Math.min(personalRemaining, remaining), startAt: startAt === 0 ? subDays(new Date(), 1) : fromUnixTime(startAt), endAt: endAt === 0 ? addDays(new Date(), 1) : fromUnixTime(endAt), + started: maskBoxStatus.started, total: totalComputed, sold, canceled: maskBoxStatus.canceled, @@ -135,17 +137,18 @@ function useContext(initialState?: { boxId: string }) { const boxState = useMemo(() => { if (errorMaskBoxInfo || errorMaskBoxStatus || errorBoxInfo) return BoxState.ERROR - if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo) return BoxState.UNKNOWN + if (loadingMaskBoxInfo || loadingMaskBoxStatus || loadingBoxInfo) { + if (!maskBoxInfo && !boxInfo) return BoxState.UNKNOWN + } if (maskBoxInfo && !boxInfo) return BoxState.UNKNOWN if (!maskBoxInfo || !maskBoxStatus || !boxInfo) return BoxState.NOT_FOUND if (maskBoxStatus.canceled) return BoxState.CANCELED - const now = new Date() if (isGreaterThanOrEqualTo(boxInfo.tokenIdsPurchased.length, boxInfo.personalLimit)) return BoxState.DRAWED_OUT if (isLessThanOrEqualTo(boxInfo.remaining, 0)) return BoxState.SOLD_OUT - if (boxInfo.startAt > now) return BoxState.NOT_READY + 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, heartBit]) + }, [boxInfo, loadingBoxInfo, errorBoxInfo, maskBoxInfo, loadingMaskBoxInfo, errorMaskBoxInfo, heartBeat]) const isWhitelisted = useIsWhitelisted(boxInfo?.qualificationAddress, account) const isQualifiedByContract = @@ -170,10 +173,10 @@ function useContext(initialState?: { boxId: string }) { case BoxState.EXPIRED: return 'Ended' case BoxState.NOT_READY: - const now = Date.now() + const nowAt = now.getTime() const startAt = boxInfo?.startAt.getTime() ?? 0 - if (startAt <= now) return 'Loading...' - const countdown = formatCountdown(startAt - now) + if (startAt <= nowAt) return 'Syncing status...' + const countdown = formatCountdown(startAt, nowAt) return countdown ? `Start sale in ${countdown}` : 'Loading...' case BoxState.SOLD_OUT: return 'Sold Out' @@ -186,7 +189,16 @@ function useContext(initialState?: { boxId: string }) { default: unreachable(boxState) } - }, [boxState, heartBit]) + }, [boxState, boxInfo?.startAt, heartBeat]) + + useEffect(() => { + if (!boxInfo || boxInfo.started) return + + if (boxInfo.startAt < now) { + retryMaskBoxStatus() + } + }, [boxInfo, heartBeat]) + //#endregion //#region the box metadata @@ -253,8 +265,8 @@ function useContext(initialState?: { boxId: string }) { const canPurchase = !isBalanceInsufficient && isQualified && !!boxInfo?.personalRemaining const allowToPurchase = boxState === BoxState.READY const isAllowanceEnough = isNativeToken ? true : costAmount.lte(erc20Allowance ?? '0') - const { value: openBoxTransactionGasLimit = 0 } = useAsyncRetry(async () => { - if (!openBoxTransaction || !canPurchase || !allowToPurchase || !isAllowanceEnough) return 0 + const { value: openBoxTransactionGasLimit } = useAsyncRetry(async () => { + if (!openBoxTransaction || !canPurchase || !allowToPurchase || !isAllowanceEnough) return const estimatedGas = await openBoxTransaction.method.estimateGas(omit(openBoxTransaction.config, 'gas')) return new BigNumber(estimatedGas).toNumber() }, [openBoxTransaction, canPurchase, allowToPurchase, isAllowanceEnough]) diff --git a/packages/mask/src/plugins/MaskBox/hooks/useHeartBit.ts b/packages/mask/src/plugins/MaskBox/hooks/useHeartBeat.ts similarity index 85% rename from packages/mask/src/plugins/MaskBox/hooks/useHeartBit.ts rename to packages/mask/src/plugins/MaskBox/hooks/useHeartBeat.ts index 13d5662e5b61..8d476c76cb68 100644 --- a/packages/mask/src/plugins/MaskBox/hooks/useHeartBit.ts +++ b/packages/mask/src/plugins/MaskBox/hooks/useHeartBeat.ts @@ -1,7 +1,7 @@ import { useState } from 'react' import { useTimeoutFn } from 'react-use' -export function useHeartBit(delay = 1000) { +export function useHeartBeat(delay = 1000) { const [bit, setBit] = useState(0) const [, , reset] = useTimeoutFn(() => { setBit((x) => (x + 1) % Number.MAX_SAFE_INTEGER) diff --git a/packages/mask/src/plugins/MaskBox/type.ts b/packages/mask/src/plugins/MaskBox/type.ts index 7f9426a78aa3..3e0bb2b64368 100644 --- a/packages/mask/src/plugins/MaskBox/type.ts +++ b/packages/mask/src/plugins/MaskBox/type.ts @@ -47,6 +47,7 @@ export interface BoxInfo { sold: number startAt: Date endAt: Date + started: boolean tokenIds: string[] tokenIdsPurchased: string[] tokenAddress: string diff --git a/packages/web3-shared/evm/hooks/useTransactionCallback.ts b/packages/web3-shared/evm/hooks/useTransactionCallback.ts index 5d9cb51d6e75..7bb891671b27 100644 --- a/packages/web3-shared/evm/hooks/useTransactionCallback.ts +++ b/packages/web3-shared/evm/hooks/useTransactionCallback.ts @@ -1,4 +1,5 @@ import { useCallback } from 'react' +import { omit } from 'lodash-unified' import type { PayableTransactionObject, PayableTx } from '@masknet/web3-contracts/types/types' import { useTransactionState } from './useTransactionState' import { TransactionStateType, TransactionEventType } from '../types' @@ -21,9 +22,13 @@ export function useTransactionCallback( setState({ type: TransactionStateType.WAIT_FOR_CONFIRMING, }) + const gasExpectedConfig = { ...config } try { - await method.estimateGas(config) + const estimatedGas = await method.estimateGas(omit(config, 'gas')) + if (!gasExpectedConfig.gas && estimatedGas) { + gasExpectedConfig.gas = estimatedGas + } } catch (error) { try { await method.call(config) @@ -38,7 +43,7 @@ export function useTransactionCallback( return new Promise(async (resolve, reject) => { method - .send(config) + .send(gasExpectedConfig) .once(TransactionEventType.TRANSACTION_HASH, (hash) => { if (type !== TransactionStateType.HASH) return setState({