Skip to content

Claim dashboard

The /claim route is the holder’s cockpit. It reads every piece of your on-chain position — Tide-LP NFTs, accrued fees, vesting, and any lottery prize — and exposes every write the protocol supports: pokeFees, claim / claimMany, processOwed, withdrawVested, activatePrize / expirePrize, plus a buy/sell swap widget against the canonical Uniswap V4 pool.

Everything on this page is keyed to the connected wallet. Until you connect, the main column shows a connect prompt — your positions and fees live against your address, not in any server.

The dashboard is two columns:

ColumnContents
MainPosition summary · Lottery panel (conditional) · Vesting card · Fees-by-position list
SidebarGet TIDE swap widget · Launch fee gauge · Wallet readout

Three tiles read from useUserPosition(address) and usePendingFees(ids):

TileSourceMeaning
Your TIDEbalanceOf(you)Your ERC-20 TIDE balance.
Tide-LP heldnftBalanceOf(you)Live NFT count — one per whole TIDE held.
Accrued, unclaimedΣ pendingFees(id) over owned idsETH leg (accent) + TIDE leg, summed across every NFT you own.

“Accrued, unclaimed” is the sum of pendingFees(tokenId) for each owned token. That view returns each NFT’s (eth, tide) accrual since its last checkpoint and excludes anything not yet poked into the accumulators — so the figure can lag real swap fees until someone calls pokeFees(). Use Sync fees (below) to refresh it.


The sidebar Launch fee card renders the live FeeGauge from useProtocolStats(): currentFee, seeded, launchTime, and the two window lengths feeWindow1 / feeWindow2. It shows which tier of the degressive schedule is active right now and counts down to the next step:

TierPipsRateWindow
1250_00025%t < feeWindow1
2100_00010%feeWindow1 ≤ t < feeWindow2
Final50_0005%t ≥ feeWindow2

t is block.timestamp − launchTime. The window lengths are read live from the hook’s feeWindow1 / feeWindow2 immutables (Sepolia runs compressed test durations), but the three tier rates are fixed constants. If the chain is unreachable the card shows a Retry button. Full mechanics: Degressive launch fee.


The Vesting card pairs a VestingBar with the write buttons that drive your vesting tranche.

VestingBar is fed claimableNow, lockedOf, vestEndsAt, and vestingDuration. It splits your tranche into two legends — Claimable (claimableNow(you)) and Locked (lockedOf(you)) — and fills a progress meter from the linear unlock:

start = vestEndsAt - vestingDuration
pct = clamp(0, 100, (now - start) / vestingDuration * 100)

When something is still locked it shows an unlocks in countdown to vestEndsAt, ticking once per second. With nothing locked it reads fully unlocked; with no tranche at all it prompts you to claim fees to start one. See 72-hour linear vesting.

The card header carries a Sync fees button calling pokeFees() (permissionless, unguarded). It pulls accrued swap fees out of the V4 position into the per-share accumulators so your “Accrued, unclaimed” and per-position rows reflect reality.

The primary button calls withdrawVested(), paying out everything currently unlocked (claimableNow) as TIDE. It is disabled when claimableNow === 0. The button label shows the exact amount, e.g. Withdraw 9.88 TIDE.

A secondary Convert owed button appears only when you have raw owed fees (owedEth > 0 or owedTide > 0). Owed fees are accruals captured involuntarily — when an NFT you held moved or burned, the protocol harvested its pending fees into your pendingETH / pendingTIDE buckets (see PendingCredited). They are not yet vested.

processOwed() converts them: it zeroes your owed buckets, buys TIDE in-pool with the ETH leg (Buyback), and deposits tideOwed + bought into a fresh vesting tranche. There is no direct ETH payout anywhere — the ETH leg is always converted to TIDE. The button label itemizes what will convert, e.g. Convert owed (0.0021 ETH + 14.2 TIDE). A no-op (no owed fees) surfaces “You had no owed fees to convert.”


