Skip to content

Execution

Uniswap v4 Flash Accounting: Follow Every Delta

A checked three-currency ledger shows why paying the input and taking the final output still leaves a five-token credit that must be resolved.

Ivory paper ribbons run through a grooved ceramic tray, with a lime block left in one channel.
Illustration for execution; examples in this article are educational.

Uniswap v4 flash accounting records what each address owes or is owed in each currency while the PoolManager is unlocked. Operations can offset those deltas without intermediate token transfers, but every delta must reach zero before the PoolManager completes that unlock. An unpaid debt or an unclaimed credit makes the unlock revert.

A route can therefore pay its input and request its final output yet still fail because a small intermediate credit remains. Following the signed balances explains both the transfer savings and the failure.

The mechanics below follow Uniswap's v4-core release v4.0.0, alongside its official developer documentation. A deployed router or hook can add its own behavior; the worked example isolates the core accounting.

One manager, separate obligations

Uniswap v4 holds pool state in a shared PoolManager. A swap updates the relevant pool and books obligations internally, allowing a later operation to use an intermediate currency without first transferring it out to a router. That is the source of the netting opportunity described in Uniswap's flash-accounting overview.

Pool identity and settlement identity answer different questions. The v4 pool ID guide explains how to identify the pool being called. The settlement ledger instead keys each balance by an address and a currency, as the pinned CurrencyDelta library shows.

For an integrating router R, a negative A delta means R owes A to the manager. A positive A delta means the manager owes A to R. Zero means neither obligation remains. These are signed quantities of that asset, not dollar values.

Consequently, a positive B balance cannot cancel a negative A balance, even if an external price assigns them identical values. A second address's positive A balance also cannot cancel R's negative A balance. Both the address and the currency must match for additions to reach the same ledger entry.

This differs from Uniswap v3 fee growth and tokens owed. Those records reconstruct a position's accrued claim over time. A v4 transient delta describes an obligation still open during execution.

The callback creates a settlement window

R calls unlock, and the PoolManager calls back into R's unlockCallback. R performs its swaps or liquidity operations and resolves the resulting balances before returning. Uniswap's callback and delta guide describes that control flow and the sign convention.

The decisive check is at the end of the unlock call. In the pinned PoolManager implementation, a nonzero outstanding-delta count triggers CurrencyNotSettled; only a zero count permits the manager to lock again. Positive credits count just as debts do.

The word “transient” does not make an unresolved obligation harmless. EIP-1153 specifies storage that survives relevant calls within a transaction and is discarded when the transaction ends. The PoolManager's settlement check occurs earlier. If an integration performs several unlock calls in one transaction, each must satisfy its own completion check.

A route that leaves five B behind

Consider this constructed accounting example. R directly calls two pools inside one unlock callback. A, B and C are three standard ERC-20 tokens, each with six decimals. There are no hooks, unusual transfer behaviors or other participants with outstanding deltas.

Stipulate that the first swap's returned caller delta is −1,000 A and +980 B. The second swap uses 975 B and returns 450 C. Any swap fees are already reflected in these stipulated results. These figures test settlement; they are not live quotes or outputs derived from an AMM curve. Constant product versus StableSwap examines that separate pricing calculation.

The table shows R's cumulative obligations after each step, in whole-token display units. The count measures distinct address and currency pairs with a nonzero balance, not the sum of the columns.

Completed stepA deltaB deltaC deltaNonzero entries
Before swaps0000
Swap 1,000 A for 980 B−1,000+98002
Swap 975 B for 450 C−1,000+5+4503
Pay and settle 1,000 A0+5+4502
Take 450 C0+501
Take the remaining 5 B0000

After the second swap, B is 980 − 975 = 5. Most of the intermediate credit has funded the second operation without leaving the manager. The last five tokens still belong to R's settlement ledger.

If R returns after taking C, the count remains one. There is no unpaid A debt and no outstanding C credit, but +5 B is enough to fail the completion check. Reverting the unlock undoes its swaps and transfers. An enclosing contract may catch the error, but it cannot retain those reverted effects. This follows Solidity's exception and subcall rules.

