TideHook
TideHook is the heart of TIDE. A single contract is, simultaneously:
- the TIDE ERC-20 token (
name() = "Tide",symbol() = "TIDE"), - the Uniswap V4 hook for the canonical
ETH/TIDEpool, - the LP owner of the one protocol-owned V4 position, and
- the fee router that distributes swap fees pro-rata to NFT holders.
It also deploys and wires the TideMirror (the Tide-LP ERC-721 face), TideArt (on-chain SVG), and Treasury in its constructor. For the full architecture, see Architecture overview; for every event and custom error, see Events & custom errors.
Constants
Section titled “Constants”All constants are public (so they are readable on-chain) except ACC_SCALE and the buyback slippage cap, which are private.
| Constant | Type | Value | Meaning |
|---|---|---|---|
SUPPLY | uint256 | 10_000 ether (10000e18) | Fixed total supply, minted once in the constructor. Never inflates. |
UNIT | uint256 | 1 ether (1e18) | One whole TIDE = one Tide-LP NFT = a 1/10000 share of the pool. |
TICK_SPACING | int24 | 200 | Tick spacing of the canonical pool. |
FEE_TIER_1 | uint24 | 250_000 | First fee tier in pips (1e6 = 100%): 25%. |
FEE_TIER_2 | uint24 | 100_000 | Second fee tier: 10%. |
FEE_FINAL | uint24 | 50_000 | Steady-state fee: 5% forever. |
ACC_SCALE | uint256 (private) | 1e12 | Fixed-point scale of the per-share fee accumulators. |
MAX_BUYBACK_SLIPPAGE_BPS | uint256 (private) | 1000 | Slippage floor on the in-pool claim buyback: 10%. |
The pool itself is keyed with the dynamic-fee flag (LPFeeLibrary.DYNAMIC_FEE_FLAG), which is what lets the hook override the fee per swap. Inside _beforeSwap, the per-swap override is returned OR’d with LPFeeLibrary.OVERRIDE_FEE_FLAG. See Degressive launch fee.
Constructor-set durations (immutables)
Section titled “Constructor-set durations (immutables)”The four lifecycle durations are constructor immutables — there is no setter. Because they are part of the CREATE2 init code, changing any of them changes the mined hook address. The dapp reads them live from chain, so the same UI renders correctly whether durations are compressed (Sepolia) or full (mainnet). See Addresses & network for the live values.
| Immutable | Type | Production default | Meaning |
|---|---|---|---|
feeWindow1 | uint32 | 300 (5 min) | t < feeWindow1 → 25% fee. |
feeWindow2 | uint32 | 480 (8 min) | feeWindow1 ≤ t < feeWindow2 → 10% fee; t ≥ feeWindow2 → 5%. |
vestingDuration | uint64 | 259_200 (72 h) | Linear vesting length applied to all claims. |
prizeActivationWindow | uint64 | 172_800 (48 h) | Window a lottery winner has to activate a prize before it expires to the Treasury. |
Other immutables: POSM (V4 PositionManager), PERMIT2, mirror (TideMirror), art (TideArt), treasury (Treasury).
Key state
Section titled “Key state”| State variable | Type | Meaning |
|---|---|---|
seeded | bool | true once seed() has run. The pool is one-shot. |
launchTime | uint64 | Timestamp the pool was seeded; drives the fee schedule. 0 until seeded. |
totalShares | uint256 | Count of live (minted, not burned) NFTs; the fee-distribution denominator. |
accFeesPerShareETH | uint256 | Cumulative ETH fees per share, scaled by ACC_SCALE. Monotonic. |
accFeesPerShareTIDE | uint256 | Cumulative TIDE fees per share, scaled by ACC_SCALE. Monotonic. |
hookPositionTokenId | uint256 | The V4 PositionManager token id of the protocol-owned position. |
globalTickLower | int24 | Lower tick of the seeded range. |
globalTickUpper | int24 | Upper tick of the seeded range. |
pendingETH / pendingTIDE | mapping(address => uint256) | Raw, not-yet-converted fees owed to a user. |
vests | mapping(address => Vest) | Per-user linear vesting position (denominated in TIDE). |
pendingPrize / prizeAwardedAt | mapping(address => uint256) | Lottery prize amount and award timestamp. |
Read / view functions
Section titled “Read / view functions”Every function below is view (or pure) and free to call before connecting a wallet. Reverting functions are noted.
Token & supply
Section titled “Token & supply”| Function | Returns | Meaning |
|---|---|---|
name() | string | "Tide". |
symbol() | string | "TIDE". |
totalSupply() | uint256 | Circulating TIDE (Solady ERC-20; starts at SUPPLY = 10000e18, only ever decreases via burns). |
balanceOf(address) | uint256 | TIDE balance of an address (Solady ERC-20). |
totalShares | uint256 | Live NFT count = fee denominator. |
totalMinted() | uint256 | NFTs ever minted (monotonic; ignores burns). |
Pool & launch
Section titled “Pool & launch”| Function | Returns | Meaning |
|---|---|---|
seeded | bool | Whether the pool has been seeded. |
launchTime | uint64 | Seed/launch timestamp (0 pre-launch). |
currentFee() | uint24 | The fee in pips applying to a swap right now (250_000 / 100_000 / 50_000). Returns FEE_FINAL pre-launch. |
feeWindow1 / feeWindow2 | uint32 | Fee-tier window lengths (seconds). |
vestingDuration | uint64 | Vesting length (seconds). |
prizeActivationWindow | uint64 | Prize activation window (seconds). |
accFeesPerShareETH / accFeesPerShareTIDE | uint256 | Per-share fee accumulators (scaled by ACC_SCALE = 1e12). |
poolKey() | PoolKey | The canonical V4 pool key (currency0 = address(0) native ETH, currency1 = address(this), dynamic fee, tickSpacing = 200, hooks = this). |
globalTickLower / globalTickUpper | int24 | Seeded range ticks. |
hookPositionTokenId | uint256 | PositionManager token id of the protocol position. |
Per-user position & vesting
Section titled “Per-user position & vesting”See 72-hour linear vesting and Claim = in-pool buyback.
| Function | Returns | Meaning |
|---|---|---|
nftBalanceOf(address owner) | uint256 | Live Tide-LP NFT count held (_ownedLength). |
ownedTokensOf(address owner) | uint256[] | The owner’s owned tokenId array (view-only unpack). |
claimableNow(address user) | uint256 | TIDE withdrawable right now (claimable + linearly-vested-not-yet-withdrawn). |
lockedOf(address user) | uint256 | TIDE still locked (not yet vested). |
vestEndsAt(address user) | uint256 | Timestamp the current locked tranche finishes vesting (0 if nothing locked). |
owedOf(address user) | (uint256 ethOwed, uint256 tideOwed) | Raw fees owed but not yet converted+vested = (pendingETH[user], pendingTIDE[user]). |
prizeStatus(address user) | (uint256 amount, uint256 expiresAt, bool expired) | Lottery prize state. See The forfeited-vesting lottery. |
Per-NFT views
Section titled “Per-NFT views”| Function | Returns | Meaning |
|---|---|---|
pendingFees(uint256 tokenId) | (uint256 owedETH, uint256 owedTIDE) | Fees owed to a tokenId since its last checkpoint. Excludes un-poked fees still inside the V4 position — call pokeFees() first for the freshest figure. Returns (0, 0) for a non-existent id. |
nftOwnerOf(uint256 tokenId) | address | Owner of a Tide-LP NFT. Reverts InvalidTokenId if none. |
nftBalanceOf(address owner) | uint256 | (also listed above) Live NFT count. |
nftGetApproved(uint256 tokenId) | address | Single-token approval. |
nftIsApprovedForAll(address o, address s) | bool | Operator approval. |
nftTokenURI(uint256 tokenId) | string | On-chain art data-URI via TideArt. Reverts InvalidTokenId if none. |
seedOf(uint256 tokenId) | bytes32 | Deterministic art seed (pure function of (id, this)). Reverts InvalidTokenId if none. |
Write functions
Section titled “Write functions”| Function | Guard | Effect |
|---|---|---|
seed(uint160 sqrtPriceX96, int24 tickLower, int24 tickUpper, uint128 liquidity) returns (uint256 tokenId) | onlyOwner, one-shot | Initialize the pool and mint the single full-supply V4 position. Reverts AlreadySeeded on a second call. |
pokeFees() | none (permissionless) | Pull accrued swap fees out of the V4 position into the per-share accumulators. Idempotent; early-returns if not seeded, totalShares == 0, or the pool is currently unlocked. |
claim(uint256 tokenId) | nonReentrant | Poke fees, harvest that NFT’s accrual to its owner, then convert+vest only the caller’s owed. |
claimMany(uint256[] tokenIds) | nonReentrant | Poke fees, harvest a batch to their owners, then convert+vest the caller’s owed. |
processOwed() | nonReentrant | Convert+vest the caller’s owed without needing an NFT (for owed accrued via NFT moves/burns). |
withdrawVested() | nonReentrant | Withdraw the linearly-unlocked portion of the caller’s vesting position (paid in TIDE). Does not restart the vesting clock. |
activatePrize() | nonReentrant | Activate the caller’s pending lottery prize → re-vests it. Reverts NoActivatablePrize if none or the window passed. |
expirePrize(address winner) | nonReentrant, permissionless | Route an un-activated, expired prize to the Treasury. Reverts NoActivatablePrize if still activatable. |
burn(uint256 amount) | Treasury-only | Burn TIDE held by the Treasury. Reverts TreasuryOnly for any other caller; only ever burns Treasury-held supply. |
The hook also inherits Solady ERC20 (transfer, transferFrom, approve, permit, …) and Solady Ownable (transferOwnership, renounceOwnership, …), and exposes receive() to accept native ETH for pool flows.
Mirror-only functions — never call from a client
Section titled “Mirror-only functions — never call from a client”The hook exposes three onlyMirror mutating functions that perform NFT authorization. They take an explicit trusted caller argument injected by the mirror and are not meant to be called directly by clients.
function handleNFTTransfer(address from, address to, uint256 tokenId, address caller) external; // onlyMirror, nonReentrantfunction handleNFTApprove(address spender, uint256 tokenId, address caller) external; // onlyMirrorfunction handleNFTSetApprovalForAll(address operator, bool approved, address caller) external; // onlyMirrorV4 hook entrypoints
Section titled “V4 hook entrypoints”getHookPermissions() declares beforeSwap and afterSwap live. _beforeSwap overrides the LP fee per the degressive schedule (the hook’s own buyback, where sender == address(this), is fee-exempt); _afterSwap is a no-op reserved for future accounting. Both are onlyPoolManager via the vendored BaseHook. The ETH→TIDE buyback runs inside the hook’s own _unlockCallback, reachable only via the hook’s unlock (PoolManager-restricted).
Related pages
Section titled “Related pages”- Hold = LP (DN404) — how holding mints/burns NFTs
- Degressive launch fee — the 25% → 10% → 5% schedule
- Claim = in-pool buyback — what
claimactually does - 72-hour linear vesting and The forfeited-vesting lottery
- Treasury & buyback-burn and the Treasury contract
- Events & custom errors — full enumerated surface