Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/hungry-bananas-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@status-im/status-network": patch
"hub": patch
---

withdraw
24 changes: 24 additions & 0 deletions apps/hub/src/app/_components/emergency-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AlertIcon } from '@status-im/icons/20'
import { ButtonLink } from '@status-im/status-network/components'

const EmergencyBar = () => {
return (
<div className="h-10 bg-danger-50 p-2" data-theme="dark">
<div className="flex items-center justify-center gap-2">
<p className="text-15 font-semibold text-white-100">
Contracts have been compromised.
</p>
<ButtonLink
variant="secondary"
size="24"
iconBefore={<AlertIcon />}
href="/stake"
>
Withdraw funds
</ButtonLink>
</div>
</div>
)
}

export { EmergencyBar }
12 changes: 12 additions & 0 deletions apps/hub/src/app/_components/hub-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
import { useState } from 'react'

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

import { STAKING_MANAGER } from '../_constants/address'
import { EmergencyBar } from './emergency-bar'
import { Sidebar } from './sidebar'
import { TopBar } from './top-bar'

Expand All @@ -12,6 +15,14 @@ interface HubLayoutProps {

export function HubLayout({ children }: HubLayoutProps) {
const [sidebarOpen, setSidebarOpen] = useState(false)
const { data: emergencyModeEnabled } = useReadContract({
address: STAKING_MANAGER.address,
abi: STAKING_MANAGER.abi,
functionName: 'emergencyModeEnabled',
query: {
refetchInterval: 30000,
},
})

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

{/* Main Content Area */}
<div className="relative w-full overflow-hidden rounded-20 bg-white-100">
{Boolean(emergencyModeEnabled) && <EmergencyBar />}
<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)]">
{/* Sidebar */}
<Sidebar isOpen={sidebarOpen} onClose={() => setSidebarOpen(false)} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { InfoIcon } from '@status-im/icons/12'
import { Button } from '@status-im/status-network/components'
import { useAccount } from 'wagmi'

import { useVaultWithdraw } from '~hooks/useVaultWithdraw'
import { useVaultEmergencyExit } from '~hooks/useVaultEmergencyExit'

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

