Skip to content

Commit 1b54ec0

Browse files
authored
Fix: count "Unlocks In" down to the start of the unlock, not its end (#121)
The Token Vault card counted down to globalLock.endTime, the end of the linear unlock. For the employee and investor vaults, whose unlock starts on Nov 13 and then runs for two years, that showed "751 days" when tokens actually start unlocking in about three weeks: it was reporting the time until the last token unlocks, i.e. the vesting duration plus the wait. Count down to the cliff instead. It is the first moment any tokens become claimable, and after it the lock releases tokens every block, so "Available now" is the right reading from then on. The cliff is never before startTime and equals it when a schedule has no cliff. This matches what the vesting chart already shows as "Unlock Start Time". ATPCard carried its own copy of the same end-of-vesting countdown; it now uses the shared helper. The tooltip, which described the old behaviour, is reworded. NCATP vaults keep their separate withdrawal-timestamp countdown, and MATP vaults still show "Milestone still locked" once the time lock has passed. Fixes A-2456
1 parent 551a06f commit 1b54ec0

3 files changed

Lines changed: 12 additions & 26 deletions

File tree

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

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import styles from "./ATPCard.module.css";
44
import { formatAddress } from "../../utils/formatAddress";
55
import { IconCopy, IconCheck } from "@tabler/icons-react";
66
import type { ATPData } from "../../hooks/atp/atpTypes";
7+
import { getTimeToClaimForATP } from "../../utils/atpFormatters";
78

89
interface CopyButtonProps {
910
address: string;
@@ -112,27 +113,6 @@ export default function ATPCard({
112113
return "0";
113114
};
114115

115-
// Calculate time left to claim for each ATP
116-
const getTimeToClaimForATP = (atp: ATPData) => {
117-
// Use globalLock data if available
118-
if (atp.globalLock?.endTime) {
119-
const now = Math.floor(Date.now() / 1000);
120-
const endTime = Number(atp.globalLock.endTime);
121-
const timeLeft = endTime - now;
122-
123-
if (timeLeft <= 0) {
124-
return "Available now";
125-
}
126-
127-
const days = Math.floor(timeLeft / (24 * 60 * 60));
128-
129-
return `${days} days`;
130-
}
131-
132-
// Fallback for ATPs without lock data
133-
return "Available now";
134-
};
135-
136116
return (
137117
<div key={`${atp.atpAddress}-${index}`} className={styles.atpCard}>
138118
<div className={styles.atpCardHeader}>

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ export const ATPStakingCard = ({
639639
? withdrawalTimestamp === undefined
640640
? "Set your Staker version to view unlock date. It is recommended that every Token Vault upgrades to the latest version."
641641
: "Unlock requires staking tokens first. Then, your Token Vault unlock countdown will be displayed."
642-
: "Time remaining until the next batch of tokens completes vesting and becomes claimable from your Token Vault."
642+
: "Time remaining until tokens in your Token Vault start unlocking. From then on, tokens unlock linearly with every Ethereum block."
643643
}
644644
size="sm"
645645
maxWidth="max-w-xs"

‎staking-dashboard/src/utils/atpFormatters.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,24 @@ export function getStakeableAmount(atp: ATPData): bigint {
4444
}
4545

4646
/**
47-
* Calculate time until claimable
47+
* Calculate time until tokens start unlocking
48+
*
49+
* Counts down to the cliff, the first moment any tokens become claimable, not to endTime: past the
50+
* cliff the lock releases tokens linearly every block, so counting to endTime reports the whole
51+
* vesting duration (e.g. "751 days" for a vault whose unlock starts in three weeks).
52+
* The cliff is never before startTime, and equals it when the schedule has no cliff.
53+
*
4854
* @param atp - ATP data with globalLock info
4955
* @param blockTimestamp - Optional blockchain timestamp to use instead of Date.now()
5056
* This ensures consistency with anvil time warps during testing
5157
*/
5258
export function getTimeToClaimForATP(atp: ATPData, blockTimestamp?: bigint): string {
53-
if (atp.globalLock?.endTime) {
59+
if (atp.globalLock) {
5460
const now = blockTimestamp
5561
? Number(blockTimestamp)
5662
: Math.floor(Date.now() / 1000)
57-
const endTime = Number(atp.globalLock.endTime)
58-
const timeLeft = endTime - now
63+
const unlockStart = Number(atp.globalLock.cliff || atp.globalLock.startTime)
64+
const timeLeft = unlockStart - now
5965

6066
if (timeLeft <= 0) {
6167
return "Available now"

0 commit comments

Comments
 (0)