Single-page staking dashboard for the Hoodi network.
- Build the image:
docker build -t ssv-staking-app:local . - Run the app:
docker run --rm -p 3000:3000 ssv-staking-app:local - Open
http://localhost:3000
app/Next.js App Router pages, layouts, and API routes.components/UI and staking components.hooks/shared React hooks.lib/config, ABIs, wagmi setup, and staking logic.public/static assets and icons.
Enable Hidden Features:
In localstorage add ssv-feature-flags = true
SSV Staking Dashboard
├─ Top Bar
│ └─ Connect / Account
├─ Network Summary
│ ├─ Current APR
│ └─ Total SSV Staked
├─ Portfolio Summary
│ ├─ Available to Stake (SSV)
│ ├─ Staked Balance (cSSV)
│ └─ Claimable ETH
├─ Actions (Tabs)
│ ├─ Stake
│ ├─ Unstake
│ └─ Claim
└─ FAQ
Single-page staking dashboard on the Hoodi network. Users stake SSV to mint cSSV 1:1, request unstake with a cooldown, then withdraw, and claim ETH rewards. Network stats are visible without connecting; balances and actions unlock after connecting a wallet.
| User Personas and Stories SSV Token Holder / Incentivized Receiver As an SSV Holder, I want a single screen that shows available balance, staked balance, APR, and claimable rewards As an SSV Holder, I want simple flows for stake, unstake (with cooldown visibility), and claim As an SSV Holder, I want to see network-wide totals before connecting my wallet Crypto Native / Protocol Researcher As a researcher, I want to view the staking interface and network stats read-only without connecting a wallet |
|---|
Top bar with branding and wallet state. When disconnected, clicking the primary button opens the wallet connect modal.
Always visible (no wallet required).
- Current APR: computed from the change in
accEthPerShareover time and adjusted by ETH/SSV price ratio (see below). - Total SSV Staked: global total supply of staked SSV (cSSV supply).
- Includes a staking calculator icon with tooltip “Staking Calculator” that links to
https://ethaccrualtoken.com/.
APR Calculation
APR = ((ΔIndex / (1e18 × ΔTime)) × 31,536,000) × (Price_ETH / Price_SSV) × 100
ΔIndex= change inaccEthPerSharebetween the two latest samples.ΔTime= seconds between samples (unix timestamps).- Prices are from CoinGecko (ETH + SSV).
- Sampling is stored once per 24h via a cron job; UI reads the latest two samples.
- If delta ≤ 0, ΔTime ≤ 0, or no data available, APR shows
--. - APR endpoint is optional locally; when disabled or missing env, return
--.
Contract Reads
accEthPerShare()
totalStaked()
Off-chain Sources
CoinGecko simple price API (ethereum, ssv-network)
Visible once connected.
- Available to Stake: wallet SSV balance.
- Staked Balance: wallet cSSV balance.
- Claimable: ETH rewards available to claim.
- Numbers show “--” if data is unavailable; otherwise formatted with thousands separators.
- Input amount or click MAX to fill wallet SSV balance.
- Primary CTA “Stake”
- Validation:
- CTA "Connect Wallet" when disconnected.
- Disabled when amount is empty/zero.
- Errors shown for insufficient SSV balance.
- On stake submit:
- Open stake modal and auto-start approval if allowance is insufficient.
- Approval step shows spinner + “Waiting…”, then tx hash, then checkmark on confirm.
- After approval confirms, stake auto-starts.
- If approval isn’t required, stake step starts immediately.
- On error, step shows red icon with “Try Again”.
- On success: show success toast, clear tx state, and refresh balances/allowances.
- After stake completes:
- Show “Add cSSV to Metamask” button in the modal (wallet_watchAsset).
- Show a full-width “Close” button and a top-right “X”. These appear only after completion.
- Clicking outside the modal does not close it - IMPORTANT, REMEMBER COMPOSE??
- Expected result: cSSV minted 1:1 to the staked SSV amount; wallet SSV decreases by staked amount.
Contract Calls
approve(address spender, uint256 amount) // SSV token -> staking contract
stake(uint256 amount) // Staking contract
Contract Reads
decimals() // SSV token
decimals() // cSSV token
allowance(address owner, address spender)
Two-step process: request, then withdraw after cooldown.
Request Unstake
- Input amount (cSSV).
- If no amount, CTA disabled. If disconnected, CTA = "Connect Wallet"
- On submit:
- Open unstake modal; if cSSV allowance is insufficient, auto-approve then request.
- Burns cSSV immediately and records pending withdrawal with unlockTime = now + cooldownDuration.
- Refresh staked balance, pending data, claimable ETH, allowance, wallet balance.
- UI state after request:
- Pending card shows requested amount and status pill (“Requested” or “Withdrawable”).
- Countdown updates live until unlock; withdraw action is disabled until unlocked.
Withdraw Unlocked
- When unlockTime has passed, the action button becomes “Withdraw”.
- On click:
- Transfers SSV back to the wallet for the pending amount.
- Clears pending state.
- Open withdraw modal (single step) with waiting → tx hash → confirmed.
- Refresh balances and claimable stats.
Cooldown Rules
- Cooldown duration shown in days on stake tab and countdown on unstake tab.
- If current time < unlockTime: button disabled with countdown text.
- If unlockTime passed: button enabled “Withdraw”.
Contract Calls
requestUnstake(uint256 amount) // Staking contract
withdrawUnlocked() // Staking contract
Contract Reads
pendingUnstake(address user) // amount, unlockTime
cooldownDuration()
stakedBalanceOf(address user)
- Shows total claimable ETH in a summary card.
- CTA is enabled when connected; CTA "Connect Wallet" when disconnected.
- On click:
- Open claim modal (single step) with waiting → tx hash → confirmed.
- On success: show success toast and refresh claimable ETH, staked balance, pending data, and balances.
- On failure: show error toast and “Try Again”.
Contract Call
claimEthRewards() // Staking contract
Contract Reads
previewClaimableEth(address user)
stakingEthPoolBalance()
- Modal step states:
- Waiting: spinner + “Waiting…”
- Submitted: tx hash button opens explorer.
- Confirmed: green check.
- Error: red icon + “Try Again”.
- Toasts:
- Pending: “Transaction pending…”
- Success: “ confirmed”
- Failure: “ failed: ”
- After success: refetch balances (SSV, cSSV), allowance, claimable ETH, total staked, pending unstake.
- APR card: calculator icon shows tooltip “Staking Calculator” and opens external link.
- Tooltip dots: info tooltip on Current APR and Unstake Cooldown labels.
- Modals:
- Disable closing on backdrop click.
- Close controls (top-right “X” + full-width “Close”) appear only after all required steps confirm.
- Error state shows red icon + “Try Again”; close controls stay hidden.
- Unstake cooldown:
- Countdown label updates every second.
- Withdraw button text switches to “Withdraw in Xm Ys…” until unlocked.
- External tx hash buttons open the block explorer in a new tab.
- “Add cSSV to Metamask” button appears only after stake completes.
- Disconnected: all action CTAs show “Connect Wallet”
- Amount empty or zero: CTAs disabled.
- Insufficient balance: surface toast error.
- Insufficient allowance: handled in modal (auto-approval) with retry on error.
- Data unavailable: show “--” placeholders.
// ERC20 metadata + approvals
decimals() // SSV token
decimals() // cSSV token
allowance(address owner, address spender)
// Staking views
previewClaimableEth(address user)
stakedBalanceOf(address user)
pendingUnstake(address user) // returns amount, unlockTime
totalStaked()
cooldownDuration()
accEthPerShare()
stakingEthPoolBalance()
approve(address spender, uint256 amount) // SSV token -> staking contract
stake(uint256 amount) // Stake SSV, mint cSSV 1:1
requestUnstake(uint256 amount) // Burn cSSV, start cooldown
withdrawUnlocked() // Withdraw SSV after cooldown
claimEthRewards() // Claim ETH rewards
- On any confirmed write: refetch wallet balances (SSV), staked balance (cSSV), pending unstake, allowance, claimable ETH, total staked.
- APR is fetched once on entry to the site; show
--if unavailable.
- Expand/collapse per question.
- Defaults closed unless marked otherwise.