diff --git a/packages/extension-base/src/background/KoniTypes.ts b/packages/extension-base/src/background/KoniTypes.ts index 5fa3cd5c7b..26edab76fc 100644 --- a/packages/extension-base/src/background/KoniTypes.ts +++ b/packages/extension-base/src/background/KoniTypes.ts @@ -1607,7 +1607,8 @@ export interface UnstakingInfo { chain: string; status: UnstakingStatus; claimable: string; // amount to be withdrawn - waitingTime: number; // in hours + waitingTime?: number; + targetTimestampMs?: number; validatorAddress?: string; // might unstake from a validator or not } diff --git a/packages/extension-base/src/services/chain-service/constants.ts b/packages/extension-base/src/services/chain-service/constants.ts index 53aa343a49..312adc8247 100644 --- a/packages/extension-base/src/services/chain-service/constants.ts +++ b/packages/extension-base/src/services/chain-service/constants.ts @@ -74,6 +74,7 @@ export const _STAKING_ERA_LENGTH_MAP: Record = { // in hours shibuya: 24, bifrost_testnet: 0.5, bifrost: 2, + bifrost_dot: 24, ternoa: 24, calamari: 6, calamari_test: 6, diff --git a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/acala.ts b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/acala.ts index db0f6074a5..93ad7d1527 100644 --- a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/acala.ts +++ b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/acala.ts @@ -46,7 +46,7 @@ export default class AcalaLiquidStakingPoolHandler extends BaseLiquidStakingPool defaultUnstake: true, fastUnstake: true, cancelUnstake: false, - withdraw: true, + withdraw: false, // TODO: Change after verify unstake info claimReward: false }; diff --git a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/bifrost.ts b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/bifrost.ts index 3744045f96..8c7f7ef408 100644 --- a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/bifrost.ts +++ b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/bifrost.ts @@ -203,7 +203,8 @@ export default class BifrostLiquidStakingPoolHandler extends BaseLiquidStakingPo const exchangeRate = new BigNumber(rate); const currentRelayEraObj = _currentRelayEra.toPrimitive() as Record; - const currentRelayEra = currentRelayEraObj.Era; + + const currentRelayEra = currentRelayEraObj.era; const unlockLedgerList: BifrostUnlockLedger[] = []; @@ -277,6 +278,8 @@ export default class BifrostLiquidStakingPoolHandler extends BaseLiquidStakingPo const isClaimable = unlocking.era - currentRelayEra < 0; const remainingEra = unlocking.era - currentRelayEra; const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[this.chain]; + // const currentTimestampMs = Date.now(); + // const targetTimestampMs = currentTimestampMs + waitingTime * 60 * 60 * 1000; unlockBalance = unlockBalance.add(new BN(unlocking.balance)); unstakingList.push({ @@ -284,6 +287,7 @@ export default class BifrostLiquidStakingPoolHandler extends BaseLiquidStakingPo status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: unlocking.balance, waitingTime: waitingTime + // targetTimestampMs: targetTimestampMs } as UnstakingInfo); }); } diff --git a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts index ad1001fa94..cc3af02486 100644 --- a/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts +++ b/packages/extension-base/src/services/earning-service/handlers/liquid-staking/parallel.ts @@ -177,6 +177,8 @@ export default class ParallelLiquidStakingPoolHandler extends BaseLiquidStakingP const remainingEra = chunk.era - currentEra; const eraTime = _STAKING_ERA_LENGTH_MAP[this.chain] || _STAKING_ERA_LENGTH_MAP.default; const waitingTime = remainingEra * eraTime; + // const currentTimestampMs = Date.now(); + // const targetTimestampMs = currentTimestampMs + waitingTime * 60 * 60 * 1000; totalBalance = totalBalance.add(amount); unlockingBalance = unlockingBalance.add(amount); @@ -185,6 +187,7 @@ export default class ParallelLiquidStakingPoolHandler extends BaseLiquidStakingP status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: amount.toString(), waitingTime: waitingTime + // targetTimestampMs: targetTimestampMs } as UnstakingInfo); } } diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts index 6d1b38a54a..b4085d74be 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/amplitude.ts @@ -200,6 +200,8 @@ export default class AmplitudeNativeStakingPoolHandler extends BaseParaNativeSta const isClaimable = parseInt(unstakingBlock) - currentBlockNumber < 0; const remainingBlock = parseInt(unstakingBlock) - currentBlockNumber; const waitingTime = remainingBlock * blockDuration; + // const currentTimestampMs = Date.now(); + // const targetTimestampMs = currentTimestampMs + waitingTime * 60 * 60 * 1000; unstakingBalance = unstakingAmount.toString(); @@ -208,6 +210,7 @@ export default class AmplitudeNativeStakingPoolHandler extends BaseParaNativeSta status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: unstakingAmount.toString(), waitingTime, + // targetTimestampMs: targetTimestampMs, validatorAddress: undefined }); } diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts index 698cf165f9..bcbbe4c07d 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/astar.ts @@ -27,8 +27,7 @@ export function getAstarWithdrawable (yieldPosition: YieldPositionInfo): Unstaki const unstakingInfo: UnstakingInfo = { chain: yieldPosition.chain, status: UnstakingStatus.CLAIMABLE, - claimable: '0', - waitingTime: 0 + claimable: '0' }; let bnWithdrawable = BN_ZERO; @@ -249,12 +248,15 @@ export default class AstarNativeStakingPoolHandler extends BaseParaNativeStaking const isClaimable = unlockingChunk.unlockEra - parseInt(currentEra) < 0; const remainingEra = unlockingChunk.unlockEra - parseInt(currentEra); const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; + // const currentTimestampMs = Date.now(); + // const targetTimestampMs = currentTimestampMs + waitingTime * 60 * 60 * 1000; unstakingList.push({ chain: chainInfo.slug, status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: unlockingChunk.amount.toString(), waitingTime + // targetTimestampMs: targetTimestampMs }); } } diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts index e96b9ebe82..93564761d9 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/para-chain.ts @@ -176,6 +176,8 @@ export default class ParaNativeStakingPoolHandler extends BaseParaNativeStakingP const remainingEra = scheduledRequest.whenExecutable - currentRound; const waitingTime = remainingEra * _STAKING_ERA_LENGTH_MAP[chainInfo.slug]; const claimable = Object.values(scheduledRequest.action)[0]; + // const currentTimestampMs = Date.now(); + // const targetTimestampMs = currentTimestampMs + waitingTime * 60 * 60 * 1000; unstakingMap[delegation.owner] = { chain: chainInfo.slug, @@ -183,6 +185,7 @@ export default class ParaNativeStakingPoolHandler extends BaseParaNativeStakingP validatorAddress: delegation.owner, claimable: claimable.toString(), waitingTime + // targetTimestampMs: targetTimestampMs } as UnstakingInfo; hasUnstaking = true; diff --git a/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts b/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts index a8db6b0b2c..edf93f1d1c 100644 --- a/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts +++ b/packages/extension-base/src/services/earning-service/handlers/native-staking/relay-chain.ts @@ -10,7 +10,7 @@ import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/ import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/services/chain-service/utils'; import { _STAKING_CHAIN_GROUP } from '@subwallet/extension-base/services/earning-service/constants'; import { parseIdentity } from '@subwallet/extension-base/services/earning-service/utils'; -import { BaseYieldPositionInfo, EarningStatus, NativeYieldPoolInfo, OptimalYieldPath, PalletStakingExposure, PalletStakingNominations, PalletStakingStakingLedger, PalletStakingValidatorPrefs, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, SubmitYieldJoinData, TernoaStakingRewardsStakingRewardsData, TransactionData, UnstakingStatus, ValidatorExtraInfo, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { BaseYieldPositionInfo, EarningStatus, NativeYieldPoolInfo, OptimalYieldPath, PalletStakingActiveEraInfo, PalletStakingExposure, PalletStakingNominations, PalletStakingStakingLedger, PalletStakingValidatorPrefs, StakeCancelWithdrawalParams, SubmitJoinNativeStaking, SubmitYieldJoinData, TernoaStakingRewardsStakingRewardsData, TransactionData, UnstakingStatus, ValidatorExtraInfo, ValidatorInfo, YieldPoolInfo, YieldPositionInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; import { t } from 'i18next'; @@ -159,11 +159,11 @@ export default class RelayNativeStakingPoolHandler extends BaseNativeStakingPool async parseNominatorMetadata (chainInfo: _ChainInfo, address: string, substrateApi: _SubstrateApi, ledger: PalletStakingStakingLedger, currentEra: string, minStake: BN, _deriveSessionProgress: DeriveSessionProgress): Promise> { const chain = chainInfo.slug; - const [_nominations, _bonded] = await Promise.all([ + const [_nominations, _bonded, _activeEra] = await Promise.all([ substrateApi.api.query?.staking?.nominators(address), - substrateApi.api.query?.staking?.bonded(address) + substrateApi.api.query?.staking?.bonded(address), + substrateApi.api.query?.staking?.activeEra() ]); - const unlimitedNominatorRewarded = substrateApi.api.consts.staking.maxExposurePageSize !== undefined; const _maxNominatorRewardedPerValidator = (substrateApi.api.consts.staking.maxNominatorRewardedPerValidator || 0).toString(); const maxNominatorRewardedPerValidator = parseInt(_maxNominatorRewardedPerValidator); @@ -232,22 +232,21 @@ export default class RelayNativeStakingPoolHandler extends BaseNativeStakingPool } ledger.unlocking.forEach((unlockingChunk) => { - // Calculate the remaining time for current era ending - const isClaimable = unlockingChunk.era - parseInt(currentEra) < 0; - const remainingEra = unlockingChunk.era - parseInt(currentEra); - const expectedBlockTime = _EXPECTED_BLOCK_TIME[chain]; - const eraLength = _deriveSessionProgress.eraLength.toNumber(); - const eraProgress = _deriveSessionProgress.eraProgress.toNumber(); - const remainingSlots = eraLength - eraProgress; - const remainingHours = expectedBlockTime * remainingSlots / 60 / 60; + const activeEra = _activeEra.toPrimitive() as unknown as PalletStakingActiveEraInfo; + const era = parseInt(activeEra.index); + const startTimestampMs = parseInt(activeEra.start); + + const remainingEra = unlockingChunk.era - era; const eraTime = _STAKING_ERA_LENGTH_MAP[chainInfo.slug] || _STAKING_ERA_LENGTH_MAP.default; // in hours - const waitingTime = remainingEra * eraTime + remainingHours; + const remaningTimestampMs = remainingEra * eraTime * 60 * 60 * 1000; + const targetTimestampMs = startTimestampMs + remaningTimestampMs; + const isClaimable = targetTimestampMs - Date.now() <= 0; unstakingList.push({ chain, status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: unlockingChunk.value.toString(), - waitingTime: waitingTime + targetTimestampMs: targetTimestampMs } as UnstakingInfo); }); diff --git a/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts b/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts index c906ed990b..0e4f56c6e8 100644 --- a/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts +++ b/packages/extension-base/src/services/earning-service/handlers/nomination-pool/index.ts @@ -8,7 +8,7 @@ import KoniState from '@subwallet/extension-base/koni/background/handlers/State' import { _EXPECTED_BLOCK_TIME, _STAKING_ERA_LENGTH_MAP } from '@subwallet/extension-base/services/chain-service/constants'; import { _SubstrateApi } from '@subwallet/extension-base/services/chain-service/types'; import { _getChainSubstrateAddressPrefix } from '@subwallet/extension-base/services/chain-service/utils'; -import { BaseYieldPositionInfo, EarningRewardHistoryItem, EarningRewardItem, EarningStatus, HandleYieldStepData, NominationPoolInfo, NominationYieldPoolInfo, OptimalYieldPath, OptimalYieldPathParams, PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember, PalletStakingExposure, PalletStakingNominations, RequestStakePoolingBonding, StakeCancelWithdrawalParams, SubmitJoinNominationPool, SubmitYieldJoinData, TransactionData, UnstakingStatus, YieldPoolInfo, YieldPoolMethodInfo, YieldPoolType, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { BaseYieldPositionInfo, EarningRewardHistoryItem, EarningRewardItem, EarningStatus, HandleYieldStepData, NominationPoolInfo, NominationYieldPoolInfo, OptimalYieldPath, OptimalYieldPathParams, PalletNominationPoolsBondedPoolInner, PalletNominationPoolsPoolMember, PalletStakingActiveEraInfo, PalletStakingExposure, PalletStakingNominations, RequestStakePoolingBonding, StakeCancelWithdrawalParams, SubmitJoinNominationPool, SubmitYieldJoinData, TransactionData, UnstakingStatus, YieldPoolInfo, YieldPoolMethodInfo, YieldPoolType, YieldPositionInfo, YieldStepBaseInfo, YieldStepType, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; import { balanceFormatter, formatNumber, reformatAddress } from '@subwallet/extension-base/utils'; import BigN from 'bignumber.js'; import { t } from 'i18next'; @@ -190,9 +190,10 @@ export default class NominationPoolHandler extends BasePoolHandler { const poolsPalletId = substrateApi.api.consts.nominationPools.palletId.toString(); const poolStashAccount = parsePoolStashAddress(substrateApi.api, 0, poolMemberInfo.poolId, poolsPalletId); - const [_nominations, _poolMetadata] = await Promise.all([ + const [_nominations, _poolMetadata, _activeEra] = await Promise.all([ substrateApi.api.query.staking.nominators(poolStashAccount), - substrateApi.api.query.nominationPools.metadata(poolMemberInfo.poolId) + substrateApi.api.query.nominationPools.metadata(poolMemberInfo.poolId), + substrateApi.api.query.staking.activeEra() ]); const poolMetadata = _poolMetadata.toPrimitive() as unknown as string; @@ -240,23 +241,22 @@ export default class NominationPoolHandler extends BasePoolHandler { let unstakingBalance = BN_ZERO; Object.entries(poolMemberInfo.unbondingEras).forEach(([unlockingEra, amount]) => { - // Calculate the remaining time for current era ending - const isClaimable = parseInt(unlockingEra) - parseInt(currentEra) < 0; - const remainingEra = parseInt(unlockingEra) - parseInt(currentEra); - const expectedBlockTime = _EXPECTED_BLOCK_TIME[this.chain]; - const eraLength = _deriveSessionProgress.eraLength.toNumber(); - const eraProgress = _deriveSessionProgress.eraProgress.toNumber(); - const remainingSlots = eraLength - eraProgress; - const remainingHours = expectedBlockTime * remainingSlots / 60 / 60; + const activeEra = _activeEra.toPrimitive() as unknown as PalletStakingActiveEraInfo; + const era = parseInt(activeEra.index); + const startTimestampMs = parseInt(activeEra.start); + + const remainingEra = parseInt(unlockingEra) - era; const eraTime = _STAKING_ERA_LENGTH_MAP[chainInfo.slug] || _STAKING_ERA_LENGTH_MAP.default; // in hours - const waitingTime = remainingEra * eraTime + remainingHours; + const remaningTimestampMs = remainingEra * eraTime * 60 * 60 * 1000; + const targetTimestampMs = startTimestampMs + remaningTimestampMs; + const isClaimable = targetTimestampMs - Date.now() <= 0; unstakingBalance = unstakingBalance.add(new BN(amount)); unstakings.push({ chain: chainInfo.slug, status: isClaimable ? UnstakingStatus.CLAIMABLE : UnstakingStatus.UNLOCKING, claimable: amount.toString(), - waitingTime: waitingTime + targetTimestampMs: targetTimestampMs } as UnstakingInfo); }); diff --git a/packages/extension-base/src/types/yield/info/account/unstake.ts b/packages/extension-base/src/types/yield/info/account/unstake.ts index 392f1f3025..3b38fc4807 100644 --- a/packages/extension-base/src/types/yield/info/account/unstake.ts +++ b/packages/extension-base/src/types/yield/info/account/unstake.ts @@ -30,6 +30,8 @@ export interface UnstakingInfo { claimable: string; /** Time remains to wait (in hours) */ waitingTime?: number; + /** Timestamp to withdraw */ + targetTimestampMs?: number; /** Address of validator */ validatorAddress?: string; } diff --git a/packages/extension-base/src/types/yield/info/pallet.ts b/packages/extension-base/src/types/yield/info/pallet.ts index fb90cc052f..e6e0cf2d0b 100644 --- a/packages/extension-base/src/types/yield/info/pallet.ts +++ b/packages/extension-base/src/types/yield/info/pallet.ts @@ -161,6 +161,11 @@ export interface PalletStakingStakingLedger { claimedRewards: number[] } +export interface PalletStakingActiveEraInfo { + index: string, + start: string +} + export interface RuntimeDispatchInfo { weight: { refTime: number, diff --git a/packages/extension-koni-ui/src/Popup/Home/Earning/EarningPositionDetail/WithdrawInfoPart.tsx b/packages/extension-koni-ui/src/Popup/Home/Earning/EarningPositionDetail/WithdrawInfoPart.tsx index da4fe5853f..db9bd43530 100644 --- a/packages/extension-koni-ui/src/Popup/Home/Earning/EarningPositionDetail/WithdrawInfoPart.tsx +++ b/packages/extension-koni-ui/src/Popup/Home/Earning/EarningPositionDetail/WithdrawInfoPart.tsx @@ -13,7 +13,7 @@ import { ThemeProps } from '@subwallet/extension-koni-ui/types'; import { Button, Icon, Number } from '@subwallet/react-ui'; import CN from 'classnames'; import { CheckCircle, ProhibitInset } from 'phosphor-react'; -import React, { Context, useCallback, useContext, useMemo } from 'react'; +import React, { Context, useCallback, useContext, useEffect, useMemo, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import styled, { ThemeContext } from 'styled-components'; import { useLocalStorage } from 'usehooks-ts'; @@ -26,8 +26,7 @@ type Props = ThemeProps & { transactionChainValue: string; }; -function Component ({ className, inputAsset, poolInfo, transactionChainValue, transactionFromValue, - unstakings }: Props) { +function Component ({ className, inputAsset, poolInfo, transactionChainValue, transactionFromValue, unstakings }: Props) { const { t } = useTranslation(); const navigate = useNavigate(); const { slug } = poolInfo; @@ -36,22 +35,35 @@ function Component ({ className, inputAsset, poolInfo, transactionChainValue, tr const [, setCancelUnStakeStorage] = useLocalStorage(CANCEL_UN_STAKE_TRANSACTION, DEFAULT_CANCEL_UN_STAKE_PARAMS); const [, setWithdrawStorage] = useLocalStorage(WITHDRAW_TRANSACTION, DEFAULT_WITHDRAW_PARAMS); + const [currentTimestampMs, setCurrentTimestampMs] = useState(Date.now()); const items = useMemo(() => { return [...unstakings].sort((a, b) => { - if (a.waitingTime === undefined && b.waitingTime === undefined) { - return 0; + if (a.targetTimestampMs === undefined && b.targetTimestampMs === undefined) { + if (a.waitingTime === undefined && b.waitingTime === undefined) { + return 0; + } + + if (a.waitingTime === undefined) { + return -1; + } + + if (b.waitingTime === undefined) { + return 1; + } + + return a.waitingTime - b.waitingTime; } - if (a.waitingTime === undefined) { + if (a.targetTimestampMs === undefined) { return -1; } - if (b.waitingTime === undefined) { + if (b.targetTimestampMs === undefined) { return 1; } - return a.waitingTime - b.waitingTime; + return a.targetTimestampMs - b.targetTimestampMs; }); }, [unstakings]); @@ -59,13 +71,17 @@ function Component ({ className, inputAsset, poolInfo, transactionChainValue, tr let result = BN_ZERO; unstakings.forEach((value) => { - if (value.status === UnstakingStatus.CLAIMABLE) { + const canClaim = value.targetTimestampMs + ? value.targetTimestampMs <= currentTimestampMs + : value.status === UnstakingStatus.CLAIMABLE; + + if (canClaim) { result = result.plus(value.claimable); } }); return result; - }, [unstakings]); + }, [currentTimestampMs, unstakings]); const haveUnlocking = useMemo(() => unstakings.some((i) => i.status === UnstakingStatus.UNLOCKING), [unstakings]); @@ -75,8 +91,8 @@ function Component ({ className, inputAsset, poolInfo, transactionChainValue, tr ); const canWithdraw = useMemo(() => { - return totalWithdrawable.gt(BN_ZERO); - }, [totalWithdrawable]); + return poolInfo.metadata.availableMethod.withdraw && totalWithdrawable.gt(BN_ZERO); + }, [poolInfo.metadata.availableMethod.withdraw, totalWithdrawable]); const onWithDraw = useCallback(() => { setWithdrawStorage({ @@ -100,39 +116,55 @@ function Component ({ className, inputAsset, poolInfo, transactionChainValue, tr const renderWithdrawTime = useCallback( (item: UnstakingInfo) => { - if (item.waitingTime === undefined) { + if (!poolInfo.metadata.availableMethod.withdraw) { return ( - <> -
{t('Waiting for withdrawal')}
- {item.status === UnstakingStatus.CLAIMABLE && ( - - )} - +
{t('Automatic withdrawal')}
); } else { - return ( - <> -
{getWaitingTime(item.waitingTime, item.status, t)}
- {item.status === UnstakingStatus.CLAIMABLE && ( - - )} - - ); + if (item.targetTimestampMs === undefined && item.waitingTime === undefined) { + return ( + <> +
{t('Waiting for withdrawal')}
+ {item.status === UnstakingStatus.CLAIMABLE && ( + + )} + + ); + } else { + return ( + <> +
{getWaitingTime(t, currentTimestampMs, item.targetTimestampMs, item.waitingTime)}
+ {item.status === UnstakingStatus.CLAIMABLE && ( + + )} + + ); + } } }, - [t, token.colorSecondary] + [currentTimestampMs, poolInfo.metadata.availableMethod.withdraw, t, token.colorSecondary] ); + useEffect(() => { + const timer = setInterval(() => { + setCurrentTimestampMs(Date.now()); + }, 1000); + + return () => { + clearInterval(timer); + }; + }, []); + if (!unstakings.length) { return null; } diff --git a/packages/extension-koni-ui/src/Popup/Transaction/helper/earning/earningHandler.ts b/packages/extension-koni-ui/src/Popup/Transaction/helper/earning/earningHandler.ts index 3cb2f9b277..235beb2b6d 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/helper/earning/earningHandler.ts +++ b/packages/extension-koni-ui/src/Popup/Transaction/helper/earning/earningHandler.ts @@ -1,7 +1,7 @@ // Copyright 2019-2022 @subwallet/extension-koni-ui authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { SpecialYieldPoolInfo, SubmitYieldStepData, UnstakingStatus, YieldPoolInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; +import { SpecialYieldPoolInfo, SubmitYieldStepData, YieldPoolInfo, YieldTokenBaseInfo } from '@subwallet/extension-base/types'; // @ts-ignore import humanizeDuration from 'humanize-duration'; import { TFunction } from 'react-i18next'; @@ -20,15 +20,29 @@ export function getUnstakingPeriod (t: TFunction, unstakingPeriod?: number) { return ''; } -export function getWaitingTime (waitingTime: number, status: UnstakingStatus, t: TFunction) { - if (status === UnstakingStatus.CLAIMABLE) { +export function getWaitingTime (t: TFunction, currentTimestampMs: number, targetTimestampMs?: number, waitingTime?: number) { + let remainingTimestampMs: number; + + if (targetTimestampMs !== undefined) { + remainingTimestampMs = targetTimestampMs - currentTimestampMs; + } else { + if (waitingTime !== undefined) { + remainingTimestampMs = waitingTime * 60 * 60 * 1000; + } else { + return t('Automatic withdrawal'); + } + } + + if (remainingTimestampMs <= 0) { return t('Available for withdrawal'); } else { - const waitingTimeInMs = waitingTime * 60 * 60 * 1000; + const remainingTimeHr = remainingTimestampMs / 1000 / 60 / 60; + + // Formatted waitting time without round up // eslint-disable-next-line @typescript-eslint/no-unsafe-call,@typescript-eslint/no-unsafe-assignment - const formattedWaitingTime = humanizeDuration(waitingTimeInMs, { - units: ['d', 'h'], - round: true, + const _formattedWaitingTime = humanizeDuration(remainingTimestampMs, { + units: remainingTimeHr >= 24 ? ['d', 'h'] : ['h', 'm'], + round: false, delimiter: ' ', language: 'shortEn', languages: { @@ -45,6 +59,15 @@ export function getWaitingTime (waitingTime: number, status: UnstakingStatus, t: } // TODO: should not be shorten }) as string; + // Formatted waitting time with round up + const formattedWaitingTime = _formattedWaitingTime.split(' ').map((segment, index) => { + if (index % 2 === 0) { + return Math.ceil(parseFloat(segment)).toString(); + } + + return segment; + }).join(' '); + return t('Withdrawable in {{time}}', { replace: { time: formattedWaitingTime } }); } } diff --git a/packages/extension-koni-ui/src/Popup/Transaction/variants/Earn.tsx b/packages/extension-koni-ui/src/Popup/Transaction/variants/Earn.tsx index 327cbb263e..86dd597081 100644 --- a/packages/extension-koni-ui/src/Popup/Transaction/variants/Earn.tsx +++ b/packages/extension-koni-ui/src/Popup/Transaction/variants/Earn.tsx @@ -172,7 +172,7 @@ const Component = () => { if (fee.slug !== '') { const asset = chainAsset[fee.slug]; const feeDecimals = _getAssetDecimals(asset); - const _priceValue = asset.priceId ? priceMap[asset.priceId] : 0; + const _priceValue = asset.priceId ? (priceMap[asset.priceId] ?? 0) : 0; const feeNumb = _priceValue * (fee.amount ? parseFloat(fee.amount) / 10 ** feeDecimals : 0); _totalFee += feeNumb;