From b952063092462ea182f7fe88f6ed5eeb88bb1c47 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 27 Jun 2023 22:47:36 +0300 Subject: [PATCH 01/35] v4 support pre-alpha --- .gitignore | 3 + uniswap/__init__.py | 1 + uniswap/uniswap4.py | 362 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 366 insertions(+) create mode 100644 uniswap/uniswap4.py diff --git a/.gitignore b/.gitignore index 71a12ed6..17206bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,6 @@ ENV/ # mkdocs documentation /site +/.vs/slnx.sqlite +/.vs/ProjectSettings.json +/.vs diff --git a/uniswap/__init__.py b/uniswap/__init__.py index 53d0a5bb..a165fc9e 100644 --- a/uniswap/__init__.py +++ b/uniswap/__init__.py @@ -1,3 +1,4 @@ from . import exceptions from .uniswap import Uniswap, _str_to_addr +from .uni4 import Uniswap4 from .cli import main diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py new file mode 100644 index 00000000..32c6bf9b --- /dev/null +++ b/uniswap/uniswap4.py @@ -0,0 +1,362 @@ +import os +import time +import logging +import functools +from typing import List, Any, Optional, Union, Tuple, Dict + +from web3 import Web3 +from web3.eth import Contract +from web3.contract import ContractFunction +from web3.exceptions import BadFunctionCallOutput, ContractLogicError +from web3.types import ( + TxParams, + Wei, + Address, + ChecksumAddress, + Nonce, + HexBytes, +) + +from .types import AddressLike +from .token import ERC20Token +from .tokens import tokens, tokens_rinkeby +from .exceptions import InvalidToken, InsufficientBalance +from .util import ( + _str_to_addr, + _addr_to_str, + _validate_address, + _load_contract, + _load_contract_erc20, + is_same_address, +) +from .decorators import supports, check_approval +from .constants import ( + _netid_to_name, + _poolmanager_contract_addresses, + ETH_ADDRESS, +) + +logger = logging.getLogger(__name__) + + +class Uniswap4: + """ + Wrapper around Uniswap v4 contracts. + """ + + def __init__( + self, + address: Union[AddressLike, str, None], + private_key: Optional[str], + provider: str = None, + web3: Web3 = None, + default_slippage: float = 0.01, + poolmanager_contract_addr: str = None, + ) -> None: + """ + :param address: The public address of the ETH wallet to use. + :param private_key: The private key of the ETH wallet to use. + :param provider: Can be optionally set to a Web3 provider URI. If none set, will fall back to the PROVIDER environment variable, or web3 if set. + :param web3: Can be optionally set to a custom Web3 instance. + :param poolmanager_contract_addr: Can be optionally set to override the address of the PoolManager contract. + """ + self.address: AddressLike = _str_to_addr( + address or "0x0000000000000000000000000000000000000000" + ) + self.private_key = ( + private_key + or "0x0000000000000000000000000000000000000000000000000000000000000000" + ) + + if web3: + self.w3 = web3 + else: + # Initialize web3. Extra provider for testing. + self.provider = provider or os.environ["PROVIDER"] + self.w3 = Web3( + Web3.HTTPProvider(self.provider, request_kwargs={"timeout": 60}) + ) + + netid = int(self.w3.net.version) + if netid in _netid_to_name: + self.network = _netid_to_name[netid] + else: + raise Exception(f"Unknown netid: {netid}") + logger.info(f"Using {self.w3} ('{self.network}')") + + self.last_nonce: Nonce = self.w3.eth.get_transaction_count(self.address) + + if poolmanager_contract_addr is None: + poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] + + self.poolmanager_contract = _load_contract( + self.w3, + abi_name="uniswap-v4/poolmanager", + address=_str_to_addr(poolmanager_contract_addr), + ) + + if hasattr(self, "poolmanager_contract"): + logger.info(f"Using factory contract: {self.poolmanager_contract}") + + # ------ Market -------------------------------------------------------------------- + + def get_price( + self, + token0: AddressLike, # input token + token1: AddressLike, # output token + qty: int, + fee: int, + route: Optional[List[AddressLike]] = None, + zero_to_one: bool = true, + ) -> int: + """ + :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. + :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + """ + + # WIP + + return 0 + + # ------ Make Trade ---------------------------------------------------------------- + def make_trade( + self, + currency0: ERC20Token, + currency1: ERC20Token, + qty: Union[int, Wei], + fee: int, + tick_spacing: int, + sqrt_price_limit_x96: int = 0, + zero_for_one: bool = true, + hooks: AddressLike = ETH, + ) -> HexBytes: + """ + :Swap against the given pool + : + :`currency0`:The lower currency of the pool, sorted numerically + :`currency1`:The higher currency of the pool, sorted numerically + :`fee`: The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. + :`tickSpacing`: Ticks that involve positions must be a multiple of tick spacing + :`hooks`: The hooks of the pool + :if `zero_for_one` is true: make a trade by defining the qty of the input token. + :if `zero_for_one` is false: make a trade by defining the qty of the output token. + """ + if currency0 == currency1: + raise ValueError + + pool_key = { + "currency0": currency0.address, + "currency1": currency1.address, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } + + swap_params = { + "zeroForOne": zero_for_one, + "amountSpecified": qty, + "sqrtPriceLimitX96": sqrt_price_limit_x96, + } + + return self._build_and_send_tx( + self.router.functions.swap( + { + "key": pool_key, + "params": swap_params, + } + ), + self._get_tx_params(value=qty), + ) + + # ------ Wallet balance ------------------------------------------------------------ + def get_eth_balance(self) -> Wei: + """Get the balance of ETH for your address.""" + return self.w3.eth.get_balance(self.address) + + def get_token_balance(self, token: AddressLike) -> int: + """Get the balance of a token for your address.""" + _validate_address(token) + if _addr_to_str(token) == ETH_ADDRESS: + return self.get_eth_balance() + erc20 = _load_contract_erc20(self.w3, token) + balance: int = erc20.functions.balanceOf(self.address).call() + return balance + + # ------ Liquidity ----------------------------------------------------------------- + def initialize( + self, + currency0: ERC20Token, + currency1: ERC20Token, + qty: Union[int, Wei], + fee: int, + tick_spacing: int, + hooks: AddressLike, + sqrt_price_limit_x96: int, + ) -> HexBytes: + """ + :Initialize the state for a given pool ID + : + :`currency0`:The lower currency of the pool, sorted numerically + :`currency1`:The higher currency of the pool, sorted numerically + :`fee`: The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. + :`tickSpacing`: Ticks that involve positions must be a multiple of tick spacing + :`hooks`: The hooks of the pool + """ + if currency0 == currency1: + raise ValueError + + pool_key = { + "currency0": currency0.address, + "currency1": currency1.address, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } + + return self._build_and_send_tx( + self.router.functions.initialize( + { + "key": pool_key, + "sqrtPriceX96": sqrt_price_limit_x96, + } + ), + self._get_tx_params(value=qty), + ) + + def modify_position( + self, + currency0: ERC20Token, + currency1: ERC20Token, + qty: Union[int, Wei], + fee: int, + tick_spacing: int, + tick_upper: int, + tick_lower: int, + hooks: AddressLike, + ) -> HexBytes: + if currency0 == currency1: + raise ValueError + + pool_key = { + "currency0": currency0.address, + "currency1": currency1.address, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } + + modify_position_params = { + "tickLower": tick_lower, + "tickUpper": tick_upper, + "liquidityDelta": qty, + } + + return self._build_and_send_tx( + self.router.functions.modifyPosition( + { + "key": pool_key, + "params": modify_position_params, + } + ), + self._get_tx_params(value=qty), + ) + + # ------ Approval Utils ------------------------------------------------------------ + def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: + """Give an exchange/router max approval of a token.""" + max_approval = self.max_approval_int if not max_approval else max_approval + contract_addr = ( + self._exchange_address_from_token(token) + if self.version == 1 + else self.router_address + ) + function = _load_contract_erc20(self.w3, token).functions.approve( + contract_addr, max_approval + ) + logger.warning(f"Approving {_addr_to_str(token)}...") + tx = self._build_and_send_tx(function) + self.w3.eth.wait_for_transaction_receipt(tx, timeout=6000) + + # Add extra sleep to let tx propogate correctly + time.sleep(1) + + # ------ Tx Utils ------------------------------------------------------------------ + def _deadline(self) -> int: + """Get a predefined deadline. 10min by default (same as the Uniswap SDK).""" + return int(time.time()) + 10 * 60 + + def _build_and_send_tx( + self, function: ContractFunction, tx_params: Optional[TxParams] = None + ) -> HexBytes: + """Build and send a transaction.""" + if not tx_params: + tx_params = self._get_tx_params() + transaction = function.buildTransaction(tx_params) + # Uniswap3 uses 20% margin for transactions + transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) + signed_txn = self.w3.eth.account.sign_transaction( + transaction, private_key=self.private_key + ) + # TODO: This needs to get more complicated if we want to support replacing a transaction + # FIXME: This does not play nice if transactions are sent from other places using the same wallet. + try: + return self.w3.eth.send_raw_transaction(signed_txn.rawTransaction) + finally: + logger.debug(f"nonce: {tx_params['nonce']}") + self.last_nonce = Nonce(tx_params["nonce"] + 1) + + def _get_tx_params(self, value: Wei = Wei(0)) -> TxParams: + """Get generic transaction parameters.""" + return { + "from": _addr_to_str(self.address), + "value": value, + "nonce": max( + self.last_nonce, self.w3.eth.get_transaction_count(self.address) + ), + } + + # ------ Helpers ------------------------------------------------------------ + + def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token: + """ + Retrieves metadata from the ERC20 contract of a given token, like its name, symbol, and decimals. + """ + # FIXME: This function should always return the same output for the same input + # and would therefore benefit from caching + if address == ETH_ADDRESS: + return ERC20Token("ETH", ETH_ADDRESS, "Ether", 18) + token_contract = _load_contract(self.w3, abi_name, address=address) + try: + _name = token_contract.functions.name().call() + _symbol = token_contract.functions.symbol().call() + decimals = token_contract.functions.decimals().call() + except Exception as e: + logger.warning( + f"Exception occurred while trying to get token {_addr_to_str(address)}: {e}" + ) + raise InvalidToken(address) + try: + name = _name.decode() + except: + name = _name + try: + symbol = _symbol.decode() + except: + symbol = _symbol + return ERC20Token(symbol, address, name, decimals) + + # ------ Test utilities ------------------------------------------------------------ + + def _get_token_addresses(self) -> Dict[str, ChecksumAddress]: + """ + Returns a dict with addresses for tokens for the current net. + Used in testing. + """ + netid = int(self.w3.net.version) + netname = _netid_to_name[netid] + if netname == "mainnet": + return tokens + elif netname == "rinkeby": + return tokens_rinkeby + else: + raise Exception(f"Unknown net '{netname}'") From 38e1d027f2f08c0b685449b8013922d618c15266 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 27 Jun 2023 22:49:53 +0300 Subject: [PATCH 02/35] v4 support pre-alpha --- uniswap/constants.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/uniswap/constants.py b/uniswap/constants.py index 12959a72..50678d88 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -70,6 +70,17 @@ "harmony_mainnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", "harmony_testnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", } +# + need to replace with actual addresses +_poolmanager_contract_addresses = { + "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "rinkeby": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "görli": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", + "binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + "binance_testnet": "0x6725F303b657a9451d8BA641348b6761A6CC7a17", +} MAX_UINT_128 = (2**128) - 1 From f64bdee497143d85c81649e2e6c7ca05a54bb4b8 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 27 Jun 2023 22:51:58 +0300 Subject: [PATCH 03/35] v4 support pre-alpha --- uniswap/constants.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uniswap/constants.py b/uniswap/constants.py index 50678d88..a9508827 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -70,8 +70,7 @@ "harmony_mainnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", "harmony_testnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", } -# - need to replace with actual addresses +# need to replace with actual addresses _poolmanager_contract_addresses = { "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", From e59ec7181193bc2d3d2a324068f77a6d85e089e4 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 27 Jun 2023 22:52:42 +0300 Subject: [PATCH 04/35] v4 support pre-alpha --- uniswap/constants.py | 1 + 1 file changed, 1 insertion(+) diff --git a/uniswap/constants.py b/uniswap/constants.py index a9508827..70c6fcec 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -70,6 +70,7 @@ "harmony_mainnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", "harmony_testnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", } + # need to replace with actual addresses _poolmanager_contract_addresses = { "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", From 811541784f8ca47e07c8d4576b74719f88743d94 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 27 Jun 2023 22:55:57 +0300 Subject: [PATCH 05/35] v4 support pre-alpha --- uniswap/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniswap/__init__.py b/uniswap/__init__.py index a165fc9e..4c23bbb3 100644 --- a/uniswap/__init__.py +++ b/uniswap/__init__.py @@ -1,4 +1,4 @@ from . import exceptions from .uniswap import Uniswap, _str_to_addr -from .uni4 import Uniswap4 +from .uniswap4 import Uniswap4 from .cli import main From 24c367195a77358570a039aea310f573f3cda21b Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Mon, 21 Aug 2023 17:29:49 +0300 Subject: [PATCH 06/35] futhermore --- uniswap/uniswap4.py | 44 ++++++++++++++++++++++++++++++++++++++++---- uniswap/util.py | 4 ++++ 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 32c6bf9b..dac0f739 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -16,7 +16,7 @@ Nonce, HexBytes, ) - +from web3._utils.abi import encode_abi from .types import AddressLike from .token import ERC20Token from .tokens import tokens, tokens_rinkeby @@ -106,7 +106,40 @@ def get_price( token1: AddressLike, # output token qty: int, fee: int, - route: Optional[List[AddressLike]] = None, + zero_to_one: bool = true, + ) -> int: + """ + :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. + :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + """ + + # WIP + + return 0 + + def get_spot_price( + self, + token0: AddressLike, # input token + token1: AddressLike, # output token + qty: int, + fee: int, + zero_to_one: bool = true, + ) -> int: + """ + :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. + :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + """ + + # WIP + + return 0 + + def get_price_impact( + self, + token0: AddressLike, # input token + token1: AddressLike, # output token + qty: int, + fee: int, zero_to_one: bool = true, ) -> int: """ @@ -190,8 +223,8 @@ def initialize( qty: Union[int, Wei], fee: int, tick_spacing: int, - hooks: AddressLike, sqrt_price_limit_x96: int, + hooks: AddressLike = ETH, ) -> HexBytes: """ :Initialize the state for a given pool ID @@ -232,7 +265,7 @@ def modify_position( tick_spacing: int, tick_upper: int, tick_lower: int, - hooks: AddressLike, + hooks: AddressLike = ETH, ) -> HexBytes: if currency0 == currency1: raise ValueError @@ -345,6 +378,9 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token symbol = _symbol return ERC20Token(symbol, address, name, decimals) + def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> int: + return int(self.w3.keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) + # ------ Test utilities ------------------------------------------------------------ def _get_token_addresses(self) -> Dict[str, ChecksumAddress]: diff --git a/uniswap/util.py b/uniswap/util.py index 761e6347..1be2f993 100644 --- a/uniswap/util.py +++ b/uniswap/util.py @@ -80,6 +80,10 @@ def _encode_path(token_in: AddressLike, route: List[Tuple[int, AddressLike]]) -> raise NotImplementedError +# Adapted from: https://github.com/Uniswap/v3-sdk/blob/main/src/utils/encodeSqrtRatioX96.ts +def decode_sqrt_ratioX96(amount_0: int, amount_1: int) -> int: + return int(amount_0 * amount_0 * 10**amount_1 >> 192) + # Adapted from: https://github.com/Uniswap/v3-sdk/blob/main/src/utils/encodeSqrtRatioX96.ts def encode_sqrt_ratioX96(amount_0: int, amount_1: int) -> int: numerator = amount_1 << 192 From c91c767fc22db82dc132966b04247d5874a371e3 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sat, 23 Sep 2023 11:48:46 +0300 Subject: [PATCH 07/35] Pool manager ABI added; Poolid function fix; Splitting methods to contract's calls wraps and market functions. --- uniswap/assets/uniswap-v4/poolmanager.abi | 1370 +++++++++++++++++++++ uniswap/constants.py | 1 + uniswap/uniswap4.py | 12 +- 3 files changed, 1378 insertions(+), 5 deletions(-) create mode 100644 uniswap/assets/uniswap-v4/poolmanager.abi diff --git a/uniswap/assets/uniswap-v4/poolmanager.abi b/uniswap/assets/uniswap-v4/poolmanager.abi new file mode 100644 index 00000000..dd6e9c66 --- /dev/null +++ b/uniswap/assets/uniswap-v4/poolmanager.abi @@ -0,0 +1,1370 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_controllerGasLimit", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CannotUpdateEmptyPosition", + "type": "error" + }, + { + "inputs": [], + "name": "CurrencyNotSettled", + "type": "error" + }, + { + "inputs": [], + "name": "DelegateCallNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "ERC20TransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "FeeTooLarge", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "hooks", + "type": "address" + } + ], + "name": "HookAddressNotValid", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCaller", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidHookResponse", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidSqrtRatio", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTick", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "locker", + "type": "address" + } + ], + "name": "LockedBy", + "type": "error" + }, + { + "inputs": [], + "name": "MaxCurrenciesTouched", + "type": "error" + }, + { + "inputs": [], + "name": "NativeTransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "NoLiquidityToReceiveFees", + "type": "error" + }, + { + "inputs": [], + "name": "NotPoolManagerToken", + "type": "error" + }, + { + "inputs": [], + "name": "PoolAlreadyInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "PoolNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceCurrentX96", + "type": "uint160" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "PriceLimitAlreadyExceeded", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "PriceLimitOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolFeeCannotBeFetched", + "type": "error" + }, + { + "inputs": [], + "name": "SwapAmountCannotBeZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "name": "TickLiquidityOverflow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + } + ], + "name": "TickLowerOutOfBounds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + } + ], + "name": "TickMisaligned", + "type": "error" + }, + { + "inputs": [], + "name": "TickSpacingTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "TickSpacingTooSmall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "TickUpperOutOfBounds", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "TicksMisordered", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "hookSwapFee", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "hookWithdrawFee", + "type": "uint8" + } + ], + "name": "HookFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + } + ], + "name": "ModifyPosition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "protocolFeeController", + "type": "address" + } + ], + "name": "ProtocolFeeControllerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "protocolSwapFee", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "protocolWithdrawFee", + "type": "uint8" + } + ], + "name": "ProtocolFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount0", + "type": "int128" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount1", + "type": "int128" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_TICK_SPACING", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_PROTOCOL_FEE_DENOMINATOR", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TICK_SPACING", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "collectHookFees", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "collectProtocolFees", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "donate", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "delta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32", + "name": "value", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "startSlot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nSlots", + "type": "uint256" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "getCurrencyDelta", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "getLiquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + } + ], + "name": "getLiquidity", + "outputs": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getNonzeroDeltaCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + } + ], + "name": "getPosition", + "outputs": [ + { + "components": [ + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside0LastX128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthInside1LastX128", + "type": "uint256" + } + ], + "internalType": "struct Position.Info", + "name": "position", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + } + ], + "name": "getSlot0", + "outputs": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint8", + "name": "protocolSwapFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "protocolWithdrawFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "hookSwapFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "hookWithdrawFee", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "hookAddress", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "hookFeesAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "lock", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "lockedBy", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lockedByLength", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + } + ], + "internalType": "struct IPoolManager.ModifyPositionParams", + "name": "params", + "type": "tuple" + } + ], + "name": "modifyPosition", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "delta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + } + ], + "name": "pools", + "outputs": [ + { + "components": [ + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "internalType": "uint8", + "name": "protocolSwapFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "protocolWithdrawFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "hookSwapFee", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "hookWithdrawFee", + "type": "uint8" + } + ], + "internalType": "struct Pool.Slot0", + "name": "slot0", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "feeGrowthGlobal0X128", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "feeGrowthGlobal1X128", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFeeController", + "outputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "protocolFeesAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "reservesOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + } + ], + "name": "setHookFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "controller", + "type": "address" + } + ], + "name": "setProtocolFeeController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + } + ], + "name": "setProtocolFees", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "settle", + "outputs": [ + { + "internalType": "uint256", + "name": "paid", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct IPoolManager.PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IPoolManager.SwapParams", + "name": "params", + "type": "tuple" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "delta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "take", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] \ No newline at end of file diff --git a/uniswap/constants.py b/uniswap/constants.py index 70c6fcec..df4fa4b4 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -13,6 +13,7 @@ ) ETH_ADDRESS = "0x0000000000000000000000000000000000000000" +NOHOOK_ADDRESS = "0x0000000000000000000000000000000000000000" WETH9_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" # see: https://chainid.network/chains/ diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index dac0f739..918a45c9 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -98,6 +98,8 @@ def __init__( if hasattr(self, "poolmanager_contract"): logger.info(f"Using factory contract: {self.poolmanager_contract}") + # ------ Contract calls ------------------------------------------------------------ + # ------ Market -------------------------------------------------------------------- def get_price( @@ -161,7 +163,7 @@ def make_trade( tick_spacing: int, sqrt_price_limit_x96: int = 0, zero_for_one: bool = true, - hooks: AddressLike = ETH, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: """ :Swap against the given pool @@ -224,7 +226,7 @@ def initialize( fee: int, tick_spacing: int, sqrt_price_limit_x96: int, - hooks: AddressLike = ETH, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: """ :Initialize the state for a given pool ID @@ -265,7 +267,7 @@ def modify_position( tick_spacing: int, tick_upper: int, tick_lower: int, - hooks: AddressLike = ETH, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: if currency0 == currency1: raise ValueError @@ -378,8 +380,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token symbol = _symbol return ERC20Token(symbol, address, name, decimals) - def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> int: - return int(self.w3.keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) + def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: + return self.w3.keccak_solidity(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) # ------ Test utilities ------------------------------------------------------------ From 2f9e0120f6d74f39ec95c499a71b2a2c55270300 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Mon, 8 Jan 2024 04:13:47 +0200 Subject: [PATCH 08/35] UniswapV4 alpha --- uniswap/__init__.py | 2 +- uniswap/assets/uniswap-v4/poolmanager.abi | 8 +- uniswap/constants.py | 2 + uniswap/types.py | 29 ++ uniswap/uniswap4.py | 306 ++++++++++++++++++---- 5 files changed, 296 insertions(+), 51 deletions(-) diff --git a/uniswap/__init__.py b/uniswap/__init__.py index 4c23bbb3..9af461fa 100644 --- a/uniswap/__init__.py +++ b/uniswap/__init__.py @@ -1,4 +1,4 @@ from . import exceptions from .uniswap import Uniswap, _str_to_addr -from .uniswap4 import Uniswap4 +from .uniswap4 import Uniswap4Core from .cli import main diff --git a/uniswap/assets/uniswap-v4/poolmanager.abi b/uniswap/assets/uniswap-v4/poolmanager.abi index dd6e9c66..8ede6bcc 100644 --- a/uniswap/assets/uniswap-v4/poolmanager.abi +++ b/uniswap/assets/uniswap-v4/poolmanager.abi @@ -635,9 +635,9 @@ { "inputs": [ { - "internalType": "uint256", - "name": "id", - "type": "uint256" + "internalType": "address", + "name": "locker", + "type": "address" }, { "internalType": "Currency", @@ -645,7 +645,7 @@ "type": "address" } ], - "name": "getCurrencyDelta", + "name": "currencyDelta", "outputs": [ { "internalType": "int256", diff --git a/uniswap/constants.py b/uniswap/constants.py index df4fa4b4..f86daeab 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -29,8 +29,10 @@ 137: "polygon", 100: "xdai", 250: "fantom", + 17000: "holesky", 42161: "arbitrum", 421611: "arbitrum_testnet", + 11155111: "sepolia", 1666600000: "harmony_mainnet", 1666700000: "harmony_testnet", } diff --git a/uniswap/types.py b/uniswap/types.py index d55ec98a..1df836d4 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -1,5 +1,34 @@ from typing import Union +from dataclasses import dataclass from eth_typing.evm import Address, ChecksumAddress AddressLike = Union[Address, ChecksumAddress] + +@dataclass +class UniswapV4_slot0: + sqrtPriceX96: int + tick: int + protocolFee: int + + def __repr__(self) -> str: + return f"Slot0 value (sqrtPriceX96: {self.sqrtPriceX96}; tick: {self.tick}; protocolFee: {self.protocolFee!r})" + +@dataclass +class UniswapV4_position_info: + liquidity: int + feeGrowthInside0LastX128: int + feeGrowthInside1LastX128: int + + def __repr__(self) -> str: + return f"Position info (liquidity: {self.liquidity}; feeGrowthInside0LastX128: {self.feeGrowthInside0LastX128}; feeGrowthInside1LastX128: {self.feeGrowthInside1LastX128!r})" + +@dataclass +class UniswapV4_tick_info: + liquidityGross : int + liquidityNet : int + feeGrowthOutside0X128 : int + feeGrowthOutside1X128 : int + + def __repr__(self) -> str: + return f"Tick info (liquidityGross: {self.liquidityGross}; liquidityNet: {self.liquidityNet}; feeGrowthOutside0X128: {self.feeGrowthOutside0X128}; feeGrowthOutside1X128: {self.feeGrowthOutside1X128!r})" diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 918a45c9..60a1f2ee 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -17,7 +17,7 @@ HexBytes, ) from web3._utils.abi import encode_abi -from .types import AddressLike +from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info from .token import ERC20Token from .tokens import tokens, tokens_rinkeby from .exceptions import InvalidToken, InsufficientBalance @@ -39,7 +39,7 @@ logger = logging.getLogger(__name__) -class Uniswap4: +class Uniswap4Core: """ Wrapper around Uniswap v4 contracts. """ @@ -96,65 +96,188 @@ def __init__( ) if hasattr(self, "poolmanager_contract"): - logger.info(f"Using factory contract: {self.poolmanager_contract}") + logger.info(f"Using pool manager contract: {self.poolmanager_contract}") # ------ Contract calls ------------------------------------------------------------ - # ------ Market -------------------------------------------------------------------- + # ------ Pool manager READ methods -------------------------------------------------------------------- def get_price( self, - token0: AddressLike, # input token - token1: AddressLike, # output token + currency0: AddressLike, # input token + currency1: AddressLike, # output token qty: int, fee: int, + tick_spacing: int, zero_to_one: bool = true, + sqrt_price_limit_x96: int = 0, + zero_for_one: bool = true, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> int: """ :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. """ - # WIP + if currency0 == currency1: + raise ValueError + + pool_key = { + "currency0": currency0.address, + "currency1": currency1.address, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } - return 0 + swap_params = { + "zeroForOne": zero_for_one, + "amountSpecified": qty, + "sqrtPriceLimitX96": sqrt_price_limit_x96, + } - def get_spot_price( + tx_params = self._get_tx_params() + transaction = self.router.functions.swap( + { + "key": pool_key, + "params": swap_params, + } + ).buildTransaction(tx_params) + # Uniswap3 uses 20% margin for transactions + transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) + signed_txn = self.w3.eth.account.sign_transaction( + transaction, private_key=self.private_key + ) + + try: + price = self.w3.eth.call(signed_txn) + except ContractLogicError as revert: + price = self.w3.codec.decode_abi(["int128[]","uint160","uint32"], revert.data)[1] + return price + + def get_slot0( self, - token0: AddressLike, # input token - token1: AddressLike, # output token - qty: int, + currency0: AddressLike, # input token + currency1: AddressLike, # output token fee: int, - zero_to_one: bool = true, + tick_spacing: int, + hooks: AddressLike = NOHOOK_ADDRESS, + ) -> UniswapV4_slot0: + """ + :Get the current value in slot0 of the given pool + """ + + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + slot0 = UniswapV4_slot0(*self.router.functions.getSlot0(pool_id).call()) + return slot0 + + def get_liquidity( + self, + currency0: AddressLike, # input token + currency1: AddressLike, # output token + fee: int, + tick_spacing: int, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> int: """ - :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. - :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + :Get the current value of liquidity of the given pool """ + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + liquidity = self.router.functions.getLiquidity(pool_id).call() + return liquidity - # WIP + def get_liquidity_for_position( + self, + currency0: AddressLike, # input token + currency1: AddressLike, # output token + fee: int, + tick_spacing: int, + owner: AddressLike, # output token + tick_lower: int, + tick_upper: int, + hooks: AddressLike = NOHOOK_ADDRESS, + ) -> int: + """ + :Get the current value of liquidity for the specified pool and position + """ + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + liquidity = self.router.functions.getLiquidity(pool_id,owner,tick_lower,tick_upper).call() + return liquidity - return 0 + def get_position( + self, + currency0: AddressLike, # input token + currency1: AddressLike, # output token + fee: int, + tick_spacing: int, + owner: AddressLike, # output token + tick_lower: int, + tick_upper: int, + hooks: AddressLike = NOHOOK_ADDRESS, + ) -> UniswapV4_position_info: + """ + :Get the current value of liquidity for the specified pool and position + """ + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + liquidity = UniswapV4_position_info(*self.router.functions.getPosition(pool_id,owner,tick_lower,tick_upper).call()) + return liquidity - def get_price_impact( + def get_pool_tick_info( self, - token0: AddressLike, # input token - token1: AddressLike, # output token - qty: int, + currency0: AddressLike, # input token + currency1: AddressLike, # output token fee: int, - zero_to_one: bool = true, + tick_spacing: int, + tick: int, + hooks: AddressLike = NOHOOK_ADDRESS, + ) -> UniswapV4_tick_info: + """ + :Get the current value of liquidity for the specified pool and position + """ + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + tick_info = UniswapV4_tick_info(*self.router.functions.getPoolTickInfo(pool_id,tick).call()) + return tick_info + + def get_pool_bitmap_info( + self, + currency0: AddressLike, # input token + currency1: AddressLike, # output token + fee: int, + tick_spacing: int, + word: int, + hooks: AddressLike = NOHOOK_ADDRESS, ) -> int: """ - :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. - :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + :Get the current value of liquidity for the specified pool and position """ + pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + bitmap_info = self.router.functions.getPoolBitmapInfo(pool_id, word).call() + return bitmap_info - # WIP + def currency_delta( + self, + locker: AddressLike, # input token + currency0: AddressLike, # output token + ) -> int: + """ + :Get the current value of liquidity for the specified pool and position + """ + currency_delta = self.router.functions.currencyDelta(locker, currency0).call() + return currency_delta - return 0 + def reserves_of( + self, + currency0: AddressLike, # input token + ) -> int: + """ + :Get the current value in slot0 of the given pool + """ + + reserves = self.router.functions.reservesOf().call() + return reserves - # ------ Make Trade ---------------------------------------------------------------- - def make_trade( + # ------ Pool manager WRITE methods ---------------------------------------------------------------- + def swap( self, currency0: ERC20Token, currency1: ERC20Token, @@ -200,24 +323,9 @@ def make_trade( "params": swap_params, } ), - self._get_tx_params(value=qty), + self._get_tx_params(), ) - # ------ Wallet balance ------------------------------------------------------------ - def get_eth_balance(self) -> Wei: - """Get the balance of ETH for your address.""" - return self.w3.eth.get_balance(self.address) - - def get_token_balance(self, token: AddressLike) -> int: - """Get the balance of a token for your address.""" - _validate_address(token) - if _addr_to_str(token) == ETH_ADDRESS: - return self.get_eth_balance() - erc20 = _load_contract_erc20(self.w3, token) - balance: int = erc20.functions.balanceOf(self.address).call() - return balance - - # ------ Liquidity ----------------------------------------------------------------- def initialize( self, currency0: ERC20Token, @@ -229,7 +337,7 @@ def initialize( hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: """ - :Initialize the state for a given pool ID + :Initialize the state for a given pool key : :`currency0`:The lower currency of the pool, sorted numerically :`currency1`:The higher currency of the pool, sorted numerically @@ -255,7 +363,47 @@ def initialize( "sqrtPriceX96": sqrt_price_limit_x96, } ), - self._get_tx_params(value=qty), + self._get_tx_params(), + ) + + def donate( + self, + currency0: ERC20Token, + currency1: ERC20Token, + qty: Union[int, Wei], + fee: int, + tick_spacing: int, + sqrt_price_limit_x96: int, + hooks: AddressLike = NOHOOK_ADDRESS, + ) -> HexBytes: + """ + :Donate the given currency amounts to the pool with the given pool key + : + :`currency0`:The lower currency of the pool, sorted numerically + :`currency1`:The higher currency of the pool, sorted numerically + :`fee`: The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. + :`tickSpacing`: Ticks that involve positions must be a multiple of tick spacing + :`hooks`: The hooks of the pool + """ + if currency0 == currency1: + raise ValueError + + pool_key = { + "currency0": currency0.address, + "currency1": currency1.address, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } + + return self._build_and_send_tx( + self.router.functions.donate( + { + "key": pool_key, + "sqrtPriceX96": sqrt_price_limit_x96, + } + ), + self._get_tx_params(), ) def modify_position( @@ -269,6 +417,16 @@ def modify_position( tick_lower: int, hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: + """ + :Modify the liquidity for the given pool + :Poke by calling with a zero liquidityDelta + : + :`currency0`:The lower currency of the pool, sorted numerically + :`currency1`:The higher currency of the pool, sorted numerically + :`fee`: The pool swap fee, capped at 1_000_000. The upper 4 bits determine if the hook sets any fees. + :`tickSpacing`: Ticks that involve positions must be a multiple of tick spacing + :`hooks`: The hooks of the pool + """ if currency0 == currency1: raise ValueError @@ -296,6 +454,60 @@ def modify_position( self._get_tx_params(value=qty), ) + def settle( + self, + currency0: ERC20Token, + qty: Union[int, Wei], + ) -> HexBytes: + """ + :Called by the user to pay what is owed + """ + + return self._build_and_send_tx( + self.router.functions.settle( + { + "currency ": currency0, + } + ), + self._get_tx_params(value=qty), + ) + + def take( + self, + currency0: ERC20Token, + to: AddressLike, + qty: Union[int, Wei], + ) -> HexBytes: + """ + :Called by the user to net out some value owed to the user + :Can also be used as a mechanism for _free_ flash loans + """ + + return self._build_and_send_tx( + self.router.functions.take( + { + "currency ": currency0, + "to ": to, + "amount ": qty, + } + ), + self._get_tx_params(), + ) + + # ------ Wallet balance ------------------------------------------------------------ + def get_eth_balance(self) -> Wei: + """Get the balance of ETH for your address.""" + return self.w3.eth.get_balance(self.address) + + def get_token_balance(self, token: AddressLike) -> int: + """Get the balance of a token for your address.""" + _validate_address(token) + if _addr_to_str(token) == ETH_ADDRESS: + return self.get_eth_balance() + erc20 = _load_contract_erc20(self.w3, token) + balance: int = erc20.functions.balanceOf(self.address).call() + return balance + # ------ Approval Utils ------------------------------------------------------------ def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" @@ -381,6 +593,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: + if currency0 > currency1: + currency0 , currency1 = currency1 , currency0 return self.w3.keccak_solidity(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) # ------ Test utilities ------------------------------------------------------------ From 21090702f72396ec246f98a8594e99fe337f332e Mon Sep 17 00:00:00 2001 From: Yohan K <72107640+liquid-8@users.noreply.github.com> Date: Mon, 8 Jan 2024 11:14:13 +0200 Subject: [PATCH 09/35] Update __init__.py --- uniswap/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/uniswap/__init__.py b/uniswap/__init__.py index 9af461fa..4e50bf09 100644 --- a/uniswap/__init__.py +++ b/uniswap/__init__.py @@ -1,4 +1,6 @@ from . import exceptions +from .cli import main from .uniswap import Uniswap, _str_to_addr from .uniswap4 import Uniswap4Core -from .cli import main + +__all__ = ["Uniswap", "Uniswap4Core", "exceptions", "_str_to_addr", "main"] From 442771dc3e12b5d65539650b75f0c6a75091a70e Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 01:45:42 +0200 Subject: [PATCH 10/35] fix p.1 --- uniswap/uniswap4.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 60a1f2ee..ee233670 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -5,8 +5,8 @@ from typing import List, Any, Optional, Union, Tuple, Dict from web3 import Web3 -from web3.eth import Contract -from web3.contract import ContractFunction +from web3.contract import Contract +from web3.contract.contract import ContractFunction from web3.exceptions import BadFunctionCallOutput, ContractLogicError from web3.types import ( TxParams, @@ -34,6 +34,7 @@ _netid_to_name, _poolmanager_contract_addresses, ETH_ADDRESS, + NOHOOK_ADDRESS, ) logger = logging.getLogger(__name__) @@ -86,17 +87,22 @@ def __init__( self.last_nonce: Nonce = self.w3.eth.get_transaction_count(self.address) + max_approval_hex = f"0x{64 * 'f'}" + self.max_approval_int = int(max_approval_hex, 16) + max_approval_check_hex = f"0x{15 * '0'}{49 * 'f'}" + self.max_approval_check_int = int(max_approval_check_hex, 16) + if poolmanager_contract_addr is None: poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] - self.poolmanager_contract = _load_contract( + self.router = _load_contract( self.w3, abi_name="uniswap-v4/poolmanager", address=_str_to_addr(poolmanager_contract_addr), ) if hasattr(self, "poolmanager_contract"): - logger.info(f"Using pool manager contract: {self.poolmanager_contract}") + logger.info(f"Using pool manager contract: {self.router}") # ------ Contract calls ------------------------------------------------------------ @@ -109,9 +115,8 @@ def get_price( qty: int, fee: int, tick_spacing: int, - zero_to_one: bool = true, sqrt_price_limit_x96: int = 0, - zero_for_one: bool = true, + zero_for_one: bool = True, hooks: AddressLike = NOHOOK_ADDRESS, ) -> int: """ @@ -593,7 +598,7 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: - if currency0 > currency1: + if int(currency0) > (currency1): currency0 , currency1 = currency1 , currency0 return self.w3.keccak_solidity(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) From 5a3e35bd4363aa08c5c44f95da1d406022e9b1a0 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 01:50:15 +0200 Subject: [PATCH 11/35] and more --- uniswap/uniswap4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index ee233670..455cf9e1 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -290,7 +290,7 @@ def swap( fee: int, tick_spacing: int, sqrt_price_limit_x96: int = 0, - zero_for_one: bool = true, + zero_for_one: bool = True, hooks: AddressLike = NOHOOK_ADDRESS, ) -> HexBytes: """ From a908abd57fa11a1b618a7210bd87c26744d9b328 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 02:15:50 +0200 Subject: [PATCH 12/35] and more --- uniswap/uniswap4.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 455cf9e1..19fae85e 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -16,7 +16,6 @@ Nonce, HexBytes, ) -from web3._utils.abi import encode_abi from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info from .token import ERC20Token from .tokens import tokens, tokens_rinkeby @@ -600,7 +599,7 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: if int(currency0) > (currency1): currency0 , currency1 = currency1 , currency0 - return self.w3.keccak_solidity(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) + return self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) # ------ Test utilities ------------------------------------------------------------ From fd3f9324302723747d3c0792bb2bc7e6074908f5 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 17:48:43 +0200 Subject: [PATCH 13/35] and more --- uniswap/uniswap4.py | 53 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 19fae85e..47ef25d3 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -18,7 +18,6 @@ ) from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info from .token import ERC20Token -from .tokens import tokens, tokens_rinkeby from .exceptions import InvalidToken, InsufficientBalance from .util import ( _str_to_addr, @@ -116,7 +115,7 @@ def get_price( tick_spacing: int, sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> int: """ :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. @@ -127,8 +126,8 @@ def get_price( raise ValueError pool_key = { - "currency0": currency0.address, - "currency1": currency1.address, + "currency0": currency0, + "currency1": currency1, "fee": fee, "tickSpacing": tick_spacing, "hooks": hooks, @@ -146,7 +145,7 @@ def get_price( "key": pool_key, "params": swap_params, } - ).buildTransaction(tx_params) + ).build_transaction(tx_params) # Uniswap3 uses 20% margin for transactions transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) signed_txn = self.w3.eth.account.sign_transaction( @@ -156,7 +155,7 @@ def get_price( try: price = self.w3.eth.call(signed_txn) except ContractLogicError as revert: - price = self.w3.codec.decode_abi(["int128[]","uint160","uint32"], revert.data)[1] + price = int(self.w3.codec.decode_abi(["int128[]","uint160","uint32"], revert.data)[1]) return price def get_slot0( @@ -165,7 +164,7 @@ def get_slot0( currency1: AddressLike, # output token fee: int, tick_spacing: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> UniswapV4_slot0: """ :Get the current value in slot0 of the given pool @@ -181,13 +180,13 @@ def get_liquidity( currency1: AddressLike, # output token fee: int, tick_spacing: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> int: """ :Get the current value of liquidity of the given pool """ pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) - liquidity = self.router.functions.getLiquidity(pool_id).call() + liquidity = int(self.router.functions.getLiquidity(pool_id).call()) return liquidity def get_liquidity_for_position( @@ -196,16 +195,16 @@ def get_liquidity_for_position( currency1: AddressLike, # output token fee: int, tick_spacing: int, - owner: AddressLike, # output token + owner: AddressLike, tick_lower: int, tick_upper: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> int: """ :Get the current value of liquidity for the specified pool and position """ pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) - liquidity = self.router.functions.getLiquidity(pool_id,owner,tick_lower,tick_upper).call() + liquidity = int(self.router.functions.getLiquidity(pool_id,owner,tick_lower,tick_upper).call()) return liquidity def get_position( @@ -217,7 +216,7 @@ def get_position( owner: AddressLike, # output token tick_lower: int, tick_upper: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> UniswapV4_position_info: """ :Get the current value of liquidity for the specified pool and position @@ -233,7 +232,7 @@ def get_pool_tick_info( fee: int, tick_spacing: int, tick: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> UniswapV4_tick_info: """ :Get the current value of liquidity for the specified pool and position @@ -249,13 +248,13 @@ def get_pool_bitmap_info( fee: int, tick_spacing: int, word: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> int: """ :Get the current value of liquidity for the specified pool and position """ pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) - bitmap_info = self.router.functions.getPoolBitmapInfo(pool_id, word).call() + bitmap_info = int(self.router.functions.getPoolBitmapInfo(pool_id, word).call()) return bitmap_info def currency_delta( @@ -285,12 +284,12 @@ def swap( self, currency0: ERC20Token, currency1: ERC20Token, - qty: Union[int, Wei], + qty: int, fee: int, tick_spacing: int, sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> HexBytes: """ :Swap against the given pool @@ -334,11 +333,11 @@ def initialize( self, currency0: ERC20Token, currency1: ERC20Token, - qty: Union[int, Wei], + qty: int, fee: int, tick_spacing: int, sqrt_price_limit_x96: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> HexBytes: """ :Initialize the state for a given pool key @@ -374,11 +373,11 @@ def donate( self, currency0: ERC20Token, currency1: ERC20Token, - qty: Union[int, Wei], + qty: int, fee: int, tick_spacing: int, sqrt_price_limit_x96: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> HexBytes: """ :Donate the given currency amounts to the pool with the given pool key @@ -414,12 +413,12 @@ def modify_position( self, currency0: ERC20Token, currency1: ERC20Token, - qty: Union[int, Wei], + qty: int, fee: int, tick_spacing: int, tick_upper: int, tick_lower: int, - hooks: AddressLike = NOHOOK_ADDRESS, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, ) -> HexBytes: """ :Modify the liquidity for the given pool @@ -461,7 +460,7 @@ def modify_position( def settle( self, currency0: ERC20Token, - qty: Union[int, Wei], + qty: int, ) -> HexBytes: """ :Called by the user to pay what is owed @@ -480,7 +479,7 @@ def take( self, currency0: ERC20Token, to: AddressLike, - qty: Union[int, Wei], + qty: int, ) -> HexBytes: """ :Called by the user to net out some value owed to the user @@ -597,7 +596,7 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: - if int(currency0) > (currency1): + if int(currency0) > int(currency1): currency0 , currency1 = currency1 , currency0 return self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) From 7309f1518080bf9c9669b8e706298c8e2e8dece4 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 18:17:54 +0200 Subject: [PATCH 14/35] and more --- uniswap/uniswap4.py | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 47ef25d3..4e5ed4b2 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -47,10 +47,10 @@ def __init__( self, address: Union[AddressLike, str, None], private_key: Optional[str], - provider: str = None, - web3: Web3 = None, + provider: Optional[str] = None, + web3: Optional[Web3] = None, default_slippage: float = 0.01, - poolmanager_contract_addr: str = None, + poolmanager_contract_addr: Optional[str] = None, ) -> None: """ :param address: The public address of the ETH wallet to use. @@ -91,7 +91,7 @@ def __init__( self.max_approval_check_int = int(max_approval_check_hex, 16) if poolmanager_contract_addr is None: - poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] + self.poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] self.router = _load_contract( self.w3, @@ -153,7 +153,7 @@ def get_price( ) try: - price = self.w3.eth.call(signed_txn) + price = int(self.w3.eth.call(signed_txn)) except ContractLogicError as revert: price = int(self.w3.codec.decode_abi(["int128[]","uint160","uint32"], revert.data)[1]) return price @@ -265,7 +265,7 @@ def currency_delta( """ :Get the current value of liquidity for the specified pool and position """ - currency_delta = self.router.functions.currencyDelta(locker, currency0).call() + currency_delta = int(self.router.functions.currencyDelta(locker, currency0).call()) return currency_delta def reserves_of( @@ -276,7 +276,7 @@ def reserves_of( :Get the current value in slot0 of the given pool """ - reserves = self.router.functions.reservesOf().call() + reserves = int(self.router.functions.reservesOf().call()) return reserves # ------ Pool manager WRITE methods ---------------------------------------------------------------- @@ -454,7 +454,7 @@ def modify_position( "params": modify_position_params, } ), - self._get_tx_params(value=qty), + self._get_tx_params(value=Wei(qty)), ) def settle( @@ -472,7 +472,7 @@ def settle( "currency ": currency0, } ), - self._get_tx_params(value=qty), + self._get_tx_params(value=Wei(qty)), ) def take( @@ -515,11 +515,7 @@ def get_token_balance(self, token: AddressLike) -> int: def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" max_approval = self.max_approval_int if not max_approval else max_approval - contract_addr = ( - self._exchange_address_from_token(token) - if self.version == 1 - else self.router_address - ) + contract_addr = poolmanager_contract_addr function = _load_contract_erc20(self.w3, token).functions.approve( contract_addr, max_approval ) @@ -595,23 +591,11 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token symbol = _symbol return ERC20Token(symbol, address, name, decimals) - def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : AddressLike = ETH) -> bytes: + def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: if int(currency0) > int(currency1): currency0 , currency1 = currency1 , currency0 - return self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]) + pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) + return - # ------ Test utilities ------------------------------------------------------------ - def _get_token_addresses(self) -> Dict[str, ChecksumAddress]: - """ - Returns a dict with addresses for tokens for the current net. - Used in testing. - """ - netid = int(self.w3.net.version) - netname = _netid_to_name[netid] - if netname == "mainnet": - return tokens - elif netname == "rinkeby": - return tokens_rinkeby - else: - raise Exception(f"Unknown net '{netname}'") + \ No newline at end of file From c5fd61c9c1e86f9c533881b3cb00cd8f8aea9dd5 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 18:41:09 +0200 Subject: [PATCH 15/35] fix p.2 --- uniswap/uniswap4.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 4e5ed4b2..55e2bd8a 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -50,7 +50,7 @@ def __init__( provider: Optional[str] = None, web3: Optional[Web3] = None, default_slippage: float = 0.01, - poolmanager_contract_addr: Optional[str] = None, + poolmanager_contract_addr: Optional[AddressLike,str] = None, ) -> None: """ :param address: The public address of the ETH wallet to use. @@ -155,7 +155,7 @@ def get_price( try: price = int(self.w3.eth.call(signed_txn)) except ContractLogicError as revert: - price = int(self.w3.codec.decode_abi(["int128[]","uint160","uint32"], revert.data)[1]) + price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert))[1]) return price def get_slot0( @@ -170,7 +170,7 @@ def get_slot0( :Get the current value in slot0 of the given pool """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) slot0 = UniswapV4_slot0(*self.router.functions.getSlot0(pool_id).call()) return slot0 @@ -185,7 +185,7 @@ def get_liquidity( """ :Get the current value of liquidity of the given pool """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) liquidity = int(self.router.functions.getLiquidity(pool_id).call()) return liquidity @@ -203,7 +203,7 @@ def get_liquidity_for_position( """ :Get the current value of liquidity for the specified pool and position """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) liquidity = int(self.router.functions.getLiquidity(pool_id,owner,tick_lower,tick_upper).call()) return liquidity @@ -221,7 +221,7 @@ def get_position( """ :Get the current value of liquidity for the specified pool and position """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) liquidity = UniswapV4_position_info(*self.router.functions.getPosition(pool_id,owner,tick_lower,tick_upper).call()) return liquidity @@ -237,7 +237,7 @@ def get_pool_tick_info( """ :Get the current value of liquidity for the specified pool and position """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) tick_info = UniswapV4_tick_info(*self.router.functions.getPoolTickInfo(pool_id,tick).call()) return tick_info @@ -253,7 +253,7 @@ def get_pool_bitmap_info( """ :Get the current value of liquidity for the specified pool and position """ - pool_id = get_pool_id(currency0, currency1, fee, tick_spacing, hooks) + pool_id = self.get_pool_id(currency0, currency1, fee, tick_spacing, hooks) bitmap_info = int(self.router.functions.getPoolBitmapInfo(pool_id, word).call()) return bitmap_info @@ -515,7 +515,7 @@ def get_token_balance(self, token: AddressLike) -> int: def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" max_approval = self.max_approval_int if not max_approval else max_approval - contract_addr = poolmanager_contract_addr + contract_addr = self.poolmanager_contract_addr function = _load_contract_erc20(self.w3, token).functions.approve( contract_addr, max_approval ) @@ -537,7 +537,7 @@ def _build_and_send_tx( """Build and send a transaction.""" if not tx_params: tx_params = self._get_tx_params() - transaction = function.buildTransaction(tx_params) + transaction = function.build_transaction(tx_params) # Uniswap3 uses 20% margin for transactions transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) signed_txn = self.w3.eth.account.sign_transaction( @@ -570,7 +570,7 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token # FIXME: This function should always return the same output for the same input # and would therefore benefit from caching if address == ETH_ADDRESS: - return ERC20Token("ETH", ETH_ADDRESS, "Ether", 18) + return ERC20Token("ETH", address, "Ether", 18) token_contract = _load_contract(self.w3, abi_name, address=address) try: _name = token_contract.functions.name().call() From de55f2979e183706c0a72e2ed9d65c1b43543024 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 19:16:16 +0200 Subject: [PATCH 16/35] fix p2.1 --- uniswap/uniswap4.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 55e2bd8a..c378f4ce 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -50,7 +50,7 @@ def __init__( provider: Optional[str] = None, web3: Optional[Web3] = None, default_slippage: float = 0.01, - poolmanager_contract_addr: Optional[AddressLike,str] = None, + poolmanager_contract_addr: Optional[str] = None, ) -> None: """ :param address: The public address of the ETH wallet to use. @@ -91,12 +91,14 @@ def __init__( self.max_approval_check_int = int(max_approval_check_hex, 16) if poolmanager_contract_addr is None: - self.poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] + self.poolmanager_contract_addr: AddressLike = _poolmanager_contract_addresses[self.network] + else: + self.poolmanager_contract_addr: AddressLike = poolmanager_contract_addr self.router = _load_contract( self.w3, abi_name="uniswap-v4/poolmanager", - address=_str_to_addr(poolmanager_contract_addr), + address=_str_to_addr(self.poolmanager_contract_addr), ) if hasattr(self, "poolmanager_contract"): @@ -155,7 +157,7 @@ def get_price( try: price = int(self.w3.eth.call(signed_txn)) except ContractLogicError as revert: - price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert))[1]) + price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert.data))[1]) return price def get_slot0( From b0553858e48fb4df952ca9b38c735d0d8ce5d6a1 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 19:27:59 +0200 Subject: [PATCH 17/35] fix p2.2 --- uniswap/uniswap4.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index c378f4ce..93c3d479 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -91,14 +91,14 @@ def __init__( self.max_approval_check_int = int(max_approval_check_hex, 16) if poolmanager_contract_addr is None: - self.poolmanager_contract_addr: AddressLike = _poolmanager_contract_addresses[self.network] + self.poolmanager_contract_addr: AddressLike = _str_to_addr(_poolmanager_contract_addresses[self.network]) else: - self.poolmanager_contract_addr: AddressLike = poolmanager_contract_addr + self.poolmanager_contract_addr: AddressLike = _str_to_addr(poolmanager_contract_addr) self.router = _load_contract( self.w3, abi_name="uniswap-v4/poolmanager", - address=_str_to_addr(self.poolmanager_contract_addr), + address=self.poolmanager_contract_addr, ) if hasattr(self, "poolmanager_contract"): @@ -517,7 +517,7 @@ def get_token_balance(self, token: AddressLike) -> int: def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" max_approval = self.max_approval_int if not max_approval else max_approval - contract_addr = self.poolmanager_contract_addr + contract_addr = _addr_to_str(self.poolmanager_contract_addr) function = _load_contract_erc20(self.w3, token).functions.approve( contract_addr, max_approval ) @@ -597,7 +597,7 @@ def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, if int(currency0) > int(currency1): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) - return + return pool_id \ No newline at end of file From 56e7e6aa3f156024c1a2c1e4140d4cd9adeaa9f4 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 19:35:57 +0200 Subject: [PATCH 18/35] fix p2.3 --- uniswap/uniswap4.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 93c3d479..934543a0 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -91,9 +91,8 @@ def __init__( self.max_approval_check_int = int(max_approval_check_hex, 16) if poolmanager_contract_addr is None: - self.poolmanager_contract_addr: AddressLike = _str_to_addr(_poolmanager_contract_addresses[self.network]) - else: - self.poolmanager_contract_addr: AddressLike = _str_to_addr(poolmanager_contract_addr) + poolmanager_contract_addr = _str_to_addr(_poolmanager_contract_addresses[self.network]) + self.poolmanager_contract_addr: AddressLike = _str_to_addr(poolmanager_contract_addr) self.router = _load_contract( self.w3, From bfde21c4df131dd702b79d9d54e7b44fa5cec98c Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 19:48:01 +0200 Subject: [PATCH 19/35] fix pt2.4 --- uniswap/uniswap4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 934543a0..46e2e606 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -91,7 +91,7 @@ def __init__( self.max_approval_check_int = int(max_approval_check_hex, 16) if poolmanager_contract_addr is None: - poolmanager_contract_addr = _str_to_addr(_poolmanager_contract_addresses[self.network]) + poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] self.poolmanager_contract_addr: AddressLike = _str_to_addr(poolmanager_contract_addr) self.router = _load_contract( From 00acbe474e76e501fbdf9f33673f8784fa5216d5 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Tue, 9 Jan 2024 20:57:19 +0200 Subject: [PATCH 20/35] fix p2.5 --- uniswap/uniswap4.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 46e2e606..23579827 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -156,7 +156,7 @@ def get_price( try: price = int(self.w3.eth.call(signed_txn)) except ContractLogicError as revert: - price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert.data))[1]) + price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert.data))[1]) # type: ignore return price def get_slot0( From 2bc594cfb2b48b0f771e8cbfe3dc868c0cdee1a3 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 6 Jun 2024 01:42:29 +0300 Subject: [PATCH 21/35] price fetching refactored; custom gas pricing; misc --- uniswap/assets/uniswap-v4/quoter.abi | 88 +++++++++++++ uniswap/constants.py | 12 +- uniswap/types.py | 16 +++ uniswap/uniswap4.py | 190 ++++++++++++++++++++++----- 4 files changed, 273 insertions(+), 33 deletions(-) create mode 100644 uniswap/assets/uniswap-v4/quoter.abi diff --git a/uniswap/assets/uniswap-v4/quoter.abi b/uniswap/assets/uniswap-v4/quoter.abi new file mode 100644 index 00000000..9eb5372a --- /dev/null +++ b/uniswap/assets/uniswap-v4/quoter.abi @@ -0,0 +1,88 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "_poolManager", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "poolManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "name": "quoteExactInput", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "path", "type": "bytes" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" } + ], + "name": "quoteExactOutput", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "tokenIn", "type": "address" }, + { "internalType": "address", "name": "tokenOut", "type": "address" }, + { "internalType": "uint24", "name": "fee", "type": "uint24" }, + { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { "internalType": "uint256", "name": "amountIn", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/uniswap/constants.py b/uniswap/constants.py index f86daeab..820af352 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -74,7 +74,7 @@ "harmony_testnet": "0x1b02dA8Cb0d097eB8D57A175b88c7D8b47997506", } -# need to replace with actual addresses +# need to replace with actual addresses after release _poolmanager_contract_addresses = { "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", @@ -85,6 +85,16 @@ "binance_testnet": "0x6725F303b657a9451d8BA641348b6761A6CC7a17", } +# need to replace with actual addresses after release +_quoter_contract_addresses = { + "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "rinkeby": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "görli": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + "xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", + "binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + "binance_testnet": "0x6725F303b657a9451d8BA641348b6761A6CC7a17", +} MAX_UINT_128 = (2**128) - 1 # Source: https://github.com/Uniswap/v3-core/blob/v1.0.0/contracts/libraries/TickMath.sol#L8-L11 diff --git a/uniswap/types.py b/uniswap/types.py index 1df836d4..acc5105a 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -32,3 +32,19 @@ class UniswapV4_tick_info: def __repr__(self) -> str: return f"Tick info (liquidityGross: {self.liquidityGross}; liquidityNet: {self.liquidityNet}; feeGrowthOutside0X128: {self.feeGrowthOutside0X128}; feeGrowthOutside1X128: {self.feeGrowthOutside1X128!r})" + +@dataclass +class UniswapV4_PathKey: + # The lower currency of the pool, sorted numerically + currency0 : Address + # The higher currency of the pool, sorted numerically + currency1 : Address + # The pool swap fee, capped at 1_000_000. If the first bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 + fee : int + # Ticks that involve positions must be a multiple of tick spacing + tickSpacing : int + # The hooks of the pool + hooks : list[Address] + + def __repr__(self) -> (Address, Address, int, int, list[Address]): + return (self.currency0, self.currency1, self.fee, self.tickSpacing, self.hooks) \ No newline at end of file diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 23579827..12782217 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -16,7 +16,7 @@ Nonce, HexBytes, ) -from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info +from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info, UniswapV4_PathKey from .token import ERC20Token from .exceptions import InvalidToken, InsufficientBalance from .util import ( @@ -31,6 +31,7 @@ from .constants import ( _netid_to_name, _poolmanager_contract_addresses, + _quoter_contract_addresses, ETH_ADDRESS, NOHOOK_ADDRESS, ) @@ -51,6 +52,7 @@ def __init__( web3: Optional[Web3] = None, default_slippage: float = 0.01, poolmanager_contract_addr: Optional[str] = None, + quoter_contract_addr: Optional[str] = None, ) -> None: """ :param address: The public address of the ETH wallet to use. @@ -94,30 +96,41 @@ def __init__( poolmanager_contract_addr = _poolmanager_contract_addresses[self.network] self.poolmanager_contract_addr: AddressLike = _str_to_addr(poolmanager_contract_addr) + if quoter_contract_addr is None: + quoter_contract_addr = _quoter_contract_addresses[self.network] + self.quoter_contract_addr: AddressLike = _str_to_addr(quoter_contract_addr) + self.router = _load_contract( self.w3, abi_name="uniswap-v4/poolmanager", address=self.poolmanager_contract_addr, ) + self.quoter = _load_contract( + self.w3, + abi_name="uniswap-v4/quoter", + address=self.quoter_contract_addr, + ) + if hasattr(self, "poolmanager_contract"): logger.info(f"Using pool manager contract: {self.router}") # ------ Contract calls ------------------------------------------------------------ - # ------ Pool manager READ methods -------------------------------------------------------------------- + # ------ Quoter methods -------------------------------------------------------------------- - def get_price( + def get_quote_exact_input_single( self, currency0: AddressLike, # input token currency1: AddressLike, # output token qty: int, fee: int, tick_spacing: int, + hook_data: bytes, sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, - ) -> int: + ): """ :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. @@ -134,31 +147,117 @@ def get_price( "hooks": hooks, } - swap_params = { + quote_params = { + "poolKey": pool_key, "zeroForOne": zero_for_one, - "amountSpecified": qty, + "recipient": self.address, + "exactAmount": qty, "sqrtPriceLimitX96": sqrt_price_limit_x96, + "hookData" : hook_data, } - tx_params = self._get_tx_params() - transaction = self.router.functions.swap( - { - "key": pool_key, - "params": swap_params, - } - ).build_transaction(tx_params) - # Uniswap3 uses 20% margin for transactions - transaction["gas"] = Wei(int(self.w3.eth.estimate_gas(transaction) * 1.2)) - signed_txn = self.w3.eth.account.sign_transaction( - transaction, private_key=self.private_key - ) + values = self.quoter.functions.quoteExactInputSingle(quote_params) + #[0]returns deltaAmounts: Delta amounts resulted from the swap + #[1]returns sqrtPriceX96After: The sqrt price of the pool after the swap + #[2]returns initializedTicksLoaded: The number of initialized ticks that the swap loaded + return values - try: - price = int(self.w3.eth.call(signed_txn)) - except ContractLogicError as revert: - price = int(self.w3.codec.decode(["int128[]","uint160","uint32"], bytes(revert.data))[1]) # type: ignore - return price + def get_quote_exact_output_single( + self, + currency0: AddressLike, # input token + currency1: AddressLike, # output token + qty: int, + fee: int, + tick_spacing: int, + hook_data: bytes, + sqrt_price_limit_x96: int = 0, + zero_for_one: bool = True, + hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, + ): + """ + :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. + :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. + """ + + if currency0 == currency1: + raise ValueError + pool_key = { + "currency0": currency0, + "currency1": currency1, + "fee": fee, + "tickSpacing": tick_spacing, + "hooks": hooks, + } + + quote_params = { + "poolKey": pool_key, + "zeroForOne": zero_for_one, + "recipient": self.address, + "exactAmount": qty, + "sqrtPriceLimitX96": sqrt_price_limit_x96, + "hookData" : hook_data, + } + + values = self.quoter.functions.quoteExactOutputSingle(quote_params) + #[0]returns deltaAmounts: Delta amounts resulted from the swap + #[1]returns sqrtPriceX96After: The sqrt price of the pool after the swap + #[2]returns initializedTicksLoaded: The number of initialized ticks that the swap loaded + return values + + def get_quote_exact_input( + self, + currency: AddressLike, # input token + qty: int, + path : list[UniswapV4_PathKey], + ): + """ + :path is a swap route + """ + + if currency0 == currency1: + raise ValueError + + quote_params = { + "exactCurrency": currency, + "path": path, + "recipient": self.address, + "exactAmount": qty, + } + + values = self.quoter.functions.quoteExactInput(quote_params) + #[0] returns deltaAmounts: Delta amounts along the path resulted from the swap + #[1] returns sqrtPriceX96AfterList: List of the sqrt price after the swap for each pool in the path + #[2] returns initializedTicksLoadedList: List of the initialized ticks that the swap loaded for each pool in the path + return values + + def get_quote_exact_output( + self, + currency: AddressLike, # input token + qty: int, + path : list[UniswapV4_PathKey], + ): + """ + :path is a swap route + """ + + if currency0 == currency1: + raise ValueError + + quote_params = { + "exactCurrency": currency, + "path": path, + "recipient": self.address, + "exactAmount": qty, + } + + values = self.quoter.functions.quoteExactOutput(quote_params) + #[0] returns deltaAmounts: Delta amounts along the path resulted from the swap + #[1] returns sqrtPriceX96AfterList: List of the sqrt price after the swap for each pool in the path + #[2] returns initializedTicksLoadedList: List of the initialized ticks that the swap loaded for each pool in the path + return values + + # ------ Pool manager READ methods -------------------------------------------------------------------- def get_slot0( self, currency0: AddressLike, # input token @@ -291,6 +390,9 @@ def swap( sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Swap against the given pool @@ -327,7 +429,7 @@ def swap( "params": swap_params, } ), - self._get_tx_params(), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) def initialize( @@ -339,6 +441,9 @@ def initialize( tick_spacing: int, sqrt_price_limit_x96: int, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Initialize the state for a given pool key @@ -367,7 +472,7 @@ def initialize( "sqrtPriceX96": sqrt_price_limit_x96, } ), - self._get_tx_params(), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) def donate( @@ -379,6 +484,9 @@ def donate( tick_spacing: int, sqrt_price_limit_x96: int, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Donate the given currency amounts to the pool with the given pool key @@ -407,7 +515,7 @@ def donate( "sqrtPriceX96": sqrt_price_limit_x96, } ), - self._get_tx_params(), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) def modify_position( @@ -420,6 +528,9 @@ def modify_position( tick_upper: int, tick_lower: int, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Modify the liquidity for the given pool @@ -455,13 +566,16 @@ def modify_position( "params": modify_position_params, } ), - self._get_tx_params(value=Wei(qty)), + self._get_tx_params(value=Wei(qty), gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) def settle( self, currency0: ERC20Token, qty: int, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Called by the user to pay what is owed @@ -473,7 +587,7 @@ def settle( "currency ": currency0, } ), - self._get_tx_params(value=Wei(qty)), + self._get_tx_params(value=Wei(qty), gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) def take( @@ -481,6 +595,9 @@ def take( currency0: ERC20Token, to: AddressLike, qty: int, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, ) -> HexBytes: """ :Called by the user to net out some value owed to the user @@ -495,7 +612,7 @@ def take( "amount ": qty, } ), - self._get_tx_params(), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) # ------ Wallet balance ------------------------------------------------------------ @@ -529,7 +646,7 @@ def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> Non # ------ Tx Utils ------------------------------------------------------------------ def _deadline(self) -> int: - """Get a predefined deadline. 10min by default (same as the Uniswap SDK).""" + """Get a predefined deadline. 10min by default.""" return int(time.time()) + 10 * 60 def _build_and_send_tx( @@ -552,9 +669,9 @@ def _build_and_send_tx( logger.debug(f"nonce: {tx_params['nonce']}") self.last_nonce = Nonce(tx_params["nonce"] + 1) - def _get_tx_params(self, value: Wei = Wei(0)) -> TxParams: + def _get_tx_params(self, value: Wei = Wei(0), gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, priority_fee: Optional[Wei] = None) -> TxParams: """Get generic transaction parameters.""" - return { + params: TxParams = { "from": _addr_to_str(self.address), "value": value, "nonce": max( @@ -562,6 +679,15 @@ def _get_tx_params(self, value: Wei = Wei(0)) -> TxParams: ), } + if gas: + params["gas"] = gas + if max_fee: + params["maxFeePerGas"] = max_fee + if priority_fee: + params["maxPriorityFeePerGas"] = priority_fee + + return params + # ------ Helpers ------------------------------------------------------------ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token: From 14130d1b7e6058cf77952257b27044d416e61d26 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 6 Jun 2024 01:55:39 +0300 Subject: [PATCH 22/35] typing fixes --- uniswap/types.py | 5 +++-- uniswap/uniswap4.py | 14 ++++---------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/uniswap/types.py b/uniswap/types.py index acc5105a..beb65b75 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -1,6 +1,7 @@ from typing import Union from dataclasses import dataclass from eth_typing.evm import Address, ChecksumAddress +from typing import List, Tuple AddressLike = Union[Address, ChecksumAddress] @@ -44,7 +45,7 @@ class UniswapV4_PathKey: # Ticks that involve positions must be a multiple of tick spacing tickSpacing : int # The hooks of the pool - hooks : list[Address] + hooks : List[Address] - def __repr__(self) -> (Address, Address, int, int, list[Address]): + def __repr__(self) -> Tuple(Address, Address, int, int, List[Address]): return (self.currency0, self.currency1, self.fee, self.tickSpacing, self.hooks) \ No newline at end of file diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 12782217..24a6831d 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -130,7 +130,7 @@ def get_quote_exact_input_single( sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, - ): + ) -> Any: """ :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. @@ -173,7 +173,7 @@ def get_quote_exact_output_single( sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, - ): + ) -> Any: """ :if `zero_to_one` is true: given `qty` amount of the input `token0`, returns the maximum output amount of output `token1`. :if `zero_to_one` is false: returns the minimum amount of `token0` required to buy `qty` amount of `token1`. @@ -210,14 +210,11 @@ def get_quote_exact_input( currency: AddressLike, # input token qty: int, path : list[UniswapV4_PathKey], - ): + ) -> Any: """ :path is a swap route """ - if currency0 == currency1: - raise ValueError - quote_params = { "exactCurrency": currency, "path": path, @@ -236,14 +233,11 @@ def get_quote_exact_output( currency: AddressLike, # input token qty: int, path : list[UniswapV4_PathKey], - ): + ) -> Any: """ :path is a swap route """ - if currency0 == currency1: - raise ValueError - quote_params = { "exactCurrency": currency, "path": path, From 88e927521d5d3c4f3fff5f872ef7af13ac85223a Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 6 Jun 2024 02:10:01 +0300 Subject: [PATCH 23/35] typing fix 2 --- uniswap/types.py | 2 +- uniswap/uniswap4.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/uniswap/types.py b/uniswap/types.py index beb65b75..a0a2a38c 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -47,5 +47,5 @@ class UniswapV4_PathKey: # The hooks of the pool hooks : List[Address] - def __repr__(self) -> Tuple(Address, Address, int, int, List[Address]): + def __repr__(self) -> Tuple[Address, Address, int, int, List[Address]]: return (self.currency0, self.currency1, self.fee, self.tickSpacing, self.hooks) \ No newline at end of file diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 24a6831d..873b00e2 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -209,7 +209,7 @@ def get_quote_exact_input( self, currency: AddressLike, # input token qty: int, - path : list[UniswapV4_PathKey], + path : List[UniswapV4_PathKey], ) -> Any: """ :path is a swap route @@ -232,7 +232,7 @@ def get_quote_exact_output( self, currency: AddressLike, # input token qty: int, - path : list[UniswapV4_PathKey], + path : List[UniswapV4_PathKey], ) -> Any: """ :path is a swap route From ab23b0489e8ea2eb963a165e55eae1855523529d Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 6 Jun 2024 02:36:03 +0300 Subject: [PATCH 24/35] typing fixes --- uniswap/types.py | 3 --- uniswap/uniswap4.py | 6 ++++-- uniswap/util.py | 7 +++++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/uniswap/types.py b/uniswap/types.py index a0a2a38c..a43c1324 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -46,6 +46,3 @@ class UniswapV4_PathKey: tickSpacing : int # The hooks of the pool hooks : List[Address] - - def __repr__(self) -> Tuple[Address, Address, int, int, List[Address]]: - return (self.currency0, self.currency1, self.fee, self.tickSpacing, self.hooks) \ No newline at end of file diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 873b00e2..6babcaa8 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -215,9 +215,10 @@ def get_quote_exact_input( :path is a swap route """ + quote_path = [item.astulpe() for item in path] quote_params = { "exactCurrency": currency, - "path": path, + "path": quote_path, "recipient": self.address, "exactAmount": qty, } @@ -238,9 +239,10 @@ def get_quote_exact_output( :path is a swap route """ + quote_path = [item.astulpe() for item in path] quote_params = { "exactCurrency": currency, - "path": path, + "path": quote_path, "recipient": self.address, "exactAmount": qty, } diff --git a/uniswap/util.py b/uniswap/util.py index 1be2f993..7a50c5d9 100644 --- a/uniswap/util.py +++ b/uniswap/util.py @@ -81,8 +81,11 @@ def _encode_path(token_in: AddressLike, route: List[Tuple[int, AddressLike]]) -> # Adapted from: https://github.com/Uniswap/v3-sdk/blob/main/src/utils/encodeSqrtRatioX96.ts -def decode_sqrt_ratioX96(amount_0: int, amount_1: int) -> int: - return int(amount_0 * amount_0 * 10**amount_1 >> 192) +def decode_sqrt_ratioX96(sqrtPriceX96: int) -> float: + Q96 = 2**96 + ratio = sqrtPriceX96 / Q96 + price = ratio**2 + return price # Adapted from: https://github.com/Uniswap/v3-sdk/blob/main/src/utils/encodeSqrtRatioX96.ts def encode_sqrt_ratioX96(amount_0: int, amount_1: int) -> int: From 1a8199911ecece3c2e3c51ad54fa724a795eade8 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Thu, 6 Jun 2024 02:42:25 +0300 Subject: [PATCH 25/35] 123 --- uniswap/uniswap4.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 6babcaa8..6344f624 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -2,6 +2,7 @@ import time import logging import functools +import dataclasses from typing import List, Any, Optional, Union, Tuple, Dict from web3 import Web3 @@ -215,7 +216,7 @@ def get_quote_exact_input( :path is a swap route """ - quote_path = [item.astulpe() for item in path] + quote_path = [dataclasses.astuple(item) for item in path] quote_params = { "exactCurrency": currency, "path": quote_path, @@ -239,7 +240,7 @@ def get_quote_exact_output( :path is a swap route """ - quote_path = [item.astulpe() for item in path] + quote_path = [dataclasses.astuple(item) for item in path] quote_params = { "exactCurrency": currency, "path": quote_path, From 9444386cb9f34dfb765f22ec783e5bf1f2da62f3 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sat, 8 Jun 2024 02:53:12 +0300 Subject: [PATCH 26/35] ABIs update --- uniswap/assets/uniswap-v4/poolmanager.abi | 2653 ++++++++++----------- uniswap/assets/uniswap-v4/quoter.abi | 796 ++++++- 2 files changed, 1995 insertions(+), 1454 deletions(-) diff --git a/uniswap/assets/uniswap-v4/poolmanager.abi b/uniswap/assets/uniswap-v4/poolmanager.abi index 8ede6bcc..3c09a94b 100644 --- a/uniswap/assets/uniswap-v4/poolmanager.abi +++ b/uniswap/assets/uniswap-v4/poolmanager.abi @@ -1,1370 +1,1289 @@ [ - { - "inputs": [ - { - "internalType": "uint256", - "name": "_controllerGasLimit", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "CannotUpdateEmptyPosition", - "type": "error" - }, - { - "inputs": [], - "name": "CurrencyNotSettled", - "type": "error" - }, - { - "inputs": [], - "name": "DelegateCallNotAllowed", - "type": "error" - }, - { - "inputs": [], - "name": "ERC20TransferFailed", - "type": "error" - }, - { - "inputs": [], - "name": "FeeTooLarge", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "hooks", - "type": "address" - } - ], - "name": "HookAddressNotValid", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidCaller", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidHookResponse", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidSqrtRatio", - "type": "error" - }, - { - "inputs": [], - "name": "InvalidTick", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "locker", - "type": "address" - } - ], - "name": "LockedBy", - "type": "error" - }, - { - "inputs": [], - "name": "MaxCurrenciesTouched", - "type": "error" - }, - { - "inputs": [], - "name": "NativeTransferFailed", - "type": "error" - }, - { - "inputs": [], - "name": "NoLiquidityToReceiveFees", - "type": "error" - }, - { - "inputs": [], - "name": "NotPoolManagerToken", - "type": "error" - }, - { - "inputs": [], - "name": "PoolAlreadyInitialized", - "type": "error" - }, - { - "inputs": [], - "name": "PoolNotInitialized", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint160", - "name": "sqrtPriceCurrentX96", - "type": "uint160" - }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "name": "PriceLimitAlreadyExceeded", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "name": "PriceLimitOutOfBounds", - "type": "error" - }, - { - "inputs": [], - "name": "ProtocolFeeCannotBeFetched", - "type": "error" - }, - { - "inputs": [], - "name": "SwapAmountCannotBeZero", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int24", - "name": "tick", - "type": "int24" - } - ], - "name": "TickLiquidityOverflow", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int24", - "name": "tickLower", - "type": "int24" - } - ], - "name": "TickLowerOutOfBounds", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int24", - "name": "tick", - "type": "int24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - } - ], - "name": "TickMisaligned", - "type": "error" - }, - { - "inputs": [], - "name": "TickSpacingTooLarge", - "type": "error" - }, - { - "inputs": [], - "name": "TickSpacingTooSmall", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - } - ], - "name": "TickUpperOutOfBounds", - "type": "error" - }, - { - "inputs": [ - { - "internalType": "int24", - "name": "tickLower", - "type": "int24" - }, - { - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - } - ], - "name": "TicksMisordered", - "type": "error" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "hookSwapFee", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "hookWithdrawFee", - "type": "uint8" - } - ], - "name": "HookFeeUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "indexed": true, - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "indexed": false, - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "indexed": false, - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "name": "Initialize", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "int24", - "name": "tickLower", - "type": "int24" - }, - { - "indexed": false, - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - }, - { - "indexed": false, - "internalType": "int256", - "name": "liquidityDelta", - "type": "int256" - } - ], - "name": "ModifyPosition", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "oldOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnerChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "protocolFeeController", - "type": "address" - } - ], - "name": "ProtocolFeeControllerUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "protocolSwapFee", - "type": "uint8" - }, - { - "indexed": false, - "internalType": "uint8", - "name": "protocolWithdrawFee", - "type": "uint8" - } - ], - "name": "ProtocolFeeUpdated", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "indexed": false, - "internalType": "int128", - "name": "amount0", - "type": "int128" - }, - { - "indexed": false, - "internalType": "int128", - "name": "amount1", - "type": "int128" - }, - { - "indexed": false, - "internalType": "uint160", - "name": "sqrtPriceX96", - "type": "uint160" - }, - { - "indexed": false, - "internalType": "uint128", - "name": "liquidity", - "type": "uint128" - }, - { - "indexed": false, - "internalType": "int24", - "name": "tick", - "type": "int24" - }, - { - "indexed": false, - "internalType": "uint24", - "name": "fee", - "type": "uint24" - } - ], - "name": "Swap", - "type": "event" - }, - { - "inputs": [], - "name": "MAX_TICK_SPACING", - "outputs": [ - { - "internalType": "int24", - "name": "", - "type": "int24" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_PROTOCOL_FEE_DENOMINATOR", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "MIN_TICK_SPACING", - "outputs": [ - { - "internalType": "int24", - "name": "", - "type": "int24" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "collectHookFees", - "outputs": [ - { - "internalType": "uint256", - "name": "amountCollected", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "collectProtocolFees", - "outputs": [ - { - "internalType": "uint256", - "name": "amountCollected", - "type": "uint256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "amount0", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "amount1", - "type": "uint256" - } - ], - "name": "donate", - "outputs": [ - { - "internalType": "BalanceDelta", - "name": "delta", - "type": "int256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "slot", - "type": "bytes32" - } - ], - "name": "extsload", - "outputs": [ - { - "internalType": "bytes32", - "name": "value", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "startSlot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "nSlots", - "type": "uint256" - } - ], - "name": "extsload", - "outputs": [ - { - "internalType": "bytes", - "name": "", - "type": "bytes" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "locker", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency", - "type": "address" - } - ], - "name": "currencyDelta", - "outputs": [ - { - "internalType": "int256", - "name": "", - "type": "int256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "int24", - "name": "tickLower", - "type": "int24" - }, - { - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - } - ], - "name": "getLiquidity", - "outputs": [ - { - "internalType": "uint128", - "name": "liquidity", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - } - ], - "name": "getLiquidity", - "outputs": [ - { - "internalType": "uint128", - "name": "liquidity", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "id", - "type": "uint256" - } - ], - "name": "getNonzeroDeltaCount", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - }, - { - "internalType": "address", + { + "inputs": [ + { + "internalType": "uint256", + "name": "controllerGasLimit", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "AlreadyUnlocked", + "type": "error" + }, + { + "inputs": [], + "name": "CurrenciesOutOfOrderOrEqual", + "type": "error" + }, + { + "inputs": [], + "name": "CurrencyNotSettled", + "type": "error" + }, + { + "inputs": [], + "name": "DelegateCallNotAllowed", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidCaller", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidProtocolFee", + "type": "error" + }, + { + "inputs": [], + "name": "ManagerLocked", + "type": "error" + }, + { + "inputs": [], + "name": "NonZeroNativeValue", + "type": "error" + }, + { + "inputs": [], + "name": "PoolNotInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolFeeCannotBeFetched", + "type": "error" + }, + { + "inputs": [], + "name": "SwapAmountCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "TickSpacingTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "TickSpacingTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "UnauthorizedDynamicLPFeeUpdate", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "indexed": true, + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "indexed": false, + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "name": "Initialize", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "indexed": false, + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + } + ], + "name": "ModifyLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "OperatorSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "protocolFeeController", + "type": "address" + } + ], + "name": "ProtocolFeeControllerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "protocolFee", + "type": "uint24" + } + ], + "name": "ProtocolFeeUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "PoolId", + "name": "id", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount0", + "type": "int128" + }, + { + "indexed": false, + "internalType": "int128", + "name": "amount1", + "type": "int128" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint128", + "name": "liquidity", + "type": "uint128" + }, + { + "indexed": false, + "internalType": "int24", + "name": "tick", + "type": "int24" + }, + { + "indexed": false, + "internalType": "uint24", + "name": "fee", + "type": "uint24" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "caller", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [], + "name": "MAX_TICK_SPACING", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TICK_SPACING", + "outputs": [ + { + "internalType": "int24", + "name": "", + "type": "int24" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "burn", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "collectProtocolFees", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCollected", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "donate", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "delta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "startSlot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nSlots", + "type": "uint256" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "extsload", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "slots", + "type": "bytes32[]" + } + ], + "name": "exttload", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot", + "type": "bytes32" + } + ], + "name": "exttload", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "int24", + "name": "tick", + "type": "int24" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "int24", + "name": "tickLower", + "type": "int24" + }, + { + "internalType": "int24", + "name": "tickUpper", + "type": "int24" + }, + { + "internalType": "int256", + "name": "liquidityDelta", + "type": "int256" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "internalType": "struct IPoolManager.ModifyLiquidityParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "modifyLiquidity", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "callerDelta", + "type": "int256" + }, + { + "internalType": "BalanceDelta", + "name": "feesAccrued", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], "name": "owner", - "type": "address" - }, - { - "internalType": "int24", - "name": "tickLower", - "type": "int24" - }, - { - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - } - ], - "name": "getPosition", - "outputs": [ - { - "components": [ - { - "internalType": "uint128", - "name": "liquidity", - "type": "uint128" - }, - { - "internalType": "uint256", - "name": "feeGrowthInside0LastX128", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "feeGrowthInside1LastX128", - "type": "uint256" - } - ], - "internalType": "struct Position.Info", - "name": "position", - "type": "tuple" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - } - ], - "name": "getSlot0", - "outputs": [ - { - "internalType": "uint160", - "name": "sqrtPriceX96", - "type": "uint160" - }, - { - "internalType": "int24", - "name": "tick", - "type": "int24" - }, - { - "internalType": "uint8", - "name": "protocolSwapFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "protocolWithdrawFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "hookSwapFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "hookWithdrawFee", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "hookAddress", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency", - "type": "address" - } - ], - "name": "hookFeesAccrued", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - }, - { - "internalType": "uint160", - "name": "sqrtPriceX96", - "type": "uint160" - } - ], - "name": "initialize", - "outputs": [ - { - "internalType": "int24", - "name": "tick", - "type": "int24" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "name": "lock", - "outputs": [ - { - "internalType": "bytes", - "name": "result", - "type": "bytes" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "lockedBy", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "lockedByLength", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "int24", - "name": "tickLower", - "type": "int24" - }, - { - "internalType": "int24", - "name": "tickUpper", - "type": "int24" - }, - { - "internalType": "int256", - "name": "liquidityDelta", - "type": "int256" - } - ], - "internalType": "struct IPoolManager.ModifyPositionParams", - "name": "params", - "type": "tuple" - } - ], - "name": "modifyPosition", - "outputs": [ - { - "internalType": "BalanceDelta", - "name": "delta", - "type": "int256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "PoolId", - "name": "id", - "type": "bytes32" - } - ], - "name": "pools", - "outputs": [ - { - "components": [ - { - "internalType": "uint160", - "name": "sqrtPriceX96", - "type": "uint160" - }, - { - "internalType": "int24", - "name": "tick", - "type": "int24" - }, - { - "internalType": "uint8", - "name": "protocolSwapFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "protocolWithdrawFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "hookSwapFee", - "type": "uint8" - }, - { - "internalType": "uint8", - "name": "hookWithdrawFee", - "type": "uint8" - } - ], - "internalType": "struct Pool.Slot0", - "name": "slot0", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "feeGrowthGlobal0X128", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "feeGrowthGlobal1X128", - "type": "uint256" - }, - { - "internalType": "uint128", - "name": "liquidity", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "protocolFeeController", - "outputs": [ - { - "internalType": "contract IProtocolFeeController", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Currency", - "name": "currency", - "type": "address" - } - ], - "name": "protocolFeesAccrued", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Currency", - "name": "currency", - "type": "address" - } - ], - "name": "reservesOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - } - ], - "name": "setHookFees", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_owner", - "type": "address" - } - ], - "name": "setOwner", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IProtocolFeeController", - "name": "controller", - "type": "address" - } - ], - "name": "setProtocolFeeController", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - } - ], - "name": "setProtocolFees", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Currency", - "name": "currency", - "type": "address" - } - ], - "name": "settle", - "outputs": [ - { - "internalType": "uint256", - "name": "paid", - "type": "uint256" - } - ], - "stateMutability": "payable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "Currency", - "name": "currency0", - "type": "address" - }, - { - "internalType": "Currency", - "name": "currency1", - "type": "address" - }, - { - "internalType": "uint24", - "name": "fee", - "type": "uint24" - }, - { - "internalType": "int24", - "name": "tickSpacing", - "type": "int24" - }, - { - "internalType": "contract IHooks", - "name": "hooks", - "type": "address" - } - ], - "internalType": "struct IPoolManager.PoolKey", - "name": "key", - "type": "tuple" - }, - { - "components": [ - { - "internalType": "bool", - "name": "zeroForOne", - "type": "bool" - }, - { - "internalType": "int256", - "name": "amountSpecified", - "type": "int256" - }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "internalType": "struct IPoolManager.SwapParams", - "name": "params", - "type": "tuple" - } - ], - "name": "swap", - "outputs": [ - { - "internalType": "BalanceDelta", - "name": "delta", - "type": "int256" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "Currency", - "name": "currency", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "take", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "stateMutability": "payable", - "type": "receive" - } + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "protocolFeeController", + "outputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "protocolFeesAccrued", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setOperator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint24", + "name": "newProtocolFee", + "type": "uint24" + } + ], + "name": "setProtocolFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "controller", + "type": "address" + } + ], + "name": "setProtocolFeeController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "settle", + "outputs": [ + { + "internalType": "uint256", + "name": "paid", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "int256", + "name": "amountSpecified", + "type": "int256" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + } + ], + "internalType": "struct IPoolManager.SwapParams", + "name": "params", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [ + { + "internalType": "BalanceDelta", + "name": "swapDelta", + "type": "int256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + } + ], + "name": "sync", + "outputs": [ + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "Currency", + "name": "currency", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "take", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "receiver", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "unlock", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "key", + "type": "tuple" + }, + { + "internalType": "uint24", + "name": "newDynamicLPFee", + "type": "uint24" + } + ], + "name": "updateDynamicLPFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } ] \ No newline at end of file diff --git a/uniswap/assets/uniswap-v4/quoter.abi b/uniswap/assets/uniswap-v4/quoter.abi index 9eb5372a..e9069975 100644 --- a/uniswap/assets/uniswap-v4/quoter.abi +++ b/uniswap/assets/uniswap-v4/quoter.abi @@ -1,88 +1,710 @@ [ - { - "inputs": [ - { - "internalType": "address", - "name": "_poolManager", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "inputs": [], - "name": "poolManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes", "name": "path", "type": "bytes" }, - { "internalType": "uint256", "name": "amountIn", "type": "uint256" } - ], - "name": "quoteExactInput", - "outputs": [ - { "internalType": "uint256", "name": "amountOut", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "tokenIn", "type": "address" }, - { "internalType": "address", "name": "tokenOut", "type": "address" }, - { "internalType": "uint24", "name": "fee", "type": "uint24" }, - { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "name": "quoteExactInputSingle", - "outputs": [ - { "internalType": "uint256", "name": "amountOut", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "bytes", "name": "path", "type": "bytes" }, - { "internalType": "uint256", "name": "amountOut", "type": "uint256" } - ], - "name": "quoteExactOutput", - "outputs": [ - { "internalType": "uint256", "name": "amountIn", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { "internalType": "address", "name": "tokenIn", "type": "address" }, - { "internalType": "address", "name": "tokenOut", "type": "address" }, - { "internalType": "uint24", "name": "fee", "type": "uint24" }, - { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, - { - "internalType": "uint160", - "name": "sqrtPriceLimitX96", - "type": "uint160" - } - ], - "name": "quoteExactOutputSingle", - "outputs": [ - { "internalType": "uint256", "name": "amountIn", "type": "uint256" } - ], - "stateMutability": "nonpayable", - "type": "function" - } -] + { + "inputs": [ + { + "internalType": "address", + "name": "_poolManager", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InsufficientAmountOut", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidLockCaller", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidQuoteBatchParams", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUnlockCallbackSender", + "type": "error" + }, + { + "inputs": [], + "name": "LockFailure", + "type": "error" + }, + { + "inputs": [], + "name": "NotSelf", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "revertData", + "type": "bytes" + } + ], + "name": "UnexpectedRevertBytes", + "type": "error" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "exactCurrency", + "type": "address" + }, + { + "components": [ + { + "internalType": "Currency", + "name": "intermediateCurrency", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct PathKey[]", + "name": "path", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + } + ], + "internalType": "struct IQuoter.QuoteExactParams", + "name": "params", + "type": "tuple" + } + ], + "name": "_quoteExactInput", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "poolKey", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct IQuoter.QuoteExactSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "_quoteExactInputSingle", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "exactCurrency", + "type": "address" + }, + { + "components": [ + { + "internalType": "Currency", + "name": "intermediateCurrency", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct PathKey[]", + "name": "path", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + } + ], + "internalType": "struct IQuoter.QuoteExactParams", + "name": "params", + "type": "tuple" + } + ], + "name": "_quoteExactOutput", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "poolKey", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct IQuoter.QuoteExactSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "_quoteExactOutputSingle", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "manager", + "outputs": [ + { + "internalType": "contract IPoolManager", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "exactCurrency", + "type": "address" + }, + { + "components": [ + { + "internalType": "Currency", + "name": "intermediateCurrency", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct PathKey[]", + "name": "path", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + } + ], + "internalType": "struct IQuoter.QuoteExactParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactInput", + "outputs": [ + { + "internalType": "int128[]", + "name": "deltaAmounts", + "type": "int128[]" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksLoadedList", + "type": "uint32[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "poolKey", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct IQuoter.QuoteExactSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactInputSingle", + "outputs": [ + { + "internalType": "int128[]", + "name": "deltaAmounts", + "type": "int128[]" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksLoaded", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "Currency", + "name": "exactCurrency", + "type": "address" + }, + { + "components": [ + { + "internalType": "Currency", + "name": "intermediateCurrency", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct PathKey[]", + "name": "path", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + } + ], + "internalType": "struct IQuoter.QuoteExactParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactOutput", + "outputs": [ + { + "internalType": "int128[]", + "name": "deltaAmounts", + "type": "int128[]" + }, + { + "internalType": "uint160[]", + "name": "sqrtPriceX96AfterList", + "type": "uint160[]" + }, + { + "internalType": "uint32[]", + "name": "initializedTicksLoadedList", + "type": "uint32[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "Currency", + "name": "currency0", + "type": "address" + }, + { + "internalType": "Currency", + "name": "currency1", + "type": "address" + }, + { + "internalType": "uint24", + "name": "fee", + "type": "uint24" + }, + { + "internalType": "int24", + "name": "tickSpacing", + "type": "int24" + }, + { + "internalType": "contract IHooks", + "name": "hooks", + "type": "address" + } + ], + "internalType": "struct PoolKey", + "name": "poolKey", + "type": "tuple" + }, + { + "internalType": "bool", + "name": "zeroForOne", + "type": "bool" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint128", + "name": "exactAmount", + "type": "uint128" + }, + { + "internalType": "uint160", + "name": "sqrtPriceLimitX96", + "type": "uint160" + }, + { + "internalType": "bytes", + "name": "hookData", + "type": "bytes" + } + ], + "internalType": "struct IQuoter.QuoteExactSingleParams", + "name": "params", + "type": "tuple" + } + ], + "name": "quoteExactOutputSingle", + "outputs": [ + { + "internalType": "int128[]", + "name": "deltaAmounts", + "type": "int128[]" + }, + { + "internalType": "uint160", + "name": "sqrtPriceX96After", + "type": "uint160" + }, + { + "internalType": "uint32", + "name": "initializedTicksLoaded", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "unlockCallback", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file From 7383ee09e19882c9b72eb3a681d4c694dfa13ed8 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sun, 9 Jun 2024 22:13:26 +0300 Subject: [PATCH 27/35] final fixes and clean-ups --- uniswap/types.py | 2 +- uniswap/uniswap4.py | 86 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 74 insertions(+), 14 deletions(-) diff --git a/uniswap/types.py b/uniswap/types.py index a43c1324..e58ee8b1 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -35,7 +35,7 @@ def __repr__(self) -> str: return f"Tick info (liquidityGross: {self.liquidityGross}; liquidityNet: {self.liquidityNet}; feeGrowthOutside0X128: {self.feeGrowthOutside0X128}; feeGrowthOutside1X128: {self.feeGrowthOutside1X128!r})" @dataclass -class UniswapV4_PathKey: +class UniswapV4_path_key: # The lower currency of the pool, sorted numerically currency0 : Address # The higher currency of the pool, sorted numerically diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 6344f624..a9193ff6 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -17,7 +17,7 @@ Nonce, HexBytes, ) -from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info, UniswapV4_PathKey +from .types import AddressLike, UniswapV4_slot0, UniswapV4_position_info, UniswapV4_tick_info, UniswapV4_path_key from .token import ERC20Token from .exceptions import InvalidToken, InsufficientBalance from .util import ( @@ -210,7 +210,7 @@ def get_quote_exact_input( self, currency: AddressLike, # input token qty: int, - path : List[UniswapV4_PathKey], + path : List[UniswapV4_path_key], ) -> Any: """ :path is a swap route @@ -234,7 +234,7 @@ def get_quote_exact_output( self, currency: AddressLike, # input token qty: int, - path : List[UniswapV4_PathKey], + path : List[UniswapV4_path_key], ) -> Any: """ :path is a swap route @@ -384,6 +384,7 @@ def swap( qty: int, fee: int, tick_spacing: int, + hook_data : bytes, sqrt_price_limit_x96: int = 0, zero_for_one: bool = True, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, @@ -437,6 +438,7 @@ def initialize( fee: int, tick_spacing: int, sqrt_price_limit_x96: int, + hook_data : bytes, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, @@ -467,6 +469,7 @@ def initialize( { "key": pool_key, "sqrtPriceX96": sqrt_price_limit_x96, + "hookData": hook_data, } ), self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), @@ -476,10 +479,12 @@ def donate( self, currency0: ERC20Token, currency1: ERC20Token, - qty: int, + qty1: int, + qty2: int, fee: int, tick_spacing: int, sqrt_price_limit_x96: int, + hook_data : bytes, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, @@ -509,13 +514,15 @@ def donate( self.router.functions.donate( { "key": pool_key, - "sqrtPriceX96": sqrt_price_limit_x96, + "amount0": qty1, + "amount1": qty2, + "hookData": hook_data, } ), self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) - def modify_position( + def modify_liquidity( self, currency0: ERC20Token, currency1: ERC20Token, @@ -524,6 +531,8 @@ def modify_position( tick_spacing: int, tick_upper: int, tick_lower: int, + salt : int, + hook_data : bytes, hooks: Union[AddressLike, str, None] = NOHOOK_ADDRESS, gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, @@ -550,17 +559,19 @@ def modify_position( "hooks": hooks, } - modify_position_params = { + modify_liquidity_params = { "tickLower": tick_lower, "tickUpper": tick_upper, "liquidityDelta": qty, + "salt": salt, } return self._build_and_send_tx( - self.router.functions.modifyPosition( + self.router.functions.modifyLiquidity( { "key": pool_key, "params": modify_position_params, + "hookData": hook_data, } ), self._get_tx_params(value=Wei(qty), gas = gas, max_fee = max_fee, priority_fee = priority_fee), @@ -568,8 +579,7 @@ def modify_position( def settle( self, - currency0: ERC20Token, - qty: int, + currency0: Union[AddressLike, str, None], gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, priority_fee: Optional[Wei] = None, @@ -589,7 +599,7 @@ def settle( def take( self, - currency0: ERC20Token, + currency0: Union[AddressLike, str, None], to: AddressLike, qty: int, gas: Optional[Wei] = None, @@ -612,6 +622,56 @@ def take( self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) + def mint( + self, + currency0: Union[AddressLike, str, None], + id: int, + qty: int, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, + ) -> HexBytes: + """ + :Called by the user to net out some value owed to the user + :Can also be used as a mechanism for _free_ flash loans + """ + + return self._build_and_send_tx( + self.router.functions.mint( + { + "currency ": currency0, + "id ": id, + "amount ": qty, + } + ), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), + ) + + def burn( + self, + currency0: Union[AddressLike, str, None], + id: int, + qty: int, + gas: Optional[Wei] = None, + max_fee: Optional[Wei] = None, + priority_fee: Optional[Wei] = None, + ) -> HexBytes: + """ + :Called by the user to net out some value owed to the user + :Can also be used as a mechanism for _free_ flash loans + """ + + return self._build_and_send_tx( + self.router.functions.burn( + { + "currency ": currency0, + "id ": id, + "amount ": qty, + } + ), + self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), + ) + # ------ Wallet balance ------------------------------------------------------------ def get_eth_balance(self) -> Wei: """Get the balance of ETH for your address.""" @@ -715,8 +775,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token symbol = _symbol return ERC20Token(symbol, address, name, decimals) - def get_pool_id(self, currency0: AddressLike, currency1: AddressLike, fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: - if int(currency0) > int(currency1): + def get_pool_id(self, currency0: str, currency1: str, fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: + if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) return pool_id From 890dab52bb8a6bd19858e36d7ed4e9e2ae011402 Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sun, 9 Jun 2024 22:17:29 +0300 Subject: [PATCH 28/35] 1 --- uniswap/uniswap4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index a9193ff6..fcda0171 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -600,7 +600,7 @@ def settle( def take( self, currency0: Union[AddressLike, str, None], - to: AddressLike, + to: Union[AddressLike, str, None], qty: int, gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, @@ -775,7 +775,7 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token symbol = _symbol return ERC20Token(symbol, address, name, decimals) - def get_pool_id(self, currency0: str, currency1: str, fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: + def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) From a252678740d26ed7f729acd5ff887520c6e5ba2f Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Sun, 9 Jun 2024 22:25:30 +0300 Subject: [PATCH 29/35] 2 --- uniswap/uniswap4.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index fcda0171..240dc57f 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -570,7 +570,7 @@ def modify_liquidity( self.router.functions.modifyLiquidity( { "key": pool_key, - "params": modify_position_params, + "params": modify_liquidity_params, "hookData": hook_data, } ), @@ -580,6 +580,7 @@ def modify_liquidity( def settle( self, currency0: Union[AddressLike, str, None], + qty: int, gas: Optional[Wei] = None, max_fee: Optional[Wei] = None, priority_fee: Optional[Wei] = None, @@ -776,6 +777,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: + currency0 = str(currency0) + currency1 = str(currency1) if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) From 70a627bcd6294482db18017a1f836766dac017be Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Mon, 10 Jun 2024 06:14:19 +0300 Subject: [PATCH 30/35] more stupidity fix --- uniswap/types.py | 6 +++--- uniswap/uniswap4.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/uniswap/types.py b/uniswap/types.py index e58ee8b1..e131b7a5 100644 --- a/uniswap/types.py +++ b/uniswap/types.py @@ -37,12 +37,12 @@ def __repr__(self) -> str: @dataclass class UniswapV4_path_key: # The lower currency of the pool, sorted numerically - currency0 : Address + currency0 : str # The higher currency of the pool, sorted numerically - currency1 : Address + currency1 : str # The pool swap fee, capped at 1_000_000. If the first bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 fee : int # Ticks that involve positions must be a multiple of tick spacing tickSpacing : int # The hooks of the pool - hooks : List[Address] + hooks : str diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 240dc57f..9128eb0e 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -777,8 +777,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: - currency0 = str(currency0) - currency1 = str(currency1) + currency0 = str(self.w3.to_checksum_address(currency0)) + currency1 = str(self.w3.to_checksum_address(currency1)) if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) From 371454b568896f3536c8645ad1eb17c3bf78e57c Mon Sep 17 00:00:00 2001 From: liquid-8 Date: Mon, 10 Jun 2024 06:25:12 +0300 Subject: [PATCH 31/35] 1 --- uniswap/uniswap4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 9128eb0e..6cc02461 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -777,8 +777,8 @@ def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token return ERC20Token(symbol, address, name, decimals) def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: - currency0 = str(self.w3.to_checksum_address(currency0)) - currency1 = str(self.w3.to_checksum_address(currency1)) + currency0 = self.w3.to_checksum_address(str(currency0)) + currency1 = self.w3.to_checksum_address(str(currency1)) if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)])) From f442f4b716cc849c0a957a8b9afaab44554dbea1 Mon Sep 17 00:00:00 2001 From: Yohan K <72107640+liquid-8@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:13:45 +0300 Subject: [PATCH 32/35] Update uniswap/constants.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Erik Bjäreholt --- uniswap/constants.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/uniswap/constants.py b/uniswap/constants.py index 60042de5..6f75799e 100644 --- a/uniswap/constants.py +++ b/uniswap/constants.py @@ -77,25 +77,25 @@ "sepolia": "0xC532a74256D3Db42D0Bf7a0400fEFDbad7694008", } -# need to replace with actual addresses after release +# TODO: replace with actual addresses after official deployment _poolmanager_contract_addresses = { - "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + #"mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "rinkeby": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "görli": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - "xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", - "binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + #"xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", + #"binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", "binance_testnet": "0x6725F303b657a9451d8BA641348b6761A6CC7a17", } -# need to replace with actual addresses after release +# TODO: replace with actual addresses after official deployment _quoter_contract_addresses = { - "mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", + #"mainnet": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "ropsten": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "rinkeby": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", "görli": "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f", - "xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", - "binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", + #"xdai": "0xA818b4F111Ccac7AA31D0BCc0806d64F2E0737D7", + #"binance": "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", "binance_testnet": "0x6725F303b657a9451d8BA641348b6761A6CC7a17", } MAX_UINT_128 = (2**128) - 1 From 812b5d6b589fb312b236eae758defd4b1b8c82bc Mon Sep 17 00:00:00 2001 From: Yohan K <72107640+liquid-8@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:24:56 +0300 Subject: [PATCH 33/35] Update uniswap4.py: get_token() removed --- uniswap/uniswap4.py | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index 6cc02461..be4cee80 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -747,35 +747,7 @@ def _get_tx_params(self, value: Wei = Wei(0), gas: Optional[Wei] = None, max_fee return params # ------ Helpers ------------------------------------------------------------ - - def get_token(self, address: AddressLike, abi_name: str = "erc20") -> ERC20Token: - """ - Retrieves metadata from the ERC20 contract of a given token, like its name, symbol, and decimals. - """ - # FIXME: This function should always return the same output for the same input - # and would therefore benefit from caching - if address == ETH_ADDRESS: - return ERC20Token("ETH", address, "Ether", 18) - token_contract = _load_contract(self.w3, abi_name, address=address) - try: - _name = token_contract.functions.name().call() - _symbol = token_contract.functions.symbol().call() - decimals = token_contract.functions.decimals().call() - except Exception as e: - logger.warning( - f"Exception occurred while trying to get token {_addr_to_str(address)}: {e}" - ) - raise InvalidToken(address) - try: - name = _name.decode() - except: - name = _name - try: - symbol = _symbol.decode() - except: - symbol = _symbol - return ERC20Token(symbol, address, name, decimals) - + def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: currency0 = self.w3.to_checksum_address(str(currency0)) currency1 = self.w3.to_checksum_address(str(currency1)) @@ -785,4 +757,4 @@ def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union return pool_id - \ No newline at end of file + From 30a4efe1eba29fa25d976adc3e3e2a0323959b32 Mon Sep 17 00:00:00 2001 From: Yohan K <72107640+liquid-8@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:32:45 +0300 Subject: [PATCH 34/35] Update uniswap4.py: removed wallet-related functions --- uniswap/uniswap4.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index be4cee80..a8776565 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -673,20 +673,6 @@ def burn( self._get_tx_params(gas = gas, max_fee = max_fee, priority_fee = priority_fee), ) - # ------ Wallet balance ------------------------------------------------------------ - def get_eth_balance(self) -> Wei: - """Get the balance of ETH for your address.""" - return self.w3.eth.get_balance(self.address) - - def get_token_balance(self, token: AddressLike) -> int: - """Get the balance of a token for your address.""" - _validate_address(token) - if _addr_to_str(token) == ETH_ADDRESS: - return self.get_eth_balance() - erc20 = _load_contract_erc20(self.w3, token) - balance: int = erc20.functions.balanceOf(self.address).call() - return balance - # ------ Approval Utils ------------------------------------------------------------ def approve(self, token: AddressLike, max_approval: Optional[int] = None) -> None: """Give an exchange/router max approval of a token.""" From 1fee8752c122247e94e7a5e155a49c7597f3f80a Mon Sep 17 00:00:00 2001 From: Yohan K <72107640+liquid-8@users.noreply.github.com> Date: Mon, 10 Jun 2024 22:59:59 +0300 Subject: [PATCH 35/35] Update uniswap4.py: removed to_checksum_address in get_pool_id() --- uniswap/uniswap4.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uniswap/uniswap4.py b/uniswap/uniswap4.py index a8776565..d777a894 100644 --- a/uniswap/uniswap4.py +++ b/uniswap/uniswap4.py @@ -735,8 +735,8 @@ def _get_tx_params(self, value: Wei = Wei(0), gas: Optional[Wei] = None, max_fee # ------ Helpers ------------------------------------------------------------ def get_pool_id(self, currency0: Union[AddressLike, str, None], currency1: Union[AddressLike, str, None], fee : int, tickSpacing : int, hooks : Union[AddressLike, str, None] = NOHOOK_ADDRESS) -> bytes: - currency0 = self.w3.to_checksum_address(str(currency0)) - currency1 = self.w3.to_checksum_address(str(currency1)) + currency0 = str(currency0) + currency1 = str(currency1) if int(currency0, 16) > int(currency1, 16): currency0 , currency1 = currency1 , currency0 pool_id = bytes(self.w3.solidity_keccak(["address", "address", "int24", "int24", "address"], [(currency0, currency1, fee, tickSpacing, hooks)]))