## Mastering the Uniswap Universal Router: Command Execution and Failure Handling The Uniswap Universal Router is a powerful and flexible smart contract that allows developers to execute multiple actions, such as swaps and transfers, within a single, atomic transaction. A key feature of its design is the ability to precisely control the transaction's outcome if an individual action fails. This lesson explores how the Universal Router processes commands and how you can leverage its failure-handling mechanism to build more resilient and sophisticated DeFi applications. ### Executing Batched Commands with the `execute` Function The core of the Universal Router's functionality is the `execute` function. This function serves as the single entry point for bundling a series of commands into one transaction. It accepts two primary arguments: `commands` and `inputs`. - **`commands` (`bytes calldata`):** This is a byte string where each byte represents a specific command to be performed. For example, one byte might signify a V3 swap, another might represent wrapping ETH, and a third could be a transfer. The router processes these bytes sequentially. - **`inputs` (`bytes[] calldata`):** This is an array of ABI-encoded byte strings. Each element in this array contains the specific parameters for the corresponding command in the `commands` string. Therefore, the `inputs` array must have the same length as the `commands` byte string; the first `input` corresponds to the first `command`, the second to the second, and so on. By default, the `execute` function is atomic in the strictest sense: every command must succeed. If any single command in the sequence fails for any reason (e.g., insufficient liquidity, a failed deadline), the entire transaction will revert. All state changes from preceding, successful commands within the transaction will be rolled back. ### Controlling Revert Behavior on a Per-Command Basis While the default "all-or-nothing" behavior is often desirable for ensuring atomicity, there are scenarios where you might want the transaction to continue even if a specific step fails. For instance, you might want to attempt an opportunistic swap that is not critical to the transaction's primary goal. The Universal Router accommodates this by allowing you to specify, on a per-command basis, whether its failure should cause a full transaction revert or be gracefully ignored. This control is not managed by a separate parameter but is cleverly encoded into the command byte itself. Each command is represented by an 8-bit byte. The most significant bit (MSB) of this byte acts as a special flag that dictates the failure-handling logic: - **MSB = 0 (Required Success):** If the first bit of the command byte is `0`, the command is considered mandatory. If it fails, the entire `execute` call reverts. This is the default behavior. - **MSB = 1 (Allowed Failure):** If the first bit of the command byte is `1`, the command is considered optional. If it fails, its individual state changes are reverted, but the Universal Router catches the error, ignores it, and proceeds to execute the next command in the sequence. To set this flag, you take the standard command value and perform a bitwise OR operation with `0x80` (binary `10000000`). For example: - The command for `V3_SWAP_EXACT_IN` has a value of `0x00`. To make it a mandatory command, you use `0x00`. - To allow this same `V3_SWAP_EXACT_IN` command to fail without reverting the parent transaction, you would use `0x00 | 0x80`, which results in the command byte `0x80`. ### The `execute` Function Logic Under the Hood This failure-handling mechanism is implemented within the `execute` function's logic. Let's examine the relevant portion of the code from `Commands.sol`: ```solidity function execute(bytes calldata commands, bytes[] calldata inputs) public payable { bool success; bytes memory output; uint256 numCommands = commands.length; if (inputs.length != numCommands) revert LengthMismatch(); // 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); if (!success && successRequired(command)) { revert ExecutionFailed(commandIndex: commandIndex, message: output); } } } ``` The critical logic resides in the `if` statement at the end of the loop: 1. The `dispatch` function is called to execute the current command. It returns a boolean `success` flag. 2. If the command fails, `success` will be `false`. 3. The condition `!success && successRequired(command)` is then evaluated. 4. The internal function `successRequired(command)` checks the most significant bit of the `command` byte. It returns `true` if the bit is `0` (success is required) and `false` if the bit is `1` (failure is allowed). 5. Therefore, the transaction only reverts if the command failed (`!success` is true) **AND** its success was mandatory (`successRequired(command)` is true). If a command marked for allowed failure fails, the `successRequired` check returns `false`, the `if` condition is not met, and the loop continues to the next command.
The Uniswap Universal Router is a powerful and flexible smart contract that allows developers to execute multiple actions, such as swaps and transfers, within a single, atomic transaction. A key feature of its design is the ability to precisely control the transaction's outcome if an individual action fails. This lesson explores how the Universal Router processes commands and how you can leverage its failure-handling mechanism to build more resilient and sophisticated DeFi applications.
execute FunctionThe core of the Universal Router's functionality is the execute function. This function serves as the single entry point for bundling a series of commands into one transaction. It accepts two primary arguments: commands and inputs.
commands (bytes calldata): This is a byte string where each byte represents a specific command to be performed. For example, one byte might signify a V3 swap, another might represent wrapping ETH, and a third could be a transfer. The router processes these bytes sequentially.
inputs (bytes[] calldata): This is an array of ABI-encoded byte strings. Each element in this array contains the specific parameters for the corresponding command in the commands string. Therefore, the inputs array must have the same length as the commands byte string; the first input corresponds to the first command, the second to the second, and so on.
By default, the execute function is atomic in the strictest sense: every command must succeed. If any single command in the sequence fails for any reason (e.g., insufficient liquidity, a failed deadline), the entire transaction will revert. All state changes from preceding, successful commands within the transaction will be rolled back.
While the default "all-or-nothing" behavior is often desirable for ensuring atomicity, there are scenarios where you might want the transaction to continue even if a specific step fails. For instance, you might want to attempt an opportunistic swap that is not critical to the transaction's primary goal.
The Universal Router accommodates this by allowing you to specify, on a per-command basis, whether its failure should cause a full transaction revert or be gracefully ignored. This control is not managed by a separate parameter but is cleverly encoded into the command byte itself.
Each command is represented by an 8-bit byte. The most significant bit (MSB) of this byte acts as a special flag that dictates the failure-handling logic:
MSB = 0 (Required Success): If the first bit of the command byte is 0, the command is considered mandatory. If it fails, the entire execute call reverts. This is the default behavior.
MSB = 1 (Allowed Failure): If the first bit of the command byte is 1, the command is considered optional. If it fails, its individual state changes are reverted, but the Universal Router catches the error, ignores it, and proceeds to execute the next command in the sequence.
To set this flag, you take the standard command value and perform a bitwise OR operation with 0x80 (binary 10000000). For example:
The command for V3_SWAP_EXACT_IN has a value of 0x00. To make it a mandatory command, you use 0x00.
To allow this same V3_SWAP_EXACT_IN command to fail without reverting the parent transaction, you would use 0x00 | 0x80, which results in the command byte 0x80.
execute Function Logic Under the HoodThis failure-handling mechanism is implemented within the execute function's logic. Let's examine the relevant portion of the code from Commands.sol:
The critical logic resides in the if statement at the end of the loop:
The dispatch function is called to execute the current command. It returns a boolean success flag.
If the command fails, success will be false.
The condition !success && successRequired(command) is then evaluated.
The internal function successRequired(command) checks the most significant bit of the command byte. It returns true if the bit is 0 (success is required) and false if the bit is 1 (failure is allowed).
Therefore, the transaction only reverts if the command failed (!success is true) AND its success was mandatory (successRequired(command) is true). If a command marked for allowed failure fails, the successRequired check returns false, the if condition is not met, and the loop continues to the next command.
A control-oriented breakdown to Universal Router Failure Handling - Explore the `execute` function's default atomic behavior for batched commands, and see how the most significant bit is used to create optional actions that can fail without reverting the entire transaction.
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