Treasury
Reference for Treasury.sol — the buyback-burn sink. It receives forfeited TIDE whose lottery prize expired un-activated, and the only thing anyone can ever do with that TIDE is burn it. There is no withdrawal path. For the narrative (“the dev holds the button, not the cash”) see ../mechanics/treasury.md.
Deployment
Section titled “Deployment”| Field | Value |
|---|---|
| Contract | Treasury |
| Sepolia address | 0x076f29063199DB470D260E5cE2cb560Af98cfBd3 |
| Constructed by | TideHook constructor — treasury = new Treasury(address(this), _owner) |
tide | the TideHook address (the TIDE ERC-20, which exposes a treasury-only burn) |
guardian (initial) | the TideHook owner / deployer |
See ../deployment/addresses.md for all addresses.
ITideBurn public immutable tide; // the TIDE token (the hook)address public guardian; // may trigger burns and hand off the role; never custodialtideis set once at construction and can never change.guardianis the only privileged role. It can burn and re-assign itself — nothing else.
How TIDE arrives here
Section titled “How TIDE arrives here”The Treasury is a sink, not a participant. TIDE reaches it through exactly one routine path: a lottery prize that expired un-activated.
When a holder sells or transfers a Tide-LP NFT before their vesting completes, the unvested remainder is forfeited and raffled to another holder as a pending prize (see ../mechanics/lottery.md). The winner has a bounded window (prizeActivationWindow) to activate it. If they don’t, anyone may permissionlessly call expirePrize(winner) on the hook, which transfers the stale prize to the Treasury:
// TideHook.expirePrize(address winner) — after the activation window elapses_transfer(address(this), address(treasury), amount);emit PrizeExpired(winner, amount);The forfeit/redistribution flow can also route to the Treasury as a fallback when no eligible winner is found and there are no live shares to redistribute to. The receive() function also accepts raw ETH, but no standard flow sends ETH here — and, critically, there is no path to send it back out.
Functions
Section titled “Functions”executeBuyback()
Section titled “executeBuyback()”function executeBuyback() external onlyGuardianBurns all TIDE currently held by the Treasury. This is the only outward action the contract can take.
uint256 bal = tide.balanceOf(address(this));if (bal > 0) tide.burn(bal);emit BuybackBurned(bal);tide.burn(bal) is the hook’s treasury-gated burn — it reverts TreasuryOnly() for any other caller and only ever burns the Treasury’s own balance, never user funds:
function burn(uint256 amount) external { if (msg.sender != address(treasury)) revert TreasuryOnly(); _burn(address(treasury), amount);}Burning reduces total TIDE supply, raising every remaining holder’s share of the pool. The guardian controls only the timing of this reduction; it gains nothing from calling it.
transferGuardian(address to)
Section titled “transferGuardian(address to)”function transferGuardian(address to) external onlyGuardianHands off the guardian role (for example to a multisig or timelock). Reverts ZeroGuardian() if to is the zero address. Emits GuardianTransferred(from, to). The new guardian inherits the same single power: it can only burn.
pendingBurn() — view
Section titled “pendingBurn() — view”function pendingBurn() external view returns (uint256)Returns tide.balanceOf(address(this)) — the TIDE currently queued for burning. The dapp surfaces this as “queued to burn” (see ../dapp/overview.md).
Events
Section titled “Events”| Event | Emitted by | Meaning |
|---|---|---|
BuybackBurned(uint256 tideBurned) | executeBuyback() | The Treasury’s full TIDE balance was burned (amount may be 0). |
GuardianTransferred(address indexed from, address indexed to) | transferGuardian() | The guardian role was reassigned. |
Errors
Section titled “Errors”| Error | Condition |
|---|---|
GuardianOnly() | A guarded function was called by an address other than guardian. |
ZeroGuardian() | transferGuardian() was passed the zero address. |
See events-and-errors.md for the protocol-wide catalogue.
The no-rug invariant
Section titled “The no-rug invariant”The entire state-changing surface of this contract is two functions: executeBuyback() and transferGuardian(). Neither transfers value to the guardian or any chosen recipient — one burns, the other re-assigns a role whose only power is to burn. There is no withdraw, no sweep, no onlyGuardian escape hatch, and the receive() function has no matching outflow. The guardian holds the button, not the cash.
For the full trust model and the limitations TIDE discloses openly, see ../security/trust-model.md and ../security/known-limitations.md.