Skip to content
Open
Show file tree
Hide file tree
Changes from all 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