Skip to content

Commit

Permalink
refactor: add useLockedInReferenda (PolkaGate#1608)
Browse files Browse the repository at this point in the history
Co-authored-by: Amir Ekbatanifard <[email protected]>
  • Loading branch information
Nick-1979 and AMIRKHANEF authored Oct 27, 2024
1 parent 5a70c77 commit 7d55deb
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@

/* eslint-disable react/jsx-max-props-per-line */

// @ts-ignore
import type { PalletBalancesBalanceLock } from '@polkadot/types/lookup';
import type { UnlockInformationType } from '..';

import { faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
import { faLock, faUnlockAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Divider, Grid, IconButton, Typography, useTheme } from '@mui/material';
import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { BN_MAX_INTEGER } from '@polkadot/util';
import React, { useCallback } from 'react';

import { FormatPrice, ShowBalance } from '../../../components';
import { useAccountLocks, useCurrentBlockNumber, useHasDelegated, useInfo, useTimeToUnlock, useTranslation } from '../../../hooks';
import { useAnimateOnce, useInfo, useLockedInReferenda, useTranslation } from '../../../hooks';
import { TIME_TO_SHAKE_ICON } from '../../../util/constants';
import { popupNumbers } from '..';

Expand All @@ -32,26 +28,9 @@ export default function LockedInReferendaFS ({ address, price, refreshNeeded, se
const theme = useTheme();

const { api, decimal, token } = useInfo(address);
const delegatedBalance = useHasDelegated(address, refreshNeeded);
const referendaLocks = useAccountLocks(address, 'referenda', 'convictionVoting', false, refreshNeeded);
const currentBlock = useCurrentBlockNumber(address);
const { timeToUnlock, totalLocked, unlockableAmount } = useTimeToUnlock(address, referendaLocks, refreshNeeded);

const [shake, setShake] = useState<boolean>();

const classToUnlock = currentBlock ? referendaLocks?.filter((ref) => ref.endBlock.ltn(currentBlock) && ref.classId.lt(BN_MAX_INTEGER)) : undefined;
const isDisable = useMemo(() => !unlockableAmount || unlockableAmount.isZero() || !classToUnlock || !totalLocked, [classToUnlock, totalLocked, unlockableAmount]);

const hasDescription = useMemo(() =>
(unlockableAmount && !unlockableAmount.isZero()) || (delegatedBalance && !delegatedBalance.isZero()) || timeToUnlock
, [delegatedBalance, timeToUnlock, unlockableAmount]);

useEffect(() => {
if (unlockableAmount && !unlockableAmount.isZero()) {
setShake(true);
setTimeout(() => setShake(false), TIME_TO_SHAKE_ICON);
}
}, [unlockableAmount]);
const { classToUnlock, delegatedBalance, hasDescription, isDisable, timeToUnlock, totalLocked, unlockableAmount } = useLockedInReferenda(address, refreshNeeded);
const shake = useAnimateOnce(unlockableAmount && !unlockableAmount.isZero(), { duration: TIME_TO_SHAKE_ICON });

const onUnlock = useCallback(() => {
if (isDisable) {
Expand Down Expand Up @@ -110,7 +89,7 @@ export default function LockedInReferendaFS ({ address, price, refreshNeeded, se
>
<FontAwesomeIcon
color={isDisable ? theme.palette.action.disabledBackground : theme.palette.secondary.light}
icon={faUnlockAlt}
icon={ unlockableAmount && !unlockableAmount.isZero() ? faUnlockAlt : faLock}
shake={shake}
style={{ height: '25px' }}
/>
Expand Down
1 change: 1 addition & 0 deletions packages/extension-polkagate/src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export { default as useIsRecoverableTooltipText } from './useIsRecoverableToolti
export { default as useIsTestnetEnabled } from './useIsTestnetEnabled';
export { default as useIsValidator } from './useIsValidator';
export { useLedger } from './useLedger';
export { default as useLockedInReferenda } from './useLockedInReferenda';
export { default as useLostAccountInformation } from './useLostAccountInformation';
export { default as useManifest } from './useManifest';
export { useMapEntries } from './useMapEntries';
Expand Down
7 changes: 4 additions & 3 deletions packages/extension-polkagate/src/hooks/useAccountLocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { ApiPromise } from '@polkadot/api';
import type { Option, u32 } from '@polkadot/types';
// @ts-ignore
import type { PalletConvictionVotingVoteAccountVote, PalletConvictionVotingVoteCasting, PalletConvictionVotingVoteVoting, PalletReferendaReferendumInfoConvictionVotingTally } from '@polkadot/types/lookup';
import type { ITuple } from '@polkadot/types-codec/types';
import type { BN } from '@polkadot/util';

import { useEffect, useMemo, useState } from 'react';
Expand All @@ -16,7 +17,6 @@ import { BN_MAX_INTEGER, BN_ZERO } from '@polkadot/util';
import { CONVICTIONS } from '../fullscreen/governance/utils/consts';
import useCurrentBlockNumber from './useCurrentBlockNumber';
import { useInfo } from '.';
import type { ITuple } from '@polkadot/types-codec/types';

export interface Lock {
classId: BN;
Expand Down Expand Up @@ -124,6 +124,8 @@ export default function useAccountLocks (address: string | undefined, palletRefe
return undefined;
}

setInfo(undefined);

const locks = await api.query[palletVote]?.['classLocksFor'](formatted) as unknown as [BN, BN][];
const lockClasses = locks?.length
? locks.map((l) => l[0])
Expand Down Expand Up @@ -203,8 +205,7 @@ export default function useAccountLocks (address: string | undefined, palletRefe
});
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
getLockClass();
getLockClass().catch(console.error);
}, [api, chain?.genesisHash, formatted, palletReferenda, palletVote, refresh]);

return useMemo(() => {
Expand Down
11 changes: 6 additions & 5 deletions packages/extension-polkagate/src/hooks/useCurrentBlockNumber.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

import { useEffect, useState } from 'react';

import type { AccountId } from '@polkadot/types/interfaces/runtime';

import { useEffect, useState } from 'react';

import { useApi } from '.';

export default function useCurrentBlockNumber(address: AccountId | string | undefined): number | undefined {
export default function useCurrentBlockNumber (address: AccountId | string | undefined): number | undefined {
const api = useApi(address);

const [blockNumber, setCurrentBlockNumber] = useState<number | undefined>();

useEffect(() => {
api && api.rpc.chain.getHeader().then((b) => setCurrentBlockNumber(b.number.unwrap().toNumber()));
api?.rpc.chain.getHeader()
.then((b) => setCurrentBlockNumber(b.number.unwrap().toNumber()))
.catch(console.error);
}, [api]);

return blockNumber;
Expand Down
57 changes: 35 additions & 22 deletions packages/extension-polkagate/src/hooks/useHasDelegated.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,66 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0
// @ts-nocheck

import type { ApiPromise } from '@polkadot/api';
//@ts-ignore
import type { PalletConvictionVotingVoteVoting } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';

import { useEffect, useState } from 'react';

import { BN, BN_ZERO } from '@polkadot/util';
import { BN_ZERO } from '@polkadot/util';

import useApi from './useApi';
import useFormatted from './useFormatted';
import { useChain, useTracks } from '.';
import { useInfo, useTracks } from '.';

export default function useHasDelegated(address: string | undefined, refresh?: boolean): BN | null | undefined {
const api = useApi(address);
const formatted = useFormatted(address);
const chain = useChain(address);
export default function useHasDelegated (address: string | undefined, refresh?: boolean): BN | null | undefined {
const { api, chain, formatted } = useInfo(address);
const { tracks } = useTracks(address);

const [hasDelegated, setHasDelegated] = useState<BN | null>();
const [fetchedFor, setFetchedFor] = useState<string | undefined>(undefined);

useEffect(() => {
if (refresh) {
setHasDelegated(undefined);
setFetchedFor(undefined);
}

if (!api || !formatted || !tracks || !tracks?.length || !api?.query?.convictionVoting) {
if (!api || !formatted || !tracks?.length || !api?.query?.['convictionVoting'] || fetchedFor === address) {
return;
}

if (chain?.genesisHash && api && api.genesisHash.toString() !== chain.genesisHash) {
return;
}

const params: [string, BN][] = tracks.map((t) => [String(formatted), t[0]]);
const fetchDelegationData = async (
api: ApiPromise,
formatted: string,
tracks: [BN, unknown][]
): Promise<void> => {
try {
setFetchedFor(address);

// eslint-disable-next-line no-void
void api.query.convictionVoting.votingFor.multi(params).then((votingFor: PalletConvictionVotingVoteVoting[]) => {
let maxDelegated = BN_ZERO;
const params: [string, BN][] = tracks.map((t) => [formatted, t[0]]);
const votingFor: PalletConvictionVotingVoteVoting[] = await api.query['convictionVoting']['votingFor'].multi(params);

votingFor?.filter((v) => v.isDelegating).forEach((v) => {
if (v.asDelegating.balance.gt(maxDelegated)) {
maxDelegated = v.asDelegating.balance;
}
});
const maxDelegated = votingFor
.filter((v) => v.isDelegating)
.reduce((max, v) => {
const balance = v.asDelegating.balance;

maxDelegated.isZero() ? setHasDelegated(null) : setHasDelegated(maxDelegated);
});
}, [api, chain?.genesisHash, formatted, tracks, refresh]);
return balance.gt(max) ? balance : max;
}, BN_ZERO);

setHasDelegated(maxDelegated.isZero() ? null : maxDelegated);
} catch (error) {
console.error('Error fetching delegation data:', error);
setFetchedFor(undefined);
}
};

fetchDelegationData(api, formatted, tracks).catch(console.error);
}, [api, chain?.genesisHash, formatted, tracks, refresh, fetchedFor, address]);

return hasDelegated;
}
54 changes: 54 additions & 0 deletions packages/extension-polkagate/src/hooks/useLockedInReferenda.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2019-2024 @polkadot/extension-polkagate authors & contributors
// SPDX-License-Identifier: Apache-2.0

import type { BN } from '@polkadot/util';

import { useMemo } from 'react';

import { BN_MAX_INTEGER } from '@polkadot/util';

import { useAccountLocks, useCurrentBlockNumber, useHasDelegated, useTimeToUnlock } from '.';

interface Lock {
classId: BN;
endBlock: BN;
locked: string;
refId: BN | 'N/A';
total: BN;
}

interface OutputType {
classToUnlock: Lock[] | undefined;
delegatedBalance: BN | null | undefined;
hasDescription: boolean;
isDisable: boolean;
lockedInRef: BN | undefined
timeToUnlock: string | null | undefined;
totalLocked: BN | null | undefined;
unlockableAmount: BN | undefined;
}

export default function useLockedInReferenda (address: string | undefined, refreshNeeded: boolean | undefined): OutputType {
const delegatedBalance = useHasDelegated(address, refreshNeeded);
const referendaLocks = useAccountLocks(address, 'referenda', 'convictionVoting', false, refreshNeeded);
const currentBlock = useCurrentBlockNumber(address);
const { lockedInRef, timeToUnlock, totalLocked, unlockableAmount } = useTimeToUnlock(address, delegatedBalance, referendaLocks, refreshNeeded);

const classToUnlock = currentBlock ? referendaLocks?.filter((ref) => ref.endBlock.ltn(currentBlock) && ref.classId.lt(BN_MAX_INTEGER)) : undefined;
const isDisable = useMemo(() => !unlockableAmount || unlockableAmount.isZero() || !classToUnlock || !totalLocked, [classToUnlock, totalLocked, unlockableAmount]);

const hasDescription = useMemo(() =>
Boolean((unlockableAmount && !unlockableAmount.isZero()) || (delegatedBalance && !delegatedBalance.isZero()) || timeToUnlock)
, [delegatedBalance, timeToUnlock, unlockableAmount]);

return {
classToUnlock,
delegatedBalance,
hasDescription,
isDisable,
lockedInRef,
timeToUnlock,
totalLocked,
unlockableAmount
};
}
35 changes: 24 additions & 11 deletions packages/extension-polkagate/src/hooks/useTimeToUnlock.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import { BN_MAX_INTEGER, BN_ZERO } from '@polkadot/util';

import blockToDate from '../popup/crowdloans/partials/blockToDate';
import { type Lock } from './useAccountLocks';
import { useCurrentBlockNumber, useHasDelegated, useInfo, useTranslation } from '.';
import { useCurrentBlockNumber, useInfo, useTranslation } from '.';

export default function useTimeToUnlock (address: string | undefined, referendaLocks: Lock[] | null | undefined, refresh?: boolean) {
export default function useTimeToUnlock (address: string | undefined, delegatedBalance: BN | null | undefined, referendaLocks: Lock[] | null | undefined, refresh?: boolean) {
const { t } = useTranslation();
const { api, chain, formatted } = useInfo(address);
const delegatedBalance = useHasDelegated(address, refresh);
const currentBlock = useCurrentBlockNumber(address);

const [unlockableAmount, setUnlockableAmount] = useState<BN>();
Expand All @@ -31,6 +30,15 @@ export default function useTimeToUnlock (address: string | undefined, referendaL
return maybeFound ? maybeFound.total : BN_ZERO;
}, []);

// Reset states when address changes
useEffect(() => {
setUnlockableAmount(undefined);
setLockedInReferenda(undefined);
setTotalLocked(undefined);
setTimeToUnlock(undefined);
setMiscRefLock(undefined);
}, [address]);

useEffect(() => {
if (refresh) {
setLockedInReferenda(undefined); // TODO: needs double check
Expand All @@ -43,13 +51,17 @@ export default function useTimeToUnlock (address: string | undefined, referendaL
useEffect(() => {
if (referendaLocks === null) {
setLockedInReferenda(BN_ZERO);
setUnlockableAmount(BN_ZERO);
setTotalLocked(BN_ZERO);
setTimeToUnlock(null);

return;
}

if (!referendaLocks || !currentBlock) {
setLockedInReferenda(undefined);
setUnlockableAmount(undefined);
setTotalLocked(undefined);
setTimeToUnlock(undefined);

return;
Expand Down Expand Up @@ -116,16 +128,17 @@ export default function useTimeToUnlock (address: string | undefined, referendaL
return setMiscRefLock(undefined);
}

// eslint-disable-next-line no-void
void api.query['balances']['locks'](formatted).then((locks) => {
const _locks = locks as unknown as PalletBalancesBalanceLock[];
api.query['balances']['locks'](formatted)
.then((locks) => {
const _locks = locks as unknown as PalletBalancesBalanceLock[];

if (_locks?.length) {
const foundRefLock = _locks.find((l) => l.id.toHuman() === 'pyconvot');
if (_locks?.length) {
const foundRefLock = _locks.find((l) => l.id.toHuman() === 'pyconvot');

setMiscRefLock(foundRefLock?.amount);
}
});
setMiscRefLock(foundRefLock?.amount);
}
})
.catch(console.error);
}, [api, chain?.genesisHash, formatted, refresh]);

useEffect(() => {
Expand Down
Loading

0 comments on commit 7d55deb

Please sign in to comment.