@@ -182,7 +182,7 @@ def get_price_input(
182
182
token1 :AddressLike ,# output token
183
183
qty :int ,
184
184
fee :int = None ,
185
- route :Optional [ List [AddressLike ] ]= None ,
185
+ route :List [AddressLike ]= None ,
186
186
)-> int :
187
187
"""Given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`."""
188
188
if fee is None :
@@ -203,7 +203,7 @@ def get_price_output(
203
203
token1 :AddressLike ,
204
204
qty :int ,
205
205
fee :int = None ,
206
- route :Optional [ List [AddressLike ] ]= None ,
206
+ route :List [AddressLike ]= None ,
207
207
)-> int :
208
208
"""Returns the minimum amount of `token0` required to buy `qty` amount of `token1`."""
209
209
if fee is None :
@@ -264,7 +264,7 @@ def _get_token_token_input_price(
264
264
token1 :AddressLike ,# output token
265
265
qty :int ,
266
266
fee :int ,
267
- route :Optional [ List [AddressLike ] ]= None ,
267
+ route :List [AddressLike ]= None ,
268
268
)-> int :
269
269
"""
270
270
Public price (i.e. amount of output token received) for token to token trades with an exact input.
@@ -581,68 +581,93 @@ def _token_to_eth_swap_input(
581
581
function = token_funcs .tokenToEthTransferInput (* func_params )
582
582
return self ._build_and_send_tx (function )
583
583
elif self .version == 2 :
584
- if recipient is None :
585
- recipient = self .address
586
- amount_out_min = int (
587
- (1 - slippage )* self ._get_token_eth_input_price (input_token ,qty ,fee )
588
- )
589
- if fee_on_transfer :
590
- func = (
591
- self .router .functions .swapExactTokensForETHSupportingFeeOnTransferTokens
592
- )
593
- else :
594
- func = self .router .functions .swapExactTokensForETH
595
- return self ._build_and_send_tx (
596
- func (
597
- qty ,
598
- amount_out_min ,
599
- [input_token ,self .get_weth_address ()],
600
- recipient ,
601
- self ._deadline (),
602
- ),
584
+ return self ._token_to_eth_swap_input_v2 (
585
+ input_token ,qty ,recipient ,fee ,slippage ,fee_on_transfer
603
586
)
604
587
elif self .version == 3 :
605
- if recipient is None :
606
- recipient = self .address
607
-
608
588
if fee_on_transfer :
609
589
raise Exception ("fee on transfer not supported by Uniswap v3" )
610
590
611
- output_token = self .get_weth_address ()
612
- min_tokens_bought = int (
613
- (1 - slippage )
614
- * self ._get_token_eth_input_price (input_token ,qty ,fee = fee )
591
+ return self ._token_to_eth_swap_input_v3 (
592
+ input_token ,qty ,recipient ,fee ,slippage
615
593
)
616
- sqrtPriceLimitX96 = 0
594
+ else :
595
+ raise ValueError
617
596
618
- swap_data = self .router .encodeABI (
619
- fn_name = "exactInputSingle" ,
620
- args = [
621
- (
622
- input_token ,
623
- output_token ,
624
- fee ,
625
- ETH_ADDRESS ,
626
- self ._deadline (),
627
- qty ,
628
- min_tokens_bought ,
629
- sqrtPriceLimitX96 ,
630
- )
631
- ],
597
+ def _token_to_eth_swap_input_v2 (
598
+ self ,
599
+ input_token :AddressLike ,
600
+ qty :int ,
601
+ recipient :Optional [AddressLike ],
602
+ fee :int ,
603
+ slippage :float ,
604
+ fee_on_transfer :bool ,
605
+ )-> HexBytes :
606
+ if recipient is None :
607
+ recipient = self .address
608
+ amount_out_min = int (
609
+ (1 - slippage )* self ._get_token_eth_input_price (input_token ,qty ,fee )
610
+ )
611
+ if fee_on_transfer :
612
+ func = (
613
+ self .router .functions .swapExactTokensForETHSupportingFeeOnTransferTokens
632
614
)
615
+ else :
616
+ func = self .router .functions .swapExactTokensForETH
617
+ return self ._build_and_send_tx (
618
+ func (
619
+ qty ,
620
+ amount_out_min ,
621
+ [input_token ,self .get_weth_address ()],
622
+ recipient ,
623
+ self ._deadline (),
624
+ ),
625
+ )
633
626
634
- unwrap_data = self .router .encodeABI (
635
- fn_name = "unwrapWETH9" ,args = [min_tokens_bought ,recipient ]
636
- )
627
+ def _token_to_eth_swap_input_v3 (
628
+ self ,
629
+ input_token :AddressLike ,
630
+ qty :int ,
631
+ recipient :Optional [AddressLike ],
632
+ fee :int ,
633
+ slippage :float ,
634
+ )-> HexBytes :
635
+ """NOTE: Should always be called via the dispatcher `_token_to_eth_swap_input`"""
636
+ if recipient is None :
637
+ recipient = self .address
637
638
638
- # Multicall
639
- return self ._build_and_send_tx (
640
- self .router .functions .multicall ([swap_data ,unwrap_data ]),
641
- self ._get_tx_params (),
642
- )
639
+ output_token = self .get_weth_address ()
640
+ min_tokens_bought = int (
641
+ (1 - slippage )* self ._get_token_eth_input_price (input_token ,qty ,fee = fee )
642
+ )
643
+ sqrtPriceLimitX96 = 0
644
+
645
+ swap_data = self .router .encodeABI (
646
+ fn_name = "exactInputSingle" ,
647
+ args = [
648
+ (
649
+ input_token ,
650
+ output_token ,
651
+ fee ,
652
+ ETH_ADDRESS ,
653
+ self ._deadline (),
654
+ qty ,
655
+ min_tokens_bought ,
656
+ sqrtPriceLimitX96 ,
657
+ )
658
+ ],
659
+ )
643
660
644
- else :
645
- raise ValueError
661
+ # NOTE: This will probably lead to dust WETH accumulation
662
+ unwrap_data = self .router .encodeABI (
663
+ fn_name = "unwrapWETH9" ,args = [min_tokens_bought ,recipient ]
664
+ )
665
+
666
+ # Multicall
667
+ return self ._build_and_send_tx (
668
+ self .router .functions .multicall ([swap_data ,unwrap_data ]),
669
+ self ._get_tx_params (),
670
+ )
646
671
647
672
def _token_to_token_swap_input (
648
673
self ,
@@ -1111,13 +1136,17 @@ def _build_and_send_tx(
1111
1136
# `use_estimate_gas` needs to be True for networks like Arbitrum (can't assume 250000 gas),
1112
1137
# but it breaks tests for unknown reasons because estimateGas takes forever on some tx's.
1113
1138
# Maybe an issue with ganache? (got GC warnings once...)
1139
+
1140
+ # In case gas estimation is disabled.
1141
+ # Without this set before gas estimation, it can lead to ganache stack overflow.
1142
+ # See: https://github.com/trufflesuite/ganache/issues/985#issuecomment-998937085
1143
+ transaction ["gas" ]= Wei (250000 )
1144
+
1114
1145
if self .use_estimate_gas :
1115
1146
# The Uniswap V3 UI uses 20% margin for transactions
1116
1147
transaction ["gas" ]= Wei (
1117
1148
int (self .w3 .eth .estimate_gas (transaction )* 1.2 )
1118
1149
)
1119
- else :
1120
- transaction ["gas" ]= Wei (250000 )
1121
1150
1122
1151
signed_txn = self .w3 .eth .account .sign_transaction (
1123
1152
transaction ,private_key = self .private_key
@@ -1225,11 +1254,11 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token
1225
1254
raise InvalidToken (address )
1226
1255
try :
1227
1256
name = _name .decode ()
1228
- except :
1257
+ except Exception : # FIXME: Be more precise about exception to catch
1229
1258
name = _name
1230
1259
try :
1231
1260
symbol = _symbol .decode ()
1232
- except :
1261
+ except Exception : # FIXME: Be more precise about exception to catch
1233
1262
symbol = _symbol
1234
1263
return ERC20Token (symbol ,address ,name ,decimals )
1235
1264
@@ -1256,11 +1285,11 @@ def get_raw_price(
1256
1285
if token_out == ETH_ADDRESS :
1257
1286
token_out = self .get_weth_address ()
1258
1287
1288
+ params :Tuple [ChecksumAddress ,ChecksumAddress ]= (
1289
+ self .w3 .toChecksumAddress (token_in ),
1290
+ self .w3 .toChecksumAddress (token_out ),
1291
+ )
1259
1292
if self .version == 2 :
1260
- params :Iterable [Union [ChecksumAddress ,Optional [int ]]]= [
1261
- self .w3 .toChecksumAddress (token_in ),
1262
- self .w3 .toChecksumAddress (token_out ),
1263
- ]
1264
1293
pair_token = self .factory_contract .functions .getPair (* params ).call ()
1265
1294
token_in_erc20 = _load_contract_erc20 (
1266
1295
self .w3 ,self .w3 .toChecksumAddress (token_in )
@@ -1286,12 +1315,7 @@ def get_raw_price(
1286
1315
1287
1316
raw_price = token_out_balance / token_in_balance
1288
1317
else :
1289
- params = [
1290
- self .w3 .toChecksumAddress (token_in ),
1291
- self .w3 .toChecksumAddress (token_out ),
1292
- fee ,
1293
- ]
1294
- pool_address = self .factory_contract .functions .getPool (* params ).call ()
1318
+ pool_address = self .factory_contract .functions .getPool (* params ,fee ).call ()
1295
1319
pool_contract = _load_contract (
1296
1320
self .w3 ,abi_name = "uniswap-v3/pool" ,address = pool_address
1297
1321
)
@@ -1317,7 +1341,7 @@ def estimate_price_impact(
1317
1341
token_out :AddressLike ,
1318
1342
amount_in :int ,
1319
1343
fee :int = None ,
1320
- route :Optional [ List [AddressLike ] ]= None ,
1344
+ route :List [AddressLike ]= None ,
1321
1345
)-> float :
1322
1346
"""
1323
1347
Returns the estimated price impact as a positive float (0.01 = 1%).