SatoHook

a uniswap v4 hook that takes over every swap, replaces the amm math, and acts as the counterparty.

SatoHook is the v4 hook that does all of the work. it holds the eth reserves, runs the curve, enforces the guardrails, and is the one address allowed to mint or burn SatoToken. it is the only contract in the system with non-trivial logic.

permissions

v4 hooks declare which lifecycle callbacks they want via 14 bits encoded in the hook's address. SatoHook turns on exactly four of them:

  • beforeInitialize — validates the pool key.
  • beforeAddLiquidity— reverts forever. the pool can never carry liquidity.
  • beforeSwap— intercepts every swap and replaces amm math with the curve.
  • beforeSwapReturnDelta — flags that beforeSwap returns a BeforeSwapDelta that cancels the amm-routed portion of the swap.

the constructor calls Hooks.validateHookPermissionsto check that the deployed contract's address has these exact bits set in its low 14 bits. a salt grinder in the deploy script searches for a CREATE2 salt that produces such an address.

state

ethCum
cumulative fair-curve eth (post-fee) ever absorbed by the curve. buys add, sells subtract. this is the single number that determines the curve's position.
totalMintedFair
fair-curve circulating supply. equals totalMinted(ethCum) up to rounding. differs from SATO_TOKEN.totalSupply() whenever entropy bonuses have been minted — the inverse curve always references this fair number, not the actual supply.
selfDeprecated
bool. flips to true the first time totalMintedFair / K ≥ 99/100. once true, all future buys revert.
poolInitialized
bool. flips to true when beforeInitialize accepts a pool key for the first time.
lastBuyBlock[a]
mapping(address => uint256). the block in which address a last bought. read by the cooldown check on sells.
feesAccrued
cumulative 0.3% fee revenue, in eth. the hook does not expose a withdraw function, so this counter grows forever and the eth backing it is locked.

the buy flow

the buyer (or their router) pre-settles eth into the pool manager, then calls swap. the hook's beforeSwap intercepts before the amm sees anything, computes the curve mint amount, applies the entropy multiplier if applicable, and returns a BeforeSwapDelta that nets eth in and sato out at settlement time.

the sell flow

sells take the seller's actual sato, convert it into fair-curve units, evaluate the inverse curve at the current totalMintedFair, deduct the fee, and pay eth out from the hook's balance.

fair-curve unit conversion

because entropy adds bonus sato above the curve, the actual erc-20 supply and the curve's notion of supply diverge. when sellers burn, the contract scales their input back into fair-curve units:

satoFairIn = satoIn × totalMintedFair / totalSupply

this keeps the inverse curve referencing the canonical state (ethCum, totalMintedFair), regardless of how much bonus supply has entered circulation. entropy bonuses get socialized uniformly into every holder's per-token exit value — nobody's sell prices ahead of the curve.

error surface

NotPoolManager
any entrypoint called by an address other than the v4 pool manager.
InvalidPool
pool key mismatch: not eth/sato, wrong fee, wrong hook. raised in beforeInitialize or in beforeSwap if a malformed key gets through.
LiquidityAdditionsForbidden
any attempt to add liquidity to the pool, ever.
BuyTooLarge
a buy with ethIn > MAX_BUY_WEI (5 eth). split into multiple buys to get around this, but each will pay gas and be priced one step further up the curve.
CooldownActive
a sell from an address whose last buy was within COOLDOWN_BLOCKS (1 block). wait until the next block.
SelfDeprecatedNoBuys
a buy after selfDeprecated has flipped. only sells remain enabled from this point forward.
ExactOutputUnsupported
a swap with amountSpecified >= 0. only exact-input swaps are accepted in either direction.
MissingSwapperInHookData
hookData failed to encode an address. the swapper identity must be threaded through so the cooldown can attribute buys to the right account.
InsufficientEthReserves
the inverse curve would owe more eth than the hook currently holds. should be impossible in practice (the hook holds every eth wei ever sent into it minus what it has paid out), but catches degenerate cases like external donations being withdrawn through synthetic paths.

view helpers

curveReserveEth()
returns address(this).balance − feesAccrued— the eth pool that backs sells. drops as sells consume it, grows as buys add to it.
philosophy()
returns the contract's 256-byte manifesto string. set in the constructor from 8 bytes32 words; the getter concatenates them and trims trailing nulls. immutable, on-chain statement of intent.

this page describes deployed code. nothing here is a promise — it is a description of what the contract does and what it doesn't.