Skip to content

Commit d4244f6

Browse files
authored
Fix: label the vault countdown "Unlocks Start In", then show "% Unlocked" (#122)
* Fix: label the vault countdown "Unlocks Start In" until the unlock begins Since #121 the countdown runs to the start of the unlock, after which tokens release linearly every block. Under the old "Unlocks In" label, "21 days" read as the whole vault unlocking in 21 days. Show "Unlocks Start In" while that countdown is running. Once the unlock has started the value is "Available now" (or "Milestone still locked" for a milestone vault), and the label goes back to "Unlocks In": "Unlocks Start In: Available now" would read oddly. NCATP vaults count down to a full unlock at their withdrawal timestamp, so they keep "Unlocks In". * Fix: show "% Unlocked" once a vault's unlock has started After the unlock starts, the card showed "Unlocks In: Available now", which says nothing about how far through the schedule the vault is. Show "% Unlocked" with the share of the allocation the schedule has released instead, computed by useVestingCalculation so it matches the vesting chart in the details modal. The share is rounded down to 0.01% so no vault reads 100% before its last token unlocks. A milestone vault whose milestone is not achieved follows the same schedule but can release nothing, so it shows the same percentage with "Milestone still locked" underneath, and the tooltip says the unlocked tokens can't be withdrawn yet. Previously "Milestone still locked" replaced the value. Vaults without a lock schedule, and NCATP vaults, keep "Unlocks In" and their existing values.
1 parent 1b54ec0 commit d4244f6

1 file changed

Lines changed: 52 additions & 4 deletions

File tree

‎staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx‎

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
useATPClaim,
1010
useUpgradeStaker,
1111
useUpdateStakerOperator,
12+
useVestingCalculation,
1213
} from "@/hooks/atp";
1314
import { useTokenVaultSetupStatus } from "@/hooks/atp/useTokenVaultSetupStatus";
1415
import { useRollupData } from "@/hooks/rollup/useRollupData";
@@ -35,6 +36,9 @@ import { useMilestoneStatus, MilestoneStatus } from "@/hooks/atpRegistry/useMile
3536
import { MilestoneStatusBadge } from "@/components/MilestoneStatusBadge";
3637
import { ERC20Abi } from "@/contracts/abis/ERC20";
3738

39+
// useVestingCalculation needs a lock on every render; vaults without one never show its result.
40+
const NO_GLOBAL_LOCK = { startTime: 0n, cliff: 0n, endTime: 0n, allocation: 0n };
41+
3842
interface ATPStakingCardProps {
3943
data: ATPData;
4044
onStakeClick: (atp: ATPData) => void;
@@ -220,6 +224,11 @@ export const ATPStakingCard = ({
220224
const [isGovernanceModalOpen, setIsGovernanceModalOpen] = useState(false);
221225

222226
const globalLockTimeDisplay = getTimeToClaimForATP(data, blockTimestamp);
227+
// Same schedule maths as the vesting chart, so the card and the chart agree on how much has unlocked
228+
const { currentVestedAmount } = useVestingCalculation(
229+
data.globalLock ?? NO_GLOBAL_LOCK,
230+
blockTimestamp
231+
);
223232
const { activationThreshold } = useRollupData();
224233

225234
const {
@@ -346,6 +355,34 @@ export const ATPStakingCard = ({
346355
? getMATPlockDisplay()
347356
: globalLockTimeDisplay;
348357

358+
// Lock-scheduled vaults count down to the *start* of the unlock, after which tokens release every
359+
// block. "Unlocks In: 21 days" reads as a full unlock in 21 days, so before the start the countdown
360+
// is labelled for what it is, and after it the card shows how much of the allocation has unlocked.
361+
// A milestone vault follows the same schedule but can't release anything until its milestone is
362+
// achieved, so it also says so. NCATPs count down to a full unlock and keep "Unlocks In".
363+
const hasUnlockSchedule = !isNCATP && !!data.globalLock;
364+
const hasUnlockStarted =
365+
hasUnlockSchedule && globalLockTimeDisplay === "Available now";
366+
const isMilestoneStillLocked =
367+
isMATP && hasUnlockStarted && milestoneStatus !== MilestoneStatus.Succeeded;
368+
369+
// Rounded down to 0.01% so a vault never shows 100% before its last token has unlocked
370+
const allocationForPercent = data.globalLock?.allocation ?? 0n;
371+
const unlockedBasisPoints =
372+
allocationForPercent > 0n
373+
? (currentVestedAmount * 10_000n) / allocationForPercent
374+
: 0n;
375+
const unlockedPercentDisplay = `${(Number(unlockedBasisPoints) / 100).toLocaleString(undefined, { maximumFractionDigits: 2 })}%`;
376+
377+
const unlockLabel = !hasUnlockSchedule
378+
? "Unlocks In"
379+
: hasUnlockStarted
380+
? "% Unlocked"
381+
: "Unlocks Start In";
382+
const unlockValue = hasUnlockStarted
383+
? unlockedPercentDisplay
384+
: timeToClaimDisplay;
385+
349386
// Calculate remaining allocation after withdrawals and slashing
350387
// Total Funds = allocation - totalWithdrawn - totalSlashed
351388
// Slashed tokens are burned/confiscated and should not appear as remaining funds
@@ -631,15 +668,19 @@ export const ATPStakingCard = ({
631668
</div>
632669
<div className="flex items-center gap-1">
633670
<div className="text-xs text-parchment/60 uppercase tracking-wide font-oracle-standard">
634-
Unlocks In
671+
{unlockLabel}
635672
</div>
636673
<TooltipIcon
637674
content={
638675
isNCATP
639676
? withdrawalTimestamp === undefined
640677
? "Set your Staker version to view unlock date. It is recommended that every Token Vault upgrades to the latest version."
641678
: "Unlock requires staking tokens first. Then, your Token Vault unlock countdown will be displayed."
642-
: "Time remaining until tokens in your Token Vault start unlocking. From then on, tokens unlock linearly with every Ethereum block."
679+
: hasUnlockStarted
680+
? isMilestoneStillLocked
681+
? "Share of this Token Vault's allocation unlocked by its schedule. Unlocked tokens can't be withdrawn until the vault's milestone is achieved."
682+
: "Share of this Token Vault's allocation unlocked so far. Tokens unlock linearly with every Ethereum block."
683+
: "Time remaining until tokens in your Token Vault start unlocking. From then on, tokens unlock linearly with every Ethereum block."
643684
}
644685
size="sm"
645686
maxWidth="max-w-xs"
@@ -650,8 +691,15 @@ export const ATPStakingCard = ({
650691
<div className="font-mono text-lg font-bold text-parchment">
651692
{formatTokenAmountFull(remainingAllocation, decimals, symbol)}
652693
</div>
653-
<div className="font-mono text-lg font-bold text-parchment">
654-
{isFullyWithdrawn ? "—" : timeToClaimDisplay}
694+
<div className="flex flex-col items-end">
695+
<div className="font-mono text-lg font-bold text-parchment">
696+
{isFullyWithdrawn ? "—" : unlockValue}
697+
</div>
698+
{isMilestoneStillLocked && !isFullyWithdrawn && (
699+
<div className="text-xs text-parchment/60 font-oracle-standard">
700+
Milestone still locked
701+
</div>
702+
)}
655703
</div>
656704
</div>
657705

0 commit comments

Comments
 (0)