Sitowise
Protocol

The hook lifecycle

A Uniswap v4 hook has a stricter life than an ordinary contract. Its address encodes its permissions, its pools choose it at birth and can never change it, and it only ever runs inside somebody else’s transaction. Here is that lifecycle end to end.

The five stages

  1. Mine an address. v4 reads permissions from the low bits of the hook address, so the address has to be ground out before deployment.
  2. Deploy to it. Through CREATE2, with the salt that produces that exact address.
  3. Initialise pools that name it. A pool commits to its hook at initialize and never again.
  4. Accrue. Every swap through such a pool calls afterSwap and leaves a share behind.
  5. Sweep. The accrued value is pushed into the factory as payout liquidity.
Sitowise, launch periodPaymentA plain transfer tothe payments walletWatcherSees the transfer andrecords the tx hashmintForThe relayer createsthe node, pays the gascreditBatchThe distributor sendsETH onto balancesNode balanceHeld on chain,counted in outstandingwithdrawYou call it yourself,the whole balance moves
Stages three through five, with launch-period funding shown as the branch it is.

1. The address is the permission set

Uniswap v4 does not ask a hook which callbacks it wants. It reads them from the low 14 bits of the hook’s own address. A contract deployed to the wrong address is not a misconfigured hook, it is a hook that v4 silently never calls.

Sitowise needs two flags: afterSwap, and afterSwapReturnDelta because taking value from the pool means returning a non-zero delta. Together they are 0x44.

The address constraint
uint160(address(hook)) & 0x3FFF == 0x44

AFTER_SWAP_FLAG               = 1 << 6   // 0x40
AFTER_SWAP_RETURNS_DELTA_FLAG = 1 << 2   // 0x04

The constructor calls Hooks.validateHookPermissions against its own address, so a deployment to a wrong address reverts instead of quietly producing a dead hook. That check costs one deployment and removes an entire class of silent failure.

2. Deploying to a mined address

Only CREATE2 gives a predictable address, so the deploy script grinds salts locally until the resulting address carries the right low bits, then deploys through the standard deterministic factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C and asserts that the deployed address equals the mined one.

contracts, deploying the hook
export FACTORY_ADDRESS=0x...        # the SitowiseFactory
export OWNER_ADDRESS=0x...
export HOOK_SHARE_BPS=25             # optional, cap 500

forge script script/DeployHook.s.sol:DeployHook \
  --rpc-url $RPC_URL --private-key $DEPLOYER_PRIVATE_KEY --broadcast

POOL_MANAGER defaults to the Robinhood Chain v4 PoolManager, which is verified on the explorer under that name. The v4 PositionManager at 0x58daec3116aae6D93017bAAea7749052E8a04fA7 reports the same address from poolManager(), which is how it was confirmed rather than assumed.

ContractAddress on Robinhood Chain
Uniswap v4 PoolManager0x8366a39CC670B4001A1121B8F6A443A643e40951
Uniswap v4 PositionManager0x58daec3116aae6D93017bAAea7749052E8a04fA7
CREATE2 deterministic factory0x4e59b44847b379578588920cA78FbF26c0B4956C

3. Pools choose their hook once

This is the single fact that decides whether the protocol is swap funded or not, which is why it appears on this page, on How accrual works, in the contract’s own comments, and on Risks. Until those pools exist and carry volume, rewards are funded by Sitowise.

4. Accrual, per swap

Once a pool is live on the hook, every swap through it runs the same short path: measure the unspecified side, compute the share, take it from the manager, add it to the per-currency cumulative total, emit SwapAccrued, return the delta. It adds a few thousand gas to a swap and cannot revert the swap for any reason other than a genuine accounting failure.

5. Sweeping into the factory

Accrued native value would sit on the hook until somebody swept it. sweepNative() is permissionless and always sends to the factory through fund(), so the value lands as unattached contract balance and is booked with a Funded event. A sweep credits nothing by itself: balances only ever move through creditBatch, which is a separate, payable call. See Settlement.

Sweeping
# permissionless, destination fixed in code
cast send $HOOK "sweepNative()" --private-key $ANY_KEY

# owner only, for ERC-20 accruals the factory cannot hold
cast send $HOOK "sweepToken(address)" $TOKEN --private-key $OWNER_KEY

After the sweep, accrued is unchanged: it is a lifetime total, not a balance. The difference between it and the hook’s current balance is what has already been swept.

What the hook owner can change

CallEffectBound
setShareBpsChanges the share taken per swapCannot exceed MAX_SHARE_BPS, which is a constant
setFactoryChanges where sweepNative sends valueCannot be the zero address
setSweepRecipientChanges where token accruals are sentCannot be the zero address

There is no owner call that pulls native value out of the hook to an arbitrary address. Native value has exactly one exit, and it leads to the factory. The equivalent analysis for the factory is on Security model.