Skip to content

Commit 727db1e

Browse files
committed
add withdraw
1 parent 3090f93 commit 727db1e

File tree

12 files changed

+332
-72
lines changed

12 files changed

+332
-72
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { AlertIcon } from '@status-im/icons/20'
2+
import { ButtonLink } from '@status-im/status-network/components'
3+
4+
const EmergencyBar = () => {
5+
return (
6+
<div className="h-10 bg-danger-50 p-2" data-theme="dark">
7+
<div className="flex items-center justify-center gap-2">
8+
<p className="text-15 font-semibold text-white-100">
9+
Contracts have been compromised.
10+
</p>
11+
<ButtonLink
12+
variant="secondary"
13+
size="24"
14+
iconBefore={<AlertIcon />}
15+
href="/stake"
16+
>
17+
Withdraw funds
18+
</ButtonLink>
19+
</div>
20+
</div>
21+
)
22+
}
23+
24+
export { EmergencyBar }

apps/hub/src/app/_components/hub-layout.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import { useState } from 'react'
33

44
import { Divider, Footer } from '@status-im/status-network/components'
5+
import { useReadContract } from 'wagmi'
56

7+
import { STAKING_MANAGER } from '../_constants/address'
8+
import { EmergencyBar } from './emergency-bar'
69
import { Sidebar } from './sidebar'
710
import { TopBar } from './top-bar'
811

