The Uniswap Universal Router was adapted to work with the Velodrome ecosystem. This router allows a single swap to be routed through both Velodrome Finance V2 pools and Slipstream (CL) pools.
To see the commit of the smart contracts that was used in the latest deployment, see branch deployed-commit
. To see the addresses of this latest deployment on each network, see folder deploy-addresses
.
The Universal Router is a ERC20 and NFT swap router that allows users greater flexibility when performing trades across multiple token types.
Our flexible command style allows us to provide users with:
- Splitting and interleaving of Uniswap trades
- Purchases of NFTs across 8 marketplaces
- Partial fills of trades
- Wrapping and Unwrapping of ETH
- Time-bound, signature controlled token approvals using Permit2
Transactions are encoded using a string of commands, allowing users to have maximum flexibility over what they want to perform. With all of these features available in a single transaction, the possibilities available to users are endless
The Universal Router codebase consists of the UniversalRouter
contract, and all of its dependencies. The purpose of the UniversalRouter
is to allow users to unify swaps on both Velodrome V2 and CL with NFT purchases across 8 marketplaces, in a single transaction. This repository is lightly modified from the
Uniswap UniversalRouter
repository to be compatible with V2 and CL pools.
UniversalRouter
integrates with Permit2, to enable users to have more safety, flexibility, and control over their ERC20 token approvals.
Calls to UniversalRouter.execute
, the entrypoint to the contracts, provide 2 main parameters:
bytes commands
: A bytes string. Each individual byte represents 1 command that the transaction will execute.bytes[] inputs
: An array of bytes strings. Each element in the array is the encoded parameters for a command.
commands[i]
is the command that will use inputs[i]
as its encoded input parameters.
Through function overloading there is also an optional third parameter for the execute
function:
uint256 deadline
: The timestamp deadline by which this transaction must be executed. Transactions executed after this specified deadline will revert.
Each command is a bytes1
containing the following 8 bits:
0 1 2 3 4 5 6 7
┌─┬─┬───────────┐
│f│r| command │
└─┴─┴───────────┘
-
f
is a single bit flag, that signals whether or not the command should be allowed to revert. Iff
isfalse
, and the command reverts, then the entire transaction will revert. Iff
istrue
and the command reverts then the transaction will continue, allowing us to achieve partial fills. If using this flag, be careful to include further commands that will remove any funds that could be left unused in theUniversalRouter
contract. -
r
is one bit of reserved space. This will allow us to increase the space used for commands, or add new flags in future. -
command
is a 6 bit unique identifier for the command that should be carried out. The values of these commands can be found within Commands.sol, or can be viewed in the table below.
┌──────┬───────────────────────────────┐
│ 0x00 │ V3_SWAP_EXACT_IN │
├──────┼───────────────────────────────┤
│ 0x01 │ V3_SWAP_EXACT_OUT │
├──────┼───────────────────────────────┤
│ 0x02 │ PERMIT2_TRANSFER_FROM │
├──────┼───────────────────────────────┤
│ 0x03 │ PERMIT2_PERMIT_BATCH │
├──────┼───────────────────────────────┤
│ 0x04 │ SWEEP │
├──────┼───────────────────────────────┤
│ 0x05 │ TRANSFER │
├──────┼───────────────────────────────┤
│ 0x06 │ PAY_PORTION │
├──────┼───────────────────────────────┤
│ 0x07 │ ------- │
├──────┼───────────────────────────────┤
│ 0x08 │ V2_SWAP_EXACT_IN │
├──────┼───────────────────────────────┤
│ 0x09 │ V2_SWAP_EXACT_OUT │
├──────┼───────────────────────────────┤
│ 0x0a │ PERMIT2_PERMIT │
├──────┼───────────────────────────────┤
│ 0x0b │ WRAP_ETH │
├──────┼───────────────────────────────┤
│ 0x0c │ UNWRAP_WETH │
├──────┼───────────────────────────────┤
│ 0x0d │ PERMIT2_TRANSFER_FROM_BATCH │
├──────┼───────────────────────────────┤
│ 0x0e │ ------- │
├──────┼───────────────────────────────┤
│ 0x0f │ ------- │
├──────┼───────────────────────────────┤
│ 0x10 │ SEAPORT_V1_5 │
├──────┼───────────────────────────────┤
│ 0x11 │ LOOKS_RARE_721 │
├──────┼───────────────────────────────┤
│ 0x12 │ NFTX │
├──────┼───────────────────────────────┤
│ 0x13 │ CRYPTOPUNKS │
├──────┼───────────────────────────────┤
│ 0x14 │ LOOKS_RARE_1155 │
├──────┼───────────────────────────────┤
│ 0x15 │ OWNER_CHECK_721 │
├──────┼───────────────────────────────┤
│ 0x16 │ OWNER_CHECK_1155 │
├──────┼───────────────────────────────┤
│ 0x17 │ SWEEP_ERC721 │
├──────┼───────────────────────────────┤
│ 0x18 │ X2Y2_721 │
├──────┼───────────────────────────────┤
│ 0x19 │ SUDOSWAP │
├──────┼───────────────────────────────┤
│ 0x1a │ NFT20 │
├──────┼───────────────────────────────┤
│ 0x1b │ X2Y2_1155 │
├──────┼───────────────────────────────┤
│ 0x1c │ FOUNDATION │
├──────┼───────────────────────────────┤
│ 0x1d │ SWEEP_ERC1155 │
├──────┼───────────────────────────────┤
│ 0x1e-│ ------- │
│ 0x3f │ │
└──────┴───────────────────────────────┘
Note that some of the commands in the middle of the series are unused. These gaps allowed us to create gas-efficiencies when selecting which command to execute.
Each input bytes string is merely the abi encoding of a set of parameters. Depending on the command chosen, the input bytes string will be different. For example:
The inputs for V3_SWAP_EXACT_IN
is the encoding of 5 parameters:
address
The recipient of the output of the tradeuint256
The amount of input tokens for the tradeuint256
The minimum amount of output tokens the user wantsbytes
The UniswapV3 path you want to trade alongbool
A flag for whether the input funds should come from the caller (through Permit2) or whether the funds are already in the UniversalRouter
Support for V2 pools has been added. For V2_SWAP_EXACT_IN
The following parameters are required:
address
The recipient of the output of the tradeuint256
The amount of input tokens for the tradeamountOutMin
The minimum amount of output tokens the user wantsRoute[]
The route struct representing the V2 pool (address from, address to, bool stable)bool
A flag for whether the input funds should come from the caller
Whereas in contrast CRYPTOPUNKS
has just 3 parameters encoded:
uint256
The ID of the punk you wish to purchaseaddress
The recipient of the punkuint256
The amount of ETH to pay for the punk
Encoding parameters in a bytes string in this way gives us maximum flexiblity to be able to support many commands which require different datatypes in a gas-efficient way.
For a more detailed breakdown of which parameters you should provide for each command take a look at the Dispatcher.dispatch
function, or alternatively at the ABI_DEFINITION
mapping in planner.ts
.
Developer documentation to give a detailed explanation of the inputs for every command will be coming soon!
- Create
.env
file with a complete rpc url
RPC_URL=''
- Run yarn commands to compile and test
yarn install
yarn symlink
yarn compile
yarn test
yarn test:gas
forge install
forge build
forge test
Fill out parameters in script/deployParameters/Deploy<network>.s.sol
forge script --broadcast \
--rpc-url <RPC-URL> \
--private-key <PRIVATE_KEY> \
--sig 'run()' \
script/deployParameters/Deploy<network>.s.sol:Deploy<network>
forge script --broadcast \
--rpc-url <RPC-URL> \
--private-key <PRIVATE-KEY> \
--sig 'run()' \
script/deployParameters/Deploy<network>.s.sol:Deploy<network>
--etherscan-api-key <ETHERSCAN-API-KEY> \
--verify