Skip to content

Claim = in-pool buyback

Claiming in TIDE does not pay you ETH. When you claim the fees accrued to your Tide-LP NFTs, the protocol takes your ETH fee share, buys TIDE with it inside the canonical pool, and pays you in TIDE. Every claim is therefore buy pressure on the token — value that supports the price for every holder, not just the claimant.

This page covers the full claim path: syncing pool fees with pokeFees(), reading per-NFT accrual with pendingFees, claiming with claim / claimMany, the raw-owed escape hatch (owedOf / processOwed), and the in-pool buyback that converts the ETH leg. What happens to the bought TIDE afterwards — the 72-hour linear vesting — is covered in vesting.md. For exact signatures and the surrounding contract surface, see ../contracts/tidehook.md.

swap fees sit in the v4 position
│ pokeFees() — permissionless, idempotent
accFeesPerShareETH / accFeesPerShareTIDE (per-share accumulators)
│ claim(id) / claimMany(ids) — _harvest() credits the NFT's OWNER
pendingETH[user] / pendingTIDE[user] (raw owed; owedOf() reads it)
│ processOwed() / the tail of claim() — _process()
ETH leg → in-pool buyback → TIDE + TIDE leg as-is
│ _depositVesting()
your vesting position → see ../mechanics/vesting.md

Step 1 — pokeFees(): sync the pool’s fees

Section titled “Step 1 — pokeFees(): sync the pool’s fees”

Swap fees do not stream into the accumulators automatically; they sit inside the protocol’s single Uniswap v4 position until someone pokes them out.

function pokeFees() public;

pokeFees() is permissionless, idempotent, and gasless to everyone else — anyone can call it, and it only ever benefits holders. It performs a zero-delta DECREASE_LIQUIDITY + TAKE_PAIR against the position to pull accrued ETH and TIDE to the hook, measures what arrived by balance delta, and rolls it into the two per-share accumulators:

if (ethGained > 0) accFeesPerShareETH += ethGained * ACC_SCALE / totalShares;
if (tideGained > 0) accFeesPerShareTIDE += tideGained * ACC_SCALE / totalShares;

where ACC_SCALE = 1e12 and totalShares is the count of live Tide-LP NFTs (the fee denominator). It early-returns and does nothing if the pool is not yet seeded, if totalShares == 0, or if the pool is currently unlocked (so it can never re-enter mid-swap). On success it emits FeesPoked(ethGained, tideGained).

Step 2 — pendingFees(tokenId): read one NFT’s accrual

Section titled “Step 2 — pendingFees(tokenId): read one NFT’s accrual”

Each Tide-LP NFT carries a per-NFT fee debt checkpoint. Its accrued (but un-harvested) share since that checkpoint is:

function pendingFees(uint256 tokenId)
external view returns (uint256 owedETH, uint256 owedTIDE);

This is a pure read of the accumulator minus the NFT’s debt, divided by ACC_SCALE. It returns (0, 0) for a non-existent / burned token.

Step 3 — claim(id) / claimMany(ids): harvest, then convert

Section titled “Step 3 — claim(id) / claimMany(ids): harvest, then convert”
function claim(uint256 tokenId) external nonReentrant;
function claimMany(uint256[] calldata tokenIds) external nonReentrant;

Both functions:

  1. Call pokeFees() to sync the latest fees.
  2. Harvest each NFT’s accrued share into that NFT’s current owner’s raw owed buckets (_harvestpendingETH[owner] += …, pendingTIDE[owner] += …), checkpointing the NFT’s debt and emitting Claimed(tokenId, owner, owedETH, owedTIDE).
  3. Convert only the caller’s owed via _process(msg.sender) — see Step 4.

The split between harvest and convert is deliberate and is an anti-grief property:

claimMany is the batch form: it pokes once, harvests every supplied id to its respective owner (skipping any with no owner), then converts the caller’s owed. The dapp exposes it as Claim all over your owned ids. claim reverts InvalidTokenId if the single token has no owner.

