diff --git a/apps/hub/.env b/apps/hub/.env index 5d2858140..a1d8b7ebf 100644 --- a/apps/hub/.env +++ b/apps/hub/.env @@ -16,4 +16,5 @@ # – https://vercel.com/docs/cli/env#exporting-development-environment-variables -NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="123" +NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID="7ab664eee6a734b14327cdf4678a3431" +NEXT_PUBLIC_API_URL=http://localhost:3001/api/v1 \ No newline at end of file diff --git a/apps/hub/package.json b/apps/hub/package.json index 509c86da3..1154132ff 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -38,7 +38,7 @@ "rehype-slug": "^6.0.0", "siwe": "^2.3.2", "ts-pattern": "^5.6.2", - "viem": "^2.21.1", + "viem": "^2.29.1", "wagmi": "2.15.2", "zod": "^3.24.1" }, diff --git a/apps/hub/src/app/_components/connect-button.tsx b/apps/hub/src/app/_components/connect-button.tsx index e8cb92ab6..dfa6a5c04 100644 --- a/apps/hub/src/app/_components/connect-button.tsx +++ b/apps/hub/src/app/_components/connect-button.tsx @@ -2,45 +2,39 @@ import { Button, ShortenAddress } from '@status-im/status-network/components' import { ConnectKitButton } from 'connectkit' -import { useAccount } from 'wagmi' import type { ComponentProps } from 'react' type Props = { size?: ComponentProps['size'] label?: string - shortLabel?: string + className?: string + /** If true, shows the label instead of the shortened address when connected */ + alwaysShowLabel?: boolean } const ConnectButton = (props: Props) => { const { size = '32', label = 'Connect wallet', - shortLabel = 'Connect', + className, + alwaysShowLabel = false, } = props - const { address, isConnected } = useAccount() - return ( - {({ show }) => { + {({ show, isConnected, address }) => { return ( ) diff --git a/apps/hub/src/app/_components/vaults/table-columns.tsx b/apps/hub/src/app/_components/vaults/table-columns.tsx index 5de6eee72..a4242ad5a 100644 --- a/apps/hub/src/app/_components/vaults/table-columns.tsx +++ b/apps/hub/src/app/_components/vaults/table-columns.tsx @@ -8,11 +8,7 @@ import { SNT_TOKEN } from '~constants/index' import { type StakingVault } from '~hooks/useStakingVaults' import { shortenAddress } from '~utils/address' import { formatSNT } from '~utils/currency' -import { - calculateDaysUntilUnlock, - calculateVaultBoost, - isVaultLocked, -} from '~utils/vault' +import { calculateDaysUntilUnlock, isVaultLocked } from '~utils/vault' import { LockVaultModal } from './modals/lock-vault-modal' import { WithdrawVaultModal } from './modals/withdraw-vault-modal' @@ -25,6 +21,45 @@ interface TableColumnsProps { isConnected: boolean } +// Calculate total staked across all vaults +const calculateTotalStaked = (vaults: StakingVault[]): bigint => { + return vaults.reduce( + (acc, vault) => acc + (vault.data?.stakedBalance || 0n), + BigInt(0) + ) +} + +// Calculate total karma across all vaults +const calculateTotalKarma = (vaults: StakingVault[]): bigint => { + return vaults.reduce( + (acc, vault) => acc + (vault.data?.rewardsAccrued || 0n), + BigInt(0) + ) +} + +// Cache current time to avoid calling Date.now() on every cell render +const getCurrentTimestamp = (): bigint => { + return BigInt(Math.floor(Date.now() / 1000)) +} + +// Validation function for lock time - extracted to avoid recreating on every render +const validateLockTime = (_: string, days: string): string | null => { + const totalDays = parseInt(days || '0') + // TODO: read this from the contract + return totalDays > 1460 ? 'Maximum lock time is 4 years' : null +} + +// Modal action configurations - static, never change +const EXTEND_LOCK_ACTIONS = [ + { label: 'Cancel' }, + { label: 'Extend lock' }, +] as const + +const LOCK_VAULT_ACTIONS = [{ label: "Don't lock" }, { label: 'Lock' }] as const + +const LOCK_INFO_MESSAGE = + 'Boost the rate at which you receive Karma. The longer you lock your vault, the higher your boost, and the faster you accumulate Karma. You can add more SNT at any time, but withdrawing your SNT is only possible once the vault unlocks.' as const + export const createVaultTableColumns = ({ vaults = [], openModalVaultId, @@ -32,14 +67,10 @@ export const createVaultTableColumns = ({ emergencyModeEnabled, isConnected, }: TableColumnsProps) => { - const totalStaked = vaults.reduce( - (acc, vault) => acc + (vault.data?.stakedBalance || 0n), - BigInt(0) - ) - const totalKarma = vaults.reduce( - (acc, vault) => acc + (vault.data?.rewardsAccrued || 0n), - BigInt(0) - ) + // Calculate totals and current time once per column creation + const totalStaked = calculateTotalStaked(vaults) + const totalKarma = calculateTotalKarma(vaults) + const currentTimestamp = getCurrentTimestamp() const columnHelper = createColumnHelper() return [ @@ -48,14 +79,14 @@ export const createVaultTableColumns = ({ header: 'Vault', cell: ({ row }) => { return ( - + #{Number(row.index) + 1} ) }, footer: () => { return ( - Total + Total ) }, meta: { @@ -67,7 +98,7 @@ export const createVaultTableColumns = ({ header: 'Address', cell: ({ row }) => { return ( - + {shortenAddress(row.original.address)} ) @@ -82,7 +113,7 @@ export const createVaultTableColumns = ({ cell: ({ row }) => { return (
- + {formatSNT(row.original.data?.stakedBalance || 0n)} SNT @@ -91,7 +122,7 @@ export const createVaultTableColumns = ({ }, footer: () => { return ( - + {formatSNT(totalStaked)} SNT @@ -116,7 +147,7 @@ export const createVaultTableColumns = ({ return (
- + {daysUntilUnlock} d @@ -140,13 +171,22 @@ export const createVaultTableColumns = ({ ? (maxMP - mpAccrued) / stakedBalance : undefined + // Calculate boost directly instead of recalculating for all vaults + // Boost = (mpAccrued / stakedBalance) + 1 (base multiplier) + const currentBoost = + stakedBalance > 0n + ? Number(formatUnits(mpAccrued, SNT_TOKEN.decimals)) / + Number(formatUnits(stakedBalance, SNT_TOKEN.decimals)) + + 1 + : 1 + return (
- - x{calculateVaultBoost(vaults, row.original.address)} + + x{currentBoost.toFixed(2)} {potentialBoost && ( - + x{formatSNT(formatUnits(potentialBoost, SNT_TOKEN.decimals))} if locked @@ -165,7 +205,7 @@ export const createVaultTableColumns = ({ const karma = Number(row.original.data?.rewardsAccrued) / 1e18 return (
- + {formatSNT(karma)} KARMA @@ -174,7 +214,7 @@ export const createVaultTableColumns = ({ }, footer: () => { return ( - + {formatSNT(totalKarma)} KARMA @@ -198,7 +238,7 @@ export const createVaultTableColumns = ({ ) : ( )} - + {isLocked ? 'Locked' : 'Open'}
@@ -218,8 +258,9 @@ export const createVaultTableColumns = ({ const isWithdrawModalOpen = openModalVaultId === withdrawModalId const isLockModalOpen = openModalVaultId === lockModalId + // Use cached timestamp instead of calling Date.now() on every render const isLocked = row.original?.data?.lockUntil - ? row.original.data.lockUntil > BigInt(Math.floor(Date.now() / 1000)) + ? row.original.data.lockUntil > currentTimestamp : false return ( @@ -239,7 +280,7 @@ export const createVaultTableColumns = ({ variant="danger" size="32" disabled={!isConnected} - className="min-w-fit bg-danger-50 text-13 text-white-100 hover:bg-danger-60" + className="min-w-fit bg-danger-50 text-[13px] text-white-100 hover:bg-danger-60" > @@ -261,22 +302,15 @@ export const createVaultTableColumns = ({ initialYears="2" initialDays="732" description="Extending lock time increasing Karma boost" - actions={[ - { - label: 'Cancel', - }, - { - label: 'Extend lock', - }, - ]} + actions={[...EXTEND_LOCK_ACTIONS]} onClose={() => setOpenModalVaultId(null)} - infoMessage="Boost the rate at which you receive Karma. The longer you lock your vault, the higher your boost, and the faster you accumulate Karma. You can add more SNT at any time, but withdrawing your SNT is only possible once the vault unlocks." + infoMessage={LOCK_INFO_MESSAGE} >