1/5
## Understanding the Uniswap v4 Interaction Model Interacting with the Uniswap v4 `PoolManager` introduces a fundamental shift from previous versions. Unlike v3, where you could call `swap` directly from an Externally Owned Account (EOA), v4 employs a sophisticated "lock and callback" mechanism. This design ensures that all operations occur within a controlled, secure context. To execute a swap or modify liquidity, you must route your calls through a smart contract that communicates with the `PoolManager` using a specific, required pattern. This lesson will break down that pattern, explaining why it exists and how to work with it. ## The Gatekeeper: Why You Can't Call `swap` Directly The primary reason direct calls are prohibited is a security feature built into the core functions of the `PoolManager.sol` contract. Let's examine the `swap` function's signature: ```solidity // File: PoolManager.sol (lines 187-189) function swap(PoolKey memory key, SwapParams memory params, ...) external onlyWhenUnlocked ... ``` The key element here is the `onlyWhenUnlocked` modifier. This modifier acts as a gatekeeper, checking the lock state of the `PoolManager` contract before allowing the function's logic to execute. The modifier's implementation is straightforward: ```solidity // File: PoolManager.sol (lines 96-99) modifier onlyWhenUnlocked() { if (!Lock.isUnlocked()) ManagerLocked.selector.revertWith(); _; } ``` If the contract is not in an "unlocked" state, the transaction immediately reverts. By default, the `PoolManager` is always locked. This means any direct attempt to call `swap` from an EOA or an unprepared smart contract will fail. The central challenge, therefore, is to find the correct way to unlock the contract before making your call. ## The Entry Point: Using the `unlock` Function and Callback Pattern The designated entry point for all core interactions with the `PoolManager` is the `unlock` function. This function orchestrates the entire lock, execute, and re-lock cycle. It is designed to receive a call from your smart contract, temporarily unlock itself, and then "call back" to your contract to receive instructions. Here is the step-by-step workflow: 1. **Initiate the Call:** Your smart contract calls the `unlock` function on the `PoolManager` contract. 2. **Contract is Unlocked:** The `PoolManager` receives the call and immediately invokes `Lock.unlock()`, changing its internal state to unlocked. 3. **The Callback:** The `PoolManager` makes an external call back to the `msg.sender` (your contract), invoking a specific function named `unlockCallback` on it. 4. **Execute Actions:** Inside your contract's `unlockCallback` function, you can now perform your desired actions, such as calling `PoolManager.swap()` or `PoolManager.modifyLiquidity()`. Because the `PoolManager` is in an unlocked state, these calls will now succeed. 5. **Re-Lock and Finalize:** Once your `unlockCallback` function completes, control returns to the `PoolManager`. It performs a final settlement check and then calls `Lock.lock()` to re-secure the contract before the transaction ends. The code for the `unlock` function clearly illustrates this process: ```solidity // File: PoolManager.sol (lines 104-114) function unlock(bytes calldata data) external override returns (bytes memory result) { if (Lock.isUnlocked()) AlreadyUnlocked.selector.revertWith(); // Step 2: Unlock the contract Lock.unlock(); // Step 3: Call back to the sender's contract result = IUnlockCallback(msg.sender).unlockCallback(data); // Final check before re-locking if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith(); // Step 5: Lock the contract again Lock.lock(); } ``` This design has a critical implication: because the `PoolManager` explicitly calls `unlockCallback` on `msg.sender`, the caller **must be a smart contract that implements the `IUnlockCallback` interface**. An EOA cannot implement functions, making it impossible for a regular user account to interact directly with the `PoolManager`'s core logic. ## The Technology Under the Hood: Transient Storage and Settlement Checks Two key technical components make this pattern both efficient and secure. ### The `Lock` Library and Transient Storage The locking mechanism is powered by the `Lock.sol` library, which leverages a new EVM feature: **transient storage**. Accessed via the `tstore` and `tload` opcodes, transient storage is a new data location that is both cheap like memory and persistent across contract calls within a single transaction, like storage. It is automatically cleared at the end of each transaction. This makes it perfect for a re-entrancy guard. It provides a gas-efficient way to maintain the lock state across the external callback without incurring the high costs of traditional `SSTORE` and `SLOAD` operations. The `Lock` library's logic is concise: ```solidity // File: libraries/Lock.sol library Lock { bytes32 internal constant IS_UNLOCKED_SLOT = 0x...; function unlock() internal { assembly ('memory-safe') { // Store 'true' in a transient storage slot tstore(IS_UNLOCKED_SLOT, true) } } function lock() internal { assembly ('memory-safe') { // Store 'false' in a transient storage slot tstore(IS_UNLOCKED_SLOT, false) } } function isUnlocked() internal view returns (bool unlocked) { assembly ('memory-safe') { // Load the value from the transient storage slot unlocked := tload(IS_UNLOCKED_SLOT) } } } ``` ### The `NonzeroDeltaCount` Settlement Check Before the `unlock` function completes and re-locks the contract, it performs one final, crucial validation: ```solidity if (NonzeroDeltaCount.read() != 0) CurrencyNotSettled.selector.revertWith(); ``` This check ensures that all currency balances are settled before the transaction concludes. During the callback, operations like swapping change token balances owed to or by the pool. The `NonzeroDeltaCount` check verifies that your contract has settled all these debts. In short, it confirms that the `PoolManager` is not left holding tokens it shouldn't have, nor does it owe tokens it hasn't sent. The counter must return to zero, signifying that all accounting is balanced. ## Key Takeaways for Developers - **Interaction Model:** To use Uniswap v4, you do not call `swap` directly. You call the `unlock` function, which then calls your contract back. Inside this callback, your contract calls `swap`. - **Smart Contract Intermediaries:** All interactions with the `PoolManager`'s core functions must be performed through a smart contract that implements the `IUnlockCallback` interface. - **Transient Storage for Efficiency:** The lock is implemented using gas-efficient transient storage (`tstore`/`tload`), which maintains state only for the duration of a single transaction. - **Enforced Currency Settlement:** The `NonzeroDeltaCount` mechanism guarantees that all token deltas are fully settled, ensuring the pool's accounting is balanced before a transaction can succeed.
Interacting with the Uniswap v4 PoolManager
introduces a fundamental shift from previous versions. Unlike v3, where you could call swap
directly from an Externally Owned Account (EOA), v4 employs a sophisticated "lock and callback" mechanism. This design ensures that all operations occur within a controlled, secure context. To execute a swap or modify liquidity, you must route your calls through a smart contract that communicates with the PoolManager
using a specific, required pattern. This lesson will break down that pattern, explaining why it exists and how to work with it.
swap
DirectlyThe primary reason direct calls are prohibited is a security feature built into the core functions of the PoolManager.sol
contract. Let's examine the swap
function's signature:
The key element here is the onlyWhenUnlocked
modifier. This modifier acts as a gatekeeper, checking the lock state of the PoolManager
contract before allowing the function's logic to execute.
The modifier's implementation is straightforward:
If the contract is not in an "unlocked" state, the transaction immediately reverts. By default, the PoolManager
is always locked. This means any direct attempt to call swap
from an EOA or an unprepared smart contract will fail. The central challenge, therefore, is to find the correct way to unlock the contract before making your call.
unlock
Function and Callback PatternThe designated entry point for all core interactions with the PoolManager
is the unlock
function. This function orchestrates the entire lock, execute, and re-lock cycle. It is designed to receive a call from your smart contract, temporarily unlock itself, and then "call back" to your contract to receive instructions.
Here is the step-by-step workflow:
Initiate the Call: Your smart contract calls the unlock
function on the PoolManager
contract.
Contract is Unlocked: The PoolManager
receives the call and immediately invokes Lock.unlock()
, changing its internal state to unlocked.
The Callback: The PoolManager
makes an external call back to the msg.sender
(your contract), invoking a specific function named unlockCallback
on it.
Execute Actions: Inside your contract's unlockCallback
function, you can now perform your desired actions, such as calling PoolManager.swap()
or PoolManager.modifyLiquidity()
. Because the PoolManager
is in an unlocked state, these calls will now succeed.
Re-Lock and Finalize: Once your unlockCallback
function completes, control returns to the PoolManager
. It performs a final settlement check and then calls Lock.lock()
to re-secure the contract before the transaction ends.
The code for the unlock
function clearly illustrates this process:
This design has a critical implication: because the PoolManager
explicitly calls unlockCallback
on msg.sender
, the caller must be a smart contract that implements the IUnlockCallback
interface. An EOA cannot implement functions, making it impossible for a regular user account to interact directly with the PoolManager
's core logic.
Two key technical components make this pattern both efficient and secure.
Lock
Library and Transient StorageThe locking mechanism is powered by the Lock.sol
library, which leverages a new EVM feature: transient storage. Accessed via the tstore
and tload
opcodes, transient storage is a new data location that is both cheap like memory and persistent across contract calls within a single transaction, like storage. It is automatically cleared at the end of each transaction.
This makes it perfect for a re-entrancy guard. It provides a gas-efficient way to maintain the lock state across the external callback without incurring the high costs of traditional SSTORE
and SLOAD
operations.
The Lock
library's logic is concise:
NonzeroDeltaCount
Settlement CheckBefore the unlock
function completes and re-locks the contract, it performs one final, crucial validation:
This check ensures that all currency balances are settled before the transaction concludes. During the callback, operations like swapping change token balances owed to or by the pool. The NonzeroDeltaCount
check verifies that your contract has settled all these debts. In short, it confirms that the PoolManager
is not left holding tokens it shouldn't have, nor does it owe tokens it hasn't sent. The counter must return to zero, signifying that all accounting is balanced.
Interaction Model: To use Uniswap v4, you do not call swap
directly. You call the unlock
function, which then calls your contract back. Inside this callback, your contract calls swap
.
Smart Contract Intermediaries: All interactions with the PoolManager
's core functions must be performed through a smart contract that implements the IUnlockCallback
interface.
Transient Storage for Efficiency: The lock is implemented using gas-efficient transient storage (tstore
/tload
), which maintains state only for the duration of a single transaction.
Enforced Currency Settlement: The NonzeroDeltaCount
mechanism guarantees that all token deltas are fully settled, ensuring the pool's accounting is balanced before a transaction can succeed.
A technical guide to the Uniswap v4 lock-and-callback pattern - Learn why direct calls to the `PoolManager` are locked and how the `unlock` function enables interactions via a required callback. Discover the role of transient storage and settlement checks in this secure, gas-efficient design.
Previous lesson
Previous
Next lesson
Next
Give us feedback
Note:
Last updated on September 27, 2025
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