Conversion (_process, run as the tail of claim / claimMany and as the whole of processOwed) is where the buyback happens:

uint256 bought = ethOwed > 0 ? _buybackEthToTide(ethOwed) : 0;
uint256 toVest = tideOwed + bought;
_depositVesting(user, toVest);
emit OwedProcessed(user, ethOwed, bought, toVest);

The ETH leg is spent buying TIDE in the pool; the TIDE leg is added as-is; the sum is deposited into your vesting position. It zeroes your pendingETH / pendingTIDE, and does nothing (returns early) if both are zero.

_buybackEthToTide(ethIn) reads spot price from getSlot0, computes an expected TIDE output, and enforces a trustless 10% slippage floor:

uint256 minOut = expected * (10_000 - MAX_BUYBACK_SLIPPAGE_BPS) / 10_000; // 10_000 - 1000 = 9000 → 90%

with MAX_BUYBACK_SLIPPAGE_BPS = 1000 (10%). It then unlocks the PoolManager and, in the callback, performs a zeroForOne ETH→TIDE swap with sqrtPriceLimitX96 = MIN_SQRT_PRICE + 1; if the TIDE received is below minOut it reverts SlippageExceeded. On success it emits Buyback(ethIn, tideOut).

Two properties matter here:

  • The buyback is fee-exempt. Because the hook itself is the swap sender, the degressive launch fee is overridden to 0 for protocol buybacks — your claim does not pay the 25/10/5% fee back into the pool. See fee-schedule.md.
  • Every claim is buy pressure. The ETH that swaps came into the pool bought TIDE out of it, nudging the price up. This is the reflexive core of the protocol: claiming rewards is itself a market buy that benefits all holders.

owedOf + processOwed: amounts that couldn’t be paid immediately

Section titled “owedOf + processOwed: amounts that couldn’t be paid immediately”

Fees are also captured involuntarily when an NFT moves or is burned (an NFT departure harvests its accrual to the departing holder’s owed buckets, emitting PendingCredited). A holder can therefore have raw owed sitting in pendingETH / pendingTIDE without holding any NFT to claim.

function owedOf(address user)
external view returns (uint256 ethOwed, uint256 tideOwed);
function processOwed() external nonReentrant;
  • owedOf reads your raw, not-yet-converted owed — the ETH and TIDE legs still awaiting the buyback. (claim works through the NFT path; owedOf exposes the bucket directly.)
  • processOwed() runs _process(msg.sender) on its own: it buys back your ETH leg, adds your TIDE leg, vests the result (OwedProcessed), and zeroes the buckets — no NFT required. The dapp surfaces it as Convert owed, shown only when your owed legs are non-zero.
FunctionGuardEffect
pokeFees()none (permissionless)Pull pool fees into the per-share accumulators; emit FeesPoked
pendingFees(tokenId)viewPoked, un-harvested (owedETH, owedTIDE) for one NFT
claim(tokenId)nonReentrantPoke → harvest the NFT to its owner → convert + vest the caller’s owed
claimMany(tokenIds)nonReentrantPoke → harvest each NFT to its owner → convert + vest the caller’s owed
owedOf(user)viewRaw, not-yet-converted (ethOwed, tideOwed)
processOwed()nonReentrantConvert + vest the caller’s owed (no NFT needed)

Related events: FeesPoked(ethGained, tideGained) · Claimed(tokenId, owner, ethOut, tideOut) · PendingCredited(user, ethAmount, tideAmount) · Buyback(ethIn, tideOut) · OwedProcessed(user, ethConverted, tideBought, tideVested). Full definitions: ../contracts/events-and-errors.md.

The TIDE you are paid — the bought-back ETH leg plus the TIDE leg — does not land in your wallet immediately. It is deposited into your 72-hour linear vesting position, and a fresh claim re-locks the still-locked remainder. Read vesting.md for how vesting unlocks, how withdrawVested() pays out, and why selling before you fully vest forfeits the remainder to the lottery.