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 71a12ed..17206bd 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 53d0a5b..a165fc9 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 0000000..32c6bf9 --- /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 12959a7..50678d8 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 50678d8..a950882 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 a950882..70c6fce 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 a165fc9..4c23bbb 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 32c6bf9..dac0f73 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 761e634..1be2f99 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 0000000..dd6e9c6 --- /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 70c6fce..df4fa4b 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 dac0f73..918a45c 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 4c23bbb..9af461f 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 dd6e9c6..8ede6bc 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 df4fa4b..f86daea 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 d55ec98..1df836d 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 918a45c..60a1f2e 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 9af461f..4e50bf0 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 60a1f2e..ee23367 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 ee23367..455cf9e 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 455cf9e..19fae85 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 19fae85..47ef25d 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 47ef25d..4e5ed4b 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 4e5ed4b..55e2bd8 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 55e2bd8..c378f4c 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 c378f4c..93c3d47 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 93c3d47..934543a 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 934543a..46e2e60 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 46e2e60..2357982 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 0000000..9eb5372 --- /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 f86daea..820af35 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 1df836d..acc5105 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 2357982..1278221 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 acc5105..beb65b7 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 1278221..24a6831 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 beb65b7..a0a2a38 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 24a6831..873b00e 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 a0a2a38..a43c132 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 873b00e..6babcaa 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 1be2f99..7a50c5d 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 6babcaa..6344f62 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 8ede6bc..3c09a94 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 9eb5372..e906997 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 a43c132..e58ee8b 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 6344f62..a9193ff 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 a9193ff..fcda017 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 fcda017..240dc57 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 e58ee8b..e131b7a 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 240dc57..9128eb0 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 9128eb0..6cc0246 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 60042de..6f75799 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 6cc0246..be4cee8 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 be4cee8..a877656 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 a877656..d777a89 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)])) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy