5/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
Note:
Last updated on January 5, 2026
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025
Duration: 5min
Duration: 7min
Duration: 39min
Duration: 35min
Duration: 34min
Duration: 31min
Course Overview
About the course
Difference between Uniswap v3 and v4
Uniswap v4 PoolManager
Uniswap v4 Hooks
Uniswap v4 PositionManager
Uniswap v4 Universal Router
Uniswap v4 Singleton architecture and flash accounting
Uniswap v4 operations and lifecycle
Uniswap v4 multihopping and quoting
How to build a Uniswap v4 swap router
How to build a smart contract a liquidation bot executes
Last updated on November 6, 2025