Uniswap Universal Router Decoder & Encoder
Many thanks to everyone who has ☕️ offered some coffees! ☕️
or ⭐ starred this project! ⭐
It is greatly appreciated! :)
Project Information
Code Quality
Release Notes
v1.2.1
- Add support for web3 v7
- Add support for Python 3.12 & 3.13
v1.2.0
- Add
compute_gas_fees()
: utility function to compute gas fees - Add
build_transaction()
method: It's now possible to build the full transaction i/o just the input data. - Add
fetch_permit2_allowance()
: Easy way to check the current Permit2 allowed amount, expiration and nonce. - Make verifying contract (Permit2) configurable (Thanks to @speedssr and @freereaper)
- Replace deprecated
eth_account.encode_structured_data()
with eth_account.messages.encode_typed_data()
v1.1.0
- Add support for the TRANSFER function
- Add support for decoding the "revert on fail" flag and prepare for encoding on UR functions that support it.
- Add support for encoding the
execute()
function without deadline
Overview and Points of Attention
The object of this library is to decode & encode the transaction input sent to the Uniswap universal router (UR)
(address 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD
on Ethereum Mainnet). It is based on, and is intended to be used with web3.py
The target audience is Python developers who are familiar with the Ethereum blockchain concepts and web3.py, and how DEXes work.
⚠ This library has not been audited, so use at your own risk !
⚠ Before using this library, ensure you are familiar with general blockchain concepts and web3.py in particular.
⚠ This project is a work in progress so not all commands are decoded yet. Below the list of the already implemented ones.
Command Id | Function Name | Decode | Encode |
---|
0x00 | V3_SWAP_EXACT_IN | ✅ | ✅ |
0x01 | V3_SWAP_EXACT_OUT | ✅ | ✅ |
0x02 - 0x03 | | ❌ | ❌ |
0x04 | SWEEP | ✅ | ✅ |
0x05 | TRANSFER | ✅ | ✅ |
0x06 | PAY_PORTION | ✅ | ✅ |
0x07 | placeholder | N/A | N/A |
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 | placeholders | N/A | N/A |
0x10 - 0x1d | | ❌ | ❌ |
0x1e - 0x3f | placeholders | N/A | N/A |
Installation
A good practice is to use Python virtual environments, here is a tutorial.
The library can be pip installed from pypi.org as usual:
pip install -U pip
pip install uniswap-universal-router-decoder
Usage
The library exposes a class, RouterCodec
with several public methods that can be used to decode or encode UR data.
How to decode a transaction input
To decode a transaction input, use the decode.function_input()
method as follows:
from uniswap_universal_router_decoder import RouterCodec
trx_input = "0x3593564c000000000000000000 ... 90095b5c4e9f5845bba"
codec = RouterCodec()
decoded_trx_input = codec.decode.function_input(trx_input)
Example of decoded input returned by decode.function_input()
:
(
<Function execute(bytes,bytes[],uint256)>,
{
'commands': b'\x0b\x00',
'inputs': [
(
<Function WRAP_ETH(address,uint256)>,
{
'recipient': '0x0000000000000000000000000000000000000002',
'amountMin': 4500000000000000000
},
{
'revert_on_fail': True
},
),
(
<Function V3_SWAP_EXACT_IN(address,uint256,uint256,bytes,bool)>,
{
'recipient': '0x0000000000000000000000000000000000000001',
'amountIn': 4500000000000000000,
'amountOutMin': 6291988002,
'path': b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2"
b'\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.'
b'\x9e\xb0\xce6\x06\xebH',
'payerIsSender': False
},
{
'revert_on_fail': True
},
)
],
'deadline': 1678441619
}
)
How to decode a transaction
It's also possible to decode the whole transaction, given its hash
and providing the codec has been built with either a valid Web3
instance or the link to a rpc endpoint:
from web3 import Web3
from uniswap_universal_router_decoder import RouterCodec
w3 = Web3(...)
codec = RouterCodec(w3=w3)
from web3 import Web3
from uniswap_universal_router_decoder import RouterCodec
rpc_link = "https://..."
codec = RouterCodec(rpc_endpoint=rpc_link)
And then the decoder will get the transaction from the blockchain and decode it, along with its input data:
trx_hash = "0x52e63b7 ... 11b979dd9"
decoded_transaction = codec.decode.transaction(trx_hash)
How to decode an Uniswap V3 swap path
The RouterCodec
class exposes also the method decode.v3_path()
which can be used to decode a given Uniswap V3 path.
from uniswap_universal_router_decoder import RouterCodec
uniswap_v3_path = b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e ... \xd7\x89"
fn_name = "V3_SWAP_EXACT_IN"
codec = RouterCodec()
decoded_path = codec.decode.v3_path(fn_name, uniswap_v3_path)
The result is a tuple, starting with the "in-token" and ending with the "out-token", with the pool fees between each pair.
How to encode
The Universal Router allows the chaining of several functions in the same transaction.
This codec supports it (at least for supported functions) and exposes public methods that can be chained.
The chaining starts with the encode.chain()
method and ends with the build()
one which returns the full encoded data to be included in the transaction.
Below some examples of encoded data for one function and one example for 2 functions.
Default values for deadlines and expirations can be computed with the static methods get_default_deadline()
and get_default_expiration()
respectively.
from uniswap_universal_router_decoder import RouterCodec
default_deadline = RouterCodec.get_default_deadline()
default_expiration = RouterCodec.get_default_expiration()
These 2 functions accept a custom duration in seconds as argument.
How to encode a call to the function WRAP_ETH
This function can be used to convert eth to weth using the UR.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
encoded_data = codec.encode.chain().wrap_eth(FunctionRecipient.SENDER, amount_in_wei).build(1676825611)
transaction["data"] = encoded_data
How to encode a call to the function V2_SWAP_EXACT_IN
This function can be used to swap tokens on a V2 pool. Correct allowances must have been set before sending such transaction.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
encoded_data = codec.encode.chain().v2_swap_exact_in(
FunctionRecipient.SENDER,
amount_in,
min_amount_out,
[
in_token_address,
out_token_address,
],
).build(timestamp)
transaction["data"] = encoded_data
For more details, see this tutorial
How to encode a call to the function V2_SWAP_EXACT_OUT
This function can be used to swap tokens on a V2 pool. Correct allowances must have been set before sending such transaction.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
encoded_data = codec.encode.chain().v2_swap_exact_out(
FunctionRecipient.SENDER,
amount_out,
max_amount_in,
[
in_token_address,
out_token_address,
],
).build(timestamp)
transaction["data"] = encoded_data
How to encode a call to the function V3_SWAP_EXACT_IN
This function can be used to swap tokens on a V3 pool. Correct allowances must have been set before using sending such transaction.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
encoded_data = codec.encode.chain().v3_swap_exact_in(
FunctionRecipient.SENDER,
amount_in,
min_amount_out,
[
in_token_address,
pool_fee,
out_token_address,
],
).build(timestamp)
transaction["data"] = encoded_data
How to encode a call to the function V3_SWAP_EXACT_OUT
This function can be used to swap tokens on a V3 pool. Correct allowances must have been set before sending such transaction.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
encoded_data = codec.encode.chain().v3_swap_exact_out(
FunctionRecipient.SENDER,
amount_out,
max_amount_in,
[
in_token_address,
pool_fee,
out_token_address,
],
).build(timestamp)
transaction["data"] = encoded_data
How to encode a call to the function PERMIT2_PERMIT
This function is used to give an allowance to the universal router thanks to the Permit2 contract (0x000000000022D473030F116dDEE9F6B43aC78BA3
).
It is also necessary to approve the Permit2 contract using the token approve function.
See this tutorial
from uniswap_universal_router_decoder import RouterCodec
codec = RouterCodec()
data, signable_message = codec.create_permit2_signable_message(
token_address,
amount,
expiration,
nonce,
spender,
deadline,
1,
)
signed_message = acc.sign_message(signable_message)
encoded_data = codec.encode.chain().permit2_permit(data, signed_message).build(deadline)
transaction["data"] = encoded_data
After that, you can swap tokens using the Uniswap universal router.
How to get the current permit2 allowance, expiration and nonce
You can get the nonce you need to build the permit2 signable message like this:
amount, expiration, nonce = codec.fetch_permit2_allowance(acc.address, token_address)
How to chain a call to PERMIT2_PERMIT and V2_SWAP_EXACT_IN in the same transaction
Don't forget to give a token allowance to the Permit2 contract as well.
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
data, signable_message = codec.create_permit2_signable_message(
token_address,
amount,
expiration,
nonce,
spender,
deadline,
1,
)
signed_message = acc.sign_message(signable_message)
path = [token_in_address, token_out_address]
encoded_data = (
codec
.encode
.chain()
.permit2_permit(data, signed_message)
.v2_swap_exact_in(FunctionRecipient.SENDER, Wei(10**18), Wei(0), path)
.build(deadline)
)
transaction["data"] = encoded_data
Other chainable functions
(See integration tests for full examples)
PAY_PORTION
Example where a recipient is paid 1% of the USDC amount:
.pay_portion(FunctionRecipient.CUSTOM, usdc_address, 100, recipient_address)
SWEEP
Example where the sender gets back all remaining USDC:
.sweep(FunctionRecipient.SENDER, usdc_address, 0)
TRANSFER
Example where an USDC amount is sent to a recipient:
.transfer(FunctionRecipient.CUSTOM, usdc_address, usdc_amount, recipient_address)
How to build directly a transaction
The SDK provides a handy method to build very easily the full transaction in addition to the input data.
It can compute most of the transaction parameters (if the codec has been instantiated with a valid w3 or rpc url)
or you can provide them.
Example where a swap is encoded and a transaction is built automatically:
from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
codec = RouterCodec()
trx_params = (
codec.encode.chain().v2_swap_exact_in(
FunctionRecipient.SENDER,
amount_in,
min_amount_out,
[
in_token_address,
out_token_address,
],
).build_transaction(
sender_address,
deadline=timestamp,
)
)
And that's it! You can now sign and send this transaction.
A few other important parameters:
value
: the quantity of ETH in wei you send to the Universal Router (for ex when you wrap them before a swap)trx_speed
: an enum which influences the transaction rank in the block. Values are:
TransactionSpeed.SLOW
TransactionSpeed.AVERAGE
TransactionSpeed.FAST
(default)TransactionSpeed.FASTER
max_fee_per_gas_limit
: if the computed max_fee_per_gas
is greater than max_fee_per_gas_limit
a ValueError
will be raised to allow you to stay in control. Default is 100 gwei.
How to use the trx_speed
parameter:
from uniswap_universal_router_decoder import TransactionSpeed
.build_transaction(sender_address, trx_speed=TransactionSpeed.FASTER)
Utility functions
How to compute the gas fees
The SDK provides a handy method to compute the current "priority fee" and "max fee per gas":
from uniswap_universal_router_decoder.utils import compute_gas_fees
priority_fee, max_fee_per_gas = compute_gas_fees(w3)
Tutorials and Recipes:
See the SDK Wiki.
News and Q&A:
See the Discussions and 𝕏