-
Notifications
You must be signed in to change notification settings - Fork 19
/
universal_router_swap.py
130 lines (113 loc) · 10.2 KB
/
universal_router_swap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# uni's universal router is similar to sushi's route processor, if not a little simpler so can be worth looking at the walk through of those transactions
# as it covers the same considerations and steps in trying to understand the universal router.
from net import con; w3 = con('POLYGON') # i.e from web3 import Web3; w3 = Web3(Provider(Endpoint))
from os import getenv
from dotenv import load_dotenv
load_dotenv()
KEY = getenv('your_key')
EOA = getenv('your_address')
#----------------------
#---
USDC_ADDRESS = '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174'
WMATIC_ADDRESS = '0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270'
WETH_ADDRESS= '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619'
WMATIC_USDC_ADDRESS = '0xcd353F79d9FADe311fC3119B841e1f456b54e858'
USDC_WETH_ADDRESS = '0x34965ba0ac2451A34a0471F04CCa3F990b8dea27'
ROUTER_ADDRESS = '0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD'
ROUTER_ABI = '''
[{"inputs":[{"components":[{"internalType":"address","name":"permit2","type":"address"},{"internalType":"address","name":"weth9","type":"address"},{"internalType":"address","name":"seaportV1_5","type":"address"},{"internalType":"address","name":"seaportV1_4","type":"address"},{"internalType":"address","name":"openseaConduit","type":"address"},{"internalType":"address","name":"nftxZap","type":"address"},{"internalType":"address","name":"x2y2","type":"address"},{"internalType":"address","name":"foundation","type":"address"},{"internalType":"address","name":"sudoswap","type":"address"},{"internalType":"address","name":"elementMarket","type":"address"},{"internalType":"address","name":"nft20Zap","type":"address"},{"internalType":"address","name":"cryptopunks","type":"address"},{"internalType":"address","name":"looksRareV2","type":"address"},{"internalType":"address","name":"routerRewardsDistributor","type":"address"},{"internalType":"address","name":"looksRareRewardsDistributor","type":"address"},{"internalType":"address","name":"looksRareToken","type":"address"},{"internalType":"address","name":"v2Factory","type":"address"},{"internalType":"address","name":"v3Factory","type":"address"},{"internalType":"bytes32","name":"pairInitCodeHash","type":"bytes32"},{"internalType":"bytes32","name":"poolInitCodeHash","type":"bytes32"}],"internalType":"struct RouterParameters","name":"params","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BalanceTooLow","type":"error"},{"inputs":[],"name":"BuyPunkFailed","type":"error"},{"inputs":[],"name":"ContractLocked","type":"error"},{"inputs":[],"name":"ETHNotAccepted","type":"error"},{"inputs":[{"internalType":"uint256","name":"commandIndex","type":"uint256"},{"internalType":"bytes","name":"message","type":"bytes"}],"name":"ExecutionFailed","type":"error"},{"inputs":[],"name":"FromAddressIsNotOwner","type":"error"},{"inputs":[],"name":"InsufficientETH","type":"error"},{"inputs":[],"name":"InsufficientToken","type":"error"},{"inputs":[],"name":"InvalidBips","type":"error"},{"inputs":[{"internalType":"uint256","name":"commandType","type":"uint256"}],"name":"InvalidCommandType","type":"error"},{"inputs":[],"name":"InvalidOwnerERC1155","type":"error"},{"inputs":[],"name":"InvalidOwnerERC721","type":"error"},{"inputs":[],"name":"InvalidPath","type":"error"},{"inputs":[],"name":"InvalidReserves","type":"error"},{"inputs":[],"name":"InvalidSpender","type":"error"},{"inputs":[],"name":"LengthMismatch","type":"error"},{"inputs":[],"name":"SliceOutOfBounds","type":"error"},{"inputs":[],"name":"TransactionDeadlinePassed","type":"error"},{"inputs":[],"name":"UnableToClaim","type":"error"},{"inputs":[],"name":"UnsafeCast","type":"error"},{"inputs":[],"name":"V2InvalidPath","type":"error"},{"inputs":[],"name":"V2TooLittleReceived","type":"error"},{"inputs":[],"name":"V2TooMuchRequested","type":"error"},{"inputs":[],"name":"V3InvalidAmountOut","type":"error"},{"inputs":[],"name":"V3InvalidCaller","type":"error"},{"inputs":[],"name":"V3InvalidSwap","type":"error"},{"inputs":[],"name":"V3TooLittleReceived","type":"error"},{"inputs":[],"name":"V3TooMuchRequested","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"RewardsSent","type":"event"},{"inputs":[{"internalType":"bytes","name":"looksRareClaim","type":"bytes"}],"name":"collectRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"commands","type":"bytes"},{"internalType":"bytes[]","name":"inputs","type":"bytes[]"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"commands","type":"bytes"},{"internalType":"bytes[]","name":"inputs","type":"bytes[]"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"execute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"uint256[]","name":"","type":"uint256[]"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155BatchReceived","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC1155Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]
'''
router = w3.eth.contract(address=ROUTER_ADDRESS, abi=ROUTER_ABI)
# https://github.com/Uniswap/universal-router/blob/main/contracts/libraries/Commands.sol
#
# // Command Types where value<0x08, executed in the first nested-if block
# uint256 constant V3_SWAP_EXACT_IN = 0x00;
# uint256 constant V3_SWAP_EXACT_OUT = 0x01;
# uint256 constant PERMIT2_TRANSFER_FROM = 0x02;
# uint256 constant PERMIT2_PERMIT_BATCH = 0x03;
# uint256 constant SWEEP = 0x04;
# uint256 constant TRANSFER = 0x05;
# uint256 constant PAY_PORTION = 0x06;
# // COMMAND_PLACEHOLDER = 0x07;
#
#....
# // Command Types where 0x08<=value<=0x0f, executed in the second nested-if block
# uint256 constant V2_SWAP_EXACT_IN = 0x08;
# uint256 constant V2_SWAP_EXACT_OUT = 0x09;
# uint256 constant PERMIT2_PERMIT = 0x0a;
# uint256 constant WRAP_ETH = 0x0b;
# uint256 constant UNWRAP_WETH = 0x0c;
#
# ----
# we will wrap the native coin, then swap on v3
commands = '0x0b00'
# https://github.com/Uniswap/universal-router/blob/main/contracts/base/Dispatcher.sol
#
# if (command == Commands.V3_SWAP_EXACT_IN) {
# // equivalent: abi.decode(inputs, (address, uint256, uint256, bytes, bool))
# address recipient;
# uint256 amountIn;
# uint256 amountOutMin;
# bool payerIsUser
#....
#
# https://github.com/Uniswap/universal-router/blob/228f2d151a5fc99836d72ae00f81db92cdb44bd3/contracts/modules/uniswap/v3/V3SwapRouter.sol
#
# /// @notice Performs a Uniswap v3 exact input swap
# /// @param recipient The recipient of the output tokens
# /// @param amountIn The amount of input tokens for the trade
# /// @param amountOutMinimum The minimum desired amount of output tokens
# /// @param path The path of the trade as a bytes string
# /// @param payer The address that will be paying the input
# function v3SwapExactInput(
# address recipient,
# uint256 amountIn,
# uint256 amountOutMinimum,
# bytes calldata path,
# address payer
from eth_abi import encode
from eth_abi.packed import encode_packed
# some sane inputs (sane doesn't mean safe in this case, always use a good slippage value unless protected some other way)
to = EOA
amount = 1 * 10 ** 17
slippage = 0
FEE = 500
# (address tokenIn, uint24 fee, address tokenOut)
path = encode_packed(['address','uint24','address'], [WMATIC_ADDRESS, FEE, USDC_ADDRESS])
from_eoa = False # the router or user? router after wrapping
wrap_calldata = encode(['address', 'uint256'], [router.address, amount])
v3_calldata = encode(['address', 'uint256', 'uint256', 'bytes', 'bool'], [to, amount, slippage, path, from_eoa])
deadline = 2*10**10
print(commands)
print(wrap_calldata.hex())
print(v3_calldata.hex())
print(deadline)
# --------------------------------------------------------------------------------------------------------------------------------------------------------------------
tx = {
'from': EOA,
'value': amount,
'chainId': 137,
'gas': 250000,
'maxFeePerGas': w3.eth.gas_price * 2,
'maxPriorityFeePerGas': w3.eth.max_priority_fee*2,
'nonce': w3.eth.get_transaction_count(EOA)
}
def sign_tx(tx, key):
sig = w3.eth.account.sign_transaction
signed_tx = sig(tx, private_key=key)
return signed_tx
def send_tx(signed_tx):
w3.eth.send_raw_transaction(signed_tx.rawTransaction)
tx_hash = w3.to_hex(w3.keccak(signed_tx.rawTransaction))
return tx_hash
def main():
swap = router.functions.execute(commands, [wrap_calldata, v3_calldata], deadline).build_transaction(tx)
print(swap)
print('[-] Simulating swap...')
w3.eth.call(swap)
print('[-] Attempting swap...')
tx_hash = send_tx(sign_tx(swap, KEY))
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print (f'[>] Hash of swap: {tx_hash}\n[>] {receipt}')
if __name__ == '__main__':
main()