Treasury & buyback-burn
The Treasury is the protocol’s supply sink. It receives forfeited TIDE whose lottery prize was never claimed, and it can do exactly one thing with that TIDE: burn it. The dev — in the role of guardian — controls when the burn happens, but there is no function anywhere in the contract that pays funds out to anyone. This is the structural core of TIDE’s no-rug guarantee: the dev holds the timing button, never the cash.
What flows into the Treasury
Section titled “What flows into the Treasury”The Treasury is the terminal endpoint of the forfeited-vesting lottery. There are two paths by which TIDE lands there:
-
Expired prizes (the common path). When you sell or transfer a Tide-LP NFT before your rewards finish vesting, the unvested remainder is forfeited and raffled to another holder. The winner has
prizeActivationWindowto claim it. If they don’t, anyone can permissionlessly callexpirePrize(winner), which sweeps the un-activated prize to the Treasury:_transfer(address(this), address(treasury), amount);emit PrizeExpired(winner, amount); -
No-eligible-winner fallback (rare). When a forfeit occurs but the lottery draw finds no eligible winner, the forfeit is normally redistributed pro-rata to all live shares. Only in the edge case where
totalShares == 0does it route to the Treasury instead:if (totalShares > 0) {accFeesPerShareTIDE += forfeited * ACC_SCALE / totalShares;emit PrizeRedistributed(forfeited);} else {_transfer(address(this), address(treasury), forfeited);}
Both paths deposit TIDE that was already forfeited — never anyone’s held balance, vested rewards, or the liquidity position. The Treasury can only accumulate value that holders chose to abandon by exiting early.
What’s queued to burn
Section titled “What’s queued to burn”The amount sitting in the Treasury, waiting to be burned, is just its TIDE balance:
function pendingBurn() external view returns (uint256) { return tide.balanceOf(address(this));}The dapp surfaces this as the Treasury’s “queued to burn” figure (see ../dapp/overview.md).
The only outward action: executeBuyback()
Section titled “The only outward action: executeBuyback()”The guardian can call executeBuyback() at any time. It burns the entire Treasury-held balance and nothing else:
function executeBuyback() external onlyGuardian { uint256 bal = tide.balanceOf(address(this)); if (bal > 0) tide.burn(bal); emit BuybackBurned(bal);}The burn is routed through the hook’s burn(amount), which is itself gated so that only the Treasury can call it:
function burn(uint256 amount) external { if (msg.sender != address(treasury)) revert TreasuryOnly(); _burn(address(treasury), amount);}Burning TIDE permanently reduces total supply. Because holding is the position, shrinking supply mechanically raises every remaining holder’s pro-rata share of the one pool. The guardian gains nothing from triggering it — there is no fee, no skim, no payout to the caller. The only effect is supply down, everyone else’s slice up. This is the on-chain expression of the project’s flywheel slogan, “exits enrich those who stay.”
The guardian role
Section titled “The guardian role”The guardian is set at construction. In the live deployment the constructor wires the Treasury with the hook’s owner as guardian:
treasury = new Treasury(address(this), _owner);The role is transferable, so it can later be handed to a multisig or timelock, but the handoff grants no new powers — the recipient still can only burn:
function transferGuardian(address to) external onlyGuardian { if (to == address(0)) revert ZeroGuardian(); emit GuardianTransferred(guardian, to); guardian = to;}The guardian’s complete authority over the Treasury is therefore: trigger a burn, and hand the burn button to someone else. That is the entire privileged surface.
ETH in the Treasury
Section titled “ETH in the Treasury”The Treasury has a bare receive() so it won’t reject ETH that might be routed to it:
receive() external payable {}In the standard flow nothing sends ETH here — forfeits arrive as TIDE. The contract is honest about this: as its own NatSpec notes, “there is no path to send it back out to anyone, so it can only sit.” Any ETH that ever lands there is stuck, not extractable by the guardian. The current executeBuyback() burns the TIDE balance only; an ETH→TIDE buyback-then-burn is a possible future extension, not a path that pays the guardian.
Why this is the no-rug guarantee
Section titled “Why this is the no-rug guarantee”The Treasury is the place a malicious dev would normally drain. Here, structurally, they can’t:
| Property | Treasury guarantee |
|---|---|
| Custody of user funds | None — the Treasury only ever holds already-forfeited TIDE, never held balances or the LP position. |
| Withdrawal path to the dev | Does not exist. No function transfers value out except executeBuyback, which burns it. |
| What the guardian controls | Only the timing of the burn, plus handing the role to another address. |
| What the guardian gains from a burn | Nothing. Supply shrinks; every other holder’s share grows. |
| Upgradeability / proxy / pause | None. |
| Detector cleanliness | No skim/withdraw/owner-drain pattern, so honeypot/rug scanners read it as clean. |
This is the literal meaning of “the dev holds the button, not the cash”: the dev controls the timing of supply reduction, never the custody of funds.
Events & errors
Section titled “Events & errors”| Item | Signature | Meaning |
|---|---|---|
| Event | BuybackBurned(uint256 tideBurned) | All Treasury-held TIDE was burned. |
| Event | GuardianTransferred(address indexed from, address indexed to) | Guardian role handed off. |
| Event | PrizeExpired(address indexed winner, uint256 amount) | An un-activated prize was swept to the Treasury (emitted by the hook). |
| Error | GuardianOnly() | Caller is not the guardian. |
| Error | ZeroGuardian() | transferGuardian target is the zero address. |
For the full contract reference, addresses, and the rest of the system’s events and errors, see ../contracts/treasury.md and ../contracts/events-and-errors.md.