Skip to content

Commit c5de825

Browse files
authored
add withdraw (#813)
* add withdraw * Add changeset for patch updates * address feedback * address feedback * change contracts address back to production * add use client to emergencyMode hook * test fix build * test fix build
1 parent f25ffd0 commit c5de825

File tree

14 files changed

+421
-132
lines changed

14 files changed

+421
-132
lines changed

.changeset/hungry-bananas-join.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@status-im/status-network": patch
3+
"hub": patch
4+
---
5+
6+
withdraw
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@
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/index'
8+
import { CACHE_CONFIG } from '~constants/staking'
9+
10+
import { EmergencyBar } from './emergency-bar'
611
import { Sidebar } from './sidebar'
712
import { TopBar } from './top-bar'
813

@@ -12,6 +17,14 @@ interface HubLayoutProps {
1217

1318
export function HubLayout({ children }: HubLayoutProps) {
1419
const [sidebarOpen, setSidebarOpen] = useState(false)
20+
const { data: emergencyModeEnabled } = useReadContract({
21+
address: STAKING_MANAGER.address,
22+
abi: STAKING_MANAGER.abi,
23+
functionName: 'emergencyModeEnabled',
24+
query: {
25+
refetchInterval: CACHE_CONFIG.EMERGENCY_MODE_REFETCH_INTERVAL,
26+
},
27+
})
1528

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

2134
{/* Main Content Area */}
2235
<div className="relative w-full overflow-hidden rounded-20 bg-white-100">
36+
{Boolean(emergencyModeEnabled) && <EmergencyBar />}
2337
<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)]">
2438
{/* Sidebar */}
2539
<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: 89 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { Button } from '@status-im/status-network/components'
55
import { createColumnHelper } from '@tanstack/react-table'
66
import { formatUnits } from 'viem'
77

8-
import { SNT_TOKEN } from '~constants/index'
8+
import { EXTEND_LOCK_PERIOD, SNT_TOKEN } from '~constants/index'
99
import { type StakingVault } from '~hooks/useStakingVaults'
1010
import { shortenAddress } from '~utils/address'
1111
import { formatSNT } from '~utils/currency'
@@ -29,9 +29,16 @@ interface TableColumnsProps {
2929
}
3030

3131
// Calculate total staked across all vaults
32-
const calculateTotalStaked = (vaults: StakingVault[]): bigint => {
32+
const calculateTotalStaked = (
33+
vaults: StakingVault[],
34+
emergencyMode: boolean
35+
): bigint => {
3336
return vaults.reduce(
34-
(acc, vault) => acc + (vault.data?.stakedBalance || 0n),
37+
(acc, vault) =>
38+
acc +
39+
(emergencyMode
40+
? vault.data?.depositedBalance || 0n
41+
: vault.data?.stakedBalance || 0n),
3542
BigInt(0)
3643
)
3744
}
@@ -90,7 +97,10 @@ export const createVaultTableColumns = ({
9097
chainId,
9198
}: TableColumnsProps) => {
9299
// Calculate totals and current time once per column creation
93-
const totalStaked = calculateTotalStaked(vaults)
100+
const totalStaked = calculateTotalStaked(
101+
vaults,
102+
Boolean(emergencyModeEnabled)
103+
)
94104
const totalKarma = calculateTotalKarma(vaults)
95105
const currentTimestamp = getCurrentTimestamp()
96106
const columnHelper = createColumnHelper<StakingVault>()
@@ -131,12 +141,15 @@ export const createVaultTableColumns = ({
131141
},
132142
}),
133143
columnHelper.accessor('data.stakedBalance', {
134-
header: 'Staked',
144+
header: emergencyModeEnabled ? 'Vault balance' : 'Staked',
135145
cell: ({ row }) => {
146+
const balance = emergencyModeEnabled
147+
? row.original.data?.depositedBalance
148+
: row.original.data?.stakedBalance
136149
return (
137150
<div className="flex items-center gap-1">
138151
<span className="text-13 font-medium text-neutral-100">
139-
{formatSNT(row.original.data?.stakedBalance || 0n)}
152+
{formatSNT(balance || 0n)}
140153
<span className="ml-0.5 text-neutral-50">SNT</span>
141154
</span>
142155
</div>
@@ -290,86 +303,83 @@ export const createVaultTableColumns = ({
290303
: false
291304

292305
return (
293-
<div className="flex items-end justify-end gap-2">
294-
{isLocked ? (
295-
<div className="flex items-center gap-2">
296-
{!emergencyModeEnabled && (
297-
<WithdrawVaultModal
298-
open={isWithdrawModalOpen}
299-
onOpenChange={open =>
300-
setOpenModalVaultId(open ? withdrawModalId : null)
301-
}
302-
onClose={() => setOpenModalVaultId(null)}
303-
vaultAddress={row.original.address}
304-
>
305-
<Button
306-
variant="danger"
307-
size="32"
308-
disabled={!isConnected}
309-
className="min-w-fit bg-danger-50 text-13 text-white-100 hover:bg-danger-60"
310-
>
311-
<AlertIcon className="shrink-0" />
312-
<span className="hidden whitespace-nowrap xl:inline">
313-
Withdraw funds
314-
</span>
315-
<span className="whitespace-nowrap xl:hidden">
316-
Withdraw
317-
</span>
318-
</Button>
319-
</WithdrawVaultModal>
320-
)}
321-
<LockVaultModal
322-
open={isLockModalOpen}
323-
onOpenChange={open =>
324-
setOpenModalVaultId(open ? lockModalId : null)
325-
}
326-
vaultAddress={row.original.address}
327-
title="Extend lock time"
328-
initialYears="2"
329-
initialDays="732"
330-
description="Extending lock time increasing Karma boost"
331-
actions={[...EXTEND_LOCK_ACTIONS]}
332-
onClose={() => setOpenModalVaultId(null)}
333-
infoMessage={LOCK_INFO_MESSAGE}
334-
>
335-
<Button
336-
variant="primary"
337-
size="32"
338-
disabled={!isConnected}
339-
className="min-w-fit text-13"
340-
>
341-
<TimeIcon className="shrink-0" />
342-
<span className="hidden whitespace-nowrap xl:inline">
343-
Extend lock time
344-
</span>
345-
<span className="whitespace-nowrap xl:hidden">Extend</span>
346-
</Button>
347-
</LockVaultModal>
348-
</div>
349-
) : (
350-
<LockVaultModal
351-
open={isLockModalOpen}
306+
<div className="flex items-end justify-end gap-2 lg:gap-4">
307+
{emergencyModeEnabled ? (
308+
<WithdrawVaultModal
309+
open={isWithdrawModalOpen}
352310
onOpenChange={open =>
353-
setOpenModalVaultId(open ? lockModalId : null)
311+
setOpenModalVaultId(open ? withdrawModalId : null)
354312
}
355-
vaultAddress={row.original.address}
356-
title="Do you want to lock the vault?"
357-
description="Lock this vault to receive more Karma"
358-
actions={[...LOCK_VAULT_ACTIONS]}
359313
onClose={() => setOpenModalVaultId(null)}
360-
infoMessage={LOCK_INFO_MESSAGE}
361-
onValidate={validateLockTime}
314+
vaultAddress={row.original.address}
315+
amountWei={row.original.data?.depositedBalance || 0n}
362316
>
363317
<Button
364-
variant="primary"
365-
size="32"
366-
disabled={!isConnected}
367-
className="min-w-fit text-13"
318+
variant="danger"
319+
size="24"
320+
iconBefore={<AlertIcon />}
321+
disabled={
322+
!row.original.data?.depositedBalance ||
323+
row.original.data.depositedBalance === 0n
324+
}
368325
>
369-
<LockedIcon fill="white" className="shrink-0" />
370-
<span className="whitespace-nowrap">Lock</span>
326+
Withdraw funds
371327
</Button>
372-
</LockVaultModal>
328+
</WithdrawVaultModal>
329+
) : (
330+
<>
331+
{isLocked ? (
332+
<div className="flex items-center gap-2">
333+
<LockVaultModal
334+
open={isLockModalOpen}
335+
onOpenChange={open =>
336+
setOpenModalVaultId(open ? lockModalId : null)
337+
}
338+
vaultAddress={row.original.address}
339+
title="Extend lock time"
340+
initialYears={EXTEND_LOCK_PERIOD.INITIAL_YEARS}
341+
initialDays={EXTEND_LOCK_PERIOD.INITIAL_DAYS}
342+
description="Extending lock time increasing Karma boost"
343+
actions={[...EXTEND_LOCK_ACTIONS]}
344+
onClose={() => setOpenModalVaultId(null)}
345+
infoMessage={LOCK_INFO_MESSAGE}
346+
>
347+
<Button
348+
variant="primary"
349+
size="24"
350+
disabled={!isConnected}
351+
>
352+
<TimeIcon className="shrink-0" />
353+
<span className="hidden whitespace-nowrap xl:inline">
354+
Extend lock time
355+
</span>
356+
<span className="whitespace-nowrap xl:hidden">
357+
Extend
358+
</span>
359+
</Button>
360+
</LockVaultModal>
361+
</div>
362+
) : (
363+
<LockVaultModal
364+
open={isLockModalOpen}
365+
onOpenChange={open =>
366+
setOpenModalVaultId(open ? lockModalId : null)
367+
}
368+
vaultAddress={row.original.address}
369+
title="Do you want to lock the vault?"
370+
description="Lock this vault to receive more Karma"
371+
actions={[...LOCK_VAULT_ACTIONS]}
372+
onClose={() => setOpenModalVaultId(null)}
373+
infoMessage={LOCK_INFO_MESSAGE}
374+
onValidate={validateLockTime}
375+
>
376+
<Button variant="primary" size="24" disabled={!isConnected}>
377+
<LockedIcon fill="white" className="shrink-0" />
378+
<span className="whitespace-nowrap">Lock</span>
379+
</Button>
380+
</LockVaultModal>
381+
)}
382+
</>
373383
)}
374384
<DropdownMenu.Root
375385
onOpenChange={open => setOpenDropdownId(open ? dropdownId : null)}

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

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import {
99
getCoreRowModel,
1010
useReactTable,
1111
} from '@tanstack/react-table'
12-
import { useAccount, useChainId, useReadContract } from 'wagmi'
12+
import { useAccount, useChainId } from 'wagmi'
1313

14-
import { STAKING_MANAGER } from '~constants/index'
1514
import { useCreateVault } from '~hooks/useCreateVault'
15+
import { useEmergencyModeEnabled } from '~hooks/useEmergencyModeEnabled'
1616
import { type StakingVault, useStakingVaults } from '~hooks/useStakingVaults'
1717

1818
import { createVaultTableColumns } from './table-columns'
@@ -127,15 +127,7 @@ export function VaultsTable() {
127127
const { isConnected } = useAccount()
128128
const chainId = useChainId()
129129
const { mutate: createVault } = useCreateVault()
130-
131-
const { data: emergencyModeEnabled } = useReadContract({
132-
address: STAKING_MANAGER.address,
133-
abi: STAKING_MANAGER.abi,
134-
functionName: 'emergencyModeEnabled',
135-
query: {
136-
staleTime: 60_000, // Consider data fresh for 1 minute
137-
},
138-
})
130+
const { data: emergencyModeEnabled } = useEmergencyModeEnabled()
139131

140132
// Stable callback reference prevents column recreation on every render
141133
const handleSetOpenModalVaultId = useCallback(
@@ -186,6 +178,7 @@ export function VaultsTable() {
186178
size="32"
187179
onClick={() => createVault()}
188180
className="w-full sm:w-auto"
181+
disabled={Boolean(emergencyModeEnabled)}
189182
>
190183
<AddCircleIcon />
191184
<span className="whitespace-nowrap">Add vault</span>

0 commit comments

Comments
 (0)