Skip to main content

Decode & Encode transaction data sent to Uniswap Universal Router

Project description

Uniswap Universal Router Decoder & Encoder v0.9.0


Project Information

Tests & Lint PyPI - Python Version GitHub release (latest by date) PyPi Repository GitHub

Code Quality

CodeQL Test Coverage Imports: isort Type Checker: mypy Linter: flake8


Release Notes

V0.9.0

  • Add support for UNWRAP_WETH encoding
  • Add v2_swap_exact_in_from_balance() and v3_swap_exact_in_from_balance(): 2 convenient methods which are used when the exact in_amount is not known when the transaction is built, typically chained after a V*_SWAP_EXACT_IN.

V0.8.0

  • Breaking changes because of refactoring
  • Command chaining extension: all supported UR functions can now be chained in a single transaction

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

⚠ This library has not been audited, so use at your own risk !

⚠ 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 - 0x06
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:

# update pip to latest version if needed
pip install -U pip

# install the decoder from pypi.org
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"  # the trx input to decode
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)>,  # the UR function that executes all commands
    {
        'commands': b'\x0b\x00',  # the list of commands sent to the UR
        'inputs': [  # the inputs used for each command
            (
                <Function WRAP_ETH(address,uint256)>,  # the function corresponding to the first command
                {                                      # and its parameters
                    'recipient': '0x0000000000000000000000000000000000000002',  # code indicating the recipient of this command is the router
                    'amountMin': 4500000000000000000  # the amount in WEI to wrap
                }
            ),
            (
                <Function V3_SWAP_EXACT_IN(address,uint256,uint256,bytes,bool)>,  # the function corresponding to the second command
                {                                                                 # and its parameters
                    'recipient': '0x0000000000000000000000000000000000000001',  # code indicating the sender will receive the output of this command
                    'amountIn': 4500000000000000000,  # the exact amount sent
                    'amountOutMin': 6291988002,  # the min amount expected of the bought token for the swap to be executed 
                    'path': b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e\\O'\xea\xd9\x08<ul\xc2"  # the V3 path (tokens + pool fees)
                           b'\x00\x01\xf4\xa0\xb8i\x91\xc6!\x8b6\xc1\xd1\x9dJ.'  # can be decoded with the method decode.v3_path()
                           b'\x9e\xb0\xce6\x06\xebH',
                    'payerIsSender': False  # a bool indicating if the input tokens come from the sender or are already in the UR
                }
            )
        ],
        'deadline': 1678441619  # The deadline after which the transaction is not valid any more.
    }
)

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:

# Using a web3 instance
from web3 import Web3
from uniswap_universal_router_decoder import RouterCodec
w3 = Web3(...)  # your web3 instance
codec = RouterCodec(w3=w3)
# Using a rpc endpoint
from web3 import Web3
from uniswap_universal_router_decoder import RouterCodec
rpc_link = "https://..."  # your rpc endpoint
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"  # bytes or str hex
fn_name = "V3_SWAP_EXACT_IN"  # Or V3_SWAP_EXACT_OUT
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 UR 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 return the full encoded data to be included in the transaction. Below some examples of encoded input 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)  # to convert amount_in_wei eth to weth, and send them to the transaction sender.

# then in your transaction dict:
transaction["data"] = encoded_data

# you can now sign and send the transaction to the UR

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 using sending such transaction.

from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec

codec = RouterCodec()
encoded_data = codec.encode.chain().v2_swap_exact_in(
        FunctionRecipient.SENDER,  # the output tokens are sent to the transaction sender
        amount_in,  # in Wei
        min_amount_out,  # in Wei
        [
            in_token_address,  # checksum address of the token sent to the UR 
            out_token_address,  # checksum address of the received token
        ],
    ).build(timestamp)  # unix timestamp after which the trx will not be valid any more

# then in your transaction dict:
transaction["data"] = encoded_data

# you can now sign and send the transaction to the UR

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 using 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,  # in Wei
        max_amount_in,  # in Wei
        [
            in_token_address,
            out_token_address,
        ],
    ).build(timestamp)  # unix timestamp after which the trx will not be valid any more

# then in your transaction dict:
transaction["data"] = encoded_data
# you can now sign and send the transaction to the UR

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,  # in Wei
        min_amount_out,  # in Wei
        [
            in_token_address,
            pool_fee,
            out_token_address,
        ],
    ).build(timestamp)  # unix timestamp after which the trx will not be valid any more

# then in your transaction dict:
transaction["data"] = encoded_data

# you can now sign and send the transaction to the UR

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 using 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,  # in Wei
        max_amount_in,  # in Wei
        [
            in_token_address,
            pool_fee,
            out_token_address,
        ],
    ).build(timestamp)  # unix timestamp after which the trx will not be valid any more

# then in your transaction dict:
transaction["data"] = encoded_data
# you can now sign and send the transaction to the UR

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.

from uniswap_universal_router_decoder import RouterCodec

codec = RouterCodec()
data, signable_message = codec.create_permit2_signable_message(
    token_address,
    amount,  # max = 2**160 - 1
    expiration,
    nonce,  # Permit2 nonce
    spender,  # UR
    deadline,
    1,  # chain id
)

# Then you need to sign the message:
signed_message = acc.sign_message(signable_message)  # where acc is your LocalAccount

# And now you can encode the data:
encoded_data = codec.encode.chain().permit2_permit(data, signed_message).build(deadline)

# Then in your transaction dict:
transaction["data"] = encoded_data

# you can now sign and send the transaction to the UR

After that, you can swap tokens using the Uniswap universal router.

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()

# Permit signature
data, signable_message = codec.create_permit2_signable_message(
    token_address,
    amount,  # max = 2**160 - 1
    expiration,
    nonce,  # Permit2 nonce
    spender,  # UR
    deadline,
    1,  # chain id
)

# Then you need to sign the message:
signed_message = acc.sign_message(signable_message)  # where acc is your LocalAccount

# Permit + v2 swap encoding
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)
)

# Then in your transaction dict:
transaction["data"] = encoded_data

# you can now sign and send the transaction to the UR

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

uniswap-universal-router-decoder-0.9.0.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

File details

Details for the file uniswap-universal-router-decoder-0.9.0.tar.gz.

File metadata

File hashes

Hashes for uniswap-universal-router-decoder-0.9.0.tar.gz
Algorithm Hash digest
SHA256 7b3634a572c85b5102c94c2fcc6b7b310252762ba56d37ead7241a3f64f92b85
MD5 3ec781f91882a7980004cc2378f458ad
BLAKE2b-256 8b40aab2762371c12adec3a0bfaa8a3ae9d4d6fe71fd2651cd27debee1bdd56b

See more details on using hashes here.

File details

Details for the file uniswap_universal_router_decoder-0.9.0-py3-none-any.whl.

File metadata

File hashes

Hashes for uniswap_universal_router_decoder-0.9.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ab1f308448e49040fdc216eedc9017426a9b34005d3152c4845fe975828bece1
MD5 c82ca1bd1343ed4d26dc447812028430
BLAKE2b-256 b9f6151e18e47f748d21f3c875d4a258f93523dd8cbf404e5bf237495d460ed1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page