This card lists your earning NFTs and lets you claim them.

The header Claim all button calls claimMany(tokenIds) over every owned id. In one transaction it pokes fees, harvests each NFT to its current owner, then converts and vests your owed. It is disabled while another action is pending or when you own no NFTs.

Each NftFeeRow shows the token id (linked to the explorer), its pending ETH + TIDE, and a per-row Claim button calling claim(tokenId). The list is capped at the first 60 rows; a note clarifies that Claim all still processes every position beyond the cap.

A claim is not an ETH payout. For each harvested NFT the protocol credits the owner’s owed buckets (Claimed), then _process buys TIDE with the ETH leg in the pool and deposits the result into vesting (OwedProcessedVested):

  1. pokeFees() — pull fresh fees into the accumulators.
  2. Harvest each NFT’s accrual to its owner’s owed.
  3. Buy TIDE in-pool with your ETH owed (this is the in-pool buyback that creates buy pressure on every claim).
  4. Deposit tideOwed + bought into your vesting tranche.

The LotteryPanel renders only when you have a pending prize (prizeAmount > 0). It appears between the position summary and the vesting card. You win a prize when another holder sells before their vesting finishes and forfeits the locked remainder, and an on-chain draw routes that forfeit to you. (Forfeits are biased by prevrandao; see The forfeited-vesting lottery.)

The panel shows the prize amount and a countdown to prizeExpiresAt, and offers one of two actions depending on the window:

StateButtonCallEffect
Within windowActivate prizeactivatePrize()Pull payment (you pay gas); re-vests the prize over a fresh vesting window.
Window passed (expired or countdown ≤ 0)Send to TreasuryexpirePrize(you)Permissionless; routes the un-activated prize to the Treasury.

expirePrize is callable by anyone for any stale winner; the dashboard wires it to your own address. Prize status (amount, expiresAt, expired) comes from the hook’s prizeStatus(user) view.


The sidebar SwapWidget is a Buy/Sell interface against the canonical V4 pool, routed through the swap router (PoolSwapTest on Sepolia).

Native ETH in. The widget builds a zeroForOne: true swap with amountSpecified = -ethIn and value = ethIn:

swapRouter.swap(
POOL_KEY,
{ zeroForOne: true, amountSpecified: -ethIn, sqrtPriceLimitX96: limit },
{ takeClaims: false, settleUsingBurn: false },
"0x",
)

limit comes from sqrtLimitForSlippage(spot, slippage, true). Every whole TIDE you end up holding auto-mints one Tide-LP NFT (success toast: “Whole tokens auto-mint Tide-LP NFTs in your wallet.”).

TIDE in. Selling first needs an ERC-20 approve of TIDE to the router — the widget reads allowance(you, swapRouter) and shows an Approve TIDE button (sets maxUint256) until the allowance covers the amount. The sell is zeroForOne: false, amountSpecified = -tideIn.

  • Max slippage field, default 5, validated to 0 < s ≤ 50. Out-of-range shows “Set a slippage between 0 and 50%.”
  • You receive (est.) and Min received are computed from spot price (usePoolPricetidePerEth) and the live currentFee; the estimate already nets the swap fee.
  • If the pool price is unavailable (sqrtPriceX96 == 0), the widget warns “Pool price unavailable, the swap will run unprotected.”
  • Not connected → Connect wallet to trade; wrong network → Switch to {chain}.

On mount the widget reads a buy query param and, if it is a positive decimal, switches to the Buy tab and prefills the amount:

/claim/?buy=0.0123

This is the target of the Get TIDE CTA on the Collection / My position card, which grosses up the ETH needed to clear your next whole-token threshold.


When connected, the sidebar shows a Wallet card: your address (as a copyable pill), your native ETH balance, and a Vest unlocks clock counting down to vestEndsAt (shown only while you have something locked).