Expand All @@ -16,6 +16,7 @@ import type { Address } from 'viem'
interface WithdrawVaultModalProps {
onClose: () => void
vaultAddress: Address
amountWei: bigint
open?: boolean
onOpenChange?: (open: boolean) => void
children?: React.ReactNode
Expand All @@ -25,31 +26,30 @@ interface WithdrawVaultModalProps {
* Modal for emergency withdrawal from vault
*/
export function WithdrawVaultModal(props: WithdrawVaultModalProps) {
const { onClose, vaultAddress, open, onOpenChange, children } = props
const { onClose, vaultAddress, amountWei, open, onOpenChange, children } =
props

const { address } = useAccount()
const { mutate: withdraw } = useVaultWithdraw()
const { mutate: emergencyExit } = useVaultEmergencyExit()

const handleVaultWithdrawal = useCallback(() => {
const amountWei = 1000000000000000000n

if (!address) {
console.error('No address found - wallet not connected')
return
}

try {
withdraw({
emergencyExit({
amountWei,
vaultAddress,
onSigned: () => {
onClose()
},
})
} catch (error) {
console.error('Error calling withdraw:', error)
console.error('Error calling emergencyExit:', error)
}
}, [address, onClose, vaultAddress, withdraw])
}, [address, amountWei, onClose, vaultAddress, emergencyExit])

return (
<BaseVaultModal
Expand Down
71 changes: 41 additions & 30 deletions apps/hub/src/app/_components/vaults/table-columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,16 @@ interface TableColumnsProps {
}

// Calculate total staked across all vaults
const calculateTotalStaked = (vaults: StakingVault[]): bigint => {
const calculateTotalStaked = (
vaults: StakingVault[],
emergencyMode: boolean
): bigint => {
return vaults.reduce(
(acc, vault) => acc + (vault.data?.stakedBalance || 0n),
(acc, vault) =>
acc +
(emergencyMode
? vault.data?.depositedBalance || 0n
: vault.data?.stakedBalance || 0n),
BigInt(0)
)
}
Expand Down Expand Up @@ -68,7 +75,10 @@ export const createVaultTableColumns = ({
isConnected,
}: TableColumnsProps) => {
// Calculate totals and current time once per column creation
const totalStaked = calculateTotalStaked(vaults)
const totalStaked = calculateTotalStaked(
vaults,
Boolean(emergencyModeEnabled)
)
const totalKarma = calculateTotalKarma(vaults)
const currentTimestamp = getCurrentTimestamp()
const columnHelper = createColumnHelper<StakingVault>()
Expand Down Expand Up @@ -109,12 +119,15 @@ export const createVaultTableColumns = ({
},
}),
columnHelper.accessor('data.stakedBalance', {
header: 'Staked',
header: emergencyModeEnabled ? 'Vault balance' : 'Staked',
cell: ({ row }) => {
const balance = emergencyModeEnabled
? row.original.data?.depositedBalance
: row.original.data?.stakedBalance
return (
<div className="flex items-center gap-1">
<span className="text-[13px] font-medium text-neutral-100">
{formatSNT(row.original.data?.stakedBalance || 0n)}
{formatSNT(balance || 0n)}
<span className="ml-0.5 text-neutral-50">SNT</span>
</span>
</div>
Expand Down Expand Up @@ -265,33 +278,31 @@ export const createVaultTableColumns = ({

return (
<div className="flex items-end justify-end gap-2 lg:gap-4">
{Boolean(emergencyModeEnabled) && (
<WithdrawVaultModal
open={isWithdrawModalOpen}
onOpenChange={open =>
setOpenModalVaultId(open ? withdrawModalId : null)
}
onClose={() => setOpenModalVaultId(null)}
vaultAddress={row.original.address}
amountWei={row.original.data?.depositedBalance || 0n}
>
<Button
variant="danger"
size="24"
iconBefore={<AlertIcon />}
disabled={
!row.original.data?.depositedBalance ||
row.original.data.depositedBalance === 0n
}
>
Withdraw funds
</Button>
</WithdrawVaultModal>
)}
{isLocked ? (
<div className="flex items-center gap-2">
{!emergencyModeEnabled && (
<WithdrawVaultModal
open={isWithdrawModalOpen}
onOpenChange={open =>
setOpenModalVaultId(open ? withdrawModalId : null)
}
onClose={() => setOpenModalVaultId(null)}
vaultAddress={row.original.address}
>
<Button
variant="danger"
size="32"
disabled={!isConnected}
className="min-w-fit bg-danger-50 text-[13px] text-white-100 hover:bg-danger-60"
>
<AlertIcon className="shrink-0" />
<span className="hidden whitespace-nowrap xl:inline">
Withdraw funds
</span>
<span className="whitespace-nowrap xl:hidden">
Withdraw
</span>
</Button>
</WithdrawVaultModal>
)}
<LockVaultModal
open={isLockModalOpen}
onOpenChange={open =>
Expand Down
2 changes: 1 addition & 1 deletion apps/hub/src/app/_components/vaults/vaults-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export function VaultsTable() {
abi: STAKING_MANAGER.abi,
functionName: 'emergencyModeEnabled',
query: {
staleTime: 60_000, // Consider data fresh for 1 minute
refetchInterval: 30000,
},
})

Expand Down
4 changes: 2 additions & 2 deletions apps/hub/src/app/_constants/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
import type { Abi, Address } from 'viem'

export const STAKING_MANAGER = {
address: '0x5cDf1646E4c1D21eE94DED1DA8da3Ca450dc96D1' as Address,
address: '0x07301236DDAD37dCA93690e7a7049Bc13F55158E' as Address,
abi: stakingManagerAbi as Abi,
} as const

export const VAULT_FACTORY = {
address: '0xddDcd43a0B0dA865decf3e4Ae71FbBE3e2DfFF14' as Address,
address: '0x489427Fad204FF494Cd8BE860D4af76b4Ce9F717' as Address,
abi: vaultFactoryAbi as Abi,
} as const

Expand Down
29 changes: 23 additions & 6 deletions apps/hub/src/app/_hooks/useStakingVaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useAccount, useChainId, useConfig } from 'wagmi'
import { readContract, readContracts } from 'wagmi/actions'

import { vaultAbi } from '~constants/contracts'
import { CACHE_CONFIG, STAKING_MANAGER } from '~constants/index'
import { CACHE_CONFIG, SNT_TOKEN, STAKING_MANAGER } from '~constants/index'

// ============================================================================
// Types
Expand All @@ -29,6 +29,8 @@ export interface StakingVaultData {
lockUntil: bigint
/** Total rewards accrued and available for claiming */
rewardsAccrued: bigint
/** Actual token balance in the vault (from balanceOf) - reliable in emergency mode */
depositedBalance: bigint
}

/**
Expand Down Expand Up @@ -112,32 +114,47 @@ async function fetchVaultData(
functionName: 'lockUntil',
args: [],
},
{
chainId,
address: SNT_TOKEN.address,
abi: SNT_TOKEN.abi,
functionName: 'balanceOf',
args: [vaultAddress],
},
],
})

// Check if both contract calls succeeded
const [vaultResult, lockUntilResult] = results
// Check if all contract calls succeeded
const [vaultResult, lockUntilResult, depositedBalanceResult] = results

if (
vaultResult.status !== 'success' ||
lockUntilResult.status !== 'success'
lockUntilResult.status !== 'success' ||
depositedBalanceResult.status !== 'success'
) {
console.error(
`Failed to fetch vault data for ${vaultAddress}:`,
vaultResult.status !== 'success'
? vaultResult.error
: lockUntilResult.error
: lockUntilResult.status !== 'success'
? lockUntilResult.error
: depositedBalanceResult.error
)
return null
}

// Extract the actual data from successful results
const vaultData = vaultResult.result as Omit<StakingVaultData, 'lockUntil'>
const vaultData = vaultResult.result as Omit<
StakingVaultData,
'lockUntil' | 'depositedBalance'
>
const lockUntil = lockUntilResult.result as bigint
const depositedBalance = depositedBalanceResult.result as bigint

return {
...vaultData,
lockUntil,
depositedBalance,
}
} catch (error) {
// Log error for debugging but don't throw - allows partial results
Expand Down
Loading
Loading