diff --git a/src/merkle_distributor/distribution_tree.py b/src/merkle_distributor/distribution_tree.py index 9ca0db8..799a5d8 100644 --- a/src/merkle_distributor/distribution_tree.py +++ b/src/merkle_distributor/distribution_tree.py @@ -20,6 +20,7 @@ OraclesSettings, Rewards, get_uniswap_v3_staked_eth_balances, + get_uniswap_v3_full_range_balances ) logger = logging.getLogger(__name__) @@ -72,6 +73,9 @@ def __init__( self.uniswap_v3_staked_eth_pairs: Set[ChecksumAddress] = oracles_settings[ "uniswap_v3_staked_eth_pairs" ] + self.uniswap_v3_full_range_pairs: Set[ChecksumAddress] = oracles_settings[ + "uniswap_v3_full_range_pairs" + ] self.erc20_tokens: Dict[ChecksumAddress, BlockNumber] = oracles_settings[ "erc20_tokens" ] @@ -237,6 +241,13 @@ def get_balances( staked_eth_token_address=self.staked_eth_token_address, to_block=block_number, ) + elif contract_address in self.uniswap_v3_full_range_pairs: + logger.info(f"Fetching Uniswap V3 infinity positions balances: pool={contract_address}") + return get_uniswap_v3_full_range_balances( + subgraph_url=self.uniswap_v3_subgraph_url, + pool_address=contract_address, + to_block=block_number, + ) elif contract_address in self.uniswap_v3_pairs: logger.info(f"Fetching Uniswap V3 balances: pool={contract_address}") return get_uniswap_v3_balances( diff --git a/src/merkle_distributor/utils.py b/src/merkle_distributor/utils.py index c52e6f1..744ec5b 100644 --- a/src/merkle_distributor/utils.py +++ b/src/merkle_distributor/utils.py @@ -62,6 +62,9 @@ class OraclesSettings(TypedDict): # set of Uniswap V3 supported staked ETH pairs uniswap_v3_staked_eth_pairs: Set[ChecksumAddress] + # set of Uniswap V3 full range pairs + uniswap_v3_full_range_pairs: Set[ChecksumAddress] + # dictionary of supported ERC-20 tokens and the block number when they were created erc20_tokens: Dict[ChecksumAddress, BlockNumber] @@ -334,6 +337,12 @@ def get_oracles_config( for pair in oracles_settings.get("uniswap_v3_staked_eth_pairs", []) ] ), + uniswap_v3_full_range_pairs=set( + [ + Web3.toChecksumAddress(pair) + for pair in oracles_settings.get("uniswap_v3_full_range_pairs", []) + ] + ), erc20_tokens={ Web3.toChecksumAddress(token_address): int(block_number) for token_address, block_number in oracles_settings.get( @@ -841,6 +850,101 @@ def get_uniswap_v3_balances( return account_to_liquidity, total_liquidity +@retry( + reraise=True, + wait=backoff, + stop=stop_attempts, + before_sleep=before_sleep_log(logger, logging.WARNING), +) +def get_uniswap_v3_full_range_balances( + subgraph_url: str, pool_address: ChecksumAddress, to_block: BlockNumber +) -> Tuple[Dict[ChecksumAddress, Wei], Wei]: + """Fetches balances of the Uniswap V3 positions with full range.""" + transport = RequestsHTTPTransport(url=subgraph_url) + + # create a GraphQL client using the defined transport + client = Client(transport=transport, fetch_schema_from_transport=False) + + # fetch positions that cover the current tick + query = gql( + """ + query getPositions( + $block_number: Int + $pool_address: String + $last_id: ID + ) { + positions( + first: 1000 + block: { number: $block_number } + where: { + tickLower: -887220 + tickUpper: 887220 + pool: $pool_address + id_gt: $last_id + } + orderBy: id + orderDirection: asc + ) { + owner + liquidity + } + } + """ + ) + + # execute the query on the transport in chunks of 1000 entities + last_id = "" + result: Dict = execute_graphql_query( + client=client, + query=query, + variables=dict( + block_number=to_block, + pool_address=pool_address.lower(), + last_id=last_id, + ), + ) + positions_chunk = result.get("positions", []) + positions = positions_chunk + + # accumulate chunks + while len(positions_chunk) >= 1000: + last_id = positions_chunk[-1]["id"] + if not last_id: + break + + result = execute_graphql_query( + client=client, + query=query, + variables=dict( + block_number=to_block, + pool_address=pool_address.lower(), + last_id=last_id, + ), + ) + positions_chunk = result.get("positions", []) + positions.extend(positions_chunk) + + # process positions + account_to_liquidity: Dict[ChecksumAddress, Wei] = {} + total_liquidity: Wei = Wei(0) + for position in positions: + account: ChecksumAddress = position.get("owner", EMPTY_ADDR_HEX) + if account in (EMPTY_ADDR_HEX, ""): + continue + + account = Web3.toChecksumAddress(account) + liquidity: Wei = Wei(int(position.get("liquidity", "0"))) + if liquidity <= 0: + continue + + account_to_liquidity[account] = Wei( + account_to_liquidity.setdefault(account, Wei(0)) + liquidity + ) + total_liquidity += liquidity + + return account_to_liquidity, total_liquidity + + @retry( reraise=True, wait=backoff,