Package: uniswap-universal-router-decoder
Uniswap Universal Router Decoder & EncoderProject InformationCode QualityRelease Notesv1.1.0Add support for the TRANSFER functionAdd support for decoding the "revert on fail" flag and prepare for encoding on UR functions that support it.Add support for encoding theexecute()function without deadlinev1.0.1Fix issue #35 - fails to decode input data when there is too many commandsv1.0.0Add support for SWEEP and PAY_PORTIONFix decoding issuesRemove useless parameterpayer_is_senderfromv*_swap_exact_in_from_balance()methodsUpdate Router ABIAdd uint48 and uint160 in ABI builderOverview and Points of AttentionThe object of this library is to decode & encode the transaction input sent to the Uniswap universal router (UR)
(address0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FADon Ethereum Mainnet). It is based on, and is intended to be used withweb3.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 IdFunction NameDecodeEncode0x00V3_SWAP_EXACT_IN✅✅0x01V3_SWAP_EXACT_OUT✅✅0x02 - 0x03❌❌0x04SWEEP✅✅0x05TRANSFER✅✅0x06PAY_PORTION✅✅0x07placeholderN/AN/A0x08V2_SWAP_EXACT_IN✅✅0x09V2_SWAP_EXACT_OUT✅✅0x0aPERMIT2_PERMIT✅✅0x0bWRAP_ETH✅✅0x0cUNWRAP_WETH✅✅0x0dPERMIT2_TRANSFER_FROM_BATCH❌❌0x0e - 0x0fplaceholdersN/AN/A0x10 - 0x1d❌❌0x1e - 0x3fplaceholdersN/AN/AInstallationA good practice is to usePython virtual environments, here is atutorial.The library can be pip installed frompypi.orgas usual:# update pip to latest version if neededpipinstall-Upip# install the decoder from pypi.orgpipinstalluniswap-universal-router-decoderUsageThe library exposes a class,RouterCodecwith several public methods that can be used to decode or encode UR data.How to decode a transaction inputTo decode a transaction input, use thedecode.function_input()method as follows:fromuniswap_universal_router_decoderimportRouterCodectrx_input="0x3593564c000000000000000000 ... 90095b5c4e9f5845bba"# the trx input to decodecodec=RouterCodec()decoded_trx_input=codec.decode.function_input(trx_input)Example of decoded input returned bydecode.function_input():(<Functionexecute(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(<FunctionWRAP_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},{'revert_on_fail':True# flag indicating if the transaction must revert when this command fails},),(<FunctionV3_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},{'revert_on_fail':True# flag indicating if the transaction must revert when this command fails},)],'deadline':1678441619# The deadline after which the transaction is not valid any more.})How to decode a transactionIt's also possible to decode the whole transaction, given its hash
and providing the codec has been built with either a validWeb3instance or the link to a rpc endpoint:# Using a web3 instancefromweb3importWeb3fromuniswap_universal_router_decoderimportRouterCodecw3=Web3(...)# your web3 instancecodec=RouterCodec(w3=w3)# Using a rpc endpointfromweb3importWeb3fromuniswap_universal_router_decoderimportRouterCodecrpc_link="https://..."# your rpc endpointcodec=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 pathTheRouterCodecclass exposes also the methoddecode.v3_path()which can be used to decode a given Uniswap V3 path.fromuniswap_universal_router_decoderimportRouterCodecuniswap_v3_path=b"\xc0*\xaa9\xb2#\xfe\x8d\n\x0e...\xd7\x89"# bytes or str hexfn_name="V3_SWAP_EXACT_IN"# Or V3_SWAP_EXACT_OUTcodec=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 encodeThe 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 theencode.chain()method and ends with thebuild()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 methodsget_default_deadline()andget_default_expiration()respectively.fromuniswap_universal_router_decoderimportRouterCodecdefault_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_ETHThis function can be used to convert eth to weth using the UR.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=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 URHow to encode a call to the function V2_SWAP_EXACT_INThis function can be used to swap tokens on a V2 pool. Correct allowances must have been set before sending such transaction.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=RouterCodec()encoded_data=codec.encode.chain().v2_swap_exact_in(FunctionRecipient.SENDER,# the output tokens are sent to the transaction senderamount_in,# in Weimin_amount_out,# in Wei[in_token_address,# checksum address of the token sent to the URout_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 URFor more details, see thistutorialHow to encode a call to the function V2_SWAP_EXACT_OUTThis function can be used to swap tokens on a V2 pool. Correct allowances must have been set before sending such transaction.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=RouterCodec()encoded_data=codec.encode.chain().v2_swap_exact_out(FunctionRecipient.SENDER,amount_out,# in Weimax_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 URHow to encode a call to the function V3_SWAP_EXACT_INThis function can be used to swap tokens on a V3 pool. Correct allowances must have been set before using sending such transaction.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=RouterCodec()encoded_data=codec.encode.chain().v3_swap_exact_in(FunctionRecipient.SENDER,amount_in,# in Weimin_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 URHow to encode a call to the function V3_SWAP_EXACT_OUTThis function can be used to swap tokens on a V3 pool. Correct allowances must have been set before sending such transaction.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=RouterCodec()encoded_data=codec.encode.chain().v3_swap_exact_out(FunctionRecipient.SENDER,amount_out,# in Weimax_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 URHow to encode a call to the function PERMIT2_PERMITThis 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 thistutorialfromuniswap_universal_router_decoderimportRouterCodeccodec=RouterCodec()data,signable_message=codec.create_permit2_signable_message(token_address,amount,# max = 2**160 - 1expiration,nonce,# Permit2 noncespender,# The UR checksum addressdeadline,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 URAfter 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 transactionDon't forget to give a token allowance to the Permit2 contract as well.fromuniswap_universal_router_decoderimportFunctionRecipient,RouterCodeccodec=RouterCodec()# Permit signaturedata,signable_message=codec.create_permit2_signable_message(token_address,amount,# max = 2**160 - 1expiration,nonce,# Permit2 noncespender,# The UR checksum addressdeadline,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 encodingpath=[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 UROther chainable functions(See integration tests for full examples)PAY_PORTIONExample where a recipient is paid 1% of the USDC amount:.pay_portion(FunctionRecipient.CUSTOM,usdc_address,100,recipient_address)SWEEPExample where the sender gets back all remaining USDC:.sweep(FunctionRecipient.SENDER,usdc_address,0)TRANSFERExample where an USDC amount is sent to a recipient:.transfer(FunctionRecipient.CUSTOM,usdc_address,usdc_amount,recipient_address)Tutorials and Recipes:See theSDK Wiki.