5/5
## How the Uniswap Universal Router Uses Permit2 for V4 Swaps When you execute a swap with an ERC20 token through the Uniswap Universal Router, you aren't sending a traditional `approve` transaction. Instead, you grant a `Permit2` approval to the router, allowing it to spend tokens on your behalf. This lesson traces the execution path of a Uniswap V4 swap to uncover exactly where and how this `Permit2` approval is used within the smart contract architecture. ## The Entrypoint: The `execute` Function in UniversalRouter.sol Every interaction with the Universal Router starts with a single function call: `execute`. This function is the main entrypoint, designed to process a series of actions in a single, atomic transaction. It accepts two primary arguments: `commands` and `inputs`. The `commands` parameter is a byte string where each byte represents a specific action to be performed (e.g., a V4 swap, an NFT purchase). The `inputs` parameter is an array of calldata, with each element corresponding to a command and containing the necessary arguments for that action. The `execute` function loops through these commands and uses a `dispatch` function to route each one to the appropriate handler module. ```solidity // File: UniversalRouter.sol function execute(bytes calldata commands, bytes[] calldata inputs) public payable { // ... // loop through all given commands, execute them and pass along outputs for (uint256 commandIndex = 0; commandIndex < numCommands; commandIndex++) { bytes1 command = commands[commandIndex]; bytes calldata input = inputs[commandIndex]; (success, output) = dispatch(command, input); // Dispatch logic // ... } } ``` For our trace, we are interested in a V4 swap command. When the `dispatch` function encounters this command, it forwards the execution to the `V4SwapRouter.sol` module. ## The V4 Modules: From Universal Router to V4 Periphery The execution now leaves the core Universal Router contract and enters the specialized modules responsible for handling Uniswap V4 interactions. This involves two key contracts across two different repositories: 1. **`V4SwapRouter.sol`**: This contract exists within the `universal-router` repository. It serves as the specific module that the Universal Router calls to handle V4 trades and inherits its core logic from `V4Router`. 2. **`V4Router.sol`**: This is a more abstract contract located in the `v4-periphery` repository. It contains the fundamental logic for interacting with the V4 `PoolManager` and is responsible for routing actions like swaps. During a V4 swap, the `PoolManager` operates on the concept of "deltas"—virtual balance changes that track how much of each token is owed to or by the pool. The tokens are not transferred immediately. These deltas must be "settled" at the end of the interaction, which is the point where the actual token transfers occur. ## Delta Settlement: The Role of `DeltaResolver.sol` The logic for settling these token deltas resides in `DeltaResolver.sol`, another contract found in the `v4-periphery` repository. `V4Router` inherits from this contract, giving it the ability to resolve pending balances with the `PoolManager`. The key function is `_settle`. This internal function is called to pay the `PoolManager` what it is owed. It first checks if the currency to be paid is native ETH (`address(0)`) or an ERC20 token. For ERC20 tokens, the logic is delegated to an abstract internal function named `_pay`. ```solidity // File: DeltaResolver.sol (in v4-periphery repo) function _settle(Currency currency, address payer, uint256 amount) internal { // ... poolManager.sync(currency); if (currency.isAddressZero()) { poolManager.settle{value: amount}(); } else { _pay(currency, payer, amount); // Calls the abstract _pay function for ERC20s poolManager.settle(); } } // ... // Abstract function to be implemented by inheriting contracts function _pay(Currency token, address payer, uint256 amount) internal virtual; ``` This abstract `_pay` function is a critical design choice. It allows the generic `v4-periphery` contracts to define the settlement flow without being coupled to a specific payment mechanism like `Permit2`. The implementation is left to the contract that inherits it. ## The Implementation: Connecting V4 Logic to Permit2 in `V4SwapRouter.sol` We now return to the `universal-router` repository. The `V4SwapRouter.sol` contract provides the concrete implementation for the abstract `_pay` function it inherits from `DeltaResolver` (via `V4Router`). This implementation is the bridge between the generic V4 settlement logic and the Universal Router's `Permit2`-based payment system. It overrides `_pay` and simply calls another function, `payOrPermit2Transfer`, to handle the payment. ```solidity // File: V4SwapRouter.sol (in universal-router repo) abstract contract V4SwapRouter is V4Router, Permit2Payments { // ... function _pay(Currency token, address payer, uint256 amount) internal override { payOrPermit2Transfer(Currency.unwrap(token), payer, address(poolManager), amount); } } ``` ## The Core Payment Logic: `Permit2Payments.sol` The `V4SwapRouter` inherits from `Permit2Payments.sol`, a contract dedicated to handling token payments using `Permit2`. This contract contains the final, crucial piece of logic in the `payOrPermit2Transfer` function. This function determines *how* to pay the `PoolManager`. It checks if the `payer` is the Universal Router contract itself (`address(this)`). * **If `payer == address(this)`**: This means the router contract already holds the required tokens (perhaps from a previous command in the same `execute` call). In this case, it performs a standard token transfer to the recipient. * **If `payer` is an external address (the user)**: This is the standard scenario for a swap. It means the tokens must be pulled directly from the user's wallet. To do this without a standard `approve` call, the function relies on the user's `Permit2` approval and calls `permit2TransferFrom`. ```solidity // File: Permit2Payments.sol (in universal-router repo) function payOrPermit2Transfer(address token, address payer, address recipient, uint256 amount) internal { if (payer == address(this)) { // If the router already has the tokens, pay directly _pay(token, recipient, amount); } else { // If the user is the payer, use Permit2 to pull the tokens permit2TransferFrom(token, payer, recipient, amount.toUint160()); } } ``` ## The Final Step: Calling the `PERMIT2` Contract The `permit2TransferFrom` function is the final link in the chain. It makes a direct, external call to the canonical `PERMIT2` contract's `transferFrom` function. This is the action that spends the user's allowance, moving the specified amount of ERC20 tokens from the user's address (`from`) to the `PoolManager` (`to`), thus settling the swap. ```solidity // File: Permit2Payments.sol (in universal-router repo) function permit2TransferFrom(address token, address from, address to, uint160 amount) internal { PERMIT2.transferFrom(from, to, amount, token); } ``` ## Conclusion and Key Takeaways By tracing the execution flow, we can see how the Uniswap architecture elegantly separates concerns to integrate V4 swaps into the Universal Router's payment system. * **Execution Path**: The logic for a V4 swap payment flows from `UniversalRouter` -> `V4SwapRouter` -> `V4Router` -> `DeltaResolver`'s `_settle` -> the `_pay` implementation in `V4SwapRouter` -> `payOrPermit2Transfer` -> `permit2TransferFrom`, which finally calls the `PERMIT2` contract. * **Architectural Separation**: The system is split across two repositories. `v4-periphery` contains the generic, reusable V4 routing and settlement logic, while `universal-router` provides the specific implementation that integrates this logic with its `Permit2`-based payment model. * **The Importance of Permit2**: For a user to execute a V4 swap with an ERC20 token through the Universal Router, a `Permit2` approval for the router contract is mandatory. The entire settlement process relies on this mechanism to pull funds from the user's wallet to satisfy the swap's obligations to the `PoolManager`.
When you execute a swap with an ERC20 token through the Uniswap Universal Router, you aren't sending a traditional approve transaction. Instead, you grant a Permit2 approval to the router, allowing it to spend tokens on your behalf. This lesson traces the execution path of a Uniswap V4 swap to uncover exactly where and how this Permit2 approval is used within the smart contract architecture.
execute Function in UniversalRouter.solEvery interaction with the Universal Router starts with a single function call: execute. This function is the main entrypoint, designed to process a series of actions in a single, atomic transaction. It accepts two primary arguments: commands and inputs.
The commands parameter is a byte string where each byte represents a specific action to be performed (e.g., a V4 swap, an NFT purchase). The inputs parameter is an array of calldata, with each element corresponding to a command and containing the necessary arguments for that action.
The execute function loops through these commands and uses a dispatch function to route each one to the appropriate handler module.
For our trace, we are interested in a V4 swap command. When the dispatch function encounters this command, it forwards the execution to the V4SwapRouter.sol module.
The execution now leaves the core Universal Router contract and enters the specialized modules responsible for handling Uniswap V4 interactions. This involves two key contracts across two different repositories:
V4SwapRouter.sol: This contract exists within the universal-router repository. It serves as the specific module that the Universal Router calls to handle V4 trades and inherits its core logic from V4Router.
V4Router.sol: This is a more abstract contract located in the v4-periphery repository. It contains the fundamental logic for interacting with the V4 PoolManager and is responsible for routing actions like swaps.
During a V4 swap, the PoolManager operates on the concept of "deltas"—virtual balance changes that track how much of each token is owed to or by the pool. The tokens are not transferred immediately. These deltas must be "settled" at the end of the interaction, which is the point where the actual token transfers occur.
DeltaResolver.solThe logic for settling these token deltas resides in DeltaResolver.sol, another contract found in the v4-periphery repository. V4Router inherits from this contract, giving it the ability to resolve pending balances with the PoolManager.
The key function is _settle. This internal function is called to pay the PoolManager what it is owed. It first checks if the currency to be paid is native ETH (address(0)) or an ERC20 token. For ERC20 tokens, the logic is delegated to an abstract internal function named _pay.
This abstract _pay function is a critical design choice. It allows the generic v4-periphery contracts to define the settlement flow without being coupled to a specific payment mechanism like Permit2. The implementation is left to the contract that inherits it.
V4SwapRouter.solWe now return to the universal-router repository. The V4SwapRouter.sol contract provides the concrete implementation for the abstract _pay function it inherits from DeltaResolver (via V4Router).
This implementation is the bridge between the generic V4 settlement logic and the Universal Router's Permit2-based payment system. It overrides _pay and simply calls another function, payOrPermit2Transfer, to handle the payment.
Permit2Payments.solThe V4SwapRouter inherits from Permit2Payments.sol, a contract dedicated to handling token payments using Permit2. This contract contains the final, crucial piece of logic in the payOrPermit2Transfer function.
This function determines how to pay the PoolManager. It checks if the payer is the Universal Router contract itself (address(this)).
If payer == address(this): This means the router contract already holds the required tokens (perhaps from a previous command in the same execute call). In this case, it performs a standard token transfer to the recipient.
If payer is an external address (the user): This is the standard scenario for a swap. It means the tokens must be pulled directly from the user's wallet. To do this without a standard approve call, the function relies on the user's Permit2 approval and calls permit2TransferFrom.
PERMIT2 ContractThe permit2TransferFrom function is the final link in the chain. It makes a direct, external call to the canonical PERMIT2 contract's transferFrom function. This is the action that spends the user's allowance, moving the specified amount of ERC20 tokens from the user's address (from) to the PoolManager (to), thus settling the swap.
By tracing the execution flow, we can see how the Uniswap architecture elegantly separates concerns to integrate V4 swaps into the Universal Router's payment system.
Execution Path: The logic for a V4 swap payment flows from UniversalRouter -> V4SwapRouter -> V4Router -> DeltaResolver's _settle -> the _pay implementation in V4SwapRouter -> payOrPermit2Transfer -> permit2TransferFrom, which finally calls the PERMIT2 contract.
Architectural Separation: The system is split across two repositories. v4-periphery contains the generic, reusable V4 routing and settlement logic, while universal-router provides the specific implementation that integrates this logic with its Permit2-based payment model.
The Importance of Permit2: For a user to execute a V4 swap with an ERC20 token through the Universal Router, a Permit2 approval for the router contract is mandatory. The entire settlement process relies on this mechanism to pull funds from the user's wallet to satisfy the swap's obligations to the PoolManager.
A cross-repository trace to How the Universal Router Uses Permit2 for V4 Swaps - Follow the execution path from the `execute` entrypoint to the final `PERMIT2` call, and discover how an abstract `_pay` function bridges the generic V4 settlement logic with the router's specific payment implementation.
Previous lesson
Previous
Next lesson
Next
Note:
Last updated on December 10, 2025
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