@@ -12,6 +15,14 @@ interface HubLayoutProps {
1215

1316
export function HubLayout({ children }: HubLayoutProps) {
1417
const [sidebarOpen, setSidebarOpen] = useState(false)
18+
const { data: emergencyModeEnabled } = useReadContract({
19+
address: STAKING_MANAGER.address,
20+
abi: STAKING_MANAGER.abi,
21+
functionName: 'emergencyModeEnabled',
22+
query: {
23+
refetchInterval: 30000,
24+
},
25+
})
1526

1627
return (
1728
<div className="relative isolate z-10 min-h-screen w-full bg-neutral-100 px-1 pb-1">
@@ -20,6 +31,7 @@ export function HubLayout({ children }: HubLayoutProps) {
2031

2132
{/* Main Content Area */}
2233
<div className="relative w-full overflow-hidden rounded-20 bg-white-100">
34+
{Boolean(emergencyModeEnabled) && <EmergencyBar />}
2335
<div className="mx-auto flex h-[calc(100vh-64px-123px)] w-full max-w-[1504px] flex-row overflow-hidden lg:h-[calc(100vh-64px-50px)]">
2436
{/* Sidebar */}
2537
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />

apps/hub/src/app/_components/vaults/modals/withdraw-vault-modal.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { InfoIcon } from '@status-im/icons/12'
77
import { Button } from '@status-im/status-network/components'
88
import { useAccount } from 'wagmi'
99

10-
import { useVaultWithdraw } from '~hooks/useVaultWithdraw'
10+
import { useVaultEmergencyExit } from '~hooks/useVaultEmergencyExit'
1111

1212
import { BaseVaultModal } from './base-vault-modal'
1313

@@ -16,6 +16,7 @@ import type { Address } from 'viem'
1616
interface WithdrawVaultModalProps {
1717
onClose: () => void
1818
vaultAddress: Address
19+
amountWei: bigint
1920
open?: boolean
2021
onOpenChange?: (open: boolean) => void
2122
children?: React.ReactNode
@@ -25,31 +26,30 @@ interface WithdrawVaultModalProps {
2526
* Modal for emergency withdrawal from vault
2627
*/
2728
export function WithdrawVaultModal(props: WithdrawVaultModalProps) {
28-
const { onClose, vaultAddress, open, onOpenChange, children } = props
29+
const { onClose, vaultAddress, amountWei, open, onOpenChange, children } =
30+
props
2931

3032
const { address } = useAccount()
31-
const { mutate: withdraw } = useVaultWithdraw()
33+
const { mutate: emergencyExit } = useVaultEmergencyExit()
3234

3335
const handleVaultWithdrawal = useCallback(() => {
34-
const amountWei = 1000000000000000000n
35-
3636
if (!address) {
3737
console.error('No address found - wallet not connected')
3838
return
3939
}
4040

4141
try {
42-
withdraw({
42+
emergencyExit({
4343
amountWei,
4444
vaultAddress,
4545
onSigned: () => {
4646
onClose()
4747
},
4848
})
4949
} catch (error) {
50-
console.error('Error calling withdraw:', error)
50+
console.error('Error calling emergencyExit:', error)
5151
}
52-
}, [address, onClose, vaultAddress, withdraw])
52+
}, [address, amountWei, onClose, vaultAddress, emergencyExit])
5353

5454
return (
5555
<BaseVaultModal

apps/hub/src/app/_components/vaults/table-columns.tsx

Lines changed: 41 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,16 @@ interface TableColumnsProps {
2222
}
2323

2424
// Calculate total staked across all vaults
25-
const calculateTotalStaked = (vaults: StakingVault[]): bigint => {
25+
const calculateTotalStaked = (
26+
vaults: StakingVault[],
27+
emergencyMode: boolean
28+
): bigint => {
2629
return vaults.reduce(
27-
(acc, vault) => acc + (vault.data?.stakedBalance || 0n),
30+
(acc, vault) =>
31+
acc +
32+
(emergencyMode
33+
? vault.data?.depositedBalance || 0n
34+
: vault.data?.stakedBalance || 0n),
2835
BigInt(0)
2936
)
3037
}
@@ -68,7 +75,10 @@ export const createVaultTableColumns = ({
6875
isConnected,
6976
}: TableColumnsProps) => {
7077
// Calculate totals and current time once per column creation
71-
const totalStaked = calculateTotalStaked(vaults)
78+
const totalStaked = calculateTotalStaked(
79+
vaults,
80+
Boolean(emergencyModeEnabled)
81+
)
7282
const totalKarma = calculateTotalKarma(vaults)
7383
const currentTimestamp = getCurrentTimestamp()
7484
const columnHelper = createColumnHelper<StakingVault>()
@@ -109,12 +119,15 @@ export const createVaultTableColumns = ({
109119
},
110120
}),
111121
columnHelper.accessor('data.stakedBalance', {
112-
header: 'Staked',
122+
header: emergencyModeEnabled ? 'Vault balance' : 'Staked',
113123
cell: ({ row }) => {
124+
const balance = emergencyModeEnabled
125+
? row.original.data?.depositedBalance
126+
: row.original.data?.stakedBalance
114127
return (
115128
<div className="flex items-center gap-1">
116129
<span className="text-[13px] font-medium text-neutral-100">
117-
{formatSNT(row.original.data?.stakedBalance || 0n)}
130+
{formatSNT(balance || 0n)}
118131
<span className="ml-0.5 text-neutral-50">SNT</span>
119132
</span>
120133
</div>
@@ -265,33 +278,31 @@ export const createVaultTableColumns = ({
265278

266279
return (
267280
<div className="flex items-end justify-end gap-2 lg:gap-4">
281+
{Boolean(emergencyModeEnabled) && (
282+
<WithdrawVaultModal
283+
open={isWithdrawModalOpen}
284+
onOpenChange={open =>
285+
setOpenModalVaultId(open ? withdrawModalId : null)
286+
}
287+
onClose={() => setOpenModalVaultId(null)}
288+
vaultAddress={row.original.address}
289+
amountWei={row.original.data?.depositedBalance || 0n}
290+
>
291+
<Button
292+
variant="danger"
293+
size="24"
294+
iconBefore={<AlertIcon />}
295+
disabled={
296+
!row.original.data?.depositedBalance ||
297+
row.original.data.depositedBalance === 0n
298+
}
299+
>
300+
Withdraw funds
301+
</Button>
302+
</WithdrawVaultModal>
303+
)}
268304
{isLocked ? (
269305
<div className="flex items-center gap-2">
270-
{!emergencyModeEnabled && (
271-
<WithdrawVaultModal
272-
open={isWithdrawModalOpen}
273-
onOpenChange={open =>
274-
setOpenModalVaultId(open ? withdrawModalId : null)
275-
}
276-
onClose={() => setOpenModalVaultId(null)}
277-
vaultAddress={row.original.address}
278-
>
279-
<Button
280-
variant="danger"
281-
size="32"
282-
disabled={!isConnected}
283-
className="min-w-fit bg-danger-50 text-[13px] text-white-100 hover:bg-danger-60"
284-
>
285-
<AlertIcon className="shrink-0" />
286-
<span className="hidden whitespace-nowrap xl:inline">
287-
Withdraw funds
288-
</span>
289-
<span className="whitespace-nowrap xl:hidden">
290-
Withdraw
291-
</span>
292-
</Button>
293-
</WithdrawVaultModal>
294-
)}
295306
<LockVaultModal
296307
open={isLockModalOpen}
297308
onOpenChange={open =>

apps/hub/src/app/_components/vaults/vaults-table.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ export function VaultsTable() {
131131
abi: STAKING_MANAGER.abi,
132132
functionName: 'emergencyModeEnabled',
133133
query: {
134-
staleTime: 60_000, // Consider data fresh for 1 minute
134+
refetchInterval: 30000,
135135
},
136136
})
137137

apps/hub/src/app/_constants/address.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {
88
import type { Abi, Address } from 'viem'
99

1010
export const STAKING_MANAGER = {
11-
address: '0x5cDf1646E4c1D21eE94DED1DA8da3Ca450dc96D1' as Address,
11+
address: '0x07301236DDAD37dCA93690e7a7049Bc13F55158E' as Address,
1212
abi: stakingManagerAbi as Abi,
1313
} as const
1414

1515
export const VAULT_FACTORY = {
16-
address: '0xddDcd43a0B0dA865decf3e4Ae71FbBE3e2DfFF14' as Address,
16+
address: '0x489427Fad204FF494Cd8BE860D4af76b4Ce9F717' as Address,
1717
abi: vaultFactoryAbi as Abi,
1818
} as const
1919

apps/hub/src/app/_hooks/useStakingVaults.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useAccount, useChainId, useConfig } from 'wagmi'
44
import { readContract, readContracts } from 'wagmi/actions'
55

66
import { vaultAbi } from '~constants/contracts'
7-
import { CACHE_CONFIG, STAKING_MANAGER } from '~constants/index'
7+
import { CACHE_CONFIG, SNT_TOKEN, STAKING_MANAGER } from '~constants/index'
88

99
// ============================================================================
1010
// Types
@@ -29,6 +29,8 @@ export interface StakingVaultData {
2929
lockUntil: bigint
3030
/** Total rewards accrued and available for claiming */
3131
rewardsAccrued: bigint
32+
/** Actual token balance in the vault (from balanceOf) - reliable in emergency mode */
33+
depositedBalance: bigint
3234
}
3335

3436
/**
@@ -112,32 +114,47 @@ async function fetchVaultData(
112114
functionName: 'lockUntil',
113115
args: [],
114116
},
117+
{
118+
chainId,
119+
address: SNT_TOKEN.address,
120+
abi: SNT_TOKEN.abi,
121+
functionName: 'balanceOf',
122+
args: [vaultAddress],
123+
},
115124
],
116125
})
117126

118-
// Check if both contract calls succeeded
119-
const [vaultResult, lockUntilResult] = results
127+
// Check if all contract calls succeeded
128+
const [vaultResult, lockUntilResult, depositedBalanceResult] = results
120129

121130
if (
122131
vaultResult.status !== 'success' ||
123-
lockUntilResult.status !== 'success'
132+
lockUntilResult.status !== 'success' ||
133+
depositedBalanceResult.status !== 'success'
124134
) {
125135
console.error(
126136
`Failed to fetch vault data for ${vaultAddress}:`,
127137
vaultResult.status !== 'success'
128138
? vaultResult.error
129-
: lockUntilResult.error
139+
: lockUntilResult.status !== 'success'
140+
? lockUntilResult.error
141+
: depositedBalanceResult.error
130142
)
131143
return null
132144
}
133145

134146
// Extract the actual data from successful results
135-
const vaultData = vaultResult.result as Omit<StakingVaultData, 'lockUntil'>
147+
const vaultData = vaultResult.result as Omit<
148+
StakingVaultData,
149+
'lockUntil' | 'depositedBalance'
150+
>
136151
const lockUntil = lockUntilResult.result as bigint
152+
const depositedBalance = depositedBalanceResult.result as bigint
137153

138154
return {
139155
...vaultData,
140156
lockUntil,
157+
depositedBalance,
141158
}
142159
} catch (error) {
143160
// Log error for debugging but don't throw - allows partial results

0 commit comments

Comments
 (0)