Taking the five B closes the last entry. The external result is then a payment of 1,000 A and receipts of 450 C plus 5 B. The manager's external balance changes are the opposite: +1,000 A, −450 C and −5 B. Each currency reconciles separately.

The raw B remainder is 5,000,000 units, not five raw units. The calculation uses integers throughout; decimals change the display, not the zero condition. Even one remaining raw unit would still be nonzero.

For this route, three underlying-token transfers resolve three remaining currencies. If the second swap instead consumed the full 980 B credit, B would net to zero and this simplified route would need only the A payment and C withdrawal. “Only two transfers” therefore depends on the actual residuals and settlement choices.

Sync, pay, settle; then take what is owed

For the example's A payment, R first calls sync(A), then arranges the A transfer into the PoolManager, then calls settle(). The official flash-accounting integration guide documents this ERC-20 sequence.

sync records a reserve checkpoint. It does not approve spending, move tokens or discharge a debt. The CurrencyReserves library stores the synced currency and reserve amount. Preserve that checkpoint through the matching transfer and settlement instead of replacing it with an unrelated currency.

In this example, the manager's A balance rises by 1,000,000,000 raw units after the checkpoint. The settlement implementation credits that observed increase, bringing R's A delta from −1,000,000,000 to zero. Sending A before taking the checkpoint can leave no subsequent increase to credit. Merely seeing tokens at the manager is not evidence that R's debt was settled.

take moves the other way: it subtracts the requested amount from the caller's delta while transferring that currency to the specified destination. Thus R can call take to deliver C directly to the user's address while closing R's credit. The recipient of the transfer and the address whose obligation is reduced need not be the same.

Native ETH follows a different payment branch using the value attached to settlement. The native ETH versus WETH guide covers that distinction; the ERC-20 checkpoint-and-transfer example should not be applied unchanged to native funds.

Mistakes that a portfolio total hides

Suppose, in a separate illustrative failure, R owes 1,000 A but a helper pays and invokes settleFor(U) for the end user U. R's entry remains −1,000 A, while U receives a +1,000 A credit. Their signed total is zero, yet two entries remain unresolved. The pinned PoolManager interface defines the recipient as the address credited for the payment. Record that credited address explicitly.

Nor should an integration quietly drop the +5 B remainder because it considers it dust. It can transfer B out, or deliberately choose a different supported resolution. Uniswap's ERC-6909 documentation explains an alternative: retain underlying tokens in the manager and receive claim tokens. Those claims are distinct from the transient balance that must reach zero.

Another explicit option is clear, which forfeits an exact positive delta without a payout. It cannot forgive a negative debt. The interface warns that cleared currency is permanently non-retrievable, so clearing and withdrawing five B are economically different outcomes even when both leave a zero delta.

A zero ledger also does not prove the user received an acceptable price. The swap interface warns that liquidity constraints and hooks can change amounts; integrations should check the returned caller delta. Settlement correctness and the user's input and output limits are separate requirements.

Validate the route while its deltas still exist

The pinned TransientStateLibrary supplies readers for a target's currency delta and the manager's nonzero-delta count. Use those checks within the execution or simulation being examined. A later transaction cannot recover the earlier transient ledger by reading current transient state.

Keep this worksheet alongside the route trace:

Evidence to retainQuestion it must answer
Chain, PoolManager, router and code versionWhich implementation and calling address are being evaluated?
Each PoolKey and its ordered currenciesWhich asset does each returned amount describe?
Returned caller deltas after each operationWhat obligations actually arose, including any hook effects?
Synced currency, checkpoint and observed paymentWhich reserve increase was credited, and to which address?
Recipient and amount for every take or claimHow was each positive balance resolved?
Final per-address balances and global countAre all entries zero before the callback returns?
User limits and resulting receiptsDoes correct settlement also deliver the intended outcome?

For the worked route, the final audit should show three closed entries for R, a global count of zero, and separate A, B and C balance reconciliations. If a residual remains, identify its address, currency and sign before choosing the operation that resolves it. That is more useful than declaring the route balanced because its input was paid or its aggregate value appears unchanged.

Keep the context with the example.

Each worked example is illustrative, with its sources and assumptions in the article. Read our editorial standards for authorship, verification and corrections.