Skip to content

Commit

Permalink
Merge pull request #25 from session-foundation/fix/deregistered_liqui…
Browse files Browse the repository at this point in the history
…dation

fix: add liquidation states to unlocked deregistered nodes
  • Loading branch information
Aerilym authored Feb 4, 2025
2 parents eb61da4 + 81d0cfa commit 6d0d454
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 57 deletions.
8 changes: 5 additions & 3 deletions apps/staking/app/mystakes/modules/useTotalStaked.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ export function useTotalStaked(addressOverride?: Address) {
const { address: connectedAddress } = useWallet();
const address = addressOverride ?? connectedAddress;

const { stakes, contracts, refetch, status, enabled } = useStakes(addressOverride);
const { stakes, contracts, refetch, status, enabled, currentContractIds } =
useStakes(addressOverride);

const totalStakedAmount = useMemo(() => {
if (!address) return formatSENTBigInt(0n);

const stakedStakes = stakes.filter((stake) => {
const eventState = parseStakeEventState(stake);
return !(
eventState === STAKE_EVENT_STATE.EXITED || eventState === STAKE_EVENT_STATE.LIQUIDATED
return (
!(eventState === STAKE_EVENT_STATE.EXITED || eventState === STAKE_EVENT_STATE.LIQUIDATED) &&
currentContractIds?.has(stake.contract_id)
);
});

Expand Down
112 changes: 60 additions & 52 deletions apps/staking/components/StakedNodeCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {
} from '@/components/StakedNode/state';
import { CopyToClipboardButton } from '@session/ui/components/CopyToClipboardButton';
import { NodeExitButtonDialog } from '@/components/StakedNode/NodeExitButtonDialog';
import { useStakes } from '@/hooks/useStakes';

/**
* Checks if a given stake is ready to exit the smart contract.
Expand Down Expand Up @@ -68,16 +69,13 @@ export const useIsReadyToExitByDeregistrationUnlock = (
deregistrationHeight?: number | null,
blockHeight?: number
) => {
const { chainId } = useWallet();
return !!(
state === STAKE_STATE.DEREGISTERED &&
eventState !== STAKE_EVENT_STATE.EXITED &&
eventState !== STAKE_EVENT_STATE.LIQUIDATED &&
deregistrationHeight &&
blockHeight &&
deregistrationHeight +
msInBlocks(SESSION_NODE_TIME(chainId).DEREGISTRATION_LOCKED_STAKE_SECONDS * 1000) <=
blockHeight
deregistrationHeight <= blockHeight
);
};

Expand Down Expand Up @@ -136,11 +134,21 @@ const NodeNotification = forwardRef<HTMLSpanElement, NodeNotificationProps>(
)
);

export const NodeOperatorIndicator = forwardRef<HTMLDivElement, HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
type NodeOperatorIndicatorProps = HTMLAttributes<HTMLDivElement> & {
isOperatorConnectedWallet?: boolean;
};

export const NodeOperatorIndicator = forwardRef<HTMLDivElement, NodeOperatorIndicatorProps>(
({ className, isOperatorConnectedWallet, ...props }, ref) => {
const dictionary = useTranslations('nodeCard.staked');
return (
<Tooltip tooltipContent={dictionary('operatorTooltip')}>
<Tooltip
tooltipContent={
isOperatorConnectedWallet
? dictionary('operatorTooltip')
: dictionary('operatorTooltipOther')
}
>
<div
ref={ref}
className={cn(
Expand Down Expand Up @@ -168,10 +176,12 @@ const isDateSoonOrPast = (date: Date | null): boolean =>
const ReadyForExitNotification = ({
date,
timeString,
isDeregistered,
className,
}: {
date: Date | null;
timeString: string | null;
isDeregistered?: boolean;
className?: string;
}) => {
const dictionary = useTranslations('nodeCard.staked');
Expand All @@ -187,7 +197,11 @@ const ReadyForExitNotification = ({
return (
<Tooltip
tooltipContent={dictionary.rich(
isLiquidationSoon ? 'exitTimerDescriptionNow' : 'exitTimerDescription',
isDeregistered
? 'liquidationDescription'
: isLiquidationSoon
? 'exitTimerDescriptionNow'
: 'exitTimerDescription',
{
relativeTime,
link: externalLink(URL.NODE_LIQUIDATION_LEARN_MORE),
Expand All @@ -196,8 +210,11 @@ const ReadyForExitNotification = ({
>
<NodeNotification level={isLiquidationSoon ? 'error' : 'warning'} className={className}>
{isLiquidationSoon
? dictionary('exitTimerNotificationNow')
: dictionary('exitTimerNotification', { relativeTime })}
? dictionary(isDeregistered ? 'liquidationNotification' : 'exitTimerNotificationNow')
: dictionary(
isDeregistered ? 'deregistrationTimerDescription' : 'exitTimerNotification',
{ relativeTime }
)}
</NodeNotification>
</Tooltip>
);
Expand All @@ -207,9 +224,11 @@ const ExitUnlockTimerNotification = ({
date,
timeString,
className,
isDeregistered,
}: {
date: Date | null;
timeString: string | null;
isDeregistered?: boolean;
className?: string;
}) => {
const dictionary = useTranslations('nodeCard.staked');
Expand All @@ -225,15 +244,23 @@ const ExitUnlockTimerNotification = ({

return (
<Tooltip
tooltipContent={dictionary('exitUnlockTimerDescription', {
relativeTime,
date: date ? formatDate(date, { dateStyle: 'full', timeStyle: 'short' }) : notFoundString,
})}
tooltipContent={dictionary(
isDeregistered ? 'deregisteredTimerDescription' : 'exitUnlockTimerDescription',
{
relativeTime,
date: date ? formatDate(date, { dateStyle: 'full', timeStyle: 'short' }) : notFoundString,
}
)}
>
<NodeNotification level="warning" className={className}>
{relativeTime
? dictionary('exitUnlockTimerNotification', { relativeTime })
: dictionary('exitUnlockTimerProcessing')}
? dictionary(
isDeregistered ? 'deregisteredTimerNotification' : 'exitUnlockTimerNotification',
{
relativeTime,
}
)
: dictionary(isDeregistered ? 'deregisteredProcessing' : 'exitUnlockTimerProcessing')}
</NodeNotification>
</Tooltip>
);
Expand Down Expand Up @@ -290,6 +317,7 @@ type NodeSummaryProps = {
liquidationTime: string | null;
showAllTimers?: boolean;
isOperator?: boolean;
isInContractIdList?: boolean;
};

const NodeSummary = ({
Expand All @@ -304,6 +332,7 @@ const NodeSummary = ({
deregistrationUnlockTime,
liquidationDate,
liquidationTime,
isInContractIdList,
}: NodeSummaryProps) => {
const eventState = parseStakeEventState(node);
const isExited =
Expand All @@ -327,13 +356,18 @@ const NodeSummary = ({
return (
<>
{contributors}
{!isExited ? (
{!isExited && isInContractIdList ? (
isReadyToUnlockByDeregistration ? (
<ReadyForExitNotification date={liquidationDate} timeString={liquidationTime} />
<ReadyForExitNotification
timeString={liquidationTime}
date={liquidationDate}
isDeregistered
/>
) : (
<ExitUnlockTimerNotification
date={deregistrationUnlockDate}
timeString={deregistrationUnlockTime}
date={deregistrationUnlockDate}
isDeregistered
/>
)
) : null}
Expand All @@ -345,7 +379,7 @@ const NodeSummary = ({
return (
<>
{contributors}
{isExited ? null : isReadyToExitByUnlock(
{isExited || !isInContractIdList ? null : isReadyToExitByUnlock(
state,
eventState,
node.requested_unlock_height,
Expand Down Expand Up @@ -450,6 +484,9 @@ const StakedNodeCard = forwardRef<

const address = targetWalletAddress ?? connectedAddress;

const { currentContractIds } = useStakes(address);
const isInContractIdList = currentContractIds?.has(stake.contract_id);

const {
operator_fee: fee,
operator_address: operatorAddress,
Expand Down Expand Up @@ -508,6 +545,7 @@ const StakedNodeCard = forwardRef<
node={stake}
state={state}
blockHeight={blockHeight}
isInContractIdList={isInContractIdList}
deregistrationDate={deregistrationDate}
deregistrationTime={deregistrationTime}
deregistrationUnlockDate={deregistrationUnlockDate}
Expand Down Expand Up @@ -636,8 +674,6 @@ const StakedNodeCard = forwardRef<
stake={stake}
state={state}
blockHeight={blockHeight}
deregistrationUnlockDate={deregistrationUnlockDate}
deregistrationUnlockTime={deregistrationUnlockTime}
requestedUnlockDate={requestedUnlockDate}
requestedUnlockTime={requestedUnlockTime}
notFoundString={notFoundString}
Expand All @@ -656,56 +692,28 @@ function StakeNodeCardButton({
blockHeight,
notFoundString,
requestedUnlockDate,
deregistrationUnlockDate,
requestedUnlockTime,
deregistrationUnlockTime,
}: {
stake: Stake;
state: STAKE_STATE;
blockHeight: number;
deregistrationUnlockTime?: string | null;
deregistrationUnlockDate?: Date | null;
requestedUnlockDate?: Date | null;
requestedUnlockTime?: string | null;
notFoundString?: string;
}) {
const dictionary = useTranslations('nodeCard.staked');

const eventState = parseStakeEventState(stake);
const isReadyToExitByDeregistrationUnlock = useIsReadyToExitByDeregistrationUnlock(
state,
eventState,
stake.deregistration_height,
blockHeight
);

if (
state === STAKE_STATE.EXITED ||
eventState === STAKE_EVENT_STATE.EXITED ||
eventState === STAKE_EVENT_STATE.LIQUIDATED
eventState === STAKE_EVENT_STATE.LIQUIDATED ||
state === STAKE_STATE.DEREGISTERED
) {
return null;
}

if (state === STAKE_STATE.DEREGISTERED) {
if (isReadyToExitByDeregistrationUnlock) {
return <NodeExitButtonDialog node={stake} />;
}

return (
<Tooltip
tooltipContent={dictionary('exit.disabledButtonTooltipContent', {
relativeTime: deregistrationUnlockTime ?? notFoundString,
date: deregistrationUnlockDate
? formatDate(deregistrationUnlockDate, { dateStyle: 'full', timeStyle: 'short' })
: notFoundString,
})}
>
<NodeExitButton disabled />
</Tooltip>
);
}

if (isReadyToExitByUnlock(state, eventState, stake.requested_unlock_height, blockHeight)) {
return <NodeExitButtonDialog node={stake} />;
}
Expand Down
6 changes: 5 additions & 1 deletion apps/staking/hooks/useStakes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,12 +146,13 @@ export function useStakes(overrideAddress?: Address) {
contracts,
hiddenContractsWithStakes,
addedBlsKeys,
currentContractIds,
runningStakesBlsKeys,
blockHeight,
networkTime,
network,
] = useMemo(() => {
if (!address || !data) return [[], [], [], null, null, null];
if (!address || !data) return [[], [], [], null, null, null, null];
const stakesArr = 'stakes' in data && Array.isArray(data.stakes) ? data.stakes : [];
const contractsArr = 'contracts' in data && Array.isArray(data.contracts) ? data.contracts : [];

Expand All @@ -169,6 +170,7 @@ export function useStakes(overrideAddress?: Address) {
: {};

const addedBlsKeysSet = new Set(Object.keys(blsKeysObject));
const currentContractIdsSet = new Set(Object.values(blsKeysObject));
const runningStakesBlsKeysSet = new Set(
stakesArr
.filter((stake) => parseStakeEventState(stake) === STAKE_EVENT_STATE.ACTIVE)
Expand Down Expand Up @@ -228,6 +230,7 @@ export function useStakes(overrideAddress?: Address) {
filteredContracts,
hiddenContracts,
addedBlsKeysSet,
currentContractIdsSet,
runningStakesBlsKeysSet,
blockHeight,
networkTime,
Expand All @@ -240,6 +243,7 @@ export function useStakes(overrideAddress?: Address) {
contracts,
hiddenContractsWithStakes,
addedBlsKeys,
currentContractIds,
runningStakesBlsKeys,
network,
blockHeight,
Expand Down
4 changes: 4 additions & 0 deletions apps/staking/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,7 @@
"staked": {
"operator": "Operator",
"operatorTooltip": "You are the operator of this node",
"operatorTooltipOther": "This is the operator of this node",
"labelExpand": "Expand",
"ariaExpand": "Expand node details",
"labelCollapse": "Collapse",
Expand All @@ -740,6 +741,9 @@
"exitTimerDescriptionNow": "Node is ready for exit. If you don't exit now, you may be subject to a small penalty. <link>Learn more</link>",
"deregistrationTimerNotification": "Deregistration {relativeTime}",
"deregistrationTimerDescription": "Time left until the node is deregistered and funds are locked for {lockedStakeTime}. Node will be deregistered {relativeTime} ({date})",
"deregisteredProcessing": "Deregistration is being processed",
"deregisteredTimerNotification": "Stake will unlock {relativeTime}",
"deregisteredTimerDescription": "Time left until the stake is unlocked and the node is available for liquidation. ({date})",
"liquidationNotification": "Awaiting liquidation",
"liquidationDescription": "The node is inactive and waiting to be removed by the network.",
"finalize": {
Expand Down
2 changes: 1 addition & 1 deletion packages/staking-api-js/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ export interface GetStakesResponse {
network: NetworkInfo;
contracts: Array<ContributorContractInfo>;
stakes: Array<Stake>;
added_bls_keys: Record<number, string>;
added_bls_keys: Record<string, number>;
}

/** /store */
Expand Down

0 comments on commit 6d0d454

Please sign in to comment.