How accrual works
Accrual is the moment value is taken out of a swap. The hook that does it is not deployed yet, so this page describes what it measures and who pays it, and then says plainly what is putting ETH on node balances today instead.
The mechanism
Uniswap v4 keeps every pool inside one contract, the PoolManager, and lets a pool nominate a hook contract that the manager calls at fixed points during an operation. Sitowise implements a single one of those points: afterSwap. It runs in the same transaction as the swap, after the pool has computed the trade but before the manager settles balances.
At that moment the hook takes a fixed share of the swap and keeps it. That share is shareBps, expressed in basis points, currently 25 bps, which is 0.25% of the measured side. The contract also carries a constant MAX_SHARE_BPS of 500 bps, which the owner cannot raise. A cap the owner cannot move is the only kind of cap a swapper can read once and rely on.
bool specifiedTokenIs0 = (params.amountSpecified < 0) == params.zeroForOne;
(Currency feeCurrency, int128 swapAmount) =
specifiedTokenIs0 ? (key.currency1, delta.amount1())
: (key.currency0, delta.amount0());
if (swapAmount < 0) swapAmount = -swapAmount;
uint256 amount = (uint256(uint128(swapAmount)) * bps) / 10_000;
if (amount == 0) return (IHooks.afterSwap.selector, int128(0));
poolManager.take(feeCurrency, address(this), amount);Everything in this section and the two below it describes the hook as it is being written. None of it is running: the hook is not deployed and no pool names it. What is actually putting ETH on node balances today is described further down, under What is funding rewards today.
Which side pays
A swap has a specified side and an unspecified side. If you ask to sell exactly one token, the input is specified and the output is not. If you ask to receive exactly one token, the output is specified and the input is not.
Uniswap v4 only permits an afterSwap hook to move the unspecified currency, so that is where the share is charged. The consequence is worth stating plainly:
| Swap type | Charged from | What the trader sees |
|---|---|---|
| Exact input | The output | Slightly less of the token they are buying |
| Exact output | The input | Slightly more of the token they are selling |
Liquidity providers are untouched. The int128 the hook returns tells the PoolManager that the hook owes that amount to pool accounting, which is what makes the swapper carry it rather than the LPs. If you write a test or an integration that asserts on swap output, note that the delta a router reports is already net of the hook’s share.
Dust rounds to nothing. When the computed share truncates to zero the hook returns immediately, so tiny swaps neither pay nor pay for the extra gas of a zero-value take.
What is accrued, and in what
The hook accrues in whatever token sat on the unspecified side, and keeps a per-currency cumulative total in accrued(address currency), where address(0) means native ETH. That figure only ever increases, including across sweeps, so it doubles as an on-chain total to reconcile against.
- Native accruals are moved into the factory by
sweepNative(), which anyone may call. It arrives through the factory’sfund(), so it lands as unattached balance the distributor can then credit to nodes. The destination is fixed in code, so there is nothing for a caller to redirect and a stalled operator cannot strand the value. - Token accruals go to
sweepRecipientthroughsweepToken(), an owner-only call, because node balances are native ETH only. Those are converted off chain.
Every accrual emits an event, which is the interface anyone auditing the protocol should use. It is documented on Events.
What is funding rewards today
A Uniswap v4 pool fixes its hook when the pool is initialised. It cannot be changed later, and a hook cannot be attached to pools that already exist. So the hook earns nothing until Sitowise creates pools that name it and those pools carry real volume.
Until then, accrued stays at zero and the value credited to nodes is funded by Sitowise out of its own funds. It is not swap revenue and is not presented as swap revenue. Sitowise can reduce or stop that funding at any time.
What credits a node is the same call in either case. The distributor sends creditBatch(ids, amounts) to the factory as a payable call, and msg.value has to equal the sum of amounts or the call reverts with ValueMismatch. The ETH lands on the balances in the same transaction that records them, so a balance can never exist without the money behind it.
uint256 sum;
for (uint256 i; i < n; ++i) {
uint256 amt = amounts[i];
if (amt == 0) revert BadInput();
if (amt > type(uint128).max) revert AmountTooLarge();
sum += amt;
}
if (sum != msg.value) revert ValueMismatch();The switch between the two sources is a single operational setting, DIST_MODE. In treasury mode the amount credited each round is funded and decided by Sitowise. In swaps mode the worker reads real accrual from the hook’s SwapAccrued events over the period and splits that instead. Nothing downstream changes: the same creditBatch call puts the same ETH on the same node balances, and the owner withdraws it themselves, from their own wallet, with no server in the path. The mechanics are on Distribution.
creditBatch is launch-period funding, which is where the credited value is actually coming from.Verifying accrual yourself
You do not have to take any of this on trust. There is no hook address to read yet, and that absence is itself the check: if no hook is deployed on chain 4663, no swap has paid one. The PoolManager any future hook would have to point at is 0x8366a39CC670B4001A1121B8F6A443A643e40951, and the addresses that do exist are listed on Addresses.
# native value the hook has ever taken
cast call $HOOK "accrued(address)(uint256)" \
0x0000000000000000000000000000000000000000
# the share it charges, and the cap the owner cannot exceed
cast call $HOOK "shareBps()(uint16)"
cast call $HOOK "MAX_SHARE_BPS()(uint16)"Until then the reads that mean something are on the factory: totalDistributed is every wei ever credited to nodes, and outstanding is what is credited and not yet withdrawn. Continue with The hook lifecycle for what deploying the hook involves.