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({