From 0d99cbb7156c30a047b4db32187977c1bc7b42c4 Mon Sep 17 00:00:00 2001 From: Ross Bulat Date: Thu, 2 Jan 2025 10:00:18 +0700 Subject: [PATCH] feat: Use `useCanFastUnstake` query (#2377) --- packages/app/src/Router.tsx | 2 +- .../app/src/StakingApi/FastUnstakeApi.tsx | 24 ++ .../UnclaimedRewardsApi.tsx} | 18 +- packages/app/src/StakingApi/index.tsx | 28 ++ packages/app/src/StakingApi/types.ts | 9 + .../app/src/contexts/FastUnstake/defaults.ts | 13 +- .../app/src/contexts/FastUnstake/index.tsx | 243 +----------------- .../app/src/contexts/FastUnstake/types.ts | 17 +- packages/app/src/contexts/Validators/Utils.ts | 58 ----- packages/app/src/hooks/useUnstaking/index.tsx | 18 +- .../modals/ManageFastUnstake/index.tsx | 24 +- .../Active/Status/NominationStatus.tsx | 11 +- .../app/src/pages/Nominate/Active/index.tsx | 2 +- packages/app/src/workers/stakers.ts | 89 +------ packages/app/src/workers/types.ts | 10 - .../locales/src/resources/cn/library.json | 1 - .../locales/src/resources/en/library.json | 1 - packages/plugin-staking-api/src/index.tsx | 1 + .../src/queries/useCanFastUnstake.tsx | 26 ++ packages/plugin-staking-api/src/types.ts | 17 ++ 20 files changed, 157 insertions(+), 455 deletions(-) create mode 100644 packages/app/src/StakingApi/FastUnstakeApi.tsx rename packages/app/src/{StakingApi.tsx => StakingApi/UnclaimedRewardsApi.tsx} (64%) create mode 100644 packages/app/src/StakingApi/index.tsx create mode 100644 packages/app/src/StakingApi/types.ts create mode 100644 packages/plugin-staking-api/src/queries/useCanFastUnstake.tsx diff --git a/packages/app/src/Router.tsx b/packages/app/src/Router.tsx index 4ddb988a83..a67136edfb 100644 --- a/packages/app/src/Router.tsx +++ b/packages/app/src/Router.tsx @@ -61,7 +61,7 @@ const RouterInner = () => { return ( {pluginEnabled('staking_api') && !inSetup() && activeAccount && ( - + )} diff --git a/packages/app/src/StakingApi/FastUnstakeApi.tsx b/packages/app/src/StakingApi/FastUnstakeApi.tsx new file mode 100644 index 0000000000..8b162a21d9 --- /dev/null +++ b/packages/app/src/StakingApi/FastUnstakeApi.tsx @@ -0,0 +1,24 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useFastUnstake } from 'contexts/FastUnstake' +import { useCanFastUnstake } from 'plugin-staking-api' +import { useEffect } from 'react' +import type { Props } from './types' + +export const FastUnstakeApi = ({ activeAccount, network }: Props) => { + const { setFastUnstakeStatus } = useFastUnstake() + const { data, loading, error } = useCanFastUnstake({ + chain: network, + who: activeAccount, + }) + + // Update fast unstake status on active account change. Must be bonding + useEffect(() => { + if (!loading && !error && data?.canFastUnstake) { + setFastUnstakeStatus(data.canFastUnstake) + } + }, [JSON.stringify(data?.canFastUnstake)]) + + return null +} diff --git a/packages/app/src/StakingApi.tsx b/packages/app/src/StakingApi/UnclaimedRewardsApi.tsx similarity index 64% rename from packages/app/src/StakingApi.tsx rename to packages/app/src/StakingApi/UnclaimedRewardsApi.tsx index fde0b032ac..5f5f1989d8 100644 --- a/packages/app/src/StakingApi.tsx +++ b/packages/app/src/StakingApi/UnclaimedRewardsApi.tsx @@ -2,18 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only import { useApi } from 'contexts/Api' -import { useNetwork } from 'contexts/Network' import { usePayouts } from 'contexts/Payouts' -import { ApolloProvider, client, useUnclaimedRewards } from 'plugin-staking-api' +import { useUnclaimedRewards } from 'plugin-staking-api' import { useEffect } from 'react' +import type { Props } from './types' -interface Props { - activeAccount: string -} - -const Inner = ({ activeAccount }: Props) => { +export const UnclaimedRewardsApi = ({ activeAccount, network }: Props) => { const { activeEra } = useApi() - const { network } = useNetwork() const { setUnclaimedRewards } = usePayouts() const { data, loading, error } = useUnclaimedRewards({ @@ -22,6 +17,7 @@ const Inner = ({ activeAccount }: Props) => { fromEra: Math.max(activeEra.index.minus(1).toNumber(), 0), }) + // Update unclaimed rewards on total change useEffect(() => { if (!loading && !error && data?.unclaimedRewards) { setUnclaimedRewards(data?.unclaimedRewards) @@ -30,9 +26,3 @@ const Inner = ({ activeAccount }: Props) => { return null } - -export const StakingApi = (props: Props) => ( - - - -) diff --git a/packages/app/src/StakingApi/index.tsx b/packages/app/src/StakingApi/index.tsx new file mode 100644 index 0000000000..ceb2bcbb8c --- /dev/null +++ b/packages/app/src/StakingApi/index.tsx @@ -0,0 +1,28 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { useFastUnstake } from 'contexts/FastUnstake' +import { useStaking } from 'contexts/Staking' +import { ApolloProvider, client } from 'plugin-staking-api' +import { useEffect } from 'react' +import { FastUnstakeApi } from './FastUnstakeApi' +import type { Props } from './types' +import { UnclaimedRewardsApi } from './UnclaimedRewardsApi' + +export const StakingApi = (props: Props) => { + const { isBonding } = useStaking() + const { setFastUnstakeStatus } = useFastUnstake() + + useEffect(() => { + if (!isBonding()) { + setFastUnstakeStatus(null) + } + }, [isBonding()]) + + return ( + + + {isBonding() && } + + ) +} diff --git a/packages/app/src/StakingApi/types.ts b/packages/app/src/StakingApi/types.ts new file mode 100644 index 0000000000..2aa6b1fa3f --- /dev/null +++ b/packages/app/src/StakingApi/types.ts @@ -0,0 +1,9 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import type { NetworkId } from 'common-types' + +export interface Props { + activeAccount: string + network: NetworkId +} diff --git a/packages/app/src/contexts/FastUnstake/defaults.ts b/packages/app/src/contexts/FastUnstake/defaults.ts index f1b78094e1..a94330af7a 100644 --- a/packages/app/src/contexts/FastUnstake/defaults.ts +++ b/packages/app/src/contexts/FastUnstake/defaults.ts @@ -2,18 +2,13 @@ // SPDX-License-Identifier: GPL-3.0-only /* eslint-disable @typescript-eslint/no-unused-vars */ -import type { FastUnstakeContextInterface, MetaInterface } from './types' - -export const defaultMeta: MetaInterface = { - checked: [], -} +import type { FastUnstakeContextInterface } from './types' export const defaultFastUnstakeContext: FastUnstakeContextInterface = { - getLocalkey: (address) => '', - checking: false, - meta: defaultMeta, - isExposed: null, + exposed: false, head: undefined, queueDeposit: undefined, counterForQueue: undefined, + fastUnstakeStatus: null, + setFastUnstakeStatus: (status) => {}, } diff --git a/packages/app/src/contexts/FastUnstake/index.tsx b/packages/app/src/contexts/FastUnstake/index.tsx index 00297d6cdf..fa64a542a6 100644 --- a/packages/app/src/contexts/FastUnstake/index.tsx +++ b/packages/app/src/contexts/FastUnstake/index.tsx @@ -1,9 +1,6 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import { useEffectIgnoreInitial } from '@w3ux/hooks' -import type { AnyJson } from '@w3ux/types' -import { setStateWithRef } from '@w3ux/utils' import { FastUnstakeConfig } from 'api/subscribe/fastUnstakeConfig' import type { FastUnstakeHead } from 'api/subscribe/fastUnstakeConfig/types' import { FastUnstakeQueue } from 'api/subscribe/fastUnstakeQueue' @@ -11,26 +8,19 @@ import BigNumber from 'bignumber.js' import { useActiveAccounts } from 'contexts/ActiveAccounts' import { useApi } from 'contexts/Api' import { useNetwork } from 'contexts/Network' -import { useStaking } from 'contexts/Staking' -import { validateLocalExposure } from 'contexts/Validators/Utils' import { Apis } from 'controllers/Apis' import { Subscriptions } from 'controllers/Subscriptions' import { isCustomEvent } from 'controllers/utils' +import type { FastUnstakeResult } from 'plugin-staking-api/types' import type { ReactNode } from 'react' import { createContext, useContext, useEffect, useRef, useState } from 'react' -import type { MaybeAddress } from 'types' import { useEventListener } from 'usehooks-ts' -import Worker from 'workers/stakers?worker' -import { defaultFastUnstakeContext, defaultMeta } from './defaults' +import { defaultFastUnstakeContext } from './defaults' import type { FastUnstakeContextInterface, FastUnstakeQueueDeposit, - LocalMeta, - MetaInterface, } from './types' -const worker = new Worker() - export const FastUnstakeContext = createContext( defaultFastUnstakeContext ) @@ -38,54 +28,27 @@ export const FastUnstakeContext = createContext( export const useFastUnstake = () => useContext(FastUnstakeContext) export const FastUnstakeProvider = ({ children }: { children: ReactNode }) => { + const { isReady } = useApi() const { network } = useNetwork() const { activeAccount } = useActiveAccounts() - const { inSetup, fetchEraStakers, isBonding } = useStaking() - const { - consts, - isReady, - activeEra, - consts: { bondDuration }, - networkMetrics: { fastUnstakeErasToCheckPerBlock }, - } = useApi() - - const { maxExposurePageSize } = consts - - // store whether a fast unstake check is in progress - const [checking, setChecking] = useState(false) - const checkingRef = useRef(checking) - // store whether the account is exposed for fast unstake - const [isExposed, setIsExposed] = useState(null) - const isExposedRef = useRef(isExposed) + // Store fast unstake status + const [fastUnstakeStatus, setFastUnstakeStatus] = + useState(null) - // store state of elibigility checking - const [meta, setMeta] = useState(defaultMeta) - const metaRef = useRef(meta) - - // store fastUnstake queue deposit for user + // Store fastUnstake queue deposit for user const [queueDeposit, setQueueDeposit] = useState() - // store fastUnstake head + // Store fastUnstake head const [head, setHead] = useState() - // store fastUnstake counter for queue + // Store fastUnstake counter for queue const [counterForQueue, setCounterForQueue] = useState() - // localStorage key to fetch local metadata - const getLocalkey = (a: MaybeAddress) => `${network}_fast_unstake_${a}` - - // check until bond duration eras surpasssed - const checkToEra = activeEra.index.minus(bondDuration) - // Reset state on active account change useEffect(() => { // Reset fast unstake managment state setQueueDeposit(undefined) - setStateWithRef(false, setChecking, checkingRef) - setStateWithRef(null, setIsExposed, isExposedRef) - setStateWithRef(defaultMeta, setMeta, metaRef) - // Re-subscribe to fast unstake queue Subscriptions.remove(network, 'fastUnstakeQueue') @@ -111,159 +74,6 @@ export const FastUnstakeProvider = ({ children }: { children: ReactNode }) => { } }, [isReady]) - // initiate fast unstake check for accounts that are nominating but not active - useEffectIgnoreInitial(() => { - if ( - isReady && - activeAccount && - !activeEra.index.isZero() && - fastUnstakeErasToCheckPerBlock > 0 && - isBonding() - ) { - // get any existing localStorage records for account - const localMeta: LocalMeta | null = getLocalMeta() - - const initialMeta = localMeta - ? { checked: localMeta.checked } - : defaultMeta - - // even if localMeta.isExposed is false, we don't assume a final value until current era + - // bondDuration is checked - let initialIsExposed = null - if (localMeta) { - if (bondDuration.plus(1).isEqualTo(localMeta.checked.length)) { - initialIsExposed = localMeta.isExposed - } else if (localMeta.isExposed === true) { - initialIsExposed = true - } else { - initialIsExposed = null - } - } - - // Initial local meta: localMeta - setStateWithRef(initialMeta, setMeta, metaRef) - setStateWithRef(initialIsExposed, setIsExposed, isExposedRef) - - // start process if account is inactively nominating & local fast unstake data is not - // complete - if ( - activeAccount && - !inSetup() && - initialIsExposed === null && - isBonding() - ) { - // if localMeta existed, start checking from the next era - const nextEra = localMeta?.checked.at(-1) || 0 - const maybeNextEra = localMeta - ? new BigNumber(nextEra - 1) - : activeEra.index - - // Check from the possible next era `maybeNextEra` - processEligibility(activeAccount, maybeNextEra) - } - } - }, [ - inSetup(), - isReady, - activeEra.index, - fastUnstakeErasToCheckPerBlock, - isBonding(), - ]) - - // handle worker message on completed exposure check - worker.onmessage = (message: MessageEvent) => { - if (message) { - // ensure correct task received - const { data } = message - const { task } = data - if (task !== 'processEraForExposure') { - return - } - - // ensure still same conditions - const { networkName, who } = data - if (networkName !== network || who !== activeAccount) { - return - } - - const { era, exposed } = data - - // ensure checked eras are in order highest first - const checked = metaRef.current.checked - .concat(Number(era)) - .sort((a: number, b: number) => b - a) - - if (!metaRef.current.checked.includes(Number(era))) { - // update localStorage with updated changes - localStorage.setItem( - getLocalkey(who), - JSON.stringify({ - isExposed: exposed, - checked, - }) - ) - - // update check metadata - setStateWithRef( - { - checked, - }, - setMeta, - metaRef - ) - } - - if (exposed) { - // Account is exposed - stop checking - // cancel checking and update exposed state - setStateWithRef(false, setChecking, checkingRef) - setStateWithRef(true, setIsExposed, isExposedRef) - } else if (bondDuration.plus(1).isEqualTo(checked.length)) { - // successfully checked current era - bondDuration eras - setStateWithRef(false, setChecking, checkingRef) - setStateWithRef(false, setIsExposed, isExposedRef) - } else { - // Finished, not exposed - // continue checking the next era - checkEra(new BigNumber(era).minus(1)) - } - } - } - - // initiate fast unstake eligibility check - const processEligibility = async (a: MaybeAddress, era: BigNumber) => { - // ensure current era has synced - if ( - era.isLessThan(0) || - !bondDuration.isGreaterThan(0) || - !a || - checkingRef.current || - !activeAccount || - !isBonding() - ) { - return - } - - setStateWithRef(true, setChecking, checkingRef) - checkEra(era) - } - - // calls service worker to check exppsures for given era - const checkEra = async (era: BigNumber) => { - const exposures = await fetchEraStakers(era.toString()) - - worker.postMessage({ - task: 'processEraForExposure', - era: era.toString(), - who: activeAccount, - networkName: network, - exitOnExposed: true, - maxExposurePageSize: maxExposurePageSize.toString(), - exposures, - }) - } - - // subscribe to fastUnstake queue const subscribeToFastUnstakeMeta = async () => { const api = Apis.getApi(network) if (!api) { @@ -276,31 +86,6 @@ export const FastUnstakeProvider = ({ children }: { children: ReactNode }) => { ) } - // gets any existing fast unstake metadata for an account - const getLocalMeta = (): LocalMeta | null => { - const localMeta: AnyJson = localStorage.getItem(getLocalkey(activeAccount)) - if (!localMeta) { - return null - } - - const localMetaValidated = validateLocalExposure( - JSON.parse(localMeta), - checkToEra - ) - if (!localMetaValidated) { - // remove if not valid - localStorage.removeItem(getLocalkey(activeAccount)) - return null - } - // set validated localStorage - localStorage.setItem( - getLocalkey(activeAccount), - JSON.stringify(localMetaValidated) - ) - return localMetaValidated - } - - // Handle fast unstake meta events const handleNewFastUnstakeConfig = (e: Event) => { if (isCustomEvent(e)) { const { head: eventHead, counterForQueue: eventCounterForQueue } = @@ -310,7 +95,6 @@ export const FastUnstakeProvider = ({ children }: { children: ReactNode }) => { } } - // Handle fast unstake deposit events const handleNewFastUnstakeDeposit = (e: Event) => { if (isCustomEvent(e)) { const { address, deposit } = e.detail @@ -333,13 +117,14 @@ export const FastUnstakeProvider = ({ children }: { children: ReactNode }) => { return ( {children} diff --git a/packages/app/src/contexts/FastUnstake/types.ts b/packages/app/src/contexts/FastUnstake/types.ts index 2aaa8fa4f0..17385ef1b4 100644 --- a/packages/app/src/contexts/FastUnstake/types.ts +++ b/packages/app/src/contexts/FastUnstake/types.ts @@ -3,24 +3,15 @@ import type { FastUnstakeHead } from 'api/subscribe/fastUnstakeConfig/types' import type BigNumber from 'bignumber.js' -import type { MaybeAddress } from 'types' - -export interface LocalMeta { - isExposed: boolean - checked: number[] -} -export interface MetaInterface { - checked: number[] -} +import type { FastUnstakeResult } from 'plugin-staking-api/types' export interface FastUnstakeContextInterface { - getLocalkey: (address: MaybeAddress) => string - checking: boolean - meta: MetaInterface - isExposed: boolean | null + exposed: boolean queueDeposit: FastUnstakeQueueDeposit | undefined head: FastUnstakeHead | undefined counterForQueue: number | undefined + fastUnstakeStatus: FastUnstakeResult | null + setFastUnstakeStatus: (status: FastUnstakeResult | null) => void } export interface FastUnstakeQueueDeposit { diff --git a/packages/app/src/contexts/Validators/Utils.ts b/packages/app/src/contexts/Validators/Utils.ts index 1ff4b37bc9..41d23d2e51 100644 --- a/packages/app/src/contexts/Validators/Utils.ts +++ b/packages/app/src/contexts/Validators/Utils.ts @@ -1,10 +1,8 @@ // Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors // SPDX-License-Identifier: GPL-3.0-only -import type { AnyJson } from '@w3ux/types' import BigNumber from 'bignumber.js' import type { NetworkId } from 'common-types' -import type { LocalMeta } from 'contexts/FastUnstake/types' import type { EraRewardPoints, LocalValidatorEntriesData, @@ -49,62 +47,6 @@ export const setLocalEraValidators = ( ) } -// Validate local exposure metadata, currently used for fast unstake only -export const validateLocalExposure = ( - localMeta: AnyJson, - endEra: BigNumber -): LocalMeta | null => { - const localIsExposed = localMeta?.isExposed ?? null - let localChecked = localMeta?.checked ?? null - - // check types saved - if (typeof localIsExposed !== 'boolean' || !Array.isArray(localChecked)) { - return null - } - - // check checked only contains numbers - const checkedNumeric = localChecked.every((e) => typeof e === 'number') - if (!checkedNumeric) { - return null - } - - // remove any expired eras and sort highest first - localChecked = localChecked - .filter((e: number) => endEra.isLessThan(e)) - .sort((a: number, b: number) => b - a) - - // if no remaining eras, invalid - if (!localChecked.length) { - return null - } - - // check if highest -> lowest are decremented, no missing eras - let i = 0 - let prev = 0 - const noMissingEras = localChecked.every((e: number) => { - i++ - if (i === 1) { - prev = e - return true - } - const p = prev - prev = e - if (e === p - 1) { - return true - } - return false - }) - - if (!noMissingEras) { - return null - } - - return { - isExposed: localIsExposed, - checked: localChecked, - } -} - // Check if era reward points entry exists for an era export const hasLocalEraRewardPoints = (network: NetworkId, era: string) => { const current = JSON.parse( diff --git a/packages/app/src/hooks/useUnstaking/index.tsx b/packages/app/src/hooks/useUnstaking/index.tsx index 5aa60b30c5..2c07c66b5f 100644 --- a/packages/app/src/hooks/useUnstaking/index.tsx +++ b/packages/app/src/hooks/useUnstaking/index.tsx @@ -3,7 +3,6 @@ import type { AnyJson } from '@w3ux/types' import { useActiveAccounts } from 'contexts/ActiveAccounts' -import { useApi } from 'contexts/Api' import { useFastUnstake } from 'contexts/FastUnstake' import { useStaking } from 'contexts/Staking' import { useTransferOptions } from 'contexts/TransferOptions' @@ -12,13 +11,12 @@ import { useNominationStatus } from '../useNominationStatus' export const useUnstaking = () => { const { t } = useTranslation('library') - const { consts, activeEra } = useApi() const { inSetup } = useStaking() const { activeAccount } = useActiveAccounts() const { getTransferOptions } = useTransferOptions() const { getNominationStatus } = useNominationStatus() - const { checking, head, isExposed, queueDeposit, meta } = useFastUnstake() - const { bondDuration } = consts + const { head, queueDeposit, fastUnstakeStatus, exposed } = useFastUnstake() + const transferOptions = getTransferOptions(activeAccount).nominate const { nominees } = getNominationStatus(activeAccount, 'nominator') @@ -34,17 +32,9 @@ export const useUnstaking = () => { // determine unstake button const getFastUnstakeText = () => { - const { checked } = meta - if (checking) { - return `${t('fastUnstakeCheckingEras', { - checked: checked.length, - total: bondDuration.toString(), - })}...` - } - if (isExposed) { - const lastExposed = activeEra.index.minus(checked[0] || 0) + if (exposed && fastUnstakeStatus?.lastExposed) { return t('fastUnstakeExposed', { - count: lastExposed.toNumber(), + count: Number(fastUnstakeStatus.lastExposed), }) } if (registered) { diff --git a/packages/app/src/overlay/modals/ManageFastUnstake/index.tsx b/packages/app/src/overlay/modals/ManageFastUnstake/index.tsx index b1de62a9c9..e30af8a600 100644 --- a/packages/app/src/overlay/modals/ManageFastUnstake/index.tsx +++ b/packages/app/src/overlay/modals/ManageFastUnstake/index.tsx @@ -44,9 +44,9 @@ export const ManageFastUnstake = () => { const { getSignerWarnings } = useSignerWarnings() const { setModalResize, setModalStatus } = useOverlay().modal const { feeReserve, getTransferOptions } = useTransferOptions() - const { isExposed, counterForQueue, queueDeposit, meta } = useFastUnstake() + const { counterForQueue, queueDeposit, fastUnstakeStatus, exposed } = + useFastUnstake() - const { checked } = meta const controller = getBondedAccount(activeAccount) const allTransferOptions = getTransferOptions(activeAccount) const { nominate, transferrableBalance } = allTransferOptions @@ -63,12 +63,12 @@ export const ManageFastUnstake = () => { fastUnstakeErasToCheckPerBlock > 0 && ((!isFastUnstaking && enoughForDeposit && - isExposed === false && + fastUnstakeStatus?.status === 'NOT_EXPOSED' && totalUnlockChunks === 0) || isFastUnstaking) ) }, [ - isExposed, + fastUnstakeStatus?.status, fastUnstakeErasToCheckPerBlock, totalUnlockChunks, isFastUnstaking, @@ -77,7 +77,10 @@ export const ManageFastUnstake = () => { feeReserve, ]) - useEffect(() => setModalResize(), [isExposed, queueDeposit, isFastUnstaking]) + useEffect( + () => setModalResize(), + [fastUnstakeStatus?.status, queueDeposit, isFastUnstaking] + ) const getTx = () => { let tx = null @@ -130,9 +133,10 @@ export const ManageFastUnstake = () => { } // manage last exposed - const lastExposedAgo = !isExposed - ? new BigNumber(0) - : activeEra.index.minus(checked[0] || 0) + const lastExposedAgo = + !exposed || !fastUnstakeStatus?.lastExposed + ? new BigNumber(0) + : activeEra.index.minus(fastUnstakeStatus.lastExposed.toString()) const erasRemaining = BigNumber.max(1, bondDuration.minus(lastExposedAgo)) @@ -151,7 +155,7 @@ export const ManageFastUnstake = () => { ) : null} - {isExposed ? ( + {exposed ? ( <> { )} - {!isExposed ? ( + {!exposed ? ( 0 && !nominationStatus.nominees.active.length && - (checking || !isExposed) + fastUnstakeStatus !== null && + !exposed ? { - disabled: checking || isReadOnlyAccount(controller), - title: fastUnstakeText, + disabled: isReadOnlyAccount(controller), + title: getFastUnstakeText(), icon: faBolt, onClick: () => { openModal({ key: 'ManageFastUnstake', size: 'sm' }) diff --git a/packages/app/src/pages/Nominate/Active/index.tsx b/packages/app/src/pages/Nominate/Active/index.tsx index 7d178cbe67..5c65e4a6a2 100644 --- a/packages/app/src/pages/Nominate/Active/index.tsx +++ b/packages/app/src/pages/Nominate/Active/index.tsx @@ -48,8 +48,8 @@ export const Active = () => { - + {!isFastUnstaking && } diff --git a/packages/app/src/workers/stakers.ts b/packages/app/src/workers/stakers.ts index 761b8164fc..d1bf9db4bc 100644 --- a/packages/app/src/workers/stakers.ts +++ b/packages/app/src/workers/stakers.ts @@ -9,8 +9,7 @@ import type { ExposureOther, Staker, } from 'contexts/Staking/types' -import type { LocalValidatorExposure } from 'contexts/Validators/types' -import type { ProcessEraForExposureArgs, ProcessExposuresArgs } from './types' +import type { ProcessExposuresArgs } from './types' // eslint-disable-next-line @typescript-eslint/no-explicit-any export const ctx: Worker = self as any @@ -24,97 +23,11 @@ ctx.addEventListener('message', (event: AnyJson) => { case 'processExposures': message = processExposures(data as ProcessExposuresArgs) break - case 'processEraForExposure': - message = processEraForExposure(data as ProcessEraForExposureArgs) - break default: } postMessage({ task, ...message }) }) -// Process era exposures and return if an account was exposed, along with the validator they backed. -const processEraForExposure = (data: ProcessEraForExposureArgs) => { - const { - era, - maxExposurePageSize, - exposures, - exitOnExposed, - task, - networkName, - who, - } = data - let exposed = false - - // If exposed, the validator that was backed. - const exposedValidators: Record = {} - - // Check exposed as validator or nominator. - exposures.every(({ keys, val }) => { - const validator = keys[1] - const others = val?.others ?? [] - const own = val?.own || '0' - const total = val?.total || '0' - const isValidator = validator === who - - if (isValidator) { - const share = new BigNumber(own).isZero() - ? '0' - : new BigNumber(own).dividedBy(total).toString() - - exposedValidators[validator] = { - staked: own, - total, - share, - isValidator, - // Validator is paid regardless of page. Default to page 1. - exposedPage: 1, - } - - exposed = true - if (exitOnExposed) { - return false - } - } - - const inOthers = others.find((o) => o.who === who) - - if (inOthers) { - const index = others.findIndex((o) => o.who === who) - const exposedPage = Math.floor(index / Number(maxExposurePageSize)) - - const share = - new BigNumber(inOthers.value).isZero() || total === '0' - ? '0' - : new BigNumber(inOthers.value).dividedBy(total).toString() - - exposedValidators[validator] = { - staked: inOthers.value, - total, - share, - isValidator, - exposedPage, - } - exposed = true - if (exitOnExposed) { - return false - } - } - - return true - }) - - return { - networkName, - era, - exposed, - exposedValidators: Object.keys(exposedValidators).length - ? exposedValidators - : null, - task, - who, - } -} - // process exposures. // // abstracts active nominators erasStakers. diff --git a/packages/app/src/workers/types.ts b/packages/app/src/workers/types.ts index fb1fcd2af7..e6fe8a42d3 100644 --- a/packages/app/src/workers/types.ts +++ b/packages/app/src/workers/types.ts @@ -28,13 +28,3 @@ export interface ProcessExposuresResponse { activeValidators: number who: MaybeAddress } - -export interface ProcessEraForExposureArgs { - era: string - maxExposurePageSize: string - exposures: Exposure[] - exitOnExposed: boolean - task: string - networkName: NetworkId - who: MaybeAddress -} diff --git a/packages/locales/src/resources/cn/library.json b/packages/locales/src/resources/cn/library.json index 85280b1cc9..b6814bc651 100644 --- a/packages/locales/src/resources/cn/library.json +++ b/packages/locales/src/resources/cn/library.json @@ -80,7 +80,6 @@ "exclude": "不含", "failed": "失败", "fastUnstake": "快速解除抵押", - "fastUnstakeCheckingEras": "正在查验 {{total}} Eras中的 {{checked}}", "fastUnstakeExposed": "在 {{count}} Era前己被显示", "favorite": "收藏夹", "favoritePoolAdded": "己添加提名池", diff --git a/packages/locales/src/resources/en/library.json b/packages/locales/src/resources/en/library.json index cf43679553..418ac41e0e 100644 --- a/packages/locales/src/resources/en/library.json +++ b/packages/locales/src/resources/en/library.json @@ -81,7 +81,6 @@ "exclude": "Exclude", "failed": "Failed", "fastUnstake": "Fast Unstake", - "fastUnstakeCheckingEras": "Checking {{checked}} of {{total}} Eras", "fastUnstakeExposed_one": "Exposed {{count}} Era Ago", "fastUnstakeExposed_other": "Exposed {{count}} Eras Ago", "favorite": "Favorite", diff --git a/packages/plugin-staking-api/src/index.tsx b/packages/plugin-staking-api/src/index.tsx index 78fff87fae..9c44e80ff6 100644 --- a/packages/plugin-staking-api/src/index.tsx +++ b/packages/plugin-staking-api/src/index.tsx @@ -4,6 +4,7 @@ import { ApolloProvider } from '@apollo/client' export * from './Client' +export * from './queries/useCanFastUnstake' export * from './queries/usePoolRewards' export * from './queries/useRewards' export * from './queries/useTokenPrice' diff --git a/packages/plugin-staking-api/src/queries/useCanFastUnstake.tsx b/packages/plugin-staking-api/src/queries/useCanFastUnstake.tsx new file mode 100644 index 0000000000..541cc28a94 --- /dev/null +++ b/packages/plugin-staking-api/src/queries/useCanFastUnstake.tsx @@ -0,0 +1,26 @@ +// Copyright 2024 @polkadot-cloud/polkadot-staking-dashboard authors & contributors +// SPDX-License-Identifier: GPL-3.0-only + +import { gql, useQuery } from '@apollo/client' +import type { CanFastUnstakeResult } from '../types' + +const QUERY = gql` + query PoolRewards($chain: String!, $who: String!) { + canFastUnstake(chain: $chain, who: $who) { + status + } + } +` + +export const useCanFastUnstake = ({ + chain, + who, +}: { + chain: string + who: string +}): CanFastUnstakeResult => { + const { loading, error, data, refetch } = useQuery(QUERY, { + variables: { chain, who }, + }) + return { loading, error, data, refetch } +} diff --git a/packages/plugin-staking-api/src/types.ts b/packages/plugin-staking-api/src/types.ts index af5532a9f3..cac3de66e0 100644 --- a/packages/plugin-staking-api/src/types.ts +++ b/packages/plugin-staking-api/src/types.ts @@ -54,6 +54,23 @@ export type PoolRewardResults = Query & { } } +export type FastUnstakeStatus = + | 'UNSUPPORTED_CHAIN' + | 'NOT_PROCESSED' + | 'NOT_EXPOSED' + | 'EXPOSED' + +export interface FastUnstakeResult { + status: FastUnstakeStatus + lastExposed?: number +} + +export type CanFastUnstakeResult = Query & { + data: { + canFastUnstake: FastUnstakeResult + } +} + export interface UnclaimedRewards { total: string entries: EraUnclaimedReward[]