5/5
## Understanding the Uniswap v4 Account Delta In decentralized finance, gas efficiency and transactional atomicity are paramount. Uniswap v4 introduces a powerful internal accounting system within its core `PoolManager` contract called the **Account Delta**. This mechanism enables complex, multi-step actions like swaps and liquidity provisions to occur within a single, atomic transaction while minimizing gas costs by deferring actual token transfers until the very end. This lesson explores the Account Delta, how it works through the lock/callback pattern, and how it ensures the financial integrity of the pools. ### What is an Account Delta? An Account Delta is a virtual balance tracker. Instead of transferring tokens for every single operation within a transaction, the `PoolManager` simply records a change—a "delta"—to an account. This system significantly reduces the number of expensive `transfer` calls. Each account delta is defined by two properties: 1. **Identifier**: A unique pair of `(target, currency)`, where the `target` is the smart contract interacting with the pool (e.g., a Router) and `currency` is the token being tracked. 2. **Value**: A signed integer (`int128`) that represents the balance change. The value of the delta has a clear and important meaning: * A **negative delta (-)** signifies that the `target` contract **owes** tokens to the `PoolManager`. * A **positive delta (+)** signifies that the `target` contract is **owed** tokens and can take them from the `PoolManager`. ### The Lock/Callback Interaction Pattern Interactions with the `PoolManager` are not direct. An Externally Owned Account (EOA) cannot simply call its functions. Instead, a smart contract, such as a Router, must initiate the process by calling the `unlock` function. This call triggers a lock/callback pattern: 1. The `target` contract calls `unlock` on the `PoolManager`. 2. The `PoolManager` "unlocks" its state, allowing the `target` to perform a series of actions (like `swap`, `mint`, or `burn`) through a callback. 3. During this callback, all financial operations update the account deltas instead of executing token transfers. 4. Crucially, by the time the `unlock` function completes its execution, all account deltas for the `target` must be resolved to zero. The `PoolManager` enforces this rule using an internal counter called `nonZeroDeltaCount`. If this counter is not zero at the end of the `unlock` call, the entire transaction reverts. This brilliant mechanism forces the interacting contract to settle all its debts and claim all its credits atomically, preventing any financial inconsistencies. ### Walkthrough: Swapping 1000 USDC for 0.2 ETH Let's trace the flow of account deltas during a typical swap. A user wants to swap 1000 USDC for 0.2 ETH, and they initiate this through a `Router` contract. The `Router` will be our `target` for this interaction. We will track its deltas for both USDC and ETH. 1. **`unlock`**: The Router calls `unlock` on the `PoolManager`. At this point, no financial actions have occurred. * USDC Delta: 0 * ETH Delta: 0 2. **`swap`**: Inside the callback, the Router executes the swap. It provides 1000 USDC to receive 0.2 ETH. * The Router now owes the pool 1000 USDC. Its USDC delta becomes negative. * The Router is now owed 0.2 ETH by the pool. Its ETH delta becomes positive. * USDC Delta: **-1000** * ETH Delta: **+0.2** 3. **`take`**: The Router needs to claim the 0.2 ETH it is owed. To do this, it calls the `take` function. This function resolves the positive delta. * The `PoolManager` performs the actual token transfer, sending 0.2 ETH to the Router. * Simultaneously, it updates the ETH delta for the Router back to zero. * USDC Delta: -1000 * ETH Delta: **0** 4. **`settle`**: The Router still owes the pool 1000 USDC, represented by the negative delta. To resolve this, it must first transfer 1000 USDC into the `PoolManager` contract and *then* call the `settle` function. * The `settle` function verifies that the pool's USDC balance has increased by the required amount. * It then updates the USDC delta for the Router back to zero. * USDC Delta: **0** * ETH Delta: 0 At the end of the callback, both deltas are zero. The `nonZeroDeltaCount` is `0`, and the `unlock` function can complete successfully, committing the state changes. ### Resolving Deltas: A Look at the Code To fully understand how deltas are cleared, let's examine the key functions responsible for resolving them: `take` and `settle`. #### The `take` Function: Claiming What You're Owed The `take` function is used to resolve a **positive delta**. When a contract is owed tokens, it calls `take` to receive them. ```solidity // Simplified from PoolManager.sol function take(Currency currency, address to, uint256 amount) external { _accountDelta(currency, -(amount.toInt128()), msg.sender); currency.transfer(to, amount); } ``` In our example, the Router calls `take` for 0.2 ETH. * The `_accountDelta` function is called with `-0.2 ETH`. * The new delta is calculated: `current_delta (+0.2) + update (-0.2) = 0`. * The `currency.transfer` line executes the actual `transfer` of 0.2 ETH from the `PoolManager` to the recipient. #### The `settle` Function: Paying Your Debts The `settle` function is used to resolve a **negative delta**. A crucial detail is that the `target` contract **must transfer the tokens to the `PoolManager` *before* calling `settle`**. The function then reconciles the balance. The logic resides in an internal function, `_settle`. ```solidity // Simplified from PoolManager.sol (internal function) function _settle(address recipient) internal returns (uint256 paid) { // ... // For ERC20 tokens: uint256 reservesBefore = CurrencyReserves.getSyncedReserves(); uint256 reservesNow = currency.balanceOfSelf(); paid = reservesNow - reservesBefore; // ... _accountDelta(currency, paid.toInt128(), recipient); } ``` In our example, the Router first transfers 1000 USDC to the `PoolManager`. Then it calls `settle`. 1. `reservesBefore` holds the USDC balance the `PoolManager` had before the transfer. 2. `reservesNow` gets the contract's current, higher USDC balance. 3. `paid` is calculated as the difference, which equals `1000`. 4. `_accountDelta` is called with this positive `paid` amount. 5. The new delta is calculated: `current_delta (-1000) + update (+1000) = 0`. ### The Final Security Check The entire system is secured by a final check at the end of the `unlock` function's execution. ```solidity // Simplified from PoolManager.sol function unlock(bytes calldata data) external override returns (bytes memory) { // ... (logic to unlock and perform callback) if (NonzeroDeltaCount.read() != 0) revert CurrencyNotSettled(); // ... (logic to re-lock) } ``` This check is non-negotiable. If the interacting contract fails to zero out all its deltas by the end of the callback, this `if` statement will trigger, reverting the entire transaction. This guarantees that the `PoolManager` can never be left in a state where it is owed funds or owes funds that were never claimed, ensuring the complete financial integrity of the protocol.
In decentralized finance, gas efficiency and transactional atomicity are paramount. Uniswap v4 introduces a powerful internal accounting system within its core PoolManager
contract called the Account Delta. This mechanism enables complex, multi-step actions like swaps and liquidity provisions to occur within a single, atomic transaction while minimizing gas costs by deferring actual token transfers until the very end.
This lesson explores the Account Delta, how it works through the lock/callback pattern, and how it ensures the financial integrity of the pools.
An Account Delta is a virtual balance tracker. Instead of transferring tokens for every single operation within a transaction, the PoolManager
simply records a change—a "delta"—to an account. This system significantly reduces the number of expensive transfer
calls.
Each account delta is defined by two properties:
Identifier: A unique pair of (target, currency)
, where the target
is the smart contract interacting with the pool (e.g., a Router) and currency
is the token being tracked.
Value: A signed integer (int128
) that represents the balance change.
The value of the delta has a clear and important meaning:
A negative delta (-) signifies that the target
contract owes tokens to the PoolManager
.
A positive delta (+) signifies that the target
contract is owed tokens and can take them from the PoolManager
.
Interactions with the PoolManager
are not direct. An Externally Owned Account (EOA) cannot simply call its functions. Instead, a smart contract, such as a Router, must initiate the process by calling the unlock
function.
This call triggers a lock/callback pattern:
The target
contract calls unlock
on the PoolManager
.
The PoolManager
"unlocks" its state, allowing the target
to perform a series of actions (like swap
, mint
, or burn
) through a callback.
During this callback, all financial operations update the account deltas instead of executing token transfers.
Crucially, by the time the unlock
function completes its execution, all account deltas for the target
must be resolved to zero.
The PoolManager
enforces this rule using an internal counter called nonZeroDeltaCount
. If this counter is not zero at the end of the unlock
call, the entire transaction reverts. This brilliant mechanism forces the interacting contract to settle all its debts and claim all its credits atomically, preventing any financial inconsistencies.
Let's trace the flow of account deltas during a typical swap. A user wants to swap 1000 USDC for 0.2 ETH, and they initiate this through a Router
contract.
The Router
will be our target
for this interaction. We will track its deltas for both USDC and ETH.
unlock
: The Router calls unlock
on the PoolManager
. At this point, no financial actions have occurred.
USDC Delta: 0
ETH Delta: 0
swap
: Inside the callback, the Router executes the swap. It provides 1000 USDC to receive 0.2 ETH.
The Router now owes the pool 1000 USDC. Its USDC delta becomes negative.
The Router is now owed 0.2 ETH by the pool. Its ETH delta becomes positive.
USDC Delta: -1000
ETH Delta: +0.2
take
: The Router needs to claim the 0.2 ETH it is owed. To do this, it calls the take
function. This function resolves the positive delta.
The PoolManager
performs the actual token transfer, sending 0.2 ETH to the Router.
Simultaneously, it updates the ETH delta for the Router back to zero.
USDC Delta: -1000
ETH Delta: 0
settle
: The Router still owes the pool 1000 USDC, represented by the negative delta. To resolve this, it must first transfer 1000 USDC into the PoolManager
contract and then call the settle
function.
The settle
function verifies that the pool's USDC balance has increased by the required amount.
It then updates the USDC delta for the Router back to zero.
USDC Delta: 0
ETH Delta: 0
At the end of the callback, both deltas are zero. The nonZeroDeltaCount
is 0
, and the unlock
function can complete successfully, committing the state changes.
To fully understand how deltas are cleared, let's examine the key functions responsible for resolving them: take
and settle
.
take
Function: Claiming What You're OwedThe take
function is used to resolve a positive delta. When a contract is owed tokens, it calls take
to receive them.
In our example, the Router calls take
for 0.2 ETH.
The _accountDelta
function is called with -0.2 ETH
.
The new delta is calculated: current_delta (+0.2) + update (-0.2) = 0
.
The currency.transfer
line executes the actual transfer
of 0.2 ETH from the PoolManager
to the recipient.
settle
Function: Paying Your DebtsThe settle
function is used to resolve a negative delta. A crucial detail is that the target
contract must transfer the tokens to the PoolManager
before calling settle
. The function then reconciles the balance.
The logic resides in an internal function, _settle
.
In our example, the Router first transfers 1000 USDC to the PoolManager
. Then it calls settle
.
reservesBefore
holds the USDC balance the PoolManager
had before the transfer.
reservesNow
gets the contract's current, higher USDC balance.
paid
is calculated as the difference, which equals 1000
.
_accountDelta
is called with this positive paid
amount.
The new delta is calculated: current_delta (-1000) + update (+1000) = 0
.
The entire system is secured by a final check at the end of the unlock
function's execution.
This check is non-negotiable. If the interacting contract fails to zero out all its deltas by the end of the callback, this if
statement will trigger, reverting the entire transaction. This guarantees that the PoolManager
can never be left in a state where it is owed funds or owes funds that were never claimed, ensuring the complete financial integrity of the protocol.
An operational guide to the Uniswap v4 Account Delta - Delve into the gas-efficient accounting system at the heart of the `PoolManager` contract. Learn how the lock/callback pattern uses virtual deltas to guarantee atomic settlement of swaps and liquidity actions.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025
Duration: 5min
Duration: 7min
Duration: 39min
Duration: 35min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 Singleton architecture
Uniswap v4 flash accounting
Uniswap v4 operations
Uniswap v4 lifecycle
How to build a Uniswap v4 swap router
Last updated on September 22, 2025