From f52b39842b935ec9fc2cba60adea75b638cf4d1a Mon Sep 17 00:00:00 2001 From: Robert Brada Date: Fri, 24 Apr 2026 18:01:12 +0200 Subject: [PATCH 1/2] fix: warn users about pending governance withdrawals blocking vault withdrawal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds an amber warning under the Withdraw button on each Token Vault card when the vault has unfinalized governance withdrawals. Tokens initiated for withdrawal from Governance stay with the Governance contract until finalize() is called, so they cannot be withdrawn to the wallet via the vault — the warning links directly to the external governance dashboard. Also clarifies the Available to Withdraw tooltip to explain that tokens held in Governance are not counted. --- .../ATPStakingCard/ATPStakingCard.tsx | 43 ++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx b/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx index 669205d2f..6ad6593f7 100644 --- a/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx +++ b/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx @@ -15,6 +15,8 @@ import { useRollupData } from "@/hooks/rollup/useRollupData"; import { useStakerBalance } from "@/hooks/staker/useStakerBalance"; import { useNCStakerStatus } from "@/hooks/staker/useNCStakerStatus"; import { useBlockTimestamp } from "@/hooks/useBlockTimestamp"; +import { usePendingWithdrawals } from "@/hooks/governance"; +import { EXTERNAL_GOVERNANCE_FRONTENDS } from "@/config/externalGovernance"; import { useTransactionCart } from "@/contexts/TransactionCartContext"; import { ATPStakingStepsWithTransaction, @@ -189,6 +191,20 @@ export const ATPStakingCard = ({ // Get cached block timestamp for withdrawal eligibility check (refreshes every 60s) const { blockTimestamp } = useBlockTimestamp(); + // Pending governance withdrawals for this vault. Tokens initiated for + // withdrawal from Governance stay with the Governance contract until + // finalize() is called — they are NOT in the vault, so a vault withdrawal + // (vault → wallet) cannot release them. Users often miss this step. + const { pendingWithdrawals: governancePendingWithdrawals } = + usePendingWithdrawals({ userAddress: data.atpAddress as Address }); + const pendingGovernanceAmount = governancePendingWithdrawals.reduce( + (sum, w) => sum + w.amount, + 0n + ); + const governanceDashboardUrl = EXTERNAL_GOVERNANCE_FRONTENDS.find( + (f) => f.url + )?.url; + const globalLockTimeDisplay = getTimeToClaimForATP(data, blockTimestamp); const { activationThreshold } = useRollupData(); @@ -705,7 +721,7 @@ export const ATPStakingCard = ({ Available to Withdraw @@ -766,6 +782,31 @@ export const ATPStakingCard = ({ )} + {!isFullyWithdrawn && pendingGovernanceAmount > 0n && ( + +
+ +{formatTokenAmount(pendingGovernanceAmount, decimals, symbol)}{" "} + in governance + {governanceDashboardUrl && ( + <> + {" — "} + + finalize in dashboard + + + )} +
+
+ )} {/* Withdrawn */} From 54a0ef326474ab17a54e8cdef3c9adc818fdc660 Mon Sep 17 00:00:00 2001 From: Robert Brada Date: Mon, 27 Apr 2026 10:39:32 +0200 Subject: [PATCH 2/2] fix: open governance modal instead of hardcoded dashboard link --- .../ATPStakingCard/ATPStakingCard.tsx | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx b/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx index 6ad6593f7..f02b094ab 100644 --- a/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx +++ b/staking-dashboard/src/components/ATPStakingCard/ATPStakingCard.tsx @@ -16,7 +16,7 @@ import { useStakerBalance } from "@/hooks/staker/useStakerBalance"; import { useNCStakerStatus } from "@/hooks/staker/useNCStakerStatus"; import { useBlockTimestamp } from "@/hooks/useBlockTimestamp"; import { usePendingWithdrawals } from "@/hooks/governance"; -import { EXTERNAL_GOVERNANCE_FRONTENDS } from "@/config/externalGovernance"; +import { ExternalGovernanceModal } from "@/components/ExternalGovernanceModal"; import { useTransactionCart } from "@/contexts/TransactionCartContext"; import { ATPStakingStepsWithTransaction, @@ -201,9 +201,7 @@ export const ATPStakingCard = ({ (sum, w) => sum + w.amount, 0n ); - const governanceDashboardUrl = EXTERNAL_GOVERNANCE_FRONTENDS.find( - (f) => f.url - )?.url; + const [isGovernanceModalOpen, setIsGovernanceModalOpen] = useState(false); const globalLockTimeDisplay = getTimeToClaimForATP(data, blockTimestamp); const { activationThreshold } = useRollupData(); @@ -790,20 +788,14 @@ export const ATPStakingCard = ({ >
+{formatTokenAmount(pendingGovernanceAmount, decimals, symbol)}{" "} - in governance - {governanceDashboardUrl && ( - <> - {" — "} - - finalize in dashboard - - - )} + in governance{" — "} +
)} @@ -838,6 +830,11 @@ export const ATPStakingCard = ({ onRefetchAllowance={refetchAllowance} onUpgradeSuccess={refetchNCStatus} /> + + setIsGovernanceModalOpen(false)} + /> ); };