From cb316b835ad50e5d2914b4b73be20a270b7b80e4 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:15:06 +0800 Subject: [PATCH 01/70] fix connection pool exhausted and unlimited retries (#74) * fix connection pool exhausted and unlimited retries * make format --- cli/stream.py | 2 +- indexer/controller/reorg_controller.py | 45 +++++++++++++---- .../controller/scheduler/reorg_scheduler.py | 4 ++ indexer/jobs/export_reorg_job.py | 50 +++++++++++-------- indexer/utils/reorg.py | 50 +++++++++++-------- 5 files changed, 98 insertions(+), 53 deletions(-) diff --git a/cli/stream.py b/cli/stream.py index e9df3570d..5d40167fe 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -240,7 +240,7 @@ def wrapper(*args, **kwargs): ) @click.option( "--auto-reorg", - default=True, + default=False, show_default=True, type=bool, envvar="AUTO_REORG", diff --git a/indexer/controller/reorg_controller.py b/indexer/controller/reorg_controller.py index 905c2957f..cdac8cab3 100644 --- a/indexer/controller/reorg_controller.py +++ b/indexer/controller/reorg_controller.py @@ -7,23 +7,22 @@ from common.models.blocks import Blocks from common.models.fix_record import FixRecord +from common.utils.exception_control import HemeraBaseException from common.utils.web3_utils import build_web3 from indexer.controller.base_controller import BaseController +from indexer.utils.exception_recorder import ExceptionRecorder + +exception_recorder = ExceptionRecorder() class ReorgController(BaseController): - def __init__( - self, - batch_web3_provider, - job_scheduler, - ranges, - config, - ): + def __init__(self, batch_web3_provider, job_scheduler, ranges, config, max_retries=5): self.ranges = ranges self.web3 = build_web3(batch_web3_provider) self.db_service = config.get("db_service") self.job_scheduler = job_scheduler + self.max_retries = max_retries def action(self, job_id=None, block_number=None, remains=None, retry_errors=True): if block_number is None: @@ -75,7 +74,7 @@ def action(self, job_id=None, block_number=None, remains=None, retry_errors=True }, ) offset += 1 - except (Exception, KeyboardInterrupt) as e: + except (Exception, KeyboardInterrupt, HemeraBaseException) as e: self.update_job_info( job_id, job_info={ @@ -95,23 +94,51 @@ def action(self, job_id=None, block_number=None, remains=None, retry_errors=True self.wake_up_next_job() def _do_fixing(self, fix_block, retry_errors=True): + tries, tries_reset = 0, True while True: try: # Main reorging logic + tries_reset = True self.job_scheduler.run_jobs(fix_block, fix_block) logging.info(f"Block No.{fix_block} and relative entities completely fixed .") break + except HemeraBaseException as e: + logging.exception(f"An rpc response exception occurred while syncing block data. error: {e}") + if e.crashable: + logging.exception("Mission will crash immediately.") + raise e + + if e.retriable: + tries += 1 + tries_reset = False + if tries >= self.max_retries: + logging.info(f"The number of retry is reached limit {self.max_retries}. Program will exit.") + raise e + else: + logging.info(f"No: {tries} retry is about to start.") + else: + logging.exception("Mission will not retry, and exit immediately.") + raise e + except Exception as e: print(e) logging.exception("An exception occurred while reorging block data.") - if not retry_errors: + tries += 1 + tries_reset = False + if not retry_errors or tries >= self.max_retries: + logging.info(f"The number of retry is reached limit {self.max_retries}. Program will exit.") + exception_recorder.force_to_flush() raise e else: logging.info("After 5 seconds will retry the job.") time.sleep(5) + finally: + if tries_reset: + tries = 0 + def submit_new_fixing_job(self, start_block_number, remain_process): session = self.db_service.get_service_session() stmt = insert(FixRecord).values( diff --git a/indexer/controller/scheduler/reorg_scheduler.py b/indexer/controller/scheduler/reorg_scheduler.py index a21cb4295..6f0b5d9de 100644 --- a/indexer/controller/scheduler/reorg_scheduler.py +++ b/indexer/controller/scheduler/reorg_scheduler.py @@ -47,6 +47,7 @@ def __init__( item_exporters=[], required_output_types=[], cache="memory", + multicall=None, ): self.batch_web3_provider = batch_web3_provider self.batch_web3_debug_provider = batch_web3_debug_provider @@ -61,6 +62,7 @@ def __init__( self.job_map = defaultdict(list) self.dependency_map = defaultdict(list) self.pg_service = config.get("db_service") if "db_service" in config else None + self._is_multicall = multicall self.discover_and_register_job_classes() self.required_job_classes = self.get_required_job_classes(required_output_types) @@ -119,6 +121,7 @@ def instantiate_jobs(self): max_workers=self.max_workers, config=self.config, reorg=True, + multicall=self._is_multicall, ) if isinstance(job, FilterTransactionDataJob): filters.append(job.get_filter()) @@ -137,6 +140,7 @@ def instantiate_jobs(self): config=self.config, filters=filters, reorg=True, + multicall=self._is_multicall, ) self.jobs.insert(0, export_blocks_job) diff --git a/indexer/jobs/export_reorg_job.py b/indexer/jobs/export_reorg_job.py index 5ef665f48..114c5a14f 100644 --- a/indexer/jobs/export_reorg_job.py +++ b/indexer/jobs/export_reorg_job.py @@ -20,36 +20,44 @@ def _process(self, **kwargs): conn = self._service.get_conn() cur = conn.cursor() - for key in self._data_buff.keys(): - if len(self._data_buff[key]) > 0: - items = self._data_buff[key] - domain = type(items[0]) - if domain.__name__ not in domain_model_mapping: - continue + try: + for key in self._data_buff.keys(): + if len(self._data_buff[key]) > 0: + items = self._data_buff[key] + domain = type(items[0]) + if domain.__name__ not in domain_model_mapping: + continue - pg_config = domain_model_mapping[domain.__name__] + pg_config = domain_model_mapping[domain.__name__] - table = pg_config["table"] - do_update = pg_config["conflict_do_update"] - update_strategy = pg_config["update_strategy"] - converter = pg_config["converter"] + table = pg_config["table"] + do_update = pg_config["conflict_do_update"] + update_strategy = pg_config["update_strategy"] + converter = pg_config["converter"] - if not hasattr(table, "reorg"): - continue + if not hasattr(table, "reorg"): + continue - reorg_data = [converter(table, item, do_update) for item in items] + reorg_data = [converter(table, item, do_update) for item in items] - columns = list(reorg_data[0].keys()) - values = [tuple(d.values()) for d in reorg_data] + columns = list(reorg_data[0].keys()) + values = [tuple(d.values()) for d in reorg_data] - insert_stmt = sql_insert_statement(table, do_update, columns, where_clause=update_strategy) + insert_stmt = sql_insert_statement(table, do_update, columns, where_clause=update_strategy) - if table.__tablename__ != "blocks": - cur.execute(self._build_clean_sql(table.__tablename__, block_number)) + if table.__tablename__ != "blocks": + cur.execute(self._build_clean_sql(table.__tablename__, block_number)) - execute_values(cur, insert_stmt, values, page_size=500) + execute_values(cur, insert_stmt, values, page_size=500) - conn.commit() + conn.commit() + except Exception as e: + # print(e) + logger.error(f"Reorg chain data error:{e}") + # print(item_type, insert_stmt, [i[-1] for i in data]) + raise Exception("Reorg chain data error") + finally: + self._service.release_conn(conn) self._data_buff.clear() @staticmethod diff --git a/indexer/utils/reorg.py b/indexer/utils/reorg.py index ea8d90b78..fef99ec97 100644 --- a/indexer/utils/reorg.py +++ b/indexer/utils/reorg.py @@ -5,36 +5,42 @@ from common.models import HemeraModel, __models_imports from common.services.postgresql_service import PostgreSQLService +from common.utils.exception_control import RetriableError from common.utils.module_loading import import_string def set_reorg_sign(block_number, service): conn = service.get_conn() cur = conn.cursor() - for model, path in __models_imports.items(): - table = import_string(f"{path}.{model}") - if hasattr(table, "reorg"): - update_stmt = ( - f"UPDATE {table.__tablename__} " - + f"SET reorg=TRUE, update_time='{datetime.utcfromtimestamp(datetime.now(timezone.utc).timestamp())}'" - ) - - if hasattr(table, "number"): - update_stmt += f"WHERE number={block_number}" - elif hasattr(table, "block_number"): - update_stmt += f"WHERE block_number={block_number}" - else: - update_stmt = None - logging.warning( - f"Reorging table: {table} has no block number info, " - f"could not complete reorg action, " - f"reorging will be skipped this table." + try: + for model, path in __models_imports.items(): + table = import_string(f"{path}.{model}") + if hasattr(table, "reorg"): + update_stmt = ( + f"UPDATE {table.__tablename__} " + + f"SET reorg=TRUE, update_time='{datetime.utcfromtimestamp(datetime.now(timezone.utc).timestamp())}'" ) - if update_stmt: - cur.execute(update_stmt) - - conn.commit() + if hasattr(table, "number"): + update_stmt += f"WHERE number={block_number}" + elif hasattr(table, "block_number"): + update_stmt += f"WHERE block_number={block_number}" + else: + update_stmt = None + logging.warning( + f"Reorging table: {table} has no block number info, " + f"could not complete reorg action, " + f"reorging will be skipped this table." + ) + + if update_stmt: + cur.execute(update_stmt) + conn.commit() + except Exception as e: + logging.error(e) + raise RetriableError(e) + finally: + service.release_conn(conn) def should_reorg(block_number: int, table: HemeraModel, service: PostgreSQLService): From 8a36c93cac54941141a8a62cc5c54121258505e8 Mon Sep 17 00:00:00 2001 From: li xiang Date: Tue, 27 Aug 2024 18:42:14 +0800 Subject: [PATCH 02/70] feat: Add blocks API compatibility and support token price queries (#73) * feat: Add blocks API compatibility and support token price FDW queries - Implement compatibility layer for blocks interface - Add token price table --- api/app/db_service/tokens.py | 2 +- api/app/explorer/routes.py | 102 ++++++++++++------ api/app/token/token_prices.py | 39 ++++++- api/app/utils/utils.py | 6 +- .../20240827_add_token_price_table.py | 47 ++++++++ 5 files changed, 157 insertions(+), 39 deletions(-) create mode 100644 migrations/versions/20240827_add_token_price_table.py diff --git a/api/app/db_service/tokens.py b/api/app/db_service/tokens.py index 9c06cb286..3b709e14a 100644 --- a/api/app/db_service/tokens.py +++ b/api/app/db_service/tokens.py @@ -197,7 +197,7 @@ def parse_token_transfers(token_transfers, type=None): else: token_transfer_json["token_symbol"] = "UNKNOWN" token_transfer_json["token_name"] = "Unknown Token" - token_transfer_json["token_logo_url"] = f"/images/empty-token-{app_config.chain}.png" + token_transfer_json["token_logo_url"] = None token_transfer_list.append(token_transfer_json) diff --git a/api/app/explorer/routes.py b/api/app/explorer/routes.py index 985e51575..ada8881ab 100644 --- a/api/app/explorer/routes.py +++ b/api/app/explorer/routes.py @@ -1073,17 +1073,33 @@ def get(self): }, 200 +class CustomRequestParser(reqparse.RequestParser): + def add_argument(self, *args, **kwargs): + if "location" not in kwargs: + kwargs["location"] = "args" + return super(CustomRequestParser, self).add_argument(*args, **kwargs) + + +blocks_parser = CustomRequestParser() + +blocks_parser.add_argument("page", type=int, default=1, help="Page number") +blocks_parser.add_argument("size", type=int, default=25, help="Page size") +blocks_parser.add_argument("state_batch", type=int, default=None, help="State batch filter") +blocks_parser.add_argument("batch", type=int, default=None, help="Batch filter") + + @explorer_namespace.route("/v1/explorer/blocks") class ExplorerBlocks(Resource): @cache.cached(timeout=10, query_string=True) def get(self): - page_index = int(flask.request.args.get("page", 1)) - page_size = int(flask.request.args.get("size", 25)) + args = blocks_parser.parse_args() + page_index = args.get("page") + page_size = args.get("size") if page_index <= 0 or page_size <= 0: raise APIError("Invalid page or size", code=400) - state_batch = flask.request.args.get("state_batch", None) - batch = flask.request.args.get("batch", None) + state_batch = args.get("state_batch") + batch = args.get("batch") block_list_columns = [ "hash", @@ -1099,6 +1115,7 @@ def get(self): ] if state_batch is None and batch is None: + latest_block = get_last_block(columns=["number"]) total_blocks = latest_block.number if latest_block else 0 @@ -1110,32 +1127,32 @@ def get(self): blocks = get_blocks_by_condition( columns=block_list_columns, filter_condition=Blocks.number.between(start_block, end_block) ) - - block_list = [format_to_dict(block) for block in blocks] - - return { - "data": block_list, - "total": total_blocks, - "page": page_index, - "size": page_size, - }, 200 - - filter_condition = True - total_blocks = 0 - blocks = get_blocks_by_condition( - columns=block_list_columns, - filter_condition=filter_condition, - limit=page_size, - offset=(page_index - 1) * page_size, - ) - - block_list = [] - for block in blocks: - block_list.append(as_dict(block)) - - if total_blocks == 0 and len(block_list) > 0: - latest_block = get_last_block(columns=["number", "timestamp"]) - total_blocks = latest_block.number + else: + # TODO: Fix blocks filter by state_batch and batch + filter_condition = True + total_blocks = 0 + blocks = get_blocks_by_condition( + columns=block_list_columns, + filter_condition=filter_condition, + limit=page_size, + offset=(page_index - 1) * page_size, + ) + if total_blocks == 0 and len(blocks) > 0: + latest_block = get_last_block(columns=["number", "timestamp"]) + total_blocks = latest_block.number + block_list = [ + format_to_dict(block) + | { + "transaction_count": block.transactions_count, + "internal_transaction_count": ( + 0 if block.internal_transactions_count is None else block.internal_transactions_count + ), + "internal_transactions_count": ( + 0 if block.internal_transactions_count is None else block.internal_transactions_count + ), + } + for block in blocks + ] return { "data": block_list, @@ -1162,7 +1179,9 @@ def get(self, number_or_hash): # Added by indexer now # internal_transaction_count = get_internal_transactions_cnt_by_condition( # filter_condition=ContractInternalTransactions.block_number == block.number) - block_json["internal_transaction_count"] = block.internal_transactions_count + block_json["internal_transaction_count"] = ( + 0 if block.internal_transactions_count is None else block.internal_transactions_count + ) block_json["gas_fee_token_price"] = "{0:.2f}".format( get_token_price(app_config.token_configuration.gas_fee_token, block.timestamp) @@ -1172,6 +1191,7 @@ def get(self, number_or_hash): earlier_block = get_block_by_number(block_number=earlier_block_number, columns=["number", "timestamp"]) block_json["seconds_since_last_block"] = block.timestamp.timestamp() - earlier_block.timestamp.timestamp() + block_json["transaction_count"] = block.transactions_count latest_block = get_last_block(columns=["number"]) @@ -1212,6 +1232,10 @@ def get(self, address): if contract.verified_implementation_contract else None ) + profile_json["bytecode"] = "0x" + contract.deployed_code.hex() if contract.deployed_code else None + profile_json["creation_code"] = "0x" + contract.creation_code.hex() if contract.creation_code else None + profile_json["deployed_code"] = "0x" + contract.deployed_code.hex() if contract.deployed_code else None + deployed_code = contract.deployed_code or get_sha256_hash(get_code(address)) addresses = get_similar_addresses(deployed_code) profile_json["similar_verified_addresses"] = [add for add in addresses if add != address] @@ -1220,7 +1244,7 @@ def get(self, address): if token: profile_json["is_token"] = True - profile_json["token_type"] = token.type # ERC20/ERC721/ERC1155 + profile_json["token_type"] = token.token_type # ERC20/ERC721/ERC1155 profile_json["token_name"] = token.name or "Unknown Token" profile_json["token_symbol"] = token.symbol or "UNKNOWN" profile_json["token_logo_url"] = token.icon_url or None @@ -1422,6 +1446,17 @@ def get(self, address): return {"total": len(logs), "data": log_list}, 200 +def token_type_convert(token_type): + if token_type == "ERC20": + return "tokentxns" + elif token_type == "ERC721": + return "tokentxns-nft" + elif token_type == "ERC1155": + return "tokentxns-nft1155" + else: + return None + + @explorer_namespace.route("/v1/explorer/token/
/profile") class ExplorerTokenProfile(Resource): @cache.cached(timeout=60, query_string=True) @@ -1462,7 +1497,8 @@ def get(self, address): "total_supply": "{:f}".format(token.total_supply or 0), "total_holders": token.holder_count, "total_transfers": get_token_address_token_transfer_cnt(token.token_type, address), - "type": token.token_type, + "token_type": token.token_type, + "type": token_type_convert(token.token_type), } token_info.update(extra_token_info) diff --git a/api/app/token/token_prices.py b/api/app/token/token_prices.py index 4377de03e..f75532b0f 100644 --- a/api/app/token/token_prices.py +++ b/api/app/token/token_prices.py @@ -1,9 +1,46 @@ from decimal import Decimal +from sqlalchemy import Column, DateTime, Numeric, String + from api.app.cache import cache +from common.models import HemeraModel +from common.models import db as postgres_db +from common.utils.config import get_config + +app_config = get_config() + + +class TokenPrices(HemeraModel): + symbol = Column(String, primary_key=True) + timestamp = Column(DateTime, primary_key=True) + price = Column(Numeric) + + +class TokenHourlyPrices(HemeraModel): + symbol = Column(String, primary_key=True) + timestamp = Column(DateTime, primary_key=True) + price = Column(Numeric) @cache.memoize(300) def get_token_price(symbol, date=None) -> Decimal: - + if date: + token_price = ( + postgres_db.session.query(TokenHourlyPrices) + .filter( + TokenHourlyPrices.symbol == symbol, + TokenHourlyPrices.timestamp <= date, + ) + .order_by(TokenHourlyPrices.timestamp.desc()) + .first() + ) + else: + token_price = ( + postgres_db.session.query(TokenPrices) + .filter(TokenPrices.symbol == symbol) + .order_by(TokenPrices.timestamp.desc()) + .first() + ) + if token_price: + return token_price.price return Decimal(0.0) diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index 06bdfc331..1f87cc45b 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -364,12 +364,10 @@ def process_token_transfer(token_transfers, token_type): token_transfer_json["value"] = ( "{0:.18f}".format(token_transfer.value / 10 ** (token_transfer.decimals or 18)).rstrip("0").rstrip(".") ) - token_transfer_json["token_logo_url"] = ( - token_transfer.icon_url or f"/images/empty-token-{app_config.chain}.png" - ) + token_transfer_json["token_logo_url"] = token_transfer.icon_url or None else: token_transfer_json["token_id"] = "{:f}".format(token_transfer.token_id) - token_transfer_json["token_logo_url"] = f"/images/empty-token-{app_config.chain}.png" + token_transfer_json["token_logo_url"] = None if token_type == "tokentxns-nft1155": token_transfer_json["value"] = "{:f}".format(token_transfer.value) diff --git a/migrations/versions/20240827_add_token_price_table.py b/migrations/versions/20240827_add_token_price_table.py new file mode 100644 index 000000000..7553d3dcc --- /dev/null +++ b/migrations/versions/20240827_add_token_price_table.py @@ -0,0 +1,47 @@ +"""add token price table + +Revision ID: 2359a28d63cb +Revises: bf51d23c852f +Create Date: 2024-08-27 17:58:50.838313 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op + +# revision identifiers, used by Alembic. +revision: str = "2359a28d63cb" +down_revision: Union[str, None] = "bf51d23c852f" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + conn = op.get_bind() + inspector = sa.inspect(conn) + if not inspector.has_table("token_hourly_prices"): + op.create_table( + "token_hourly_prices", + sa.Column("symbol", sa.String(), nullable=False), + sa.Column("timestamp", sa.DateTime(), nullable=False), + sa.Column("price", sa.Numeric(), nullable=True), + sa.PrimaryKeyConstraint("symbol", "timestamp"), + ) + if not inspector.has_table("token_prices"): + op.create_table( + "token_prices", + sa.Column("symbol", sa.String(), nullable=False), + sa.Column("timestamp", sa.DateTime(), nullable=False), + sa.Column("price", sa.Numeric(), nullable=True), + sa.PrimaryKeyConstraint("symbol", "timestamp"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + pass + # ### end Alembic commands ### From acffb8a946201345fcebe1d1057b691b9db70806 Mon Sep 17 00:00:00 2001 From: 2024river Date: Wed, 28 Aug 2024 10:02:09 +0800 Subject: [PATCH 03/70] Add uop api (#76) * add uop api --- api/app/api.py | 3 + api/app/user_operation/__init__.py | 6 + api/app/user_operation/routes.py | 346 ++++++++++++++++++ api/app/utils/utils.py | 12 +- docker-compose/docker-compose.yaml | 8 + .../user_ops/models/user_operation_results.py | 12 +- .../versions/20240731_add_user_ops_table.py | 12 +- 7 files changed, 381 insertions(+), 18 deletions(-) create mode 100644 api/app/user_operation/__init__.py create mode 100644 api/app/user_operation/routes.py diff --git a/api/app/api.py b/api/app/api.py index f4d5ba43b..9aa36a458 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -4,9 +4,12 @@ from flask_restx import Api from api.app.explorer.routes import explorer_namespace +from api.app.user_operation.routes import user_operation_namespace # from api.app.l2_explorer.routes import l2_explorer_namespace api = Api() + api.add_namespace(explorer_namespace) +api.add_namespace(user_operation_namespace) # api.add_namespace(l2_explorer_namespace) diff --git a/api/app/user_operation/__init__.py b/api/app/user_operation/__init__.py new file mode 100644 index 000000000..ed806a4b4 --- /dev/null +++ b/api/app/user_operation/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from flask_restx.namespace import Namespace + +user_operation_namespace = Namespace("User Operation Namespace", path="/", description="User Operation API") diff --git a/api/app/user_operation/routes.py b/api/app/user_operation/routes.py new file mode 100644 index 000000000..6b791a905 --- /dev/null +++ b/api/app/user_operation/routes.py @@ -0,0 +1,346 @@ +import re + +import flask +from flask_restx import Resource + +from api.app.cache import cache +from api.app.user_operation import user_operation_namespace +from api.app.utils.utils import ( + fill_address_display_to_transactions, + get_total_row_count, + parse_log_with_transaction_input_list, + process_token_transfer, +) +from common.models import db +from common.models.erc20_token_transfers import ERC20TokenTransfers +from common.models.erc721_token_transfers import ERC721TokenTransfers +from common.models.erc1155_token_transfers import ERC1155TokenTransfers +from common.models.logs import Logs +from common.models.tokens import Tokens +from common.models.transactions import Transactions +from common.utils.config import get_config +from common.utils.exception_control import APIError +from common.utils.format_utils import format_value_for_json +from indexer.modules.user_ops.models.user_operation_results import UserOperationResult + +PAGE_SIZE = 25 +MAX_TRANSACTION = 500000 +MAX_TRANSACTION_WITH_CONDITION = 10000 +MAX_INTERNAL_TRANSACTION = 10000 +MAX_TOKEN_TRANSFER = 10000 + +app_config = get_config() + + +@user_operation_namespace.route("/v1/explorer/ops") +class ExplorerUserOperations(Resource): + @cache.cached(timeout=10, query_string=True) + def get(self): + page_index = int(flask.request.args.get("page", 1)) + page_size = int(flask.request.args.get("size", 25)) + + size = (page_index - 1) * page_size + start_item = size + 1 + if start_item > MAX_TRANSACTION: + return {"error": f"The requested data range exceeds the maximum({MAX_TRANSACTION}) allowed."}, 400 + + sender = flask.request.args.get("sender") + + condition = True + if sender: + if not re.match(r"^0x[a-fA-F0-9]{40}$", sender): + raise APIError("Invalid wallet address", code=400) + condition = UserOperationResult.sender == sender + + user_operation_results = ( + db.session.query(UserOperationResult) + .filter(condition) + .order_by( + UserOperationResult.block_number.desc(), + ) + .limit(page_size) + .offset(size) + .all() + ) + + if sender: + total_count = db.session.query(UserOperationResult).filter(condition).count() + else: + total_count = get_total_row_count("user_operations_results") + + if not user_operation_results: + raise APIError("There are not any user operations", code=400) + + user_operation_result_list = [] + for user_operation_result in user_operation_results: + user_operation_result_dict = {} + user_operation_result_dict["user_op_hash"] = user_operation_result.user_op_hash + user_operation_result_dict["block_timestamp"] = user_operation_result.block_timestamp + user_operation_result_dict["status"] = user_operation_result.status + user_operation_result_dict["sender"] = user_operation_result.sender + user_operation_result_dict["transactions_hash"] = user_operation_result.transactions_hash + user_operation_result_dict["block_number"] = user_operation_result.block_number + wei_amount = user_operation_result.actual_gas_cost + formatted_eth = format(wei_amount / 10**18, ".10f") + user_operation_result_dict["fee"] = formatted_eth + + user_operation_result_list.append( + {k: format_value_for_json(v) for k, v in user_operation_result_dict.items()} + ) + + return { + "data": user_operation_result_list, + "total": total_count, + "max_display": min( + MAX_TRANSACTION, + total_count, + ), + "page": page_index, + "size": page_size, + }, 200 + + +@user_operation_namespace.route("/v1/explorer/op/") +class ExplorerUserOperationDetails(Resource): + @cache.cached(timeout=60, query_string=True) + def get(self, hash): + # parameter validated + if not re.match(r"^0x[a-fA-F0-9]{64}$", hash): + raise APIError("Invalid user operation hash", code=400) + + bytes_hash = bytes.fromhex(hash[2:]) + + user_operation_result = db.session.query(UserOperationResult).get(bytes_hash) + if not user_operation_result: + raise APIError("Cannot find user operation with hash", code=400) + + user_operation_result_dict = {} + user_operation_result_dict["user_op_hash"] = user_operation_result.user_op_hash + user_operation_result_dict["sender"] = user_operation_result.sender + user_operation_result_dict["status"] = user_operation_result.status + user_operation_result_dict["block_timestamp"] = user_operation_result.block_timestamp + user_operation_result_dict["fee"] = format(user_operation_result.actual_gas_cost / 10**18, ".10f") + + user_operation_result_dict["gas_limit"] = ( + user_operation_result.call_gas_limit + + user_operation_result.verification_gas_limit + + user_operation_result.pre_verification_gas + ) + user_operation_result_dict["gas_used"] = user_operation_result.actual_gas_used + user_operation_result_dict["transactions_hash"] = user_operation_result.transactions_hash + user_operation_result_dict["block_number"] = user_operation_result.block_number + user_operation_result_dict["user_op_hash"] = user_operation_result.user_op_hash + user_operation_result_dict["entry_point"] = ( + "0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789" # todo: maybe there will be some new contract address? + ) + user_operation_result_dict["call_gas_limit"] = user_operation_result.call_gas_limit + user_operation_result_dict["verification_gas_limit"] = user_operation_result.verification_gas_limit + user_operation_result_dict["pre_verification_gas"] = user_operation_result.pre_verification_gas + user_operation_result_dict["max_fee_per_gas"] = user_operation_result.max_fee_per_gas + user_operation_result_dict["max_priority_fee_per_gas"] = user_operation_result.max_priority_fee_per_gas + user_operation_result_dict["bundler"] = user_operation_result.bundler + user_operation_result_dict["paymaster"] = user_operation_result.paymaster + user_operation_result_dict["sponsor_type"] = ( + 1 if user_operation_result.paymaster != "0x0000000000000000000000000000000000000000" else 0 + ) # todo: add more types + + user_operation_result_dict["signature"] = user_operation_result.signature + user_operation_result_dict["nonce"] = str(user_operation_result.nonce) + user_operation_result_dict["call_data"] = user_operation_result.call_data + + result_json = {k: format_value_for_json(v) for k, v in user_operation_result_dict.items()} + return result_json, 200 + + +@user_operation_namespace.route("/v1/explorer/op//token-transfers") +class ExplorerUserOperationTokenTransfers(Resource): + @cache.cached(timeout=60, query_string=True) + def get(self, hash): + if not re.match(r"^0x[a-fA-F0-9]{64}$", hash): + raise APIError("Invalid user operation hash", code=400) + + bytes_hash = bytes.fromhex(hash[2:]) + + user_operation_result = ( + db.session.query(UserOperationResult) + .filter_by(user_op_hash=bytes_hash) + .with_entities( + UserOperationResult.transactions_hash, + UserOperationResult.start_log_index, + UserOperationResult.end_log_index, + ) + .first() + ) + + erc20_token_transfers = ( + db.session.query(ERC20TokenTransfers) + .filter(ERC20TokenTransfers.transaction_hash == user_operation_result.transactions_hash) + .filter( + (ERC20TokenTransfers.log_index > user_operation_result.start_log_index) + & (ERC20TokenTransfers.log_index < user_operation_result.end_log_index) + ) + .join( + Tokens, # not sure Erc20Tokens + ERC20TokenTransfers.token_address == Tokens.address, + ) + .add_columns( + Tokens.name, + Tokens.symbol, + Tokens.decimals, + Tokens.logo, + ) + .all() + ) + + erc721_token_transfers = ( + db.session.query(ERC721TokenTransfers) + .filter(ERC721TokenTransfers.transaction_hash == user_operation_result.transactions_hash) + .filter( + (ERC721TokenTransfers.log_index > user_operation_result.start_log_index) + & (ERC721TokenTransfers.log_index < user_operation_result.end_log_index) + ) + .join( + Tokens, # Erc721Tokens + ERC721TokenTransfers.token_address == Tokens.address, + ) + .add_columns( + Tokens.name, + Tokens.symbol, + ) + .all() + ) + + # ERC1155 + erc1155_token_transfers = ( + db.session.query(ERC1155TokenTransfers) + .filter(ERC1155TokenTransfers.transaction_hash == user_operation_result.transactions_hash) + .filter( + (ERC1155TokenTransfers.log_index > user_operation_result.start_log_index) + & (ERC1155TokenTransfers.log_index < user_operation_result.end_log_index) + ) + .join( + Tokens, # Erc1155Tokens + ERC1155TokenTransfers.token_address == Tokens.address, + ) + .add_columns( + Tokens.name, + Tokens.symbol, + ) + .all() + ) + + token_transfer_list = [] + token_transfer_list.extend(process_token_transfer(erc20_token_transfers, "tokentxns")) + token_transfer_list.extend(process_token_transfer(erc721_token_transfers, "tokentxns-nft")) + token_transfer_list.extend(process_token_transfer(erc1155_token_transfers, "tokentxns-nft1155")) + fill_address_display_to_transactions(token_transfer_list) + return { + "total": len(token_transfer_list), + "data": token_transfer_list, + }, 200 + + +@user_operation_namespace.route("/v1/explorer/op//logs") +class ExplorerUserOperationLogs(Resource): + @cache.cached(timeout=60, query_string=True) + def get(self, hash): + if not re.match(r"^0x[a-fA-F0-9]{64}$", hash): + raise APIError("Invalid user operation hash", code=400) + + bytes_hash = bytes.fromhex(hash[2:]) + + user_operation_result = ( + db.session.query(UserOperationResult) + .filter_by(user_op_hash=bytes_hash) + .with_entities( + UserOperationResult.transactions_hash, + UserOperationResult.start_log_index, + UserOperationResult.end_log_index, + ) + .first() + ) + + logs = ( + db.session.query(Logs) + .filter(Logs.transaction_hash == user_operation_result.transactions_hash) + .filter( + (Logs.log_index > user_operation_result.start_log_index) + & (Logs.log_index < user_operation_result.end_log_index) + ) + .join(Transactions, Logs.transaction_hash == Transactions.hash) + .add_columns(Transactions.input) + .all() + ) + log_list = parse_log_with_transaction_input_list(logs) + + return {"total": len(log_list), "data": log_list}, 200 + + +@user_operation_namespace.route("/v1/explorer/op//raw") +class ExplorerUserOperationRaw(Resource): + @cache.cached(timeout=60, query_string=True) + def get(self, hash): + if not re.match(r"^0x[a-fA-F0-9]{64}$", hash): + raise APIError("Invalid user operation hash", code=400) + bytes_hash = bytes.fromhex(hash[2:]) + + user_operation_result = db.session.query(UserOperationResult).get(bytes_hash) + if not user_operation_result: + raise APIError("Cannot find user operation with hash", code=400) + + user_operation_result_dict = {} + user_operation_result_dict["sender"] = user_operation_result.sender + user_operation_result_dict["nonce"] = str(user_operation_result.nonce) + user_operation_result_dict["init_code"] = user_operation_result.init_code + user_operation_result_dict["call_data"] = user_operation_result.call_data + user_operation_result_dict["call_gas_limit"] = user_operation_result.call_gas_limit + user_operation_result_dict["verification_gas_limit"] = user_operation_result.verification_gas_limit + user_operation_result_dict["pre_verification_gas"] = user_operation_result.pre_verification_gas + user_operation_result_dict["max_fee_per_gas"] = user_operation_result.max_fee_per_gas + user_operation_result_dict["max_priority_fee_per_gas"] = user_operation_result.max_priority_fee_per_gas + user_operation_result_dict["paymaster_and_data"] = user_operation_result.paymaster_and_data + user_operation_result_dict["signature"] = user_operation_result.signature + result_json = {k: format_value_for_json(v) for k, v in user_operation_result_dict.items()} + return result_json, 200 + + +@user_operation_namespace.route("/v1/explorer/transaction//ops") +class ExplorerTransactionOperation(Resource): + @cache.cached(timeout=360, query_string=True) + def get(self, txn_hash): + if not re.match(r"^0x[a-fA-F0-9]{64}$", txn_hash): + raise APIError("Invalid user operation hash", code=400) + + bytes_hash = bytes.fromhex(txn_hash[2:]) + + user_operation_result = db.session.query(UserOperationResult).filter_by(transactions_hash=bytes_hash) + if not user_operation_result: + raise APIError("Cannot find user operation with hash", code=400) + + user_operation_result_list = [] + for user_operation_result in user_operation_result: + user_operation_result_dict = {} + user_operation_result_dict["user_op_hash"] = user_operation_result.user_op_hash + user_operation_result_dict["block_timestamp"] = user_operation_result.block_timestamp + user_operation_result_dict["status"] = user_operation_result.status + user_operation_result_dict["sender"] = user_operation_result.sender + user_operation_result_dict["transactions_hash"] = user_operation_result.transactions_hash + user_operation_result_dict["block_number"] = user_operation_result.block_number + wei_amount = user_operation_result.actual_gas_cost + formatted_eth = format(wei_amount / 10**18, ".10f") + user_operation_result_dict["fee"] = formatted_eth + user_operation_result_dict["nonce"] = str(user_operation_result.nonce) + user_operation_result_dict["init_code"] = user_operation_result.init_code + user_operation_result_dict["call_data"] = user_operation_result.call_data + user_operation_result_dict["call_gas_limit"] = user_operation_result.call_gas_limit + user_operation_result_dict["verification_gas_limit"] = user_operation_result.verification_gas_limit + user_operation_result_dict["pre_verification_gas"] = user_operation_result.pre_verification_gas + user_operation_result_dict["max_fee_per_gas"] = user_operation_result.max_fee_per_gas + user_operation_result_dict["max_priority_fee_per_gas"] = user_operation_result.max_priority_fee_per_gas + user_operation_result_dict["paymaster_and_data"] = user_operation_result.paymaster_and_data + user_operation_result_dict["signature"] = user_operation_result.signature + + user_operation_result_list.append( + {k: format_value_for_json(v) for k, v in user_operation_result_dict.items()} + ) + return user_operation_result_list, 200 diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index 1f87cc45b..2db95ed0a 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -32,7 +32,6 @@ def get_count_by_address(table, chain, wallet_address=None): def get_total_row_count(table): - estimate_transaction = db.session.execute( text( f""" @@ -100,11 +99,12 @@ def format_transaction(GAS_FEE_TOKEN_PRICE, transaction: dict): transaction_json["value"] = format_coin_value(int(transaction["value"])) transaction_json["value_dollar"] = "{0:.2f}".format(transaction["value"] * GAS_FEE_TOKEN_PRICE / 10**18) - transaction_json["gas_price_gwei"] = "{0:.6f}".format(transaction["gas_price"] / 10**9).rstrip("0").rstrip(".") - transaction_json["gas_price"] = "{0:.15f}".format(transaction["gas_price"] / 10**18).rstrip("0").rstrip(".") + gas_price = transaction["gas_price"] or 0 + transaction_json["gas_price_gwei"] = "{0:.6f}".format(gas_price / 10**9).rstrip("0").rstrip(".") + transaction_json["gas_price"] = "{0:.15f}".format(gas_price / 10**18).rstrip("0").rstrip(".") - transaction_fee = transaction["gas_price"] * transaction["receipt_gas_used"] - total_transaction_fee = transaction["gas_price"] * transaction["receipt_gas_used"] + transaction_fee = gas_price * transaction["receipt_gas_used"] + total_transaction_fee = gas_price * transaction["receipt_gas_used"] if "receipt_l1_fee" in transaction_json and transaction_json["receipt_l1_fee"]: transaction_json["receipt_l1_fee"] = ( @@ -120,7 +120,7 @@ def format_transaction(GAS_FEE_TOKEN_PRICE, transaction: dict): total_transaction_fee = transaction_fee + transaction["receipt_l1_fee"] transaction_json["transaction_fee"] = "{0:.15f}".format(transaction_fee / 10**18).rstrip("0").rstrip(".") transaction_json["transaction_fee_dollar"] = "{0:.2f}".format( - transaction["gas_price"] * GAS_FEE_TOKEN_PRICE * transaction["receipt_gas_used"] / 10**18 + gas_price * GAS_FEE_TOKEN_PRICE * transaction["receipt_gas_used"] / 10**18 ) transaction_json["total_transaction_fee"] = ( diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index bae068066..9e4e73d20 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -29,6 +29,14 @@ services: - ENTITY_TYPES=EXPLORER_TRACE - SYNC_RECORDER=pg:trace_recorder + hemera-uop-indexer: + <<: *common-settings + container_name: indexer-uop + environment: + - ENTITY_TYPES=USER_OPS + - SYNC_RECORDER=pg:uop_recorder + + hemera-api: <<: *common-settings container_name: hemera-api diff --git a/indexer/modules/user_ops/models/user_operation_results.py b/indexer/modules/user_ops/models/user_operation_results.py index e9422759d..6d7878b3f 100644 --- a/indexer/modules/user_ops/models/user_operation_results.py +++ b/indexer/modules/user_ops/models/user_operation_results.py @@ -15,15 +15,15 @@ class UserOperationResult(HemeraModel): nonce = Column(NUMERIC) status = Column(BOOLEAN) actual_gas_cost = Column(NUMERIC) - actual_gas_used = Column(INTEGER) + actual_gas_used = Column(NUMERIC) init_code = Column(BYTEA) call_data = Column(BYTEA) - call_gas_limit = Column(INTEGER) - verification_gas_limit = Column(INTEGER) - pre_verification_gas = Column(INTEGER) - max_fee_per_gas = Column(INTEGER) - max_priority_fee_per_gas = Column(INTEGER) + call_gas_limit = Column(NUMERIC) + verification_gas_limit = Column(NUMERIC) + pre_verification_gas = Column(NUMERIC) + max_fee_per_gas = Column(NUMERIC) + max_priority_fee_per_gas = Column(NUMERIC) paymaster_and_data = Column(BYTEA) signature = Column(BYTEA) diff --git a/migrations/versions/20240731_add_user_ops_table.py b/migrations/versions/20240731_add_user_ops_table.py index 94c74b1d1..07ee4f990 100644 --- a/migrations/versions/20240731_add_user_ops_table.py +++ b/migrations/versions/20240731_add_user_ops_table.py @@ -29,14 +29,14 @@ def upgrade() -> None: sa.Column("nonce", sa.NUMERIC(), nullable=True), sa.Column("status", sa.BOOLEAN(), nullable=True), sa.Column("actual_gas_cost", sa.NUMERIC(), nullable=True), - sa.Column("actual_gas_used", sa.INTEGER(), nullable=True), + sa.Column("actual_gas_used", sa.NUMERIC(), nullable=True), sa.Column("init_code", postgresql.BYTEA(), nullable=True), sa.Column("call_data", postgresql.BYTEA(), nullable=True), - sa.Column("call_gas_limit", sa.INTEGER(), nullable=True), - sa.Column("verification_gas_limit", sa.INTEGER(), nullable=True), - sa.Column("pre_verification_gas", sa.INTEGER(), nullable=True), - sa.Column("max_fee_per_gas", sa.INTEGER(), nullable=True), - sa.Column("max_priority_fee_per_gas", sa.INTEGER(), nullable=True), + sa.Column("call_gas_limit", sa.NUMERIC(), nullable=True), + sa.Column("verification_gas_limit", sa.NUMERIC(), nullable=True), + sa.Column("pre_verification_gas", sa.NUMERIC(), nullable=True), + sa.Column("max_fee_per_gas", sa.NUMERIC(), nullable=True), + sa.Column("max_priority_fee_per_gas", sa.NUMERIC(), nullable=True), sa.Column("paymaster_and_data", postgresql.BYTEA(), nullable=True), sa.Column("signature", postgresql.BYTEA(), nullable=True), sa.Column("transactions_hash", postgresql.BYTEA(), nullable=True), From 7b192d494df11185fbf6b93e60c89aeda8668315 Mon Sep 17 00:00:00 2001 From: 2024river Date: Wed, 28 Aug 2024 11:12:51 +0800 Subject: [PATCH 04/70] fix bug of op logo (#78) --- api/app/user_operation/routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/app/user_operation/routes.py b/api/app/user_operation/routes.py index 6b791a905..4d40ab04e 100644 --- a/api/app/user_operation/routes.py +++ b/api/app/user_operation/routes.py @@ -187,7 +187,7 @@ def get(self, hash): Tokens.name, Tokens.symbol, Tokens.decimals, - Tokens.logo, + Tokens.icon_url, ) .all() ) From 1ea14ed32edaef7c614659686a3aa3f7b5175ae2 Mon Sep 17 00:00:00 2001 From: li xiang Date: Wed, 28 Aug 2024 11:15:20 +0800 Subject: [PATCH 05/70] Add token symbol config (#77) --- api/app/config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/app/config.py b/api/app/config.py index 2c75a129e..e60a23fbb 100644 --- a/api/app/config.py +++ b/api/app/config.py @@ -196,6 +196,15 @@ def update_from_env(self): self.cache_config.cache_type = os.getenv("CACHE_TYPE", self.cache_config.cache_type) self.cache_config.cache_redis_host = os.getenv("REDIS_HOST", self.cache_config.cache_redis_host) + if os.getenv("DASHBOARD_TOKEN"): + self.token_configuration.dashboard_token = os.getenv( + "DASHBOARD_TOKEN", self.token_configuration.dashboard_token + ) + if os.getenv("NATIVE_TOKEN"): + self.token_configuration.native_token = os.getenv("NATIVE_TOKEN", self.token_configuration.native_token) + if os.getenv("GAS_FEE_TOKEN"): + self.token_configuration.gas_fee_token = os.getenv("GAS_FEE_TOKEN", self.token_configuration.gas_fee_token) + def get_onchain_badge_config(self): if "on_chain_badge" in self.extra_config: return self.extra_config["ONCHAIN_BADGE_INFO"] From 10cf4fb1cd13be36cc51b960ffc5a0c4b125572a Mon Sep 17 00:00:00 2001 From: li xiang Date: Wed, 28 Aug 2024 19:12:27 +0800 Subject: [PATCH 06/70] Fix set trace rpc request id (#81) * Fix trace rpc request id --- indexer/utils/json_rpc_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer/utils/json_rpc_requests.py b/indexer/utils/json_rpc_requests.py index 3ef8c42ba..fb79caa0e 100644 --- a/indexer/utils/json_rpc_requests.py +++ b/indexer/utils/json_rpc_requests.py @@ -13,7 +13,7 @@ def generate_trace_block_by_number_json_rpc(block_numbers): method="debug_traceBlockByNumber", params=[block_number, {"tracer": "callTracer"}], # save block_number in request ID, so later we can identify block number in response - request_id=block_number, + request_id=int(block_number, 16), ) From 2e5b0cbed7aecb52e8b0f76a3724602ea93b0f2e Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 29 Aug 2024 11:24:49 +0800 Subject: [PATCH 07/70] Remove metrics log (#80) --- indexer/utils/multicall_hemera/util.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/indexer/utils/multicall_hemera/util.py b/indexer/utils/multicall_hemera/util.py index 6fc2c838e..839754a3b 100644 --- a/indexer/utils/multicall_hemera/util.py +++ b/indexer/utils/multicall_hemera/util.py @@ -81,8 +81,7 @@ def wrapper(*args, **kwargs): result = func(*args, **kwargs) end_time = time.time() execution_time = end_time - start_time - print(f"function {func.__name__} time: {execution_time:.6f} s") - logger.info(f"function {func.__name__} time: {execution_time:.6f} s") + logger.debug(f"function {func.__name__} time: {execution_time:.6f} s") return result return wrapper From 5a248fc0799986be0094e74e9e80b98d2c3255fd Mon Sep 17 00:00:00 2001 From: li xiang Date: Mon, 2 Sep 2024 17:50:10 +0800 Subject: [PATCH 08/70] feat: Add opensea index job (#85) * feat: Add opensea index job --- cli/stream.py | 39 +++- common/models/__init__.py | 5 +- common/services/postgresql_service.py | 2 +- indexer-config.yaml | 4 + indexer/controller/scheduler/job_scheduler.py | 41 ++-- indexer/jobs/base_job.py | 5 + indexer/jobs/export_blocks_job.py | 2 +- indexer/jobs/filter_transaction_data_job.py | 1 + indexer/modules/custom/opensea/__init__.py | 0 .../modules/custom/opensea/domain/__init__.py | 0 .../domain/address_opensea_transactions.py | 24 +++ .../custom/opensea/domain/opensea_order.py | 18 ++ .../models/address_opensea_transaction.py | 41 ++++ .../models/daily_address_opensea_stats.py | 27 +++ .../custom/opensea/models/opensea_order.py | 37 ++++ indexer/modules/custom/opensea/opensea_job.py | 189 ++++++++++++++++++ .../modules/custom/opensea/parser/__init__.py | 0 .../opensea/parser/opensea_contract_parser.py | 67 +++++++ indexer/utils/abi.py | 19 +- 19 files changed, 500 insertions(+), 21 deletions(-) create mode 100644 indexer-config.yaml create mode 100644 indexer/modules/custom/opensea/__init__.py create mode 100644 indexer/modules/custom/opensea/domain/__init__.py create mode 100644 indexer/modules/custom/opensea/domain/address_opensea_transactions.py create mode 100644 indexer/modules/custom/opensea/domain/opensea_order.py create mode 100644 indexer/modules/custom/opensea/models/address_opensea_transaction.py create mode 100644 indexer/modules/custom/opensea/models/daily_address_opensea_stats.py create mode 100644 indexer/modules/custom/opensea/models/opensea_order.py create mode 100644 indexer/modules/custom/opensea/opensea_job.py create mode 100644 indexer/modules/custom/opensea/parser/__init__.py create mode 100644 indexer/modules/custom/opensea/parser/opensea_contract_parser.py diff --git a/cli/stream.py b/cli/stream.py index 5d40167fe..8ee4b9516 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -1,10 +1,12 @@ import logging +import os import time import click from web3 import Web3 from common.services.postgresql_service import PostgreSQLService +from common.utils.format_utils import to_snake_case from enumeration.entity_type import DEFAULT_COLLECTION, calculate_entity_value, generate_output_types from indexer.controller.scheduler.job_scheduler import JobScheduler from indexer.controller.stream_controller import StreamController @@ -236,7 +238,7 @@ def wrapper(*args, **kwargs): show_default=True, type=bool, help="if `multicall` is set to True, it will decrease the consume of rpc calls", - envvar="Multicall", + envvar="MULTI_CALL_ENABLE", ) @click.option( "--auto-reorg", @@ -246,6 +248,22 @@ def wrapper(*args, **kwargs): envvar="AUTO_REORG", help="Whether to detect reorg in data streams and automatically repair data.", ) +@click.option( + "--config-file", + default=False, + show_default=True, + type=str, + envvar="CONFIG_FILE", + help="The path to the configuration file, if provided, the configuration file will be used to load the configuration. Supported formats are json and yaml.", +) +@click.option( + "--force-filter-mode", + default=False, + show_default=True, + type=bool, + envvar="FORCE_FILTER_MODE", + help="Force the filter mode to be enabled, even if no filters job are provided.", +) @calculate_execution_time def stream( provider_uri, @@ -271,6 +289,8 @@ def stream( cache="memory", auto_reorg=True, multicall=True, + config_file=None, + force_filter_mode=False, ): configure_logging(log_file) configure_signals() @@ -295,6 +315,21 @@ def stream( else: logging.warning("No postgres url provided. Exception recorder will not be useful.") + if config_file: + if not os.path.exists(config_file): + raise click.ClickException(f"Config file {config_file} not found") + with open(config_file, "r") as f: + if config_file.endswith(".json"): + import json + + config.update(json.load(f)) + elif config_file.endswith(".yaml") or config_file.endswith(".yml"): + import yaml + + config.update(yaml.safe_load(f)) + else: + raise click.ClickException(f"Config file {config_file} is not supported)") + if output_types is None: entity_types = calculate_entity_value(entity_types) output_types = list(generate_output_types(entity_types)) @@ -303,6 +338,7 @@ def stream( parse_output_types = set() for output_type in output_types.split(","): + output_type = to_snake_case(output_type) if output_type not in domain_dict: raise click.ClickException(f"Output type {output_type} is not supported") parse_output_types.add(domain_dict[output_type]) @@ -323,6 +359,7 @@ def stream( cache=cache, auto_reorg=auto_reorg, multicall=multicall, + force_filter_mode=force_filter_mode, ) controller = StreamController( diff --git a/common/models/__init__.py b/common/models/__init__.py index e511647ab..1dc5c0d3d 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -2,7 +2,8 @@ from typing import Type from flask_sqlalchemy import SQLAlchemy -from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, TIMESTAMP +from psycopg2._json import Json +from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, TIMESTAMP from common.utils.module_loading import import_string, scan_subclass_by_path_patterns from indexer.domain import Domain @@ -51,6 +52,8 @@ def general_converter(table: Type[HemeraModel], data: Domain, is_update=False): converted_data[key] = datetime.utcfromtimestamp(getattr(data, key)) elif isinstance(column_type, ARRAY) and isinstance(column_type.item_type, BYTEA): converted_data[key] = [bytes.fromhex(address[2:]) for address in getattr(data, key)] + elif isinstance(column_type, JSONB) and getattr(data, key) is not None: + converted_data[key] = Json(getattr(data, key)) else: converted_data[key] = getattr(data, key) diff --git a/common/services/postgresql_service.py b/common/services/postgresql_service.py index 7dc2daed9..b1db34f31 100644 --- a/common/services/postgresql_service.py +++ b/common/services/postgresql_service.py @@ -28,7 +28,7 @@ def __new__(cls, *args, **kwargs): cls.instance = super().__new__(cls) return cls.instance - def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=True): + def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=False): self.db_version = db_version self.engine = create_engine( jdbc_url, diff --git a/indexer-config.yaml b/indexer-config.yaml new file mode 100644 index 000000000..de9384585 --- /dev/null +++ b/indexer-config.yaml @@ -0,0 +1,4 @@ +opensea_job: + seaport_contract_address: "0x0000000000000068f116a894984e2db1123eb395" + seaport_fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" \ No newline at end of file diff --git a/indexer/controller/scheduler/job_scheduler.py b/indexer/controller/scheduler/job_scheduler.py index ff5a8f72b..595086b17 100644 --- a/indexer/controller/scheduler/job_scheduler.py +++ b/indexer/controller/scheduler/job_scheduler.py @@ -59,6 +59,7 @@ def __init__( cache="memory", multicall=None, auto_reorg=True, + force_filter_mode=False, ): self.logger = logging.getLogger(__name__) self.auto_reorg = auto_reorg @@ -79,7 +80,11 @@ def __init__( self.pg_service = config.get("db_service") if "db_service" in config else None self.discover_and_register_job_classes() - self.required_job_classes = self.get_required_job_classes(required_output_types) + self.required_job_classes, self.is_pipeline_filter = self.get_required_job_classes(required_output_types) + + if force_filter_mode: + self.is_pipeline_filter = True + self.resolved_job_classes = self.resolve_dependencies(self.required_job_classes) token_dict_from_db = defaultdict() if self.pg_service is not None: @@ -98,12 +103,29 @@ def __init__( self.instantiate_jobs() self.logger.info("Export output types: %s", required_output_types) - def get_data_buff(self): - return BaseJob._data_buff + def get_required_job_classes(self, output_types) -> (List[Type[BaseJob]], bool): + required_job_classes = set() + output_type_queue = deque(output_types) + is_filter = True + for output_type in output_types: + for job_class in self.job_map[output_type.type()]: + is_filter = job_class.is_filter and is_filter + + while output_type_queue: + output_type = output_type_queue.popleft() + for job_class in self.job_map[output_type.type()]: + if job_class in self.job_classes: + required_job_classes.add(job_class) + for dependency in job_class.dependency_types: + output_type_queue.append(dependency) + return required_job_classes, is_filter def clear_data_buff(self): BaseJob._data_buff.clear() + def get_data_buff(self): + return BaseJob._data_buff + def discover_and_register_job_classes(self): if self.load_from_source: source_job = get_source_job_type(source_path=self.load_from_source) @@ -154,6 +176,7 @@ def instantiate_jobs(self): debug_batch_size=self.debug_batch_size, max_workers=self.max_workers, config=self.config, + is_filter=self.is_pipeline_filter, filters=filters, ) self.jobs.insert(0, export_blocks_job) @@ -189,18 +212,6 @@ def run_jobs(self, start_block, end_block): finally: exception_recorder.force_to_flush() - def get_required_job_classes(self, output_types): - required_job_classes = set() - job_queue = deque(output_types) - while job_queue: - output_type = job_queue.popleft() - for job_class in self.job_map[output_type.type()]: - if job_class not in required_job_classes: - required_job_classes.add(job_class) - for dependency in job_class.dependency_types: - job_queue.append(dependency) - return required_job_classes - def resolve_dependencies(self, required_jobs: Set[Type[BaseJob]]) -> List[Type[BaseJob]]: sorted_order = [] job_graph = defaultdict(list) diff --git a/indexer/jobs/base_job.py b/indexer/jobs/base_job.py index c404b14f9..c9bc493e9 100644 --- a/indexer/jobs/base_job.py +++ b/indexer/jobs/base_job.py @@ -7,6 +7,7 @@ from common.converter.pg_converter import domain_model_mapping from common.utils.exception_control import FastShutdownError +from common.utils.format_utils import to_snake_case from indexer.utils.reorg import should_reorg @@ -42,6 +43,7 @@ class BaseJob(metaclass=BaseJobMeta): tokens = None + is_filter = False dependency_types = [] output_types = [] able_to_reorg = False @@ -71,6 +73,9 @@ def __init__(self, **kwargs): self._should_reorg_type = set() self._service = kwargs["config"].get("db_service", None) + job_name_snake = to_snake_case(self.job_name) + self.user_defined_config = kwargs["config"][job_name_snake] if kwargs["config"].get(job_name_snake) else {} + def run(self, **kwargs): try: self._start(**kwargs) diff --git a/indexer/jobs/export_blocks_job.py b/indexer/jobs/export_blocks_job.py index 26dfe4a4d..6cf524730 100644 --- a/indexer/jobs/export_blocks_job.py +++ b/indexer/jobs/export_blocks_job.py @@ -38,7 +38,7 @@ def __init__(self, **kwargs): ) self._is_batch = kwargs["batch_size"] > 1 self._filters = kwargs.get("filters", []) - self._is_filter = all(output_type.is_filter_data() for output_type in self._required_output_types) + self._is_filter = kwargs.get("is_filter", False) self._specification = AlwaysFalseSpecification() if self._is_filter else AlwaysTrueSpecification() def _start(self, **kwargs): diff --git a/indexer/jobs/filter_transaction_data_job.py b/indexer/jobs/filter_transaction_data_job.py index 29ae2b138..bccb41a7f 100644 --- a/indexer/jobs/filter_transaction_data_job.py +++ b/indexer/jobs/filter_transaction_data_job.py @@ -5,6 +5,7 @@ class FilterTransactionDataJob(ExtensionJob): dependency_types = [Transaction] output_types = [] + is_filter = True def get_filter(self): raise NotImplementedError diff --git a/indexer/modules/custom/opensea/__init__.py b/indexer/modules/custom/opensea/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/opensea/domain/__init__.py b/indexer/modules/custom/opensea/domain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/opensea/domain/address_opensea_transactions.py b/indexer/modules/custom/opensea/domain/address_opensea_transactions.py new file mode 100644 index 000000000..d89d19f03 --- /dev/null +++ b/indexer/modules/custom/opensea/domain/address_opensea_transactions.py @@ -0,0 +1,24 @@ +from dataclasses import dataclass + +from indexer.domain import Domain + + +@dataclass +class AddressOpenseaTransaction(Domain): + address: str + related_address: str + is_offer: bool + transaction_type: int + order_hash: str + zone: str + + offer: dict + consideration: dict + + fee: dict + + transaction_hash: str + block_number: int + log_index: int + block_timestamp: int + block_hash: str diff --git a/indexer/modules/custom/opensea/domain/opensea_order.py b/indexer/modules/custom/opensea/domain/opensea_order.py new file mode 100644 index 000000000..052bee85e --- /dev/null +++ b/indexer/modules/custom/opensea/domain/opensea_order.py @@ -0,0 +1,18 @@ +from dataclasses import dataclass + +from indexer.domain import Domain + + +@dataclass +class OpenseaOrder(Domain): + order_hash: str + zone: str + offerer: str + recipient: str + offer: dict + consideration: dict + block_timestamp: int + block_hash: str + transaction_hash: str + log_index: int + block_number: int diff --git a/indexer/modules/custom/opensea/models/address_opensea_transaction.py b/indexer/modules/custom/opensea/models/address_opensea_transaction.py new file mode 100644 index 000000000..d96fc60c2 --- /dev/null +++ b/indexer/modules/custom/opensea/models/address_opensea_transaction.py @@ -0,0 +1,41 @@ +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class AddressOpenseaTransactions(HemeraModel): + __tablename__ = "af_opensea__transactions" + + address = Column(BYTEA, primary_key=True) + is_offer = Column(BOOLEAN, primary_key=True) + related_address = Column(BYTEA) + transaction_type = Column(SMALLINT) + + order_hash = Column(BYTEA) + zone = Column(BYTEA) + + offer = Column(JSONB) + consideration = Column(JSONB) + fee = Column(JSONB) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + transaction_hash = Column(BYTEA) + block_number = Column(BIGINT, primary_key=True) + log_index = Column(BIGINT, primary_key=True) + block_timestamp = Column(TIMESTAMP) + block_hash = Column(BYTEA, primary_key=True) + reorg = Column(BOOLEAN, default=False) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "AddressOpenseaTransaction", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py new file mode 100644 index 000000000..725f6137e --- /dev/null +++ b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py @@ -0,0 +1,27 @@ +from sqlalchemy import INT, NUMERIC, Column, Date, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP + +from common.models import HemeraModel + + +class DailyAddressOpenseaTransactions(HemeraModel): + __tablename__ = "af_opensea_daily_transactions" + + address = Column(BYTEA, primary_key=True) + related_address = Column(BYTEA) + transaction_type = Column(SMALLINT) + + block_date = Column(Date, primary_key=True) + + buy_txn_count = Column(INT) + sell_txn_count = Column(INT) + swap_txn_count = Column(INT) + + buy_txn_volume_crypto = Column(JSONB) + sell_txn_volume_crypto = Column(JSONB) + + buy_txn_volume_usd = Column(NUMERIC) + sell_txn_volume_usd = Column(NUMERIC) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) diff --git a/indexer/modules/custom/opensea/models/opensea_order.py b/indexer/modules/custom/opensea/models/opensea_order.py new file mode 100644 index 000000000..ea089e5f0 --- /dev/null +++ b/indexer/modules/custom/opensea/models/opensea_order.py @@ -0,0 +1,37 @@ +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class OpenseaOrders(HemeraModel): + __tablename__ = "af_opensea_na_orders" + + order_hash = Column(BYTEA) + zone = Column(BYTEA) + offerer = Column(BYTEA) + recipient = Column(BYTEA) + + offer = Column(JSONB) + consideration = Column(JSONB) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + transaction_hash = Column(BYTEA) + block_number = Column(BIGINT, primary_key=True) + log_index = Column(BIGINT, primary_key=True) + block_timestamp = Column(TIMESTAMP) + block_hash = Column(BYTEA, primary_key=True) + reorg = Column(BOOLEAN, default=False) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "OpenseaOrder", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/opensea/opensea_job.py b/indexer/modules/custom/opensea/opensea_job.py new file mode 100644 index 000000000..6069497e3 --- /dev/null +++ b/indexer/modules/custom/opensea/opensea_job.py @@ -0,0 +1,189 @@ +import logging +from collections import defaultdict +from enum import Enum +from typing import List + +from indexer.domain.transaction import Transaction +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom.opensea.domain.address_opensea_transactions import AddressOpenseaTransaction +from indexer.modules.custom.opensea.domain.opensea_order import OpenseaOrder +from indexer.modules.custom.opensea.parser.opensea_contract_parser import ( + OPENSEA_EVENT_ABI_SIGNATURE_MAPPING, + OpenseaLog, + parse_opensea_transaction_order_fulfilled_event, +) +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs + +logger = logging.getLogger(__name__) + +from collections import defaultdict + + +class OpenseaTransactionType(Enum): + BUY = 0 + SELL = 1 + SWAP = 2 + + +class ItemType(Enum): + # 0: ETH on mainnet, MATIC on polygon, etc. + NATIVE = 0 + # 1: ERC20 items (ERC777 and ERC20 analogues could also technically work) + ERC20 = 1 + # 2: ERC721 items + ERC721 = 2 + # 3: ERC1155 items + ERC1155 = 3 + # 4: ERC721 items where a number of tokenIds are supported + ERC721_WITH_CRITERIA = 4 + # 5: ERC1155 items where a number of ids are supported + ERC1155_WITH_CRITERIA = 5 + + +def get_opensea_transaction_type(offer, consideration): + for item in offer: + if item["itemType"] > 1: + for item in consideration: + if item["itemType"] > 1: + return OpenseaTransactionType.SWAP + else: + return OpenseaTransactionType.SELL + else: + return OpenseaTransactionType.BUY + return OpenseaTransactionType.SELL + + +def calculate_total_amount(consideration): + total_amount = {} + + for item in consideration: + token = item["token"] + item_type = item["itemType"] + amount = item["amount"] + + if token not in total_amount: + total_amount[token] = {"type": item_type, "amount": {}} + total_amount[token]["amount"][item_type] = amount + else: + total_amount[token]["amount"][item_type] += amount + + return total_amount + + +def calculate_fee_amount(consideration, seaport_fee_addresses): + fee_amount = {} + + for item in consideration: + recipient = item["recipient"] + + if recipient in seaport_fee_addresses: + token = item["token"] + item_type = item["itemType"] + amount = item["amount"] + + if token not in fee_amount: + fee_amount[token] = {"type": item_type, "amount": {}} + fee_amount[token]["amount"][item_type] = amount + else: + fee_amount[token]["amount"][item_type] += amount + + return fee_amount + + +class OpenseaJob(FilterTransactionDataJob): + dependency_types = [Transaction] + output_types = [AddressOpenseaTransaction, OpenseaOrder] + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + + self._seaport_contract_address = self.user_defined_config["seaport_contract_address"] + self._seaport_fee_addresses = self.user_defined_config["seaport_fee_addresses"] + + def _collect(self, **kwargs): + pass + + def transfer(self, opensea_logs: List[OpenseaLog]): + for opensea_log in opensea_logs: + yield OpenseaOrder( + order_hash=opensea_log.orderHash, + zone=opensea_log.zone, + offerer=opensea_log.offerer, + recipient=opensea_log.recipient, + offer=opensea_log.offer, + consideration=opensea_log.consideration, + block_timestamp=opensea_log.block_timestamp, + block_hash=opensea_log.block_hash, + transaction_hash=opensea_log.transaction_hash, + block_number=opensea_log.block_number, + log_index=opensea_log.log_index, + ) + offer = calculate_total_amount(opensea_log.offer) + consideration = calculate_total_amount(opensea_log.consideration) + fee = calculate_fee_amount(opensea_log.consideration, self._seaport_fee_addresses) + + opensea_transaciton_type = get_opensea_transaction_type(opensea_log.offer, opensea_log.consideration) + + yield AddressOpenseaTransaction( + address=opensea_log.offerer, + related_address=opensea_log.recipient, + is_offer=True, + transaction_type=opensea_transaciton_type.value, + order_hash=opensea_log.orderHash, + zone=opensea_log.zone, + offer=offer, + consideration=consideration, + fee=fee, + transaction_hash=opensea_log.transaction_hash, + block_number=opensea_log.block_number, + log_index=opensea_log.log_index, + block_timestamp=opensea_log.block_timestamp, + block_hash=opensea_log.block_hash, + ) + yield AddressOpenseaTransaction( + address=opensea_log.recipient, + related_address=opensea_log.offerer, + is_offer=False, + transaction_type=( + 1 - opensea_transaciton_type.value + if opensea_transaciton_type.value <= 1 + else opensea_transaciton_type.value + ), + order_hash=opensea_log.orderHash, + zone=opensea_log.zone, + offer=offer, + consideration=consideration, + fee=fee, + transaction_hash=opensea_log.transaction_hash, + block_number=opensea_log.block_number, + log_index=opensea_log.log_index, + block_timestamp=opensea_log.block_timestamp, + block_hash=opensea_log.block_hash, + ) + + def _process(self, **kwargs): + transactions = self._get_domain(Transaction) + for transaction in transactions: + self._collect_domains( + self.transfer( + parse_opensea_transaction_order_fulfilled_event(transaction, self._seaport_contract_address) + ) + ) + + def get_filter(self): + topic_filter_list = [] + topic_filter_list.append( + TopicSpecification( + addresses=[self._seaport_contract_address], + topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), + ) + ) + + return TransactionFilterByLogs(topic_filter_list) diff --git a/indexer/modules/custom/opensea/parser/__init__.py b/indexer/modules/custom/opensea/parser/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/opensea/parser/opensea_contract_parser.py b/indexer/modules/custom/opensea/parser/opensea_contract_parser.py new file mode 100644 index 000000000..3cb54e539 --- /dev/null +++ b/indexer/modules/custom/opensea/parser/opensea_contract_parser.py @@ -0,0 +1,67 @@ +import json +from dataclasses import dataclass +from enum import Enum +from typing import Dict, List, cast + +from web3.types import ABIEvent + +from indexer.domain.transaction import Transaction +from indexer.utils.abi import decode_log, event_log_abi_to_topic + +OPENSEA_EVENT_ABIS = { + "ORDER_FULFILLED_EVENT": '{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"orderHash","type":"bytes32"},{"indexed":true,"internalType":"address","name":"offerer","type":"address"},{"indexed":true,"internalType":"address","name":"zone","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"components":[{"internalType":"enum ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"}],"indexed":false,"internalType":"struct SpentItem[]","name":"offer","type":"tuple[]"},{"components":[{"internalType":"enum ItemType","name":"itemType","type":"uint8"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"identifier","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address payable","name":"recipient","type":"address"}],"indexed":false,"internalType":"struct ReceivedItem[]","name":"consideration","type":"tuple[]"}],"name":"OrderFulfilled","type":"event"}' +} + +OPENSEA_EVENT_ABI_MAPPING: Dict[str, ABIEvent] = { + key: cast(ABIEvent, json.loads(value)) for key, value in OPENSEA_EVENT_ABIS.items() +} + +OPENSEA_EVENT_ABI_SIGNATURE_MAPPING: Dict[str, str] = { + name: event_log_abi_to_topic(abi) for name, abi in OPENSEA_EVENT_ABI_MAPPING.items() +} + + +@dataclass +class OpenseaLog: + orderHash: str + offerer: str + zone: str + recipient: str + offer: Dict[str, str] + consideration: Dict[str, str] + block_timestamp: int + block_hash: str + block_number: int + transaction_hash: str + log_index: int + + +def parse_opensea_transaction_order_fulfilled_event( + transaction: Transaction, contract_address: str +) -> List[OpenseaLog]: + results = [] + logs = transaction.receipt.logs + for log in logs: + if ( + log.topic0 == OPENSEA_EVENT_ABI_SIGNATURE_MAPPING["ORDER_FULFILLED_EVENT"] + and log.address == contract_address + ): + opensea_transaction = decode_log(OPENSEA_EVENT_ABI_MAPPING["ORDER_FULFILLED_EVENT"], log) + + results.append( + OpenseaLog( + orderHash="0x" + opensea_transaction["orderHash"].hex(), + offerer=opensea_transaction["offerer"], + zone=opensea_transaction["zone"], + recipient=opensea_transaction["recipient"], + offer=opensea_transaction["offer"], + consideration=opensea_transaction["consideration"], + block_timestamp=transaction.block_timestamp, + block_hash=transaction.block_hash, + block_number=transaction.block_number, + transaction_hash=transaction.hash, + log_index=log.log_index, + ) + ) + + return results diff --git a/indexer/utils/abi.py b/indexer/utils/abi.py index 75ca84c16..229e4ba81 100644 --- a/indexer/utils/abi.py +++ b/indexer/utils/abi.py @@ -48,6 +48,21 @@ def function_abi_to_4byte_selector_str(function_abi: ABIFunction) -> str: return "0x" + function_abi_to_4byte_selector(function_abi).hex() +def get_types_from_abi_type_list(abi_type_list: Sequence[Dict[str, Any]]) -> Sequence[str]: + return [generate_type_str(abi_type) for abi_type in abi_type_list] + + +def generate_type_str(component): + if component["type"] == "tuple[]": + tuple_types = tuple(map(lambda x: generate_type_str(x), component["components"])) + return "(" + ",".join(tuple_types) + ")[]" + elif component["type"] == "tuple": + tuple_types = tuple(map(lambda x: generate_type_str(x), component["components"])) + return "(" + ",".join(tuple_types) + ")" + else: + return component["type"] + + def decode_log( fn_abi: ABIEvent, log: Log, @@ -61,10 +76,10 @@ def decode_log( data_types = exclude_indexed_event_inputs(fn_abi) abi_codec = ABICodec(eth_abi.registry.registry) - decode_indexed = abi_codec.decode([t["type"] for t in indexed_types], log.get_bytes_topics()) + decode_indexed = abi_codec.decode(get_types_from_abi_type_list(indexed_types), log.get_bytes_topics()) indexed = named_tree(indexed_types, decode_indexed) - decoded_data = abi_codec.decode([t["type"] for t in data_types], log.get_bytes_data()) + decoded_data = abi_codec.decode(get_types_from_abi_type_list(data_types), log.get_bytes_data()) data = named_tree(data_types, decoded_data) except Exception as e: logging.warning(f"Failed to decode log: {e}, log: {log}") From 90dcefdf039c83b3e0e22f8547d53779ebee6baf Mon Sep 17 00:00:00 2001 From: li xiang Date: Tue, 3 Sep 2024 11:09:47 +0800 Subject: [PATCH 09/70] bugfix: Set config file default None (#86) --- cli/stream.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/stream.py b/cli/stream.py index 8ee4b9516..1537d4cb5 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -250,7 +250,7 @@ def wrapper(*args, **kwargs): ) @click.option( "--config-file", - default=False, + default=None, show_default=True, type=str, envvar="CONFIG_FILE", From 9739bccc8bf017debc7d867a3d319349769afc6f Mon Sep 17 00:00:00 2001 From: li xiang Date: Tue, 3 Sep 2024 11:28:47 +0800 Subject: [PATCH 10/70] Fix/address token balance primary key (#87) * update token balance table primary key --------- Co-authored-by: zihao.xu --- common/models/current_token_balances.py | 11 +++++------ common/models/token_balances.py | 17 ++++++++++++++--- migrations/versions/20240704_base_version.py | 2 +- .../20240725_update_index_table_optimize.py | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/common/models/current_token_balances.py b/common/models/current_token_balances.py index 56f33381e..197dcb1e6 100644 --- a/common/models/current_token_balances.py +++ b/common/models/current_token_balances.py @@ -1,16 +1,15 @@ -from datetime import datetime - from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR -from common.models import HemeraModel, general_converter +from common.models import HemeraModel +from common.models.token_balances import token_balances_general_converter class CurrentTokenBalances(HemeraModel): __tablename__ = "address_current_token_balances" address = Column(BYTEA, primary_key=True) - token_id = Column(NUMERIC(100), default=-1) + token_id = Column(NUMERIC(78), primary_key=True) token_type = Column(VARCHAR) token_address = Column(BYTEA, primary_key=True) balance = Column(NUMERIC(100)) @@ -22,7 +21,7 @@ class CurrentTokenBalances(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) - __table_args__ = (PrimaryKeyConstraint("address", "token_address"),) + __table_args__ = (PrimaryKeyConstraint("address", "token_address", "token_id"),) @staticmethod def model_domain_mapping(): @@ -31,7 +30,7 @@ def model_domain_mapping(): "domain": "CurrentTokenBalance", "conflict_do_update": True, "update_strategy": "EXCLUDED.block_number > address_current_token_balances.block_number", - "converter": general_converter, + "converter": token_balances_general_converter, } ] diff --git a/common/models/token_balances.py b/common/models/token_balances.py index 92ac677ca..c039a0334 100644 --- a/common/models/token_balances.py +++ b/common/models/token_balances.py @@ -1,14 +1,25 @@ +from typing import Type + from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter +from indexer.domain.token_balance import TokenBalance + + +def token_balances_general_converter(table: Type[HemeraModel], data: TokenBalance, is_update=False): + + if data.token_id is None: + data.token_id = -1 + + return general_converter(table, data, is_update) class AddressTokenBalances(HemeraModel): __tablename__ = "address_token_balances" address = Column(BYTEA, primary_key=True) - token_id = Column(NUMERIC(100)) + token_id = Column(NUMERIC(78), primary_key=True) token_type = Column(VARCHAR) token_address = Column(BYTEA, primary_key=True) balance = Column(NUMERIC(100)) @@ -20,7 +31,7 @@ class AddressTokenBalances(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) - __table_args__ = (PrimaryKeyConstraint("address", "token_address", "block_number"),) + __table_args__ = (PrimaryKeyConstraint("address", "token_address", "token_id", "block_number"),) @staticmethod def model_domain_mapping(): @@ -29,7 +40,7 @@ def model_domain_mapping(): "domain": "TokenBalance", "conflict_do_update": False, "update_strategy": None, - "converter": general_converter, + "converter": token_balances_general_converter, } ] diff --git a/migrations/versions/20240704_base_version.py b/migrations/versions/20240704_base_version.py index 7093c6040..504cfcdd4 100644 --- a/migrations/versions/20240704_base_version.py +++ b/migrations/versions/20240704_base_version.py @@ -44,7 +44,7 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("address", "token_address", "block_number"), + sa.PrimaryKeyConstraint("address", "token_address", "token_id", "block_number"), ) op.create_table( "block_ts_mapper", diff --git a/migrations/versions/20240725_update_index_table_optimize.py b/migrations/versions/20240725_update_index_table_optimize.py index c976c13b2..f137b57f6 100644 --- a/migrations/versions/20240725_update_index_table_optimize.py +++ b/migrations/versions/20240725_update_index_table_optimize.py @@ -24,7 +24,7 @@ def upgrade() -> None: op.create_table( "address_current_token_balances", sa.Column("address", postgresql.BYTEA(), nullable=False), - sa.Column("token_id", sa.NUMERIC(precision=100), nullable=True), + sa.Column("token_id", sa.NUMERIC(precision=78), nullable=True), sa.Column("token_type", sa.VARCHAR(), nullable=True), sa.Column("token_address", postgresql.BYTEA(), nullable=False), sa.Column("balance", sa.NUMERIC(precision=100), nullable=True), @@ -33,7 +33,7 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("address", "token_address"), + sa.PrimaryKeyConstraint("address", "token_address", "token_id"), ) op.create_index( "current_token_balances_token_address_balance_of_index", From db49f7a7e4b05a2552e6963f7bec7f203cfbe783 Mon Sep 17 00:00:00 2001 From: li xiang Date: Thu, 5 Sep 2024 19:11:43 +0800 Subject: [PATCH 11/70] Fix/transaction method api (#90) --- api/app/api.py | 2 + api/app/contract/__init__.py | 7 + api/app/contract/contract_verify.py | 12 +- api/app/contract/routes.py | 321 ++++++++++++++++++++++++++++ api/app/explorer/routes.py | 4 +- api/app/utils/utils.py | 4 +- common/models/contracts.py | 6 +- 7 files changed, 349 insertions(+), 7 deletions(-) create mode 100644 api/app/contract/routes.py diff --git a/api/app/api.py b/api/app/api.py index 9aa36a458..5a0458ab0 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -3,6 +3,7 @@ from flask_restx import Api +from api.app.contract.routes import contract_namespace from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace @@ -12,4 +13,5 @@ api.add_namespace(explorer_namespace) api.add_namespace(user_operation_namespace) +api.add_namespace(contract_namespace) # api.add_namespace(l2_explorer_namespace) diff --git a/api/app/contract/__init__.py b/api/app/contract/__init__.py index e69de29bb..f605d131b 100644 --- a/api/app/contract/__init__.py +++ b/api/app/contract/__init__.py @@ -0,0 +1,7 @@ +from flask_restx.namespace import Namespace + +contract_namespace = Namespace( + "Explorer Contract Parser", + path="/", + description="Explorer Contract Parser API", +) diff --git a/api/app/contract/contract_verify.py b/api/app/contract/contract_verify.py index 08bd7be58..e12724c4b 100644 --- a/api/app/contract/contract_verify.py +++ b/api/app/contract/contract_verify.py @@ -22,6 +22,14 @@ ABI_HOST = f"{VERIFY_HOST}/v1/contract_verify/method" +def hex_string_to_bytes(hex_string): + if not hex_string: + return None + if hex_string.startswith("0x"): + return bytes.fromhex(hex_string[2:]) + return bytes.fromhex(hex_string) + + def initial_chain_id(): try: CHAIN_ID = config.chain_id @@ -72,7 +80,7 @@ def validate_input(address, compiler_type, compiler_version): def get_contract_by_address(address: str): - contract = db.session().query(Contracts).filter_by(address=address).first() + contract = db.session().query(Contracts).filter_by(address=hex_string_to_bytes(address)).first() if not contract: raise APIError("The address is not a contract", code=400) return contract @@ -296,7 +304,7 @@ def get_abis_for_logs(address_signed_prefix_list: List[Tuple[str, str, int]]): def get_abis_by_address_signed_prefix(address_signed_prefix_list: List[Tuple[str, str, int]]): result_list = [] for address, signed_prefix, indexed_true_count in address_signed_prefix_list: - contract = db.session.get(Contracts, address) + contract = db.session.get(Contracts, hex_string_to_bytes(address)) if not contract: continue deployed_code_hash = contract.deployed_code_hash diff --git a/api/app/contract/routes.py b/api/app/contract/routes.py new file mode 100644 index 000000000..1045249be --- /dev/null +++ b/api/app/contract/routes.py @@ -0,0 +1,321 @@ +import flask +from flask_restx import Resource + +from api.app.cache import cache +from api.app.contract import contract_namespace +from api.app.contract.contract_verify import ( + check_contract_verification_status, + command_normal_contract_data, + get_abi_by_chain_id_address, + get_contract_by_address, + get_contract_code_by_address, + get_contract_verification_history_by_address, + get_creation_or_deployed_code, + get_evm_versions, + get_explorer_license_type, + get_implementation_contract, + get_solidity_version, + get_vyper_version, + hex_string_to_bytes, + send_async_verification_request, + send_sync_verification_request, + validate_input, +) +from api.app.limiter import limiter +from common.models import db as postgres_db +from common.models.contracts import Contracts +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict +from indexer.utils.utils import ZERO_ADDRESS + + +@contract_namespace.route("/v1/explorer/verify_contract/verify") +@contract_namespace.route("/v2/explorer/verify_contract/verify") +class ExplorerVerifyContract(Resource): + def post(_): + request_form = flask.request.form + address = request_form.get("address", "").lower() + compiler_type = request_form.get("compiler_type") + compiler_version = request_form.get("compiler_version") + evm_version = request_form.get("evm_version") + proxy = request_form.get("proxy") + implementation = request_form.get("implementation") + license_type = request_form.get("license_type", "None") + optimization = request_form.get("optimization") + optimization_runs = request_form.get("optimization_runs") + constructor_arguments = request_form.get("constructor_arguments") + file_list = flask.request.files.getlist("files") + input_str = request_form.get("input_str") + + validate_input(address, compiler_type, compiler_version) + + contracts = get_contract_by_address(address) + check_contract_verification_status(contracts) + + creation_code, deployed_code = get_creation_or_deployed_code(contracts) + + payload = { + "address": address, + "wallet_address": ZERO_ADDRESS, + "compiler_type": compiler_type, + "compiler_version": compiler_version, + "evm_version": evm_version, + "license_type": license_type, + "optimization": optimization, + "optimization_runs": optimization_runs, + "input_str": input_str, + "constructor_arguments": constructor_arguments, + "proxy": proxy, + "implementation": implementation, + "creation_code": creation_code, + "deployed_code": deployed_code, + } + + if compiler_type != "Solidity (Standard-Json-Input)": + libraries = request_form.get("libraries") + payload["libraries_data"] = libraries + + response = send_sync_verification_request(payload, file_list) + + if response.status_code == 200: + contracts.is_verified = True + postgres_db.session.commit() + return {"message": "Contract verified successfully"}, 200 + else: + return {"message": f"Verified contract failed: {response.text}"}, 400 + + +@contract_namespace.route("/v1/explorer/verify_contract/solidity_versions") +class ExplorerSolidityCompilerVersion(Resource): + @cache.cached(timeout=3600, query_string=True) + def get(self): + response = get_solidity_version() + if response: + compiler_versions = response.get("compiler_versions") + return {"compiler_versions": compiler_versions}, 200 + else: + raise APIError("Failed to retrieve compiler versions", code=400) + + +@contract_namespace.route("/v1/explorer/verify_contract/compiler_types") +class ExplorerCompilerType(Resource): + def get(self): + compiler_types = [ + "Solidity (Single file)", + "Solidity (Multi-Part files)", + "Solidity (Standard-Json-Input)", + "Vyper (Experimental)", + ] + return {"compiler_types": compiler_types}, 200 + + +@contract_namespace.route("/v1/explorer/verify_contract/evm_versions") +class ExplorerEvmVersions(Resource): + @cache.cached(timeout=3600, query_string=True) + def get(self): + evm_versions = get_evm_versions() + if evm_versions: + return evm_versions, 200 + raise APIError("Failed to retrieve evm versions", code=400) + + +@contract_namespace.route("/v1/explorer/verify_contract/license_types") +class ExplorerLicenseType(Resource): + @cache.cached(timeout=3600, query_string=True) + def get(self): + license_type = get_explorer_license_type() + if license_type: + return license_type, 200 + raise APIError("Failed to retrieve license types", code=400) + + +@contract_namespace.route("/v1/explorer/verify_contract/vyper_versions") +class ExplorerVyperCompilerVersion(Resource): + @cache.cached(timeout=3600, query_string=True) + def get(self): + response = get_vyper_version() + if response: + compiler_versions = response.get("compiler_versions") + return {"compiler_versions": compiler_versions}, 200 + else: + raise APIError("Failed to retrieve compiler versions", code=400) + + +@contract_namespace.route("/v1/explorer/verify_contract/check") +class ExplorerVerifyContractBeforeCheck(Resource): + def post(self): + request_body = flask.request.json + address = request_body.get("address") + + if not address: + raise APIError("Missing required data", code=400) + address = address.lower() + # Check if address exists in ContractsInfo + contracts = postgres_db.session.query(Contracts).filter_by(address=hex_string_to_bytes(address)).first() + + if not contracts or not contracts.transaction_hash: + raise APIError("The address is not a contract", code=400) + + if contracts.is_verified: + return { + "message": "This contract already verified", + "already_verified": True, + }, 200 + + return { + "message": "This contract can be verified", + "already_verified": False, + }, 200 + + +@contract_namespace.route("/v1/explorer/verify_contract/verify_proxy") +class ExplorerVerifyContract(Resource): + def post(self): + request_body = flask.request.json + proxy_contract_address = request_body.get("proxy_contract_address") + if not proxy_contract_address: + raise APIError("Please sent correct proxy contract address") + + implementation_address = get_implementation_contract(proxy_contract_address) + print(implementation_address) + if not implementation_address: + return { + "implementation_address": None, + "message": "This contract does not look like it contains any delegatecall opcode sequence.", + } + exists = get_abi_by_chain_id_address(address=implementation_address) + + if not exists: + return { + "implementation_contract_address": implementation_address, + "message": f"The implementation contract at {implementation_address} does not seem to be verified.", + "is_verified": False, + } + + return { + "implementation_contract_address": implementation_address, + "message": f"The proxy's implementation contract is found at: {implementation_address}.", + "is_verified": True, + } + + +@contract_namespace.route("/v1/explorer/verify_contract/save_proxy") +class ExplorerVerifyContract(Resource): + def post(self): + request_body = flask.request.json + proxy_contract_address = request_body.get("proxy_contract_address") + implementation_contract_address = request_body.get("implementation_contract_address") + + if not proxy_contract_address or not implementation_contract_address: + raise APIError("Not such proxy contract address", code=400) + + contract = Contracts.query.filter(Contracts.address == proxy_contract_address.lower()).first() + contract.verified_implementation_contract = implementation_contract_address.lower() + + postgres_db.session.add(contract) + postgres_db.session.commit() + return as_dict(contract) + + +@contract_namespace.route("/v1/explorer/command_api/contract") +class ExplorerContractCommandApi(Resource): + def get(self): + module = flask.request.args.get("module") + action = flask.request.args.get("action") + guid = flask.request.args.get("guid") + address = flask.request.args.get("address") + return command_normal_contract_data(module, action, address, guid) + + @limiter.limit("10 per minute") + def post(self): + request_form = flask.request.form + action = request_form.get("action") + module = request_form.get("module") + if module != "contract": + return {"message": "The module is error", "status": "0"}, 200 + + if action != "verifysourcecode": + guid = request_form.get("guid") + address = request_form.get("address") + return command_normal_contract_data(module, action, address, guid) + + address = request_form.get("contractaddress") + address = address.lower() + compiler_type = request_form.get("codeformat") + compiler_version = request_form.get("compilerversion") + optimization_used = request_form.get("optimizationUsed") + if optimization_used == "1": + optimization = True + else: + optimization = False + optimization_runs = request_form.get("runs") + if not optimization_runs: + optimization_runs = 0 + input_str = request_form.get("sourceCode") + constructor_arguments = request_form.get("constructorArguments") + license_type = "None" + evm_version = "default" + + contracts = get_contract_by_address(address) + if contracts.is_verified: + return {"message": "This contract is verified", "status": "0"}, 200 + + creation_code, deployed_code = get_creation_or_deployed_code(contracts) + payload = { + "address": address, + "compiler_type": compiler_type, + "compiler_version": compiler_version, + "evm_version": evm_version, + "license_type": license_type, + "optimization": optimization, + "optimization_runs": optimization_runs, + "input_str": input_str, + "constructor_arguments": constructor_arguments, + "creation_code": creation_code, + "deployed_code": deployed_code, + } + + response = send_async_verification_request(payload) + + if response.status_code == 202: + # todo: use async way + contracts.is_verified = True + postgres_db.session.commit() + return { + "message": "Contract successfully verified", + # "message": "Contract is being verified", + "result": response.json()["guid"], + "status": "1", + }, 200 + else: + return { + "message": response.text, + "status": "0", + }, 200 + + +@contract_namespace.route("/v1/explorer/contract//code") +class ExplorerContractCode(Resource): + def get(self, contract_address): + contract_address = contract_address.lower() + + contract = Contracts.query.get(hex_string_to_bytes(contract_address)) + if not contract or contract.is_verified == False: + raise APIError("Contract not exist or contract is not verified.", code=400) + + contracts_verification = get_contract_code_by_address(address=contract_address) + if not contracts_verification: + raise APIError("Contract code not found!", code=400) + + # need front to change + files = [] + if "folder_path" in contracts_verification: + for file in contracts_verification["folder_path"]: + files.append( + { + "name": file.split("/")[-1], + "path": "https://contract-verify-files.s3.amazonaws.com/" + file, + } + ) + contracts_verification["files"] = files + return contracts_verification, 200 diff --git a/api/app/explorer/routes.py b/api/app/explorer/routes.py index ada8881ab..ce6971533 100644 --- a/api/app/explorer/routes.py +++ b/api/app/explorer/routes.py @@ -429,7 +429,7 @@ def get(self): @explorer_namespace.route("/v1/explorer/transactions") class ExplorerTransactions(Resource): - @cache.cached(timeout=10, query_string=True) + @cache.cached(timeout=3, query_string=True) def get(self): page_index = int(flask.request.args.get("page", 1)) page_size = int(flask.request.args.get("size", 25)) @@ -1090,7 +1090,7 @@ def add_argument(self, *args, **kwargs): @explorer_namespace.route("/v1/explorer/blocks") class ExplorerBlocks(Resource): - @cache.cached(timeout=10, query_string=True) + @cache.cached(timeout=3, query_string=True) def get(self): args = blocks_parser.parse_args() page_index = args.get("page") diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index 2db95ed0a..f2f427988 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -147,7 +147,7 @@ def parse_transactions(transactions: list[Transactions]): bytea_address_list.append(transaction.to_address) transaction_json = format_to_dict(transaction) - + transaction_json["method_id"] = "0x" + transaction_json["method_id"] transaction_json["method"] = transaction_json["method_id"] transaction_json["is_contract"] = False transaction_json["contract_name"] = None @@ -162,7 +162,7 @@ def parse_transactions(transactions: list[Transactions]): # Find contract contracts = get_contracts_by_addresses(address_list=to_address_list, columns=["address"]) - contract_list = set(map(lambda x: x.address, contracts)) + contract_list = set(map(lambda x: "0x" + x.address.hex(), contracts)) method_list = [] for transaction_json in transaction_list: diff --git a/common/models/contracts.py b/common/models/contracts.py index 105d17591..5b7c42c9b 100644 --- a/common/models/contracts.py +++ b/common/models/contracts.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, func +from sqlalchemy import Column, Computed, func from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, JSONB, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -36,6 +36,10 @@ class Contracts(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) + deployed_code_hash = Column( + TIMESTAMP, Computed("encode(digest('0x'||encode(deployed_code, 'hex'), 'sha256'), 'hex')") + ) + @staticmethod def model_domain_mapping(): return [ From 87deebf8b2c6db41824b845f45bf7282a6f61d8f Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Thu, 5 Sep 2024 19:18:24 +0800 Subject: [PATCH 12/70] only able_to_reorg job's output table should update reorg sign (#89) * only able_to_reorg job's output table should update reorg sign * fixed interrupt record * enable token id job to reorg * Skip tables that have already been set reorg --- indexer/controller/reorg_controller.py | 4 +- .../controller/scheduler/reorg_scheduler.py | 1 + indexer/jobs/export_blocks_job.py | 3 +- indexer/jobs/export_token_id_infos_job.py | 1 + indexer/utils/reorg.py | 53 +++++++++++-------- 5 files changed, 36 insertions(+), 26 deletions(-) diff --git a/indexer/controller/reorg_controller.py b/indexer/controller/reorg_controller.py index cdac8cab3..27f95e693 100644 --- a/indexer/controller/reorg_controller.py +++ b/indexer/controller/reorg_controller.py @@ -78,8 +78,8 @@ def action(self, job_id=None, block_number=None, remains=None, retry_errors=True self.update_job_info( job_id, job_info={ - "last_fixed_block_number": block_number - offset, - "remain_process": limit - offset - 1, + "last_fixed_block_number": block_number - offset + 1, + "remain_process": limit - offset, "update_time": datetime.now(timezone.utc), "job_status": "interrupt", }, diff --git a/indexer/controller/scheduler/reorg_scheduler.py b/indexer/controller/scheduler/reorg_scheduler.py index 6f0b5d9de..071ede0d8 100644 --- a/indexer/controller/scheduler/reorg_scheduler.py +++ b/indexer/controller/scheduler/reorg_scheduler.py @@ -140,6 +140,7 @@ def instantiate_jobs(self): config=self.config, filters=filters, reorg=True, + reorg_jobs=self.job_classes, multicall=self._is_multicall, ) self.jobs.insert(0, export_blocks_job) diff --git a/indexer/jobs/export_blocks_job.py b/indexer/jobs/export_blocks_job.py index 6cf524730..aae05f27c 100644 --- a/indexer/jobs/export_blocks_job.py +++ b/indexer/jobs/export_blocks_job.py @@ -40,6 +40,7 @@ def __init__(self, **kwargs): self._filters = kwargs.get("filters", []) self._is_filter = kwargs.get("is_filter", False) self._specification = AlwaysFalseSpecification() if self._is_filter else AlwaysTrueSpecification() + self._reorg_jobs = kwargs.get("reorg_jobs", []) def _start(self, **kwargs): if self.able_to_reorg and self._reorg: @@ -47,7 +48,7 @@ def _start(self, **kwargs): raise FastShutdownError("PG Service is not set") reorg_block = int(kwargs["start_block"]) - set_reorg_sign(reorg_block, self._service) + set_reorg_sign(self._reorg_jobs, reorg_block, self._service) self._should_reorg_type.add(Block.type()) self._should_reorg = True diff --git a/indexer/jobs/export_token_id_infos_job.py b/indexer/jobs/export_token_id_infos_job.py index b2cae3f76..3f81c6fdb 100644 --- a/indexer/jobs/export_token_id_infos_job.py +++ b/indexer/jobs/export_token_id_infos_job.py @@ -28,6 +28,7 @@ class ExportTokenIdInfosJob(BaseExportJob): ERC1155TokenIdDetail, UpdateERC1155TokenIdDetail, ] + able_to_reorg = True def __init__(self, **kwargs): super().__init__(**kwargs) diff --git a/indexer/utils/reorg.py b/indexer/utils/reorg.py index fef99ec97..d84e22baf 100644 --- a/indexer/utils/reorg.py +++ b/indexer/utils/reorg.py @@ -3,38 +3,45 @@ from sqlalchemy import and_ -from common.models import HemeraModel, __models_imports +from common.converter.pg_converter import domain_model_mapping +from common.models import HemeraModel from common.services.postgresql_service import PostgreSQLService from common.utils.exception_control import RetriableError -from common.utils.module_loading import import_string -def set_reorg_sign(block_number, service): +def set_reorg_sign(jobs, block_number, service): conn = service.get_conn() cur = conn.cursor() try: - for model, path in __models_imports.items(): - table = import_string(f"{path}.{model}") - if hasattr(table, "reorg"): - update_stmt = ( - f"UPDATE {table.__tablename__} " - + f"SET reorg=TRUE, update_time='{datetime.utcfromtimestamp(datetime.now(timezone.utc).timestamp())}'" - ) - - if hasattr(table, "number"): - update_stmt += f"WHERE number={block_number}" - elif hasattr(table, "block_number"): - update_stmt += f"WHERE block_number={block_number}" - else: - update_stmt = None - logging.warning( - f"Reorging table: {table} has no block number info, " - f"could not complete reorg action, " - f"reorging will be skipped this table." + table_done = set() + for job in jobs: + for output in job.output_types: + model = domain_model_mapping[output.__name__] + table = model["table"] + if table.__name__ in table_done: + continue + + table_done.add(table.__name__) + if hasattr(table, "reorg"): + update_stmt = ( + f"UPDATE {table.__tablename__} " + + f"SET reorg=TRUE, update_time='{datetime.utcfromtimestamp(datetime.now(timezone.utc).timestamp())}'" ) - if update_stmt: - cur.execute(update_stmt) + if hasattr(table, "number"): + update_stmt += f"WHERE number={block_number}" + elif hasattr(table, "block_number"): + update_stmt += f"WHERE block_number={block_number}" + else: + update_stmt = None + logging.warning( + f"Reorging table: {table} has no block number info, " + f"could not complete reorg action, " + f"reorging will be skipped this table." + ) + + if update_stmt: + cur.execute(update_stmt) conn.commit() except Exception as e: logging.error(e) From b71cb380611fb81da9615931e31992bb59ce9671 Mon Sep 17 00:00:00 2001 From: li xiang Date: Fri, 6 Sep 2024 14:38:58 +0800 Subject: [PATCH 13/70] Add more type to hemera prod (#88) * Add more type to hemera prod * Add seaport 1.5 * Add recent_data to profile --- Makefile | 1 + api/app/api.py | 2 + cli/stream.py | 4 +- common/models/__init__.py | 3 + common/utils/format_utils.py | 8 +- enumeration/entity_type.py | 11 + indexer-config.yaml | 11 +- .../hemera_address_postgres_item_exporter.py | 8 +- indexer/jobs/export_token_balances_job.py | 9 +- indexer/jobs/export_token_id_infos_job.py | 9 +- indexer/jobs/filter_transaction_data_job.py | 3 + .../custom/address_index/address_index_job.py | 179 +++++---- .../domain/address_nft_1155_holders.py | 11 + .../models/address_nft_1155_holders.py | 28 ++ .../domain/address_opensea_transactions.py | 2 + .../custom/opensea/domain/opensea_order.py | 2 + .../custom/opensea/endpoint/__init__.py | 6 + .../modules/custom/opensea/endpoint/routes.py | 343 ++++++++++++++++++ .../opensea/models/address_opensea_profile.py | 27 ++ .../models/address_opensea_transaction.py | 3 +- .../models/daily_address_opensea_stats.py | 28 +- .../opensea/models/opensea_crypto_mapping.py | 12 + .../custom/opensea/models/opensea_order.py | 4 +- .../opensea/models/scheduled_metadata.py | 13 + indexer/modules/custom/opensea/opensea_job.py | 65 +++- .../opensea/parser/opensea_contract_parser.py | 7 +- indexer/specification/specification.py | 2 +- indexer/utils/json_rpc_requests.py | 5 +- indexer/utils/multicall_hemera/call.py | 9 +- indexer/utils/multicall_hemera/multi_call.py | 2 +- indexer/utils/multicall_hemera/util.py | 1 + indexer/utils/token_fetcher.py | 8 +- indexer/utils/utils.py | 6 +- pyproject.toml | 1 + 34 files changed, 686 insertions(+), 147 deletions(-) create mode 100644 indexer/modules/custom/address_index/domain/address_nft_1155_holders.py create mode 100644 indexer/modules/custom/address_index/models/address_nft_1155_holders.py create mode 100644 indexer/modules/custom/opensea/endpoint/__init__.py create mode 100644 indexer/modules/custom/opensea/endpoint/routes.py create mode 100644 indexer/modules/custom/opensea/models/address_opensea_profile.py create mode 100644 indexer/modules/custom/opensea/models/opensea_crypto_mapping.py create mode 100644 indexer/modules/custom/opensea/models/scheduled_metadata.py diff --git a/Makefile b/Makefile index 690dd25b7..04e4e910b 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,7 @@ RESET=\033[0m image: docker build $(IMAGE_FLAGS) --network host -t hemera-protocol:$(VERSION)-$(BUILD) . -q + echo "Built image hemera-protocol:$(VERSION)-$(BUILD)" test: @if [ "$(filter-out $@,$(MAKECMDGOALS))" = "" ]; then \ diff --git a/api/app/api.py b/api/app/api.py index 5a0458ab0..94e6a86d8 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -6,6 +6,7 @@ from api.app.contract.routes import contract_namespace from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace +from indexer.modules.custom.opensea.endpoint.routes import opensea_namespace # from api.app.l2_explorer.routes import l2_explorer_namespace @@ -13,5 +14,6 @@ api.add_namespace(explorer_namespace) api.add_namespace(user_operation_namespace) +api.add_namespace(opensea_namespace) api.add_namespace(contract_namespace) # api.add_namespace(l2_explorer_namespace) diff --git a/cli/stream.py b/cli/stream.py index 1537d4cb5..fec1f7050 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -332,7 +332,7 @@ def stream( if output_types is None: entity_types = calculate_entity_value(entity_types) - output_types = list(generate_output_types(entity_types)) + output_types = list(set(generate_output_types(entity_types))) else: domain_dict = Domain.get_all_domain_dict() parse_output_types = set() @@ -345,7 +345,7 @@ def stream( if not output_types: raise click.ClickException("No output types provided") - output_types = list(parse_output_types) + output_types = list(set(parse_output_types)) job_scheduler = JobScheduler( batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(provider_uri, batch=True)), diff --git a/common/models/__init__.py b/common/models/__init__.py index 1dc5c0d3d..e6fa303cb 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -3,6 +3,7 @@ from flask_sqlalchemy import SQLAlchemy from psycopg2._json import Json +from sqlalchemy import NUMERIC from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, TIMESTAMP from common.utils.module_loading import import_string, scan_subclass_by_path_patterns @@ -54,6 +55,8 @@ def general_converter(table: Type[HemeraModel], data: Domain, is_update=False): converted_data[key] = [bytes.fromhex(address[2:]) for address in getattr(data, key)] elif isinstance(column_type, JSONB) and getattr(data, key) is not None: converted_data[key] = Json(getattr(data, key)) + elif isinstance(column_type, NUMERIC) and isinstance(getattr(data, key), str): + converted_data[key] = None else: converted_data[key] = getattr(data, key) diff --git a/common/utils/format_utils.py b/common/utils/format_utils.py index 9351c6f3b..a3acace07 100644 --- a/common/utils/format_utils.py +++ b/common/utils/format_utils.py @@ -39,8 +39,10 @@ def format_to_dict(row): return None if hasattr(row, "_asdict"): return row_to_dict(row) - else: + elif hasattr(row, "__table__"): return as_dict(row) + else: + return format_value_for_json(row) def format_value_for_json(value): @@ -50,6 +52,10 @@ def format_value_for_json(value): return float(value) elif isinstance(value, bytes): return "0x" + value.hex() + elif isinstance(value, list): + return [format_value_for_json(v) for v in value] + elif isinstance(value, dict): + return {k: format_value_for_json(v) for k, v in value.items()} else: return value diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index 0fad52bb9..2c2ea64c5 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -15,12 +15,15 @@ from indexer.domain.trace import Trace from indexer.domain.transaction import Transaction from indexer.modules.custom.address_index.domain import * +from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder from indexer.modules.custom.all_features_value_record import ( AllFeatureValueRecordBlueChipHolders, AllFeatureValueRecordUniswapV3Pool, AllFeatureValueRecordUniswapV3Token, ) from indexer.modules.custom.blue_chip.domain.feature_blue_chip import BlueChipHolder +from indexer.modules.custom.opensea.domain.address_opensea_transactions import AddressOpenseaTransaction +from indexer.modules.custom.opensea.domain.opensea_order import OpenseaOrder from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import UniswapV3Pool, UniswapV3Token from indexer.modules.user_ops.domain.user_operations import UserOperationsResult @@ -41,6 +44,8 @@ class EntityType(IntFlag): ADDRESS_INDEX = 1 << 7 + OPEN_SEA = 1 << 8 + @staticmethod def combine_all_entity_types(): return reduce(lambda x, y: x | y, EntityType) @@ -114,11 +119,13 @@ def generate_output_types(entity_types): yield Token yield ERC20TokenTransfer yield ERC721TokenTransfer + yield ERC1155TokenTransfer yield AddressNftTransfer yield AddressTokenHolder yield AddressTokenTransfer yield TokenAddressNftInventory yield AddressTransaction + yield AddressNft1155Holder if entity_types & EntityType.BLUE_CHIP: yield Block @@ -130,3 +137,7 @@ def generate_output_types(entity_types): yield CurrentTokenBalance yield AllFeatureValueRecordBlueChipHolders yield BlueChipHolder + + if entity_types & EntityType.OPEN_SEA: + yield AddressOpenseaTransaction + yield OpenseaOrder diff --git a/indexer-config.yaml b/indexer-config.yaml index de9384585..fb27a0b2c 100644 --- a/indexer-config.yaml +++ b/indexer-config.yaml @@ -1,4 +1,9 @@ opensea_job: - seaport_contract_address: "0x0000000000000068f116a894984e2db1123eb395" - seaport_fee_addresses: - - "0x0000a26b00c1f0df003000390027140000faa719" \ No newline at end of file + seaport_1_6: + contract_address: "0x0000000000000068f116a894984e2db1123eb395" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_5: + contract_address: "0x00000000000000adc04c56bf30ac9d3c0aaf14dc" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" \ No newline at end of file diff --git a/indexer/exporters/hemera_address_postgres_item_exporter.py b/indexer/exporters/hemera_address_postgres_item_exporter.py index a1abcb6c0..029066ebf 100644 --- a/indexer/exporters/hemera_address_postgres_item_exporter.py +++ b/indexer/exporters/hemera_address_postgres_item_exporter.py @@ -2,16 +2,16 @@ from datetime import datetime from typing import Type -import sqlalchemy from dateutil.tz import tzlocal from psycopg2.extras import execute_values from common.converter.pg_converter import domain_model_mapping from common.models import HemeraModel from common.services.hemera_postgresql_service import HemeraPostgreSQLService -from common.services.postgresql_service import PostgreSQLService +from indexer.domain.token import Token from indexer.exporters.base_exporter import BaseExporter, group_by_item_type from indexer.modules.custom.address_index.domain import * +from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder logger = logging.getLogger(__name__) @@ -23,6 +23,10 @@ class HemeraAddressPostgresItemExporter(BaseExporter): AddressTransaction, AddressNftTransfer, AddressTokenTransfer, + AddressNft1155Holder, + AddressTokenHolder, + Token, + TokenAddressNftInventory, ] def __init__(self, output, chain_id): diff --git a/indexer/jobs/export_token_balances_job.py b/indexer/jobs/export_token_balances_job.py index 7ca4b3fab..bbbded4e2 100644 --- a/indexer/jobs/export_token_balances_job.py +++ b/indexer/jobs/export_token_balances_job.py @@ -139,7 +139,8 @@ def encode_balance_abi_parameter(address, token_type, token_id): @calculate_execution_time def extract_token_parameters( - token_transfers: List[Union[ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer]] + token_transfers: List[Union[ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer]], + block_number: Union[Optional[int], str] = None, ): origin_parameters = set() token_parameters = [] @@ -148,7 +149,7 @@ def extract_token_parameters( "token_address": transfer.token_address, "token_id": (transfer.token_id if isinstance(transfer, ERC1155TokenTransfer) else None), "token_type": transfer.token_type, - "block_number": transfer.block_number, + "block_number": transfer.block_number if block_number is None else block_number, "block_timestamp": transfer.block_timestamp, } if transfer.from_address != ZERO_ADDRESS: @@ -165,8 +166,8 @@ def extract_token_parameters( "token_type": parameter.token_type, "param_to": parameter.token_address, "param_data": encode_balance_abi_parameter(parameter.address, parameter.token_type, parameter.token_id), - "param_number": hex(parameter.block_number), - "block_number": parameter.block_number, + "param_number": parameter.block_number if block_number is None else block_number, + "block_number": parameter.block_number if block_number is None else None, "block_timestamp": parameter.block_timestamp, } ) diff --git a/indexer/jobs/export_token_id_infos_job.py b/indexer/jobs/export_token_id_infos_job.py index 3f81c6fdb..bd3b6a855 100644 --- a/indexer/jobs/export_token_id_infos_job.py +++ b/indexer/jobs/export_token_id_infos_job.py @@ -1,6 +1,6 @@ import logging from itertools import groupby -from typing import List +from typing import List, Optional, Union from indexer.domain.token_id_infos import ( ERC721TokenIdChange, @@ -90,6 +90,7 @@ def _process(self, **kwargs): def generate_token_id_info( erc721_token_transfers: List[ERC721TokenTransfer], erc1155_token_transfers: List[ERC1155TokenTransfer], + block_number: Union[Optional[int], str] = None, ) -> List[dict]: info = [] seen_keys = set() @@ -100,13 +101,13 @@ def generate_token_id_info( if key not in seen_keys: seen_keys.add(key) - if token_transfer.from_address == ZERO_ADDRESS: + if token_transfer.from_address == ZERO_ADDRESS and block_number is None: info.append( { "address": token_transfer.token_address, "token_id": token_transfer.token_id, "token_type": token_transfer.token_type, - "block_number": token_transfer.block_number, + "block_number": token_transfer.block_number if block_number is None else block_number, "block_timestamp": token_transfer.block_timestamp, "is_get_token_uri": True, "request_id": abs(hash(key + "_get_token_uri")), @@ -118,7 +119,7 @@ def generate_token_id_info( "address": token_transfer.token_address, "token_id": token_transfer.token_id, "token_type": token_transfer.token_type, - "block_number": token_transfer.block_number, + "block_number": token_transfer.block_number if block_number is None else block_number, "block_timestamp": token_transfer.block_timestamp, "is_get_token_uri": False, "request_id": abs(hash(key)), diff --git a/indexer/jobs/filter_transaction_data_job.py b/indexer/jobs/filter_transaction_data_job.py index bccb41a7f..ed04448c1 100644 --- a/indexer/jobs/filter_transaction_data_job.py +++ b/indexer/jobs/filter_transaction_data_job.py @@ -9,3 +9,6 @@ class FilterTransactionDataJob(ExtensionJob): def get_filter(self): raise NotImplementedError + + def get_filter_transactions(self): + return list(filter(self.get_filter().is_satisfied_by, self._data_buff[Transaction.type()])) diff --git a/indexer/modules/custom/address_index/address_index_job.py b/indexer/modules/custom/address_index/address_index_job.py index 8abd24582..cfdc32e21 100644 --- a/indexer/modules/custom/address_index/address_index_job.py +++ b/indexer/modules/custom/address_index/address_index_job.py @@ -1,25 +1,26 @@ import json import logging -from dataclasses import dataclass from enum import Enum +from itertools import groupby from typing import List, Union from eth_abi import abi -from indexer.domain import dict_to_dataclass +from indexer.domain.token_id_infos import UpdateERC721TokenIdDetail from indexer.domain.token_transfer import ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer from indexer.domain.transaction import Transaction from indexer.executors.batch_work_executor import BatchWorkExecutor -from indexer.jobs.base_job import BaseJob, ExtensionJob -from indexer.jobs.export_token_balances_job import BALANCE_OF_ABI_FUNCTION -from indexer.jobs.export_tokens_and_transfers_job import OWNER_OF_ABI_FUNCTION +from indexer.jobs.base_job import ExtensionJob +from indexer.jobs.export_token_balances_job import extract_token_parameters +from indexer.jobs.export_token_id_infos_job import generate_token_id_info +from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder from indexer.modules.custom.address_index.domain.address_nft_transfer import AddressNftTransfer from indexer.modules.custom.address_index.domain.address_token_holder import AddressTokenHolder from indexer.modules.custom.address_index.domain.address_token_transfer import AddressTokenTransfer from indexer.modules.custom.address_index.domain.address_transaction import AddressTransaction from indexer.modules.custom.address_index.domain.token_address_nft_inventory import TokenAddressNftInventory -from indexer.utils.abi import encode_abi, function_abi_to_4byte_selector_str -from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc_without_block_number, generate_json_rpc +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc_without_block_number +from indexer.utils.token_fetcher import TokenFetcher from indexer.utils.utils import ZERO_ADDRESS, rpc_response_to_result, zip_rpc_response logger = logging.getLogger(__name__) @@ -206,73 +207,6 @@ def nft_transfers_to_address_nft_transfers(transfers: Union[List[ERC721TokenTran ) -balance_of_sig_prefix = function_abi_to_4byte_selector_str(BALANCE_OF_ABI_FUNCTION) -owner_of_sig_prefix = function_abi_to_4byte_selector_str(OWNER_OF_ABI_FUNCTION) - - -def encode_balance_abi_parameter(address): - return encode_abi(BALANCE_OF_ABI_FUNCTION, [address], balance_of_sig_prefix) - - -def encode_owner_of_abi_parameter(token_id): - return encode_abi(OWNER_OF_ABI_FUNCTION, [token_id], owner_of_sig_prefix) - - -@dataclass(frozen=True) -class TokenBalanceParam: - address: str - token_address: str - - -@dataclass(frozen=True) -class TokenOwnerParam: - token_address: str - token_id: int - - -def extract_token_parameters(token_transfers: List[Union[ERC20TokenTransfer, ERC721TokenTransfer]]): - origin_parameters = set() - token_parameters = [] - for transfer in token_transfers: - if transfer.from_address != ZERO_ADDRESS: - origin_parameters.add( - TokenBalanceParam(address=transfer.from_address, token_address=transfer.token_address) - ) - if transfer.to_address != ZERO_ADDRESS: - origin_parameters.add(TokenBalanceParam(address=transfer.to_address, token_address=transfer.token_address)) - - for parameter in origin_parameters: - token_parameters.append( - { - "address": parameter.address, - "token_address": parameter.token_address, - "param_to": parameter.token_address, - "param_data": encode_balance_abi_parameter(parameter.address), - } - ) - - return token_parameters - - -def extract_nft_owner_parameters(nft_transfers: List[ERC721TokenTransfer]): - origin_parameters = set() - token_parameters = [] - for transfer in nft_transfers: - origin_parameters.add(TokenOwnerParam(token_address=transfer.token_address, token_id=transfer.token_id)) - - for parameter in origin_parameters: - token_parameters.append( - { - "token_address": parameter.token_address, - "token_id": parameter.token_id, - "param_to": parameter.token_address, - "param_data": encode_owner_of_abi_parameter(parameter.token_id), - } - ) - - return token_parameters - - class AddressIndexerJob(ExtensionJob): dependency_types = [Transaction, ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer] output_types = [ @@ -281,6 +215,7 @@ class AddressIndexerJob(ExtensionJob): AddressTokenTransfer, AddressNftTransfer, AddressTokenHolder, + AddressNft1155Holder, ] def __init__(self, **kwargs): @@ -290,30 +225,92 @@ def __init__(self, **kwargs): kwargs["max_workers"], job_name=self.__class__.__name__, ) + self.token_fetcher = TokenFetcher(self._web3, kwargs) + self._is_multi_call = kwargs["multicall"] - def _collect(self, **kwargs): - erc20_transfers = self._get_domain(ERC20TokenTransfer) - erc721_transfers = self._get_domain(ERC721TokenTransfer) - token_transfers = erc20_transfers + erc721_transfers - parameters = extract_token_parameters(token_transfers) + def _collect_all_token_transfers(self): + token_transfers = [] + if ERC20TokenTransfer.type() in self._data_buff: + token_transfers += self._data_buff[ERC20TokenTransfer.type()] - self._batch_work_executor.execute(parameters, self._export_token_balances_batch, total_items=len(parameters)) - self._batch_work_executor.wait() + if ERC721TokenTransfer.type() in self._data_buff: + token_transfers += self._data_buff[ERC721TokenTransfer.type()] - parameters = extract_nft_owner_parameters(erc721_transfers) + if ERC1155TokenTransfer.type() in self._data_buff: + token_transfers += self._data_buff[ERC1155TokenTransfer.type()] - self._batch_work_executor.execute(parameters, self._export_token_owner_batch, total_items=len(parameters)) - self._batch_work_executor.wait() + return token_transfers - def _export_token_owner_batch(self, parameters): - token_balances = token_owner_rpc_requests(self._batch_web3_provider.make_request, parameters, self._is_batch) - for token_balance in token_balances: - self._collect_domain(dict_to_dataclass(token_balance, TokenAddressNftInventory)) + def _collect(self, **kwargs): + token_transfers = self._collect_all_token_transfers() + + all_token_parameters = extract_token_parameters(token_transfers, "latest") + all_token_parameters.sort(key=lambda x: (x["address"], x["token_address"], x.get("token_id") or 0)) + parameters = [ + list(group)[-1] + for key, group in groupby(all_token_parameters, lambda x: (x["address"], x["token_address"], x["token_id"])) + ] + + all_owner_parameters = generate_token_id_info(self._data_buff[ERC721TokenTransfer.type()], [], "latest") + all_owner_parameters.sort(key=lambda x: (x["address"], x["token_id"])) + owner_parameters = [ + list(group)[-1] for key, group in groupby(all_owner_parameters, lambda x: (x["address"], x["token_id"])) + ] + + if self._is_multi_call: + self._collect_balance_batch(parameters) + self._collect_owner_batch(owner_parameters) + else: + self._batch_work_executor.execute(parameters, self._collect_balance_batch, total_items=len(parameters)) + self._batch_work_executor.wait() + self._batch_work_executor.execute( + owner_parameters, self._collect_owner_batch, total_items=len(owner_parameters) + ) + self._batch_work_executor.wait() + + def _collect_owner_batch(self, token_list): + items = self.token_fetcher.fetch_token_ids_info(token_list) + update_erc721_token_id_details = [] + for item in items: + if item.type() == UpdateERC721TokenIdDetail.type(): + update_erc721_token_id_details.append(item) + + update_erc721_token_id_details = [ + list(group)[-1] + for key, group in groupby( + update_erc721_token_id_details, + lambda x: (x.token_address, x.token_id), + ) + ] - def _export_token_balances_batch(self, parameters): - token_balances = token_balances_rpc_requests(self._batch_web3_provider.make_request, parameters, self._is_batch) + for item in update_erc721_token_id_details: + self._collect_domain( + TokenAddressNftInventory( + token_address=item.token_address, token_id=item.token_id, wallet_address=item.token_owner + ) + ) + + def _collect_balance_batch(self, parameters): + token_balances = self.token_fetcher.fetch_token_balance(parameters) + token_balances.sort(key=lambda x: (x["address"], x["token_address"])) for token_balance in token_balances: - self._collect_domain(dict_to_dataclass(token_balance, AddressTokenHolder)) + if token_balance["token_type"] == "ERC1155": + self._collect_domain( + AddressNft1155Holder( + address=token_balance["address"], + token_address=token_balance["token_address"], + token_id=token_balance["token_id"], + balance_of=token_balance["balance"], + ) + ) + else: + self._collect_domain( + AddressTokenHolder( + address=token_balance["address"], + token_address=token_balance["token_address"], + balance_of=token_balance["balance"], + ) + ) def _process(self, **kwargs): transactions = self._get_domain(Transaction) diff --git a/indexer/modules/custom/address_index/domain/address_nft_1155_holders.py b/indexer/modules/custom/address_index/domain/address_nft_1155_holders.py new file mode 100644 index 000000000..30eb73fc2 --- /dev/null +++ b/indexer/modules/custom/address_index/domain/address_nft_1155_holders.py @@ -0,0 +1,11 @@ +from dataclasses import dataclass + +from indexer.domain import Domain + + +@dataclass +class AddressNft1155Holder(Domain): + address: str + token_address: str + token_id: int + balance_of: str diff --git a/indexer/modules/custom/address_index/models/address_nft_1155_holders.py b/indexer/modules/custom/address_index/models/address_nft_1155_holders.py new file mode 100644 index 000000000..bceb89b20 --- /dev/null +++ b/indexer/modules/custom/address_index/models/address_nft_1155_holders.py @@ -0,0 +1,28 @@ +from datetime import datetime + +from sqlalchemy import TIMESTAMP, Column, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC + +from common.models import HemeraModel, general_converter + + +class AddressNftTokenHolders(HemeraModel): + __tablename__ = "address_nft_1155_holders" + + address = Column(BYTEA, primary_key=True) + token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + balance_of = Column(NUMERIC(100)) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "AddressNft1155Holder", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/opensea/domain/address_opensea_transactions.py b/indexer/modules/custom/opensea/domain/address_opensea_transactions.py index d89d19f03..ba37ab995 100644 --- a/indexer/modules/custom/opensea/domain/address_opensea_transactions.py +++ b/indexer/modules/custom/opensea/domain/address_opensea_transactions.py @@ -22,3 +22,5 @@ class AddressOpenseaTransaction(Domain): log_index: int block_timestamp: int block_hash: str + + protocol_version: str = "1.6" diff --git a/indexer/modules/custom/opensea/domain/opensea_order.py b/indexer/modules/custom/opensea/domain/opensea_order.py index 052bee85e..4caee89fb 100644 --- a/indexer/modules/custom/opensea/domain/opensea_order.py +++ b/indexer/modules/custom/opensea/domain/opensea_order.py @@ -16,3 +16,5 @@ class OpenseaOrder(Domain): transaction_hash: str log_index: int block_number: int + + protocol_version: str = "1.6" diff --git a/indexer/modules/custom/opensea/endpoint/__init__.py b/indexer/modules/custom/opensea/endpoint/__init__.py new file mode 100644 index 000000000..483291fe6 --- /dev/null +++ b/indexer/modules/custom/opensea/endpoint/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from flask_restx.namespace import Namespace + +opensea_namespace = Namespace("Open Sea Namespace", path="/", description="Open Sea API") diff --git a/indexer/modules/custom/opensea/endpoint/routes.py b/indexer/modules/custom/opensea/endpoint/routes.py new file mode 100644 index 000000000..030d213a9 --- /dev/null +++ b/indexer/modules/custom/opensea/endpoint/routes.py @@ -0,0 +1,343 @@ +from datetime import date, datetime +from functools import lru_cache +from typing import Any, Dict, List, Tuple, Union + +from flask import request +from flask_restx import Resource +from sqlalchemy import and_, case, desc, func + +from api.app.cache import cache +from api.app.token.token_prices import TokenHourlyPrices +from common.models import db +from common.models.tokens import Tokens +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict, format_to_dict +from indexer.modules.custom.opensea.endpoint import opensea_namespace +from indexer.modules.custom.opensea.models.address_opensea_profile import AddressOpenseaProfile +from indexer.modules.custom.opensea.models.address_opensea_transaction import AddressOpenseaTransactions +from indexer.modules.custom.opensea.models.opensea_crypto_mapping import OpenseaCryptoTokenMapping +from indexer.modules.custom.opensea.models.opensea_order import OpenseaOrders +from indexer.modules.custom.opensea.models.scheduled_metadata import ScheduledMetadata +from indexer.modules.custom.opensea.opensea_job import ( + OpenseaTransactionType, + get_item_type_string, + get_opensea_transaction_type_string, +) + +PAGE_SIZE = 10 +MAX_TRANSACTION = 500000 +MAX_TRANSACTION_WITH_CONDITION = 10000 +MAX_INTERNAL_TRANSACTION = 10000 +MAX_TOKEN_TRANSFER = 10000 + + +def get_opensea_profile(address: Union[str, bytes]) -> dict: + """ + Fetch and combine OpenSea profile data from both the profile table and recent transactions. + """ + address_bytes = bytes.fromhex(address[2:]) if isinstance(address, str) else address + + profile = db.session.query(AddressOpenseaProfile).filter_by(address=address_bytes).first() + if not profile: + raise APIError("No OpenSea profile found for this address", code=400) + + profile_data = as_dict(profile) + + last_timestamp = db.session.query(func.max(ScheduledMetadata.last_data_timestamp)).scalar() + recent_data = get_recent_opensea_transactions(address_bytes, last_timestamp) + + for key in recent_data: + if key != "address": + profile_data[key] += recent_data[key] + + return profile_data | get_latest_opensea_transaction_by_address(address) + + +def get_recent_opensea_transactions(address: bytes, timestamp: datetime) -> Dict[str, int]: + """ + Fetch recent OpenSea transactions data for a given address. + """ + base_query = db.session.query(AddressOpenseaTransactions.address) + + # Define transaction types + transaction_types = [ + (OpenseaTransactionType.BUY, "buy"), + (OpenseaTransactionType.SELL, "sell"), + (OpenseaTransactionType.SWAP, "swap"), + ] + + for tx_type, tx_name in transaction_types: + # Count distinct transaction hashes + base_query = base_query.add_columns( + func.count(func.distinct(AddressOpenseaTransactions.transaction_hash)) + .filter(AddressOpenseaTransactions.transaction_type == tx_type.value) + .label(f"{tx_name}_txn_count") + ) + # Count opensa transactions + base_query = base_query.add_columns( + func.count() + .filter(AddressOpenseaTransactions.transaction_type == tx_type.value) + .label(f"{tx_name}_opensea_order_count") + ) + + # Apply filters and group by + query = base_query.filter( + and_(AddressOpenseaTransactions.address == address, AddressOpenseaTransactions.block_timestamp > timestamp) + ).group_by(AddressOpenseaTransactions.address) + + result = query.one_or_none() + + if not result: + return { + f"{tx_name}_{hash_type}_count": 0 + for tx_name in ["buy", "sell", "swap"] + for hash_type in ["txn", "opensea_order"] + } + + return format_to_dict(result) + + +def get_opensea_order_count_by_address(address: Union[str, bytes]): + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + result = ( + db.session.query(AddressOpenseaProfile) + .with_entities(AddressOpenseaProfile.opensea_order_count) + .filter(AddressOpenseaProfile.address == address) + .first() + ) + return result + + +def get_opensea_address_order_cnt(address: Union[str, bytes]): + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + last_timestamp = db.session.query(func.max(ScheduledMetadata.last_data_timestamp)).scalar() + recently_txn_count = ( + db.session.query(AddressOpenseaTransactions.address) + .filter( + and_( + AddressOpenseaTransactions.address == address, + ( + AddressOpenseaTransactions.block_timestamp >= last_timestamp.date() + if last_timestamp is not None + else True + ), + ), + ) + .count() + ) + result = get_opensea_order_count_by_address(address) + past_txn_count = 0 if not result else result[0] + total_count = past_txn_count + recently_txn_count + return total_count + + +@lru_cache(maxsize=1000) +def get_token_price_symbol(token_address: str) -> str: + if token_address == "0x0000000000000000000000000000000000000000": + return "ETH" + mapping = ( + db.session.query(OpenseaCryptoTokenMapping) + .filter(OpenseaCryptoTokenMapping.address_var == token_address) + .first() + ) + return mapping.price_symbol if mapping and mapping.price_symbol else "ETH" + + +@cache.memoize(timeout=86400) +def get_token_daily_price(token_symbol: str, day: date) -> float: + end_of_day = datetime.combine(day, datetime.max.time()) + price_record = ( + db.session.query(TokenHourlyPrices) + .filter(TokenHourlyPrices.symbol == token_symbol) + .filter(TokenHourlyPrices.timestamp <= end_of_day) + .order_by(desc(TokenHourlyPrices.timestamp)) + .first() + ) + return float(price_record.price) if price_record else 0 + + +def get_token_price(token_address: str, timestamp: datetime) -> float: + price_symbol = get_token_price_symbol(token_address) + return get_token_daily_price(price_symbol, timestamp.date()) + + +def calculate_usd_value(amount: float, token_address: str, timestamp: datetime) -> float: + price = get_token_price(token_address, timestamp) + return amount * price + + +def parse_item(item: Dict[str, Any], token_info: Dict[str, Any], timestamp: datetime) -> Dict[str, Any]: + parsed_item = { + "token_address": item["token"], + "token_name": token_info.get(item["token"], {}).get("name"), + "token_type": get_item_type_string(item["itemType"]), + "token_symbol": token_info.get(item["token"], {}).get("symbol"), + "amount": ( + int(item["amount"]) + if item["itemType"] >= 2 + else float(item["amount"]) / (10 ** token_info.get(item["token"], {}).get("decimals", 18)) + ), + "identifier": int(item["identifier"]) if item["identifier"] else None, + } + if item["itemType"] < 2: + parsed_item["usd_value"] = calculate_usd_value(parsed_item["amount"], item["token"], timestamp) + return parsed_item + + +def fetch_token_info(token_addresses: List[str]) -> Dict[str, Any]: + byte_addresses = [bytes.fromhex(addr[2:]) for addr in token_addresses] + token_infos = db.session.query(Tokens).filter(Tokens.address.in_(byte_addresses)).all() + return {"0x" + token.address.hex(): as_dict(token) for token in token_infos} + + +def parse_opensea_order(order: OpenseaOrders, token_info: Dict[str, Any]) -> Dict[str, Any]: + parsed_order = { + "order_hash": order.order_hash, + "offer": [parse_item(item, token_info, order.block_timestamp) for item in order.offer], + "consideration": [parse_item(item, token_info, order.block_timestamp) for item in order.consideration], + "transaction_hash": order.transaction_hash, + "block_timestamp": order.block_timestamp, + "block_number": order.block_number, + "zone": order.zone, + "protocol_version": order.protocol_version, + } + return parsed_order + + +def format_opensea_transaction(transaction: Dict[str, Any]) -> Dict[str, Any]: + formatted_transaction = { + "address": transaction["address"], + "transaction_hash": transaction["transaction_hash"], + "block_number": transaction["block_number"], + "block_timestamp": transaction["block_timestamp"], + "order_hash": transaction["order_hash"], + "protocol_version": transaction["protocol_version"], + "zone": transaction["zone"], + "transaction_type": get_opensea_transaction_type_string(transaction["transaction_type"]), + } + + # Calculate volume and determine items + if (not transaction["is_offer"] and transaction["transaction_type"] == 0) or ( + transaction["is_offer"] and transaction["transaction_type"] == 1 + ): # Buy transaction + volume = sum(item.get("usd_value", 0) for item in transaction["consideration"]) + items = transaction["offer"] + else: # Sell transaction + volume = sum(item.get("usd_value", 0) for item in transaction["offer"]) + items = transaction["consideration"] + + formatted_transaction["volume_usd"] = volume + + formatted_items = [] + for item in items: + formatted_item = { + "token_address": item["token_address"], + "token_name": item["token_name"], + "token_type": item["token_type"], + "token_symbol": item["token_symbol"], + "amount": item["amount"], + "identifier": item["identifier"], + } + formatted_items.append(formatted_item) + + formatted_transaction["items"] = formatted_items + + return formatted_transaction + + +def parse_opensea_order_transactions(transactions: List[AddressOpenseaTransactions]) -> List[Dict[str, Any]]: + order_hashes = [transaction.order_hash for transaction in transactions] + orders = db.session.query(OpenseaOrders).filter(OpenseaOrders.order_hash.in_(order_hashes)).all() + + token_addresses = set() + for order in orders: + for item in order.offer + order.consideration: + token_addresses.add(item["token"]) + + token_info = fetch_token_info(list(token_addresses)) + order_dict = {} + for order in orders: + order_dict["0x" + order.order_hash.hex()] = parse_opensea_order(order, token_info) + + parsed_transactions = [] + for transaction in transactions: + transaction_dict = format_to_dict(as_dict(transaction) | order_dict.get("0x" + transaction.order_hash.hex())) + + parsed_transactions.append(format_opensea_transaction(transaction_dict)) + + return parsed_transactions + + +def get_opensea_transactions_by_address(address, limit=1, offset=0): + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + transactions = ( + db.session.query(AddressOpenseaTransactions) + .order_by( + AddressOpenseaTransactions.block_number.desc(), + AddressOpenseaTransactions.log_index.desc(), + ) + .filter(AddressOpenseaTransactions.address == address) + .limit(limit) + .offset(offset) + .all() + ) + return transactions + + +def get_latest_opensea_transaction_by_address(address: Union[str, bytes]): + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + last_opensea_transaction = ( + db.session() + .query(AddressOpenseaTransactions) + .filter_by(address=address) + .order_by(desc(AddressOpenseaTransactions.block_number), desc(AddressOpenseaTransactions.log_index)) + .first() + ) + if not last_opensea_transaction: + return {} + return { + "latest_transaction_hash": "0x" + last_opensea_transaction.transaction_hash.hex(), + "latest_block_timestamp": last_opensea_transaction.block_timestamp.astimezone().isoformat("T", "seconds"), + } + + +@opensea_namespace.route("/v1/explorer/custom/opensea/address/
/profile") +class ExplorerCustomOpenseaAddressProfile(Resource): + @cache.cached(timeout=60) + def get(self, address): + address = address.lower() + + profile = get_opensea_profile(address) + + return profile, 200 + + +@opensea_namespace.route("/v1/explorer/custom/opensea/address/
/transactions") +class ExplorerCustomOpenseaAddressTransactions(Resource): + @cache.cached(timeout=10) + def get(self, address): + address = address.lower() + page_index = int(request.args.get("page", 1)) + page_size = int(request.args.get("size", PAGE_SIZE)) + + opensea_transactions = get_opensea_transactions_by_address( + address, + limit=page_size, + offset=(page_index - 1) * page_size, + ) + + if len(opensea_transactions) < page_size: + total_count = len(opensea_transactions) + else: + total_count = get_opensea_address_order_cnt(address) + + transaction_list = parse_opensea_order_transactions(opensea_transactions) + + return { + "data": transaction_list, + "total": total_count, + }, 200 diff --git a/indexer/modules/custom/opensea/models/address_opensea_profile.py b/indexer/modules/custom/opensea/models/address_opensea_profile.py new file mode 100644 index 000000000..a205dcd85 --- /dev/null +++ b/indexer/modules/custom/opensea/models/address_opensea_profile.py @@ -0,0 +1,27 @@ +from sqlalchemy import NUMERIC, Column, func +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, JSONB, TIMESTAMP + +from common.models import HemeraModel + + +class AddressOpenseaProfile(HemeraModel): + __tablename__ = "af_opensea_profile" + + address = Column(BYTEA, primary_key=True) + buy_txn_count = Column(BIGINT, default=0) + sell_txn_count = Column(BIGINT, default=0) + swap_txn_count = Column(BIGINT, default=0) + buy_opensea_order_count = Column(BIGINT, default=0) + sell_opensea_order_count = Column(BIGINT, default=0) + swap_opensea_order_count = Column(BIGINT, default=0) + buy_nft_stats = Column(JSONB) + sell_nft_stats = Column(JSONB) + buy_volume_usd = Column(NUMERIC) + sell_volume_usd = Column(NUMERIC) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + first_transaction_hash = Column(BYTEA) + first_block_timestamp = Column(TIMESTAMP) + txn_count = Column(BIGINT, default=0) + opensea_order_count = Column(BIGINT, default=0) + volume_usd = Column(NUMERIC, default=0) diff --git a/indexer/modules/custom/opensea/models/address_opensea_transaction.py b/indexer/modules/custom/opensea/models/address_opensea_transaction.py index d96fc60c2..44592e5fe 100644 --- a/indexer/modules/custom/opensea/models/address_opensea_transaction.py +++ b/indexer/modules/custom/opensea/models/address_opensea_transaction.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, func +from sqlalchemy import VARCHAR, Column, func from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP from common.models import HemeraModel, general_converter @@ -28,6 +28,7 @@ class AddressOpenseaTransactions(HemeraModel): block_timestamp = Column(TIMESTAMP) block_hash = Column(BYTEA, primary_key=True) reorg = Column(BOOLEAN, default=False) + protocol_version = Column(VARCHAR, server_default="1.6") @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py index 725f6137e..131d701bb 100644 --- a/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py +++ b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py @@ -1,5 +1,5 @@ -from sqlalchemy import INT, NUMERIC, Column, Date, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP +from sqlalchemy import INTEGER, NUMERIC, Column, Date, func +from sqlalchemy.dialects.postgresql import BYTEA, JSONB, TIMESTAMP from common.models import HemeraModel @@ -8,20 +8,24 @@ class DailyAddressOpenseaTransactions(HemeraModel): __tablename__ = "af_opensea_daily_transactions" address = Column(BYTEA, primary_key=True) - related_address = Column(BYTEA) - transaction_type = Column(SMALLINT) - block_date = Column(Date, primary_key=True) - buy_txn_count = Column(INT) - sell_txn_count = Column(INT) - swap_txn_count = Column(INT) + buy_txn_count = Column(INTEGER) + sell_txn_count = Column(INTEGER) + swap_txn_count = Column(INTEGER) + + buy_opensea_order_count = Column(INTEGER) + sell_opensea_order_count = Column(INTEGER) + swap_opensea_order_count = Column(INTEGER) + + buy_nft_stats = Column(JSONB) + sell_nft_stats = Column(JSONB) - buy_txn_volume_crypto = Column(JSONB) - sell_txn_volume_crypto = Column(JSONB) + buy_volume_crypto = Column(JSONB) + sell_volume_crypto = Column(JSONB) - buy_txn_volume_usd = Column(NUMERIC) - sell_txn_volume_usd = Column(NUMERIC) + buy_volume_usd = Column(NUMERIC) + sell_volume_usd = Column(NUMERIC) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) diff --git a/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py b/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py new file mode 100644 index 000000000..309d1bb31 --- /dev/null +++ b/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py @@ -0,0 +1,12 @@ +from sqlalchemy import INTEGER, VARCHAR, Column + +from common.models import HemeraModel + + +class OpenseaCryptoTokenMapping(HemeraModel): + __tablename__ = "af_opensea_na_crypto_token_mapping" + + address_var = Column(VARCHAR, primary_key=True) + price_symbol = Column(VARCHAR) + + decimals = Column(INTEGER) diff --git a/indexer/modules/custom/opensea/models/opensea_order.py b/indexer/modules/custom/opensea/models/opensea_order.py index ea089e5f0..1a048c9b0 100644 --- a/indexer/modules/custom/opensea/models/opensea_order.py +++ b/indexer/modules/custom/opensea/models/opensea_order.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, func +from sqlalchemy import VARCHAR, Column, func from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP from common.models import HemeraModel, general_converter @@ -25,6 +25,8 @@ class OpenseaOrders(HemeraModel): block_hash = Column(BYTEA, primary_key=True) reorg = Column(BOOLEAN, default=False) + protocol_version = Column(VARCHAR, server_default="1.6") + @staticmethod def model_domain_mapping(): return [ diff --git a/indexer/modules/custom/opensea/models/scheduled_metadata.py b/indexer/modules/custom/opensea/models/scheduled_metadata.py new file mode 100644 index 000000000..bbda8a1bf --- /dev/null +++ b/indexer/modules/custom/opensea/models/scheduled_metadata.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, String +from sqlalchemy.dialects.postgresql import TIMESTAMP + +from common.models import HemeraModel + + +class ScheduledMetadata(HemeraModel): + __tablename__ = "af_opensea_na_scheduled_metadata" + + id = Column(Integer, primary_key=True) + dag_id = Column(String) + execution_date = Column(TIMESTAMP) + last_data_timestamp = Column(TIMESTAMP) diff --git a/indexer/modules/custom/opensea/opensea_job.py b/indexer/modules/custom/opensea/opensea_job.py index 6069497e3..4ebac1049 100644 --- a/indexer/modules/custom/opensea/opensea_job.py +++ b/indexer/modules/custom/opensea/opensea_job.py @@ -26,6 +26,13 @@ class OpenseaTransactionType(Enum): SWAP = 2 +def get_opensea_transaction_type_string(value: int) -> str: + try: + return OpenseaTransactionType(value).name.lower() + except ValueError: + return f"UNKNOWN_TYPE_{value}" + + class ItemType(Enum): # 0: ETH on mainnet, MATIC on polygon, etc. NATIVE = 0 @@ -41,6 +48,13 @@ class ItemType(Enum): ERC1155_WITH_CRITERIA = 5 +def get_item_type_string(value: int) -> str: + try: + return ItemType(value).name + except ValueError: + return f"UNKNOWN_TYPE_{value}" + + def get_opensea_transaction_type(offer, consideration): for item in offer: if item["itemType"] > 1: @@ -104,8 +118,8 @@ def __init__(self, **kwargs): job_name=self.__class__.__name__, ) - self._seaport_contract_address = self.user_defined_config["seaport_contract_address"] - self._seaport_fee_addresses = self.user_defined_config["seaport_fee_addresses"] + self._seaport_1_6_config = self.user_defined_config.get("seaport_1_6", None) + self._seaport_1_5_config = self.user_defined_config.get("seaport_1_5", None) def _collect(self, **kwargs): pass @@ -124,13 +138,15 @@ def transfer(self, opensea_logs: List[OpenseaLog]): transaction_hash=opensea_log.transaction_hash, block_number=opensea_log.block_number, log_index=opensea_log.log_index, + protocol_version=opensea_log.protocol_version, ) offer = calculate_total_amount(opensea_log.offer) consideration = calculate_total_amount(opensea_log.consideration) - fee = calculate_fee_amount(opensea_log.consideration, self._seaport_fee_addresses) + fee = calculate_fee_amount(opensea_log.consideration, opensea_log.fee_addresses) opensea_transaciton_type = get_opensea_transaction_type(opensea_log.offer, opensea_log.consideration) - + if opensea_log.offerer == opensea_log.recipient: + continue yield AddressOpenseaTransaction( address=opensea_log.offerer, related_address=opensea_log.recipient, @@ -146,6 +162,7 @@ def transfer(self, opensea_logs: List[OpenseaLog]): log_index=opensea_log.log_index, block_timestamp=opensea_log.block_timestamp, block_hash=opensea_log.block_hash, + protocol_version=opensea_log.protocol_version, ) yield AddressOpenseaTransaction( address=opensea_log.recipient, @@ -166,24 +183,44 @@ def transfer(self, opensea_logs: List[OpenseaLog]): log_index=opensea_log.log_index, block_timestamp=opensea_log.block_timestamp, block_hash=opensea_log.block_hash, + protocol_version=opensea_log.protocol_version, ) def _process(self, **kwargs): - transactions = self._get_domain(Transaction) + transactions = self.get_filter_transactions() for transaction in transactions: - self._collect_domains( - self.transfer( - parse_opensea_transaction_order_fulfilled_event(transaction, self._seaport_contract_address) + orders = [] + if self._seaport_1_6_config: + orders += parse_opensea_transaction_order_fulfilled_event( + transaction, + self._seaport_1_6_config["contract_address"], + protocol_version="1.6", + fee_addresses=self._seaport_1_6_config["fee_addresses"], ) - ) + if self._seaport_1_5_config: + orders += parse_opensea_transaction_order_fulfilled_event( + transaction, + self._seaport_1_5_config["contract_address"], + protocol_version="1.5", + fee_addresses=self._seaport_1_5_config.get("fee_addresses"), + ) + self._collect_domains(self.transfer(orders)) def get_filter(self): topic_filter_list = [] - topic_filter_list.append( - TopicSpecification( - addresses=[self._seaport_contract_address], - topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), + if self._seaport_1_6_config: + topic_filter_list.append( + TopicSpecification( + addresses=[self._seaport_1_6_config["contract_address"]], + topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), + ) + ) + if self._seaport_1_5_config: + topic_filter_list.append( + TopicSpecification( + addresses=[self._seaport_1_5_config["contract_address"]], + topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), + ) ) - ) return TransactionFilterByLogs(topic_filter_list) diff --git a/indexer/modules/custom/opensea/parser/opensea_contract_parser.py b/indexer/modules/custom/opensea/parser/opensea_contract_parser.py index 3cb54e539..53a5de243 100644 --- a/indexer/modules/custom/opensea/parser/opensea_contract_parser.py +++ b/indexer/modules/custom/opensea/parser/opensea_contract_parser.py @@ -34,10 +34,13 @@ class OpenseaLog: block_number: int transaction_hash: str log_index: int + protocol_version: str = "1.6" + + fee_addresses: List[str] = None def parse_opensea_transaction_order_fulfilled_event( - transaction: Transaction, contract_address: str + transaction: Transaction, contract_address: str, protocol_version: str = 1.6, fee_addresses: List[str] = [] ) -> List[OpenseaLog]: results = [] logs = transaction.receipt.logs @@ -61,6 +64,8 @@ def parse_opensea_transaction_order_fulfilled_event( block_number=transaction.block_number, transaction_hash=transaction.hash, log_index=log.log_index, + protocol_version=protocol_version, + fee_addresses=fee_addresses, ) ) diff --git a/indexer/specification/specification.py b/indexer/specification/specification.py index a4b128ddc..67377078e 100644 --- a/indexer/specification/specification.py +++ b/indexer/specification/specification.py @@ -115,7 +115,7 @@ class TransactionFilterByLogs: def __init__(self, topics_filters: List[TopicSpecification]): self.specifications = topics_filters self.eth_log_filters_params = { - "topics": [[topic for spec in self.specifications for topic in spec.topics]], + "topics": [list(set([topic for spec in self.specifications for topic in spec.topics]))], } filter_addresses = [to_checksum_address(address) for spec in self.specifications for address in spec.addresses] if len(filter_addresses) > 0: diff --git a/indexer/utils/json_rpc_requests.py b/indexer/utils/json_rpc_requests.py index fb79caa0e..2ae97906b 100644 --- a/indexer/utils/json_rpc_requests.py +++ b/indexer/utils/json_rpc_requests.py @@ -1,3 +1,6 @@ +from indexer.utils.utils import format_block_id + + def generate_get_block_by_number_json_rpc(block_numbers, include_transactions): for idx, block_number in enumerate(block_numbers): yield generate_json_rpc( @@ -50,7 +53,7 @@ def generate_eth_call_json_rpc(params): method="eth_call", params=[ {"to": param["param_to"], "data": param["param_data"]}, - param["param_number"], + format_block_id(param["param_number"]), ], request_id=param["request_id"], ) diff --git a/indexer/utils/multicall_hemera/call.py b/indexer/utils/multicall_hemera/call.py index afa05add4..3a9c190da 100644 --- a/indexer/utils/multicall_hemera/call.py +++ b/indexer/utils/multicall_hemera/call.py @@ -7,6 +7,7 @@ from eth_utils import to_checksum_address from indexer.utils.multicall_hemera.signature import Signature, _get_signature +from indexer.utils.utils import format_block_id logger = logging.getLogger(__name__) @@ -20,7 +21,7 @@ def __init__( target: AnyAddress, function: Union[str, Iterable[Union[str, Any]]], returns: Optional[Iterable[Tuple[str, Callable]]] = None, - block_id: Optional[int] = None, + block_id: Union[Optional[int], str] = None, gas_limit: Optional[int] = None, ) -> None: self.target = to_checksum_address(target) @@ -85,10 +86,12 @@ def to_rpc_param(self): } -def prep_args(target: str, signature: Signature, args: Optional[Any], block_id: Optional[int], gas_limit: int) -> List: +def prep_args( + target: str, signature: Signature, args: Optional[Any], block_id: Union[Optional[int], str], gas_limit: int +) -> List: call_data = signature.encode_data(args) - args = [{"to": target, "data": call_data}, hex(block_id)] + args = [{"to": target, "data": call_data}, format_block_id(block_id)] if gas_limit: args[0]["gas"] = gas_limit diff --git a/indexer/utils/multicall_hemera/multi_call.py b/indexer/utils/multicall_hemera/multi_call.py index af4af12d6..35af3b3ab 100644 --- a/indexer/utils/multicall_hemera/multi_call.py +++ b/indexer/utils/multicall_hemera/multi_call.py @@ -32,7 +32,7 @@ def __init__( self, calls: List[Call], chain_id: int = None, - block_id: Optional[int] = None, + block_id: Union[Optional[int], str] = None, require_success: bool = True, gas_limit: int = GAS_LIMIT, ) -> None: diff --git a/indexer/utils/multicall_hemera/util.py b/indexer/utils/multicall_hemera/util.py index 839754a3b..6efe17e49 100644 --- a/indexer/utils/multicall_hemera/util.py +++ b/indexer/utils/multicall_hemera/util.py @@ -8,6 +8,7 @@ import os import time from concurrent.futures import ThreadPoolExecutor, as_completed +from typing import Optional, Union import orjson from mpire import WorkerPool diff --git a/indexer/utils/token_fetcher.py b/indexer/utils/token_fetcher.py index 78cb33f49..93d33c9a7 100644 --- a/indexer/utils/token_fetcher.py +++ b/indexer/utils/token_fetcher.py @@ -36,7 +36,7 @@ rebatch_by_size, ) from indexer.utils.provider import get_provider_from_uri -from indexer.utils.utils import rpc_response_to_result, zip_rpc_response +from indexer.utils.utils import format_block_id, rpc_response_to_result, zip_rpc_response BALANCE_OF_ERC20 = "balanceOf(address)(uint256)" BALANCE_OF_ERC1155 = "balanceOf(address,uint256)(uint256)" @@ -92,7 +92,7 @@ def _prepare_token_ids_info_parameters(self, token_info_items): row[self.fixed_k] = self.build_key(row, self.token_ids_infos_k_fields) grouped_data[row["block_number"]].append(row) for block_id, items in grouped_data.items(): - if block_id < self.deploy_block_number or not self._is_multi_call: + if (isinstance(block_id, int) and block_id < self.deploy_block_number) or not self._is_multi_call: to_execute_batch_calls.extend(items) else: calls = [] @@ -229,7 +229,7 @@ def _token_ids_info_rpc_requests(self, token_info_items): "request_id": item["request_id"], "param_to": item["address"], "param_data": abi_selector_encode_and_decode_type(item), - "param_number": hex(item["block_number"]), + "param_number": format_block_id(item["block_number"]), } for item in token_info_items ] @@ -260,7 +260,7 @@ def _prepare_token_balance_parameters(self, tokens): wrapped_calls = [] for block_id, items in grouped_data.items(): - if block_id < self.deploy_block_number or not self._is_multi_call: + if (isinstance(block_id, int) and block_id < self.deploy_block_number) or not self._is_multi_call: to_execute_batch_calls.extend(items) else: calls = [] diff --git a/indexer/utils/utils.py b/indexer/utils/utils.py index 5f225b97f..078641368 100644 --- a/indexer/utils/utils.py +++ b/indexer/utils/utils.py @@ -3,7 +3,7 @@ import pkgutil import random import warnings -from typing import List, Union +from typing import List, Optional, Union from common.utils.exception_control import RetriableError, decode_response_error from indexer.domain import Domain @@ -158,3 +158,7 @@ def distinct_collections_by_group(collections: List[Domain], group_by: List[str] distinct[key] = item return [distinct[key] for key in distinct.keys()] + + +def format_block_id(block_id: Union[Optional[int], str]) -> str: + return hex(block_id) if block_id and isinstance(block_id, int) else block_id diff --git a/pyproject.toml b/pyproject.toml index 4343984d8..f0c75a537 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,6 +49,7 @@ dependencies = [ "eth_typing>=2.2.0,<5", "orjson==3.10.7", "mpire==2.10.2", + "PyYAML==6.0.2", ] [tool.setuptools.dynamic] From c8a3942321d1d57a8f5eb7c4176a943edf61d5e6 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:12:43 +0800 Subject: [PATCH 14/70] Add ens (#93) support ens --- .gitignore | 1 + api/app/af_ens/routes.py | 148 ++ api/app/api.py | 2 + api/app/main.py | 4 +- common/services/postgresql_service.py | 2 +- enumeration/entity_type.py | 17 +- indexer/jobs/export_blocks_job.py | 13 +- indexer/modules/custom/hemera_ens/__init__.py | 33 + ...00000000c2e074ec69a0dfb2997ba6c7d2e1e.json | 425 +++++ ...00000008794027c69c26d2a048dbec09de67c.json | 600 +++++++ ...b1c3c81545d370f3634392de611caabff8148.json | 156 ++ ...4dac3347ea47d208f3fd67402d039a3b99859.json | 1458 +++++++++++++++++ ...552e8f46fd509f3918a174fe62c34b42564ae.json | 219 +++ ...022710df5002339274aadee8d58218e9d6ab5.json | 318 ++++ ...159d592e2b063810a10ebf6dcbada94ed68b8.json | 684 ++++++++ ...b0ee14048e9dccd1d247744d114a4eb5e8e63.json | 1016 ++++++++++++ ...553366da8546fc250f225fe3d25d0c782303b.json | 600 +++++++ ...9b34eb43523447d3e1bcf26f009d814522687.json | 149 ++ ...af0b28c62c092c9727f1ee09c02ca627eb7f5.json | 562 +++++++ ...159265dd8dbb310642f98f50c066173c1259b.json | 204 +++ ...a76393544d5ecca80cd6ef2a560c6a395b7e3.json | 790 +++++++++ ...1ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json | 35 + ...6fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json | 866 ++++++++++ ...4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json | 143 ++ ...1887a8bf19b14fc0df6fd9b2acc9af147ea85.json | 756 +++++++++ ...0a6e47849629b7245dfa1ca21d94cd15878ef.json | 553 +++++++ ...7c2a24b5e86c38639fd1586917a8fef66a56d.json | 212 +++ ...9dd117aa5486605fc85e040ab00163a75c662.json | 212 +++ ...f0581ececcf8389c223170778cd9d029606f2.json | 22 + ...bfbcfccde554e11df213bf6d463ea00dd57cc.json | 237 +++ ...2c0a6dbd6108336bcbe4593a3d1ce05512069.json | 121 ++ ...143d946ba5d467bfc476491fdb235fef4d667.json | 513 ++++++ ...d20f524367d7e98ed849d37fc662402dca7fb.json | 658 ++++++++ ...122be93b0074270ebee7f6b7292c7deb45047.json | 73 + ...428617a523837d4adc81c67a296d42fd95e86.json | 143 ++ ...e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json | 339 ++++ ...28d626ec275e3fad363ff1393a41f581c5897.json | 229 +++ ...77e4f32e6746444970823d5506f98f5a04201.json | 216 +++ ...040c9ecaae172a89bd561c5f73e1c48d28cd9.json | 465 ++++++ ...374d0fe3d8341155663fae31b7beae0ae233a.json | 186 +++ ...35677a60884abbcf72295e88d47764beda282.json | 198 +++ ...659651d137a18b79925449722855aa327231d.json | 519 ++++++ ...16b13d2b3a9abae7acd5d6c2bbdbe25686401.json | 1458 +++++++++++++++++ ...029db2585553978190db5e85ec724aa4df23f.json | 226 +++ ...f96c344f63131acadd0ea35170e7892d3dfba.json | 845 ++++++++++ ...d8aaf34cb91087d1598e0a15b582f57f217d9.json | 598 +++++++ ...d5cad05e10572efceb849f6ff0c68f9700455.json | 434 +++++ ...83bd0c50e7a72b55a39fe0dabf5e3a330d749.json | 465 ++++++ ...9cc7abb2c4183683ab71653c4cdc9b02d44b7.json | 745 +++++++++ ...52725f6122a92551a5fa9a6b6bf10eb0be035.json | 114 ++ ...8ca4e83416b7e0443ff430cc245646434b647.json | 359 ++++ indexer/modules/custom/hemera_ens/ens_abi.py | 19 + indexer/modules/custom/hemera_ens/ens_conf.py | 54 + .../modules/custom/hemera_ens/ens_domain.py | 84 + .../modules/custom/hemera_ens/ens_handler.py | 298 ++++ indexer/modules/custom/hemera_ens/ens_hash.py | 39 + .../custom/hemera_ens/export_ens_job.py | 113 ++ .../modules/custom/hemera_ens/extractors.py | 345 ++++ .../custom/hemera_ens/models/__init__.py | 6 + .../models/af_ens_address_current.py | 27 + .../custom/hemera_ens/models/af_ens_event.py | 64 + .../hemera_ens/models/af_ens_node_current.py | 99 ++ indexer/modules/custom/hemera_ens/util.py | 19 + indexer/tests/ens/test_namehash.py | 29 + migrations/versions/20240831_add_ens.py | 175 ++ 65 files changed, 20708 insertions(+), 4 deletions(-) create mode 100644 api/app/af_ens/routes.py create mode 100644 indexer/modules/custom/hemera_ens/__init__.py create mode 100644 indexer/modules/custom/hemera_ens/abi/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x00000000008794027c69c26d2a048dbec09de67c.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x084b1c3c81545d370f3634392de611caabff8148.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x0904dac3347ea47d208f3fd67402d039a3b99859.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x1d6552e8f46fd509f3918a174fe62c34b42564ae.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x1da022710df5002339274aadee8d58218e9d6ab5.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x226159d592e2b063810a10ebf6dcbada94ed68b8.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x231b0ee14048e9dccd1d247744d114a4eb5e8e63.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x253553366da8546fc250f225fe3d25d0c782303b.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x27c9b34eb43523447d3e1bcf26f009d814522687.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x314159265dd8dbb310642f98f50c066173c1259b.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x323a76393544d5ecca80cd6ef2a560c6a395b7e3.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x6090a6e47849629b7245dfa1ca21d94cd15878ef.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x60c7c2a24b5e86c38639fd1586917a8fef66a56d.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x6109dd117aa5486605fc85e040ab00163a75c662.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x690f0581ececcf8389c223170778cd9d029606f2.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x705bfbcfccde554e11df213bf6d463ea00dd57cc.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x9062c0a6dbd6108336bcbe4593a3d1ce05512069.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x911143d946ba5d467bfc476491fdb235fef4d667.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0x9b6d20f524367d7e98ed849d37fc662402dca7fb.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xa2c122be93b0074270ebee7f6b7292c7deb45047.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xa2f428617a523837d4adc81c67a296d42fd95e86.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xab528d626ec275e3fad363ff1393a41f581c5897.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xb1377e4f32e6746444970823d5506f98f5a04201.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xb6e040c9ecaae172a89bd561c5f73e1c48d28cd9.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xb9d374d0fe3d8341155663fae31b7beae0ae233a.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xc1735677a60884abbcf72295e88d47764beda282.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xc32659651d137a18b79925449722855aa327231d.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xd7a029db2585553978190db5e85ec724aa4df23f.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xdaaf96c344f63131acadd0ea35170e7892d3dfba.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xe65d8aaf34cb91087d1598e0a15b582f57f217d9.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xf0ad5cad05e10572efceb849f6ff0c68f9700455.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xff252725f6122a92551a5fa9a6b6bf10eb0be035.json create mode 100644 indexer/modules/custom/hemera_ens/abi/0xffc8ca4e83416b7e0443ff430cc245646434b647.json create mode 100644 indexer/modules/custom/hemera_ens/ens_abi.py create mode 100644 indexer/modules/custom/hemera_ens/ens_conf.py create mode 100644 indexer/modules/custom/hemera_ens/ens_domain.py create mode 100644 indexer/modules/custom/hemera_ens/ens_handler.py create mode 100644 indexer/modules/custom/hemera_ens/ens_hash.py create mode 100644 indexer/modules/custom/hemera_ens/export_ens_job.py create mode 100644 indexer/modules/custom/hemera_ens/extractors.py create mode 100644 indexer/modules/custom/hemera_ens/models/__init__.py create mode 100644 indexer/modules/custom/hemera_ens/models/af_ens_address_current.py create mode 100644 indexer/modules/custom/hemera_ens/models/af_ens_event.py create mode 100644 indexer/modules/custom/hemera_ens/models/af_ens_node_current.py create mode 100644 indexer/modules/custom/hemera_ens/util.py create mode 100644 indexer/tests/ens/test_namehash.py create mode 100644 migrations/versions/20240831_add_ens.py diff --git a/.gitignore b/.gitignore index 04faa0157..8ecfa07cb 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,4 @@ docker-compose/postgres/ resource/hemera.ini sync_record alembic.ini +!indexer/modules/custom/hemera_ens/abi/*.json diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py new file mode 100644 index 000000000..edd6d95ab --- /dev/null +++ b/api/app/af_ens/routes.py @@ -0,0 +1,148 @@ +import decimal +from datetime import date, datetime + +from flask_restx import Resource +from flask_restx.namespace import Namespace +from web3 import Web3 + +from common.models import db +from common.models.current_token_balances import CurrentTokenBalances +from common.models.erc721_token_id_changes import ERC721TokenIdChanges +from common.utils.config import get_config +from indexer.modules.custom.hemera_ens.models.af_ens_address_current import ENSAddress +from indexer.modules.custom.hemera_ens.models.af_ens_event import ENSMiddle +from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ENSRecord + +app_config = get_config() +from sqlalchemy import and_, or_ + +w3 = Web3(Web3.HTTPProvider("https://ethereum-rpc.publicnode.com")) +af_ens_namespace = Namespace("User Operation Namespace", path="/", description="ENS feature") + + +@af_ens_namespace.route("/v1/aci/
/ens/current") +class ExplorerUserOperationDetails(Resource): + def get(self, address): + res = { + "primary_name": None, + "be_resolved_by_ens": [], + "ens_holdings": [], + "first_register_time": None, + "first_set_primary_name": None, + } + if address: + address = bytes.fromhex(address[2:]) + else: + return res + + dn = datetime.now() + # current_address holds 721 & 1155 tokens + all_721_owns = db.session.query(ERC721TokenIdChanges).filter(ERC721TokenIdChanges.token_owner == address).all() + all_721_ids = [r.token_id for r in all_721_owns] + all_owned = ( + db.session.query(ENSRecord).filter(and_(ENSRecord.token_id.in_(all_721_ids), ENSRecord.expires >= dn)).all() + ) + res["ens_holdings"] = [r.name for r in all_owned if r.name and r.name.endswith(".eth")] + all_1155_owns = db.session.query(CurrentTokenBalances).filter(CurrentTokenBalances.address == address).all() + all_1155_ids = [r.token_id for r in all_1155_owns] + all_owned_1155_ens = ( + db.session.query(ENSRecord) + .filter(and_(ENSRecord.expires >= dn, ENSRecord.w_token_id.in_(all_1155_ids))) + .all() + ) + res["ens_holdings"].extend([r.name for r in all_owned_1155_ens if r.name and r.name.endswith(".eth")]) + primary_address_row = db.session.query(ENSAddress).filter(ENSAddress.address == address).first() + if primary_address_row: + primary_record = db.session.query(ENSRecord).filter(ENSRecord.name == primary_address_row.name).first() + if primary_record: + res["primary_name"] = primary_record.name + else: + res["primary_name"] = w3.ens.name(w3.to_checksum_address(w3.to_hex(address))) + + be_resolved_ens = ( + db.session.query(ENSRecord).filter(and_(ENSRecord.address == address, ENSRecord.expires >= dn)).all() + ) + res["be_resolved_by_ens"] = [r.name for r in be_resolved_ens if r.name and r.name.endswith(".eth")] + + first_register = ( + db.session.query(ENSMiddle) + .filter(and_(ENSMiddle.from_address == address, ENSMiddle.event_name == "NameRegistered")) + .first() + ) + if first_register: + res["first_register_time"] = datetime_to_string(first_register.block_timestamp) + first_set_name = ( + db.session.query(ENSMiddle) + .filter(or_(ENSMiddle.method == "setName", ENSMiddle.event_name == "NameChanged")) + .first() + ) + if first_set_name: + res["first_set_primary_name"] = datetime_to_string(first_set_name.block_timestamp) + + return res + + +@af_ens_namespace.route("/v1/aci/
/ens/detail") +class ExplorerUserOperationDetails(Resource): + def get(self, address): + lis = [] + if address: + address = bytes.fromhex(address[2:]) + else: + return lis + + all_records_rows = ( + db.session.query(ENSMiddle) + .filter(ENSMiddle.from_address == address) + .order_by(ENSMiddle.block_number, ENSMiddle.transaction_index, ENSMiddle.log_index.desc()) + .all() + ) + node_name_map = {} + for r in all_records_rows: + if r.name: + if r.name.endswith(".eth"): + node_name_map[r.node] = r.name + else: + node_name_map[r.node] = r.name + ".eth" + for r in all_records_rows: + lis.append( + { + "method": r.method, + "event": r.event_name, + "block_number": r.block_number, + "block_timestamp": datetime_to_string(r.block_timestamp), + "transaction_index": r.transaction_index, + "log_index": r.log_index, + "transaction_hash": "0x" + r.transaction_hash.hex(), + "node": "0x" + r.node.hex(), + "name": node_name_map.get(r.node), + } + ) + + return lis + + +def datetime_to_string(dt, format="%Y-%m-%d %H:%M:%S"): + if isinstance(dt, datetime): + return dt.strftime(format) + elif isinstance(dt, date): + return dt.strftime(format) + elif dt is None: + return None + else: + raise ValueError(f"Unsupported type for dt: {type(dt)}. Expected datetime or date.") + + +def model_to_dict(instance): + res = {} + for c in instance.__table__.columns: + v = getattr(instance, c.name) + if isinstance(v, datetime): + res[c.name] = v.isoformat() + elif isinstance(v, bytes): + res[c.name] = "0x" + v.hex() + elif isinstance(v, decimal.Decimal): + res[c.name] = str(v) + else: + res[c.name] = v + return res diff --git a/api/app/api.py b/api/app/api.py index 94e6a86d8..becaeaca5 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -3,6 +3,7 @@ from flask_restx import Api +from api.app.af_ens.routes import af_ens_namespace from api.app.contract.routes import contract_namespace from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace @@ -17,3 +18,4 @@ api.add_namespace(opensea_namespace) api.add_namespace(contract_namespace) # api.add_namespace(l2_explorer_namespace) +api.add_namespace(af_ens_namespace) diff --git a/api/app/main.py b/api/app/main.py index cb8f0d356..9f25fc29c 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -1,7 +1,9 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- - +import base64 +import json import logging +from datetime import datetime import flask from flask import Flask, request diff --git a/common/services/postgresql_service.py b/common/services/postgresql_service.py index b1db34f31..7dc2daed9 100644 --- a/common/services/postgresql_service.py +++ b/common/services/postgresql_service.py @@ -28,7 +28,7 @@ def __new__(cls, *args, **kwargs): cls.instance = super().__new__(cls) return cls.instance - def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=False): + def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=True): self.db_version = db_version self.engine = create_engine( jdbc_url, diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index 2c2ea64c5..ce45fc4d2 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -22,6 +22,13 @@ AllFeatureValueRecordUniswapV3Token, ) from indexer.modules.custom.blue_chip.domain.feature_blue_chip import BlueChipHolder +from indexer.modules.custom.hemera_ens.ens_domain import ( + ENSAddressChangeD, + ENSAddressD, + ENSMiddleD, + ENSNameRenewD, + ENSRegisterD, +) from indexer.modules.custom.opensea.domain.address_opensea_transactions import AddressOpenseaTransaction from indexer.modules.custom.opensea.domain.opensea_order import OpenseaOrder from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import UniswapV3Pool, UniswapV3Token @@ -43,8 +50,9 @@ class EntityType(IntFlag): EXPLORER = EXPLORER_BASE | EXPLORER_TOKEN | EXPLORER_TRACE ADDRESS_INDEX = 1 << 7 - OPEN_SEA = 1 << 8 + ENS = 1 << 9 + @staticmethod def combine_all_entity_types(): @@ -138,6 +146,13 @@ def generate_output_types(entity_types): yield AllFeatureValueRecordBlueChipHolders yield BlueChipHolder + if entity_types & EntityType.ENS: + yield ENSMiddleD + yield ENSRegisterD + yield ENSNameRenewD + yield ENSAddressChangeD + yield ENSAddressD + if entity_types & EntityType.OPEN_SEA: yield AddressOpenseaTransaction yield OpenseaOrder diff --git a/indexer/jobs/export_blocks_job.py b/indexer/jobs/export_blocks_job.py index aae05f27c..3d50b511a 100644 --- a/indexer/jobs/export_blocks_job.py +++ b/indexer/jobs/export_blocks_job.py @@ -1,3 +1,4 @@ +import json import logging import orjson @@ -22,6 +23,16 @@ logger = logging.getLogger(__name__) +def flatten(lst): + result = [] + for item in lst: + if isinstance(item, list): + result.extend(flatten(item)) + else: + result.append(item) + return result + + # Exports blocks and block number <-> timestamp mapping class ExportBlocksJob(BaseExportJob): dependency_types = [] @@ -37,7 +48,7 @@ def __init__(self, **kwargs): job_name=self.__class__.__name__, ) self._is_batch = kwargs["batch_size"] > 1 - self._filters = kwargs.get("filters", []) + self._filters = flatten(kwargs.get("filters", [])) self._is_filter = kwargs.get("is_filter", False) self._specification = AlwaysFalseSpecification() if self._is_filter else AlwaysTrueSpecification() self._reorg_jobs = kwargs.get("reorg_jobs", []) diff --git a/indexer/modules/custom/hemera_ens/__init__.py b/indexer/modules/custom/hemera_ens/__init__.py new file mode 100644 index 000000000..cb718cbcf --- /dev/null +++ b/indexer/modules/custom/hemera_ens/__init__.py @@ -0,0 +1,33 @@ +import codecs + +from eth_abi import encoding +from eth_abi.decoding import StringDecoder +from eth_abi.registry import BaseEquals, registry + + +class CompatibleStringDecoder(StringDecoder): + + @staticmethod + def decoder_fn(data, handle_string_errors="strict"): + try: + return data.decode("utf-8", errors=handle_string_errors) + except UnicodeDecodeError: + try: + return data.decode("latin-1", errors=handle_string_errors) + except UnicodeDecodeError: + return codecs.decode(data, "hex") + + +lifo_registry = registry.copy() +lifo_registry.unregister("string") + +lifo_registry.register( + BaseEquals("string"), + encoding.TextStringEncoder, + CompatibleStringDecoder, + label="string", +) + + +from .ens_conf import CONTRACT_NAME_MAP +from .ens_handler import EnsConfLoader, EnsHandler diff --git a/indexer/modules/custom/hemera_ens/abi/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e.json b/indexer/modules/custom/hemera_ens/abi/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e.json new file mode 100644 index 000000000..7113e5985 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e.json @@ -0,0 +1,425 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_old", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "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": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "NewOwner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "NewResolver", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "NewTTL", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "old", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "recordExists", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "resolver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setRecord", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "setSubnodeOwner", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setSubnodeRecord", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setTTL", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "ttl", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x00000000008794027c69c26d2a048dbec09de67c.json b/indexer/modules/custom/hemera_ens/abi/0x00000000008794027c69c26d2a048dbec09de67c.json new file mode 100644 index 000000000..b4b8beac0 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x00000000008794027c69c26d2a048dbec09de67c.json @@ -0,0 +1,600 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract BaseRegistrarImplementation", + "name": "_base", + "type": "address" + }, + { + "internalType": "contract IPriceOracle", + "name": "_prices", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxCommitmentAge", + "type": "uint256" + }, + { + "internalType": "contract ReverseRegistrar", + "name": "_reverseRegistrar", + "type": "address" + }, + { + "internalType": "contract INameWrapper", + "name": "_nameWrapper", + "type": "address" + }, + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "CommitmentTooNew", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "CommitmentTooOld", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "DurationTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientValue", + "type": "error" + }, + { + "inputs": [], + "name": "MaxCommitmentAgeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "MaxCommitmentAgeTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameNotAvailable", + "type": "error" + }, + { + "inputs": [], + "name": "ResolverRequiredWhenDataSupplied", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "UnexpiredCommitmentExists", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "baseCost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "premium", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MIN_REGISTRATION_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "available", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "commit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "bool", + "name": "reverseRecord", + "type": "bool" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "makeCommitment", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "maxCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nameWrapper", + "outputs": [ + { + "internalType": "contract INameWrapper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "prices", + "outputs": [ + { + "internalType": "contract IPriceOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "recoverFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "bool", + "name": "reverseRecord", + "type": "bool" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "rentPrice", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "base", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "premium", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reverseRegistrar", + "outputs": [ + { + "internalType": "contract ReverseRegistrar", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "valid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x253553366da8546fc250f225fe3d25d0c782303b" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x084b1c3c81545d370f3634392de611caabff8148.json b/indexer/modules/custom/hemera_ens/abi/0x084b1c3c81545d370f3634392de611caabff8148.json new file mode 100644 index 000000000..97f54f899 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x084b1c3c81545d370f3634392de611caabff8148.json @@ -0,0 +1,156 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "ensAddr", + "type": "address" + }, + { + "internalType": "contract Resolver", + "name": "resolverAddr", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "ADDR_REVERSE_NODE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "claim", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "claimWithResolver", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "defaultResolver", + "outputs": [ + { + "internalType": "contract Resolver", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "node", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x084b1c3c81545d370f3634392de611caabff8148" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x0904dac3347ea47d208f3fd67402d039a3b99859.json b/indexer/modules/custom/hemera_ens/abi/0x0904dac3347ea47d208f3fd67402d039a3b99859.json new file mode 100644 index 000000000..37e7747ae --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x0904dac3347ea47d208f3fd67402d039a3b99859.json @@ -0,0 +1,1458 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + }, + { + "internalType": "contract IBaseRegistrar", + "name": "_registrar", + "type": "address" + }, + { + "internalType": "contract IMetadataService", + "name": "_metadataService", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CannotUpgrade", + "type": "error" + }, + { + "inputs": [], + "name": "IncompatibleParent", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectTargetOwner", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "labelHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "expectedLabelhash", + "type": "bytes32" + } + ], + "name": "LabelMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "LabelTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "LabelTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "NameIsNotWrapped", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "OperationProhibited", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Unauthorised", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "ControllerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "ExpiryExtended", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + } + ], + "name": "FusesSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "NameUnwrapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "NameWrapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "_tokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fuseMask", + "type": "uint32" + } + ], + "name": "allFusesBurned", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "canExtendSubnames", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "canModifyName", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "extendExpiry", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getData", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + } + ], + "name": "isWrapped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "isWrapped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "metadataService", + "outputs": [ + { + "internalType": "contract IMetadataService", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "names", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "recoverFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "registerAndWrapETH2LD", + "outputs": [ + { + "internalType": "uint256", + "name": "registrarExpiry", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registrar", + "outputs": [ + { + "internalType": "contract IBaseRegistrar", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [ + { + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setChildFuses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "setFuses", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IMetadataService", + "name": "_metadataService", + "type": "address" + } + ], + "name": "setMetadataService", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setRecord", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setSubnodeOwner", + "outputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setSubnodeRecord", + "outputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setTTL", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract INameWrapperUpgrade", + "name": "_upgradeAddress", + "type": "address" + } + ], + "name": "setUpgradeContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "unwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "registrant", + "type": "address" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "unwrapETH2LD", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeContract", + "outputs": [ + { + "internalType": "contract INameWrapperUpgrade", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "wrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "wrapETH2LD", + "outputs": [ + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x1d6552e8f46fd509f3918a174fe62c34b42564ae.json b/indexer/modules/custom/hemera_ens/abi/0x1d6552e8f46fd509f3918a174fe62c34b42564ae.json new file mode 100644 index 000000000..9c3ae7a45 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x1d6552e8f46fd509f3918a174fe62c34b42564ae.json @@ -0,0 +1,219 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract IETHRegistrarController", + "name": "_registrarController", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "commitmentList", + "type": "bytes32[]" + } + ], + "name": "bulkCommit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "name", + "type": "string[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "bulkMakeCommitment", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "commitmentList", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "bulkRegister", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "bulkRentPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commit", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "makeCommitmentWithConfig", + "outputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "registerWithConfig", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "registrarController", + "outputs": [ + { + "internalType": "contract IETHRegistrarController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x1d6552e8f46fd509f3918a174fe62c34b42564ae" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x1da022710df5002339274aadee8d58218e9d6ab5.json b/indexer/modules/custom/hemera_ens/abi/0x1da022710df5002339274aadee8d58218e9d6ab5.json new file mode 100644 index 000000000..e7690f169 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x1da022710df5002339274aadee8d58218e9d6ab5.json @@ -0,0 +1,318 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "contentTypes", + "type": "uint256" + } + ], + "name": "ABI", + "outputs": [ + { + "name": "contentType", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "x", + "type": "bytes32" + }, + { + "name": "y", + "type": "bytes32" + } + ], + "name": "setPubkey", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "content", + "outputs": [ + { + "name": "ret", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "name": "ret", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "contentType", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "setABI", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "name": "ret", + "type": "string" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "hash", + "type": "bytes32" + } + ], + "name": "setContent", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "pubkey", + "outputs": [ + { + "name": "x", + "type": "bytes32" + }, + { + "name": "y", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "addr", + "type": "address" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "ensAddr", + "type": "address" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "a", + "type": "address" + } + ], + "name": "AddrChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "hash", + "type": "bytes32" + } + ], + "name": "ContentChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "name", + "type": "string" + } + ], + "name": "NameChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "name": "contentType", + "type": "uint256" + } + ], + "name": "ABIChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "x", + "type": "bytes32" + }, + { + "indexed": false, + "name": "y", + "type": "bytes32" + } + ], + "name": "PubkeyChanged", + "type": "event" + } + ], + "address": "0x1da022710dF5002339274AaDEe8D58218e9D6AB5" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x226159d592e2b063810a10ebf6dcbada94ed68b8.json b/indexer/modules/custom/hemera_ens/abi/0x226159d592e2b063810a10ebf6dcbada94ed68b8.json new file mode 100644 index 000000000..f03da2cb2 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x226159d592e2b063810a10ebf6dcbada94ed68b8.json @@ -0,0 +1,684 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "setText", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "interfaceImplementer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentTypes", + "type": "uint256" + } + ], + "name": "ABI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "setPubkey", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "setContenthash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "setAuthorisation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "text", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setABI", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "a", + "type": "bytes" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "contenthash", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "pubkey", + "outputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "setInterface", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorisations", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "AuthorisationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "indexedKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "TextChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "PubkeyChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "InterfaceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "ContenthashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "AddrChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "newAddress", + "type": "bytes" + } + ], + "name": "AddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + } + ], + "name": "ABIChanged", + "type": "event" + } + ], + "address": "0x226159d592e2b063810a10ebf6dcbada94ed68b8" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x231b0ee14048e9dccd1d247744d114a4eb5e8e63.json b/indexer/modules/custom/hemera_ens/abi/0x231b0ee14048e9dccd1d247744d114a4eb5e8e63.json new file mode 100644 index 000000000..327f924dc --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x231b0ee14048e9dccd1d247744d114a4eb5e8e63.json @@ -0,0 +1,1016 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + }, + { + "internalType": "contract INameWrapper", + "name": "wrapperAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "_trustedETHController", + "type": "address" + }, + { + "internalType": "address", + "name": "_trustedReverseRegistrar", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + } + ], + "name": "ABIChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "AddrChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "newAddress", + "type": "bytes" + } + ], + "name": "AddressChanged", + "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": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "Approved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "ContenthashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "record", + "type": "bytes" + } + ], + "name": "DNSRecordChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "DNSRecordDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "lastzonehash", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "zonehash", + "type": "bytes" + } + ], + "name": "DNSZonehashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "InterfaceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "PubkeyChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "string", + "name": "indexedKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "TextChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "newVersion", + "type": "uint64" + } + ], + "name": "VersionChanged", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentTypes", + "type": "uint256" + } + ], + "name": "ABI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "clearRecords", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "contenthash", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "dnsRecord", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + } + ], + "name": "hasDNSRecords", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "interfaceImplementer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "delegate", + "type": "address" + } + ], + "name": "isApprovedFor", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "nodehash", + "type": "bytes32" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicallWithNodeCheck", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "pubkey", + "outputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "recordVersions", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setABI", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "a", + "type": "bytes" + } + ], + "name": "setAddr", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "setAddr", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "setContenthash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setDNSRecords", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "setInterface", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "newName", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "setPubkey", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "setText", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "setZonehash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "text", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "zonehash", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x231b0ee14048e9dccd1d247744d114a4eb5e8e63" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x253553366da8546fc250f225fe3d25d0c782303b.json b/indexer/modules/custom/hemera_ens/abi/0x253553366da8546fc250f225fe3d25d0c782303b.json new file mode 100644 index 000000000..b4b8beac0 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x253553366da8546fc250f225fe3d25d0c782303b.json @@ -0,0 +1,600 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract BaseRegistrarImplementation", + "name": "_base", + "type": "address" + }, + { + "internalType": "contract IPriceOracle", + "name": "_prices", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxCommitmentAge", + "type": "uint256" + }, + { + "internalType": "contract ReverseRegistrar", + "name": "_reverseRegistrar", + "type": "address" + }, + { + "internalType": "contract INameWrapper", + "name": "_nameWrapper", + "type": "address" + }, + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "CommitmentTooNew", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "CommitmentTooOld", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "DurationTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientValue", + "type": "error" + }, + { + "inputs": [], + "name": "MaxCommitmentAgeTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "MaxCommitmentAgeTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameNotAvailable", + "type": "error" + }, + { + "inputs": [], + "name": "ResolverRequiredWhenDataSupplied", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "UnexpiredCommitmentExists", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "baseCost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "premium", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [], + "name": "MIN_REGISTRATION_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "available", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "commit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "bool", + "name": "reverseRecord", + "type": "bool" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "makeCommitment", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "maxCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nameWrapper", + "outputs": [ + { + "internalType": "contract INameWrapper", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "prices", + "outputs": [ + { + "internalType": "contract IPriceOracle", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "recoverFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + }, + { + "internalType": "bool", + "name": "reverseRecord", + "type": "bool" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "register", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "rentPrice", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "base", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "premium", + "type": "uint256" + } + ], + "internalType": "struct IPriceOracle.Price", + "name": "price", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "reverseRegistrar", + "outputs": [ + { + "internalType": "contract ReverseRegistrar", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "valid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x253553366da8546fc250f225fe3d25d0c782303b" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x27c9b34eb43523447d3e1bcf26f009d814522687.json b/indexer/modules/custom/hemera_ens/abi/0x27c9b34eb43523447d3e1bcf26f009d814522687.json new file mode 100644 index 000000000..6aaf1af2a --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x27c9b34eb43523447d3e1bcf26f009d814522687.json @@ -0,0 +1,149 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_logic", + "type": "address" + }, + { + "internalType": "address", + "name": "admin_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "admin", + "outputs": [ + { + "internalType": "address", + "name": "admin_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "changeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [ + { + "internalType": "address", + "name": "implementation_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "address": "0x27c9b34eb43523447d3e1bcf26f009d814522687" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5.json b/indexer/modules/custom/hemera_ens/abi/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5.json new file mode 100644 index 000000000..97eab218c --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5.json @@ -0,0 +1,562 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract BaseRegistrar", + "name": "_base", + "type": "address" + }, + { + "internalType": "contract PriceOracle", + "name": "_prices", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxCommitmentAge", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "NewPriceOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "MIN_REGISTRATION_DURATION", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "available", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "name": "commit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "makeCommitment", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "makeCommitmentWithConfig", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCommitmentAge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "register", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "registerWithConfig", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "rentPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_maxCommitmentAge", + "type": "uint256" + } + ], + "name": "setCommitmentAges", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "contract PriceOracle", + "name": "_prices", + "type": "address" + } + ], + "name": "setPriceOracle", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "valid", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x314159265dd8dbb310642f98f50c066173c1259b.json b/indexer/modules/custom/hemera_ens/abi/0x314159265dd8dbb310642f98f50c066173c1259b.json new file mode 100644 index 000000000..eade75249 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x314159265dd8dbb310642f98f50c066173c1259b.json @@ -0,0 +1,204 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "resolver", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "label", + "type": "bytes32" + }, + { + "name": "owner", + "type": "address" + } + ], + "name": "setSubnodeOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "ttl", + "type": "uint64" + } + ], + "name": "setTTL", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "name": "ttl", + "outputs": [ + { + "name": "", + "type": "uint64" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "node", + "type": "bytes32" + }, + { + "name": "owner", + "type": "address" + } + ], + "name": "setOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "owner", + "type": "address" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "name": "owner", + "type": "address" + } + ], + "name": "NewOwner", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "resolver", + "type": "address" + } + ], + "name": "NewResolver", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "name": "ttl", + "type": "uint64" + } + ], + "name": "NewTTL", + "type": "event" + } + ], + "address": "0x314159265dd8dbb310642f98f50c066173c1259b" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x323a76393544d5ecca80cd6ef2a560c6a395b7e3.json b/indexer/modules/custom/hemera_ens/abi/0x323a76393544d5ecca80cd6ef2a560c6a395b7e3.json new file mode 100644 index 000000000..759a520cc --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x323a76393544d5ecca80cd6ef2a560c6a395b7e3.json @@ -0,0 +1,790 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ERC20Votes", + "name": "_token", + "type": "address" + }, + { + "internalType": "contract TimelockController", + "name": "_timelock", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "proposer", + "type": "address" + }, + { + "indexed": false, + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "string[]", + "name": "signatures", + "type": "string[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "startBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "endBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "ProposalCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "eta", + "type": "uint256" + } + ], + "name": "ProposalQueued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldQuorumNumerator", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "QuorumNumeratorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oldTimelock", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newTimelock", + "type": "address" + } + ], + "name": "TimelockChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "voter", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "weight", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "VoteCast", + "type": "event" + }, + { + "inputs": [], + "name": "BALLOT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "COUNTING_MODE", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + } + ], + "name": "castVote", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "castVoteBySig", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "support", + "type": "uint8" + }, + { + "internalType": "string", + "name": "reason", + "type": "string" + } + ], + "name": "castVoteWithReason", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "execute", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "getVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasVoted", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "hashProposal", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalDeadline", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalEta", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalSnapshot", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "proposalVotes", + "outputs": [ + { + "internalType": "uint256", + "name": "againstVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "forVotes", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "abstainVotes", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "string", + "name": "description", + "type": "string" + } + ], + "name": "propose", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "calldatas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "descriptionHash", + "type": "bytes32" + } + ], + "name": "queue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "blockNumber", + "type": "uint256" + } + ], + "name": "quorum", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "quorumDenominator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "quorumNumerator", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "proposalId", + "type": "uint256" + } + ], + "name": "state", + "outputs": [ + { + "internalType": "enum IGovernor.ProposalState", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timelock", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ERC20Votes", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newQuorumNumerator", + "type": "uint256" + } + ], + "name": "updateQuorumNumerator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract TimelockController", + "name": "newTimelock", + "type": "address" + } + ], + "name": "updateTimelock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "votingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "address": "0x323a76393544d5ecca80cd6ef2a560c6a395b7e3" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json b/indexer/modules/custom/hemera_ens/abi/0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json new file mode 100644 index 000000000..8038dca51 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json @@ -0,0 +1,35 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "name": "getNames", + "outputs": [ + { + "internalType": "string[]", + "name": "r", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json b/indexer/modules/custom/hemera_ens/abi/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json new file mode 100644 index 000000000..892f2dc8c --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json @@ -0,0 +1,866 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + } + ], + "name": "ABIChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "AddrChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "newAddress", + "type": "bytes" + } + ], + "name": "AddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "AuthorisationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "ContenthashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "record", + "type": "bytes" + } + ], + "name": "DNSRecordChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "DNSRecordDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "DNSZoneCleared", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "InterfaceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "PubkeyChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "string", + "name": "indexedKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "TextChanged", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentTypes", + "type": "uint256" + } + ], + "name": "ABI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorisations", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "clearDNSZone", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "contenthash", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "dnsRecord", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + } + ], + "name": "hasDNSRecords", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "interfaceImplementer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "pubkey", + "outputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setABI", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "a", + "type": "bytes" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "setAuthorisation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "setContenthash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setDNSRecords", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "setInterface", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "setPubkey", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "setText", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "text", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json b/indexer/modules/custom/hemera_ens/abi/0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json new file mode 100644 index 000000000..8393e8de4 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json @@ -0,0 +1,143 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract DNSSEC", + "name": "_dnssec", + "type": "address" + }, + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "dnsname", + "type": "bytes" + } + ], + "name": "Claim", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "claim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract DNSSEC", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "input", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "proveAndClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + } + ], + "address": "0xa2f428617a523837d4adc81c67a296d42fd95e86" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85.json b/indexer/modules/custom/hemera_ens/abi/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85.json new file mode 100644 index 000000000..a77c7eb90 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85.json @@ -0,0 +1,756 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "_baseNode", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "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": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "ControllerAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "ControllerRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameMigrated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "GRACE_PERIOD", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "addController", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "available", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "baseNode", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "nameExpires", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "reclaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "register", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "registerOnly", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "removeController", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x6090a6e47849629b7245dfa1ca21d94cd15878ef.json b/indexer/modules/custom/hemera_ens/abi/0x6090a6e47849629b7245dfa1ca21d94cd15878ef.json new file mode 100644 index 000000000..6df9cb68e --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x6090a6e47849629b7245dfa1ca21d94cd15878ef.json @@ -0,0 +1,553 @@ +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "releaseDeed", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "getAllowedTime", + "outputs": [ + { + "name": "timestamp", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "unhashedName", + "type": "string" + } + ], + "name": "invalidateName", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "owner", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "salt", + "type": "bytes32" + } + ], + "name": "shaBid", + "outputs": [ + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "bidder", + "type": "address" + }, + { + "name": "seal", + "type": "bytes32" + } + ], + "name": "cancelBid", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "entries", + "outputs": [ + { + "name": "", + "type": "uint8" + }, + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_value", + "type": "uint256" + }, + { + "name": "_salt", + "type": "bytes32" + } + ], + "name": "unsealBid", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "transferRegistrars", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + }, + { + "name": "", + "type": "bytes32" + } + ], + "name": "sealedBids", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "state", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + }, + { + "name": "_timestamp", + "type": "uint256" + } + ], + "name": "isAllowed", + "outputs": [ + { + "name": "allowed", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "finalizeAuction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registryStarted", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "launchLength", + "outputs": [ + { + "name": "", + "type": "uint32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "name": "newBid", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "labels", + "type": "bytes32[]" + } + ], + "name": "eraseNode", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hashes", + "type": "bytes32[]" + } + ], + "name": "startAuctions", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "hash", + "type": "bytes32" + }, + { + "name": "deed", + "type": "address" + }, + { + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "acceptRegistrarTransfer", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_hash", + "type": "bytes32" + } + ], + "name": "startAuction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "rootNode", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "hashes", + "type": "bytes32[]" + }, + { + "name": "sealedBid", + "type": "bytes32" + } + ], + "name": "startAuctionsAndBid", + "outputs": [], + "payable": true, + "type": "function" + }, + { + "inputs": [ + { + "name": "_ens", + "type": "address" + }, + { + "name": "_rootNode", + "type": "bytes32" + }, + { + "name": "_startDate", + "type": "uint256" + } + ], + "payable": false, + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "AuctionStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "bidder", + "type": "address" + }, + { + "indexed": false, + "name": "deposit", + "type": "uint256" + } + ], + "name": "NewBid", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "status", + "type": "uint8" + } + ], + "name": "BidRevealed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "HashRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "HashReleased", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "hash", + "type": "bytes32" + }, + { + "indexed": true, + "name": "name", + "type": "string" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "name": "registrationDate", + "type": "uint256" + } + ], + "name": "HashInvalidated", + "type": "event" + } + ], + "address": "0x6090A6e47849629b7245Dfa1Ca21D94cd15878Ef" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x60c7c2a24b5e86c38639fd1586917a8fef66a56d.json b/indexer/modules/custom/hemera_ens/abi/0x60c7c2a24b5e86c38639fd1586917a8fef66a56d.json new file mode 100644 index 000000000..8051c5a8b --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x60c7c2a24b5e86c38639fd1586917a8fef66a56d.json @@ -0,0 +1,212 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract OldBaseRegistrarImplementation", + "name": "_old", + "type": "address" + }, + { + "internalType": "contract BaseRegistrarImplementation", + "name": "_new", + "type": "address" + }, + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "_oldSubdomainRegistrar", + "type": "address" + }, + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "_newSubdomainRegistrar", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "baseNode", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "legacyRegistrar", + "outputs": [ + { + "internalType": "contract Registrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + } + ], + "name": "migrateAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "labels", + "type": "bytes32[]" + } + ], + "name": "migrateAllLegacy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "migrateLegacy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newENS", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newRegistrar", + "outputs": [ + { + "internalType": "contract BaseRegistrarImplementation", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newSubdomainRegistrar", + "outputs": [ + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldENS", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldRegistrar", + "outputs": [ + { + "internalType": "contract OldBaseRegistrarImplementation", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldSubdomainRegistrar", + "outputs": [ + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x60c7c2a24b5e86c38639fd1586917a8fef66a56d" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x6109dd117aa5486605fc85e040ab00163a75c662.json b/indexer/modules/custom/hemera_ens/abi/0x6109dd117aa5486605fc85e040ab00163a75c662.json new file mode 100644 index 000000000..c9d924054 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x6109dd117aa5486605fc85e040ab00163a75c662.json @@ -0,0 +1,212 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract OldBaseRegistrarImplementation", + "name": "_old", + "type": "address" + }, + { + "internalType": "contract BaseRegistrarImplementation", + "name": "_new", + "type": "address" + }, + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "_oldSubdomainRegistrar", + "type": "address" + }, + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "_newSubdomainRegistrar", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "baseNode", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "legacyRegistrar", + "outputs": [ + { + "internalType": "contract Registrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "migrate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256[]", + "name": "tokenIds", + "type": "uint256[]" + } + ], + "name": "migrateAll", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32[]", + "name": "labels", + "type": "bytes32[]" + } + ], + "name": "migrateAllLegacy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "migrateLegacy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newENS", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newRegistrar", + "outputs": [ + { + "internalType": "contract BaseRegistrarImplementation", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "newSubdomainRegistrar", + "outputs": [ + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldENS", + "outputs": [ + { + "internalType": "contract OldENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldRegistrar", + "outputs": [ + { + "internalType": "contract OldBaseRegistrarImplementation", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oldSubdomainRegistrar", + "outputs": [ + { + "internalType": "contract AbstractSubdomainRegistrar", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x6109dd117aa5486605fc85e040ab00163a75c662" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x690f0581ececcf8389c223170778cd9d029606f2.json b/indexer/modules/custom/hemera_ens/abi/0x690f0581ececcf8389c223170778cd9d029606f2.json new file mode 100644 index 000000000..b5a86337a --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x690f0581ececcf8389c223170778cd9d029606f2.json @@ -0,0 +1,22 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_masterCopy", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "payable": true, + "stateMutability": "payable", + "type": "fallback" + } + ], + "address": "0x690f0581ececcf8389c223170778cd9d029606f2" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x705bfbcfccde554e11df213bf6d463ea00dd57cc.json b/indexer/modules/custom/hemera_ens/abi/0x705bfbcfccde554e11df213bf6d463ea00dd57cc.json new file mode 100644 index 000000000..70190b4de --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x705bfbcfccde554e11df213bf6d463ea00dd57cc.json @@ -0,0 +1,237 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract IETHRegistrarController", + "name": "_registrarController", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "commitmentList", + "type": "bytes32[]" + } + ], + "name": "bulkCommit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "name", + "type": "string[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "bulkMakeCommitment", + "outputs": [ + { + "internalType": "bytes32[]", + "name": "commitmentList", + "type": "bytes32[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + } + ], + "name": "bulkRegister", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "bulkRenew", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "bulkRentPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "commit", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "makeCommitmentWithConfig", + "outputs": [ + { + "internalType": "bytes32", + "name": "commitment", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "secret", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "registerWithConfig", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "registrarController", + "outputs": [ + { + "internalType": "contract IETHRegistrarController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0x705bfbcfccde554e11df213bf6d463ea00dd57cc" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x9062c0a6dbd6108336bcbe4593a3d1ce05512069.json b/indexer/modules/custom/hemera_ens/abi/0x9062c0a6dbd6108336bcbe4593a3d1ce05512069.json new file mode 100644 index 000000000..7c8664ab0 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x9062c0a6dbd6108336bcbe4593a3d1ce05512069.json @@ -0,0 +1,121 @@ +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "resolver", + "type": "address" + } + ], + "name": "claimWithResolver", + "outputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "claim", + "outputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "defaultResolver", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "addr", + "type": "address" + } + ], + "name": "node", + "outputs": [ + { + "name": "ret", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [ + { + "name": "node", + "type": "bytes32" + } + ], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "ensAddr", + "type": "address" + }, + { + "name": "resolverAddr", + "type": "address" + } + ], + "payable": false, + "type": "constructor" + } + ], + "address": "0x9062c0a6dbd6108336bcbe4593a3d1ce05512069" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x911143d946ba5d467bfc476491fdb235fef4d667.json b/indexer/modules/custom/hemera_ens/abi/0x911143d946ba5d467bfc476491fdb235fef4d667.json new file mode 100644 index 000000000..4cb2e54aa --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x911143d946ba5d467bfc476491fdb235fef4d667.json @@ -0,0 +1,513 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "owners", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "revokeConfirmation", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + }, + { + "name": "", + "type": "address" + } + ], + "name": "confirmations", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "pending", + "type": "bool" + }, + { + "name": "executed", + "type": "bool" + } + ], + "name": "getTransactionCount", + "outputs": [ + { + "name": "count", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "isConfirmed", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "getConfirmationCount", + "outputs": [ + { + "name": "count", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "transactions", + "outputs": [ + { + "name": "destination", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + }, + { + "name": "executed", + "type": "bool" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getOwners", + "outputs": [ + { + "name": "", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "from", + "type": "uint256" + }, + { + "name": "to", + "type": "uint256" + }, + { + "name": "pending", + "type": "bool" + }, + { + "name": "executed", + "type": "bool" + } + ], + "name": "getTransactionIds", + "outputs": [ + { + "name": "_transactionIds", + "type": "uint256[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "getConfirmations", + "outputs": [ + { + "name": "_confirmations", + "type": "address[]" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "transactionCount", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_required", + "type": "uint256" + } + ], + "name": "changeRequirement", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "confirmTransaction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "destination", + "type": "address" + }, + { + "name": "value", + "type": "uint256" + }, + { + "name": "data", + "type": "bytes" + } + ], + "name": "submitTransaction", + "outputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MAX_OWNER_COUNT", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "required", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + }, + { + "name": "newOwner", + "type": "address" + } + ], + "name": "replaceOwner", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "transactionId", + "type": "uint256" + } + ], + "name": "executeTransaction", + "outputs": [], + "payable": false, + "type": "function" + }, + { + "inputs": [ + { + "name": "_owners", + "type": "address[]" + }, + { + "name": "_required", + "type": "uint256" + } + ], + "payable": false, + "type": "constructor" + }, + { + "payable": true, + "type": "fallback" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Confirmation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Revocation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Submission", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "Execution", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "transactionId", + "type": "uint256" + } + ], + "name": "ExecutionFailure", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "name": "value", + "type": "uint256" + } + ], + "name": "Deposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerAddition", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "owner", + "type": "address" + } + ], + "name": "OwnerRemoval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "required", + "type": "uint256" + } + ], + "name": "RequirementChange", + "type": "event" + } + ], + "address": "0x911143d946ba5d467bfc476491fdb235fef4d667" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0x9b6d20f524367d7e98ed849d37fc662402dca7fb.json b/indexer/modules/custom/hemera_ens/abi/0x9b6d20f524367d7e98ed849d37fc662402dca7fb.json new file mode 100644 index 000000000..ea63cdceb --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0x9b6d20f524367d7e98ed849d37fc662402dca7fb.json @@ -0,0 +1,658 @@ +{ + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "registrar", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "token_id", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "RegisterSubdomain", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "string", + "name": "indexedKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "TextChanged", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addresses", + "type": "address[]" + } + ], + "name": "addAddressWhitelist", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "nodeID", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "address_whitelist", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "domainHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "domainLabel", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "domainMap", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addy", + "type": "address" + } + ], + "name": "getAllCatsWithDomains", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addy", + "type": "address" + } + ], + "name": "getClaimableIdsForAddress", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "token_ids", + "type": "uint256[]" + } + ], + "name": "getTokensDomains", + "outputs": [ + { + "internalType": "string[]", + "name": "", + "type": "string[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "hashToDomainMap", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "hashToIdMap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nextRegisterTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nft", + "outputs": [ + { + "internalType": "contract IERC721Enumerable", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nftImageBaseUri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "publicClaimOpen", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addy", + "type": "address" + } + ], + "name": "resetAddressForClaim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token_id", + "type": "uint256" + } + ], + "name": "resetHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reset_period", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "uint256", + "name": "token_id", + "type": "uint256" + } + ], + "name": "setDomain", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "setDomainLabel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addy", + "type": "address" + } + ], + "name": "setEnsAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addy", + "type": "address" + } + ], + "name": "setNftAddress", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "_uri", + "type": "string" + } + ], + "name": "setNftImageBaseUri", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "setText", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "text", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "name": "texts", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "toggleNftImageLink", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "togglePublicClaim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenHashmap", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "updateResetPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "useEIP155", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "withdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "withdrawTokens", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0x9b6d20f524367d7e98ed849d37fc662402dca7fb" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xa2c122be93b0074270ebee7f6b7292c7deb45047.json b/indexer/modules/custom/hemera_ens/abi/0xa2c122be93b0074270ebee7f6b7292c7deb45047.json new file mode 100644 index 000000000..4b6dbb83f --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xa2c122be93b0074270ebee7f6b7292c7deb45047.json @@ -0,0 +1,73 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "ensAddr", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "_name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xa2c122be93b0074270ebee7f6b7292c7deb45047" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xa2f428617a523837d4adc81c67a296d42fd95e86.json b/indexer/modules/custom/hemera_ens/abi/0xa2f428617a523837d4adc81c67a296d42fd95e86.json new file mode 100644 index 000000000..8393e8de4 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xa2f428617a523837d4adc81c67a296d42fd95e86.json @@ -0,0 +1,143 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract DNSSEC", + "name": "_dnssec", + "type": "address" + }, + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "dnsname", + "type": "bytes" + } + ], + "name": "Claim", + "type": "event" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "claim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracle", + "outputs": [ + { + "internalType": "contract DNSSEC", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "input", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "proof", + "type": "bytes" + } + ], + "name": "proveAndClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + } + ], + "address": "0xa2f428617a523837d4adc81c67a296d42fd95e86" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json b/indexer/modules/custom/hemera_ens/abi/0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json new file mode 100644 index 000000000..e2afe366b --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json @@ -0,0 +1,339 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "ensAddr", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "ControllerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract NameResolver", + "name": "resolver", + "type": "address" + } + ], + "name": "DefaultResolverChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "ReverseClaimed", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "claim", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "claimForAddr", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "claimWithResolver", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultResolver", + "outputs": [ + { + "internalType": "contract NameResolver", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "node", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setDefaultResolver", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setNameForAddr", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xab528d626ec275e3fad363ff1393a41f581c5897.json b/indexer/modules/custom/hemera_ens/abi/0xab528d626ec275e3fad363ff1393a41f581c5897.json new file mode 100644 index 000000000..b369fcf70 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xab528d626ec275e3fad363ff1393a41f581c5897.json @@ -0,0 +1,229 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "TLDLocked", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "isOwner", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "lock", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "locked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "bool", + "name": "enabled", + "type": "bool" + } + ], + "name": "setController", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "setSubnodeOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xab528d626ec275e3fad363ff1393a41f581c5897" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xb1377e4f32e6746444970823d5506f98f5a04201.json b/indexer/modules/custom/hemera_ens/abi/0xb1377e4f32e6746444970823d5506f98f5a04201.json new file mode 100644 index 000000000..05a7bbf04 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xb1377e4f32e6746444970823d5506f98f5a04201.json @@ -0,0 +1,216 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_tokenSender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_startTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_endTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_streamingRate", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "claimableBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "endTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startTime", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "streamingRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenSender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalClaimed", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xb1377e4f32e6746444970823d5506f98f5a04201" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xb6e040c9ecaae172a89bd561c5f73e1c48d28cd9.json b/indexer/modules/custom/hemera_ens/abi/0xb6e040c9ecaae172a89bd561c5f73e1c48d28cd9.json new file mode 100644 index 000000000..8a1f1eeef --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xb6e040c9ecaae172a89bd561c5f73e1c48d28cd9.json @@ -0,0 +1,465 @@ +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "priceOracle", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrar", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "ratifier", + "type": "address" + } + ], + "name": "removeRatifier", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pendingClaims", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "ratifier", + "type": "address" + } + ], + "name": "addRatifier", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitPrefixClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "unresolvedClaims", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "claimed", + "type": "string" + } + ], + "name": "getClaimCost", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + } + ], + "name": "withdrawClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "destroy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "ratifyClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "claimed", + "type": "string" + }, + { + "name": "dnsname", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "computeClaimId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + }, + { + "name": "approved", + "type": "bool" + } + ], + "name": "setClaimStatus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitExactClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "phase", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + } + ], + "name": "resolveClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "approved", + "type": "bytes32[]" + }, + { + "name": "declined", + "type": "bytes32[]" + } + ], + "name": "setClaimStatuses", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "closeClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimIds", + "type": "bytes32[]" + } + ], + "name": "resolveClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "REGISTRATION_PERIOD", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "claims", + "outputs": [ + { + "name": "labelHash", + "type": "bytes32" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "paid", + "type": "uint256" + }, + { + "name": "status", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitCombinedClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_priceOracle", + "type": "address" + }, + { + "name": "_registrar", + "type": "address" + }, + { + "name": "_ratifier", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "claimed", + "type": "string" + }, + { + "indexed": false, + "name": "dnsname", + "type": "bytes" + }, + { + "indexed": false, + "name": "paid", + "type": "uint256" + }, + { + "indexed": false, + "name": "claimant", + "type": "address" + }, + { + "indexed": false, + "name": "email", + "type": "string" + } + ], + "name": "ClaimSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "claimId", + "type": "bytes32" + }, + { + "indexed": false, + "name": "status", + "type": "uint8" + } + ], + "name": "ClaimStatusChanged", + "type": "event" + } + ], + "address": "0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xb9d374d0fe3d8341155663fae31b7beae0ae233a.json b/indexer/modules/custom/hemera_ens/abi/0xb9d374d0fe3d8341155663fae31b7beae0ae233a.json new file mode 100644 index 000000000..24771fb6f --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xb9d374d0fe3d8341155663fae31b7beae0ae233a.json @@ -0,0 +1,186 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "uint256" + } + ], + "name": "rentPrices", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "", + "type": "uint256" + }, + { + "name": "duration", + "type": "uint256" + } + ], + "name": "price", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_rentPrices", + "type": "uint256[]" + } + ], + "name": "setPrices", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_usdOracle", + "type": "address" + } + ], + "name": "setOracle", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_usdOracle", + "type": "address" + }, + { + "name": "_rentPrices", + "type": "uint256[]" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "oracle", + "type": "address" + } + ], + "name": "OracleChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "prices", + "type": "uint256[]" + } + ], + "name": "RentPriceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ], + "address": "0xb9d374d0fe3d8341155663fae31b7beae0ae233a" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xc1735677a60884abbcf72295e88d47764beda282.json b/indexer/modules/custom/hemera_ens/abi/0xc1735677a60884abbcf72295e88d47764beda282.json new file mode 100644 index 000000000..47ee4210c --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xc1735677a60884abbcf72295e88d47764beda282.json @@ -0,0 +1,198 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "string", + "name": "_url", + "type": "string" + }, + { + "internalType": "address[]", + "name": "_signers", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "string[]", + "name": "urls", + "type": "string[]" + }, + { + "internalType": "bytes", + "name": "callData", + "type": "bytes" + }, + { + "internalType": "bytes4", + "name": "callbackFunction", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "OffchainLookup", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "signers", + "type": "address[]" + } + ], + "name": "NewSigners", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint64", + "name": "expires", + "type": "uint64" + }, + { + "internalType": "bytes", + "name": "request", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "makeSignatureHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "resolve", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "response", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "resolveWithProof", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "signers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "url", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0xc1735677a60884abbcf72295e88d47764beda282" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xc32659651d137a18b79925449722855aa327231d.json b/indexer/modules/custom/hemera_ens/abi/0xc32659651d137a18b79925449722855aa327231d.json new file mode 100644 index 000000000..a5d98ef94 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xc32659651d137a18b79925449722855aa327231d.json @@ -0,0 +1,519 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "label", + "type": "bytes32" + } + ], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "migration", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrarOwner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrar", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "label", + "type": "bytes32" + }, + { + "name": "subdomain", + "type": "string" + } + ], + "name": "query", + "outputs": [ + { + "name": "domain", + "type": "string" + }, + { + "name": "price", + "type": "uint256" + }, + { + "name": "rent", + "type": "uint256" + }, + { + "name": "referralFeePPM", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "label", + "type": "bytes32" + }, + { + "name": "subdomain", + "type": "string" + }, + { + "name": "_subdomainOwner", + "type": "address" + }, + { + "name": "referrer", + "type": "address" + }, + { + "name": "resolver", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_migration", + "type": "address" + } + ], + "name": "setMigrationAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "label", + "type": "bytes32" + }, + { + "name": "subdomain", + "type": "string" + } + ], + "name": "rentDue", + "outputs": [ + { + "name": "timestamp", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stopped", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "TLD_NODE", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "migrate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "label", + "type": "bytes32" + }, + { + "name": "subdomain", + "type": "string" + } + ], + "name": "payRent", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "price", + "type": "uint256" + }, + { + "name": "referralFeePPM", + "type": "uint256" + }, + { + "name": "_owner", + "type": "address" + }, + { + "name": "_transfer", + "type": "address" + } + ], + "name": "configureDomainFor", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "price", + "type": "uint256" + }, + { + "name": "referralFeePPM", + "type": "uint256" + } + ], + "name": "configureDomain", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "unlistDomain", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "name": "ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "name": "name", + "type": "string" + } + ], + "name": "DomainTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + } + ], + "name": "DomainConfigured", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + } + ], + "name": "DomainUnlisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "name": "subdomain", + "type": "string" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "name": "referrer", + "type": "address" + }, + { + "indexed": false, + "name": "price", + "type": "uint256" + } + ], + "name": "NewRegistration", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "name": "subdomain", + "type": "string" + }, + { + "indexed": false, + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "name": "expirationDate", + "type": "uint256" + } + ], + "name": "RentPaid", + "type": "event" + } + ], + "address": "0xc32659651d137a18b79925449722855aa327231d" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401.json b/indexer/modules/custom/hemera_ens/abi/0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401.json new file mode 100644 index 000000000..37e7747ae --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401.json @@ -0,0 +1,1458 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + }, + { + "internalType": "contract IBaseRegistrar", + "name": "_registrar", + "type": "address" + }, + { + "internalType": "contract IMetadataService", + "name": "_metadataService", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CannotUpgrade", + "type": "error" + }, + { + "inputs": [], + "name": "IncompatibleParent", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "IncorrectTargetOwner", + "type": "error" + }, + { + "inputs": [], + "name": "IncorrectTokenType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "labelHash", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "expectedLabelhash", + "type": "bytes32" + } + ], + "name": "LabelMismatch", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + } + ], + "name": "LabelTooLong", + "type": "error" + }, + { + "inputs": [], + "name": "LabelTooShort", + "type": "error" + }, + { + "inputs": [], + "name": "NameIsNotWrapped", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "OperationProhibited", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "Unauthorised", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "approved", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "ApprovalForAll", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "ControllerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "ExpiryExtended", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + } + ], + "name": "FusesSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "NameUnwrapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "NameWrapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + } + ], + "name": "TransferBatch", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "TransferSingle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "string", + "name": "value", + "type": "string" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "URI", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "_tokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fuseMask", + "type": "uint32" + } + ], + "name": "allFusesBurned", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "accounts", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + } + ], + "name": "balanceOfBatch", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "canExtendSubnames", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "addr", + "type": "address" + } + ], + "name": "canModifyName", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "controllers", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "extendExpiry", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getApproved", + "outputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "getData", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "address", + "name": "operator", + "type": "address" + } + ], + "name": "isApprovedForAll", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + } + ], + "name": "isWrapped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "isWrapped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "metadataService", + "outputs": [ + { + "internalType": "contract IMetadataService", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "names", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + } + ], + "name": "ownerOf", + "outputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "recoverFunds", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "registerAndWrapETH2LD", + "outputs": [ + { + "internalType": "uint256", + "name": "registrarExpiry", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "registrar", + "outputs": [ + { + "internalType": "contract IBaseRegistrar", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [ + { + "internalType": "uint256", + "name": "expires", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "ids", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "amounts", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeBatchTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "id", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "safeTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "operator", + "type": "address" + }, + { + "internalType": "bool", + "name": "approved", + "type": "bool" + } + ], + "name": "setApprovalForAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setChildFuses", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "controller", + "type": "address" + }, + { + "internalType": "bool", + "name": "active", + "type": "bool" + } + ], + "name": "setController", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + } + ], + "name": "setFuses", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IMetadataService", + "name": "_metadataService", + "type": "address" + } + ], + "name": "setMetadataService", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setRecord", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setSubnodeOwner", + "outputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + }, + { + "internalType": "uint32", + "name": "fuses", + "type": "uint32" + }, + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "name": "setSubnodeRecord", + "outputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint64", + "name": "ttl", + "type": "uint64" + } + ], + "name": "setTTL", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract INameWrapperUpgrade", + "name": "_upgradeAddress", + "type": "address" + } + ], + "name": "setUpgradeContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "parentNode", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "unwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "labelhash", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "registrant", + "type": "address" + }, + { + "internalType": "address", + "name": "controller", + "type": "address" + } + ], + "name": "unwrapETH2LD", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "bytes", + "name": "extraData", + "type": "bytes" + } + ], + "name": "upgrade", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "upgradeContract", + "outputs": [ + { + "internalType": "contract INameWrapperUpgrade", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + } + ], + "name": "uri", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "wrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "label", + "type": "string" + }, + { + "internalType": "address", + "name": "wrappedOwner", + "type": "address" + }, + { + "internalType": "uint16", + "name": "ownerControlledFuses", + "type": "uint16" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "wrapETH2LD", + "outputs": [ + { + "internalType": "uint64", + "name": "expiry", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xd7a029db2585553978190db5e85ec724aa4df23f.json b/indexer/modules/custom/hemera_ens/abi/0xd7a029db2585553978190db5e85ec724aa4df23f.json new file mode 100644 index 000000000..ab95eb51b --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xd7a029db2585553978190db5e85ec724aa4df23f.json @@ -0,0 +1,226 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ERC20", + "name": "_token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_unlockBegin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_unlockCliff", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_unlockEnd", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "Locked", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + } + ], + "name": "claimableBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "claimedAmounts", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "lock", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "lockedAmounts", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unlockBegin", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unlockCliff", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unlockEnd", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "address": "0xd7a029db2585553978190db5e85ec724aa4df23f" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xdaaf96c344f63131acadd0ea35170e7892d3dfba.json b/indexer/modules/custom/hemera_ens/abi/0xdaaf96c344f63131acadd0ea35170e7892d3dfba.json new file mode 100644 index 000000000..46dfea5c9 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xdaaf96c344f63131acadd0ea35170e7892d3dfba.json @@ -0,0 +1,845 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + } + ], + "name": "ABIChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "AddrChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "newAddress", + "type": "bytes" + } + ], + "name": "AddressChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "AuthorisationChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "ContenthashChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "record", + "type": "bytes" + } + ], + "name": "DNSRecordChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "name", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "DNSRecordDeleted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "DNSZoneCleared", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "indexed": false, + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "InterfaceChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "NameChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "PubkeyChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "string", + "name": "indexedKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "TextChanged", + "type": "event" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentTypes", + "type": "uint256" + } + ], + "name": "ABI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "address payable", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + } + ], + "name": "addr", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "authorisations", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "clearDNSZone", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "contenthash", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + }, + { + "internalType": "uint16", + "name": "resource", + "type": "uint16" + } + ], + "name": "dnsRecord", + "outputs": [ + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "name", + "type": "bytes32" + } + ], + "name": "hasDNSRecords", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "interfaceImplementer", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + } + ], + "name": "pubkey", + "outputs": [ + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "contentType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setABI", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "coinType", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "a", + "type": "bytes" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "setAddr", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "bool", + "name": "isAuthorised", + "type": "bool" + } + ], + "name": "setAuthorisation", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "hash", + "type": "bytes" + } + ], + "name": "setContenthash", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "setDNSRecords", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + }, + { + "internalType": "address", + "name": "implementer", + "type": "address" + } + ], + "name": "setInterface", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "setName", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "x", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "y", + "type": "bytes32" + } + ], + "name": "setPubkey", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + }, + { + "internalType": "string", + "name": "value", + "type": "string" + } + ], + "name": "setText", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "key", + "type": "string" + } + ], + "name": "text", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + } + ], + "address": "0xdaaf96c344f63131acadd0ea35170e7892d3dfba" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xe65d8aaf34cb91087d1598e0a15b582f57f217d9.json b/indexer/modules/custom/hemera_ens/abi/0xe65d8aaf34cb91087d1598e0a15b582f57f217d9.json new file mode 100644 index 000000000..d6a750494 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xe65d8aaf34cb91087d1598e0a15b582f57f217d9.json @@ -0,0 +1,598 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "DomainConfigured", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "DomainTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "DomainUnlisted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "subdomain", + "type": "string" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "referrer", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "price", + "type": "uint256" + } + ], + "name": "NewRegistration", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "oldOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "string", + "name": "subdomain", + "type": "string" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "expirationDate", + "type": "uint256" + } + ], + "name": "RentPaid", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "TLD_NODE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "referralFeePPM", + "type": "uint256" + } + ], + "name": "configureDomain", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "referralFeePPM", + "type": "uint256" + }, + { + "internalType": "address payable", + "name": "_owner", + "type": "address" + }, + { + "internalType": "address", + "name": "_transfer", + "type": "address" + } + ], + "name": "configureDomainFor", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "migrate", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "node", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "migrateSubdomain", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "migration", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + } + ], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "subdomain", + "type": "string" + } + ], + "name": "payRent", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "subdomain", + "type": "string" + } + ], + "name": "query", + "outputs": [ + { + "internalType": "string", + "name": "domain", + "type": "string" + }, + { + "internalType": "uint256", + "name": "price", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "rent", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "referralFeePPM", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "subdomain", + "type": "string" + }, + { + "internalType": "address", + "name": "_subdomainOwner", + "type": "address" + }, + { + "internalType": "address payable", + "name": "referrer", + "type": "address" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "register", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrar", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrarOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes32", + "name": "label", + "type": "bytes32" + }, + { + "internalType": "string", + "name": "subdomain", + "type": "string" + } + ], + "name": "rentDue", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_migration", + "type": "address" + } + ], + "name": "setMigrationAddress", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address", + "name": "resolver", + "type": "address" + } + ], + "name": "setResolver", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "stop", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stopped", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "address payable", + "name": "newOwner", + "type": "address" + } + ], + "name": "transfer", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "unlistDomain", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xe65d8aaf34cb91087d1598e0a15b582f57f217d9" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xf0ad5cad05e10572efceb849f6ff0c68f9700455.json b/indexer/modules/custom/hemera_ens/abi/0xf0ad5cad05e10572efceb849f6ff0c68f9700455.json new file mode 100644 index 000000000..a7cb0741e --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xf0ad5cad05e10572efceb849f6ff0c68f9700455.json @@ -0,0 +1,434 @@ +{ + "abi": [ + { + "constant": true, + "inputs": [ + { + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "withdraw", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_prices", + "type": "address" + } + ], + "name": "setPriceOracle", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "name": "_maxCommitmentAge", + "type": "uint256" + } + ], + "name": "setCommitmentAges", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "commitments", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "duration", + "type": "uint256" + } + ], + "name": "rentPrice", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "owner", + "type": "address" + }, + { + "name": "duration", + "type": "uint256" + }, + { + "name": "secret", + "type": "bytes32" + } + ], + "name": "register", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MIN_REGISTRATION_DURATION", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "minCommitmentAge", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "owner", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "isOwner", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "valid", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "duration", + "type": "uint256" + } + ], + "name": "renew", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "name", + "type": "string" + } + ], + "name": "available", + "outputs": [ + { + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "maxCommitmentAge", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "commitment", + "type": "bytes32" + } + ], + "name": "commit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "name", + "type": "string" + }, + { + "name": "owner", + "type": "address" + }, + { + "name": "secret", + "type": "bytes32" + } + ], + "name": "makeCommitment", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "name": "_base", + "type": "address" + }, + { + "name": "_prices", + "type": "address" + }, + { + "name": "_minCommitmentAge", + "type": "uint256" + }, + { + "name": "_maxCommitmentAge", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": true, + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "name", + "type": "string" + }, + { + "indexed": true, + "name": "label", + "type": "bytes32" + }, + { + "indexed": false, + "name": "cost", + "type": "uint256" + }, + { + "indexed": false, + "name": "expires", + "type": "uint256" + } + ], + "name": "NameRenewed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "oracle", + "type": "address" + } + ], + "name": "NewPriceOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + } + ], + "address": "0xF0AD5cAd05e10572EfcEB849f6Ff0c68f9700455" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749.json b/indexer/modules/custom/hemera_ens/abi/0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749.json new file mode 100644 index 000000000..8a1f1eeef --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749.json @@ -0,0 +1,465 @@ +{ + "abi": [ + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "removeOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "priceOracle", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "registrar", + "outputs": [ + { + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "ratifier", + "type": "address" + } + ], + "name": "removeRatifier", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "pendingClaims", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "ratifier", + "type": "address" + } + ], + "name": "addRatifier", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitPrefixClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "unresolvedClaims", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "claimed", + "type": "string" + } + ], + "name": "getClaimCost", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "owner", + "type": "address" + } + ], + "name": "addOwner", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + } + ], + "name": "withdrawClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "destroy", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "ratifyClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "claimed", + "type": "string" + }, + { + "name": "dnsname", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "computeClaimId", + "outputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + }, + { + "name": "approved", + "type": "bool" + } + ], + "name": "setClaimStatus", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitExactClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "phase", + "outputs": [ + { + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimId", + "type": "bytes32" + } + ], + "name": "resolveClaim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "approved", + "type": "bytes32[]" + }, + { + "name": "declined", + "type": "bytes32[]" + } + ], + "name": "setClaimStatuses", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "closeClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "claimIds", + "type": "bytes32[]" + } + ], + "name": "resolveClaims", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "REGISTRATION_PERIOD", + "outputs": [ + { + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "name": "", + "type": "bytes32" + } + ], + "name": "claims", + "outputs": [ + { + "name": "labelHash", + "type": "bytes32" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "paid", + "type": "uint256" + }, + { + "name": "status", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "name": "name", + "type": "bytes" + }, + { + "name": "claimant", + "type": "address" + }, + { + "name": "email", + "type": "string" + } + ], + "name": "submitCombinedClaim", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "name": "_priceOracle", + "type": "address" + }, + { + "name": "_registrar", + "type": "address" + }, + { + "name": "_ratifier", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "name": "claimed", + "type": "string" + }, + { + "indexed": false, + "name": "dnsname", + "type": "bytes" + }, + { + "indexed": false, + "name": "paid", + "type": "uint256" + }, + { + "indexed": false, + "name": "claimant", + "type": "address" + }, + { + "indexed": false, + "name": "email", + "type": "string" + } + ], + "name": "ClaimSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "name": "claimId", + "type": "bytes32" + }, + { + "indexed": false, + "name": "status", + "type": "uint8" + } + ], + "name": "ClaimStatusChanged", + "type": "event" + } + ], + "address": "0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7.json b/indexer/modules/custom/hemera_ens/abi/0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7.json new file mode 100644 index 000000000..9d8be4ecc --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7.json @@ -0,0 +1,745 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "minDelay", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "proposers", + "type": "address[]" + }, + { + "internalType": "address[]", + "name": "executors", + "type": "address[]" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "CallExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "delay", + "type": "uint256" + } + ], + "name": "CallScheduled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "Cancelled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "oldDuration", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "newDuration", + "type": "uint256" + } + ], + "name": "MinDelayChange", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "EXECUTOR_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "PROPOSER_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TIMELOCK_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "cancel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "datas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "executeBatch", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getMinDelay", + "outputs": [ + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "getTimestamp", + "outputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "hashOperation", + "outputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "datas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + } + ], + "name": "hashOperationBatch", + "outputs": [ + { + "internalType": "bytes32", + "name": "hash", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "isOperation", + "outputs": [ + { + "internalType": "bool", + "name": "pending", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "isOperationDone", + "outputs": [ + { + "internalType": "bool", + "name": "done", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "isOperationPending", + "outputs": [ + { + "internalType": "bool", + "name": "pending", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "id", + "type": "bytes32" + } + ], + "name": "isOperationReady", + "outputs": [ + { + "internalType": "bool", + "name": "ready", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "delay", + "type": "uint256" + } + ], + "name": "schedule", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "targets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "values", + "type": "uint256[]" + }, + { + "internalType": "bytes[]", + "name": "datas", + "type": "bytes[]" + }, + { + "internalType": "bytes32", + "name": "predecessor", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "delay", + "type": "uint256" + } + ], + "name": "scheduleBatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newDelay", + "type": "uint256" + } + ], + "name": "updateDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ], + "address": "0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xff252725f6122a92551a5fa9a6b6bf10eb0be035.json b/indexer/modules/custom/hemera_ens/abi/0xff252725f6122a92551a5fa9a6b6bf10eb0be035.json new file mode 100644 index 000000000..5fca6e0eb --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xff252725f6122a92551a5fa9a6b6bf10eb0be035.json @@ -0,0 +1,114 @@ +{ + "abi": [ + { + "inputs": [ + { + "internalType": "contract ENS", + "name": "_ens", + "type": "address" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "constant": true, + "inputs": [], + "name": "BULK_RENEWAL_ID", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "ens", + "outputs": [ + { + "internalType": "contract ENS", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "renewAll", + "outputs": [], + "payable": true, + "stateMutability": "payable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "string[]", + "name": "names", + "type": "string[]" + }, + { + "internalType": "uint256", + "name": "duration", + "type": "uint256" + } + ], + "name": "rentPrice", + "outputs": [ + { + "internalType": "uint256", + "name": "total", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceID", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "pure", + "type": "function" + } + ], + "address": "0xff252725f6122a92551a5fa9a6b6bf10eb0be035" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/abi/0xffc8ca4e83416b7e0443ff430cc245646434b647.json b/indexer/modules/custom/hemera_ens/abi/0xffc8ca4e83416b7e0443ff430cc245646434b647.json new file mode 100644 index 000000000..c106c0d0e --- /dev/null +++ b/indexer/modules/custom/hemera_ens/abi/0xffc8ca4e83416b7e0443ff430cc245646434b647.json @@ -0,0 +1,359 @@ +{ + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "addr", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "copy", + "type": "uint8" + } + ], + "name": "Redemption", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "subtractedValue", + "type": "uint256" + } + ], + "name": "decreaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "addedValue", + "type": "uint256" + } + ], + "name": "increaseAllowance", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "purchasedCopies", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "copy", + "type": "uint8" + } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "orderId", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "copy", + "type": "uint8" + } + ], + "name": "redeemFor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "address": "0xffc8ca4e83416b7e0443ff430cc245646434b647" +} \ No newline at end of file diff --git a/indexer/modules/custom/hemera_ens/ens_abi.py b/indexer/modules/custom/hemera_ens/ens_abi.py new file mode 100644 index 000000000..fb514e61e --- /dev/null +++ b/indexer/modules/custom/hemera_ens/ens_abi.py @@ -0,0 +1,19 @@ +import json +import os + + +def get_absolute_path(relative_path): + current_dir = os.path.dirname(os.path.abspath(__file__)) + absolute_path = os.path.join(current_dir, relative_path) + return absolute_path + + +abi_map = {} + +relative_path = "abi" +absolute_path = get_absolute_path(relative_path) +fs = os.listdir(absolute_path) +for a_f in fs: + with open(os.path.join(absolute_path, a_f), "r") as data_file: + dic = json.load(data_file) + abi_map[dic["address"].lower()] = json.dumps(dic["abi"]) diff --git a/indexer/modules/custom/hemera_ens/ens_conf.py b/indexer/modules/custom/hemera_ens/ens_conf.py new file mode 100644 index 000000000..ef62be1ca --- /dev/null +++ b/indexer/modules/custom/hemera_ens/ens_conf.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/4/16 19:48 +# @Author will +# @File ens_conf.py +# @Brief + +CONTRACT_NAME_MAP = { + "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5": "ENS: Old ETH Registrar Controller", + "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85": "ENS: Base Registrar Implementation", + "0x084b1c3c81545d370f3634392de611caabff8148": "ENS: Old Reverse Registrar 2", + "0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41": "ENS: Public Resolver 2", + "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e": "ENS: Registry with Fallback", + "0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb": "ENS: Reverse Registrar", + "0x314159265dd8dbb310642f98f50c066173c1259b": "ENS: Eth Name Service", + "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401": "ENS: Name Wrapper", + "0xff252725f6122a92551a5fa9a6b6bf10eb0be035": "ENS: Bulk Renewal", + "0x323a76393544d5ecca80cd6ef2a560c6a395b7e3": "ENS: Governance", + "0xe65d8aaf34cb91087d1598e0a15b582f57f217d9": "ENS: Migration Subdomain Registrar", + "0xc32659651d137a18b79925449722855aa327231d": "ENS: Subdomain Registrar", + "0xdaaf96c344f63131acadd0ea35170e7892d3dfba": "ENS: Public Resolver 1", + "0x9b6d20f524367d7e98ed849d37fc662402dca7fb": "ENS: Mapper", + "0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749": "ENS: Short Name Claims", + "0x690f0581ececcf8389c223170778cd9d029606f2": "ENS: Cold Wallet", + "0xd7a029db2585553978190db5e85ec724aa4df23f": "ENS: Token Timelock", + "0xffc8ca4e83416b7e0443ff430cc245646434b647": "ENS: ENS Constitution Book Token", + "0x911143d946ba5d467bfc476491fdb235fef4d667": "ENS: Multisig", + "0xa2f428617a523837d4adc81c67a296d42fd95e86": "ENS: DNS Registrar", + "0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7": "ENS: DAO Wallet", + "0xa2c122be93b0074270ebee7f6b7292c7deb45047": "ENS: Default Reverse Resolver", + "0xab528d626ec275e3fad363ff1393a41f581c5897": "ENS: Root", + "0xb9d374d0fe3d8341155663fae31b7beae0ae233a": "ENS: Stable Price Oracle", + "0xb1377e4f32e6746444970823d5506f98f5a04201": "ENS: Token Streaming Contract", + "0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c": "ENS: Reverse Records", + "0x60c7c2a24b5e86c38639fd1586917a8fef66a56d": "ENS: Registrar Migration", + "0xc1735677a60884abbcf72295e88d47764beda282": "ENS: Offchain Resolver", + "0x226159d592e2b063810a10ebf6dcbada94ed68b8": "ENS: Old Public Resolver 2", + "0x9062c0a6dbd6108336bcbe4593a3d1ce05512069": "ENS: Old Reverse Registrar", + "0x231b0ee14048e9dccd1d247744d114a4eb5e8e63": "ENS: Public Resolver", + "0x253553366da8546fc250f225fe3d25d0c782303b": "ENS: ETH Registrar Controller", + "0x00000000008794027c69c26d2a048dbec09de67c": "No Name Tag", + "0x705bfbcfccde554e11df213bf6d463ea00dd57cc": "ETHBulkRegistrar", + "0x1d6552e8f46fd509f3918a174fe62c34b42564ae": "ETHBulkRegistrarV1", + "0xf5925e20e3f4002ba17130641a87f574c71a8b3c": "NOT Verified", + "0x27c9b34eb43523447d3e1bcf26f009d814522687": "TransparentUpgradeableProxy", + "0x5d81ce189bf5267e70fb555916ffbc3d4c7b2245": "EnsBatchRenew", + "0x6109dd117aa5486605fc85e040ab00163a75c662": "RegistrarMigration", +} + + +ENS_BEGIN_BLOCK = 9380235 +ENS_CONTRACT_CREATED_BLOCK = 3327417 +BASE_NODE = "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae" +REVERSE_BASE_NODE = "0x91d1777781884d03a6757a803996e38de2a42967fb37eeaca72729271025a9e2" diff --git a/indexer/modules/custom/hemera_ens/ens_domain.py b/indexer/modules/custom/hemera_ens/ens_domain.py new file mode 100644 index 000000000..028d760e5 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/ens_domain.py @@ -0,0 +1,84 @@ +from dataclasses import dataclass +from datetime import datetime +from typing import Optional + +from indexer.domain import Domain, FilterData + +"""for ens_middle""" + + +@dataclass +class ENSMiddleD(FilterData): + transaction_hash: str + log_index: int + transaction_index: int + block_number: Optional[int] = None + block_hash: Optional[str] = None + block_timestamp: Optional[datetime] = None + method: Optional[str] = None + event_name: Optional[str] = None + topic0: Optional[str] = None + from_address: Optional[str] = None + to_address: Optional[str] = None + + base_node: Optional[str] = None + node: Optional[str] = None + label: Optional[str] = None + name: Optional[str] = None + registration: Optional[datetime] = None + expires: Optional[datetime] = None + owner: Optional[str] = None + resolver: Optional[str] = None + address: Optional[str] = None + reverse_base_node: Optional[str] = None + reverse_node: Optional[str] = None + reverse_label: Optional[str] = None + reverse_name: Optional[str] = None + # erc721 + token_id: Optional[str] = None + # erc1155 + w_token_id: Optional[str] = None + reorg: bool = False + + +"""below is for ens_record""" + + +@dataclass +class ENSRegisterD(FilterData): + + registration: Optional[datetime] = None + expires: Optional[datetime] = None + name: Optional[str] = None + label: Optional[str] = None + first_owned_by: Optional[str] = None + base_node: Optional[str] = None + node: Optional[str] = None + token_id: Optional[str] = None + w_token_id: Optional[str] = None + + +@dataclass +class ENSNameRenewD(FilterData): + + node: Optional[str] = None + expires: Optional[datetime] = None + + +@dataclass +class ENSAddressChangeD(FilterData): + + node: Optional[str] = None + address: Optional[str] = None + + +"""for ens_address""" + + +@dataclass +class ENSAddressD(FilterData): + + address: Optional[str] = None + reverse_node: Optional[str] = None + name: Optional[str] = None + block_number: Optional[int] = None diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py new file mode 100644 index 000000000..0ab82bdf1 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -0,0 +1,298 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/4/24 13:15 +# @Author will +# @File ens_handler.py +# @Brief +import logging +from collections import defaultdict +from dataclasses import asdict +from multiprocessing import Queue + +from eth_abi.codec import ABICodec +from web3 import Web3 + +from indexer.modules.custom.hemera_ens import lifo_registry +from indexer.modules.custom.hemera_ens.ens_abi import abi_map +from indexer.modules.custom.hemera_ens.ens_conf import CONTRACT_NAME_MAP, ENS_CONTRACT_CREATED_BLOCK, REVERSE_BASE_NODE +from indexer.modules.custom.hemera_ens.ens_domain import ( + ENSAddressChangeD, + ENSAddressD, + ENSMiddleD, + ENSNameRenewD, + ENSRegisterD, +) +from indexer.modules.custom.hemera_ens.ens_hash import namehash +from indexer.modules.custom.hemera_ens.extractors import BaseExtractor, RegisterExtractor +from indexer.modules.custom.hemera_ens.util import convert_str_ts + +logger = logging.getLogger(__name__) + + +class EnsConfLoader: + def __init__(self, provider=None): + self.contract_object_map = None + self.event_map = None + self.function_map = None + if not provider: + provider = "https://ethereum-rpc.publicnode.com" + self.w3 = Web3(Web3.HTTPProvider(provider)) + self.w3.codec = ABICodec(lifo_registry) + self.build_contract_map() + + def build_contract_map(self): + contract_object_map = {} + for ad_lower in CONTRACT_NAME_MAP: + if ad_lower not in abi_map: + continue + abi = abi_map[ad_lower] + contract = self.w3.eth.contract(address=Web3.to_checksum_address(ad_lower), abi=abi) + contract_object_map[ad_lower] = contract + + event_map = defaultdict(dict) + function_map = defaultdict(dict) + + for contract_address, contract in contract_object_map.items(): + abi_events = [abi for abi in contract.abi if abi["type"] == "event"] + for event in abi_events: + sig = self.get_signature_of_event(event) + event_map[sig] = event + functions = [abi for abi in contract.abi if abi["type"] == "function"] + for function in functions: + sig = self.get_function_signature(function) + function_map[sig[0:10]] = function + self.contract_object_map = contract_object_map + self.event_map = event_map + self.function_map = function_map + + def get_signature_of_event(self, event): + name = event["name"] + inputs = [p["type"] for p in event["inputs"]] + inputs = ",".join(inputs) + text = f"{name}({inputs})" + sig = self.w3.to_hex(Web3.keccak(text=text)) + return sig + + def get_function_signature(self, function_abi): + name = function_abi["name"] + inputs = [input["type"] for input in function_abi["inputs"]] + signature = f"{name}({','.join(inputs)})" + sig = self.w3.to_hex(Web3.keccak(text=signature)) + return sig + + +class AttrDict(dict): + def __init__(self, *args, **kwargs): + super(AttrDict, self).__init__(*args, **kwargs) + self.__dict__ = self + + +class EnsHandler: + def __init__(self, ens_conf_loader): + self.rss = [] + self.result = Queue() + self.ens_conf_loader = ens_conf_loader + self.contract_object_map = self.ens_conf_loader.contract_object_map + self.function_map = self.ens_conf_loader.function_map + self.event_map = self.ens_conf_loader.event_map + self.extractors = [extractor() for extractor in BaseExtractor.__subclasses__()] + + def is_ens_address(self, address): + return address.lower() in self.ens_conf_loader.contract_object_map + + def get_event_name(self, sig): + event = self.event_map[sig] + if not event: + logger.error("No Event sig: {}, event_map: {}".format(sig, self.event_map)) + raise ValueError(f"No event for {sig}") + return event["name"] + + def get_function_name(self, sig): + f = self.function_map[sig] + if not f: + logger.error("No function sig: {}, event_map: {}".format(sig, self.function_map)) + raise ValueError(f"No function sig: {sig}") + return f["name"] + + def decode_transaction(self, transaction): + if not transaction["to_address"]: + return None + con = self.contract_object_map[transaction["to_address"]] + decoded_input = con.decode_function_input(transaction["input"]) + return decoded_input + + def process(self, transaction, logs): + if ( + not self.is_ens_address(transaction["to_address"]) + or transaction["block_number"] < ENS_CONTRACT_CREATED_BLOCK + ): + return [] + method = None + tra_sig = transaction["input"][0:10] + if tra_sig in self.function_map: + function = self.function_map[tra_sig] + if function: + method = function.get("name") + tra = transaction + dic = { + "transaction_hash": tra["hash"], + "log_index": None, + "transaction_index": tra["transaction_index"], + "block_number": tra["block_number"], + "block_hash": tra["block_hash"], + "block_timestamp": convert_str_ts(tra["block_timestamp"]), + "method": method, + "event_name": None, + "from_address": tra["from_address"], + "to_address": tra["to_address"], + "name": None, + "base_node": None, + "node": None, + "label": None, + "expires": None, + "owner": None, + "resolver": None, + "address": None, + "reverse_node": None, + "reverse_label": None, + "reverse_name": None, + "reverse_base_node": None, + } + if method == "setName": + d_tnx = self.decode_transaction(transaction) + ens_middle = AttrDict(dic) + ens_middle.log_index = -1 + name = None + if d_tnx[1].get("name"): + name = d_tnx[1]["name"] + elif d_tnx[1].get("newName"): + name = d_tnx[1]["newName"] + if not name or len(name) - 4 != name.find("."): + return [] + ens_middle.reverse_name = name + + ens_middle.node = namehash(name) + ens_middle.address = tra["from_address"].lower() + return [ + ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + reverse_name=ens_middle.reverse_name, + address=ens_middle.address, + node=ens_middle.node, + reverse_node=None, + reverse_base_node=REVERSE_BASE_NODE, + event_name=None, + method="setName", + ) + ] + res = [] + start = 0 + for idx, single_log in enumerate(logs): + if not self.is_ens_address(single_log["address"]): + continue + if not single_log.get("topic0") or (single_log["topic0"] not in self.event_map): + continue + single_log["address"] = single_log["address"].lower() + ens_middle = AttrDict(dic) + ens_middle.log_index = single_log["log_index"] + + for extractor in self.extractors: + solved_event = extractor.extract( + single_log["address"], + single_log["topic0"], + single_log, + ens_middle, + self.contract_object_map, + self.event_map, + logs[start : idx + 1], + ) + if solved_event: + res.append(solved_event) + if ( + single_log["topic0"] == RegisterExtractor.tp0_register + or RegisterExtractor.tp_register_with_token + ): + start = idx + break + # merge same node register, keep 0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f delete 0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9 + mark_register = set() + for rr in res: + if ( + rr.event_name == "NameRegistered" + and rr.topic0 == "0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f" + ): + mark_register.add(rr.node) + new_res = [] + for rr in res: + if ( + rr.event_name == "NameRegistered" + and rr.node in mark_register + and rr.topic0 == "0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9" + ): + continue + else: + new_res.append(rr) + return new_res + + def process_middle(self, lis): + if not lis: + return [] + items = [] + for record in lis: + dic = self.resolve_middle(asdict(record)) + items.append(dic) + return items + + def resolve_middle(self, record): + name = record.get("name") + if name and not name.endswith(".eth"): + name += ".eth" + address = record.get("address") + if address: + address = address.lower() + if record.get("expires"): + if isinstance(record.get("expires"), str): + record["expires"] = record.get("expires") + else: + record["expires"] = record.get("expires").strftime("%Y-%m-%d %H:%M:%S") + else: + record["expires"] = None + + event_name = record.get("event_name") + if event_name == "NameChanged" or record["method"] == "setName": + return ENSAddressD( + address=address, + reverse_node=record["reverse_node"], + name=record["reverse_name"], + block_number=record["block_number"], + ) + if event_name == "NameRegistered": + return ENSRegisterD( + node=record["node"], + name=record["name"], + expires=record["expires"], + registration=record["block_timestamp"], + label=record["label"], + first_owned_by=record["owner"], + base_node=record["base_node"], + token_id=record["token_id"], + w_token_id=record["w_token_id"], + ) + + if event_name == "NameRenewed": + return ENSNameRenewD( + node=record["node"], + expires=record["expires"], + ) + if event_name == "AddressChanged": + return ENSAddressChangeD( + node=record["node"], + address=address, + ) diff --git a/indexer/modules/custom/hemera_ens/ens_hash.py b/indexer/modules/custom/hemera_ens/ens_hash.py new file mode 100644 index 000000000..3609ad8ba --- /dev/null +++ b/indexer/modules/custom/hemera_ens/ens_hash.py @@ -0,0 +1,39 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/5/23 17:18 +# @Author will +# @File ens_hash.py +# @Brief +from eth_utils import keccak +from web3 import Web3 + + +def namehash(name): + """ + Calculate the namehash of an ENS name. + + :param name: The ENS name to hash (e.g., 'example.eth'). + :return: The namehash as a hexadecimal string. + """ + node = b"\x00" * 32 + if name: + labels = name.split(".") + for label in reversed(labels): + node = keccak(node + keccak(label.encode("utf-8"))) + return Web3.to_hex(node) + + +def get_label(name): + return Web3.to_hex(keccak(name.encode("utf-8"))) + + +def compute_node_label(base_node, label): + base_node = base_node.lower() + if base_node.startswith("0x"): + base_node = base_node[2:] + label = label.lower() + if label.startswith("0x"): + label = label[2:] + + node = keccak(bytes.fromhex(base_node) + bytes.fromhex(label)) + return Web3.to_hex(node).lower() diff --git a/indexer/modules/custom/hemera_ens/export_ens_job.py b/indexer/modules/custom/hemera_ens/export_ens_job.py new file mode 100644 index 000000000..59a581b5c --- /dev/null +++ b/indexer/modules/custom/hemera_ens/export_ens_job.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/8/27 11:26 +# @Author will +# @File export_ens_job.py +# @Brief +import logging +from collections import defaultdict +from dataclasses import asdict, fields, is_dataclass +from typing import Any, Dict, List + +from common.utils.exception_control import FastShutdownError +from indexer.domain.log import Log +from indexer.domain.transaction import Transaction +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom.hemera_ens import CONTRACT_NAME_MAP, EnsConfLoader, EnsHandler +from indexer.modules.custom.hemera_ens.ens_domain import ( + ENSAddressChangeD, + ENSAddressD, + ENSMiddleD, + ENSNameRenewD, + ENSRegisterD, +) +from indexer.modules.custom.hemera_ens.extractors import BaseExtractor +from indexer.specification.specification import ( + ToAddressSpecification, + TopicSpecification, + TransactionFilterByLogs, + TransactionFilterByTransactionInfo, +) + +logger = logging.getLogger(__name__) + + +# Exports hemera_ens related info +class ExportEnsJob(FilterTransactionDataJob): + dependency_types = [Transaction, Log] + output_types = [ENSMiddleD, ENSRegisterD, ENSNameRenewD, ENSAddressChangeD, ENSAddressD] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + # check chainId, only available on ethMainNet + if self._web3.eth.chain_id != 1: + raise FastShutdownError("ExportEnsJob is only supported on Ethereum Main networks") + + self._is_batch = kwargs["batch_size"] > 1 + self._filters = kwargs.get("filters", []) + self.ens_handler = EnsHandler(EnsConfLoader(self._web3.provider.endpoint_uri)) + + def get_filter(self): + + extractors = [extractor() for extractor in BaseExtractor.__subclasses__()] + tp_variables = [ + getattr(extractor, attr) for extractor in extractors for attr in dir(extractor) if attr.startswith("tp") + ] + + addresses = list(CONTRACT_NAME_MAP.keys()) + return [ + TransactionFilterByLogs([TopicSpecification(addresses=addresses, topics=tp_variables)]), + TransactionFilterByTransactionInfo(ToAddressSpecification("0x084b1c3c81545d370f3634392de611caabff8148")), + ] + + def _collect(self, **kwargs): + transactions: List[Transaction] = self._data_buff.get(Transaction.type(), []) + logs = self._data_buff.get(Log.type(), []) + middles = [] + transactions_map = {} + group_data = defaultdict(list) + for ta in transactions: + transactions_map[ta.hash] = asdict(ta) + group_data[ta.hash] = [] + for dl in logs: + group_data[dl.transaction_hash].append(asdict(dl)) + for tnx, tnx_lgs in group_data.items(): + tra = transactions_map.get(tnx) + dic_lis = self.ens_handler.process(tra, tnx_lgs) + if dic_lis: + middles.extend(dic_lis) + middles.sort(key=lambda x: (x.block_number, x.transaction_index, x.log_index), reverse=False) + res = self.ens_handler.process_middle(middles) + res = merge_ens_objects(res) + for item in middles + res: + if item: + self._collect_item(item.type(), item) + + +def merge_ens_objects(objects: List[Any]) -> List[Any]: + latest_objects: Dict[tuple, Any] = {} + + for obj in objects: + if not is_dataclass(obj): + continue + if isinstance(obj, ENSAddressD): + key = (type(obj), getattr(obj, "address", None)) + else: + key = (type(obj), getattr(obj, "node", None)) + + if key in latest_objects: + for field in fields(obj): + if getattr(obj, field.name) is not None: + setattr(latest_objects[key], field.name, getattr(obj, field.name)) + else: + latest_objects[key] = obj + + return list(latest_objects.values()) diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py new file mode 100644 index 000000000..802a5f055 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/6/28 15:34 +# @Author will +# @File extractors.py +# @Brief +import logging + +from web3 import Web3 + +from indexer.modules.custom.hemera_ens.ens_conf import BASE_NODE, REVERSE_BASE_NODE +from indexer.modules.custom.hemera_ens.ens_domain import ENSMiddleD +from indexer.modules.custom.hemera_ens.ens_hash import compute_node_label, namehash +from indexer.modules.custom.hemera_ens.util import convert_str_ts + +logger = logging.getLogger(__name__) + + +def decode_log(lg, contract_object_map, event_map): + tp0 = lg["topic0"] + con = contract_object_map[lg["address"]] + + matched_event = event_map.get(tp0) + if not matched_event: + return None + try: + topics = [bytes.fromhex(lg.get("topic" + str(i))[2:]) for i in range(4) if lg.get("topic" + str(i))] + wlg = { + "address": lg.get("address"), + "topics": topics, + "data": bytes.fromhex(lg.get("data")[2:]), + "blockNumber": lg.get("block_number"), + "transactionHash": lg.get("transaction_hash"), + "transactionIndex": lg.get("transaction_index"), + "blockHash": lg.get("block_hash"), + "logIndex": lg.get("log_index"), + "removed": False, + } + dl = con.events[matched_event["name"]]().process_log(wlg) + event_data = { + "args": dict(dl["args"]), + "event": dl["event"], + "logIndex": dl["logIndex"], + "transactionIndex": dl["transactionIndex"], + "transactionHash": dl["transactionHash"], + "address": dl["address"], + "blockHash": dl["blockHash"], + "blockNumber": dl["blockNumber"], + "_sig": lg["topic0"], + "_event": matched_event["name"], + } + + inputs = matched_event["inputs"] + for ipt in inputs: + k = ipt["name"] + if k not in dl["args"]: + continue + v = dl["args"][k] + if isinstance(v, bytes): + v = Web3.to_hex(v).lower() + event_data["args"][k] = v + except Exception as e: + logger.error("An exception occurred while processing log: {}, wlg: {}, e: {}".format(lg, wlg, e)) + return None + else: + return event_data + + +class BaseExtractor(object): + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + pass + + +class RegisterExtractor(BaseExtractor): + + tp0_register = "0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f" + + tp_register_with_token = "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82" + + def __init__(self): + self.address = "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5" + + self.address1 = "0x253553366da8546fc250f225fe3d25d0c782303b" + self.tp0a = "0x69e37f151eb98a09618ddaa80c8cfaf1ce5996867c489f45b555b412271ebf27" + + self.address2 = "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" + self.tpb = "0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9" + + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + if (tp0 == RegisterExtractor.tp0_register) or (tp0 == self.tp0a): + event_data = decode_log(log, contract_object_map, event_map) + tmp = event_data["args"] + ens_middle.expires = convert_str_ts(tmp.get("expires", "")) + ens_middle.name = tmp.get("name") + if "." in ens_middle.name: + # not supported + return None + ens_middle.name = ens_middle.name + ".eth" + ens_middle.label = tmp.get("label").lower() + ens_middle.owner = tmp.get("owner").lower() + ens_middle.base_node = BASE_NODE + ens_middle.node = namehash(ens_middle.name) + ens_middle.event_name = event_data["_event"] + token_id = None + w_token_id = None + for sl in prev_logs: + if sl["address"] == "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" and (sl["topic2"]) == log["topic2"]: + token_id = str(int(sl["topic3"], 16)) + if ( + sl["address"] == "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401" + and sl["topic0"] == "0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62" + ): + evd = decode_log(sl, contract_object_map, event_map) + if evd["args"].get("id"): + w_token_id = str(evd["args"].get("id")) + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + topic0=tp0, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + expires=ens_middle.expires, + name=ens_middle.name, + label=ens_middle.label, + owner=ens_middle.owner, + base_node=ens_middle.base_node, + node=ens_middle.node, + event_name=ens_middle.event_name, + method=ens_middle.method, + token_id=token_id, + w_token_id=w_token_id, + ) + elif address == self.address2 and tp0 == self.tpb: + token_id = int(str(log["topic1"]).lower(), 16) + owner = extract_eth_address(str(log["topic2"]).lower()[2:]) + event_data = decode_log(log, contract_object_map, event_map) + ens_middle.event_name = event_data["_event"] + + node = None + label = None + base_node = None + for log in prev_logs: + if ( + log["address"] == "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e" + and log["topic0"] == RegisterExtractor.tp_register_with_token + ): + base_node = log["topic1"] + label = log["topic2"] + node = compute_node_label(base_node, label) + break + + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + topic0=tp0, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + expires=ens_middle.expires, + name=ens_middle.name, + base_node=base_node, + label=label, + owner=owner, + node=node, + event_name=ens_middle.event_name, + method=ens_middle.method, + token_id=token_id, + ) + else: + return None + + +class NameRenewExtractor(BaseExtractor): + def __init__(self): + self.address = "0x253553366da8546fc250f225fe3d25d0c782303b" + self.tp0 = "0x3da24c024582931cfaf8267d8ed24d13a82a8068d5bd337d30ec45cea4e506ae" + self.address1 = "0x283af0b28c62c092c9727f1ee09c02ca627eb7f5" + + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + if tp0 == self.tp0: + event_data = decode_log(log, contract_object_map, event_map) + + tmp = event_data["args"] + name = tmp.get("name") + if "." in name: + return None + name = name + ".eth" + ens_middle.name = name + ens_middle.node = namehash(name) + ens_middle.label = tmp.get("label").lower() + ens_middle.expires = convert_str_ts(tmp.get("expires", "")) + ens_middle.event_name = event_data["_event"] + + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + topic0=tp0, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + name=ens_middle.name, + node=ens_middle.node, + label=ens_middle.label, + expires=ens_middle.expires, + event_name=ens_middle.event_name, + method=ens_middle.method, + ) + + +class AddressChangedExtractor(BaseExtractor): + def __init__(self): + self.address = "0x231b0ee14048e9dccd1d247744d114a4eb5e8e63" + self.address1 = "0x226159d592e2b063810a10ebf6dcbada94ed68b8" + self.tp0 = "0x65412581168e88a1e60c6459d7f44ae83ad0832e670826c05a4e2476b57af752" + + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + if tp0 == self.tp0: + event_data = decode_log(log, contract_object_map, event_map) + tmp = event_data["args"] + coin_type = tmp["coinType"] + if not coin_type or str(coin_type) != "60": + return None + ens_middle.node = tmp["node"] + ens_middle.address = tmp["newAddress"].lower() + ens_middle.event_name = event_data["_event"] + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + topic0=tp0, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + node=ens_middle.node, + address=ens_middle.address, + event_name=ens_middle.event_name, + method=ens_middle.method, + ) + + return None + + +class NameChangedExtractor(BaseExtractor): + def __init__(self): + self.address = "0x231b0ee14048e9dccd1d247744d114a4eb5e8e63" + self.tp0 = "0xb7d29e911041e8d9b843369e890bcb72c9388692ba48b65ac54e7214c4c348f7" + + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + if tp0 == self.tp0: + event_data = decode_log(log, contract_object_map, event_map) + tmp = event_data["args"] + name = tmp.get("name") or "" + if not name or len(name) - 4 != name.find("."): + # 二级或者非法 + return None + ens_middle.reverse_name = name + ens_middle.address = ens_middle.from_address + ens_middle.node = namehash(name) + ens_middle.reverse_base_node = REVERSE_BASE_NODE + ens_middle.reverse_node = str(log["topic1"]).lower() + ens_middle.event_name = event_data["_event"] + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + topic0=tp0, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + reverse_name=ens_middle.reverse_name, + address=ens_middle.address, + node=ens_middle.node, + reverse_node=ens_middle.reverse_node, + reverse_base_node=REVERSE_BASE_NODE, + event_name=ens_middle.event_name, + method=ens_middle.method, + ) + + +""" +** integrate with ERC1155 & ERC721 transactions ** +""" +# class TransferExtractor(BaseExtractor): +# def __init__(self): +# self.address = "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" +# self.tp0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" +# +# def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSTokenTransferD: +# if address == self.address and tp0 == self.tp0: +# return ENSTokenTransferD( +# transaction_hash=ens_middle.transaction_hash, +# log_index=ens_middle.log_index, +# from_address=log['topic1'], +# to_address=log['topic2'], +# token_id=(int(log['topic3'], 16)), +# token_type='ERC721', +# token_address=log['address'], +# block_number=ens_middle.block_number, +# block_hash=ens_middle.block_hash, +# block_timestamp=ens_middle.block_timestamp, +# ) +# +# class TransferSingle(BaseExtractor): +# def __init__(self): +# self.address = '0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401' +# self.tp0 = '0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62' +# +# def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSTokenTransferD: +# if address == self.address and tp0 == self.tp0: +# event_data = decode_log(log, contract_object_map, event_map) +# return ENSTokenTransferD( +# transaction_hash=ens_middle.transaction_hash, +# log_index=ens_middle.log_index, +# from_address=log['topic2'], +# to_address=log['topic3'], +# token_id=event_data["args"].get("id"), +# token_type='ERC1155', +# token_address=log['address'], +# block_number=ens_middle.block_number, +# block_hash=ens_middle.block_hash, +# block_timestamp=ens_middle.block_timestamp, +# ) + + +def extract_eth_address(input_str): + cleaned_str = input_str.lstrip("0") + if len(cleaned_str) != 40: + raise ValueError("error address length") + + eth_address = "0x" + cleaned_str + return eth_address diff --git a/indexer/modules/custom/hemera_ens/models/__init__.py b/indexer/modules/custom/hemera_ens/models/__init__.py new file mode 100644 index 000000000..2f5625845 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/models/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/9/6 14:56 +# @Author will +# @File __init__.py.py +# @Brief diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py b/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py new file mode 100644 index 000000000..5a767584e --- /dev/null +++ b/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py @@ -0,0 +1,27 @@ +from sqlalchemy import BIGINT, TIMESTAMP, Column, String, func +from sqlalchemy.dialects.postgresql import BYTEA + +from common.models import HemeraModel +from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ens_general_converter + + +class ENSAddress(HemeraModel): + __tablename__ = "af_ens_address_current" + + address = Column(BYTEA, primary_key=True) + name = Column(String) + reverse_node = Column(BYTEA) + block_number = Column(BIGINT) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "ENSAddressD", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_ens_address_current.block_number", + "converter": ens_general_converter, + } + ] diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_event.py b/indexer/modules/custom/hemera_ens/models/af_ens_event.py new file mode 100644 index 000000000..dc20c8266 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/models/af_ens_event.py @@ -0,0 +1,64 @@ +from sqlalchemy import BIGINT, NUMERIC, TIMESTAMP, Column, Index, Integer, PrimaryKeyConstraint, String, func +from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA + +from common.models import HemeraModel +from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ens_general_converter + + +class ENSMiddle(HemeraModel): + __tablename__ = "af_ens_event" + + transaction_hash = Column(BYTEA, primary_key=True) + transaction_index = Column(Integer, nullable=True) + log_index = Column(Integer, primary_key=True) + + block_number = Column(BIGINT) + block_hash = Column(BYTEA) + block_timestamp = Column(TIMESTAMP) + method = Column(String) + event_name = Column(String) + topic0 = Column(String) + from_address = Column(BYTEA) + to_address = Column(BYTEA) + # namehash of .eth + base_node = Column(BYTEA) + # namehash of full_name + node = Column(BYTEA) + # keccak of name + label = Column(BYTEA) + name = Column(String) + + expires = Column(TIMESTAMP) + + owner = Column(BYTEA) + resolver = Column(BYTEA) + registrant = Column(BYTEA) + # resolved address + address = Column(BYTEA) + reverse_base_node = Column(BYTEA) + reverse_node = Column(BYTEA) + reverse_label = Column(BYTEA) + reverse_name = Column(String) + token_id = Column(NUMERIC(100)) + w_token_id = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("transaction_hash", "log_index", name="ens_tnx_log_index"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "ENSMiddleD", + "conflict_do_update": True, + "update_strategy": None, + "converter": ens_general_converter, + } + ] + + +Index("ens_idx_block_number_log_index", ENSMiddle.block_number, ENSMiddle.log_index.desc()) +Index("ens_event_address", ENSMiddle.from_address) diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py new file mode 100644 index 000000000..ddd8d50de --- /dev/null +++ b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py @@ -0,0 +1,99 @@ +from datetime import datetime, timezone +from typing import Type + +from psycopg2._json import Json +from sqlalchemy import Column, Index, String, UniqueConstraint, func +from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, get_column_type +from indexer.domain import Domain + + +def ens_general_converter(table: Type[HemeraModel], data: Domain, is_update=False): + converted_data = {} + for key in data.__dict__.keys(): + if key in table.__table__.c: + column_type = get_column_type(table, key) + if isinstance(column_type, BYTEA) and not isinstance(getattr(data, key), bytes): + if isinstance(getattr(data, key), str): + converted_data[key] = bytes.fromhex(getattr(data, key)[2:]) if getattr(data, key) else None + elif isinstance(getattr(data, key), int): + converted_data[key] = getattr(data, key).to_bytes(32, byteorder="big") + else: + converted_data[key] = None + elif isinstance(column_type, TIMESTAMP): + if isinstance(getattr(data, key), datetime): + converted_data[key] = getattr(data, key) + elif isinstance(getattr(data, key), str): + converted_data[key] = datetime.utcfromtimestamp( + datetime.fromisoformat(getattr(data, key)).timestamp() + ) + else: + converted_data[key] = datetime.utcfromtimestamp(getattr(data, key)) if getattr(data, key) else None + elif isinstance(column_type, ARRAY) and isinstance(column_type.item_type, BYTEA): + converted_data[key] = [bytes.fromhex(address[2:]) for address in getattr(data, key)] + elif isinstance(column_type, JSONB) and getattr(data, key) is not None: + converted_data[key] = Json(getattr(data, key)) + elif isinstance(column_type, String): + converted_data[key] = getattr(data, key).replace("\x00", "") if getattr(data, key) else None + else: + converted_data[key] = getattr(data, key) + + if is_update: + converted_data["update_time"] = datetime.utcfromtimestamp(datetime.now(timezone.utc).timestamp()) + + if "reorg" in table.__table__.columns: + converted_data["reorg"] = False + + return converted_data + + +class ENSRecord(HemeraModel): + __tablename__ = "af_ens_node_current" + + node = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100)) + w_token_id = Column(NUMERIC(100)) + first_owned_by = Column(BYTEA) + name = Column(String) + registration = Column(TIMESTAMP) + expires = Column(TIMESTAMP) + address = Column(BYTEA) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) + + __table_args__ = (UniqueConstraint("node"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "ENSRegisterD", + "conflict_do_update": True, + "update_strategy": None, + "converter": ens_general_converter, + }, + { + "domain": "ENSRegisterTokenD", + "conflict_do_update": True, + "update_strategy": None, + "converter": ens_general_converter, + }, + { + "domain": "ENSNameRenewD", + "conflict_do_update": True, + "update_strategy": None, + "converter": ens_general_converter, + }, + { + "domain": "ENSAddressChangeD", + "conflict_do_update": True, + "update_strategy": None, + "converter": ens_general_converter, + }, + ] + + +Index("ens_idx_address", ENSRecord.address) +Index("ens_idx_name", ENSRecord.name) diff --git a/indexer/modules/custom/hemera_ens/util.py b/indexer/modules/custom/hemera_ens/util.py new file mode 100644 index 000000000..df5b307d7 --- /dev/null +++ b/indexer/modules/custom/hemera_ens/util.py @@ -0,0 +1,19 @@ +from datetime import datetime + + +def convert_str_ts(st): + if not st: + return None + if isinstance(st, int): + return datetime.fromtimestamp(st).strftime("%Y-%m-%d %H:%M:%S") + if st: + try: + dt = datetime.strptime(st, "%Y-%m-%d %H:%M:%S %Z") + except ValueError: + try: + dt = datetime.strptime(st, "%Y-%m-%d %H:%M:%S.%f %Z") + except ValueError: + return None + if dt: + return dt.strftime("%Y-%m-%d %H:%M:%S") + return None diff --git a/indexer/tests/ens/test_namehash.py b/indexer/tests/ens/test_namehash.py new file mode 100644 index 000000000..04eef6138 --- /dev/null +++ b/indexer/tests/ens/test_namehash.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/8/27 15:14 +# @Author will +# @File test_namehash.py +# @Brief +import pytest + +from indexer.modules.custom.hemera_ens import compute_node_label, get_label, namehash + + +@pytest.mark.indexer +@pytest.mark.ens +@pytest.mark.serial +def test_namehash(): + demo_name = "maxga23.eth" + demo_node = "0xb13b15972f7f65be1ed1313293e4f5e5a006c5420cec802d35dc7e88e7bab183" + demo_label = "0x1914fcc93afc2b367d581bbae8d17a775b5852b620c32608dd2bdf5d99e89ab5" + demo_base_node = "93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae" + + name_hash = namehash(demo_name) + assert name_hash == demo_node + label = get_label(demo_name.split(".")[0]) + assert label == demo_label + res = compute_node_label(demo_base_node, demo_label) + assert res == demo_node + print("ok!") + print(get_label("adion")) + print(get_label("vitalik\x00")) diff --git a/migrations/versions/20240831_add_ens.py b/migrations/versions/20240831_add_ens.py new file mode 100644 index 000000000..cd276227d --- /dev/null +++ b/migrations/versions/20240831_add_ens.py @@ -0,0 +1,175 @@ +"""ens + +Revision ID: 43d14640a8ac +Revises: 2359a28d63cb +Create Date: 2024-09-05 11:08:15.501786 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "43d14640a8ac" +down_revision: Union[str, None] = "2359a28d63cb" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "af_ens_address_current", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("name", sa.String(), nullable=True), + sa.Column("reverse_node", postgresql.BYTEA(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("create_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address"), + ) + op.create_table( + "af_ens_event", + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=False), + sa.Column("transaction_index", sa.Integer(), nullable=True), + sa.Column("log_index", sa.Integer(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_timestamp", sa.TIMESTAMP(), nullable=True), + sa.Column("method", sa.String(), nullable=True), + sa.Column("event_name", sa.String(), nullable=True), + sa.Column("topic0", sa.String(), nullable=True), + sa.Column("from_address", postgresql.BYTEA(), nullable=True), + sa.Column("to_address", postgresql.BYTEA(), nullable=True), + sa.Column("base_node", postgresql.BYTEA(), nullable=True), + sa.Column("node", postgresql.BYTEA(), nullable=True), + sa.Column("label", postgresql.BYTEA(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("expires", sa.TIMESTAMP(), nullable=True), + sa.Column("owner", postgresql.BYTEA(), nullable=True), + sa.Column("resolver", postgresql.BYTEA(), nullable=True), + sa.Column("registrant", postgresql.BYTEA(), nullable=True), + sa.Column("address", postgresql.BYTEA(), nullable=True), + sa.Column("reverse_base_node", postgresql.BYTEA(), nullable=True), + sa.Column("reverse_node", postgresql.BYTEA(), nullable=True), + sa.Column("reverse_label", postgresql.BYTEA(), nullable=True), + sa.Column("reverse_name", sa.String(), nullable=True), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=True), + sa.Column("w_token_id", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("transaction_hash", "log_index", name="ens_tnx_log_index"), + ) + op.create_index("ens_event_address", "af_ens_event", ["from_address"], unique=False) + op.create_index( + "ens_idx_block_number_log_index", "af_ens_event", ["block_number", sa.text("log_index DESC")], unique=False + ) + op.create_table( + "af_ens_node_current", + sa.Column("node", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=True), + sa.Column("w_token_id", sa.NUMERIC(precision=100), nullable=True), + sa.Column("first_owned_by", postgresql.BYTEA(), nullable=True), + sa.Column("name", sa.String(), nullable=True), + sa.Column("registration", postgresql.TIMESTAMP(), nullable=True), + sa.Column("expires", postgresql.TIMESTAMP(), nullable=True), + sa.Column("address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("node"), + sa.UniqueConstraint("node"), + ) + op.create_index("ens_idx_address", "af_ens_node_current", ["address"], unique=False) + op.create_index("ens_idx_name", "af_ens_node_current", ["name"], unique=False) + op.create_table( + "af_opensea__transactions", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("is_offer", sa.BOOLEAN(), nullable=False), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("transaction_type", sa.SMALLINT(), nullable=True), + sa.Column("order_hash", postgresql.BYTEA(), nullable=True), + sa.Column("zone", postgresql.BYTEA(), nullable=True), + sa.Column("offer", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("consideration", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("fee", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("address", "is_offer", "block_number", "log_index", "block_hash"), + ) + op.create_table( + "af_opensea_daily_transactions", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("transaction_type", sa.SMALLINT(), nullable=True), + sa.Column("block_date", sa.Date(), nullable=False), + sa.Column("buy_txn_count", sa.INTEGER(), nullable=True), + sa.Column("sell_txn_count", sa.INTEGER(), nullable=True), + sa.Column("swap_txn_count", sa.INTEGER(), nullable=True), + sa.Column("buy_txn_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("sell_txn_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("buy_txn_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("sell_txn_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "block_date"), + ) + op.create_table( + "af_opensea_na_orders", + sa.Column("order_hash", postgresql.BYTEA(), nullable=True), + sa.Column("zone", postgresql.BYTEA(), nullable=True), + sa.Column("offerer", postgresql.BYTEA(), nullable=True), + sa.Column("recipient", postgresql.BYTEA(), nullable=True), + sa.Column("offer", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("consideration", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("block_number", "log_index", "block_hash"), + ) + op.drop_table("token_prices") + op.drop_table("token_hourly_prices") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "token_hourly_prices", + sa.Column("symbol", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column("timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column("price", sa.NUMERIC(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint("symbol", "timestamp", name="token_hourly_prices_pkey"), + ) + op.create_table( + "token_prices", + sa.Column("symbol", sa.VARCHAR(), autoincrement=False, nullable=False), + sa.Column("timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False), + sa.Column("price", sa.NUMERIC(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint("symbol", "timestamp", name="token_prices_pkey"), + ) + op.drop_table("af_opensea_na_orders") + op.drop_table("af_opensea_daily_transactions") + op.drop_table("af_opensea__transactions") + op.drop_index("ens_idx_name", table_name="af_ens_node_current") + op.drop_index("ens_idx_address", table_name="af_ens_node_current") + op.drop_table("af_ens_node_current") + op.drop_index("ens_idx_block_number_log_index", table_name="af_ens_event") + op.drop_index("ens_event_address", table_name="af_ens_event") + op.drop_table("af_ens_event") + op.drop_table("af_ens_address_current") + # ### end Alembic commands ### From f8d55a283a94cbb24c8d58d8b2f15cfbebf63113 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:26:20 +0800 Subject: [PATCH 15/70] import bugfixed (#94) * import bugfixed --- enumeration/entity_type.py | 1 - indexer/tests/ens/test_namehash.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index ce45fc4d2..188fbc3ec 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -52,7 +52,6 @@ class EntityType(IntFlag): ADDRESS_INDEX = 1 << 7 OPEN_SEA = 1 << 8 ENS = 1 << 9 - @staticmethod def combine_all_entity_types(): diff --git a/indexer/tests/ens/test_namehash.py b/indexer/tests/ens/test_namehash.py index 04eef6138..1c7092ad9 100644 --- a/indexer/tests/ens/test_namehash.py +++ b/indexer/tests/ens/test_namehash.py @@ -6,7 +6,7 @@ # @Brief import pytest -from indexer.modules.custom.hemera_ens import compute_node_label, get_label, namehash +from indexer.modules.custom.hemera_ens.ens_hash import compute_node_label, get_label, namehash @pytest.mark.indexer From 8cce62873a468aa38f9e86cb9e8aca810831543e Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:31:17 +0800 Subject: [PATCH 16/70] add feature: calculate deposit token's aggregated values (#83) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add feature: calculate deposit token's aggregated values * complete the data write to db and cache * update cache clean up * add deposit transaction table * set contract name as af_token_deposits_current‘s primary key * add more deposit contract * transaction item bug fix * redesign config structure to make it easier to extend the contract info * add api for check token deposit data * enable reorg * deposit api fill with more info * format deposit amount * make format * make format * make format --- api/app/api.py | 4 +- api/app/db_service/af_token_deposit.py | 77 +++++++ api/app/deposit_to_l2/__init__.py | 6 + api/app/deposit_to_l2/routes.py | 212 ++++++++++++++++++ api/app/utils/parse_utils.py | 49 ++++ api/app/utils/utils.py | 3 +- common/models/__init__.py | 6 +- common/utils/cache_utils.py | 130 +++++++++++ common/utils/web3_utils.py | 104 ++++++++- enumeration/entity_type.py | 19 +- .../modules/custom/deposit_to_l2/__init__.py | 0 .../modules/custom/deposit_to_l2/config.ini | 73 ++++++ .../custom/deposit_to_l2/deposit_parser.py | 183 +++++++++++++++ .../custom/deposit_to_l2/deposit_to_l2_job.py | 205 +++++++++++++++++ .../custom/deposit_to_l2/domain/__init__.py | 0 .../domain/address_token_deposit.py | 14 ++ .../domain/token_deposit_transaction.py | 15 ++ .../custom/deposit_to_l2/models/__init__.py | 0 .../models/af_token_deposits__transactions.py | 39 ++++ .../models/af_token_deposits_current.py | 32 +++ indexer/utils/abi.py | 2 +- ...0240830_add_address_token_deposit_table.py | 91 ++++++++ 22 files changed, 1240 insertions(+), 24 deletions(-) create mode 100644 api/app/db_service/af_token_deposit.py create mode 100644 api/app/deposit_to_l2/__init__.py create mode 100644 api/app/deposit_to_l2/routes.py create mode 100644 api/app/utils/parse_utils.py create mode 100644 common/utils/cache_utils.py create mode 100644 indexer/modules/custom/deposit_to_l2/__init__.py create mode 100644 indexer/modules/custom/deposit_to_l2/config.ini create mode 100644 indexer/modules/custom/deposit_to_l2/deposit_parser.py create mode 100644 indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py create mode 100644 indexer/modules/custom/deposit_to_l2/domain/__init__.py create mode 100644 indexer/modules/custom/deposit_to_l2/domain/address_token_deposit.py create mode 100644 indexer/modules/custom/deposit_to_l2/domain/token_deposit_transaction.py create mode 100644 indexer/modules/custom/deposit_to_l2/models/__init__.py create mode 100644 indexer/modules/custom/deposit_to_l2/models/af_token_deposits__transactions.py create mode 100644 indexer/modules/custom/deposit_to_l2/models/af_token_deposits_current.py create mode 100644 migrations/versions/20240830_add_address_token_deposit_table.py diff --git a/api/app/api.py b/api/app/api.py index becaeaca5..446ac9a4c 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -5,6 +5,7 @@ from api.app.af_ens.routes import af_ens_namespace from api.app.contract.routes import contract_namespace +from api.app.deposit_to_l2.routes import token_deposit_namespace from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace from indexer.modules.custom.opensea.endpoint.routes import opensea_namespace @@ -14,8 +15,9 @@ api = Api() api.add_namespace(explorer_namespace) -api.add_namespace(user_operation_namespace) api.add_namespace(opensea_namespace) api.add_namespace(contract_namespace) +api.add_namespace(token_deposit_namespace) +api.add_namespace(user_operation_namespace) # api.add_namespace(l2_explorer_namespace) api.add_namespace(af_ens_namespace) diff --git a/api/app/db_service/af_token_deposit.py b/api/app/db_service/af_token_deposit.py new file mode 100644 index 000000000..8d07d906c --- /dev/null +++ b/api/app/db_service/af_token_deposit.py @@ -0,0 +1,77 @@ +from api.app.db_service.tokens import get_token_by_address +from common.models import db +from common.utils.db_utils import build_entities +from common.utils.format_utils import row_to_dict +from common.utils.web3_utils import chain_id_name_mapping +from indexer.modules.custom.deposit_to_l2.models.af_token_deposits__transactions import AFTokenDepositsTransactions +from indexer.modules.custom.deposit_to_l2.models.af_token_deposits_current import AFTokenDepositsCurrent + + +def get_transactions_by_condition(filter_condition=None, columns="*", limit=None, offset=None): + entities = build_entities(AFTokenDepositsTransactions, columns) + + statement = db.session.query(AFTokenDepositsTransactions).with_entities(*entities) + + if filter_condition is not None: + statement = statement.filter(filter_condition) + + statement = statement.order_by(AFTokenDepositsTransactions.block_number.desc()) + + if limit is not None: + statement = statement.limit(limit) + + if offset is not None: + statement = statement.offset(offset) + + return statement.all() + + +def get_transactions_cnt_by_condition(filter_condition=None, columns="*"): + entities = build_entities(AFTokenDepositsTransactions, columns) + + count = db.session.query(AFTokenDepositsTransactions).with_entities(*entities).filter(filter_condition).count() + + return count + + +def get_transactions_cnt_by_wallet(wallet_address): + wallet_address = wallet_address.lower() + bytes_wallet_address = bytes.fromhex(wallet_address[2:]) + + count = get_transactions_cnt_by_condition( + filter_condition=AFTokenDepositsTransactions.wallet_address == bytes_wallet_address + ) + + return count + + +def get_deposit_chain_list(wallet_address): + wallet_address = wallet_address.lower() + bytes_wallet_address = bytes.fromhex(wallet_address[2:]) + + chain_list = ( + db.session.query(AFTokenDepositsTransactions.wallet_address, AFTokenDepositsTransactions.chain_id) + .filter(AFTokenDepositsTransactions.wallet_address == bytes_wallet_address) + .group_by(AFTokenDepositsTransactions.wallet_address, AFTokenDepositsTransactions.chain_id) + .all() + ) + + return chain_list + + +def get_deposit_assets_list(wallet_address): + entities = build_entities( + AFTokenDepositsCurrent, ["wallet_address", "chain_id", "contract_address", "token_address", "value"] + ) + + wallet_address = wallet_address.lower() + bytes_wallet_address = bytes.fromhex(wallet_address[2:]) + + assets_list = ( + db.session.query(AFTokenDepositsCurrent) + .with_entities(*entities) + .filter(AFTokenDepositsCurrent.wallet_address == bytes_wallet_address) + .all() + ) + + return assets_list diff --git a/api/app/deposit_to_l2/__init__.py b/api/app/deposit_to_l2/__init__.py new file mode 100644 index 000000000..03026971f --- /dev/null +++ b/api/app/deposit_to_l2/__init__.py @@ -0,0 +1,6 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- + +from flask_restx.namespace import Namespace + +token_deposit_namespace = Namespace("Token Deposit Namespace", path="/", description="Token Deposit API") diff --git a/api/app/deposit_to_l2/routes.py b/api/app/deposit_to_l2/routes.py new file mode 100644 index 000000000..ffca7f5a6 --- /dev/null +++ b/api/app/deposit_to_l2/routes.py @@ -0,0 +1,212 @@ +import flask +from flask_restx import Resource + +from api.app.cache import cache +from api.app.db_service.af_token_deposit import ( + get_deposit_assets_list, + get_deposit_chain_list, + get_transactions_by_condition, + get_transactions_cnt_by_condition, + get_transactions_cnt_by_wallet, +) +from api.app.db_service.blocks import get_block_by_hash +from api.app.deposit_to_l2 import token_deposit_namespace +from api.app.utils.parse_utils import parse_deposit_assets, parse_deposit_transactions +from common.utils.config import get_config +from common.utils.exception_control import APIError +from common.utils.format_utils import row_to_dict +from common.utils.web3_utils import SUPPORT_CHAINS, chain_id_name_mapping +from indexer.modules.custom.deposit_to_l2.models.af_token_deposits__transactions import AFTokenDepositsTransactions + +MAX_TRANSACTION = 500000 +MAX_TRANSACTION_WITH_CONDITION = 10000 + +app_config = get_config() + + +@token_deposit_namespace.route("/v1/explorer/deposit/transactions") +class ExplorerDepositTransactions(Resource): + @cache.cached(timeout=10, query_string=True) + def get(self): + page_index = int(flask.request.args.get("page", 1)) + page_size = int(flask.request.args.get("size", 25)) + if page_index <= 0 or page_size <= 0: + raise APIError("Invalid page or size", code=400) + + if page_index * page_size > MAX_TRANSACTION: + raise APIError(f"Showing the last {MAX_TRANSACTION} records only", code=400) + + wallet_address = flask.request.args.get("wallet_address", None) + chain = flask.request.args.get("chain", None) + contract = flask.request.args.get("contract", None) + token = flask.request.args.get("token", None) + block = flask.request.args.get("block", None) + + has_filter = False + if wallet_address or chain or contract or token or block: + has_filter = True + if page_index * page_size > MAX_TRANSACTION_WITH_CONDITION: + raise APIError( + f"Showing the last {MAX_TRANSACTION_WITH_CONDITION} records only", + code=400, + ) + + filter_condition = True + + if wallet_address: + wallet_address = wallet_address.lower() + bytes_wallet_address = bytes.fromhex(wallet_address[2:]) + filter_condition = AFTokenDepositsTransactions.wallet_address == bytes_wallet_address + + elif chain: + if chain.isnumeric(): + filter_condition = AFTokenDepositsTransactions.chain_id == chain + else: + if chain not in SUPPORT_CHAINS: + raise APIError( + f"{chain} is not supported yet, it will coming soon.", + code=400, + ) + + chain_id = SUPPORT_CHAINS[chain]["chain_id"] + filter_condition = AFTokenDepositsTransactions.chain_id == chain_id + + elif contract: + contract = contract.lower() + bytes_contract = bytes.fromhex(contract[2:]) + filter_condition = AFTokenDepositsTransactions.contract == bytes_contract + + elif token: + token = token.lower() + bytes_token = bytes.fromhex(token[2:]) + filter_condition = AFTokenDepositsTransactions.token == bytes_token + + elif block: + if block.isnumeric(): + filter_condition = AFTokenDepositsTransactions.block_number == int(block) + else: + block_number = get_block_by_hash(hash=block, columns=["number"]) + filter_condition = AFTokenDepositsTransactions.block_number == block_number + + total_records = get_transactions_cnt_by_condition(filter_condition=filter_condition) + transactions = get_transactions_by_condition( + filter_condition=filter_condition, + columns=[ + "transaction_hash", + "wallet_address", + "chain_id", + "contract_address", + "token_address", + "value", + "block_number", + "block_timestamp", + ], + limit=page_size, + offset=(page_index - 1) * page_size, + ) + + transaction_list = parse_deposit_transactions(transactions) + + return { + "data": transaction_list, + "total": total_records, + "max_display": min( + (MAX_TRANSACTION_WITH_CONDITION if has_filter else MAX_TRANSACTION), + total_records, + ), + "page": page_index, + "size": page_size, + }, 200 + + +@token_deposit_namespace.route("/v1/explorer/deposit/statistic") +class ExplorerDepositStatistic(Resource): + + @cache.cached(timeout=10, query_string=True) + def get(self): + wallet_address = flask.request.args.get("wallet_address", None) + + if wallet_address is None: + raise APIError( + f"parameter 'wallet_address' are required", + code=400, + ) + + deposit_times = get_transactions_cnt_by_wallet(wallet_address) + + chains = get_deposit_chain_list(wallet_address) + chain_list = [chain_id_name_mapping[row_to_dict(chain)["chain_id"]] for chain in chains] + + assets = get_deposit_assets_list(wallet_address) + asset_list = parse_deposit_assets(assets) + + return { + "wallet_address": wallet_address, + "deposit_times": deposit_times, + "chain_list": chain_list, + "asset_list": asset_list, + }, 200 + + +@token_deposit_namespace.route("/v1/explorer/deposit/bridge_times") +class ExplorerDepositBridgeTimes(Resource): + + @cache.cached(timeout=10, query_string=True) + def get(self): + wallet_address = flask.request.args.get("wallet_address", None) + + if wallet_address is None: + raise APIError( + f"parameter 'wallet_address' are required", + code=400, + ) + + times = get_transactions_cnt_by_wallet(wallet_address) + + return { + "wallet_address": wallet_address, + "bridge_times": times, + }, 200 + + +@token_deposit_namespace.route("/v1/explorer/deposit/chain_list") +class ExplorerDepositChainList(Resource): + + @cache.cached(timeout=10, query_string=True) + def get(self): + wallet_address = flask.request.args.get("wallet_address", None) + + if wallet_address is None: + raise APIError( + f"parameter 'wallet_address' are required", + code=400, + ) + + chains = get_deposit_chain_list(wallet_address) + + chain_list = [chain_id_name_mapping[row_to_dict(chain)["chain_id"]] for chain in chains] + return { + "wallet_address": wallet_address, + "chain_list": chain_list, + }, 200 + + +@token_deposit_namespace.route("/v1/explorer/deposit/assets_list") +class ExplorerDepositAssetsList(Resource): + + @cache.cached(timeout=10, query_string=True) + def get(self): + wallet_address = flask.request.args.get("wallet_address", None) + + if wallet_address is None: + raise APIError( + f"parameter 'wallet_address' are required", + code=400, + ) + + assets = get_deposit_assets_list(wallet_address) + asset_list = parse_deposit_assets(assets) + return { + "wallet_address": wallet_address, + "asset_list": asset_list, + }, 200 diff --git a/api/app/utils/parse_utils.py b/api/app/utils/parse_utils.py new file mode 100644 index 000000000..295bab207 --- /dev/null +++ b/api/app/utils/parse_utils.py @@ -0,0 +1,49 @@ +from api.app.db_service.tokens import get_token_by_address +from common.utils.format_utils import row_to_dict +from common.utils.web3_utils import chain_id_name_mapping + + +def parse_deposit_assets(assets): + asset_list = [] + for asset in assets: + asset_dict = row_to_dict(asset) + + token_info = get_token_by_address( + asset_dict["token_address"], ["name", "symbol", "decimals", "icon_url", "token_type"] + ) + decimals = token_info.decimals if token_info else 18 + asset_list.append( + { + "chain": chain_id_name_mapping[asset_dict["chain_id"]], + "bridge": asset_dict["contract_address"], + "token": asset_dict["token_address"], + "token_name": token_info.name if token_info else None, + "token_symbol": token_info.symbol if token_info else None, + "token_icon_url": token_info.icon_url if token_info else None, + "token_type": token_info.token_type if token_info else None, + "amount": "{0:.18f}".format(asset_dict["value"] / 10**decimals).rstrip("0").rstrip("."), + } + ) + + return asset_list + + +def parse_deposit_transactions(transactions): + transaction_list = [] + for transaction in transactions: + tx_dict = row_to_dict(transaction) + tx_dict["chain_name"] = chain_id_name_mapping[tx_dict["chain_id"]] + + token_info = get_token_by_address( + tx_dict["token_address"], ["name", "symbol", "decimals", "icon_url", "token_type"] + ) + decimals = token_info.decimals if token_info else 18 + tx_dict["name"] = token_info.name if token_info else None + tx_dict["symbol"] = token_info.symbol if token_info else None + tx_dict["icon_url"] = token_info.icon_url if token_info else None + tx_dict["token_type"] = token_info.token_type if token_info else None + + tx_dict["value"] = "{0:.18f}".format(tx_dict["value"] / 10**decimals).rstrip("0").rstrip(".") + + transaction_list.append(tx_dict) + return transaction_list diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index f2f427988..6c00f28de 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -6,7 +6,6 @@ from datetime import datetime, timedelta from flask import current_app -from sqlalchemy import text from sqlalchemy.sql import text from web3 import Web3 @@ -17,7 +16,7 @@ from common.models import db from common.models.transactions import Transactions from common.utils.config import get_config -from common.utils.format_utils import format_coin_value, format_to_dict, row_to_dict +from common.utils.format_utils import format_coin_value, format_to_dict from common.utils.web3_utils import decode_log_data app_config = get_config() diff --git a/common/models/__init__.py b/common/models/__init__.py index e6fa303cb..b82f70ddc 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -9,11 +9,7 @@ from common.utils.module_loading import import_string, scan_subclass_by_path_patterns from indexer.domain import Domain -model_path_patterns = [ - "common/models", - "indexer/modules/*/models", - "indexer/modules/custom/*/models", -] +model_path_patterns = ["common/models", "indexer/modules/*/models", "indexer/modules/custom/*/models", "api/app/token"] model_path_exclude = ["indexer/modules/custom/address_index/models"] diff --git a/common/utils/cache_utils.py b/common/utils/cache_utils.py new file mode 100644 index 000000000..db4e72f5d --- /dev/null +++ b/common/utils/cache_utils.py @@ -0,0 +1,130 @@ +import asyncio +import bisect +import queue +from dataclasses import dataclass +from threading import Lock, Thread +from typing import Any, Dict, List, Optional + + +class TimeToLiveDict: + def __init__(self, ttl=60): + self._ttl = ttl + self._dict = {} + self._timers = {} + self._lock = Lock() + self._loop = asyncio.get_event_loop() + + def set(self, key, value): + async def async_set(): + if key in self._timers: + self._timers[key].cancel() + + self._dict[key] = value + self._timers[key] = self._loop.create_task(self._delete_after(key, self._ttl)) + + with self._lock: + self._loop.call_soon_threadsafe(lambda: self._loop.create_task(async_set())) + + def get(self, key): + with self._lock: + return self._dict.get(key) + + async def _delete_after(self, key, delay): + await asyncio.sleep(delay) + with self._lock: + if key in self._dict: + del self._dict[key] + del self._timers[key] + + def set_ttl(self, key, ttl): + async def async_set_ttl(): + if key in self._timers: + self._timers[key].cancel() + self._timers[key] = self._loop.create_task(self._delete_after(key, ttl)) + + with self._lock: + self._loop.call_soon_threadsafe(lambda: self._loop.create_task(async_set_ttl())) + + +@dataclass +class ValueWithBlockNumber: + value: Any + block_number: int + + +class BlockToLiveDict: + def __init__(self, retention_blocks: int = 100, cleanup_threshold: int = 100): + self._retention_blocks = retention_blocks + self._cleanup_threshold = cleanup_threshold + self._data: Dict[Any, ValueWithBlockNumber] = {} + self._block_numbers: List[int] = [] + self._block_to_keys: Dict[int, set] = {} + self._current_block: int = 0 + self._lock = Lock() + self._cleanup_task: Optional[asyncio.Task] = None + self._insert_count: int = 0 + self._cleanup_queue = queue.Queue() + self._cleanup_thread = Thread(target=self._cleanup_loop, daemon=True) + self._cleanup_thread.start() + + def set(self, key: Any, value: Any): + if not hasattr(value, "block_number"): + raise AttributeError(f"'{key}' object has no attribute 'block_number'") + + with self._lock: + if key in self._data: + old_block_number = self._data[key].block_number + if old_block_number == value.block_number: + self._data[key].value = value + return + + self._block_to_keys[old_block_number].remove(key) + if not self._block_to_keys[old_block_number]: + del self._block_to_keys[old_block_number] + self._block_numbers.remove(old_block_number) + + self._data[key] = ValueWithBlockNumber(value, value.block_number) + + if value.block_number not in self._block_to_keys: + self._block_to_keys[value.block_number] = set() + bisect.insort(self._block_numbers, value.block_number) + self._block_to_keys[value.block_number].add(key) + + self._current_block = max(self._current_block, value.block_number) + self._insert_count += 1 + + if self._insert_count >= self._cleanup_threshold: + self._cleanup_queue.put(True) + self._insert_count = 0 + + def get(self, key: Any) -> Optional[Any]: + data = self._data.get(key) + return data.value if data else None + + def _cleanup(self): + with self._lock: + cleanup_threshold = self._current_block - self._retention_blocks + cleanup_index = bisect.bisect_right(self._block_numbers, cleanup_threshold) + + if cleanup_index > 0: + blocks_to_remove = self._block_numbers[:cleanup_index] + self._block_numbers = self._block_numbers[cleanup_index:] + + for block in blocks_to_remove: + keys_to_remove = self._block_to_keys.pop(block, set()) + for key in keys_to_remove: + del self._data[key] + + def _cleanup_loop(self): + while True: + self._cleanup_queue.get() # 等待清理信号 + self._cleanup() + self._cleanup_queue.task_done() + + def get_current_block(self) -> int: + return self._current_block + + def __del__(self): + if hasattr(self, "_cleanup_thread"): + self._cleanup_queue.put(None) # 发送退出信号 + self._cleanup_thread.join(timeout=1) diff --git a/common/utils/web3_utils.py b/common/utils/web3_utils.py index b46c04e3e..8e2f8c297 100644 --- a/common/utils/web3_utils.py +++ b/common/utils/web3_utils.py @@ -13,6 +13,99 @@ from web3.middleware import geth_poa_middleware from web3.types import ABIFunction +SUPPORT_CHAINS = { + "ethereum": { + "display_name": "Ethereum", + "rpc": "https://cloudflare-eth.com", + "etherscan_address_link": "https://etherscan.io/address/", + "explorer_transaction_link": "https://etherscan.io/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "ETH", + "chain_id": 1, + "coin": { + "symbol": "ETH", + "id": 1027, + }, + }, + "arbitrum": { + "display_name": "Arbitrum", + "rpc": "https://arb1.arbitrum.io/rpc", + "etherscan_address_link": "https://arbiscan.io/address/", + "explorer_transaction_link": "https://arbiscan.io/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "ETH", + "chain_id": 42161, + "coin": { + "symbol": "ETH", + "id": 1027, + }, + }, + "optimism": { + "display_name": "Optimism", + "rpc": "https://mainnet.optimism.io", + "etherscan_address_link": "https://optimistic.etherscan.io/address/", + "explorer_transaction_link": "https://optimistic.etherscan.io/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "ETH", + "chain_id": 10, + "coin": { + "symbol": "ETH", + "id": 1027, + }, + }, + "base": { + "display_name": "Base", + "rpc": "https://mainnet.base.org", + "etherscan_address_link": "https://basescan.org/address/", + "explorer_transaction_link": "https://basescan.org/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "ETH", + "chain_id": 8453, + "coin": { + "symbol": "ETH", + "id": 1027, + }, + }, + "linea": { + "display_name": "Linea", + "rpc": "https://rpc.linea.build", + "etherscan_address_link": "https://lineascan.build/address/", + "explorer_transaction_link": "https://lineascan.build/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "ETH", + "chain_id": 59144, + "coin": { + "symbol": "ETH", + "id": 1027, + }, + }, + "mantle": { + "display_name": "Mantle", + "rpc": "https://rpc.mantle.xyz", + "etherscan_address_link": "https://explorer.mantle.xyz/address/", + "explorer_transaction_link": "https://explorer.mantle.xyz/tx/", + "debank_address_link": "https://debank.com/profile/", + "token_name": "MNT", + "chain_id": 5000, + "coin": { + "symbol": "MNT", + "id": 27075, + }, + }, +} + +chain_id_name_mapping = {SUPPORT_CHAINS[chain_name]["chain_id"]: chain_name for chain_name in SUPPORT_CHAINS.keys()} + +ERC20_ABI = """ + [{ + "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], + "name": "balanceOf", + "outputs": [{"internalType": "uint256", "name": "balance", "type": "uint256"}], + "stateMutability": "view", + "type": "function" + }] +""" + ERC721_ABI = [ { "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], @@ -163,17 +256,6 @@ def add_trace_to_tree(node, trace, path): return prune_delegates(root) -ERC20ABI = """ - [{ - "inputs": [{"internalType": "address", "name": "owner", "type": "address"}], - "name": "balanceOf", - "outputs": [{"internalType": "uint256", "name": "balance", "type": "uint256"}], - "stateMutability": "view", - "type": "function" - }] -""" - - def generate_type_str(component): if component["type"] == "tuple[]": tuple_types = tuple(map(lambda x: generate_type_str(x), component["components"])) diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index 188fbc3ec..4683ed2fc 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -22,6 +22,8 @@ AllFeatureValueRecordUniswapV3Token, ) from indexer.modules.custom.blue_chip.domain.feature_blue_chip import BlueChipHolder +from indexer.modules.custom.deposit_to_l2.domain.address_token_deposit import AddressTokenDeposit +from indexer.modules.custom.deposit_to_l2.domain.token_deposit_transaction import TokenDepositTransaction from indexer.modules.custom.hemera_ens.ens_domain import ( ENSAddressChangeD, ENSAddressD, @@ -41,17 +43,22 @@ class EntityType(IntFlag): EXPLORER_TRACE = 1 << 2 BRIDGE = 1 << 3 + UNISWAP_V3 = 1 << 4 USER_OPS = 1 << 5 BLUE_CHIP = 1 << 6 - EXPLORER = EXPLORER_BASE | EXPLORER_TOKEN | EXPLORER_TRACE - ADDRESS_INDEX = 1 << 7 - OPEN_SEA = 1 << 8 - ENS = 1 << 9 + + DEPOSIT_TO_L2 = 1 << 8 + + OPEN_SEA = 1 << 9 + + ENS = 1 << 10 + + EXPLORER = EXPLORER_BASE | EXPLORER_TOKEN | EXPLORER_TRACE @staticmethod def combine_all_entity_types(): @@ -145,6 +152,10 @@ def generate_output_types(entity_types): yield AllFeatureValueRecordBlueChipHolders yield BlueChipHolder + if entity_types & EntityType.DEPOSIT_TO_L2: + yield TokenDepositTransaction + yield AddressTokenDeposit + if entity_types & EntityType.ENS: yield ENSMiddleD yield ENSRegisterD diff --git a/indexer/modules/custom/deposit_to_l2/__init__.py b/indexer/modules/custom/deposit_to_l2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/deposit_to_l2/config.ini b/indexer/modules/custom/deposit_to_l2/config.ini new file mode 100644 index 000000000..95250a3a7 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/config.ini @@ -0,0 +1,73 @@ +[chain_deposit_info] +contract_info= + { + "8453":[{ + "contract": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35", + "ABIFunction": [ + { + "token": "eth", + "json": {"inputs":[{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositETH","outputs":[],"stateMutability":"payable","type":"function"} + }, + { + "token": "eth", + "json":{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"bridgeETHTo","outputs":[],"stateMutability":"payable","type":"function"} + }] + }], + "10":[{ + "contract": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1", + "ABIFunction": [ + { + "token": "eth", + "json": {"inputs":[{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositETH","outputs":[],"stateMutability":"payable","type":"function"} + }, + { + "token": "eth", + "json":{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"bridgeETHTo","outputs":[],"stateMutability":"payable","type":"function"} + }, + { + "token": "_l1Token", + "json": {"inputs":[{"internalType":"address","name":"_l1Token","type":"address"},{"internalType":"address","name":"_l2Token","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositERC20To","outputs":[],"stateMutability":"nonpayable","type":"function"} + }] + }], + "42161":[{ + "contract": "0x72Ce9c846789fdB6fC1f34aC4AD25Dd9ef7031ef", + "ABIFunction": [{ + "token": "_l1Token", + "json": {"inputs":[{"name":"_l1Token","type":"address","internalType":"address"},{"name":"_to","type":"address","internalType":"address"},{"name":"_amount","type":"uint256","internalType":"uint256"},{"name":"_maxGas","type":"uint256","internalType":"uint256"},{"name":"_gasPriceBid","type":"uint256","internalType":"uint256"},{"name":"_data","type":"bytes","internalType":"bytes"}],"name":"outboundTransfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"} + }] + }, + { + "contract": "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", + "ABIFunction": [{ + "token": "eth", + "method_id": "0x439370b1" + }] + }], + "59144":[{ + "contract": "0xd19d4B5d358258f05D7B411E21A1460D11B0876F", + "ABIFunction": [{ + "token": "eth", + "json": {"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"sendMessage","outputs":[],"stateMutability":"payable","type":"function"} + }] + }, + { + "contract": "0x051F1D88f0aF5763fB888eC4378b4D8B29ea3319", + "ABIFunction": [{ + "token": "_l1Token", + "json": {"inputs":[{"internalType":"address","name":"_l1Token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"bridgeToken","outputs":[],"stateMutability":"payable","type":"function"} + }] + }, + { + "contract": "0x504A330327A089d8364C4ab3811Ee26976d388ce", + "ABIFunction": [{ + "token": "usdc", + "json": {"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"} + }] + }] + } + + +[cache_config] +#blocks or durations +clean_mode = blocks +clean_limit_value = 1000 diff --git a/indexer/modules/custom/deposit_to_l2/deposit_parser.py b/indexer/modules/custom/deposit_to_l2/deposit_parser.py new file mode 100644 index 000000000..94ac16cc1 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/deposit_parser.py @@ -0,0 +1,183 @@ +import json +from typing import List, cast + +from eth_typing import HexStr +from web3._utils.contracts import decode_transaction_data +from web3.types import ABIEvent, ABIFunction + +from indexer.domain.transaction import Transaction +from indexer.modules.custom.deposit_to_l2.domain.token_deposit_transaction import TokenDepositTransaction +from indexer.utils.abi import event_log_abi_to_topic, function_abi_to_4byte_selector_str + +ETH_ADDRESS = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" +USDC_ADDRESS = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" + +ETH_DEPOSIT_INITIATED_EVENT = cast( + ABIEvent, + json.loads( + """{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"ETHDepositInitiated","type":"event"}""" + ), +) +ETH_DEPOSIT_INITIATED_EVENT_SIG = event_log_abi_to_topic(ETH_DEPOSIT_INITIATED_EVENT) + +ETH_BRIDGE_INITIATED_EVENT = cast( + ABIEvent, + json.loads( + """{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"extraData","type":"bytes"}],"name":"ETHBridgeInitiated","type":"event"}""" + ), +) +ETH_BRIDGE_INITIATED_EVENT_SIG = event_log_abi_to_topic(ETH_BRIDGE_INITIATED_EVENT) + +TRANSACTION_DEPOSITED_EVENT = cast( + ABIEvent, + json.loads( + """{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"opaqueData","type":"bytes"}],"name":"TransactionDeposited","type":"event"}""" + ), +) +TRANSACTION_DEPOSITED_EVENT_SIG = event_log_abi_to_topic(TRANSACTION_DEPOSITED_EVENT) + +DEPOSIT_ETH_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositETH","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +DEPOSIT_ETH_FUNCTION_SIG = function_abi_to_4byte_selector_str(DEPOSIT_ETH_FUNCTION) + +BRIDGE_ETH_TO_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"bridgeETHTo","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +BRIDGE_ETH_TO_FUNCTION_SIG = function_abi_to_4byte_selector_str(BRIDGE_ETH_TO_FUNCTION) + +DEPOSIT_ERC20_TO_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"address","name":"_l1Token","type":"address"},{"internalType":"address","name":"_l2Token","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint32","name":"_minGasLimit","type":"uint32"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositERC20To","outputs":[],"stateMutability":"nonpayable","type":"function"}""" + ), +) +DEPOSIT_ERC20_TO_FUNCTION_SIG = function_abi_to_4byte_selector_str(DEPOSIT_ERC20_TO_FUNCTION) + +DEPOSIT_TRANSACTION_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint64","name":"_gasLimit","type":"uint64"},{"internalType":"bool","name":"_isCreation","type":"bool"},{"internalType":"bytes","name":"_extraData","type":"bytes"}],"name":"depositTransaction","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +DEPOSIT_TRANSACTION_FUNCTION_SIG = function_abi_to_4byte_selector_str(DEPOSIT_TRANSACTION_FUNCTION) + +DEPOSIT_ETH_TO_ARBI_FUNCTION_SIG = "0x439370b1" + +OUT_BOUND_TRANSFER_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"name":"_l1Token","type":"address","internalType":"address"},{"name":"_to","type":"address","internalType":"address"},{"name":"_amount","type":"uint256","internalType":"uint256"},{"name":"_maxGas","type":"uint256","internalType":"uint256"},{"name":"_gasPriceBid","type":"uint256","internalType":"uint256"},{"name":"_data","type":"bytes","internalType":"bytes"}],"name":"outboundTransfer","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}""" + ), +) +OUT_BOUND_TRANSFER_FUNCTION_SIG = function_abi_to_4byte_selector_str(OUT_BOUND_TRANSFER_FUNCTION) + +DEPOSIT_ETH_TO_LINEA_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_fee","type":"uint256"},{"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"sendMessage","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +DEPOSIT_ETH_TO_LINEA_FUNCTION_SIG = function_abi_to_4byte_selector_str(DEPOSIT_ETH_TO_LINEA_FUNCTION) + +BRIDGE_TOKEN_TO_LINEA_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"address","name":"_l1Token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_recipient","type":"address"}],"name":"bridgeToken","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +BRIDGE_TOKEN_TO_LINEA_FUNCTION_SIG = function_abi_to_4byte_selector_str(BRIDGE_TOKEN_TO_LINEA_FUNCTION) + +DEPOSIT_USDC_TO_LINEA_FUNCTION = cast( + ABIFunction, + json.loads( + """{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"_to","type":"address"}],"name":"depositTo","outputs":[],"stateMutability":"payable","type":"function"}""" + ), +) +DEPOSIT_USDC_TO_LINEA_FUNCTION_SIG = function_abi_to_4byte_selector_str(DEPOSIT_USDC_TO_LINEA_FUNCTION) + + +def eth_deposit_parse(transaction: Transaction, chain_mapping: dict, function: ABIFunction) -> TokenDepositTransaction: + return TokenDepositTransaction( + transaction_hash=transaction.hash, + wallet_address=transaction.from_address, + chain_id=chain_mapping[transaction.to_address], + contract_address=transaction.to_address, + token_address=ETH_ADDRESS, + value=transaction.value, + block_number=transaction.block_number, + block_timestamp=transaction.block_timestamp, + ) + + +def usdc_deposit_parse(transaction: Transaction, chain_mapping: dict, function: ABIFunction) -> TokenDepositTransaction: + decoded_input = decode_transaction_data(function, HexStr(transaction.input)) + return TokenDepositTransaction( + transaction_hash=transaction.hash, + wallet_address=transaction.from_address, + chain_id=chain_mapping[transaction.to_address], + contract_address=transaction.to_address, + token_address=USDC_ADDRESS, + value=decoded_input["_amount"], + block_number=transaction.block_number, + block_timestamp=transaction.block_timestamp, + ) + + +def token_deposit_parse( + transaction: Transaction, chain_mapping: dict, function: ABIFunction +) -> TokenDepositTransaction: + decoded_input = decode_transaction_data(function, HexStr(transaction.input)) + return TokenDepositTransaction( + transaction_hash=transaction.hash, + wallet_address=transaction.from_address, + chain_id=chain_mapping[transaction.to_address], + contract_address=transaction.to_address, + token_address=decoded_input["_l1Token"], + value=decoded_input["_amount"], + block_number=transaction.block_number, + block_timestamp=transaction.block_timestamp, + ) + + +token_parse_mapping = { + "eth": eth_deposit_parse, + "usdc": usdc_deposit_parse, + "_l1Token": token_deposit_parse, +} + + +def parse_deposit_transaction_function( + transactions: List[Transaction], + contract_set: set, + chain_mapping: dict, + sig_function_mapping: dict, + sig_parse_mapping: dict, +) -> List[TokenDepositTransaction]: + deposit_tokens = [] + for transaction in transactions: + if transaction.to_address in contract_set: + input_sig = transaction.input[0:10] + if input_sig in sig_function_mapping: + deposit_transaction = sig_parse_mapping[input_sig]( + transaction=transaction, + chain_mapping=chain_mapping, + function=sig_function_mapping[input_sig], + ) + deposit_tokens.append(deposit_transaction) + return deposit_tokens + + +if __name__ == "__main__": + print(BRIDGE_ETH_TO_FUNCTION_SIG) + print(DEPOSIT_ERC20_TO_FUNCTION_SIG) + print(DEPOSIT_ETH_FUNCTION_SIG) + print(DEPOSIT_ETH_TO_LINEA_FUNCTION_SIG) + print(BRIDGE_TOKEN_TO_LINEA_FUNCTION_SIG) + print(DEPOSIT_USDC_TO_LINEA_FUNCTION_SIG) diff --git a/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py b/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py new file mode 100644 index 000000000..ede1c3478 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py @@ -0,0 +1,205 @@ +import configparser +import json +import os +from typing import List, cast + +from eth_utils import to_normalized_address +from sqlalchemy import and_ +from web3.types import ABIFunction + +from common.utils.cache_utils import BlockToLiveDict, TimeToLiveDict +from common.utils.exception_control import FastShutdownError +from indexer.domain.block import Block +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom.deposit_to_l2.deposit_parser import parse_deposit_transaction_function, token_parse_mapping +from indexer.modules.custom.deposit_to_l2.domain.address_token_deposit import AddressTokenDeposit +from indexer.modules.custom.deposit_to_l2.domain.token_deposit_transaction import TokenDepositTransaction +from indexer.modules.custom.deposit_to_l2.models.af_token_deposits_current import AFTokenDepositsCurrent +from indexer.specification.specification import ToAddressSpecification, TransactionFilterByTransactionInfo +from indexer.utils.abi import function_abi_to_4byte_selector_str +from indexer.utils.utils import distinct_collections_by_group + + +class DepositToL2Job(FilterTransactionDataJob): + dependency_types = [Block] + output_types = [TokenDepositTransaction, AddressTokenDeposit] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + self._service = kwargs["config"].get("db_service", None) + self._contracts = set() + self._contract_chain_mapping = {} + self._sig_function_mapping = {} + self._sig_parse_mapping = {} + + self._load_config("config.ini") + + if self._service is None: + raise FastShutdownError("-pg or --postgres-url is required to run DepositToL2Job") + + if self._clean_mode == "blocks": + self.cache = BlockToLiveDict( + retention_blocks=self._clean_limit_value, cleanup_threshold=self._clean_limit_value * 2 + ) + else: + self.cache = TimeToLiveDict(ttl=self._clean_limit_value) + + self._filter = TransactionFilterByTransactionInfo( + *[ToAddressSpecification(address=contract) for contract in self._contracts] + ) + + def _load_config(self, filename): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + + try: + self._deposit_contracts = json.loads(config.get("chain_deposit_info", "contract_info")) + self._clean_mode = config.get("cache_config", "clean_mode") + self._clean_limit_value = int(config.get("cache_config", "clean_limit_value")) + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + for chain in self._deposit_contracts.keys(): + for contract_info in self._deposit_contracts[chain]: + contract_address = to_normalized_address(contract_info["contract"]) + self._contracts.add(contract_address) + self._contract_chain_mapping[contract_address] = int(chain) + for function in contract_info["ABIFunction"]: + abi_function = None + if "json" in function: + abi_function = cast(ABIFunction, function["json"]) + sig = function_abi_to_4byte_selector_str(abi_function) + else: + sig = function["method_id"] + + self._sig_function_mapping[sig] = abi_function + self._sig_parse_mapping[sig] = token_parse_mapping[function["token"]] + + def get_filter(self): + return self._filter + + def _process(self, **kwargs): + transactions = list( + filter( + self._filter.get_or_specification().is_satisfied_by, + [transaction for block in self._data_buff[Block.type()] for transaction in block.transactions], + ) + ) + deposit_tokens = parse_deposit_transaction_function( + transactions=transactions, + contract_set=self._contracts, + chain_mapping=self._contract_chain_mapping, + sig_function_mapping=self._sig_function_mapping, + sig_parse_mapping=self._sig_parse_mapping, + ) + self._collect_items(TokenDepositTransaction.type(), deposit_tokens) + + if not self._reorg: + for deposit in pre_aggregate_deposit_in_same_block(deposit_tokens): + cache_key = (deposit.wallet_address, deposit.chain_id, deposit.contract_address, deposit.token_address) + cache_value = self.cache.get(cache_key) + if cache_value and cache_value.block_number < deposit.block_number: + # add and save 2 cache + token_deposit = AddressTokenDeposit( + wallet_address=deposit.wallet_address, + chain_id=deposit.chain_id, + contract_address=deposit.contract_address, + token_address=deposit.token_address, + value=deposit.value + cache_value.value, + block_number=deposit.block_number, + block_timestamp=deposit.block_timestamp, + ) + + self.cache.set(cache_key, token_deposit) + self._collect_item(AddressTokenDeposit.type(), token_deposit) + + elif cache_value is None: + # check from db and save 2 cache + history_deposit = self.check_history_deposit_from_db( + deposit.wallet_address, deposit.chain_id, deposit.token_address + ) + if history_deposit is None or history_deposit.block_number < deposit.block_number: + token_deposit = AddressTokenDeposit( + wallet_address=deposit.wallet_address, + chain_id=deposit.chain_id, + contract_address=deposit.contract_address, + token_address=deposit.token_address, + value=deposit.value + history_deposit.value if history_deposit else deposit.value, + block_number=deposit.block_number, + block_timestamp=deposit.block_timestamp, + ) + self.cache.set(cache_key, token_deposit) + self._collect_item(AddressTokenDeposit.type(), token_deposit) + + self._data_buff[AddressTokenDeposit.type()] = distinct_collections_by_group( + collections=self._data_buff[AddressTokenDeposit.type()], + group_by=["wallet_address", "chain_id", "contract_address", "token_address"], + max_key="block_number", + ) + + def check_history_deposit_from_db( + self, wallet_address: str, chain_id: int, token_address: str + ) -> AddressTokenDeposit: + session = self._service.get_service_session() + try: + history_deposit = ( + session.query(AFTokenDepositsCurrent) + .filter( + and_( + AFTokenDepositsCurrent.wallet_address == bytes.fromhex(wallet_address[2:]), + AFTokenDepositsCurrent.chain_id == chain_id, + AFTokenDepositsCurrent.token_address == bytes.fromhex(token_address[2:]), + ) + ) + .first() + ) + finally: + session.close() + + deposit = ( + AddressTokenDeposit( + wallet_address=history_deposit.wallet_address, + chain_id=history_deposit.chain_id, + contract_address=history_deposit.contract_address, + token_address=history_deposit.token_address, + value=history_deposit.value, + block_number=history_deposit.block_number, + block_timestamp=history_deposit.block_timestamp, + ) + if history_deposit + else None + ) + + return deposit + + +def pre_aggregate_deposit_in_same_block(deposit_events: List[TokenDepositTransaction]) -> List[TokenDepositTransaction]: + aggregated_deposit_events = {} + for event in deposit_events: + key = (event.wallet_address, event.chain_id, event.contract_address, event.token_address, event.block_number) + if key in aggregated_deposit_events: + earlier_event = aggregated_deposit_events[key] + value = earlier_event.value + event.value + else: + value = event.value + + aggregated_deposit_events[key] = TokenDepositTransaction( + transaction_hash="merged_transaction", + wallet_address=event.wallet_address, + chain_id=event.chain_id, + contract_address=event.contract_address, + token_address=event.token_address, + value=value, + block_number=event.block_number, + block_timestamp=event.block_timestamp, + ) + + return [event for event in aggregated_deposit_events.values()] + + +if __name__ == "__main__": + pass diff --git a/indexer/modules/custom/deposit_to_l2/domain/__init__.py b/indexer/modules/custom/deposit_to_l2/domain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/deposit_to_l2/domain/address_token_deposit.py b/indexer/modules/custom/deposit_to_l2/domain/address_token_deposit.py new file mode 100644 index 000000000..ad7574a47 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/domain/address_token_deposit.py @@ -0,0 +1,14 @@ +from dataclasses import dataclass + +from indexer.domain import FilterData + + +@dataclass +class AddressTokenDeposit(FilterData): + wallet_address: str + chain_id: int + contract_address: str + token_address: str + value: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/deposit_to_l2/domain/token_deposit_transaction.py b/indexer/modules/custom/deposit_to_l2/domain/token_deposit_transaction.py new file mode 100644 index 000000000..fc61bd612 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/domain/token_deposit_transaction.py @@ -0,0 +1,15 @@ +from dataclasses import dataclass + +from indexer.domain import FilterData + + +@dataclass +class TokenDepositTransaction(FilterData): + transaction_hash: str + wallet_address: str + chain_id: int + contract_address: str + token_address: str + value: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/deposit_to_l2/models/__init__.py b/indexer/modules/custom/deposit_to_l2/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/deposit_to_l2/models/af_token_deposits__transactions.py b/indexer/modules/custom/deposit_to_l2/models/af_token_deposits__transactions.py new file mode 100644 index 000000000..ba42865c3 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/models/af_token_deposits__transactions.py @@ -0,0 +1,39 @@ +from sqlalchemy import Column, Index, desc, func, text +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class AFTokenDepositsTransactions(HemeraModel): + __tablename__ = "af_token_deposits__transactions" + transaction_hash = Column(BYTEA, primary_key=True) + wallet_address = Column(BYTEA) + chain_id = Column(BIGINT) + contract_address = Column(BYTEA) + token_address = Column(BYTEA) + value = Column(NUMERIC(100)) + + block_number = Column(BIGINT) + block_timestamp = Column(TIMESTAMP) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, server_default=text("False")) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "TokenDepositTransaction", + "conflict_do_update": False, + "update_strategy": None, + "converter": general_converter, + } + ] + + +Index("af_deposits_transactions_wallet_address_index", AFTokenDepositsTransactions.wallet_address) +Index("af_deposits_transactions_chain_id_index", AFTokenDepositsTransactions.chain_id) +Index("af_deposits_transactions_contract_address_index", AFTokenDepositsTransactions.contract_address) +Index("af_deposits_transactions_token_address_index", AFTokenDepositsTransactions.token_address) +Index("af_deposits_transactions_block_number_index", desc(AFTokenDepositsTransactions.block_number)) diff --git a/indexer/modules/custom/deposit_to_l2/models/af_token_deposits_current.py b/indexer/modules/custom/deposit_to_l2/models/af_token_deposits_current.py new file mode 100644 index 000000000..69df046b4 --- /dev/null +++ b/indexer/modules/custom/deposit_to_l2/models/af_token_deposits_current.py @@ -0,0 +1,32 @@ +from sqlalchemy import Column, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class AFTokenDepositsCurrent(HemeraModel): + __tablename__ = "af_token_deposits_current" + wallet_address = Column(BYTEA, primary_key=True) + chain_id = Column(BIGINT, primary_key=True) + contract_address = Column(BYTEA, primary_key=True) + token_address = Column(BYTEA, primary_key=True) + value = Column(NUMERIC(100)) + + block_number = Column(BIGINT) + block_timestamp = Column(TIMESTAMP) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("wallet_address", "token_address", "contract_address", "chain_id"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "AddressTokenDeposit", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_token_deposits_current.block_number", + "converter": general_converter, + } + ] diff --git a/indexer/utils/abi.py b/indexer/utils/abi.py index 229e4ba81..efdaa819e 100644 --- a/indexer/utils/abi.py +++ b/indexer/utils/abi.py @@ -1,6 +1,5 @@ import logging from typing import Any, Dict, Optional, Sequence, Tuple -from urllib.parse import to_bytes import eth_abi from eth_abi.codec import ABICodec @@ -13,6 +12,7 @@ hexstr_if_str, is_binary_address, text_if_str, + to_bytes, to_hex, to_text, ) diff --git a/migrations/versions/20240830_add_address_token_deposit_table.py b/migrations/versions/20240830_add_address_token_deposit_table.py new file mode 100644 index 000000000..4e143f638 --- /dev/null +++ b/migrations/versions/20240830_add_address_token_deposit_table.py @@ -0,0 +1,91 @@ +"""add address_token_deposit table + +Revision ID: 6c2eecd6316b +Revises: 2359a28d63cb +Create Date: 2024-08-30 15:09:43.357835 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "6c2eecd6316b" +down_revision: Union[str, None] = "2359a28d63cb" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "af_token_deposits__transactions", + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=True), + sa.Column("chain_id", sa.BIGINT(), nullable=True), + sa.Column("contract_address", postgresql.BYTEA(), nullable=True), + sa.Column("token_address", postgresql.BYTEA(), nullable=True), + sa.Column("value", sa.NUMERIC(precision=100), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("transaction_hash"), + ) + op.create_index( + "af_deposits_transactions_block_number_index", + "af_token_deposits__transactions", + [sa.text("block_number DESC")], + unique=False, + ) + op.create_index( + "af_deposits_transactions_chain_id_index", "af_token_deposits__transactions", ["chain_id"], unique=False + ) + op.create_index( + "af_deposits_transactions_contract_address_index", + "af_token_deposits__transactions", + ["contract_address"], + unique=False, + ) + op.create_index( + "af_deposits_transactions_token_address_index", + "af_token_deposits__transactions", + ["token_address"], + unique=False, + ) + op.create_index( + "af_deposits_transactions_wallet_address_index", + "af_token_deposits__transactions", + ["wallet_address"], + unique=False, + ) + op.create_table( + "af_token_deposits_current", + sa.Column("wallet_address", postgresql.BYTEA(), nullable=False), + sa.Column("chain_id", sa.BIGINT(), nullable=False), + sa.Column("contract_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_address", postgresql.BYTEA(), nullable=False), + sa.Column("value", sa.NUMERIC(precision=100), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("wallet_address", "token_address", "contract_address", "chain_id"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("af_deposits_transactions_wallet_address_index", table_name="af_token_deposits__transactions") + op.drop_index("af_deposits_transactions_token_address_index", table_name="af_token_deposits__transactions") + op.drop_index("af_deposits_transactions_contract_address_index", table_name="af_token_deposits__transactions") + op.drop_index("af_deposits_transactions_chain_id_index", table_name="af_token_deposits__transactions") + op.drop_index("af_deposits_transactions_block_number_index", table_name="af_token_deposits__transactions") + op.drop_table("af_token_deposits_current") + op.drop_table("af_token_deposits__transactions") + # ### end Alembic commands ### From 7fcdbe227ebbeeeff6133ea29786f2de8e917196 Mon Sep 17 00:00:00 2001 From: li xiang Date: Fri, 6 Sep 2024 15:32:43 +0800 Subject: [PATCH 17/70] Move token price model to common (#95) --- api/app/explorer/routes.py | 2 +- .../token_prices.py => utils/token_utils.py} | 26 ++++--------------- api/app/utils/utils.py | 2 +- common/models/token_hourly_price.py | 9 +++++++ common/models/token_prices.py | 9 +++++++ .../modules/custom/opensea/endpoint/routes.py | 2 +- 6 files changed, 26 insertions(+), 24 deletions(-) rename api/app/{token/token_prices.py => utils/token_utils.py} (50%) create mode 100644 common/models/token_hourly_price.py create mode 100644 common/models/token_prices.py diff --git a/api/app/explorer/routes.py b/api/app/explorer/routes.py index ce6971533..f0dfa1160 100644 --- a/api/app/explorer/routes.py +++ b/api/app/explorer/routes.py @@ -53,7 +53,7 @@ ) from api.app.db_service.wallet_addresses import get_address_display_mapping, get_ens_mapping from api.app.explorer import explorer_namespace -from api.app.token.token_prices import get_token_price +from api.app.utils.token_utils import get_token_price from api.app.utils.utils import ( fill_address_display_to_transactions, fill_is_contract_to_transactions, diff --git a/api/app/token/token_prices.py b/api/app/utils/token_utils.py similarity index 50% rename from api/app/token/token_prices.py rename to api/app/utils/token_utils.py index f75532b0f..e0f7e44da 100644 --- a/api/app/token/token_prices.py +++ b/api/app/utils/token_utils.py @@ -1,32 +1,16 @@ from decimal import Decimal -from sqlalchemy import Column, DateTime, Numeric, String - from api.app.cache import cache -from common.models import HemeraModel -from common.models import db as postgres_db -from common.utils.config import get_config - -app_config = get_config() - - -class TokenPrices(HemeraModel): - symbol = Column(String, primary_key=True) - timestamp = Column(DateTime, primary_key=True) - price = Column(Numeric) - - -class TokenHourlyPrices(HemeraModel): - symbol = Column(String, primary_key=True) - timestamp = Column(DateTime, primary_key=True) - price = Column(Numeric) +from common.models import db +from common.models.token_hourly_price import TokenHourlyPrices +from common.models.token_prices import TokenPrices @cache.memoize(300) def get_token_price(symbol, date=None) -> Decimal: if date: token_price = ( - postgres_db.session.query(TokenHourlyPrices) + db.session.query(TokenHourlyPrices) .filter( TokenHourlyPrices.symbol == symbol, TokenHourlyPrices.timestamp <= date, @@ -36,7 +20,7 @@ def get_token_price(symbol, date=None) -> Decimal: ) else: token_price = ( - postgres_db.session.query(TokenPrices) + db.session.query(TokenPrices) .filter(TokenPrices.symbol == symbol) .order_by(TokenPrices.timestamp.desc()) .first() diff --git a/api/app/utils/utils.py b/api/app/utils/utils.py index 6c00f28de..2567d9b9a 100644 --- a/api/app/utils/utils.py +++ b/api/app/utils/utils.py @@ -12,7 +12,7 @@ from api.app.contract.contract_verify import get_abis_for_logs, get_names_from_method_or_topic_list from api.app.db_service.contracts import get_contracts_by_addresses from api.app.db_service.wallet_addresses import get_address_display_mapping -from api.app.token.token_prices import get_token_price +from api.app.utils.token_utils import get_token_price from common.models import db from common.models.transactions import Transactions from common.utils.config import get_config diff --git a/common/models/token_hourly_price.py b/common/models/token_hourly_price.py new file mode 100644 index 000000000..334d49d54 --- /dev/null +++ b/common/models/token_hourly_price.py @@ -0,0 +1,9 @@ +from sqlalchemy import Column, DateTime, Numeric, String + +from common.models import HemeraModel + + +class TokenHourlyPrices(HemeraModel): + symbol = Column(String, primary_key=True) + timestamp = Column(DateTime, primary_key=True) + price = Column(Numeric) diff --git a/common/models/token_prices.py b/common/models/token_prices.py new file mode 100644 index 000000000..2b3fe38eb --- /dev/null +++ b/common/models/token_prices.py @@ -0,0 +1,9 @@ +from sqlalchemy import Column, DateTime, Numeric, String + +from common.models import HemeraModel + + +class TokenPrices(HemeraModel): + symbol = Column(String, primary_key=True) + timestamp = Column(DateTime, primary_key=True) + price = Column(Numeric) diff --git a/indexer/modules/custom/opensea/endpoint/routes.py b/indexer/modules/custom/opensea/endpoint/routes.py index 030d213a9..1423f57d4 100644 --- a/indexer/modules/custom/opensea/endpoint/routes.py +++ b/indexer/modules/custom/opensea/endpoint/routes.py @@ -7,8 +7,8 @@ from sqlalchemy import and_, case, desc, func from api.app.cache import cache -from api.app.token.token_prices import TokenHourlyPrices from common.models import db +from common.models.token_hourly_price import TokenHourlyPrices from common.models.tokens import Tokens from common.utils.exception_control import APIError from common.utils.format_utils import as_dict, format_to_dict From 5c212285e4fc6ca8620250b9db91b560a8526616 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:42:23 +0800 Subject: [PATCH 18/70] remove scan model path (#96) --- common/models/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/__init__.py b/common/models/__init__.py index b82f70ddc..ec3b55c26 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -9,7 +9,7 @@ from common.utils.module_loading import import_string, scan_subclass_by_path_patterns from indexer.domain import Domain -model_path_patterns = ["common/models", "indexer/modules/*/models", "indexer/modules/custom/*/models", "api/app/token"] +model_path_patterns = ["common/models", "indexer/modules/*/models", "indexer/modules/custom/*/models"] model_path_exclude = ["indexer/modules/custom/address_index/models"] From 9e50a931cf9645b3be0526d1b02e7f9528519af9 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 6 Sep 2024 15:53:00 +0800 Subject: [PATCH 19/70] Feature/uniswap v3 improve (#92) * enhance uniswap v3 --------- Co-authored-by: godshan.eth Co-authored-by: li xiang --- api/app/api.py | 3 + enumeration/entity_type.py | 23 +- indexer/modules/custom/common_utils.py | 125 ++++ indexer/modules/custom/uniswap_v3/config.ini | 13 +- .../modules/custom/uniswap_v3/constants.py | 20 + .../uniswap_v3/domain/feature_uniswap_v3.py | 104 +++- .../custom/uniswap_v3/endpoints/__init__.py | 5 + .../custom/uniswap_v3/endpoints/routes.py | 188 ++++++ .../uniswap_v3/export_uniswap_v3_pool_job.py | 332 ----------- .../export_uniswap_v3_tokens_job.py | 543 ------------------ .../feature_uniswap_v3_collect_fee_records.py | 63 ++ .../feature_uniswap_v3_liquidity_records.py | 66 +++ .../feature_uniswap_v3_pool_current_prices.py | 31 + .../models/feature_uniswap_v3_pool_prices.py | 33 ++ .../models/feature_uniswap_v3_pools.py | 7 +- .../models/feature_uniswap_v3_swap_records.py | 45 ++ ...feature_uniswap_v3_token_current_status.py | 40 ++ .../feature_uniswap_v3_token_details.py | 49 ++ .../models/feature_uniswap_v3_tokens.py | 7 +- .../custom/uniswap_v3/uniswap_v3_pool_job.py | 324 +++++++++++ .../custom/uniswap_v3/uniswap_v3_token_job.py | 512 +++++++++++++++++ indexer/modules/custom/uniswap_v3/util.py | 17 + .../20240906_add_uniswap_v3_enhance_table.py | 321 +++++++++++ 23 files changed, 1981 insertions(+), 890 deletions(-) create mode 100644 indexer/modules/custom/common_utils.py create mode 100644 indexer/modules/custom/uniswap_v3/endpoints/__init__.py create mode 100644 indexer/modules/custom/uniswap_v3/endpoints/routes.py delete mode 100644 indexer/modules/custom/uniswap_v3/export_uniswap_v3_pool_job.py delete mode 100644 indexer/modules/custom/uniswap_v3/export_uniswap_v3_tokens_job.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py create mode 100644 indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py create mode 100644 indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py create mode 100644 indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py create mode 100644 migrations/versions/20240906_add_uniswap_v3_enhance_table.py diff --git a/api/app/api.py b/api/app/api.py index 446ac9a4c..7b825dca1 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -9,6 +9,7 @@ from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace from indexer.modules.custom.opensea.endpoint.routes import opensea_namespace +from indexer.modules.custom.uniswap_v3.endpoints.routes import uniswap_v3_namespace # from api.app.l2_explorer.routes import l2_explorer_namespace @@ -17,7 +18,9 @@ api.add_namespace(explorer_namespace) api.add_namespace(opensea_namespace) api.add_namespace(contract_namespace) +api.add_namespace(uniswap_v3_namespace) api.add_namespace(token_deposit_namespace) api.add_namespace(user_operation_namespace) + # api.add_namespace(l2_explorer_namespace) api.add_namespace(af_ens_namespace) diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index 4683ed2fc..5de4d2ed3 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -33,7 +33,17 @@ ) from indexer.modules.custom.opensea.domain.address_opensea_transactions import AddressOpenseaTransaction from indexer.modules.custom.opensea.domain.opensea_order import OpenseaOrder -from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import UniswapV3Pool, UniswapV3Token +from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import ( + UniswapV3Pool, + UniswapV3PoolCurrentPrice, + UniswapV3PoolPrice, + UniswapV3SwapEvent, + UniswapV3Token, + UniswapV3TokenCollectFee, + UniswapV3TokenCurrentStatus, + UniswapV3TokenDetail, + UniswapV3TokenUpdateLiquidity, +) from indexer.modules.user_ops.domain.user_operations import UserOperationsResult @@ -118,10 +128,17 @@ def generate_output_types(entity_types): yield UpdateBlockInternalCount if entity_types & EntityType.UNISWAP_V3: + yield Token + yield UpdateToken yield UniswapV3Pool + yield UniswapV3SwapEvent + yield UniswapV3PoolPrice + yield UniswapV3PoolCurrentPrice yield UniswapV3Token - yield AllFeatureValueRecordUniswapV3Pool - yield AllFeatureValueRecordUniswapV3Token + yield UniswapV3TokenCollectFee + yield UniswapV3TokenUpdateLiquidity + yield UniswapV3TokenDetail + yield UniswapV3TokenCurrentStatus if entity_types & EntityType.USER_OPS: yield UserOperationsResult diff --git a/indexer/modules/custom/common_utils.py b/indexer/modules/custom/common_utils.py new file mode 100644 index 000000000..76c5026d6 --- /dev/null +++ b/indexer/modules/custom/common_utils.py @@ -0,0 +1,125 @@ +import json +import logging + +import eth_abi +from web3 import Web3 + +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) + + +def get_chain_id(web3): + return web3.eth.chain_id + + +def simple_get_rpc_requests( + web3, make_requests, requests, is_batch, abi_list, fn_name, contract_address_key, batch_size, max_worker +): + if len(requests) == 0: + return [] + + function_abi = next((abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), None) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + def process_batch(batch): + parameters = build_no_input_method_data(web3, batch, fn_name, abi_list, contract_address_key) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token[fn_name] = decoded_data[0] + except Exception as e: + logger.error( + f"Decoding {fn_name} failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + executor = BatchWorkExecutor( + starting_batch_size=batch_size, + max_workers=max_worker, + job_name=f"simple_get_rpc_requests_{fn_name}", + ) + + all_token_infos = [] + + def work_handler(batch): + nonlocal all_token_infos + batch_results = process_batch(batch) + all_token_infos.extend(batch_results) + + executor.execute(requests, work_handler, total_items=len(requests)) + executor.wait() + + return all_token_infos + + +def build_no_input_method_data(web3, requests, fn, abi_list, contract_address_key="pool_address"): + parameters = [] + + for idx, token in enumerate(requests): + # token["request_id"] = idx + token_data = { + "request_id": idx, + "param_to": token[contract_address_key], + "param_number": hex(token["block_number"]), + } + token.update(token_data) + try: + # Encode the ABI for the specific token_id + token["param_data"] = web3.eth.contract( + address=Web3.to_checksum_address(token[contract_address_key]), abi=abi_list + ).encodeABI(fn_name=fn) + except Exception as e: + logger.error( + f"Encoding for function {fn} failed. " + f"Contract address: {token[contract_address_key]}. " + f"Exception: {e}." + ) + + parameters.append(token) + return parameters + + +def build_one_input_one_output_method_data(web3, need_call_list, contract_address, fn, abi_list, data_key="token_id"): + parameters = [] + contract = web3.eth.contract(address=Web3.to_checksum_address(contract_address), abi=abi_list) + + for idx, token in enumerate(need_call_list): + token_data = { + "request_id": idx, + "param_to": contract_address, + "param_number": hex(token["block_number"]), + } + token.update(token_data) + + try: + data = contract.encodeABI(fn_name=fn, args=[token[data_key]]) + token["param_data"] = data + except Exception as e: + logger.error( + f"Encoding token id {token[data_key]} for function {fn} failed. " + f"contract address: {contract_address}. " + f"Exception: {e}." + ) + + parameters.append(token) + return parameters diff --git a/indexer/modules/custom/uniswap_v3/config.ini b/indexer/modules/custom/uniswap_v3/config.ini index 4a2d4d05a..201dd188e 100644 --- a/indexer/modules/custom/uniswap_v3/config.ini +++ b/indexer/modules/custom/uniswap_v3/config.ini @@ -1,6 +1,9 @@ -[info] +[1] nft_address = 0xc36442b4a4522e871399cd717abdd847ab11fe88 -factory_address = 0x1F98431c8aD98523631AE4a59f267346ea31F984 -create_pool_topic0 = 0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118 -pool_swap_topic0 = 0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67 -liquidity_topic0_dict = {"0x3067048beee31b25b2f1681f88dac838c8bba36af25bfb2b7cf7473a5847e35f":1,"0xee8f6362d59839b4b3c990d7e085a63a0fe2c58f4eff4a9a2b6de93a4c645ae3":3,"0x26f6a048ee9138f2c0ce266f322cb99228e8d619ae2bff30c67f8dcf9d2377b4":1} +factory_address = 0x1f98431c8ad98523631ae4a59f267346ea31f984 +[5000] +nft_address = 0xaaa78e8c4241990b4ce159e105da08129345946a +factory_address = 0xaaa32926fce6be95ea2c51cb4fcb60836d320c42 + + + diff --git a/indexer/modules/custom/uniswap_v3/constants.py b/indexer/modules/custom/uniswap_v3/constants.py index d3afde3eb..90efe91a0 100644 --- a/indexer/modules/custom/uniswap_v3/constants.py +++ b/indexer/modules/custom/uniswap_v3/constants.py @@ -1,5 +1,7 @@ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" Q96 = 2**96 +TRANSFER_TOPIC0 = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + UNISWAP_V3_ABI = [ { "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], @@ -116,3 +118,21 @@ "type": "function", }, ] + +UNISWAP_V3_POOL_SWAP_TOPIC0 = "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67" +UNISWAP_V3_CREATE_POOL_TOPIC0 = "0x783cca1c0412dd0d695e784568c96da2e9c22ff989357a2e8b1d9b2b4e6b7118" +UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 = "0x3067048beee31b25b2f1681f88dac838c8bba36af25bfb2b7cf7473a5847e35f" +UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 = "0x26f6a048ee9138f2c0ce266f322cb99228e8d619ae2bff30c67f8dcf9d2377b4" +UNISWAP_V3_TOKEN_COLLECT_FEE_TOPIC0 = "0x40d0efd1a53d60ecbf40971b9daf7dc90178c3aadc7aab1765632738fa8b8f01" +INCREASE_TYPE = "increase" +DECREASE_TYPE = "decrease" +UNISWAP_V3_POOL_PRICE_TOPIC0_LIST = [ + # Initialize pool + "0x98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95", + # mint + "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde", + # burn + "0x0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c", + # swap + "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", +] diff --git a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py index 5a042e781..9743ef5a0 100644 --- a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py +++ b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py @@ -7,12 +7,14 @@ @dataclass class UniswapV3Pool(FilterData): nft_address: str + factory_address: str pool_address: str token0_address: str token1_address: str fee: int tick_spacing: int - called_block_number: int + block_number: int + block_timestamp: int @dataclass @@ -23,4 +25,102 @@ class UniswapV3Token(FilterData): tick_lower: int tick_upper: int fee: int - called_block_number: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3PoolPrice(FilterData): + factory_address: str + pool_address: str + sqrt_price_x96: int + tick: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3TokenDetail(FilterData): + nft_address: str + token_id: int + pool_address: str + wallet_address: str + liquidity: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3PoolCurrentPrice(FilterData): + factory_address: str + pool_address: str + sqrt_price_x96: int + tick: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3SwapEvent(FilterData): + pool_address: str + nft_address: str + transaction_from_address: str + sender: str + recipient: str + amount0: int + amount1: int + liquidity: int + tick: int + sqrt_price_x96: int + token0_address: str + token1_address: str + transaction_hash: str + log_index: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3TokenCurrentStatus(FilterData): + nft_address: str + token_id: int + pool_address: str + wallet_address: str + liquidity: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3TokenUpdateLiquidity(FilterData): + nft_address: str + token_id: int + owner: str + liquidity: int + amount0: int + amount1: int + action_type: str + transaction_hash: str + pool_address: str + token0_address: str + token1_address: str + log_index: int + block_number: int + block_timestamp: int + + +@dataclass +class UniswapV3TokenCollectFee(FilterData): + nft_address: str + recipient: str + owner: str + token_id: int + amount0: int + amount1: int + pool_address: str + token0_address: str + token1_address: str + transaction_hash: str + log_index: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/uniswap_v3/endpoints/__init__.py b/indexer/modules/custom/uniswap_v3/endpoints/__init__.py new file mode 100644 index 000000000..43e8addf4 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/endpoints/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from flask_restx.namespace import Namespace + +uniswap_v3_namespace = Namespace("Uniswap V3 Explorer", path="/", description="Uniswap V3 Feature Explorer API") diff --git a/indexer/modules/custom/uniswap_v3/endpoints/routes.py b/indexer/modules/custom/uniswap_v3/endpoints/routes.py new file mode 100644 index 000000000..bca1dc92c --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/endpoints/routes.py @@ -0,0 +1,188 @@ +import binascii +import math +import re +from datetime import datetime, timedelta +from decimal import Decimal, getcontext +from operator import or_ + +from flask import request +from flask_restx import Resource +from sqlalchemy import and_, func + +from api.app import explorer +from api.app.cache import cache +from api.app.explorer import explorer_namespace +from common.models import db +from common.models import db as postgres_db +from common.models.tokens import Tokens +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict, format_to_dict, format_value_for_json, row_to_dict +from common.utils.web3_utils import is_eth_address +from indexer.modules.custom.uniswap_v3.endpoints import uniswap_v3_namespace +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_liquidity_records import UniswapV3TokenLiquidityRecords +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pool_current_prices import UniswapV3PoolCurrentPrices +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_token_current_status import UniswapV3TokenCurrentStatus +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_tokens import UniswapV3Tokens + +Q96 = 2**96 + + +@uniswap_v3_namespace.route("/v1/uniswapv3/current_holding/") +class UniswapV3WalletHolding(Resource): + def get(self, wallet_address): + wallet_address = wallet_address.lower() + address_bytes = bytes.fromhex(wallet_address[2:]) + holdings = ( + db.session.query(UniswapV3TokenCurrentStatus) + .filter(UniswapV3TokenCurrentStatus.wallet_address == address_bytes) + .all() + ) + + unique_pool_addresses = {holding.pool_address for holding in holdings} + pool_prices = ( + db.session.query(UniswapV3PoolCurrentPrices) + .filter(UniswapV3PoolCurrentPrices.pool_address.in_(unique_pool_addresses)) + .all() + ) + pool_price_map = {} + for data in pool_prices: + pool_address = "0x" + data.pool_address.hex() + pool_price_map[pool_address] = data.sqrt_price_x96 + + tokenIds = db.session.query(UniswapV3Tokens).all() + token_id_infos = {} + erc20_tokens = set() + pool_infos = {} + pools = db.session.query(UniswapV3Pools).filter(UniswapV3Pools.pool_address.in_(unique_pool_addresses)).all() + for data in pools: + pool_address = "0x" + data.pool_address.hex() + pool_infos[pool_address] = data + erc20_tokens.add(data.token0_address) + erc20_tokens.add(data.token1_address) + + erc20_datas = db.session.query(Tokens).filter(Tokens.address.in_(erc20_tokens)).all() + erc20_infos = {} + for data in erc20_datas: + erc20_infos["0x" + data.address.hex()] = data + + for token in tokenIds: + nft_address = "0x" + token.nft_address.hex() + token_id = token.token_id + key = (nft_address, token_id) + token_id_infos[key] = token + + result = [] + for holding in holdings: + nft_address = "0x" + holding.nft_address.hex() + token_id = holding.token_id + pool_address = "0x" + holding.pool_address.hex() + sqrt_price = pool_price_map[pool_address] + token_id_info = token_id_infos[(nft_address, token_id)] + pool_info = pool_infos[pool_address] + token0_address = "0x" + pool_info.token0_address.hex() + token1_address = "0x" + pool_info.token1_address.hex() + if token0_address in erc20_infos: + token0_info = erc20_infos[token0_address] + else: + token0_info = Tokens(symbol="None", decimals=18) + if token1_address in erc20_infos: + token1_info = erc20_infos[token1_address] + else: + token1_info = Tokens(symbol="None", decimals=18) + amount0_str, amount1_str = get_token_amounts( + holding.liquidity, + sqrt_price, + token_id_info.tick_lower, + token_id_info.tick_upper, + token0_info.decimals, + token1_info.decimals, + ) + result.append( + { + "nft_address": nft_address, + "token_id": str(token_id), + "token0": { + "token0_symbol": token0_info.symbol, + "token0_balance": amount0_str, + }, + "token1": { + "token1_symbol": token1_info.symbol, + "token1_balance": amount1_str, + }, + } + ) + + return result, 200 + + +@uniswap_v3_namespace.route("/v1/uniswapv3/first_liquidity_time/") +class UniswapV3WalletLiquidityDetail(Resource): + def get(self, wallet_address): + wallet_address = wallet_address.lower() + address_bytes = bytes.fromhex(wallet_address[2:]) + first_holding = ( + db.session.query(UniswapV3TokenLiquidityRecords) + .filter(UniswapV3TokenLiquidityRecords.owner == address_bytes) + .first() + ) + if not first_holding: + return { + "message": "no provide history", + }, 200 + dt_object = datetime.utcfromtimestamp(first_holding.block_timestamp) + first_provide_time = dt_object.strftime("%Y-%m-%d %H:%M:%S") + return { + "first_provide_time": first_provide_time, + "token_id": str(first_holding.token_id), + "token0_address": first_holding.token0_address, + "token1_address": first_holding.token1_address, + }, 200 + + +@uniswap_v3_namespace.route("/v1/uniswapv3/liquidity_pools/") +class UniswapV3WalletLiquidityDetail(Resource): + def get(self, wallet_address): + wallet_address = wallet_address.lower() + address_bytes = bytes.fromhex(wallet_address[2:]) + count = ( + db.session.query(UniswapV3TokenLiquidityRecords.pool_address) + .filter(UniswapV3TokenLiquidityRecords.owner == address_bytes) + .distinct() + .count() + ) + return {"provide_pool_count": count}, 200 + + +def get_tick_at_sqrt_ratio(sqrt_price_x96): + tick = math.floor(math.log((sqrt_price_x96 / Q96) ** 2) / math.log(1.0001)) + return tick + + +def get_token_amounts(liquidity, sqrt_price_x96, tick_low, tick_high, token0_decimal, token1_decimal): + liquidity = float(liquidity) + sqrt_price_x96 = float(sqrt_price_x96) + tick_low = float(tick_low) + tick_high = float(tick_high) + sqrt_ratio_a = math.sqrt(1.0001**tick_low) + sqrt_ratio_b = math.sqrt(1.0001**tick_high) + + current_tick = get_tick_at_sqrt_ratio(sqrt_price_x96) + sqrt_price = sqrt_price_x96 / Q96 + + amount0_wei = 0 + amount1_wei = 0 + + if current_tick <= tick_low: + amount0_wei = math.floor(liquidity * ((sqrt_ratio_b - sqrt_ratio_a) / (sqrt_ratio_a * sqrt_ratio_b))) + elif current_tick > tick_high: + amount1_wei = math.floor(liquidity * (sqrt_ratio_b - sqrt_ratio_a)) + elif tick_low <= current_tick < tick_high: + amount0_wei = math.floor(liquidity * ((sqrt_ratio_b - sqrt_price) / (sqrt_price * sqrt_ratio_b))) + amount1_wei = math.floor(liquidity * (sqrt_price - sqrt_ratio_a)) + + amount0_human = amount0_wei / 10**token0_decimal + amount1_human = amount1_wei / 10**token1_decimal + amount0_str = f"{amount0_human:.{token0_decimal}f}" + amount1_str = f"{amount1_human:.{token1_decimal}f}" + return [amount0_str, amount1_str] diff --git a/indexer/modules/custom/uniswap_v3/export_uniswap_v3_pool_job.py b/indexer/modules/custom/uniswap_v3/export_uniswap_v3_pool_job.py deleted file mode 100644 index 7117abdc6..000000000 --- a/indexer/modules/custom/uniswap_v3/export_uniswap_v3_pool_job.py +++ /dev/null @@ -1,332 +0,0 @@ -import configparser -import json -import logging -import os -import threading -from collections import defaultdict - -import eth_abi - -from common import models -from indexer.domain import dict_to_dataclass -from indexer.domain.log import Log -from indexer.executors.batch_work_executor import BatchWorkExecutor -from indexer.jobs import FilterTransactionDataJob -from indexer.modules.custom.all_features_value_record import AllFeatureValueRecordUniswapV3Pool -from indexer.modules.custom.feature_type import FeatureType -from indexer.modules.custom.uniswap_v3.constants import UNISWAP_V3_ABI -from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import UniswapV3Pool -from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools -from indexer.modules.custom.uniswap_v3.util import build_no_input_method_data -from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs -from indexer.utils.abi import decode_log -from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc -from indexer.utils.utils import rpc_response_to_result, zip_rpc_response - -logger = logging.getLogger(__name__) -FEATURE_ID = FeatureType.UNISWAP_V3_POOLS.value - - -class ExportUniSwapV3PoolJob(FilterTransactionDataJob): - dependency_types = [Log] - output_types = [AllFeatureValueRecordUniswapV3Pool, UniswapV3Pool] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._batch_work_executor = BatchWorkExecutor( - kwargs["batch_size"], - kwargs["max_workers"], - job_name=self.__class__.__name__, - ) - self._is_batch = kwargs["batch_size"] > 1 - self._service = (kwargs["config"].get("db_service"),) - self._pool_prices = {} - self._pool_prices_lock = threading.Lock() - self._load_config("config.ini") - self._abi_list = UNISWAP_V3_ABI - self._exist_pools = get_exist_pools(self._service[0], self._nft_address) - - def get_filter(self): - return TransactionFilterByLogs( - [ - TopicSpecification(addresses=[self._factory_address], topics=[self._create_pool_topic0]), - TopicSpecification(topics=[self._pool_swap_topic0]), - ] - ) - - def _load_config(self, filename): - base_path = os.path.dirname(os.path.abspath(__file__)) - full_path = os.path.join(base_path, filename) - config = configparser.ConfigParser() - config.read(full_path) - - try: - self._nft_address = config.get("info", "nft_address").lower() - self._factory_address = config.get("info", "factory_address").lower() - self._create_pool_topic0 = config.get("info", "create_pool_topic0").lower() - self._pool_swap_topic0 = config.get("info", "pool_swap_topic0").lower() - except (configparser.NoOptionError, configparser.NoSectionError) as e: - raise ValueError(f"Missing required configuration in {filename}: {str(e)}") - - def _collect(self, **kwargs): - logs = self._data_buff[Log.type()] - grouped_logs = defaultdict(list) - - for log in logs: - key = (log.address, log.topic0, log.block_number) - grouped_logs[key].append(log) - - max_log_index_records = [] - for group in grouped_logs.values(): - max_log_index_record = max(group, key=lambda x: x.log_index) - max_log_index_records.append(max_log_index_record) - - # first collect pool info - need_add_in_exists_pools = update_exist_pools( - self._nft_address, - self._factory_address, - self._exist_pools, - self._create_pool_topic0, - self._pool_swap_topic0, - max_log_index_records, - self._abi_list, - self._web3, - self._batch_web3_provider.make_request, - self._is_batch, - ) - self._exist_pools.update(need_add_in_exists_pools) - - for pools in format_pool_item(need_add_in_exists_pools): - self._collect_item(UniswapV3Pool.type(), pools) - - self._batch_work_executor.execute(max_log_index_records, self._collect_batch, len(max_log_index_records)) - self._batch_work_executor.wait() - - def _collect_batch(self, logs): - - pool_prices = collect_pool_prices([self._pool_swap_topic0], self._exist_pools, logs, self._abi_list) - self.update_pool_prices(pool_prices) - for record in format_value_records(self._exist_pools, pool_prices, FEATURE_ID): - self._collect_item(AllFeatureValueRecordUniswapV3Pool.type(), record) - - def _process(self, **kwargs): - self._data_buff[UniswapV3Pool.type()].sort(key=lambda x: x.called_block_number) - self._data_buff[AllFeatureValueRecordUniswapV3Pool.type()].sort(key=lambda x: x.block_number) - - def update_pool_prices(self, new_pool_prices): - if not new_pool_prices or len(new_pool_prices) == 0: - return - with self._pool_prices_lock: - for address, new_data in new_pool_prices.items(): - if address in self._pool_prices: - current_data = self._pool_prices[address] - if new_data["block_number"] > current_data["block_number"] or ( - new_data["block_number"] == current_data["block_number"] - and new_data["log_index"] > current_data["log_index"] - ): - self._pool_prices[address] = new_data - else: - self._pool_prices[address] = new_data - - -def format_pool_item(new_pools): - result = [] - for pool_address, pool in new_pools.items(): - result.append(dict_to_dataclass(pool, UniswapV3Pool)) - return result - - -def format_value_records(exist_pools, pool_prices, feature_id): - result = [] - - for address, pool_data in pool_prices.items(): - if address not in exist_pools.keys(): - continue - info = exist_pools.get(address) - block_number = pool_data["block_number"] - value = { - "token0_address": info["token0_address"], - "token1_address": info["token1_address"], - # "fee": int(info["fee"]), - "tick_spacing": int(info["tick_spacing"]), - "called_block_number": info["called_block_number"], - "sqrtPriceX96": pool_data["sqrtPriceX96"], - "tick": pool_data["tick"], - "block_number": block_number, - } - result.append( - AllFeatureValueRecordUniswapV3Pool( - feature_id=feature_id, - block_number=block_number, - address=address, - value=value, - ) - ) - return result - - -def get_exist_pools(db_service, nft_address): - if not db_service: - raise ValueError("uniswap v3 pool job must have db connection") - - session = db_service.get_service_session() - try: - result = ( - session.query(UniswapV3Pools).filter(UniswapV3Pools.nft_address == bytes.fromhex(nft_address[2:])).all() - ) - history_pools = {} - if result is not None: - for item in result: - pool_key = "0x" + item.pool_address.hex() - history_pools[pool_key] = { - "pool_address": pool_key, - "token0_address": "0x" + item.token0_address.hex(), - "token1_address": "0x" + item.token1_address.hex(), - "fee": item.fee, - "tick_spacing": item.tick_spacing, - "called_block_number": item.called_block_number, - } - - except Exception as e: - print(e) - raise e - finally: - session.close() - - return history_pools - - -def update_exist_pools( - nft_address, factory_address, exist_pools, create_topic0, swap_topic0, logs, abi_list, web3, make_requests, is_batch -): - need_add = {} - swap_pools = [] - for log in logs: - address = log.address - if address in exist_pools: - continue - current_topic0 = log.topic0 - if factory_address == address and create_topic0 == current_topic0: - decoded_data = decode_logs("PoolCreated", abi_list, log) - pool_address = decoded_data["pool"] - - new_pool = { - "nft_address": nft_address, - "token0_address": decoded_data["token0"], - "token1_address": decoded_data["token1"], - "fee": decoded_data["fee"], - "tick_spacing": decoded_data["tickSpacing"], - "pool_address": pool_address, - "called_block_number": log.block_number, - } - need_add[pool_address] = new_pool - elif swap_topic0 == current_topic0: - # if the address created by factory_address ,collect it - swap_pools.append({"address": address, "block_number": log.block_number}) - swap_new_pools = collect_swap_new_pools( - nft_address, factory_address, swap_pools, abi_list, web3, make_requests, is_batch - ) - need_add.update(swap_new_pools) - return need_add - - -def collect_pool_prices(target_topic0_list, exist_pools, logs, abi_list): - pool_prices_map = {} - for log in logs: - address = log.address - current_topic0 = log.topic0 - - if address in exist_pools and current_topic0 in target_topic0_list: - decoded_data = decode_logs("Swap", abi_list, log) - pool_data = { - "block_number": log.block_number, - "log_index": log.log_index, - "sqrtPriceX96": decoded_data["sqrtPriceX96"], - "tick": decoded_data["tick"], - } - pool_prices_map[address] = pool_data - - return pool_prices_map - - -def collect_swap_new_pools(nft_address, factory_address, swap_pools, abi_list, web3, make_requests, is_batch): - factory_infos = simple_get_rpc_requests(web3, make_requests, swap_pools, is_batch, abi_list, "factory", "address") - uniswap_pools = [] - need_add = {} - for data in factory_infos: - if "factory" in data and data["factory"] == factory_address: - uniswap_pools.append( - { - "block_number": data["block_number"], - "address": data["address"], - } - ) - if len(uniswap_pools) == 0: - return need_add - token0_infos = simple_get_rpc_requests(web3, make_requests, uniswap_pools, is_batch, abi_list, "token0", "address") - token1_infos = simple_get_rpc_requests(web3, make_requests, token0_infos, is_batch, abi_list, "token1", "address") - tick_infos = simple_get_rpc_requests( - web3, make_requests, token1_infos, is_batch, abi_list, "tickSpacing", "address" - ) - # uniswap v3 pool have no fee function - # fee_infos = simple_get_rpc_requests(web3, make_requests, tick_infos, is_batch, abi_list, "fee", "address") - for data in tick_infos: - pool_address = data["address"] - new_pool = { - "nft_address": nft_address, - "token0_address": data["token0"], - "token1_address": data["token1"], - # "fee": data["fee"], - "tick_spacing": data["tickSpacing"], - "pool_address": pool_address, - "called_block_number": data["block_number"], - } - need_add[pool_address] = new_pool - return need_add - - -def simple_get_rpc_requests(web3, make_requests, requests, is_batch, abi_list, fn_name, contract_address_key): - if len(requests) == 0: - return [] - function_abi = next((abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), None) - outputs = function_abi["outputs"] - output_types = [output["type"] for output in outputs] - - parameters = build_no_input_method_data(web3, requests, fn_name, abi_list, contract_address_key) - - token_name_rpc = list(generate_eth_call_json_rpc(parameters)) - if is_batch: - response = make_requests(params=json.dumps(token_name_rpc)) - else: - response = [make_requests(params=json.dumps(token_name_rpc[0]))] - - token_infos = [] - for data in list(zip_rpc_response(parameters, response)): - result = rpc_response_to_result(data[1]) - token = data[0] - value = result[2:] if result is not None else None - try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - token[fn_name] = decoded_data[0] - - except Exception as e: - logger.error( - f"Decoding {fn_name} failed. " - f"token: {token}. " - f"fn: {fn_name}. " - f"rpc response: {result}. " - f"exception: {e}" - ) - token_infos.append(token) - return token_infos - - -def decode_logs(fn_name, contract_abi, log): - function_abi = next( - (abi for abi in contract_abi if abi["name"] == fn_name and abi["type"] == "event"), - None, - ) - if not function_abi: - raise ValueError("Function ABI not found") - - return decode_log(function_abi, log) diff --git a/indexer/modules/custom/uniswap_v3/export_uniswap_v3_tokens_job.py b/indexer/modules/custom/uniswap_v3/export_uniswap_v3_tokens_job.py deleted file mode 100644 index 94dbcaf39..000000000 --- a/indexer/modules/custom/uniswap_v3/export_uniswap_v3_tokens_job.py +++ /dev/null @@ -1,543 +0,0 @@ -import configparser -import json -import logging -import os -import queue - -import eth_abi -from web3 import Web3 - -from indexer.domain.log import Log -from indexer.domain.token_transfer import ERC721TokenTransfer -from indexer.executors.batch_work_executor import BatchWorkExecutor -from indexer.jobs import FilterTransactionDataJob -from indexer.modules.custom.all_features_value_record import AllFeatureValueRecordUniswapV3Token -from indexer.modules.custom.feature_type import FeatureType -from indexer.modules.custom.uniswap_v3 import constants -from indexer.modules.custom.uniswap_v3.constants import UNISWAP_V3_ABI -from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import UniswapV3Token -from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_tokens import UniswapV3Tokens -from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs -from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc -from indexer.utils.utils import rpc_response_to_result, zip_rpc_response - -logger = logging.getLogger(__name__) -FEATURE_ID = FeatureType.UNISWAP_V3_TOKENS.value - - -class ExportUniSwapV3TokensJob(FilterTransactionDataJob): - dependency_types = [Log, ERC721TokenTransfer] - output_types = [AllFeatureValueRecordUniswapV3Token, UniswapV3Token] - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._batch_work_executor = BatchWorkExecutor( - kwargs["batch_size"], - kwargs["max_workers"], - job_name=self.__class__.__name__, - ) - self._is_batch = kwargs["batch_size"] > 1 - self._service = (kwargs["config"].get("db_service"),) - self._load_config("config.ini") - self._abi_list = UNISWAP_V3_ABI - self._liquidity_token_id_blocks = queue.Queue() - self._exist_token_ids = get_exist_token_ids(self._service[0], self._nft_address) - - def _load_config(self, filename): - base_path = os.path.dirname(os.path.abspath(__file__)) - full_path = os.path.join(base_path, filename) - config = configparser.ConfigParser() - config.read(full_path) - - try: - self._nft_address = config.get("info", "nft_address").lower() - self._factory_address = config.get("info", "factory_address").lower() - self._liquidity_topic0_dict = json.loads(config.get("info", "liquidity_topic0_dict")) - except (configparser.NoOptionError, configparser.NoSectionError) as e: - raise ValueError(f"Missing required configuration in {filename}: {str(e)}") - - def get_filter(self): - return TransactionFilterByLogs( - [ - TopicSpecification( - addresses=[self._factory_address], - topics=list(self._liquidity_topic0_dict.keys()), - ), - TopicSpecification(addresses=[self._nft_address]), - ] - ) - - def _collect(self, **kwargs): - # collect the nft_ids which were minted or burned - mint_token_ids, burn_token_ids, all_token_dict = extract_changed_tokens( - self._data_buff[ERC721TokenTransfer.type()], self._nft_address - ) - token_id_liquidity_records = extract_liquidity_logs( - self._liquidity_topic0_dict, self._data_buff[Log.type()], self._nft_address - ) - - need_collect_value_tokens, want_pool_tokens = gather_collect_infos( - all_token_dict, - token_id_liquidity_records, - burn_token_ids, - self._exist_token_ids, - ) - # call owners - owner_dict = get_owner_dict( - self._web3, - self._batch_web3_provider.make_request, - need_collect_value_tokens, - self._nft_address, - self._is_batch, - self._abi_list, - ) - - # call positions - token_infos = positions_rpc_requests( - self._web3, - self._batch_web3_provider.make_request, - need_collect_value_tokens, - self._nft_address, - self._is_batch, - self._abi_list, - ) - # filter the info which call pool needed - update_exist_tokens, new_nft_info = get_new_nfts( - token_infos, - want_pool_tokens, - self._nft_address, - self._web3, - self._batch_web3_provider.make_request, - need_collect_value_tokens, - self._factory_address, - self._is_batch, - self._abi_list, - ) - self._exist_token_ids.update(update_exist_tokens) - for data in new_nft_info: - self._collect_item(UniswapV3Token.type(), data) - - new_records = parse_token_records(self._exist_token_ids, owner_dict, token_infos, FEATURE_ID) - for data in new_records: - self._collect_item(AllFeatureValueRecordUniswapV3Token.type(), data) - - def _process(self, **kwargs): - self._data_buff[UniswapV3Token.type()].sort(key=lambda x: x.called_block_number) - self._data_buff[AllFeatureValueRecordUniswapV3Token.type()].sort(key=lambda x: x.block_number) - - -def parse_token_records(token_pool_dict, owner_dict, token_infos, feature_id): - # one address may have many records in one block - address_infos = {} - - for data in token_infos: - block_number = data["block_number"] - token_id = data["token_id"] - address = owner_dict[token_id][block_number] - - value = { - "token_id": token_id, - "tick_lower": data["tickLower"], - "tick_upper": data["tickUpper"], - "fee": data["fee"], - "liquidity": data["liquidity"], - "token0": data["token0"], - "token1": data["token1"], - "block_number": block_number, - "pool_address": token_pool_dict[token_id], - } - - if address not in address_infos.keys(): - address_infos[address] = {} - if block_number not in address_infos[address]: - address_infos[address][block_number] = [] - address_infos[address][block_number].append(value) - result = [] - for address, block in address_infos.items(): - for block_number, data in block.items(): - result.append( - AllFeatureValueRecordUniswapV3Token( - feature_id=feature_id, - block_number=block_number, - address=address, - value=data, - ) - ) - return result - - -def gather_collect_infos(all_token_dict, token_id_block, burn_token_ids, exist_token_ids): - seen = set() - for token_id, blocks in all_token_dict.items(): - for block_number, to_address in blocks.items(): - seen.add((token_id, block_number)) - for token_id, blocks in token_id_block.items(): - for block_number, to_address in blocks.items(): - seen.add((token_id, block_number)) - - need_collect_value_tokens = [] - want_pool_tokens = set() - for item in seen: - token_id = item[0] - block_number = item[1] - if token_id not in burn_token_ids or burn_token_ids[token_id] < block_number: - temp = { - "token_id": token_id, - "block_number": block_number, - } - need_collect_value_tokens.append(temp) - if token_id not in exist_token_ids: - want_pool_tokens.add((token_id, block_number)) - return need_collect_value_tokens, want_pool_tokens - - -def get_owner_dict(web3, make_requests, requests, nft_address, is_batch, abi_list): - owners = owner_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list) - owner_dict = {} - for data in owners: - owner_dict.setdefault(data["token_id"], {})[data["block_number"]] = data["owner"] - return owner_dict - - -def get_new_nfts( - all_token_infos, - want_pool_tokens, - nft_address, - web3, - make_requests, - requests, - factory_address, - is_batch, - abi_list, -): - result = [] - need_collect_pool_tokens = [] - for info in all_token_infos: - token_id = info["token_id"] - block_number = info["block_number"] - if (token_id, block_number) in want_pool_tokens: - need_collect_pool_tokens.append(info) - - new_pool_info = get_pool_rpc_requests(web3, make_requests, requests, factory_address, is_batch, abi_list) - pool_dict = {} - for data in new_pool_info: - key = data["token0"] + data["token1"] + str(data["fee"]) - pool_dict[key] = data["pool_address"] - # get new nft_id info - update_exist_tokens = {} - seen = set() - - for data in need_collect_pool_tokens: - token_id = data["token_id"] - if (nft_address, token_id) in seen: - continue - seen.add((nft_address, token_id)) - key = data["token0"] + data["token1"] + str(data["fee"]) - pool_address = pool_dict[key] - update_exist_tokens[token_id] = pool_address - - result.append( - UniswapV3Token( - nft_address=nft_address, - token_id=token_id, - pool_address=pool_address, - tick_lower=data["tickLower"], - tick_upper=data["tickUpper"], - fee=data["fee"], - called_block_number=data["block_number"], - ) - ) - return update_exist_tokens, result - - -def get_exist_token_ids(db_service, nft_address): - session = db_service.get_service_session() - try: - result = ( - session.query(UniswapV3Tokens.token_id, UniswapV3Tokens.pool_address) - .filter( - UniswapV3Tokens.pool_address != None, - UniswapV3Tokens.nft_address == bytes.fromhex(nft_address[2:]), - ) - .all() - ) - history_token = {} - if result is not None: - for item in result: - token_id = (item.token_id,) - history_token[token_id] = "0x" + item.pool_address.hex() - except Exception as e: - print(e) - raise e - finally: - session.close() - return history_token - - -def extract_changed_tokens(token_transfers, nft_address): - mint_tokens_dict = {} - burn_tokens_dict = {} - all_tokens_dict = {} - sorted_transfers = sorted(token_transfers, key=lambda x: (x.block_number, x.log_index)) - - for transfer in sorted_transfers: - token_address = transfer.token_address - if token_address != nft_address: - continue - token_id = transfer.token_id - block_number = transfer.block_number - to_address = transfer.to_address - from_address = transfer.from_address - - if token_id not in all_tokens_dict: - all_tokens_dict[token_id] = {} - all_tokens_dict[token_id][block_number] = to_address - - if to_address == constants.ZERO_ADDRESS: - burn_tokens_dict[token_id] = block_number - elif from_address == constants.ZERO_ADDRESS: - mint_tokens_dict[token_id] = block_number - - return mint_tokens_dict, burn_tokens_dict, all_tokens_dict - - -def extract_liquidity_logs(topic0_dict, logs, nft_address): - token_id_block = {} - - for log in logs: - if log.address != nft_address: - continue - - topic0 = log.topic0 - if topic0 not in topic0_dict: - continue - - topic_index = topic0_dict[topic0] - if topic_index == 1: - token_id_hex = log.topic1 - elif topic_index == 2: - token_id_hex = log.topic2 - else: - token_id_hex = log.topic3 - token_id = int(token_id_hex[2:], 16) - token_id_block.setdefault(token_id, {})[log.block_number] = topic0 - return token_id_block - - -def build_token_id_method_data(web3, token_ids, nft_address, fn, abi_list): - parameters = [] - contract = web3.eth.contract(address=Web3.to_checksum_address(nft_address), abi=abi_list) - - for idx, token in enumerate(token_ids): - token_data = { - "request_id": idx, - "param_to": nft_address, - "param_number": hex(token["block_number"]), - } - token.update(token_data) - - try: - # Encode the ABI for the specific token_id - data = contract.encodeABI(fn_name=fn, args=[token["token_id"]]) - token["param_data"] = data - except Exception as e: - logger.error( - f"Encoding token id {token['token_id']} for function {fn} failed. " - f"NFT address: {nft_address}. " - f"Exception: {e}." - ) - - parameters.append(token) - return parameters - - -def positions_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list): - if len(requests) == 0: - return [] - fn_name = "positions" - function_abi = next( - (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), - None, - ) - outputs = function_abi["outputs"] - output_types = [output["type"] for output in outputs] - - parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) - - token_name_rpc = list(generate_eth_call_json_rpc(parameters)) - if is_batch: - response = make_requests(params=json.dumps(token_name_rpc)) - else: - response = [make_requests(params=json.dumps(token_name_rpc[0]))] - - token_infos = [] - for data in list(zip_rpc_response(parameters, response)): - result = rpc_response_to_result(data[1]) - token = data[0] - value = result[2:] if result is not None else None - try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - token["nonce"] = decoded_data[0] - token["operator"] = decoded_data[1] - token["token0"] = decoded_data[2] - token["token1"] = decoded_data[3] - token["fee"] = decoded_data[4] - token["tickLower"] = decoded_data[5] - token["tickUpper"] = decoded_data[6] - token["liquidity"] = decoded_data[7] - token["feeGrowthInside0LastX128"] = decoded_data[8] - token["feeGrowthInside1LastX128"] = decoded_data[9] - token["tokensOwed0"] = decoded_data[10] - token["tokensOwed1"] = decoded_data[11] - - except Exception as e: - logger.error( - f"Decoding positions failed. " - f"token: {token}. " - f"fn: {fn_name}. " - f"rpc response: {result}. " - f"exception: {e}" - ) - token_infos.append(token) - return token_infos - - -def owner_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list): - if len(requests) == 0: - return [] - fn_name = "ownerOf" - function_abi = next( - (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), - None, - ) - outputs = function_abi["outputs"] - output_types = [output["type"] for output in outputs] - - parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) - - token_name_rpc = list(generate_eth_call_json_rpc(parameters)) - if is_batch: - response = make_requests(params=json.dumps(token_name_rpc)) - else: - response = [make_requests(params=json.dumps(token_name_rpc[0]))] - - token_infos = [] - for data in list(zip_rpc_response(parameters, response)): - result = rpc_response_to_result(data[1]) - token = data[0] - value = result[2:] if result is not None else None - try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - token["owner"] = decoded_data[0] - - except Exception as e: - logger.error( - f"Decoding ownerOf failed. " - f"token: {token}. " - f"fn: {fn_name}. " - f"rpc response: {result}. " - f"exception: {e}" - ) - token_infos.append(token) - return token_infos - - -def build_get_pool_method_data(web3, requests, factory_address, fn, abi_list): - parameters = [] - contract = web3.eth.contract(address=Web3.to_checksum_address(factory_address), abi=abi_list) - - for idx, token in enumerate(requests): - token["request_id"] = (idx,) - token_data = { - "request_id": idx, - "param_to": factory_address, - "param_number": hex(token["block_number"]), - } - token.update(token_data) - try: - # Encode the ABI for the specific token_id - data = contract.encodeABI( - fn_name=fn, - args=[ - Web3.to_checksum_address(token["token0"]), - Web3.to_checksum_address(token["token1"]), - token["fee"], - ], - ) - token["param_data"] = data - except Exception as e: - logger.error( - f"Encoding token0 {token['token0']} token1 {token['token1']} fee {token['fee']} for function {fn} failed. " - f"contract address: {factory_address}. " - f"Exception: {e}." - ) - - parameters.append(token) - return parameters - - -def get_pool_rpc_requests(web3, make_requests, requests, factory_address, is_batch, abi_list): - if len(requests) == 0: - return [] - fn_name = "getPool" - function_abi = next( - (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), - None, - ) - outputs = function_abi["outputs"] - output_types = [output["type"] for output in outputs] - - parameters = build_get_pool_method_data(web3, requests, factory_address, fn_name, abi_list) - token_name_rpc = list(generate_eth_call_json_rpc(parameters)) - if is_batch: - response = make_requests(params=json.dumps(token_name_rpc)) - else: - response = [make_requests(params=json.dumps(token_name_rpc[0]))] - - token_infos = [] - for data in list(zip_rpc_response(parameters, response)): - result = rpc_response_to_result(data[1]) - - token = data[0] - value = result[2:] if result is not None else None - try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - token["pool_address"] = decoded_data[0] - - except Exception as e: - logger.error( - f"Decoding getPool failed. " - f"token: {token}. " - f"fn: {fn_name}. " - f"rpc response: {result}. " - f"exception: {e}" - ) - token_infos.append(token) - return token_infos - - -def build_no_input_method_data(web3, requests, fn, block_number, abi_list): - parameters = [] - - for idx, token in enumerate(requests): - token["request_id"] = (idx,) - token_data = { - "request_id": idx, - "param_to": token["pool_address"], - "param_number": hex(block_number), - } - token.update(token_data) - try: - # Encode the ABI for the specific token_id - token["param_data"] = web3.eth.contract( - address=Web3.to_checksum_address(token["pool_address"]), abi=abi_list - ).encodeABI(fn_name=fn) - except Exception as e: - logger.error( - f"Encoding token0 {token['token0']} token1 {token['token1']} fee {token['fee']} for function {fn} failed. " - f"contract address: {token['pool_address']}. " - f"Exception: {e}." - ) - - parameters.append(token) - return parameters diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py new file mode 100644 index 000000000..b9b70fdea --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py @@ -0,0 +1,63 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR + +from common.models import HemeraModel, general_converter + + +class UniswapV3CollectFeeRecords(HemeraModel): + __tablename__ = "af_uniswap_v3_token_collect_fee_hist" + nft_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_number = Column(BIGINT, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + log_index = Column(INTEGER, primary_key=True) + transaction_hash = Column(BYTEA) + owner = Column(BYTEA) + recipient = Column(BYTEA) + + amount0 = Column(NUMERIC(100)) + amount1 = Column(NUMERIC(100)) + pool_address = Column(BYTEA) + token0_address = Column(BYTEA) + token1_address = Column(BYTEA) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3TokenCollectFee", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] + + +Index( + "af_uniswap_v3_token_collect_fee_hist_pool_index", + UniswapV3CollectFeeRecords.pool_address, +) +Index( + "af_uniswap_v3_token_collect_fee_hist_token0_index", + UniswapV3CollectFeeRecords.token0_address, +) +Index( + "af_uniswap_v3_token_collect_fee_hist_token1_index", + UniswapV3CollectFeeRecords.token1_address, +) +Index( + "af_uniswap_v3_token_collect_fee_hist_token_id_index", + UniswapV3CollectFeeRecords.token_id, +) +Index( + "af_uniswap_v3_token_collect_fee_hist_owner_index", + UniswapV3CollectFeeRecords.owner, +) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py new file mode 100644 index 000000000..1938792d7 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py @@ -0,0 +1,66 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR + +from common.models import HemeraModel, general_converter + + +class UniswapV3TokenLiquidityRecords(HemeraModel): + __tablename__ = "af_uniswap_v3_token_liquidity_hist" + nft_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_number = Column(BIGINT, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + log_index = Column(INTEGER, primary_key=True) + transaction_hash = Column(BYTEA) + owner = Column(BYTEA) + + liquidity = Column(NUMERIC(100)) + amount0 = Column(NUMERIC(100)) + amount1 = Column(NUMERIC(100)) + + pool_address = Column(BYTEA) + token0_address = Column(BYTEA) + token1_address = Column(BYTEA) + + action_type = Column(VARCHAR) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3TokenUpdateLiquidity", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] + + +Index( + "af_uniswap_v3_token_liquidity_hist_pool_index", + UniswapV3TokenLiquidityRecords.pool_address, +) +Index( + "af_uniswap_v3_token_liquidity_hist_token0_index", + UniswapV3TokenLiquidityRecords.token0_address, +) +Index( + "af_uniswap_v3_token_liquidity_hist_token1_index", + UniswapV3TokenLiquidityRecords.token1_address, +) +Index( + "af_uniswap_v3_token_liquidity_hist_token_id_index", + UniswapV3TokenLiquidityRecords.token_id, +) +Index( + "af_uniswap_v3_token_liquidity_hist_owner_index", + UniswapV3TokenLiquidityRecords.owner, +) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py new file mode 100644 index 000000000..9201022b6 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py @@ -0,0 +1,31 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class UniswapV3PoolCurrentPrices(HemeraModel): + __tablename__ = "af_uniswap_v3_pool_prices_current" + pool_address = Column(BYTEA, primary_key=True) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) + factory_address = Column(BYTEA) + + sqrt_price_x96 = Column(NUMERIC(100)) + tick = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3PoolCurrentPrice", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_pool_prices_current.block_number", + "converter": general_converter, + }, + ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py new file mode 100644 index 000000000..f794facbd --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py @@ -0,0 +1,33 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class UniswapV3PoolPrices(HemeraModel): + __tablename__ = "af_uniswap_v3_pool_prices_hist" + pool_address = Column(BYTEA, primary_key=True) + block_number = Column(BIGINT, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + + sqrt_price_x96 = Column(NUMERIC(100)) + tick = Column(NUMERIC(100)) + factory_address = Column(BYTEA) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3PoolPrice", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py index c7db9cd50..72702b363 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py @@ -7,17 +7,20 @@ class UniswapV3Pools(HemeraModel): - __tablename__ = "feature_uniswap_v3_pools" + __tablename__ = "af_uniswap_v3_pools" nft_address = Column(BYTEA, primary_key=True) pool_address = Column(BYTEA, primary_key=True) + factory_address = Column(BYTEA) + token0_address = Column(BYTEA) token1_address = Column(BYTEA) fee = Column(NUMERIC(100)) tick_spacing = Column(NUMERIC(100)) - called_block_number = Column(BIGINT) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py new file mode 100644 index 000000000..fd3e66539 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py @@ -0,0 +1,45 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR + +from common.models import HemeraModel, general_converter + + +class UniswapV3PoolSwapRecords(HemeraModel): + __tablename__ = "af_uniswap_v3_pool_swap_hist" + pool_address = Column(BYTEA, primary_key=True) + transaction_hash = Column(BYTEA, primary_key=True) + log_index = Column(INTEGER, primary_key=True) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) + nft_address = Column(BYTEA) + transaction_from_address = Column(BYTEA) + sender = Column(BYTEA) + recipient = Column(BYTEA) + + liquidity = Column(NUMERIC(100)) + tick = Column(NUMERIC(100)) + sqrt_price_x96 = Column(NUMERIC(100)) + amount0 = Column(NUMERIC(100)) + amount1 = Column(NUMERIC(100)) + + token0_address = Column(BYTEA) + token1_address = Column(BYTEA) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("pool_address", "transaction_hash", "log_index"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3SwapEvent", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py new file mode 100644 index 000000000..222c6b42c --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class UniswapV3TokenCurrentStatus(HemeraModel): + __tablename__ = "af_uniswap_v3_token_data_current" + + nft_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) + wallet_address = Column(BYTEA) + pool_address = Column(BYTEA) + liquidity = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3TokenCurrentStatus", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_token_data_current.block_number", + "converter": general_converter, + }, + ] + + +Index( + "af_uniswap_v3_token_data_current_wallet_desc_index", + desc(UniswapV3TokenCurrentStatus.wallet_address), +) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py new file mode 100644 index 000000000..c60f954c2 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py @@ -0,0 +1,49 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class UniswapV3TokenDetails(HemeraModel): + __tablename__ = "af_uniswap_v3_token_data_hist" + + nft_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_number = Column(BIGINT, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + wallet_address = Column(BYTEA) + pool_address = Column(BYTEA) + liquidity = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "UniswapV3TokenDetail", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] + + +Index( + "af_uniswap_v3_token_data_hist_token_block_desc_index", + desc(UniswapV3TokenDetails.nft_address), + desc(UniswapV3TokenDetails.block_timestamp), +) + +Index( + "af_uniswap_v3_token_data_hist_wallet_token_block_desc_index", + desc(UniswapV3TokenDetails.wallet_address), + desc(UniswapV3TokenDetails.nft_address), + desc(UniswapV3TokenDetails.block_timestamp), +) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py index 73af0dbbf..769110ebc 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py @@ -7,7 +7,7 @@ class UniswapV3Tokens(HemeraModel): - __tablename__ = "feature_uniswap_v3_tokens" + __tablename__ = "af_uniswap_v3_tokens" nft_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) @@ -17,7 +17,8 @@ class UniswapV3Tokens(HemeraModel): tick_upper = Column(NUMERIC(100)) fee = Column(NUMERIC(100)) - called_block_number = Column(BIGINT) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) @@ -36,4 +37,4 @@ def model_domain_mapping(): ] -Index("feature_uniswap_v3_tokens_nft_index", UniswapV3Tokens.nft_address) +Index("af_uniswap_v3_tokens_nft_index", UniswapV3Tokens.nft_address) diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py new file mode 100644 index 000000000..6f42be338 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py @@ -0,0 +1,324 @@ +import configparser +import json +import logging +import os +from collections import defaultdict +from dataclasses import fields + +import eth_abi +from web3 import Web3 + +from indexer.domain.log import Log +from indexer.domain.transaction import Transaction +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.feature_type import FeatureType +from indexer.modules.custom.uniswap_v3 import constants, util +from indexer.modules.custom.uniswap_v3.constants import UNISWAP_V3_ABI +from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import ( + UniswapV3Pool, + UniswapV3PoolCurrentPrice, + UniswapV3PoolPrice, + UniswapV3SwapEvent, +) +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) +FEATURE_ID = FeatureType.UNISWAP_V3_POOLS.value + + +class UniSwapV3PoolJob(FilterTransactionDataJob): + dependency_types = [Transaction, Log] + output_types = [UniswapV3Pool, UniswapV3PoolPrice, UniswapV3PoolCurrentPrice, UniswapV3SwapEvent] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._service = kwargs["config"].get("db_service") + self._chain_id = common_utils.get_chain_id(self._web3) + self._load_config("config.ini", self._chain_id) + self._exist_pools = get_exist_pools(self._service, self._nft_address) + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + self._abi_list = UNISWAP_V3_ABI + self._create_pool_topic0 = constants.UNISWAP_V3_CREATE_POOL_TOPIC0 + self._pool_price_topic0_list = constants.UNISWAP_V3_POOL_PRICE_TOPIC0_LIST + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(addresses=[self._factory_address], topics=[self._create_pool_topic0]), + TopicSpecification(topics=self._pool_price_topic0_list), + ] + ) + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + chain_id_str = str(chain_id) + try: + chain_config = config[chain_id_str] + except KeyError: + return + try: + self._nft_address = chain_config.get("nft_address").lower() + self._factory_address = chain_config.get("factory_address").lower() + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + def _collect(self, **kwargs): + logs = self._data_buff[Log.type()] + self._batch_work_executor.execute(logs, self._collect_pool_batch, len(logs)) + self._batch_work_executor.wait() + + collected_pools = self._data_buff[UniswapV3Pool.type()] + for data in collected_pools: + self._exist_pools[data.pool_address] = data + transactions = self._data_buff[Transaction.type()] + self._transaction_hash_from_dict = {} + for transaction in transactions: + self._transaction_hash_from_dict[transaction.hash] = transaction.from_address + self._batch_work_executor.execute(logs, self._collect_price_batch, len(logs), split_logs) + self._batch_work_executor.wait() + self._transaction_hash_from_dict = {} + + def _collect_pool_batch(self, logs): + for log in logs: + address = log.address + current_topic0 = log.topic0 + if self._factory_address != address or self._create_pool_topic0 != current_topic0: + continue + entity = decode_pool_created(self._nft_address, self._factory_address, log) + self._collect_item(UniswapV3Pool.type(), entity) + + def _collect_price_batch(self, logs_dict): + if not logs_dict: + return + contract_address = next(iter(logs_dict)) + if contract_address not in self._exist_pools: + return + + logs = logs_dict[contract_address] + unique_logs = set() + for log in logs: + # Collect swap logs + if log.topic0 == constants.UNISWAP_V3_POOL_SWAP_TOPIC0: + transaction_hash = log.transaction_hash + part1, part2, part3, part4, part5 = split_swap_data_hex_string(log.data) + amount0 = util.parse_hex_to_int256(part1) + amount1 = util.parse_hex_to_int256(part2) + sqrt_price_x96 = util.parse_hex_to_int256(part3) + liquidity = util.parse_hex_to_int256(part4) + tick = util.parse_hex_to_int256(part5) + pool_data = self._exist_pools[log.address] + self._collect_item( + UniswapV3SwapEvent.type(), + UniswapV3SwapEvent( + pool_address=log.address, + nft_address=self._nft_address, + transaction_hash=transaction_hash, + transaction_from_address=self._transaction_hash_from_dict[transaction_hash], + log_index=log.log_index, + block_number=log.block_number, + block_timestamp=log.block_timestamp, + sender=util.parse_hex_to_address(log.topic1), + recipient=util.parse_hex_to_address(log.topic2), + amount0=amount0, + amount1=amount1, + liquidity=liquidity, + tick=tick, + sqrt_price_x96=sqrt_price_x96, + token0_address=pool_data.token0_address, + token1_address=pool_data.token1_address, + ), + ) + log_tuple = (log.address, log.block_number, log.block_timestamp) + unique_logs.add(log_tuple) + requests = [ + {"pool_address": address, "block_number": block_number, "block_timestamp": block_timestamp} + for address, block_number, block_timestamp in unique_logs + ] + pool_prices = slot0_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + requests, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + current_price = None + for data in pool_prices: + detail = UniswapV3PoolPrice( + factory_address=self._factory_address, + pool_address=data["pool_address"], + sqrt_price_x96=data["sqrtPriceX96"], + tick=data["tick"], + block_number=data["block_number"], + block_timestamp=data["block_timestamp"], + ) + self._collect_item(UniswapV3PoolPrice.type(), detail) + if current_price is None or current_price.block_number < detail.block_number: + current_price = create_current_price_status(detail) + self._collect_item(UniswapV3PoolCurrentPrice.type(), current_price) + + def _process(self, **kwargs): + self._data_buff[UniswapV3Pool.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3PoolPrice.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3PoolCurrentPrice.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3SwapEvent.type()].sort(key=lambda x: x.block_number) + + +def decode_pool_created(nft_address, factory_address, log): + token0_address = util.parse_hex_to_address(log.topic1) + token1_address = util.parse_hex_to_address(log.topic2) + fee = util.parse_hex_to_int256(log.topic3) + tick_hex, pool_hex = split_hex_string(log.data) + pool_address = util.parse_hex_to_address(pool_hex) + tick_spacing = util.parse_hex_to_int256(tick_hex) + return UniswapV3Pool( + nft_address=nft_address, + factory_address=factory_address, + pool_address=pool_address, + token0_address=token0_address, + token1_address=token1_address, + fee=fee, + tick_spacing=tick_spacing, + block_number=log.block_number, + block_timestamp=log.block_timestamp, + ) + + +def split_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + + if len(hex_string) == 128: + part1 = hex_string[:64] + part2 = hex_string[64:] + return part1, part2 + else: + raise ValueError("The data is not belong to Uniswap-V3 Factory") + + +def get_exist_pools(db_service, nft_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Pools).filter(UniswapV3Pools.nft_address == bytes.fromhex(nft_address[2:])).all() + ) + history_pools = {} + if result is not None: + for item in result: + pool_key = "0x" + item.pool_address.hex() + history_pools[pool_key] = UniswapV3Pool( + nft_address="0x" + item.nft_address.hex(), + pool_address=pool_key, + token0_address="0x" + item.token0_address.hex(), + token1_address="0x" + item.token1_address.hex(), + factory_address="0x" + item.factory_address.hex(), + fee=item.fee, + tick_spacing=item.tick_spacing, + block_number=item.block_number, + block_timestamp=item.block_timestamp, + ) + except Exception as e: + raise e + finally: + session.close() + + return history_pools + + +def split_logs(logs): + log_dict = defaultdict(list) + for data in logs: + log_dict[data.address].append(data) + + for contract_address, data in log_dict.items(): + yield {contract_address: data} + + +def slot0_rpc_requests(web3, make_requests, requests, is_batch, abi_list, batch_size, max_worker): + if len(requests) == 0: + return [] + fn_name = "slot0" + function_abi = next((abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), None) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + def process_batch(batch): + parameters = common_utils.build_no_input_method_data(web3, batch, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + pool = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + pool["sqrtPriceX96"] = decoded_data[0] + pool["tick"] = decoded_data[1] + except Exception as e: + logger.error(f"Decoding {fn_name} failed. " f"rpc response: {result}. " f"exception: {e}") + token_infos.append(pool) + return token_infos + + executor = BatchWorkExecutor( + starting_batch_size=batch_size, + max_workers=max_worker, + job_name=f"slot0_rpc_requests_{fn_name}", + ) + + all_token_infos = [] + + def work_handler(batch): + nonlocal all_token_infos + batch_results = process_batch(batch) + all_token_infos.extend(batch_results) + + executor.execute(requests, work_handler, total_items=len(requests)) + executor.wait() + + return all_token_infos + + +def create_current_price_status(detail: UniswapV3PoolPrice) -> UniswapV3PoolCurrentPrice: + return UniswapV3PoolCurrentPrice( + **{field.name: getattr(detail, field.name) for field in fields(UniswapV3PoolPrice)} + ) + + +def split_swap_data_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + if len(hex_string) == 320: + part1 = hex_string[:64] + part2 = hex_string[64:128] + part3 = hex_string[128:192] + part4 = hex_string[192:256] + part5 = hex_string[256:] + return part1, part2, part3, part4, part5 + else: + raise ValueError("The data length is not suitable for this operation.") diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py new file mode 100644 index 000000000..ba10e4ee0 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py @@ -0,0 +1,512 @@ +import configparser +import json +import logging +import os +import queue +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import fields + +import eth_abi +from web3 import Web3 + +from indexer.domain.log import Log +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.feature_type import FeatureType +from indexer.modules.custom.uniswap_v3 import constants, util +from indexer.modules.custom.uniswap_v3.constants import UNISWAP_V3_ABI +from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import ( + UniswapV3Pool, + UniswapV3Token, + UniswapV3TokenCollectFee, + UniswapV3TokenCurrentStatus, + UniswapV3TokenDetail, + UniswapV3TokenUpdateLiquidity, +) +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_tokens import UniswapV3Tokens +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) +FEATURE_ID = FeatureType.UNISWAP_V3_TOKENS.value + + +class UniswapV3TokenJob(FilterTransactionDataJob): + dependency_types = [Log, UniswapV3Pool] + output_types = [ + UniswapV3Token, + UniswapV3TokenDetail, + UniswapV3TokenCurrentStatus, + UniswapV3TokenUpdateLiquidity, + UniswapV3TokenCollectFee, + ] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._chain_id = common_utils.get_chain_id(self._web3) + self._service = kwargs["config"].get("db_service") + self._load_config("config.ini", self._chain_id) + self._abi_list = UNISWAP_V3_ABI + self._liquidity_token_id_blocks = queue.Queue() + self._exist_token_ids = get_exist_token_ids(self._service, self._nft_address) + self._exist_pool_infos = get_exist_pools(self._service, self._nft_address) + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + chain_id_str = str(chain_id) + try: + chain_config = config[chain_id_str] + except KeyError: + return + try: + self._nft_address = chain_config.get("nft_address").lower() + self._factory_address = chain_config.get("factory_address").lower() + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(addresses=[self._nft_address]), + ] + ) + + def _collect(self, **kwargs): + # get new pool + collected_pools = self._data_buff[UniswapV3Pool.type()] + for data in collected_pools: + self._exist_pool_infos[data.pool_address] = data + info_pool_dict = {} + for data in self._exist_pool_infos.values(): + key = (data.token0_address, data.token1_address, data.fee) + info_pool_dict[key] = data.pool_address + + # collect token_id's data + logs = self._data_buff[Log.type()] + early_token_id_data = {} + need_collect_token_id_data = [] + need_collect_token_id_set = set() + for log in logs: + topic0 = log.topic0 + address = log.address + block_number = log.block_number + block_timestamp = log.block_timestamp + if address != self._nft_address: + continue + if topic0 == constants.TRANSFER_TOPIC0: + token_id_hex = log.topic3 + elif ( + topic0 == constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + ): + token_id_hex = log.topic1 + else: + continue + token_id = util.parse_hex_to_int256(token_id_hex) + key = (token_id, block_number, block_timestamp) + data = {"token_id": token_id, "block_number": block_number, "block_timestamp": block_timestamp} + if key not in need_collect_token_id_set: + need_collect_token_id_data.append(data) + need_collect_token_id_set.add(key) + if token_id in early_token_id_data: + early_block_number, early_block_timestamp = early_token_id_data[token_id] + if block_number < early_block_number: + early_token_id_data[token_id] = (block_number, block_timestamp) + else: + early_token_id_data[token_id] = (block_number, block_timestamp) + + if len(need_collect_token_id_data) == 0: + return + + # call owners + owner_info = owner_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + need_collect_token_id_data, + self._nft_address, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + # call positions + token_infos = positions_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + owner_info, + self._nft_address, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + token_id_current_status = {} + token_owner_dict = {} + for data in token_infos: + token_id = data["token_id"] + block_number = data["block_number"] + block_timestamp = data["block_timestamp"] + if token_id not in self._exist_token_ids: + # need save token_info + fee = data["fee"] + key = (data["token0"], data["token1"], fee) + pool_address = info_pool_dict[key] + tick_lower = (data["tickLower"],) + tick_upper = (data["tickUpper"],) + self._collect_item( + UniswapV3Token.type(), + UniswapV3Token( + nft_address=self._nft_address, + token_id=token_id, + pool_address=pool_address, + tick_lower=tick_lower, + tick_upper=tick_upper, + fee=fee, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + self._exist_token_ids[token_id] = pool_address + else: + pool_address = self._exist_token_ids[token_id] + wallet_address = constants.ZERO_ADDRESS + if "owner" in data: + wallet_address = data["owner"] + token_owner_dict.setdefault(token_id, {})[block_number] = wallet_address + liquidity = 0 + if "liquidity" in data: + liquidity = data["liquidity"] + detail = UniswapV3TokenDetail( + nft_address=self._nft_address, + pool_address=pool_address, + token_id=token_id, + wallet_address=wallet_address, + liquidity=liquidity, + block_number=block_number, + block_timestamp=block_timestamp, + ) + self._collect_item(UniswapV3TokenDetail.type(), detail) + token_id = detail.token_id + if token_id not in token_id_current_status or block_number > token_id_current_status[token_id].block_number: + token_id_current_status[token_id] = create_token_status(detail) + + for data in token_id_current_status.values(): + self._collect_item(UniswapV3TokenCurrentStatus.type(), data) + + # collect fee and liquidity + for log in logs: + topic0 = log.topic0 + block_number = log.block_number + block_timestamp = log.block_timestamp + if topic0 not in ( + constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0, + constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0, + constants.UNISWAP_V3_TOKEN_COLLECT_FEE_TOPIC0, + ): + continue + token_id = util.parse_hex_to_int256(log.topic1) + owner = token_owner_dict[token_id][block_number] + pool_address = self._exist_token_ids[token_id] + pool_info = self._exist_pool_infos[pool_address] + if ( + topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 + ): + liquidity_hex, amount0_hex, amount1_hex = split_hex_string(log.data) + action_type = ( + constants.DECREASE_TYPE + if topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + else constants.INCREASE_TYPE + ) + self._collect_item( + UniswapV3TokenUpdateLiquidity.type(), + UniswapV3TokenUpdateLiquidity( + nft_address=self._nft_address, + token_id=token_id, + owner=owner, + action_type=action_type, + transaction_hash=log.transaction_hash, + liquidity=util.parse_hex_to_int256(liquidity_hex), + amount0=util.parse_hex_to_int256(amount0_hex), + amount1=util.parse_hex_to_int256(amount1_hex), + pool_address=pool_address, + token0_address=pool_info.token0_address, + token1_address=pool_info.token1_address, + log_index=log.log_index, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + else: + recipient_hex, amount0_hex, amount1_hex = split_hex_string(log.data) + self._collect_item( + UniswapV3TokenCollectFee.type(), + UniswapV3TokenCollectFee( + nft_address=self._nft_address, + token_id=token_id, + owner=owner, + transaction_hash=log.transaction_hash, + recipient=util.parse_hex_to_address(recipient_hex), + amount0=util.parse_hex_to_int256(amount0_hex), + amount1=util.parse_hex_to_int256(amount1_hex), + pool_address=pool_address, + token0_address=pool_info.token0_address, + token1_address=pool_info.token1_address, + log_index=log.log_index, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + + def _process(self, **kwargs): + self._data_buff[UniswapV3Token.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3TokenDetail.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3TokenCurrentStatus.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3TokenUpdateLiquidity.type()].sort(key=lambda x: x.block_number) + self._data_buff[UniswapV3TokenCollectFee.type()].sort(key=lambda x: x.block_number) + + +def get_exist_pools(db_service, nft_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Pools).filter(UniswapV3Pools.nft_address == bytes.fromhex(nft_address[2:])).all() + ) + history_pools = {} + if result is not None: + for item in result: + pool_key = "0x" + item.pool_address.hex() + history_pools[pool_key] = UniswapV3Pool( + pool_address=pool_key, + nft_address="0x" + item.nft_address.hex(), + factory_address="0x" + item.factory_address.hex(), + token0_address="0x" + item.token0_address.hex(), + token1_address="0x" + item.token1_address.hex(), + fee=item.fee, + tick_spacing=item.tick_spacing, + block_number=item.block_number, + block_timestamp=item.block_timestamp, + ) + except Exception as e: + raise e + finally: + session.close() + + return history_pools + + +def get_exist_token_ids(db_service, nft_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Tokens.token_id, UniswapV3Tokens.pool_address) + .filter( + UniswapV3Tokens.nft_address == bytes.fromhex(nft_address[2:]), + ) + .all() + ) + history_token = {} + if result is not None: + for item in result: + token_id = item.token_id + history_token[token_id] = "0x" + item.pool_address.hex() + except Exception as e: + raise e + finally: + session.close() + return history_token + + +def build_token_id_method_data(web3, token_ids, nft_address, fn, abi_list): + parameters = [] + contract = web3.eth.contract(address=Web3.to_checksum_address(nft_address), abi=abi_list) + + for idx, token in enumerate(token_ids): + token_data = { + "request_id": idx, + "param_to": nft_address, + "param_number": hex(token["block_number"]), + } + token.update(token_data) + + try: + # Encode the ABI for the specific token_id + data = contract.encodeABI(fn_name=fn, args=[token["token_id"]]) + token["param_data"] = data + except Exception as e: + logger.error( + f"Encoding token id {token['token_id']} for function {fn} failed. " + f"NFT address: {nft_address}. " + f"Exception: {e}." + ) + + parameters.append(token) + return parameters + + +def positions_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list, batch_size, max_workers): + if len(requests) == 0: + return [] + + fn_name = "positions" + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + def process_batch(batch): + if is_batch: + response = make_requests(params=json.dumps(batch)) + else: + response = [make_requests(params=json.dumps(batch[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token["nonce"] = decoded_data[0] + token["operator"] = decoded_data[1] + token["token0"] = decoded_data[2] + token["token1"] = decoded_data[3] + token["fee"] = decoded_data[4] + token["tickLower"] = decoded_data[5] + token["tickUpper"] = decoded_data[6] + token["liquidity"] = decoded_data[7] + token["feeGrowthInside0LastX128"] = decoded_data[8] + token["feeGrowthInside1LastX128"] = decoded_data[9] + token["tokensOwed0"] = decoded_data[10] + token["tokensOwed1"] = decoded_data[11] + + except Exception as e: + logger.error( + f"Decoding positions failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + all_token_infos = [] + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = [] + for i in range(0, len(token_name_rpc), batch_size): + batch = token_name_rpc[i : i + batch_size] + futures.append(executor.submit(process_batch, batch)) + + for future in as_completed(futures): + try: + result = future.result() + all_token_infos.extend(result) + except Exception as e: + logger.error(f"Batch processing failed with exception: {e}") + + return all_token_infos + + +def owner_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list, batch_size, max_workers): + if len(requests) == 0: + return [] + + fn_name = "ownerOf" + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + def process_batch(batch): + if is_batch: + response = make_requests(params=json.dumps(batch)) + else: + response = [make_requests(params=json.dumps(batch[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token["owner"] = decoded_data[0] + + except Exception as e: + logger.error( + f"Decoding ownerOf failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + # 分批处理请求 + all_token_infos = [] + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = [] + for i in range(0, len(token_name_rpc), batch_size): + batch = token_name_rpc[i : i + batch_size] + futures.append(executor.submit(process_batch, batch)) + + for future in as_completed(futures): + try: + result = future.result() + all_token_infos.extend(result) + except Exception as e: + logger.error(f"Batch processing failed with exception: {e}") + + return all_token_infos + + +def create_token_status(detail: UniswapV3TokenDetail) -> UniswapV3TokenCurrentStatus: + return UniswapV3TokenCurrentStatus( + **{field.name: getattr(detail, field.name) for field in fields(UniswapV3TokenDetail)} + ) + + +def split_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + + if len(hex_string) == 192: + part1 = hex_string[:64] + part2 = hex_string[64:128] + part3 = hex_string[128:] + return part1, part2, part3 + else: + raise ValueError("The data is not belong to Uniswap-V3 Liquidity") diff --git a/indexer/modules/custom/uniswap_v3/util.py b/indexer/modules/custom/uniswap_v3/util.py index ca709c59d..b719a0d68 100644 --- a/indexer/modules/custom/uniswap_v3/util.py +++ b/indexer/modules/custom/uniswap_v3/util.py @@ -40,3 +40,20 @@ def build_no_input_method_data(web3, requests, fn, abi_list, contract_address_ke parameters.append(token) return parameters + + +def parse_hex_to_address(hex_string): + hex_string = hex_string.lower().replace("0x", "") + + if len(hex_string) > 40: + hex_string = hex_string[-40:] + + hex_string = hex_string.zfill(40) + return Web3.to_checksum_address(hex_string).lower() + + +def parse_hex_to_int256(hex_string): + value = Web3.to_int(hexstr=hex_string) + if value >= 2**255: + value -= 2**256 + return value diff --git a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py new file mode 100644 index 000000000..c0dbca84d --- /dev/null +++ b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py @@ -0,0 +1,321 @@ +"""add uniswap v3 enhance table + +Revision ID: f4efa18760cc +Revises: 2359a28d63cb +Create Date: 2024-09-06 13:24:28.201489 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "f4efa18760cc" +down_revision: Union[str, None] = "2359a28d63cb" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + + op.create_table( + "af_uniswap_v3_pool_prices_current", + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("factory_address", postgresql.BYTEA(), nullable=True), + sa.Column("sqrt_price_x96", sa.NUMERIC(precision=100), nullable=True), + sa.Column("tick", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("pool_address"), + ) + op.create_table( + "af_uniswap_v3_pool_prices_hist", + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("sqrt_price_x96", sa.NUMERIC(precision=100), nullable=True), + sa.Column("tick", sa.NUMERIC(precision=100), nullable=True), + sa.Column("factory_address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"), + ) + op.create_table( + "af_uniswap_v3_pool_swap_hist", + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("nft_address", postgresql.BYTEA(), nullable=True), + sa.Column("transaction_from_address", postgresql.BYTEA(), nullable=True), + sa.Column("sender", postgresql.BYTEA(), nullable=True), + sa.Column("recipient", postgresql.BYTEA(), nullable=True), + sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), + sa.Column("tick", sa.NUMERIC(precision=100), nullable=True), + sa.Column("sqrt_price_x96", sa.NUMERIC(precision=100), nullable=True), + sa.Column("amount0", sa.NUMERIC(precision=100), nullable=True), + sa.Column("amount1", sa.NUMERIC(precision=100), nullable=True), + sa.Column("token0_address", postgresql.BYTEA(), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("pool_address", "transaction_hash", "log_index"), + ) + op.create_table( + "af_uniswap_v3_pools", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("factory_address", postgresql.BYTEA(), nullable=True), + sa.Column("token0_address", postgresql.BYTEA(), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=True), + sa.Column("fee", sa.NUMERIC(precision=100), nullable=True), + sa.Column("tick_spacing", sa.NUMERIC(precision=100), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "pool_address"), + ) + op.create_table( + "af_uniswap_v3_token_collect_fee_hist", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("owner", postgresql.BYTEA(), nullable=True), + sa.Column("recipient", postgresql.BYTEA(), nullable=True), + sa.Column("amount0", sa.NUMERIC(precision=100), nullable=True), + sa.Column("amount1", sa.NUMERIC(precision=100), nullable=True), + sa.Column("pool_address", postgresql.BYTEA(), nullable=True), + sa.Column("token0_address", postgresql.BYTEA(), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"), + ) + op.create_index( + "af_uniswap_v3_token_collect_fee_hist_owner_index", + "af_uniswap_v3_token_collect_fee_hist", + ["owner"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_collect_fee_hist_pool_index", + "af_uniswap_v3_token_collect_fee_hist", + ["pool_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_collect_fee_hist_token0_index", + "af_uniswap_v3_token_collect_fee_hist", + ["token0_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_collect_fee_hist_token1_index", + "af_uniswap_v3_token_collect_fee_hist", + ["token1_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_collect_fee_hist_token_id_index", + "af_uniswap_v3_token_collect_fee_hist", + ["token_id"], + unique=False, + ) + op.create_table( + "af_uniswap_v3_token_data_current", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=True), + sa.Column("pool_address", postgresql.BYTEA(), nullable=True), + sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id"), + ) + op.create_index( + "af_uniswap_v3_token_data_current_wallet_desc_index", + "af_uniswap_v3_token_data_current", + [sa.text("wallet_address DESC")], + unique=False, + ) + op.create_table( + "af_uniswap_v3_token_data_hist", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=True), + sa.Column("pool_address", postgresql.BYTEA(), nullable=True), + sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number"), + ) + op.create_index( + "af_uniswap_v3_token_data_hist_token_block_desc_index", + "af_uniswap_v3_token_data_hist", + [sa.text("nft_address DESC"), sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_data_hist_wallet_token_block_desc_index", + "af_uniswap_v3_token_data_hist", + [sa.text("wallet_address DESC"), sa.text("nft_address DESC"), sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_table( + "af_uniswap_v3_token_liquidity_hist", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("owner", postgresql.BYTEA(), nullable=True), + sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), + sa.Column("amount0", sa.NUMERIC(precision=100), nullable=True), + sa.Column("amount1", sa.NUMERIC(precision=100), nullable=True), + sa.Column("pool_address", postgresql.BYTEA(), nullable=True), + sa.Column("token0_address", postgresql.BYTEA(), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=True), + sa.Column("action_type", sa.VARCHAR(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"), + ) + op.create_index( + "af_uniswap_v3_token_liquidity_hist_owner_index", "af_uniswap_v3_token_liquidity_hist", ["owner"], unique=False + ) + op.create_index( + "af_uniswap_v3_token_liquidity_hist_pool_index", + "af_uniswap_v3_token_liquidity_hist", + ["pool_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_liquidity_hist_token0_index", + "af_uniswap_v3_token_liquidity_hist", + ["token0_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_liquidity_hist_token1_index", + "af_uniswap_v3_token_liquidity_hist", + ["token1_address"], + unique=False, + ) + op.create_index( + "af_uniswap_v3_token_liquidity_hist_token_id_index", + "af_uniswap_v3_token_liquidity_hist", + ["token_id"], + unique=False, + ) + op.create_table( + "af_uniswap_v3_tokens", + sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=True), + sa.Column("tick_lower", sa.NUMERIC(precision=100), nullable=True), + sa.Column("tick_upper", sa.NUMERIC(precision=100), nullable=True), + sa.Column("fee", sa.NUMERIC(precision=100), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id"), + ) + op.create_index("af_uniswap_v3_tokens_nft_index", "af_uniswap_v3_tokens", ["nft_address"], unique=False) + op.drop_index("feature_uniswap_v3_tokens_nft_index", table_name="feature_uniswap_v3_tokens") + op.drop_table("feature_uniswap_v3_tokens") + op.drop_table("feature_uniswap_v3_pools") + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "feature_uniswap_v3_pools", + sa.Column("nft_address", postgresql.BYTEA(), autoincrement=False, nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), autoincrement=False, nullable=False), + sa.Column("token0_address", postgresql.BYTEA(), autoincrement=False, nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), autoincrement=False, nullable=True), + sa.Column("fee", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), + sa.Column("tick_spacing", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), + sa.Column( + "create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True + ), + sa.Column( + "update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True + ), + sa.Column("called_block_number", sa.BIGINT(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint("nft_address", "pool_address", name="feature_uniswap_v3_pools_pkey"), + ) + op.create_table( + "feature_uniswap_v3_tokens", + sa.Column("nft_address", postgresql.BYTEA(), autoincrement=False, nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), autoincrement=False, nullable=True), + sa.Column("tick_lower", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), + sa.Column("tick_upper", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), + sa.Column("fee", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), + sa.Column( + "create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True + ), + sa.Column( + "update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True + ), + sa.Column("called_block_number", sa.BIGINT(), autoincrement=False, nullable=True), + sa.PrimaryKeyConstraint("nft_address", "token_id", name="feature_uniswap_v3_tokens_pkey"), + ) + op.create_index("feature_uniswap_v3_tokens_nft_index", "feature_uniswap_v3_tokens", ["nft_address"], unique=False) + + op.drop_index("af_uniswap_v3_tokens_nft_index", table_name="af_uniswap_v3_tokens") + op.drop_table("af_uniswap_v3_tokens") + op.drop_index("af_uniswap_v3_token_liquidity_hist_token_id_index", table_name="af_uniswap_v3_token_liquidity_hist") + op.drop_index("af_uniswap_v3_token_liquidity_hist_token1_index", table_name="af_uniswap_v3_token_liquidity_hist") + op.drop_index("af_uniswap_v3_token_liquidity_hist_token0_index", table_name="af_uniswap_v3_token_liquidity_hist") + op.drop_index("af_uniswap_v3_token_liquidity_hist_pool_index", table_name="af_uniswap_v3_token_liquidity_hist") + op.drop_index("af_uniswap_v3_token_liquidity_hist_owner_index", table_name="af_uniswap_v3_token_liquidity_hist") + op.drop_table("af_uniswap_v3_token_liquidity_hist") + op.drop_index( + "af_uniswap_v3_token_data_hist_wallet_token_block_desc_index", table_name="af_uniswap_v3_token_data_hist" + ) + op.drop_index("af_uniswap_v3_token_data_hist_token_block_desc_index", table_name="af_uniswap_v3_token_data_hist") + op.drop_table("af_uniswap_v3_token_data_hist") + op.drop_index("af_uniswap_v3_token_data_current_wallet_desc_index", table_name="af_uniswap_v3_token_data_current") + op.drop_table("af_uniswap_v3_token_data_current") + op.drop_index( + "af_uniswap_v3_token_collect_fee_hist_token_id_index", table_name="af_uniswap_v3_token_collect_fee_hist" + ) + op.drop_index( + "af_uniswap_v3_token_collect_fee_hist_token1_index", table_name="af_uniswap_v3_token_collect_fee_hist" + ) + op.drop_index( + "af_uniswap_v3_token_collect_fee_hist_token0_index", table_name="af_uniswap_v3_token_collect_fee_hist" + ) + op.drop_index("af_uniswap_v3_token_collect_fee_hist_pool_index", table_name="af_uniswap_v3_token_collect_fee_hist") + op.drop_index("af_uniswap_v3_token_collect_fee_hist_owner_index", table_name="af_uniswap_v3_token_collect_fee_hist") + op.drop_table("af_uniswap_v3_token_collect_fee_hist") + op.drop_table("af_uniswap_v3_pools") + op.drop_table("af_uniswap_v3_pool_swap_hist") + op.drop_table("af_uniswap_v3_pool_prices_hist") + op.drop_table("af_uniswap_v3_pool_prices_current") + # ### end Alembic commands ### From e14fe4d718eb1a67a43e4b49f1dde35a9fd03e21 Mon Sep 17 00:00:00 2001 From: li xiang Date: Fri, 6 Sep 2024 18:10:14 +0800 Subject: [PATCH 20/70] Upgrade VERSION to 0.3.0 (#98) --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 0ea3a944b..0d91a54c7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.2.0 +0.3.0 From 9ea462c34cdefaf9d6daee8a32955b7fa5c29e4d Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:13:40 +0800 Subject: [PATCH 21/70] re chain migration scripts (#100) --- migrations/versions/20240802_add_exception_recorder_table.py | 2 +- migrations/versions/20240802_add_l2_chain_table.py | 2 +- ...20240805_add_transaction_from_address_column_to_contracts.py | 2 +- migrations/versions/20240831_add_ens.py | 2 +- migrations/versions/20240906_add_uniswap_v3_enhance_table.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/migrations/versions/20240802_add_exception_recorder_table.py b/migrations/versions/20240802_add_exception_recorder_table.py index cda0cdbd1..85710cb06 100644 --- a/migrations/versions/20240802_add_exception_recorder_table.py +++ b/migrations/versions/20240802_add_exception_recorder_table.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "040e5251f45d" -down_revision: Union[str, None] = "aa99dd347ef1" +down_revision: Union[str, None] = "9a1e927f02bb" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/migrations/versions/20240802_add_l2_chain_table.py b/migrations/versions/20240802_add_l2_chain_table.py index ada9443ff..6a2b2dec1 100644 --- a/migrations/versions/20240802_add_l2_chain_table.py +++ b/migrations/versions/20240802_add_l2_chain_table.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "e3a3e2114b9c" -down_revision: Union[str, None] = "9a1e927f02bb" +down_revision: Union[str, None] = "040e5251f45d" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py b/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py index ceccd4db8..efae9f6e0 100644 --- a/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py +++ b/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "832fa52da346" -down_revision: Union[str, None] = "040e5251f45d" +down_revision: Union[str, None] = "aa99dd347ef1" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/migrations/versions/20240831_add_ens.py b/migrations/versions/20240831_add_ens.py index cd276227d..cfcd678d1 100644 --- a/migrations/versions/20240831_add_ens.py +++ b/migrations/versions/20240831_add_ens.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "43d14640a8ac" -down_revision: Union[str, None] = "2359a28d63cb" +down_revision: Union[str, None] = "6c2eecd6316b" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py index c0dbca84d..4c1eb0958 100644 --- a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py +++ b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py @@ -14,7 +14,7 @@ # revision identifiers, used by Alembic. revision: str = "f4efa18760cc" -down_revision: Union[str, None] = "2359a28d63cb" +down_revision: Union[str, None] = "43d14640a8ac" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None From b6bfe8090cc312477bdd3fdb83e0110768f67705 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 6 Sep 2024 18:48:41 +0800 Subject: [PATCH 22/70] Fixed/uniswap v3 token burn (#101) * fix tokens burn ,not positions * cover token id mint and burn same block --- .../custom/uniswap_v3/uniswap_v3_token_job.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py index ba10e4ee0..18726dba6 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py @@ -163,6 +163,8 @@ def _collect(self, **kwargs): block_timestamp = data["block_timestamp"] if token_id not in self._exist_token_ids: # need save token_info + if "fee" not in data: + continue fee = data["fee"] key = (data["token0"], data["token1"], fee) pool_address = info_pool_dict[key] @@ -220,7 +222,15 @@ def _collect(self, **kwargs): ): continue token_id = util.parse_hex_to_int256(log.topic1) - owner = token_owner_dict[token_id][block_number] + owner = constants.ZERO_ADDRESS + if token_id in token_owner_dict: + if block_number in token_owner_dict[token_id]: + owner = token_owner_dict[token_id][block_number] + if token_id not in self._exist_token_ids: + logger.error( + f"the token id {token_id} block_number = {block_number} transaction_hash = {log.transaction_hash} is not collected" + ) + continue pool_address = self._exist_token_ids[token_id] pool_info = self._exist_pool_infos[pool_address] if ( From 8d89c30e154e96340a2a1ffb7d1954b8823e4510 Mon Sep 17 00:00:00 2001 From: li xiang Date: Fri, 6 Sep 2024 18:50:29 +0800 Subject: [PATCH 23/70] rename opensea endpoint (#99) --- indexer/modules/custom/opensea/endpoint/routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indexer/modules/custom/opensea/endpoint/routes.py b/indexer/modules/custom/opensea/endpoint/routes.py index 1423f57d4..b86d03cfa 100644 --- a/indexer/modules/custom/opensea/endpoint/routes.py +++ b/indexer/modules/custom/opensea/endpoint/routes.py @@ -305,7 +305,7 @@ def get_latest_opensea_transaction_by_address(address: Union[str, bytes]): } -@opensea_namespace.route("/v1/explorer/custom/opensea/address/
/profile") +@opensea_namespace.route("/v1/aci/
/opensea/profile") class ExplorerCustomOpenseaAddressProfile(Resource): @cache.cached(timeout=60) def get(self, address): @@ -316,7 +316,7 @@ def get(self, address): return profile, 200 -@opensea_namespace.route("/v1/explorer/custom/opensea/address/
/transactions") +@opensea_namespace.route("/v1/aci/
/opensea/transactions") class ExplorerCustomOpenseaAddressTransactions(Resource): @cache.cached(timeout=10) def get(self, address): From fe30b1839c0f2d98f3394844fd23a5e114ea430b Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:12:48 +0800 Subject: [PATCH 24/70] update uniswap v3 path (#102) --- indexer/modules/custom/uniswap_v3/endpoints/routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexer/modules/custom/uniswap_v3/endpoints/routes.py b/indexer/modules/custom/uniswap_v3/endpoints/routes.py index bca1dc92c..59e10f2df 100644 --- a/indexer/modules/custom/uniswap_v3/endpoints/routes.py +++ b/indexer/modules/custom/uniswap_v3/endpoints/routes.py @@ -28,7 +28,7 @@ Q96 = 2**96 -@uniswap_v3_namespace.route("/v1/uniswapv3/current_holding/") +@uniswap_v3_namespace.route("/v1/aci//uniswapv3/current_holding") class UniswapV3WalletHolding(Resource): def get(self, wallet_address): wallet_address = wallet_address.lower() @@ -116,7 +116,7 @@ def get(self, wallet_address): return result, 200 -@uniswap_v3_namespace.route("/v1/uniswapv3/first_liquidity_time/") +@uniswap_v3_namespace.route("/v1/aci//uniswapv3/first_liquidity_time") class UniswapV3WalletLiquidityDetail(Resource): def get(self, wallet_address): wallet_address = wallet_address.lower() @@ -140,7 +140,7 @@ def get(self, wallet_address): }, 200 -@uniswap_v3_namespace.route("/v1/uniswapv3/liquidity_pools/") +@uniswap_v3_namespace.route("/v1/aci//uniswapv3/provide_liquidity_pool_count") class UniswapV3WalletLiquidityDetail(Resource): def get(self, wallet_address): wallet_address = wallet_address.lower() From e9f7ba6e4adf2b0587832bce47ba27a6004ae1ae Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 6 Sep 2024 19:13:19 +0800 Subject: [PATCH 25/70] Feature/add new paramete to reorg (#103) * add parameter multicall --- cli/reorg.py | 28 +++++++++++++++++++ indexer/controller/scheduler/job_scheduler.py | 1 + indexer/jobs/check_block_consensus_job.py | 3 ++ 3 files changed, 32 insertions(+) diff --git a/cli/reorg.py b/cli/reorg.py index d8435627c..3e1892195 100644 --- a/cli/reorg.py +++ b/cli/reorg.py @@ -1,4 +1,5 @@ import logging +import os import click @@ -97,6 +98,15 @@ envvar="LOG_FILE", help="Log file", ) +@click.option( + "-m", + "--multicall", + default=False, + show_default=True, + type=bool, + help="if `multicall` is set to True, it will decrease the consume of rpc calls", + envvar="MULTI_CALL_ENABLE", +) @click.option("--cache", default=None, show_default=True, type=str, envvar="CACHE", help="Cache") def reorg( provider_uri, @@ -107,8 +117,10 @@ def reorg( batch_size, debug_batch_size, db_version="head", + multicall=True, log_file=None, cache=None, + config_file=None, ): configure_logging(log_file) configure_signals() @@ -127,6 +139,21 @@ def reorg( logging.error("No postgres url provided. Exception recorder will not be useful.") exit(1) + if config_file: + if not os.path.exists(config_file): + raise click.ClickException(f"Config file {config_file} not found") + with open(config_file, "r") as f: + if config_file.endswith(".json"): + import json + + config.update(json.load(f)) + elif config_file.endswith(".yaml") or config_file.endswith(".yml"): + import yaml + + config.update(yaml.safe_load(f)) + else: + raise click.ClickException(f"Config file {config_file} is not supported)") + entity_types = calculate_entity_value(",".join(ALL_ENTITY_COLLECTIONS)) output_types = list(generate_output_types(entity_types)) @@ -139,6 +166,7 @@ def reorg( required_output_types=output_types, config=config, cache=cache, + multicall=multicall, ) controller = ReorgController( diff --git a/indexer/controller/scheduler/job_scheduler.py b/indexer/controller/scheduler/job_scheduler.py index 595086b17..409f755f7 100644 --- a/indexer/controller/scheduler/job_scheduler.py +++ b/indexer/controller/scheduler/job_scheduler.py @@ -188,6 +188,7 @@ def instantiate_jobs(self): batch_web3_debug_provider=self.batch_web3_debug_provider, item_exporters=self.item_exporters, batch_size=self.batch_size, + multicall=self._is_multicall, debug_batch_size=self.debug_batch_size, max_workers=self.max_workers, config=self.config, diff --git a/indexer/jobs/check_block_consensus_job.py b/indexer/jobs/check_block_consensus_job.py index bdaf29728..3a0ee0e34 100644 --- a/indexer/jobs/check_block_consensus_job.py +++ b/indexer/jobs/check_block_consensus_job.py @@ -19,6 +19,7 @@ def __init__(self, **kwargs): self._config = kwargs["config"] self._batch_web3_debug_provider = kwargs["batch_web3_debug_provider"] self._batch_size = kwargs["batch_size"] + self._multicall = kwargs["multicall"] self._debug_batch_size = kwargs["debug_batch_size"] self.db_service = self._config.get("db_service") if "db_service" in self._config else None self._postgre_uri = self.db_service.get_service_uri() if self.db_service else None @@ -58,6 +59,8 @@ def _process(self, **kwargs): f"{self._batch_size}", "--debug-batch-size", f"{self._debug_batch_size}", + "-m", + f"{self._multicall}", "-pg", self._postgre_uri, "--block-number", From 7665a11311a85b48c877f9941a72934dcc73916a Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:42:59 +0800 Subject: [PATCH 26/70] fixed logs filter (#106) * fixed logs filter * cover uniswap v3 collect fee * filter liquidity = 0 --- indexer/modules/custom/uniswap_v3/endpoints/routes.py | 9 ++++++--- .../modules/custom/uniswap_v3/uniswap_v3_token_job.py | 4 +++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/indexer/modules/custom/uniswap_v3/endpoints/routes.py b/indexer/modules/custom/uniswap_v3/endpoints/routes.py index 59e10f2df..ad88fdba0 100644 --- a/indexer/modules/custom/uniswap_v3/endpoints/routes.py +++ b/indexer/modules/custom/uniswap_v3/endpoints/routes.py @@ -7,7 +7,7 @@ from flask import request from flask_restx import Resource -from sqlalchemy import and_, func +from sqlalchemy import and_, asc, desc, func from api.app import explorer from api.app.cache import cache @@ -36,6 +36,7 @@ def get(self, wallet_address): holdings = ( db.session.query(UniswapV3TokenCurrentStatus) .filter(UniswapV3TokenCurrentStatus.wallet_address == address_bytes) + .filter(UniswapV3TokenCurrentStatus.liquidity > 0) .all() ) @@ -124,6 +125,7 @@ def get(self, wallet_address): first_holding = ( db.session.query(UniswapV3TokenLiquidityRecords) .filter(UniswapV3TokenLiquidityRecords.owner == address_bytes) + .order_by(UniswapV3TokenLiquidityRecords.block_timestamp) .first() ) if not first_holding: @@ -135,8 +137,9 @@ def get(self, wallet_address): return { "first_provide_time": first_provide_time, "token_id": str(first_holding.token_id), - "token0_address": first_holding.token0_address, - "token1_address": first_holding.token1_address, + "transaction_hash": "0x" + first_holding.transaction_hash.hex(), + "token0_address": "0x" + first_holding.token0_address.hex(), + "token1_address": "0x" + first_holding.token1_address.hex(), }, 200 diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py index 18726dba6..c7c934791 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py @@ -113,6 +113,7 @@ def _collect(self, **kwargs): elif ( topic0 == constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 or topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_TOKEN_COLLECT_FEE_TOPIC0 ): token_id_hex = log.topic1 else: @@ -212,6 +213,8 @@ def _collect(self, **kwargs): # collect fee and liquidity for log in logs: + if log.address != self._nft_address: + continue topic0 = log.topic0 block_number = log.block_number block_timestamp = log.block_timestamp @@ -485,7 +488,6 @@ def process_batch(batch): token_infos.append(token) return token_infos - # 分批处理请求 all_token_infos = [] with ThreadPoolExecutor(max_workers=max_workers) as executor: futures = [] From 1be9afa04019b87e3f1f9bc97dbf2db184ab526c Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:45:47 +0800 Subject: [PATCH 27/70] Format deposit bridge api (#104) --- api/app/deposit_to_l2/routes.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/api/app/deposit_to_l2/routes.py b/api/app/deposit_to_l2/routes.py index ffca7f5a6..99922ba18 100644 --- a/api/app/deposit_to_l2/routes.py +++ b/api/app/deposit_to_l2/routes.py @@ -24,7 +24,7 @@ app_config = get_config() -@token_deposit_namespace.route("/v1/explorer/deposit/transactions") +@token_deposit_namespace.route("/v1/aci/deposit/transactions") class ExplorerDepositTransactions(Resource): @cache.cached(timeout=10, query_string=True) def get(self): @@ -119,13 +119,11 @@ def get(self): }, 200 -@token_deposit_namespace.route("/v1/explorer/deposit/statistic") -class ExplorerDepositStatistic(Resource): +@token_deposit_namespace.route("/v1/aci//deposit/current") +class ExplorerDepositCurrent(Resource): @cache.cached(timeout=10, query_string=True) - def get(self): - wallet_address = flask.request.args.get("wallet_address", None) - + def get(self, wallet_address): if wallet_address is None: raise APIError( f"parameter 'wallet_address' are required", @@ -148,13 +146,11 @@ def get(self): }, 200 -@token_deposit_namespace.route("/v1/explorer/deposit/bridge_times") +@token_deposit_namespace.route("/v1/aci//deposit/bridge_times") class ExplorerDepositBridgeTimes(Resource): @cache.cached(timeout=10, query_string=True) - def get(self): - wallet_address = flask.request.args.get("wallet_address", None) - + def get(self, wallet_address): if wallet_address is None: raise APIError( f"parameter 'wallet_address' are required", @@ -169,13 +165,11 @@ def get(self): }, 200 -@token_deposit_namespace.route("/v1/explorer/deposit/chain_list") +@token_deposit_namespace.route("/v1/aci//deposit/chain_list") class ExplorerDepositChainList(Resource): @cache.cached(timeout=10, query_string=True) - def get(self): - wallet_address = flask.request.args.get("wallet_address", None) - + def get(self, wallet_address): if wallet_address is None: raise APIError( f"parameter 'wallet_address' are required", @@ -191,13 +185,11 @@ def get(self): }, 200 -@token_deposit_namespace.route("/v1/explorer/deposit/assets_list") +@token_deposit_namespace.route("/v1/aci//deposit/assets_list") class ExplorerDepositAssetsList(Resource): @cache.cached(timeout=10, query_string=True) - def get(self): - wallet_address = flask.request.args.get("wallet_address", None) - + def get(self, wallet_address): if wallet_address is None: raise APIError( f"parameter 'wallet_address' are required", From 74288d1d22f7b1463aca2f69eaca3a898f6012d8 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:46:06 +0800 Subject: [PATCH 28/70] add more reorg-related logs (#105) * add more reorg-related logs --- indexer/controller/reorg_controller.py | 13 +++++++--- indexer/jobs/base_job.py | 33 ++++++++++++++++---------- indexer/jobs/export_blocks_job.py | 15 ++++++------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/indexer/controller/reorg_controller.py b/indexer/controller/reorg_controller.py index 27f95e693..efd6d0190 100644 --- a/indexer/controller/reorg_controller.py +++ b/indexer/controller/reorg_controller.py @@ -181,6 +181,7 @@ def check_job_runnable(self, job_id): def wake_up_next_job(self): session = self.db_service.get_service_session() + job = None try: job = ( session.query(FixRecord) @@ -188,15 +189,21 @@ def wake_up_next_job(self): .order_by(FixRecord.create_time) .first() ) + except Exception as e: + logging.error(f"Wake up uncompleted job error: {e}.") + finally: + session.close() - if job: + if job: + try: self.action( job_id=job.job_id, block_number=job.last_fixed_block_number - 1, remains=job.remain_process, ) - finally: - session.close() + except Exception as e: + if job: + logging.error(f"Catch exception while waking up job: {job.job_id}. error: {e}") def check_block_been_synced(self, block_number): session = self.db_service.get_service_session() diff --git a/indexer/jobs/base_job.py b/indexer/jobs/base_job.py index c9bc493e9..f545ee6dc 100644 --- a/indexer/jobs/base_job.py +++ b/indexer/jobs/base_job.py @@ -78,19 +78,26 @@ def __init__(self, **kwargs): def run(self, **kwargs): try: - self._start(**kwargs) + if self.able_to_reorg and self._reorg: + start_time = datetime.now() + self.logger.info(f"Stage _start starting.") + self._start(**kwargs) + self.logger.info(f"Stage _start finished. Took {datetime.now() - start_time}") if not self._reorg or self._should_reorg: start_time = datetime.now() + self.logger.info(f"Stage collect starting.") self._collect(**kwargs) self.logger.info(f"Stage collect finished. Took {datetime.now() - start_time}") start_time = datetime.now() + self.logger.info(f"Stage process starting.") self._process(**kwargs) self.logger.info(f"Stage process finished. Took {datetime.now() - start_time}") if not self._reorg: start_time = datetime.now() + self.logger.info(f"Stage export starting.") self._export() self.logger.info(f"Stage export finished. Took {datetime.now() - start_time}") @@ -98,21 +105,21 @@ def run(self, **kwargs): self._end() def _start(self, **kwargs): - if self.able_to_reorg and self._reorg: - if self._service is None: - raise FastShutdownError("PG Service is not set") - reorg_block = int(kwargs["start_block"]) + if self._service is None: + raise FastShutdownError("PG Service is not set") + + reorg_block = int(kwargs["start_block"]) - output_table = {} - for domain in self.output_types: - output_table[domain_model_mapping[domain.__name__]["table"]] = domain.type() - # output_table.add(domain_model_mapping[domain.__name__]["table"]) + output_table = {} + for domain in self.output_types: + output_table[domain_model_mapping[domain.__name__]["table"]] = domain.type() + # output_table.add(domain_model_mapping[domain.__name__]["table"]) - for table in output_table.keys(): - if should_reorg(reorg_block, table, self._service): - self._should_reorg_type.add(output_table[table]) - self._should_reorg = True + for table in output_table.keys(): + if should_reorg(reorg_block, table, self._service): + self._should_reorg_type.add(output_table[table]) + self._should_reorg = True def _end(self): if self._reorg: diff --git a/indexer/jobs/export_blocks_job.py b/indexer/jobs/export_blocks_job.py index 3d50b511a..951e2396a 100644 --- a/indexer/jobs/export_blocks_job.py +++ b/indexer/jobs/export_blocks_job.py @@ -54,14 +54,13 @@ def __init__(self, **kwargs): self._reorg_jobs = kwargs.get("reorg_jobs", []) def _start(self, **kwargs): - if self.able_to_reorg and self._reorg: - if self._service is None: - raise FastShutdownError("PG Service is not set") - - reorg_block = int(kwargs["start_block"]) - set_reorg_sign(self._reorg_jobs, reorg_block, self._service) - self._should_reorg_type.add(Block.type()) - self._should_reorg = True + if self._service is None: + raise FastShutdownError("PG Service is not set") + + reorg_block = int(kwargs["start_block"]) + set_reorg_sign(self._reorg_jobs, reorg_block, self._service) + self._should_reorg_type.add(Block.type()) + self._should_reorg = True def _end(self): super()._end() From bb1ac0e8297b7fc70c37531f1237417ccf7a05ac Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Mon, 9 Sep 2024 14:38:38 +0800 Subject: [PATCH 29/70] ens token bugfixed (#108) --- .../modules/custom/hemera_ens/ens_handler.py | 44 +++++++++++-------- .../modules/custom/hemera_ens/extractors.py | 6 ++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index 0ab82bdf1..dfcfc2781 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -6,8 +6,9 @@ # @Brief import logging from collections import defaultdict -from dataclasses import asdict +from dataclasses import asdict, replace from multiprocessing import Queue +from typing import List from eth_abi.codec import ABICodec from web3 import Web3 @@ -222,25 +223,32 @@ def process(self, transaction, logs): start = idx break # merge same node register, keep 0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f delete 0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9 - mark_register = set() - for rr in res: - if ( - rr.event_name == "NameRegistered" - and rr.topic0 == "0xca6abbe9d7f11422cb6ca7629fbf6fe9efb1c621f71ce8f02b9f2a230097404f" - ): - mark_register.add(rr.node) - new_res = [] - for rr in res: - if ( - rr.event_name == "NameRegistered" - and rr.node in mark_register - and rr.topic0 == "0xb3d987963d01b2f68493b4bdb130988f157ea43070d4ad840fee0466ed9370d9" - ): - continue - else: - new_res.append(rr) + new_res = self.merge_ens_middle_d(res) return new_res + def merge_ens_middle_d(self, data: List[ENSMiddleD]) -> List[ENSMiddleD]: + node_dict = {} + + new_data = [] + for item in data: + if item.method == "NameRegistered": + if item.node not in node_dict: + node_dict[item.node] = item + else: + existing = node_dict[item.node] + merged = {} + for key, value in asdict(item).items(): + if value is not None: + merged[key] = value + elif getattr(existing, key) is not None: + merged[key] = getattr(existing, key) + node_dict[item.node] = replace(existing, **merged) + else: + new_data.append(item) + if node_dict: + new_data.extend(list(node_dict.values())) + return new_data + def process_middle(self, lis): if not lis: return [] diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py index 802a5f055..131e2a4af 100644 --- a/indexer/modules/custom/hemera_ens/extractors.py +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -104,7 +104,11 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, token_id = None w_token_id = None for sl in prev_logs: - if sl["address"] == "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" and (sl["topic2"]) == log["topic2"]: + if ( + sl["address"] == "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" + and (sl["topic2"]) == log["topic2"] + and sl["topic0"] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + ): token_id = str(int(sl["topic3"], 16)) if ( sl["address"] == "0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401" From 6d2049e95b0ed8e7169505e71137afb22f9e6fc2 Mon Sep 17 00:00:00 2001 From: li xiang Date: Mon, 9 Sep 2024 16:07:08 +0800 Subject: [PATCH 30/70] Feat/support more seaport protocol (#110) * Support seaport more protocol --- indexer-config.yaml | 23 ++++++- indexer/modules/custom/opensea/opensea_job.py | 67 +++++++++---------- 2 files changed, 54 insertions(+), 36 deletions(-) diff --git a/indexer-config.yaml b/indexer-config.yaml index fb27a0b2c..21b2ec9a0 100644 --- a/indexer-config.yaml +++ b/indexer-config.yaml @@ -6,4 +6,25 @@ opensea_job: seaport_1_5: contract_address: "0x00000000000000adc04c56bf30ac9d3c0aaf14dc" fee_addresses: - - "0x0000a26b00c1f0df003000390027140000faa719" \ No newline at end of file + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_4: + contract_address: "0x00000000000001ad428e4906ae43d8f9852d0dd6" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_3: + contract_address: "0x0000000000000ad24e80fd803c6ac37206a45f15" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_2: + contract_address: "0x00000000000006c7676171937c444f6bde3d6282" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_1: + contract_address: "0x00000000006c3852cbef3e08e8df289169ede581" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + seaport_1_0: + contract_address: "0x00000000006cee72100d161c57ada5bb2be1ca79" + fee_addresses: + - "0x0000a26b00c1f0df003000390027140000faa719" + - "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073" diff --git a/indexer/modules/custom/opensea/opensea_job.py b/indexer/modules/custom/opensea/opensea_job.py index 4ebac1049..ef2f3203e 100644 --- a/indexer/modules/custom/opensea/opensea_job.py +++ b/indexer/modules/custom/opensea/opensea_job.py @@ -1,5 +1,4 @@ import logging -from collections import defaultdict from enum import Enum from typing import List @@ -17,8 +16,6 @@ logger = logging.getLogger(__name__) -from collections import defaultdict - class OpenseaTransactionType(Enum): BUY = 0 @@ -117,9 +114,15 @@ def __init__(self, **kwargs): kwargs["max_workers"], job_name=self.__class__.__name__, ) - - self._seaport_1_6_config = self.user_defined_config.get("seaport_1_6", None) - self._seaport_1_5_config = self.user_defined_config.get("seaport_1_5", None) + self._seaport_configs = { + "1.6": self.user_defined_config.get("seaport_1_6", None), + "1.5": self.user_defined_config.get("seaport_1_5", None), + "1.4": self.user_defined_config.get("seaport_1_4", None), + "1.3": self.user_defined_config.get("seaport_1_3", None), + "1.2": self.user_defined_config.get("seaport_1_2", None), + "1.1": self.user_defined_config.get("seaport_1_1", None), + "1.0": self.user_defined_config.get("seaport_1_0", None), + } def _collect(self, **kwargs): pass @@ -186,41 +189,35 @@ def transfer(self, opensea_logs: List[OpenseaLog]): protocol_version=opensea_log.protocol_version, ) + def process_transaction(self, transaction): + contract_addresses = [log.address for log in (transaction.receipt.logs if transaction.receipt else [])] + + orders = [ + parse_opensea_transaction_order_fulfilled_event( + transaction, + config["contract_address"], + protocol_version=version, + fee_addresses=config.get("fee_addresses"), + ) + for version, config in self._seaport_configs.items() + if config and config.get("contract_address") in contract_addresses + ] + + return [order for sublist in orders for order in sublist] + def _process(self, **kwargs): transactions = self.get_filter_transactions() for transaction in transactions: - orders = [] - if self._seaport_1_6_config: - orders += parse_opensea_transaction_order_fulfilled_event( - transaction, - self._seaport_1_6_config["contract_address"], - protocol_version="1.6", - fee_addresses=self._seaport_1_6_config["fee_addresses"], - ) - if self._seaport_1_5_config: - orders += parse_opensea_transaction_order_fulfilled_event( - transaction, - self._seaport_1_5_config["contract_address"], - protocol_version="1.5", - fee_addresses=self._seaport_1_5_config.get("fee_addresses"), - ) + orders = self.process_transaction(transaction) self._collect_domains(self.transfer(orders)) def get_filter(self): - topic_filter_list = [] - if self._seaport_1_6_config: - topic_filter_list.append( - TopicSpecification( - addresses=[self._seaport_1_6_config["contract_address"]], - topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), - ) - ) - if self._seaport_1_5_config: - topic_filter_list.append( - TopicSpecification( - addresses=[self._seaport_1_5_config["contract_address"]], - topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()), - ) + topic_filter_list = [ + TopicSpecification( + addresses=[config["contract_address"]], topics=list(OPENSEA_EVENT_ABI_SIGNATURE_MAPPING.values()) ) + for config in self._seaport_configs.values() + if config + ] return TransactionFilterByLogs(topic_filter_list) From ea972a52d39866a9235282a1751aa289256a0a56 Mon Sep 17 00:00:00 2001 From: li xiang Date: Mon, 9 Sep 2024 19:25:30 +0800 Subject: [PATCH 31/70] Fix decode trace from rpc result (#115) --- indexer/jobs/export_traces_job.py | 4 ++-- indexer/tests/jobs/test_export_trace_job.py | 1 + indexer/utils/json_rpc_requests.py | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/indexer/jobs/export_traces_job.py b/indexer/jobs/export_traces_job.py index 44e72f60f..2c53ebb4c 100644 --- a/indexer/jobs/export_traces_job.py +++ b/indexer/jobs/export_traces_job.py @@ -164,7 +164,7 @@ def _iterate_transaction_trace(self, geth_trace, tx_index, tx_hash, tx_trace, tr def traces_rpc_requests(make_requests, blocks: List[dict], is_batch): block_numbers = [] for block in blocks: - block["number"] = hex(block["number"]) + block["number"] = block["number"] block_numbers.append(block["number"]) trace_block_rpc = list(generate_trace_block_by_number_json_rpc(block_numbers)) @@ -207,7 +207,7 @@ def traces_rpc_requests(make_requests, blocks: List[dict], is_batch): trace_result["txHash"] = transactions[idx]["hash"] geth_trace = { - "block_number": to_int(hexstr=block_number), + "block_number": block_number, "block_hash": block["hash"], "block_timestamp": block["timestamp"], "transaction_traces": result, diff --git a/indexer/tests/jobs/test_export_trace_job.py b/indexer/tests/jobs/test_export_trace_job.py index d8c0ffddc..baf7c681b 100644 --- a/indexer/tests/jobs/test_export_trace_job.py +++ b/indexer/tests/jobs/test_export_trace_job.py @@ -31,4 +31,5 @@ def test_export_job(): ) data_buff = job_scheduler.get_data_buff() + assert len(data_buff[ContractInternalTransaction.type()]) == 24 job_scheduler.clear_data_buff() diff --git a/indexer/utils/json_rpc_requests.py b/indexer/utils/json_rpc_requests.py index 2ae97906b..44a0d2008 100644 --- a/indexer/utils/json_rpc_requests.py +++ b/indexer/utils/json_rpc_requests.py @@ -14,9 +14,9 @@ def generate_trace_block_by_number_json_rpc(block_numbers): for block_number in block_numbers: yield generate_json_rpc( method="debug_traceBlockByNumber", - params=[block_number, {"tracer": "callTracer"}], + params=[hex(block_number), {"tracer": "callTracer"}], # save block_number in request ID, so later we can identify block number in response - request_id=int(block_number, 16), + request_id=block_number, ) From 6f5313f74bd29b3081a8e71b14f74aebb1bd6ec5 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Tue, 10 Sep 2024 10:29:05 +0800 Subject: [PATCH 32/70] bugfixed (#116) --- indexer/modules/custom/hemera_ens/extractors.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py index 131e2a4af..9ff143de1 100644 --- a/indexer/modules/custom/hemera_ens/extractors.py +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -340,10 +340,11 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, # ) -def extract_eth_address(input_str): - cleaned_str = input_str.lstrip("0") - if len(cleaned_str) != 40: - raise ValueError("error address length") +def extract_eth_address(input_string): + hex_string = input_string.lower().replace("0x", "") - eth_address = "0x" + cleaned_str - return eth_address + if len(hex_string) > 40: + hex_string = hex_string[-40:] + + hex_string = hex_string.zfill(40) + return Web3.to_checksum_address(hex_string).lower() From 0b2ffb5bb7cf6d8146a3ddb1a67f899480d423a7 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Tue, 10 Sep 2024 11:40:40 +0800 Subject: [PATCH 33/70] Bugfix ens address (#117) * ens migrate bugfixed --- .../modules/custom/hemera_ens/ens_handler.py | 4 ++ .../modules/custom/hemera_ens/extractors.py | 10 ++++ indexer/tests/ens/test_namehash.py | 49 +++++++++++++++++++ 3 files changed, 63 insertions(+) diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index dfcfc2781..ab9ab4e01 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -274,6 +274,10 @@ def resolve_middle(self, record): record["expires"] = None event_name = record.get("event_name") + + node = record.get("node") + if not node: + raise Exception("pass") if event_name == "NameChanged" or record["method"] == "setName": return ENSAddressD( address=address, diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py index 9ff143de1..d5d3f0862 100644 --- a/indexer/modules/custom/hemera_ens/extractors.py +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -156,6 +156,16 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, label = log["topic2"] node = compute_node_label(base_node, label) break + elif ( + log["address"] == "0x314159265dd8dbb310642f98f50c066173c1259b" + and log["topic0"] == "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82" + ): + base_node = log["topic1"] + label = log["topic2"] + node = compute_node_label(base_node, label) + break + if not node: + return None return ENSMiddleD( transaction_hash=ens_middle.transaction_hash, diff --git a/indexer/tests/ens/test_namehash.py b/indexer/tests/ens/test_namehash.py index 1c7092ad9..9d7c89fa6 100644 --- a/indexer/tests/ens/test_namehash.py +++ b/indexer/tests/ens/test_namehash.py @@ -4,9 +4,20 @@ # @Author will # @File test_namehash.py # @Brief +from dataclasses import asdict + import pytest +from indexer.controller.scheduler.job_scheduler import JobScheduler +from indexer.domain.log import Log +from indexer.domain.transaction import Transaction +from indexer.exporters.console_item_exporter import ConsoleItemExporter +from indexer.modules.custom.hemera_ens import EnsConfLoader, EnsHandler from indexer.modules.custom.hemera_ens.ens_hash import compute_node_label, get_label, namehash +from indexer.modules.custom.hemera_ens.extractors import RegisterExtractor +from indexer.tests import ETHEREUM_PUBLIC_NODE_RPC_URL +from indexer.utils.provider import get_provider_from_uri +from indexer.utils.thread_local_proxy import ThreadLocalProxy @pytest.mark.indexer @@ -27,3 +38,41 @@ def test_namehash(): print("ok!") print(get_label("adion")) print(get_label("vitalik\x00")) + + +@pytest.mark.indexer +@pytest.mark.ens +@pytest.mark.serial +def test_mirgate_names(): + + job_scheduler = JobScheduler( + batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), + batch_web3_debug_provider=ThreadLocalProxy( + lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True) + ), + item_exporters=[ConsoleItemExporter()], + batch_size=100, + debug_batch_size=1, + max_workers=5, + config={}, + required_output_types=[Transaction, Log], + ) + + job_scheduler.run_jobs( + start_block=9412121, + end_block=9412121, + ) + ens_handler = EnsHandler(EnsConfLoader(ETHEREUM_PUBLIC_NODE_RPC_URL)) + + df = job_scheduler.get_data_buff() + for tnx in df["transaction"]: + if tnx.hash == "0x4967988122e8160400f1ed7acd315310aac98c42b2f5d1de3064fcb44a16ecc3": + break + logs = [] + for log in df["log"]: + if log.transaction_hash == tnx.hash: + logs.append(asdict(log)) + + res = ens_handler.process(asdict(tnx), logs) + for rr in res: + assert rr.node is not None From e8e21624f242526842fc1079add01dfe0fe1b191 Mon Sep 17 00:00:00 2001 From: li xiang Date: Tue, 10 Sep 2024 15:00:02 +0800 Subject: [PATCH 34/70] feat(current_token_balances): add token_id to GROUP BY clause (#118) Added token_id to the GROUP BY clause while generating the current_token_balances table to ensure token-specific aggregation. --- indexer/jobs/export_token_balances_job.py | 2 +- indexer/tests/__init__.py | 3 ++ .../test_export_token_balances_and_holders.py | 35 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/indexer/jobs/export_token_balances_job.py b/indexer/jobs/export_token_balances_job.py index bbbded4e2..6b9e53821 100644 --- a/indexer/jobs/export_token_balances_job.py +++ b/indexer/jobs/export_token_balances_job.py @@ -109,7 +109,7 @@ def _process(self, **kwargs): ) for token_balance in self._data_buff[TokenBalance.type()] ], - group_by=["token_address", "address"], + group_by=["token_address", "address", "token_id"], max_key="block_number", ) diff --git a/indexer/tests/__init__.py b/indexer/tests/__init__.py index c74be6a6b..58bac8d8e 100644 --- a/indexer/tests/__init__.py +++ b/indexer/tests/__init__.py @@ -12,6 +12,9 @@ "DODO_TESTNET_PUBLIC_NODE_RPC_URL", "https://dodochain-testnet.alt.technology" ) +MANTLE_PUBLIC_NODE_RPC_URL = os.environ.get("MANTLE_PUBLIC_NODE_RPC_URL", "https://rpc.mantle.xyz") +MANTLE_PUBLIC_NODE_DEBUG_RPC_URL = os.environ.get("MANTLE_PUBLIC_NODE_DEBUG_RPC_URL", "https://rpc.mantle.xyz") + ARBITRUM_PUBLIC_NODE_RPC_URL = os.environ.get("ARBITRUM_PUBLIC_NODE_RPC_URL", "https://arbitrum-one-rpc.publicnode.com") ARBITRUM_TESTNET_PUBLIC_NODE_RPC_URL = os.environ.get( "ARBITRUM_TESTNET_PUBLIC_NODE_RPC_URL", diff --git a/indexer/tests/jobs/test_export_token_balances_and_holders.py b/indexer/tests/jobs/test_export_token_balances_and_holders.py index 3a4db776d..dcf7c28d8 100644 --- a/indexer/tests/jobs/test_export_token_balances_and_holders.py +++ b/indexer/tests/jobs/test_export_token_balances_and_holders.py @@ -1,13 +1,46 @@ import pytest from indexer.controller.scheduler.job_scheduler import JobScheduler +from indexer.domain.current_token_balance import CurrentTokenBalance from indexer.domain.token_balance import TokenBalance from indexer.exporters.console_item_exporter import ConsoleItemExporter -from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL +from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL, MANTLE_PUBLIC_NODE_DEBUG_RPC_URL, MANTLE_PUBLIC_NODE_RPC_URL from indexer.utils.provider import get_provider_from_uri from indexer.utils.thread_local_proxy import ThreadLocalProxy +@pytest.mark.indexer +@pytest.mark.indexer_exporter +@pytest.mark.serial +def test_export_current_token_balance_job(): + # ERC1155 current token balance case + job_scheduler = JobScheduler( + batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(MANTLE_PUBLIC_NODE_RPC_URL, batch=True)), + batch_web3_debug_provider=ThreadLocalProxy( + lambda: get_provider_from_uri(MANTLE_PUBLIC_NODE_DEBUG_RPC_URL, batch=True) + ), + item_exporters=[ConsoleItemExporter()], + batch_size=100, + debug_batch_size=1, + max_workers=5, + config={}, + required_output_types=[TokenBalance, CurrentTokenBalance], + ) + + job_scheduler.run_jobs( + start_block=68891458, + end_block=68891458, + ) + + data_buff = job_scheduler.get_data_buff() + + token_balances = data_buff[TokenBalance.type()] + assert len(token_balances) == 33 + + current_token_balances = data_buff[CurrentTokenBalance.type()] + assert len(current_token_balances) == 33 + + @pytest.mark.indexer @pytest.mark.indexer_exporter @pytest.mark.serial From 8b061a877776780ef6878fb227909d1e7730a3ae Mon Sep 17 00:00:00 2001 From: li xiang Date: Wed, 11 Sep 2024 13:13:34 +0800 Subject: [PATCH 35/70] Update token_balances.py (#123) --- common/models/token_balances.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/models/token_balances.py b/common/models/token_balances.py index c039a0334..178fabe69 100644 --- a/common/models/token_balances.py +++ b/common/models/token_balances.py @@ -38,7 +38,7 @@ def model_domain_mapping(): return [ { "domain": "TokenBalance", - "conflict_do_update": False, + "conflict_do_update": True, "update_strategy": None, "converter": token_balances_general_converter, } From 2c62102603dd91d79f3be8321eaa0ae38d8653d5 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Wed, 11 Sep 2024 13:20:05 +0800 Subject: [PATCH 36/70] remove slot0 batch, remove split for price (#124) * remove slot0 batch, add group * remove no use --- .../custom/uniswap_v3/uniswap_v3_pool_job.py | 110 +++++++----------- 1 file changed, 43 insertions(+), 67 deletions(-) diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py index 6f42be338..ae26d17fc 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py @@ -4,6 +4,9 @@ import os from collections import defaultdict from dataclasses import fields +from itertools import groupby +from operator import attrgetter +from typing import Dict, List import eth_abi from web3 import Web3 @@ -90,9 +93,10 @@ def _collect(self, **kwargs): self._transaction_hash_from_dict = {} for transaction in transactions: self._transaction_hash_from_dict[transaction.hash] = transaction.from_address - self._batch_work_executor.execute(logs, self._collect_price_batch, len(logs), split_logs) + self._batch_work_executor.execute(logs, self._collect_price_batch, len(logs)) self._batch_work_executor.wait() self._transaction_hash_from_dict = {} + self._process_current_pool_prices() def _collect_pool_batch(self, logs): for log in logs: @@ -103,16 +107,11 @@ def _collect_pool_batch(self, logs): entity = decode_pool_created(self._nft_address, self._factory_address, log) self._collect_item(UniswapV3Pool.type(), entity) - def _collect_price_batch(self, logs_dict): - if not logs_dict: - return - contract_address = next(iter(logs_dict)) - if contract_address not in self._exist_pools: - return - - logs = logs_dict[contract_address] + def _collect_price_batch(self, logs): unique_logs = set() for log in logs: + if log.address not in self._exist_pools: + continue # Collect swap logs if log.topic0 == constants.UNISWAP_V3_POOL_SWAP_TOPIC0: transaction_hash = log.transaction_hash @@ -159,7 +158,6 @@ def _collect_price_batch(self, logs_dict): self._batch_size, self._max_worker, ) - current_price = None for data in pool_prices: detail = UniswapV3PoolPrice( factory_address=self._factory_address, @@ -170,9 +168,6 @@ def _collect_price_batch(self, logs_dict): block_timestamp=data["block_timestamp"], ) self._collect_item(UniswapV3PoolPrice.type(), detail) - if current_price is None or current_price.block_number < detail.block_number: - current_price = create_current_price_status(detail) - self._collect_item(UniswapV3PoolCurrentPrice.type(), current_price) def _process(self, **kwargs): self._data_buff[UniswapV3Pool.type()].sort(key=lambda x: x.block_number) @@ -180,6 +175,22 @@ def _process(self, **kwargs): self._data_buff[UniswapV3PoolCurrentPrice.type()].sort(key=lambda x: x.block_number) self._data_buff[UniswapV3SwapEvent.type()].sort(key=lambda x: x.block_number) + def _process_current_pool_prices(self): + prices = self._data_buff[UniswapV3PoolPrice.type()] + sorted_prices = sorted(prices, key=lambda x: (x.pool_address, x.block_number)) + current_prices = [ + max(group, key=attrgetter("block_number")) + for _, group in groupby(sorted_prices, key=attrgetter("pool_address")) + ] + for data in current_prices: + self._collect_item(UniswapV3PoolCurrentPrice.type(), self.create_current_price_status(data)) + + @staticmethod + def create_current_price_status(detail: UniswapV3PoolPrice) -> UniswapV3PoolCurrentPrice: + return UniswapV3PoolCurrentPrice( + **{field.name: getattr(detail, field.name) for field in fields(UniswapV3PoolPrice)} + ) + def decode_pool_created(nft_address, factory_address, log): token0_address = util.parse_hex_to_address(log.topic1) @@ -245,15 +256,6 @@ def get_exist_pools(db_service, nft_address): return history_pools -def split_logs(logs): - log_dict = defaultdict(list) - for data in logs: - log_dict[data.address].append(data) - - for contract_address, data in log_dict.items(): - yield {contract_address: data} - - def slot0_rpc_requests(web3, make_requests, requests, is_batch, abi_list, batch_size, max_worker): if len(requests) == 0: return [] @@ -262,52 +264,26 @@ def slot0_rpc_requests(web3, make_requests, requests, is_batch, abi_list, batch_ outputs = function_abi["outputs"] output_types = [output["type"] for output in outputs] - def process_batch(batch): - parameters = common_utils.build_no_input_method_data(web3, batch, fn_name, abi_list) - token_name_rpc = list(generate_eth_call_json_rpc(parameters)) - - if is_batch: - response = make_requests(params=json.dumps(token_name_rpc)) - else: - response = [make_requests(params=json.dumps(token_name_rpc[0]))] - - token_infos = [] - for data in list(zip_rpc_response(parameters, response)): - result = rpc_response_to_result(data[1]) - pool = data[0] - value = result[2:] if result is not None else None - try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - pool["sqrtPriceX96"] = decoded_data[0] - pool["tick"] = decoded_data[1] - except Exception as e: - logger.error(f"Decoding {fn_name} failed. " f"rpc response: {result}. " f"exception: {e}") - token_infos.append(pool) - return token_infos - - executor = BatchWorkExecutor( - starting_batch_size=batch_size, - max_workers=max_worker, - job_name=f"slot0_rpc_requests_{fn_name}", - ) - - all_token_infos = [] - - def work_handler(batch): - nonlocal all_token_infos - batch_results = process_batch(batch) - all_token_infos.extend(batch_results) - - executor.execute(requests, work_handler, total_items=len(requests)) - executor.wait() - - return all_token_infos - + parameters = common_utils.build_no_input_method_data(web3, requests, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] -def create_current_price_status(detail: UniswapV3PoolPrice) -> UniswapV3PoolCurrentPrice: - return UniswapV3PoolCurrentPrice( - **{field.name: getattr(detail, field.name) for field in fields(UniswapV3PoolPrice)} - ) + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + pool = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + pool["sqrtPriceX96"] = decoded_data[0] + pool["tick"] = decoded_data[1] + except Exception as e: + logger.error(f"Decoding {fn_name} failed. " f"rpc response: {result}. " f"exception: {e}") + token_infos.append(pool) + return token_infos def split_swap_data_hex_string(hex_string): From 5ec267cf770b7608c62232fe3ed678615d5cbce2 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Wed, 11 Sep 2024 14:58:41 +0800 Subject: [PATCH 37/70] unique pool price by pool and block_number (#125) * decode to split * encode update --- indexer/modules/custom/common_utils.py | 18 +++++++++---- .../custom/uniswap_v3/uniswap_v3_pool_job.py | 25 ++++++++++++++++--- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/indexer/modules/custom/common_utils.py b/indexer/modules/custom/common_utils.py index 76c5026d6..db135d2c5 100644 --- a/indexer/modules/custom/common_utils.py +++ b/indexer/modules/custom/common_utils.py @@ -1,10 +1,13 @@ import json import logging +from typing import cast import eth_abi from web3 import Web3 +from web3.types import ABIFunction from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.utils.abi import encode_abi, function_abi_to_4byte_selector_str from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc from indexer.utils.utils import rpc_response_to_result, zip_rpc_response @@ -72,9 +75,12 @@ def work_handler(batch): return all_token_infos -def build_no_input_method_data(web3, requests, fn, abi_list, contract_address_key="pool_address"): - parameters = [] +def build_no_input_method_data(web3, requests, fn, abi_list, contract_address_key="pool_address", arguments=None): + arguments = arguments or [] + parameters = [] + function_abi = next((abi for abi in abi_list if abi["name"] == fn and abi["type"] == "function"), None) + abi_function = cast(ABIFunction, function_abi) for idx, token in enumerate(requests): # token["request_id"] = idx token_data = { @@ -85,9 +91,11 @@ def build_no_input_method_data(web3, requests, fn, abi_list, contract_address_ke token.update(token_data) try: # Encode the ABI for the specific token_id - token["param_data"] = web3.eth.contract( - address=Web3.to_checksum_address(token[contract_address_key]), abi=abi_list - ).encodeABI(fn_name=fn) + token["param_data"] = encode_abi( + abi_function, + arguments, + function_abi_to_4byte_selector_str(abi_function), + ) except Exception as e: logger.error( f"Encoding for function {fn} failed. " diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py index ae26d17fc..a9c73e244 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py @@ -177,7 +177,16 @@ def _process(self, **kwargs): def _process_current_pool_prices(self): prices = self._data_buff[UniswapV3PoolPrice.type()] - sorted_prices = sorted(prices, key=lambda x: (x.pool_address, x.block_number)) + self._data_buff[UniswapV3PoolPrice.type()] = [] + unique_prices = {} + for price in prices: + key = (price.pool_address, price.block_number) + unique_prices[key] = price + + for price in unique_prices.values(): + self._collect_item(UniswapV3PoolPrice.type(), price) + + sorted_prices = sorted(unique_prices.values(), key=lambda x: (x.pool_address, x.block_number)) current_prices = [ max(group, key=attrgetter("block_number")) for _, group in groupby(sorted_prices, key=attrgetter("pool_address")) @@ -277,15 +286,23 @@ def slot0_rpc_requests(web3, make_requests, requests, is_batch, abi_list, batch_ pool = data[0] value = result[2:] if result is not None else None try: - decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) - pool["sqrtPriceX96"] = decoded_data[0] - pool["tick"] = decoded_data[1] + part1, part2 = get_price_and_tick_from_hex(value) + pool["sqrtPriceX96"] = part1 + pool["tick"] = part2 except Exception as e: logger.error(f"Decoding {fn_name} failed. " f"rpc response: {result}. " f"exception: {e}") token_infos.append(pool) return token_infos +def get_price_and_tick_from_hex(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + part1 = hex_string[:64] + part2 = hex_string[64:128] + return util.parse_hex_to_int256(part1), util.parse_hex_to_int256(part2) + + def split_swap_data_hex_string(hex_string): if hex_string.startswith("0x"): hex_string = hex_string[2:] From f397cbb258e1fc420761a3a7e82aef8b1653c5a8 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 12 Sep 2024 01:25:31 +0800 Subject: [PATCH 38/70] empty result bug fixed (#122) * empty result bug fixed --- .../test_export_token_balances_and_holders.py | 33 ++++++++++++++ indexer/utils/token_fetcher.py | 44 ++++++++++++------- 2 files changed, 61 insertions(+), 16 deletions(-) diff --git a/indexer/tests/jobs/test_export_token_balances_and_holders.py b/indexer/tests/jobs/test_export_token_balances_and_holders.py index dcf7c28d8..6ca87f70e 100644 --- a/indexer/tests/jobs/test_export_token_balances_and_holders.py +++ b/indexer/tests/jobs/test_export_token_balances_and_holders.py @@ -41,6 +41,39 @@ def test_export_current_token_balance_job(): assert len(current_token_balances) == 33 +@pytest.mark.indexer +@pytest.mark.indexer_exporter +@pytest.mark.serial +def test_export_current_token_balance_job_mul(): + # ERC1155 current token balance case + job_scheduler = JobScheduler( + batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(MANTLE_PUBLIC_NODE_RPC_URL, batch=True)), + batch_web3_debug_provider=ThreadLocalProxy( + lambda: get_provider_from_uri(MANTLE_PUBLIC_NODE_DEBUG_RPC_URL, batch=True) + ), + item_exporters=[ConsoleItemExporter()], + batch_size=100, + debug_batch_size=1, + max_workers=5, + config={}, + required_output_types=[TokenBalance, CurrentTokenBalance], + multicall=True, + ) + + job_scheduler.run_jobs( + start_block=68891458, + end_block=68891458, + ) + + data_buff = job_scheduler.get_data_buff() + + token_balances = data_buff[TokenBalance.type()] + assert len(token_balances) == 33 + + current_token_balances = data_buff[CurrentTokenBalance.type()] + assert len(current_token_balances) == 33 + + @pytest.mark.indexer @pytest.mark.indexer_exporter @pytest.mark.serial diff --git a/indexer/utils/token_fetcher.py b/indexer/utils/token_fetcher.py index 93d33c9a7..83437b6bd 100644 --- a/indexer/utils/token_fetcher.py +++ b/indexer/utils/token_fetcher.py @@ -29,6 +29,7 @@ from indexer.utils.exception_recorder import ExceptionRecorder from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc from indexer.utils.multicall_hemera import Call, Multicall, Network +from indexer.utils.multicall_hemera.constants import GAS_LIMIT from indexer.utils.multicall_hemera.util import ( calculate_execution_time, make_request_concurrent, @@ -87,6 +88,7 @@ def __init__(self, web3, kwargs=None, logger=None): def _prepare_token_ids_info_parameters(self, token_info_items): to_execute_batch_calls = [] wrapped_calls = [] + wrapped_calls_map = {} grouped_data = defaultdict(list) for row in token_info_items: row[self.fixed_k] = self.build_key(row, self.token_ids_infos_k_fields) @@ -133,9 +135,10 @@ def _prepare_token_ids_info_parameters(self, token_info_items): ) if construct_call: + wrapped_calls_map[construct_call.returns[0][0]] = row calls.append(construct_call) wrapped_calls.append(calls) - return wrapped_calls, to_execute_batch_calls + return wrapped_calls, wrapped_calls_map, to_execute_batch_calls def create_token_detail(self, token_info, value, decode_flag): common_args = { @@ -176,9 +179,9 @@ def create_token_detail(self, token_info, value, decode_flag): def fetch_token_ids_info(self, token_info_items): # export token_ids_info self.logger.info(f"TokenFetcher fetch_token_ids_info size={len(token_info_items)}") - wrapped_calls, to_execute_batch_calls = self._prepare_token_ids_info_parameters(token_info_items) - - return_data_map = {it[self.fixed_k]: it for it in token_info_items} + wrapped_calls, wrapped_calls_map, to_execute_batch_calls = self._prepare_token_ids_info_parameters( + token_info_items + ) multicall_result = {} multicall_rpc = [] @@ -188,6 +191,7 @@ def fetch_token_ids_info(self, token_info_items): for calls in wrapped_calls: self.multi_call.calls = calls self.multi_call.block_id = calls[0].block_id + self.multi_call.gas_limit = len(calls) * GAS_LIMIT rpc_para = self.multi_call.to_rpc_param() multicall_rpc.append(rpc_para) @@ -198,9 +202,11 @@ def fetch_token_ids_info(self, token_info_items): tmp = self.decode_result(wrapped_calls, res, chunks) multicall_result.update(tmp) - for k, v in multicall_result.items(): - if v is None: - to_execute_batch_calls.append(return_data_map[k]) + for k, v in wrapped_calls_map.items(): + if k in multicall_result and multicall_result[k] is not None: + pass + else: + to_execute_batch_calls.append(v) raw_result = self.fetch_to_execute_batch_calls(self._token_ids_info_rpc_requests, to_execute_batch_calls) @@ -258,7 +264,7 @@ def _prepare_token_balance_parameters(self, tokens): to_execute_batch_calls = [] wrapped_calls = [] - + wrapped_calls_map = {} for block_id, items in grouped_data.items(): if (isinstance(block_id, int) and block_id < self.deploy_block_number) or not self._is_multi_call: to_execute_batch_calls.extend(items) @@ -283,10 +289,11 @@ def _prepare_token_balance_parameters(self, tokens): block_id=block_id, ) if construct_call: + wrapped_calls_map[construct_call.returns[0][0]] = row calls.append(construct_call) wrapped_calls.append(calls) - return wrapped_calls, to_execute_batch_calls + return wrapped_calls, wrapped_calls_map, to_execute_batch_calls @calculate_execution_time def fetch_result(self, chunks): @@ -318,9 +325,7 @@ def fetch_to_execute_batch_calls(self, func, to_execute_batch_calls): @calculate_execution_time def fetch_token_balance(self, tokens): self.logger.info(f"TokenFetcher fetch_token_balance size={len(tokens)}") - wrapped_calls, to_execute_batch_calls = self._prepare_token_balance_parameters(tokens) - - return_data_map = {it[self.fixed_k]: it for it in tokens} + wrapped_calls, wrapped_calls_map, to_execute_batch_calls = self._prepare_token_balance_parameters(tokens) multicall_result = {} multicall_rpc = self.construct_multicall_rpc(wrapped_calls) @@ -330,9 +335,12 @@ def fetch_token_balance(self, tokens): res = self.fetch_result(chunks) tmp = self.decode_result(wrapped_calls, res, chunks) multicall_result.update(tmp) - for k, v in multicall_result.items(): - if v is None: - to_execute_batch_calls.append(return_data_map[k]) + + for k, v in wrapped_calls_map.items(): + if k in multicall_result and multicall_result[k] is not None: + pass + else: + to_execute_batch_calls.append(v) tmp = self.fetch_to_execute_batch_calls(self._token_balances, to_execute_batch_calls) multicall_result.update(tmp) @@ -345,7 +353,11 @@ def construct_multicall_rpc(self, wrapped_calls): for calls in wrapped_calls: multicall_rpc.append( Multicall( - calls, require_success=False, chain_id=self.chain_id, block_id=calls[0].block_id + calls, + require_success=False, + chain_id=self.chain_id, + block_id=calls[0].block_id, + gas_limit=len(calls) * GAS_LIMIT, ).to_rpc_param() ) return multicall_rpc From d7e23cb7d968498d9a2e8569feeb02f9ef7374d4 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Thu, 12 Sep 2024 10:55:10 +0800 Subject: [PATCH 39/70] update nft_address to position_token_address (#130) --- .../uniswap_v3/domain/feature_uniswap_v3.py | 14 +++--- .../feature_uniswap_v3_collect_fee_records.py | 6 ++- .../feature_uniswap_v3_liquidity_records.py | 6 ++- .../models/feature_uniswap_v3_pools.py | 4 +- .../models/feature_uniswap_v3_swap_records.py | 2 +- ...feature_uniswap_v3_token_current_status.py | 4 +- .../feature_uniswap_v3_token_details.py | 8 ++-- .../models/feature_uniswap_v3_tokens.py | 6 +-- .../custom/uniswap_v3/uniswap_v3_pool_job.py | 20 +++++---- .../custom/uniswap_v3/uniswap_v3_token_job.py | 36 ++++++++------- .../20240906_add_uniswap_v3_enhance_table.py | 44 ++++++++++--------- 11 files changed, 80 insertions(+), 70 deletions(-) diff --git a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py index 9743ef5a0..742e52d01 100644 --- a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py +++ b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py @@ -6,7 +6,7 @@ @dataclass class UniswapV3Pool(FilterData): - nft_address: str + position_token_address: str factory_address: str pool_address: str token0_address: str @@ -19,7 +19,7 @@ class UniswapV3Pool(FilterData): @dataclass class UniswapV3Token(FilterData): - nft_address: str + position_token_address: str token_id: int pool_address: str tick_lower: int @@ -41,7 +41,7 @@ class UniswapV3PoolPrice(FilterData): @dataclass class UniswapV3TokenDetail(FilterData): - nft_address: str + position_token_address: str token_id: int pool_address: str wallet_address: str @@ -63,7 +63,7 @@ class UniswapV3PoolCurrentPrice(FilterData): @dataclass class UniswapV3SwapEvent(FilterData): pool_address: str - nft_address: str + position_token_address: str transaction_from_address: str sender: str recipient: str @@ -82,7 +82,7 @@ class UniswapV3SwapEvent(FilterData): @dataclass class UniswapV3TokenCurrentStatus(FilterData): - nft_address: str + position_token_address: str token_id: int pool_address: str wallet_address: str @@ -93,7 +93,7 @@ class UniswapV3TokenCurrentStatus(FilterData): @dataclass class UniswapV3TokenUpdateLiquidity(FilterData): - nft_address: str + position_token_address: str token_id: int owner: str liquidity: int @@ -111,7 +111,7 @@ class UniswapV3TokenUpdateLiquidity(FilterData): @dataclass class UniswapV3TokenCollectFee(FilterData): - nft_address: str + position_token_address: str recipient: str owner: str token_id: int diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py index b9b70fdea..d570662d0 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py @@ -8,7 +8,7 @@ class UniswapV3CollectFeeRecords(HemeraModel): __tablename__ = "af_uniswap_v3_token_collect_fee_hist" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) block_number = Column(BIGINT, primary_key=True) block_timestamp = Column(BIGINT, primary_key=True) @@ -27,7 +27,9 @@ class UniswapV3CollectFeeRecords(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) - __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"),) + __table_args__ = ( + PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), + ) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py index 1938792d7..fcc09beb0 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py @@ -8,7 +8,7 @@ class UniswapV3TokenLiquidityRecords(HemeraModel): __tablename__ = "af_uniswap_v3_token_liquidity_hist" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) block_number = Column(BIGINT, primary_key=True) block_timestamp = Column(BIGINT, primary_key=True) @@ -30,7 +30,9 @@ class UniswapV3TokenLiquidityRecords(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) - __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"),) + __table_args__ = ( + PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), + ) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py index 72702b363..ffdf02648 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py @@ -8,7 +8,7 @@ class UniswapV3Pools(HemeraModel): __tablename__ = "af_uniswap_v3_pools" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) pool_address = Column(BYTEA, primary_key=True) factory_address = Column(BYTEA) @@ -25,7 +25,7 @@ class UniswapV3Pools(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - __table_args__ = (PrimaryKeyConstraint("nft_address", "pool_address"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "pool_address"),) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py index fd3e66539..27428f7d8 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py @@ -13,7 +13,7 @@ class UniswapV3PoolSwapRecords(HemeraModel): log_index = Column(INTEGER, primary_key=True) block_number = Column(BIGINT) block_timestamp = Column(BIGINT) - nft_address = Column(BYTEA) + position_token_address = Column(BYTEA) transaction_from_address = Column(BYTEA) sender = Column(BYTEA) recipient = Column(BYTEA) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py index 222c6b42c..3a99a6a07 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py @@ -9,7 +9,7 @@ class UniswapV3TokenCurrentStatus(HemeraModel): __tablename__ = "af_uniswap_v3_token_data_current" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) block_number = Column(BIGINT) block_timestamp = Column(BIGINT) @@ -20,7 +20,7 @@ class UniswapV3TokenCurrentStatus(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id"),) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py index c60f954c2..03e42fbdc 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py @@ -9,7 +9,7 @@ class UniswapV3TokenDetails(HemeraModel): __tablename__ = "af_uniswap_v3_token_data_hist" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) block_number = Column(BIGINT, primary_key=True) block_timestamp = Column(BIGINT, primary_key=True) @@ -21,7 +21,7 @@ class UniswapV3TokenDetails(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) reorg = Column(BOOLEAN, default=False) - __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) @staticmethod def model_domain_mapping(): @@ -37,13 +37,13 @@ def model_domain_mapping(): Index( "af_uniswap_v3_token_data_hist_token_block_desc_index", - desc(UniswapV3TokenDetails.nft_address), + desc(UniswapV3TokenDetails.position_token_address), desc(UniswapV3TokenDetails.block_timestamp), ) Index( "af_uniswap_v3_token_data_hist_wallet_token_block_desc_index", desc(UniswapV3TokenDetails.wallet_address), - desc(UniswapV3TokenDetails.nft_address), + desc(UniswapV3TokenDetails.position_token_address), desc(UniswapV3TokenDetails.block_timestamp), ) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py index 769110ebc..58355ee7b 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py @@ -9,7 +9,7 @@ class UniswapV3Tokens(HemeraModel): __tablename__ = "af_uniswap_v3_tokens" - nft_address = Column(BYTEA, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) pool_address = Column(BYTEA) @@ -23,7 +23,7 @@ class UniswapV3Tokens(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - __table_args__ = (PrimaryKeyConstraint("nft_address", "token_id"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id"),) @staticmethod def model_domain_mapping(): @@ -37,4 +37,4 @@ def model_domain_mapping(): ] -Index("af_uniswap_v3_tokens_nft_index", UniswapV3Tokens.nft_address) +Index("af_uniswap_v3_tokens_nft_index", UniswapV3Tokens.position_token_address) diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py index a9c73e244..6addb9a9d 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_pool_job.py @@ -50,7 +50,7 @@ def __init__(self, **kwargs): self._service = kwargs["config"].get("db_service") self._chain_id = common_utils.get_chain_id(self._web3) self._load_config("config.ini", self._chain_id) - self._exist_pools = get_exist_pools(self._service, self._nft_address) + self._exist_pools = get_exist_pools(self._service, self._position_token_address) self._batch_size = kwargs["batch_size"] self._max_worker = kwargs["max_workers"] self._abi_list = UNISWAP_V3_ABI @@ -76,7 +76,7 @@ def _load_config(self, filename, chain_id): except KeyError: return try: - self._nft_address = chain_config.get("nft_address").lower() + self._position_token_address = chain_config.get("nft_address").lower() self._factory_address = chain_config.get("factory_address").lower() except (configparser.NoOptionError, configparser.NoSectionError) as e: raise ValueError(f"Missing required configuration in {filename}: {str(e)}") @@ -104,7 +104,7 @@ def _collect_pool_batch(self, logs): current_topic0 = log.topic0 if self._factory_address != address or self._create_pool_topic0 != current_topic0: continue - entity = decode_pool_created(self._nft_address, self._factory_address, log) + entity = decode_pool_created(self._position_token_address, self._factory_address, log) self._collect_item(UniswapV3Pool.type(), entity) def _collect_price_batch(self, logs): @@ -126,7 +126,7 @@ def _collect_price_batch(self, logs): UniswapV3SwapEvent.type(), UniswapV3SwapEvent( pool_address=log.address, - nft_address=self._nft_address, + position_token_address=self._position_token_address, transaction_hash=transaction_hash, transaction_from_address=self._transaction_hash_from_dict[transaction_hash], log_index=log.log_index, @@ -201,7 +201,7 @@ def create_current_price_status(detail: UniswapV3PoolPrice) -> UniswapV3PoolCurr ) -def decode_pool_created(nft_address, factory_address, log): +def decode_pool_created(position_token_address, factory_address, log): token0_address = util.parse_hex_to_address(log.topic1) token1_address = util.parse_hex_to_address(log.topic2) fee = util.parse_hex_to_int256(log.topic3) @@ -209,7 +209,7 @@ def decode_pool_created(nft_address, factory_address, log): pool_address = util.parse_hex_to_address(pool_hex) tick_spacing = util.parse_hex_to_int256(tick_hex) return UniswapV3Pool( - nft_address=nft_address, + position_token_address=position_token_address, factory_address=factory_address, pool_address=pool_address, token0_address=token0_address, @@ -233,21 +233,23 @@ def split_hex_string(hex_string): raise ValueError("The data is not belong to Uniswap-V3 Factory") -def get_exist_pools(db_service, nft_address): +def get_exist_pools(db_service, position_token_address): if not db_service: return {} session = db_service.get_service_session() try: result = ( - session.query(UniswapV3Pools).filter(UniswapV3Pools.nft_address == bytes.fromhex(nft_address[2:])).all() + session.query(UniswapV3Pools) + .filter(UniswapV3Pools.position_token_address == bytes.fromhex(position_token_address[2:])) + .all() ) history_pools = {} if result is not None: for item in result: pool_key = "0x" + item.pool_address.hex() history_pools[pool_key] = UniswapV3Pool( - nft_address="0x" + item.nft_address.hex(), + position_token_address="0x" + item.position_token_address.hex(), pool_address=pool_key, token0_address="0x" + item.token0_address.hex(), token1_address="0x" + item.token1_address.hex(), diff --git a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py index c7c934791..d5bd92f99 100644 --- a/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py +++ b/indexer/modules/custom/uniswap_v3/uniswap_v3_token_job.py @@ -58,8 +58,8 @@ def __init__(self, **kwargs): self._load_config("config.ini", self._chain_id) self._abi_list = UNISWAP_V3_ABI self._liquidity_token_id_blocks = queue.Queue() - self._exist_token_ids = get_exist_token_ids(self._service, self._nft_address) - self._exist_pool_infos = get_exist_pools(self._service, self._nft_address) + self._exist_token_ids = get_exist_token_ids(self._service, self._position_token_address) + self._exist_pool_infos = get_exist_pools(self._service, self._position_token_address) self._batch_size = kwargs["batch_size"] self._max_worker = kwargs["max_workers"] @@ -74,7 +74,7 @@ def _load_config(self, filename, chain_id): except KeyError: return try: - self._nft_address = chain_config.get("nft_address").lower() + self._position_token_address = chain_config.get("nft_address").lower() self._factory_address = chain_config.get("factory_address").lower() except (configparser.NoOptionError, configparser.NoSectionError) as e: raise ValueError(f"Missing required configuration in {filename}: {str(e)}") @@ -82,7 +82,7 @@ def _load_config(self, filename, chain_id): def get_filter(self): return TransactionFilterByLogs( [ - TopicSpecification(addresses=[self._nft_address]), + TopicSpecification(addresses=[self._position_token_address]), ] ) @@ -106,7 +106,7 @@ def _collect(self, **kwargs): address = log.address block_number = log.block_number block_timestamp = log.block_timestamp - if address != self._nft_address: + if address != self._position_token_address: continue if topic0 == constants.TRANSFER_TOPIC0: token_id_hex = log.topic3 @@ -139,7 +139,7 @@ def _collect(self, **kwargs): self._web3, self._batch_web3_provider.make_request, need_collect_token_id_data, - self._nft_address, + self._position_token_address, self._is_batch, self._abi_list, self._batch_size, @@ -150,7 +150,7 @@ def _collect(self, **kwargs): self._web3, self._batch_web3_provider.make_request, owner_info, - self._nft_address, + self._position_token_address, self._is_batch, self._abi_list, self._batch_size, @@ -174,7 +174,7 @@ def _collect(self, **kwargs): self._collect_item( UniswapV3Token.type(), UniswapV3Token( - nft_address=self._nft_address, + position_token_address=self._position_token_address, token_id=token_id, pool_address=pool_address, tick_lower=tick_lower, @@ -195,7 +195,7 @@ def _collect(self, **kwargs): if "liquidity" in data: liquidity = data["liquidity"] detail = UniswapV3TokenDetail( - nft_address=self._nft_address, + position_token_address=self._position_token_address, pool_address=pool_address, token_id=token_id, wallet_address=wallet_address, @@ -213,7 +213,7 @@ def _collect(self, **kwargs): # collect fee and liquidity for log in logs: - if log.address != self._nft_address: + if log.address != self._position_token_address: continue topic0 = log.topic0 block_number = log.block_number @@ -249,7 +249,7 @@ def _collect(self, **kwargs): self._collect_item( UniswapV3TokenUpdateLiquidity.type(), UniswapV3TokenUpdateLiquidity( - nft_address=self._nft_address, + position_token_address=self._position_token_address, token_id=token_id, owner=owner, action_type=action_type, @@ -270,7 +270,7 @@ def _collect(self, **kwargs): self._collect_item( UniswapV3TokenCollectFee.type(), UniswapV3TokenCollectFee( - nft_address=self._nft_address, + position_token_address=self._position_token_address, token_id=token_id, owner=owner, transaction_hash=log.transaction_hash, @@ -294,14 +294,16 @@ def _process(self, **kwargs): self._data_buff[UniswapV3TokenCollectFee.type()].sort(key=lambda x: x.block_number) -def get_exist_pools(db_service, nft_address): +def get_exist_pools(db_service, position_token_address): if not db_service: return {} session = db_service.get_service_session() try: result = ( - session.query(UniswapV3Pools).filter(UniswapV3Pools.nft_address == bytes.fromhex(nft_address[2:])).all() + session.query(UniswapV3Pools) + .filter(UniswapV3Pools.position_token_address == bytes.fromhex(position_token_address[2:])) + .all() ) history_pools = {} if result is not None: @@ -309,7 +311,7 @@ def get_exist_pools(db_service, nft_address): pool_key = "0x" + item.pool_address.hex() history_pools[pool_key] = UniswapV3Pool( pool_address=pool_key, - nft_address="0x" + item.nft_address.hex(), + position_token_address="0x" + item.position_token_address.hex(), factory_address="0x" + item.factory_address.hex(), token0_address="0x" + item.token0_address.hex(), token1_address="0x" + item.token1_address.hex(), @@ -326,7 +328,7 @@ def get_exist_pools(db_service, nft_address): return history_pools -def get_exist_token_ids(db_service, nft_address): +def get_exist_token_ids(db_service, position_token_address): if not db_service: return {} @@ -335,7 +337,7 @@ def get_exist_token_ids(db_service, nft_address): result = ( session.query(UniswapV3Tokens.token_id, UniswapV3Tokens.pool_address) .filter( - UniswapV3Tokens.nft_address == bytes.fromhex(nft_address[2:]), + UniswapV3Tokens.position_token_address == bytes.fromhex(position_token_address[2:]), ) .all() ) diff --git a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py index 4c1eb0958..ad6dfbec6 100644 --- a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py +++ b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py @@ -54,7 +54,7 @@ def upgrade() -> None: sa.Column("log_index", sa.INTEGER(), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=True), sa.Column("block_timestamp", sa.BIGINT(), nullable=True), - sa.Column("nft_address", postgresql.BYTEA(), nullable=True), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=True), sa.Column("transaction_from_address", postgresql.BYTEA(), nullable=True), sa.Column("sender", postgresql.BYTEA(), nullable=True), sa.Column("recipient", postgresql.BYTEA(), nullable=True), @@ -72,7 +72,7 @@ def upgrade() -> None: ) op.create_table( "af_uniswap_v3_pools", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("pool_address", postgresql.BYTEA(), nullable=False), sa.Column("factory_address", postgresql.BYTEA(), nullable=True), sa.Column("token0_address", postgresql.BYTEA(), nullable=True), @@ -83,11 +83,11 @@ def upgrade() -> None: sa.Column("block_timestamp", sa.BIGINT(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "pool_address"), + sa.PrimaryKeyConstraint("position_token_address", "pool_address"), ) op.create_table( "af_uniswap_v3_token_collect_fee_hist", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=False), sa.Column("block_timestamp", sa.BIGINT(), nullable=False), @@ -103,7 +103,7 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"), + sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), ) op.create_index( "af_uniswap_v3_token_collect_fee_hist_owner_index", @@ -137,7 +137,7 @@ def upgrade() -> None: ) op.create_table( "af_uniswap_v3_token_data_current", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=True), sa.Column("block_timestamp", sa.BIGINT(), nullable=True), @@ -146,7 +146,7 @@ def upgrade() -> None: sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id"), + sa.PrimaryKeyConstraint("position_token_address", "token_id"), ) op.create_index( "af_uniswap_v3_token_data_current_wallet_desc_index", @@ -156,7 +156,7 @@ def upgrade() -> None: ) op.create_table( "af_uniswap_v3_token_data_hist", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=False), sa.Column("block_timestamp", sa.BIGINT(), nullable=False), @@ -166,23 +166,23 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number"), + sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"), ) op.create_index( "af_uniswap_v3_token_data_hist_token_block_desc_index", "af_uniswap_v3_token_data_hist", - [sa.text("nft_address DESC"), sa.text("block_timestamp DESC")], + [sa.text("position_token_address DESC"), sa.text("block_timestamp DESC")], unique=False, ) op.create_index( "af_uniswap_v3_token_data_hist_wallet_token_block_desc_index", "af_uniswap_v3_token_data_hist", - [sa.text("wallet_address DESC"), sa.text("nft_address DESC"), sa.text("block_timestamp DESC")], + [sa.text("wallet_address DESC"), sa.text("position_token_address DESC"), sa.text("block_timestamp DESC")], unique=False, ) op.create_table( "af_uniswap_v3_token_liquidity_hist", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=False), sa.Column("block_timestamp", sa.BIGINT(), nullable=False), @@ -199,7 +199,7 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id", "block_timestamp", "block_number", "log_index"), + sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), ) op.create_index( "af_uniswap_v3_token_liquidity_hist_owner_index", "af_uniswap_v3_token_liquidity_hist", ["owner"], unique=False @@ -230,7 +230,7 @@ def upgrade() -> None: ) op.create_table( "af_uniswap_v3_tokens", - sa.Column("nft_address", postgresql.BYTEA(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), sa.Column("pool_address", postgresql.BYTEA(), nullable=True), sa.Column("tick_lower", sa.NUMERIC(precision=100), nullable=True), @@ -240,9 +240,9 @@ def upgrade() -> None: sa.Column("block_timestamp", sa.BIGINT(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id"), + sa.PrimaryKeyConstraint("position_token_address", "token_id"), ) - op.create_index("af_uniswap_v3_tokens_nft_index", "af_uniswap_v3_tokens", ["nft_address"], unique=False) + op.create_index("af_uniswap_v3_tokens_nft_index", "af_uniswap_v3_tokens", ["position_token_address"], unique=False) op.drop_index("feature_uniswap_v3_tokens_nft_index", table_name="feature_uniswap_v3_tokens") op.drop_table("feature_uniswap_v3_tokens") op.drop_table("feature_uniswap_v3_pools") @@ -253,7 +253,7 @@ def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( "feature_uniswap_v3_pools", - sa.Column("nft_address", postgresql.BYTEA(), autoincrement=False, nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), autoincrement=False, nullable=False), sa.Column("pool_address", postgresql.BYTEA(), autoincrement=False, nullable=False), sa.Column("token0_address", postgresql.BYTEA(), autoincrement=False, nullable=True), sa.Column("token1_address", postgresql.BYTEA(), autoincrement=False, nullable=True), @@ -266,11 +266,11 @@ def downgrade() -> None: "update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True ), sa.Column("called_block_number", sa.BIGINT(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint("nft_address", "pool_address", name="feature_uniswap_v3_pools_pkey"), + sa.PrimaryKeyConstraint("position_token_address", "pool_address", name="feature_uniswap_v3_pools_pkey"), ) op.create_table( "feature_uniswap_v3_tokens", - sa.Column("nft_address", postgresql.BYTEA(), autoincrement=False, nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), autoincrement=False, nullable=False), sa.Column("token_id", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=False), sa.Column("pool_address", postgresql.BYTEA(), autoincrement=False, nullable=True), sa.Column("tick_lower", sa.NUMERIC(precision=100, scale=0), autoincrement=False, nullable=True), @@ -283,9 +283,11 @@ def downgrade() -> None: "update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), autoincrement=False, nullable=True ), sa.Column("called_block_number", sa.BIGINT(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint("nft_address", "token_id", name="feature_uniswap_v3_tokens_pkey"), + sa.PrimaryKeyConstraint("position_token_address", "token_id", name="feature_uniswap_v3_tokens_pkey"), + ) + op.create_index( + "feature_uniswap_v3_tokens_nft_index", "feature_uniswap_v3_tokens", ["position_token_address"], unique=False ) - op.create_index("feature_uniswap_v3_tokens_nft_index", "feature_uniswap_v3_tokens", ["nft_address"], unique=False) op.drop_index("af_uniswap_v3_tokens_nft_index", table_name="af_uniswap_v3_tokens") op.drop_table("af_uniswap_v3_tokens") From b9ef9b2c29e72a5b0c0cc5cdfe84e929fddb652b Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 12 Sep 2024 11:21:17 +0800 Subject: [PATCH 40/70] Add ENS event for ERC721/ERC1155 transfers --- indexer/modules/custom/hemera_ens/export_ens_job.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indexer/modules/custom/hemera_ens/export_ens_job.py b/indexer/modules/custom/hemera_ens/export_ens_job.py index 59a581b5c..cd255ccdd 100644 --- a/indexer/modules/custom/hemera_ens/export_ens_job.py +++ b/indexer/modules/custom/hemera_ens/export_ens_job.py @@ -61,7 +61,10 @@ def get_filter(self): tp_variables = [ getattr(extractor, attr) for extractor in extractors for attr in dir(extractor) if attr.startswith("tp") ] - + # nft transfer + tp_variables.append("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") + # 1155 transfer + tp_variables.append("0xc3d58168c5ae7397731d063d5bbf3d657854427343f4c083240f7aacaa2d0f62") addresses = list(CONTRACT_NAME_MAP.keys()) return [ TransactionFilterByLogs([TopicSpecification(addresses=addresses, topics=tp_variables)]), From e92c534300dcc05fa671020a8b9e0fb122adf2c5 Mon Sep 17 00:00:00 2001 From: 2024river Date: Thu, 12 Sep 2024 14:25:58 +0800 Subject: [PATCH 41/70] add merchantmoe daily job (#135) * add merchantmoe daily job * make format --- ...iod_feature_holding_balance_merchantmoe.py | 27 ++++++ .../period_feature_merchant_moe_token_bin.py | 19 +++++ ...od_feature_holding_balance_merchantmoe.sql | 84 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py create mode 100644 indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py create mode 100644 indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py new file mode 100644 index 000000000..2d650c09d --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py @@ -0,0 +1,27 @@ +from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, String, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel + + +class PeriodFeatureHoldingBalanceMerchantmoe(HemeraModel): + __tablename__ = "af_holding_balance_merchantmoe_period" + + period_date = Column(DATE, primary_key=True, nullable=False) + protocol_id = Column(String, primary_key=True, nullable=False) + contract_address = Column(BYTEA, primary_key=True, nullable=False) + token_id = Column(NUMERIC, primary_key=True, nullable=False) + wallet_address = Column(BYTEA, primary_key=True, nullable=False) + + token0_address = Column(BYTEA, nullable=False) + token0_symbol = Column(String, nullable=False) + token0_balance = Column(NUMERIC(100, 18)) + + token1_address = Column(BYTEA, nullable=False) + token1_symbol = Column(String, nullable=False) + token1_balance = Column(NUMERIC(100, 18)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +Index("af_holding_balance_merchantmoe_period_period_date", PeriodFeatureHoldingBalanceMerchantmoe.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py b/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py new file mode 100644 index 000000000..abd01ee78 --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py @@ -0,0 +1,19 @@ +from datetime import datetime + +from sqlalchemy import DATE, Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class PeriodFeatureMerChantMoeTokenBinRecords(HemeraModel): + __tablename__ = "af_merchant_moe_token_bin_hist_period" + + period_date = Column(DATE, primary_key=True) + position_token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + + reserve0_bin = Column(NUMERIC(100)) + reserve1_bin = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) diff --git a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql new file mode 100644 index 000000000..9648af0e6 --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql @@ -0,0 +1,84 @@ +delete +from af_merchant_moe_token_bin_hist_period +where period_date >= '{start_date}' + and period_date < '{end_date}'; + +insert into af_merchant_moe_token_bin_hist_period(period_date, position_token_address, token_id, reserve0_bin, reserve1_bin) +select date('{start_date}'), position_token_address, token_id, reserve0_bin, reserve1_bin +from (select *, row_number() over (partition by position_token_address, token_id order by block_timestamp desc) as rn + from feature_merchant_moe_token_bin_records + where to_timestamp(block_timestamp) <= '{start_date}') t +where rn = 1 +; + + +delete +from af_holding_balance_merchantmoe_period +where period_date >= '{start_date}' + and period_date < '{end_date}'; +insert +into af_holding_balance_merchantmoe_period(period_date, protocol_id, contract_address, token_id, + wallet_address, token0_address, token0_symbol, token0_balance, + token1_address, token1_symbol, token1_balance) +with moe_pools_table as (select d0.*, + + d4.symbol as token0_symbol, + d4.decimals as token0_decimals, + d5.symbol as token1_symbol, + d5.decimals as token1_decimals + from af_merchant_moe_pools d0 + inner join tokens d4 on d0.token0_address = d4.address + inner join tokens d5 on d0.token1_address = d5.address + where d4.symbol = 'FBTC' + or d5.symbol = 'FBTC'), + + moe_pool_with_records_table as (select d0.*, d1.address, d1.token_id, d1.balance + from moe_pools_table d0 + inner join + (select * + from period_address_token_balances + where token_type = 'ERC1155') d1 + on d0.token_address = d1.token_address), + + detail_table as (select d1.address + , d1.token_address + , d1.token_id + , d1.balance + , d2.total_supply + , d3.reserve0_bin + , d3.reserve1_bin + , token0_address + , token0_symbol + , token0_decimals + , token1_address + , token1_symbol + , token1_decimals + from moe_pool_with_records_table d1 + inner join + (select * + from period_feature_erc1155_token_supply_records -- todo: replace with the new name table + where period_date = '{start_date}') d2 + on d1.token_address = d2.token_address and d1.token_id = d2.token_id + inner join (select * + from af_merchant_moe_token_bin_hist_period + where period_date = '{start_date}') d3 + on d1.token_address = d3.position_token_address and d1.token_id = d3.token_id) + +select date('{start_date}'), + 'merchantmoe' as protocol_id, + token_address as nft_addres, + token_id, + address, + token0_address, + token0_symbol, + case + when total_supply > 0 then (balance / total_supply) * reserve0_bin / pow(10, token0_decimals) + else 0 end as token0_balance, + token1_address, + token1_symbol, + case + when total_supply > 0 then (balance / total_supply) * reserve1_bin / pow(10, token1_decimals) + else 0 end as token1_balance +from detail_table +; + From d592c82213e19bbc15e64ef22592bb88435d501a Mon Sep 17 00:00:00 2001 From: 2024river Date: Thu, 12 Sep 2024 14:26:08 +0800 Subject: [PATCH 42/70] add uniswapv3 daily job (#134) --- ...ily_feature_holding_balance_uniswap_v3.sql | 33 ++++ .../daily_feature_uniswap_v3_pool_prices.py | 19 +++ .../daily_feature_uniswap_v3_token_details.py | 21 +++ ...riod_feature_holding_balance_uniswap_v3.py | 30 ++++ .../period_feature_uniswap_v3_pool_prices.py | 18 ++ ...period_feature_uniswap_v3_token_details.py | 21 +++ ...iod_feature_holding_balance_uniswap_v3.sql | 157 ++++++++++++++++++ 7 files changed, 299 insertions(+) create mode 100644 indexer/aggr_jobs/disorder_jobs/daily_feature_holding_balance_uniswap_v3.sql create mode 100644 indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py create mode 100644 indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py create mode 100644 indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py create mode 100644 indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py create mode 100644 indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py create mode 100644 indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql diff --git a/indexer/aggr_jobs/disorder_jobs/daily_feature_holding_balance_uniswap_v3.sql b/indexer/aggr_jobs/disorder_jobs/daily_feature_holding_balance_uniswap_v3.sql new file mode 100644 index 000000000..574f46c8e --- /dev/null +++ b/indexer/aggr_jobs/disorder_jobs/daily_feature_holding_balance_uniswap_v3.sql @@ -0,0 +1,33 @@ +begin; +delete +from af_uniswap_v3_token_data_daily +where block_date >= '{start_date}' + and block_date < '{end_date}'; +insert into af_uniswap_v3_token_data_daily +select position_token_address, + TO_TIMESTAMP(block_timestamp)::DATE as block_date, + token_id, + wallet_address, + pool_address, + liquidity +from (select *, row_number() over (partition by nft_address, token_id order by block_timestamp desc) rn + from af_uniswap_v3_token_data_hist + where TO_TIMESTAMP(block_timestamp) >= '{start_date}' + and TO_TIMESTAMP(block_timestamp) < '{end_date}') t +where rn = 1; + + +delete +from af_uniswap_v3_pool_prices_daily +where block_date >= '{start_date}' + and block_date < '{end_date}'; +insert into af_uniswap_v3_pool_prices_daily +select pool_address, + TO_TIMESTAMP(block_timestamp)::DATE as block_date, + sqrt_price_x96 +from (select *, row_number() over (partition by pool_address order by block_timestamp desc) rn + from af_uniswap_v3_pool_prices_hist + where TO_TIMESTAMP(block_timestamp) >= '{start_date}' + and TO_TIMESTAMP(block_timestamp) < '{end_date}') t +where rn = 1; +commit \ No newline at end of file diff --git a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py new file mode 100644 index 000000000..4a95c5751 --- /dev/null +++ b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py @@ -0,0 +1,19 @@ +from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel + + +class DailyFeatureUniswapV3PoolPrices(HemeraModel): + __tablename__ = "af_uniswap_v3_pool_prices_daily" + + block_date = Column(DATE, primary_key=True, nullable=False) + pool_address = Column(BYTEA, primary_key=True, nullable=False) + + sqrt_price_x96 = Column(NUMERIC(78)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +# could be replaced by partition in case of huge amount data +Index("af_uniswap_v3_pool_prices_daily_block_date_index", DailyFeatureUniswapV3PoolPrices.block_date) diff --git a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py new file mode 100644 index 000000000..15620e3c8 --- /dev/null +++ b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py @@ -0,0 +1,21 @@ +from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel + + +class DailyFeatureUniswapV3TokenDeatils(HemeraModel): + __tablename__ = "af_uniswap_v3_token_data_daily" + + block_date = Column(DATE, primary_key=True, nullable=False) + position_token_address = Column(BYTEA, primary_key=True, nullable=False) + token_id = Column(INTEGER, primary_key=True, nullable=False) + wallet_address = Column(BYTEA, nullable=False) + pool_address = Column(BYTEA, nullable=False) + liquidity = Column(NUMERIC(78)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +# could be replaced by partition in case of huge amount data +Index("af_uniswap_v3_token_data_daily_index", DailyFeatureUniswapV3TokenDeatils.block_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py new file mode 100644 index 000000000..62749083a --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py @@ -0,0 +1,30 @@ +from flask_sqlalchemy import SQLAlchemy +from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, String, create_engine, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel, general_converter + +# db = SQLAlchemy(session_options={"autoflush": False}) + + +class PeriodFeatureHoldingBalanceUniswapV3(HemeraModel): + __tablename__ = "af_holding_balance_uniswap_v3_period" + + period_date = Column(DATE, primary_key=True, nullable=False) + protocol_id = Column(String, primary_key=True, nullable=False) + contract_address = Column(BYTEA, primary_key=True, nullable=False) + token_id = Column(INTEGER, primary_key=True, nullable=False) + wallet_address = Column(BYTEA, nullable=False) + token0_address = Column(BYTEA, nullable=False) + token0_symbol = Column(String, nullable=False) + token0_balance = Column(NUMERIC(100, 18)) + + token1_address = Column(BYTEA, nullable=False) + token1_symbol = Column(String, nullable=False) + token1_balance = Column(NUMERIC(100, 18)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +# could be replaced by partition in case of huge amount data +Index("af_holding_balance_uniswap_v3_period_period_date", PeriodFeatureHoldingBalanceUniswapV3.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py new file mode 100644 index 000000000..f8d39901d --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py @@ -0,0 +1,18 @@ +from sqlalchemy import DATE, TIMESTAMP, Column, Index, func +from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC + +from common.models import HemeraModel + + +class PeriodFeatureUniswapV3PoolPrices(HemeraModel): + __tablename__ = "af_uniswap_v3_pool_prices_period" + + period_date = Column(DATE, primary_key=True, nullable=False) + pool_address = Column(BYTEA, primary_key=True, nullable=False) + sqrt_price_x96 = Column(NUMERIC(78)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +# could be replaced by partition in case of huge amount data +Index("af_uniswap_v3_pool_prices_period_period_date_index", PeriodFeatureUniswapV3PoolPrices.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py new file mode 100644 index 000000000..0d168f908 --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py @@ -0,0 +1,21 @@ +from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel + + +class PeriodFeatureUniswapV3TokenDeatils(HemeraModel): + __tablename__ = "af_uniswap_v3_token_data_period" + + period_date = Column(DATE, primary_key=True, nullable=False) + position_token_address = Column(BYTEA, primary_key=True, nullable=False) + token_id = Column(INTEGER, primary_key=True, nullable=False) + wallet_address = Column(BYTEA, nullable=False) + pool_address = Column(BYTEA, nullable=False) + liquidity = Column(NUMERIC(78)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + + +# could be replaced by partition in case of huge amount data +Index("af_uniswap_v3_token_data_period_date_index", PeriodFeatureUniswapV3TokenDeatils.period_date) diff --git a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql new file mode 100644 index 000000000..630629759 --- /dev/null +++ b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql @@ -0,0 +1,157 @@ +delete +from af_uniswap_v3_token_data_period +where period_date >= '{start_date}' + and period_date < '{end_date}'; + +with today_table as (select * + from af_uniswap_v3_token_data_daily + where block_date = '{start_date}'), + yesterday_table as (select * + from af_uniswap_v3_token_data_period + where period_date = '{start_date_previous}') + +insert +into af_uniswap_v3_token_data_period +select COALESCE(s1.position_token_address, s2.position_token_address) AS position_token_address, + date('{start_date}') AS period_date, + COALESCE(s1.token_id, s2.token_id) AS token_id, + COALESCE(s1.wallet_address, s2.wallet_address) AS wallet_address, + COALESCE(s1.pool_address, s2.pool_address) AS pool_address, + COALESCE(s1.liquidity, s2.liquidity, 0) AS liquidity +from today_table s1 + full join + yesterday_table s2 + on s1.position_token_address = s2.position_token_address and s1.token_id = s2.token_id; + +delete +from af_uniswap_v3_pool_prices_period +where period_date >= '{start_date}' + and period_date < '{end_date}'; + +with today_table as (select * + from af_uniswap_v3_pool_prices_daily + where block_date = '{start_date}'), + yesterday_table as (select * + from af_uniswap_v3_pool_prices_period + where period_date = '{start_date_previous}') + +insert +into af_uniswap_v3_pool_prices_period +select COALESCE(s1.pool_address, s2.pool_address) AS pool_address, + date('{start_date}') AS period_date, + COALESCE(s1.sqrt_price_x96, s2.sqrt_price_x96, 0) AS liquidity +from today_table s1 + full join + yesterday_table s2 + on s1.pool_address = s2.pool_address; + +delete +from af_holding_balance_uniswap_v3_period +where period_date >= '{start_date}' + and period_date < '{end_date}'; +with period_token_price as (select symbol, price + from (select symbol, + price, + row_number() over (partition by symbol order by timestamp desc) rn + from token_price + where timestamp < '{end_date}') t + where rn = 1), + tokens_table as (select d1.address, d1.decimals, d1.symbol, d2.price + from tokens d1 + left join + period_token_price d2 on d1.symbol = d2.symbol + where d1.symbol is not null), + detail_table as (SELECT d1.period_date, + d1.wallet_address, + d1.position_token_address, + d1.liquidity, + d1.pool_address, + d1.token_id, + d2.sqrt_price_x96, + d3.tick_lower, + d3.tick_upper, + d4.token0_address, + d4.token1_address, + d5.decimals as toekn0_decimals, + d5.symbol as token0_symbol, + d5.price as token0_price, + d6.decimals as toekn1_decimals, + d6.symbol as token1_symbol, + d6.price as token1_price, + sqrt(EXP(tick_lower * LN(1.0001))) as sqrt_ratio_a, + sqrt(EXP(tick_upper * LN(1.0001))) as sqrt_ratio_b, + FLOOR(LOG((sqrt_price_x96 / pow(2, 96)) ^ 2) / LOG(1.0001)) AS current_tick, + sqrt_price_x96 / pow(2, 96) as sqrt_price + + FROM af_uniswap_v3_token_data_period d1 + inner join af_uniswap_v3_pool_prices_period d2 on + d1.pool_address = d2.pool_address + inner join af_uniswap_v3_tokens d3 + on d1.position_token_address = d3.position_token_address + and d1.token_id = d3.token_id + inner join af_uniswap_v3_pools d4 + on d1.pool_address = d4.pool_address + inner join tokens_table d5 + on d4.token0_address = d5.address + inner join tokens_table d6 + on d4.token1_address = d6.address + where d1.period_date = '{start_date}' + and d2.period_date = '{start_date}'), + tick_table as (select period_date, + wallet_address, + position_token_address, + token_id, + token0_address, + token0_symbol, + token1_symbol, + token1_address, + toekn0_decimals, + toekn1_decimals, + token0_price, + token1_price, + liquidity, + tick_lower, + tick_upper, + case + when current_tick <= tick_lower then + FLOOR(liquidity * ((sqrt_ratio_b - sqrt_ratio_a) / (sqrt_ratio_a * sqrt_ratio_b))) + when current_tick > tick_lower and current_tick < tick_upper then + FLOOR(liquidity * ((sqrt_ratio_b - sqrt_price) / (sqrt_price * sqrt_ratio_b))) + else 0 + end / pow(10, toekn0_decimals) AS token0_balance, + case + when current_tick >= tick_upper then floor(liquidity * (sqrt_ratio_b - sqrt_ratio_a)) + when current_tick > tick_lower and current_tick < tick_upper then + floor(liquidity * (sqrt_price - sqrt_ratio_a)) + else 0 end / pow(10, toekn1_decimals) AS token1_balance + from detail_table) +insert +into af_holding_balance_uniswap_v3_period(protocol_id, + contract_address, + period_date, + token_id, + wallet_address, + token0_address, + token0_symbol, + token0_balance, + token1_address, + token1_symbol, + token1_balance) +select case + when position_token_address = '\x218bf598d1453383e2f4aa7b14ffb9bfb102d637' + then 'agni' + when position_token_address = '\xaaa78e8c4241990b4ce159e105da08129345946a' then 'cleoexchange' + when position_token_address = '\xc36442b4a4522e871399cd717abdd847ab11fe88' then 'uniswap_v3' + else 'uniswap_v3' end as protoco_id, + + position_token_address, + period_date, + token_id, + wallet_address, + token0_address, + token0_symbol, + token0_balance, + token1_address, + token1_symbol, + token1_balance +from tick_table; \ No newline at end of file From e857fb444a1ac65c5922361d160cd2b5585164ce Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Thu, 12 Sep 2024 14:49:36 +0800 Subject: [PATCH 43/70] Feature/merchant moe (#119) add merchant moe feature --- api/app/api.py | 2 + indexer/modules/custom/feature_type.py | 1 + .../modules/custom/merchant_moe/__init__.py | 0 .../modules/custom/merchant_moe/constants.py | 42 +++ .../custom/merchant_moe/domain/__init__.py | 0 .../merchant_moe/domain/merchant_moe.py | 50 +++ .../custom/merchant_moe/endpoints/__init__.py | 5 + .../custom/merchant_moe/endpoints/routes.py | 137 ++++++++ ...chant_moe_1155_token_holding_detail_job.py | 325 ++++++++++++++++++ .../custom/merchant_moe/models/__init__.py | 0 .../feature_erc1155_token_current_supply.py | 32 ++ .../models/feature_erc1155_token_supply.py | 40 +++ .../models/feature_merchant_moe_pool.py | 32 ++ .../models/feature_merchant_moe_token_bin.py | 40 +++ .../feature_merchant_moe_token_current_bin.py | 39 +++ 15 files changed, 745 insertions(+) create mode 100644 indexer/modules/custom/merchant_moe/__init__.py create mode 100644 indexer/modules/custom/merchant_moe/constants.py create mode 100644 indexer/modules/custom/merchant_moe/domain/__init__.py create mode 100644 indexer/modules/custom/merchant_moe/domain/merchant_moe.py create mode 100644 indexer/modules/custom/merchant_moe/endpoints/__init__.py create mode 100644 indexer/modules/custom/merchant_moe/endpoints/routes.py create mode 100644 indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py create mode 100644 indexer/modules/custom/merchant_moe/models/__init__.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py diff --git a/api/app/api.py b/api/app/api.py index 7b825dca1..0a3e6b2c7 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -8,6 +8,7 @@ from api.app.deposit_to_l2.routes import token_deposit_namespace from api.app.explorer.routes import explorer_namespace from api.app.user_operation.routes import user_operation_namespace +from indexer.modules.custom.merchant_moe.endpoints.routes import merchant_moe_namespace from indexer.modules.custom.opensea.endpoint.routes import opensea_namespace from indexer.modules.custom.uniswap_v3.endpoints.routes import uniswap_v3_namespace @@ -21,6 +22,7 @@ api.add_namespace(uniswap_v3_namespace) api.add_namespace(token_deposit_namespace) api.add_namespace(user_operation_namespace) +api.add_namespace(merchant_moe_namespace) # api.add_namespace(l2_explorer_namespace) api.add_namespace(af_ens_namespace) diff --git a/indexer/modules/custom/feature_type.py b/indexer/modules/custom/feature_type.py index 3b967603d..5f2c576ac 100644 --- a/indexer/modules/custom/feature_type.py +++ b/indexer/modules/custom/feature_type.py @@ -7,3 +7,4 @@ class FeatureType(Enum): DAY_MINING = 3 UNISWAP_V2_INFO = 4 BLUE_CHIP_HOLDING = 5 + MERCHANT_MOE_1155_LIQUIDITY = 6 diff --git a/indexer/modules/custom/merchant_moe/__init__.py b/indexer/modules/custom/merchant_moe/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/merchant_moe/constants.py b/indexer/modules/custom/merchant_moe/constants.py new file mode 100644 index 000000000..60de165e9 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/constants.py @@ -0,0 +1,42 @@ +ABI_LIST = [ + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "totalSupply", + "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [{"internalType": "uint24", "name": "id", "type": "uint24"}], + "name": "getBin", + "outputs": [ + {"internalType": "uint128", "name": "binReserveX", "type": "uint128"}, + {"internalType": "uint128", "name": "binReserveY", "type": "uint128"}, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "getTokenX", + "outputs": [{"internalType": "address", "name": "tokenX", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "getTokenY", + "outputs": [{"internalType": "address", "name": "tokenY", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, +] + +LIQUIDITY_LIST = [ + # DepositedToBins + "0x87f1f9dcf5e8089a3e00811b6a008d8f30293a3da878cb1fe8c90ca376402f8a", + # WithdrawnFromBins + "0xa32e146844d6144a22e94c586715a1317d58a8aa3581ec33d040113ddcb24350", + # TransferBatch + "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb", +] diff --git a/indexer/modules/custom/merchant_moe/domain/__init__.py b/indexer/modules/custom/merchant_moe/domain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/merchant_moe/domain/merchant_moe.py b/indexer/modules/custom/merchant_moe/domain/merchant_moe.py new file mode 100644 index 000000000..5c30172a2 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/domain/merchant_moe.py @@ -0,0 +1,50 @@ +from dataclasses import dataclass + +from indexer.domain import Domain, FilterData + + +@dataclass +class MerChantMoeTokenBin(FilterData): + position_token_address: str + token_id: int + reserve0_bin: int + reserve1_bin: int + block_number: int + block_timestamp: int + + +@dataclass +class MerChantMoeTokenCurrentBin(FilterData): + position_token_address: str + token_id: int + reserve0_bin: int + reserve1_bin: int + block_number: int + block_timestamp: int + + +@dataclass +class MerChantMoePool(FilterData): + position_token_address: str + token0_address: str + token1_address: str + block_number: int + block_timestamp: int + + +@dataclass +class MerchantMoeErc1155TokenSupply(FilterData): + position_token_address: str + token_id: int + total_supply: int + block_number: int + block_timestamp: int + + +@dataclass +class MerchantMoeErc1155TokenCurrentSupply(FilterData): + position_token_address: str + token_id: int + total_supply: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/merchant_moe/endpoints/__init__.py b/indexer/modules/custom/merchant_moe/endpoints/__init__.py new file mode 100644 index 000000000..8e89bc5c6 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/endpoints/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from flask_restx.namespace import Namespace + +merchant_moe_namespace = Namespace("MerchantMoe Data", path="/", description="MerchantMoe Data Feature Explorer API") diff --git a/indexer/modules/custom/merchant_moe/endpoints/routes.py b/indexer/modules/custom/merchant_moe/endpoints/routes.py new file mode 100644 index 000000000..519cb1f21 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/endpoints/routes.py @@ -0,0 +1,137 @@ +import binascii +import math +import re +from datetime import datetime, timedelta +from decimal import Decimal, getcontext +from operator import or_ + +from flask import request +from flask_restx import Resource +from sqlalchemy import and_, asc, desc, func + +from api.app import explorer +from api.app.cache import cache +from api.app.explorer import explorer_namespace +from common.models import db +from common.models import db as postgres_db +from common.models.current_token_balances import CurrentTokenBalances +from common.models.tokens import Tokens +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict, format_to_dict, format_value_for_json, row_to_dict +from common.utils.web3_utils import is_eth_address +from indexer.modules.custom.merchant_moe.endpoints import merchant_moe_namespace +from indexer.modules.custom.merchant_moe.models.feature_erc1155_token_current_supply import ( + FeatureErc1155TokenCurrentSupplyStatus, +) +from indexer.modules.custom.merchant_moe.models.feature_merchant_moe_pool import FeatureMerChantMoePools +from indexer.modules.custom.merchant_moe.models.feature_merchant_moe_token_current_bin import ( + FeatureMerChantMoeTokenBinCurrentStatus, +) + +Q96 = 2**96 + + +@merchant_moe_namespace.route("/v1/aci//merchantmoe/current_holding") +class MerchantMoeWalletHolding(Resource): + def get(self, wallet_address): + pool_infos = {} + pool_tokens = db.session.query(FeatureMerChantMoePools).all() + + tokens = set() + for data in pool_tokens: + tokens.add(data.token0_address) + tokens.add(data.token1_address) + pool_infos["0x" + data.token_address.hex()] = { + "getTokenX": "0x" + data.token0_address.hex(), + "getTokenY": "0x" + data.token1_address.hex(), + } + wallet_address = wallet_address.lower() + address_bytes = bytes.fromhex(wallet_address[2:]) + holdings = ( + db.session.query(CurrentTokenBalances) + .filter( + CurrentTokenBalances.address == address_bytes, + CurrentTokenBalances.token_type == "ERC1155", + CurrentTokenBalances.balance > 0, + ) + .all() + ) + # get totalSupply + unique_token_addresses = {holding.token_address for holding in holdings} + + total_supply_list = ( + db.session.query(FeatureErc1155TokenCurrentSupplyStatus) + .filter(FeatureErc1155TokenCurrentSupplyStatus.token_address.in_(unique_token_addresses)) + .all() + ) + + token_bin_list = ( + db.session.query(FeatureMerChantMoeTokenBinCurrentStatus) + .filter(FeatureMerChantMoeTokenBinCurrentStatus.token_address.in_(unique_token_addresses)) + .all() + ) + + total_supply_map = {} + token_bin_map = {} + for data in total_supply_list: + key = (data.token_address, data.token_id) + total_supply_map[key] = data.total_supply + for data in token_bin_list: + key = (data.token_address, data.token_id) + token_bin_map[key] = data + token_id_infos = {} + + erc20_datas = db.session.query(Tokens).filter(Tokens.address.in_(tokens)).all() + erc20_infos = {} + for data in erc20_datas: + erc20_infos["0x" + data.address.hex()] = data + + result = [] + token_total_amount = {} + for holding in holdings: + nft_address = "0x" + holding.token_address.hex() + if nft_address not in pool_infos: + continue + token_info = pool_infos[nft_address] + token0_address = token_info["getTokenX"] + token1_address = token_info["getTokenY"] + token_id = holding.token_id + key = (holding.token_address, token_id) + total_supply = total_supply_map[key] + token_bin = token_bin_map[key] + token_bin0 = token_bin.reserve0_bin + token_bin1 = token_bin.reserve1_bin + rate = holding.balance / total_supply + token0_balance = token_bin0 * rate + token1_balance = token_bin1 * rate + token0_info = erc20_infos[token0_address] + token1_info = erc20_infos[token1_address] + + amount0_human = token0_balance / 10**token0_info.decimals + amount1_human = token1_balance / 10**token1_info.decimals + if token0_address not in token_total_amount: + token_total_amount[token0_address] = amount0_human + else: + token_total_amount[token0_address] = amount0_human + token_total_amount[token0_address] + if token1_address not in token_total_amount: + token_total_amount[token1_address] = amount1_human + else: + token_total_amount[token1_address] = amount1_human + token_total_amount[token1_address] + result.append( + { + "nft_address": nft_address, + "token_id": str(token_id), + "token0": { + "token0_symbol": token0_info.symbol, + "token0_balance": str(amount0_human), + }, + "token1": { + "token1_symbol": token1_info.symbol, + "token1_balance": str(amount1_human), + }, + } + ) + for key, value in token_total_amount.items(): + token_total_amount[key] = str(value) + + return {"detail": result, "token_all": token_total_amount}, 200 diff --git a/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py new file mode 100644 index 000000000..d55ef746b --- /dev/null +++ b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py @@ -0,0 +1,325 @@ +import configparser +import json +import logging +import os +from collections import defaultdict + +import eth_abi + +from indexer.domain.token_balance import TokenBalance +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.feature_type import FeatureType +from indexer.modules.custom.merchant_moe import constants +from indexer.modules.custom.merchant_moe.domain.merchant_moe import ( + MerchantMoeErc1155TokenCurrentSupply, + MerchantMoeErc1155TokenSupply, + MerChantMoePool, + MerChantMoeTokenBin, + MerChantMoeTokenCurrentBin, +) +from indexer.modules.custom.merchant_moe.models.feature_merchant_moe_pool import FeatureMerChantMoePools +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) +FEATURE_ID = FeatureType.MERCHANT_MOE_1155_LIQUIDITY.value + + +class ExportMerchantMoe1155LiquidityJob(FilterTransactionDataJob): + dependency_types = [TokenBalance] + output_types = [ + MerchantMoeErc1155TokenSupply, + MerchantMoeErc1155TokenCurrentSupply, + MerChantMoeTokenBin, + MerChantMoeTokenCurrentBin, + MerChantMoePool, + ] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._need_collected_list = constants.LIQUIDITY_LIST + self._exist_pool = get_exist_pools(self._service) + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(topics=self._need_collected_list), + ] + ) + + def _collect(self, **kwargs): + if self._need_collected_list is None or len(self._need_collected_list) == 0: + return + token_balances = self._data_buff[TokenBalance.type()] + if token_balances is None or len(token_balances) == 0: + return + self._batch_work_executor.execute( + token_balances, self._collect_batch, total_items=len(token_balances), split_method=split_token_balances + ) + + self._batch_work_executor.wait() + + def _collect_batch(self, token_balances) -> None: + if token_balances is None or len(token_balances) == 0: + return + token_address = next(iter(token_balances)) + infos = token_balances[token_address] + if infos is None or len(infos) == 0: + return + # check the token_address is in merchant_moe + if token_address not in self._exist_pool: + if infos[0].token_id is None or infos[0].token_id < 0 or infos[0].token_type != "ERC1155": + return + requests = [ + { + "block_number": infos[0].block_number, + "address": token_address, + } + ] + + token0_infos = common_utils.simple_get_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + requests, + self._is_batch, + constants.ABI_LIST, + "getTokenX", + "address", + self._batch_size, + self._max_worker, + ) + + if len(token0_infos) == 0 or "getTokenX" not in token0_infos[0] or token0_infos[0]["getTokenX"] is None: + return + token1_infos = common_utils.simple_get_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + requests, + self._is_batch, + constants.ABI_LIST, + "getTokenY", + "address", + self._batch_size, + self._max_worker, + ) + if len(token1_infos) == 0 or "getTokenY" not in token1_infos[0] or token1_infos[0]["getTokenY"] is None: + return + self._exist_pool.add(token_address) + self._collect_item( + MerChantMoePool.type(), + MerChantMoePool( + position_token_address=token_address, + token0_address=token1_infos[0]["getTokenX"], + token1_address=token1_infos[0]["getTokenY"], + block_number=infos[0].block_number, + block_timestamp=infos[0].block_timestamp, + ), + ) + + need_call_list = [] + token_id_info_set = set() + for token_balance in infos: + token_address = token_balance.token_address + block_number = token_balance.block_number + block_timestamp = token_balance.block_timestamp + token_id = token_balance.token_id + if (token_id, block_number) not in token_id_info_set: + token_id_info_set.add((token_id, block_number)) + need_call_list.append( + { + "block_number": block_number, + "block_timestamp": block_timestamp, + "token_id": token_id, + } + ) + total_supply_dtos = batch_get_total_supply( + self._web3, + self._batch_web3_provider.make_request, + need_call_list, + token_address, + self._is_batch, + constants.ABI_LIST, + ) + total_bin_dtos = batch_get_bin( + self._web3, + self._batch_web3_provider.make_request, + need_call_list, + token_address, + self._is_batch, + constants.ABI_LIST, + ) + current_total_supply_dict = {} + current_token_bin_dict = {} + for data in total_bin_dtos: + token_id = data["token_id"] + block_number = data["block_number"] + block_timestamp = data["block_timestamp"] + total_supply = data["totalSupply"] + reserve0_bin = data["reserve0_bin"] + reserve1_bin = data["reserve1_bin"] + common_data = { + "position_token_address": token_address, + "token_id": token_id, + "block_number": block_number, + "block_timestamp": block_timestamp, + } + + key = token_id + if key not in current_total_supply_dict or block_number > current_total_supply_dict[key].block_number: + current_total_supply_dict[key] = MerchantMoeErc1155TokenCurrentSupply( + **common_data, + total_supply=total_supply, + ) + if key not in current_token_bin_dict or block_number > current_token_bin_dict[key].block_number: + current_token_bin_dict[key] = MerChantMoeTokenCurrentBin( + **common_data, + reserve0_bin=reserve0_bin, + reserve1_bin=reserve1_bin, + ) + self._collect_item( + MerchantMoeErc1155TokenSupply.type(), + MerchantMoeErc1155TokenSupply( + **common_data, + total_supply=total_supply, + ), + ) + self._collect_item( + MerChantMoeTokenBin.type(), + MerChantMoeTokenBin( + **common_data, + reserve0_bin=reserve0_bin, + reserve1_bin=reserve1_bin, + ), + ) + for data in current_total_supply_dict.values(): + self._collect_item(MerchantMoeErc1155TokenCurrentSupply.type(), data) + for data in current_token_bin_dict.values(): + self._collect_item(MerChantMoeTokenCurrentBin.type(), data) + + def _process(self, **kwargs): + + self._data_buff[MerchantMoeErc1155TokenSupply.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerChantMoeTokenBin.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerchantMoeErc1155TokenCurrentSupply.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerChantMoeTokenCurrentBin.type()].sort(key=lambda x: x.block_number) + + +def split_token_balances(token_balances): + token_balance_dict = defaultdict(list) + for data in token_balances: + token_balance_dict[data.token_address].append(data) + + for token_address, data in token_balance_dict.items(): + yield {token_address: data} + + +def batch_get_bin(web3, make_requests, requests, nft_address, is_batch, abi_list): + fn_name = "getBin" + if len(requests) == 0: + return [] + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = common_utils.build_one_input_one_output_method_data(web3, requests, nft_address, fn_name, abi_list) + + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token["reserve0_bin"] = decoded_data[0] + token["reserve1_bin"] = decoded_data[1] + + except Exception as e: + logger.error( + f"Decoding token info failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + +def batch_get_total_supply(web3, make_requests, requests, nft_address, is_batch, abi_list): + fn_name = "totalSupply" + if len(requests) == 0: + return [] + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = common_utils.build_one_input_one_output_method_data(web3, requests, nft_address, fn_name, abi_list) + + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token[fn_name] = decoded_data[0] + + except Exception as e: + logger.error( + f"Decoding token info failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + +def get_exist_pools(db_service): + if not db_service: + return [] + + session = db_service.get_service_session() + try: + result = session.query(FeatureMerChantMoePools).all() + history_pools = set() + if result is not None: + for item in result: + history_pools.add("0x" + item.token_address.hex()) + except Exception as e: + print(e) + raise e + finally: + session.close() + return history_pools diff --git a/indexer/modules/custom/merchant_moe/models/__init__.py b/indexer/modules/custom/merchant_moe/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py new file mode 100644 index 000000000..213509f85 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureErc1155TokenCurrentSupplyStatus(HemeraModel): + __tablename__ = "af_merchant_moe_token_supply_current" + position_token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_timestamp = Column(BIGINT) + block_number = Column(BIGINT) + + total_supply = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerchantMoeErc1155TokenCurrentSupply", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_merchant_moe_token_supply_current.block_number", + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py new file mode 100644 index 000000000..279b6e0c8 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureErc1155TokenSupplyRecords(HemeraModel): + position_token_address = "af_merchant_moe_token_supply_hist" + token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + block_number = Column(BIGINT, primary_key=True) + + total_supply = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerchantMoeErc1155TokenSupply", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] + + +Index( + "af_merchant_moe_token_supply_hist_token_block_desc_index", + desc(FeatureErc1155TokenSupplyRecords.position_token_address), + desc(FeatureErc1155TokenSupplyRecords.block_timestamp), +) diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py new file mode 100644 index 000000000..9a5b216e0 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureMerChantMoePools(HemeraModel): + __tablename__ = "af_merchant_moe_pools" + position_token_address = Column(BYTEA, primary_key=True) + block_timestamp = Column(BIGINT) + block_number = Column(BIGINT) + token0_address = Column(BYTEA) + token1_address = Column(BYTEA) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("position_token_address"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerChantMoePool", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py new file mode 100644 index 000000000..0d5c95375 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py @@ -0,0 +1,40 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureMerChantMoeTokenBinRecords(HemeraModel): + __tablename__ = "af_merchant_moe_token_bin_hist" + position_token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + block_number = Column(BIGINT, primary_key=True) + reserve0_bin = Column(NUMERIC(100)) + reserve1_bin = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerChantMoeTokenBin", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] + + +Index( + "af_merchant_moe_token_bin_hist_token_block_desc_index", + desc(FeatureMerChantMoeTokenBinRecords.position_token_address), + desc(FeatureMerChantMoeTokenBinRecords.block_timestamp), +) diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py new file mode 100644 index 000000000..257073afb --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py @@ -0,0 +1,39 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, asc, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureMerChantMoeTokenBinCurrentStatus(HemeraModel): + __tablename__ = "af_merchant_moe_token_bin_current" + position_token_address = Column(BYTEA, primary_key=True) + token_id = Column(NUMERIC(100), primary_key=True) + block_timestamp = Column(BIGINT) + block_number = Column(BIGINT) + reserve0_bin = Column(NUMERIC(100)) + reserve1_bin = Column(NUMERIC(100)) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerChantMoeTokenCurrentBin", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_merchant_moe_token_bin_current.block_number", + "converter": general_converter, + } + ] + + +Index( + "af_merchant_moe_token_bin_current_token_id_index", + desc(FeatureMerChantMoeTokenBinCurrentStatus.position_token_address), + asc(FeatureMerChantMoeTokenBinCurrentStatus.token_id), +) From 88e85d7cab40246abd78c2181aaf0b098ac2437b Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:40:44 +0800 Subject: [PATCH 44/70] bugfix ens api (#129) * bugfix ens api and indexer --- api/app/af_ens/action_types.py | 15 +++ api/app/af_ens/routes.py | 113 +++++++++++++++--- common/services/postgresql_service.py | 2 +- indexer/modules/custom/hemera_ens/ens_conf.py | 1 + .../modules/custom/hemera_ens/ens_handler.py | 2 + .../modules/custom/hemera_ens/extractors.py | 41 +++++++ 6 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 api/app/af_ens/action_types.py diff --git a/api/app/af_ens/action_types.py b/api/app/af_ens/action_types.py new file mode 100644 index 000000000..b7beec19a --- /dev/null +++ b/api/app/af_ens/action_types.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# @Time 2024/9/12 10:22 +# @Author will +# @File action_types.py +# @Brief +from enum import Enum + + +class OperationType(Enum): + REGISTER = "register" + SET_PRIMARY_NAME = "set_primary_name" + SET_RESOLVED_ADDRESS = "set_resolved_address" + RENEW = "renew" + TRANSFER = "transfer" diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index edd6d95ab..cd11bb3e9 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -5,9 +5,12 @@ from flask_restx.namespace import Namespace from web3 import Web3 +from api.app.af_ens.action_types import OperationType from common.models import db from common.models.current_token_balances import CurrentTokenBalances from common.models.erc721_token_id_changes import ERC721TokenIdChanges +from common.models.erc721_token_transfers import ERC721TokenTransfers +from common.models.erc1155_token_transfers import ERC1155TokenTransfers from common.utils.config import get_config from indexer.modules.custom.hemera_ens.models.af_ens_address_current import ENSAddress from indexer.modules.custom.hemera_ens.models.af_ens_event import ENSMiddle @@ -73,7 +76,12 @@ def get(self, address): res["first_register_time"] = datetime_to_string(first_register.block_timestamp) first_set_name = ( db.session.query(ENSMiddle) - .filter(or_(ENSMiddle.method == "setName", ENSMiddle.event_name == "NameChanged")) + .filter( + and_( + ENSMiddle.from_address == address, + or_(ENSMiddle.method == "setName", ENSMiddle.event_name == "NameChanged"), + ) + ) .first() ) if first_set_name: @@ -97,6 +105,22 @@ def get(self, address): .order_by(ENSMiddle.block_number, ENSMiddle.transaction_index, ENSMiddle.log_index.desc()) .all() ) + # erc721_ids = list({r.token_id for r in all_records_rows if r.token_id}) + # erc721_id_transfers = ( + # db.session.query(ERC721TokenTransfers) + # .filter(ERC721TokenTransfers.token_id.in_(erc721_ids)) + # .order_by(ERC721TokenTransfers.block_number) + # .all() + # ) + # + # erc1155_ids = list({r.w_token_id for r in all_records_rows if r.w_token_id}) + # erc1155_id_transfers = ( + # db.session.query(ERC1155TokenTransfers) + # .filter(ERC1155TokenTransfers.token_id.in_(erc1155_ids)) + # .order_by(ERC1155TokenTransfers.block_number) + # .all() + # ) + node_name_map = {} for r in all_records_rows: if r.name: @@ -104,24 +128,85 @@ def get(self, address): node_name_map[r.node] = r.name else: node_name_map[r.node] = r.name + ".eth" + token_id_name_map = dict() for r in all_records_rows: - lis.append( - { - "method": r.method, - "event": r.event_name, - "block_number": r.block_number, - "block_timestamp": datetime_to_string(r.block_timestamp), - "transaction_index": r.transaction_index, - "log_index": r.log_index, - "transaction_hash": "0x" + r.transaction_hash.hex(), - "node": "0x" + r.node.hex(), - "name": node_name_map.get(r.node), - } - ) + if r.token_id: + token_id_name_map[r.token_id] = node_name_map[r.node] + if r.w_token_id: + token_id_name_map[r.w_token_id] = node_name_map[r.node] + + all_rows = merge_ens_middle(all_records_rows) + + for r in all_rows: + name = None + if hasattr(r, "node") and r.node: + name = node_name_map[r.node] + elif hasattr(r, "token_id") and r.token_id: + name = token_id_name_map[r.token_id] + elif hasattr(r, "w_token_id") and r.w_token_id: + name = token_id_name_map[r.w_token_id] + base = { + "block_number": r.block_number, + "block_timestamp": datetime_to_string(r.block_timestamp), + "transaction_hash": "0x" + r.transaction_hash.hex(), + "name": name, + } + + extras = get_action_type(r) + base.update(extras) + lis.append(base) return lis +def merge_ens_middle(records): + """Merge consecutive records when setAddr and nameRegistered are duplicated""" + if not records: + return [] + + res = [records[0]] + + for current_record in records[1:]: + previous_record = res[-1] + + # Check if the current record should be merged with the previous one + if ( + current_record.event_name == previous_record.event_name == "AddressChanged" + and current_record.name == previous_record.name + ) or ( + current_record.event_name == previous_record.event_name == "NameRegistered" + and current_record.name == previous_record.name + ): + for column in ENSMiddle.__table__.columns: + current_value = getattr(current_record, column.name) + previous_value = getattr(previous_record, column.name) + if previous_value is None and current_value is not None: + setattr(previous_record, column.name, current_value) + else: + res.append(current_record) + + return res + + +def get_action_type(record): + if isinstance(record, ERC721TokenTransfers) or isinstance(record, ERC1155TokenTransfers): + return { + "action_type": OperationType.TRANSFER.value, + "from": "0x" + record.from_address.hex(), + "to": "0x" + record.to_address.hex(), + "token_id": int(record.token_id), + } + if record.method == "setName" or record.event_name == "NameChanged": + return {"action_type": OperationType.SET_PRIMARY_NAME.value} + if record.event_name == "NameRegistered": + return {"action_type": OperationType.REGISTER.value} + if record.event_name == "NameRenewed": + return {"action_type": OperationType.RENEW.value, "expires": datetime_to_string(record.expires)} + if record.event_name == "AddressChanged": + return {"action_type": OperationType.SET_RESOLVED_ADDRESS.value, "address": "0x" + record.address.hex()} + raise ValueError("Unknown operation type") + + def datetime_to_string(dt, format="%Y-%m-%d %H:%M:%S"): if isinstance(dt, datetime): return dt.strftime(format) diff --git a/common/services/postgresql_service.py b/common/services/postgresql_service.py index 7dc2daed9..b1db34f31 100644 --- a/common/services/postgresql_service.py +++ b/common/services/postgresql_service.py @@ -28,7 +28,7 @@ def __new__(cls, *args, **kwargs): cls.instance = super().__new__(cls) return cls.instance - def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=True): + def __init__(self, jdbc_url, db_version="head", script_location="migrations", init_schema=False): self.db_version = db_version self.engine = create_engine( jdbc_url, diff --git a/indexer/modules/custom/hemera_ens/ens_conf.py b/indexer/modules/custom/hemera_ens/ens_conf.py index ef62be1ca..bd77edef9 100644 --- a/indexer/modules/custom/hemera_ens/ens_conf.py +++ b/indexer/modules/custom/hemera_ens/ens_conf.py @@ -45,6 +45,7 @@ "0x27c9b34eb43523447d3e1bcf26f009d814522687": "TransparentUpgradeableProxy", "0x5d81ce189bf5267e70fb555916ffbc3d4c7b2245": "EnsBatchRenew", "0x6109dd117aa5486605fc85e040ab00163a75c662": "RegistrarMigration", + "0x6090a6e47849629b7245dfa1ca21d94cd15878ef": "ENS: Old Registrar", } diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index ab9ab4e01..0e198b008 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -99,6 +99,8 @@ def __init__(self, ens_conf_loader): self.extractors = [extractor() for extractor in BaseExtractor.__subclasses__()] def is_ens_address(self, address): + if address == "0x00000000008794027c69c26d2a048dbec09de67c": + return True return address.lower() in self.ens_conf_loader.contract_object_map def get_event_name(self, sig): diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py index d5d3f0862..373cc3d30 100644 --- a/indexer/modules/custom/hemera_ens/extractors.py +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -191,6 +191,47 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, return None +class AncientRegister(BaseExtractor): + + def __init__(self): + self.address = "0x6090a6e47849629b7245dfa1ca21d94cd15878ef" + self.tp0 = "0x0f0c27adfd84b60b6f456b0e87cdccb1e5fb9603991588d87fa99f5b6b61e670" + + def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, prev_logs=None) -> ENSMiddleD: + if tp0 == self.tp0: + event_data = decode_log(log, contract_object_map, event_map) + tmp = event_data["args"] + ens_middle.expires = convert_str_ts(tmp.get("expires", "")) + + ens_middle.label = log["topic1"] + ens_middle.owner = extract_eth_address(log["topic2"]) + ens_middle.base_node = BASE_NODE + ens_middle.node = compute_node_label(BASE_NODE, ens_middle.label) + ens_middle.event_name = event_data["_event"] + token_id = str(int(log["topic1"], 16)) + return ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + topic0=tp0, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + expires=ens_middle.expires, + name=ens_middle.name, + label=ens_middle.label, + owner=ens_middle.owner, + base_node=ens_middle.base_node, + node=ens_middle.node, + event_name=ens_middle.event_name, + method=ens_middle.method, + token_id=token_id, + w_token_id=None, + ) + + class NameRenewExtractor(BaseExtractor): def __init__(self): self.address = "0x253553366da8546fc250f225fe3d25d0c782303b" From 3751d5bec8c27c803fe27686c53415c20e85589f Mon Sep 17 00:00:00 2001 From: 2024river Date: Thu, 12 Sep 2024 15:48:09 +0800 Subject: [PATCH 45/70] adjust contract address to position_token_address (#137) --- .../models/period_feature_holding_balance_merchantmoe.py | 2 +- .../models/period_feature_holding_balance_uniswap_v3.py | 2 +- .../order_jobs/period_feature_holding_balance_merchantmoe.sql | 4 ++-- .../order_jobs/period_feature_holding_balance_uniswap_v3.sql | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py index 2d650c09d..9b40ae69a 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py @@ -9,7 +9,7 @@ class PeriodFeatureHoldingBalanceMerchantmoe(HemeraModel): period_date = Column(DATE, primary_key=True, nullable=False) protocol_id = Column(String, primary_key=True, nullable=False) - contract_address = Column(BYTEA, primary_key=True, nullable=False) + position_token_address = Column(BYTEA, primary_key=True, nullable=False) token_id = Column(NUMERIC, primary_key=True, nullable=False) wallet_address = Column(BYTEA, primary_key=True, nullable=False) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py index 62749083a..63fd4c58f 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py @@ -12,7 +12,7 @@ class PeriodFeatureHoldingBalanceUniswapV3(HemeraModel): period_date = Column(DATE, primary_key=True, nullable=False) protocol_id = Column(String, primary_key=True, nullable=False) - contract_address = Column(BYTEA, primary_key=True, nullable=False) + position_token_address = Column(BYTEA, primary_key=True, nullable=False) token_id = Column(INTEGER, primary_key=True, nullable=False) wallet_address = Column(BYTEA, nullable=False) token0_address = Column(BYTEA, nullable=False) diff --git a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql index 9648af0e6..5f960bd29 100644 --- a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql +++ b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_merchantmoe.sql @@ -17,7 +17,7 @@ from af_holding_balance_merchantmoe_period where period_date >= '{start_date}' and period_date < '{end_date}'; insert -into af_holding_balance_merchantmoe_period(period_date, protocol_id, contract_address, token_id, +into af_holding_balance_merchantmoe_period(period_date, protocol_id, position_token_address, token_id, wallet_address, token0_address, token0_symbol, token0_balance, token1_address, token1_symbol, token1_balance) with moe_pools_table as (select d0.*, @@ -66,7 +66,7 @@ with moe_pools_table as (select d0.*, select date('{start_date}'), 'merchantmoe' as protocol_id, - token_address as nft_addres, + token_address as position_token_address, token_id, address, token0_address, diff --git a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql index 630629759..6a353f303 100644 --- a/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql +++ b/indexer/aggr_jobs/order_jobs/period_feature_holding_balance_uniswap_v3.sql @@ -127,7 +127,7 @@ with period_token_price as (select symbol, price from detail_table) insert into af_holding_balance_uniswap_v3_period(protocol_id, - contract_address, + position_token_address, period_date, token_id, wallet_address, From 97dcbfed8df816154b9a7c4b4b1f21bb7c070b3e Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:36:56 +0800 Subject: [PATCH 46/70] add merchant_moe config --- indexer/modules/custom/merchant_moe/config.ini | 3 +++ .../merchant_moe/models/feature_erc1155_token_supply.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 indexer/modules/custom/merchant_moe/config.ini diff --git a/indexer/modules/custom/merchant_moe/config.ini b/indexer/modules/custom/merchant_moe/config.ini new file mode 100644 index 000000000..7d8541f75 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/config.ini @@ -0,0 +1,3 @@ +[5000] +address_list = 0x3880233e78966eb13a9c2881d5f162d646633178 +liquidity_list = 0x87f1f9dcf5e8089a3e00811b6a008d8f30293a3da878cb1fe8c90ca376402f8a,0xa32e146844d6144a22e94c586715a1317d58a8aa3581ec33d040113ddcb24350,0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb diff --git a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py index 279b6e0c8..83c93ac03 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py +++ b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py @@ -7,8 +7,8 @@ class FeatureErc1155TokenSupplyRecords(HemeraModel): - position_token_address = "af_merchant_moe_token_supply_hist" - token_address = Column(BYTEA, primary_key=True) + __tablename__ = "af_merchant_moe_token_supply_hist" + position_token_address = Column(BYTEA, primary_key=True) token_id = Column(NUMERIC(100), primary_key=True) block_timestamp = Column(BIGINT, primary_key=True) block_number = Column(BIGINT, primary_key=True) From d3c0708fdd77d12dd0144b25045bdd8bd5c6ab5b Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:42:33 +0800 Subject: [PATCH 47/70] migration scripts generate and repair (#132) * fixd old migration file * fixd old migration file * add custom logic to alembic env * remove exclude path * generate new migration file for address_index and opensea tables * fixed reorg column server default value * Standardize column data type & Part of tables align with the online version * add migration scripts for uniswap and merchant-moe daily table * add migration scripts for merchant-moe table * modify column name --------- Co-authored-by: knight --- common/models/__init__.py | 11 +- common/models/blocks.py | 4 +- common/models/coin_balances.py | 4 +- .../models/contract_internal_transactions.py | 4 +- common/models/contracts.py | 7 +- common/models/current_token_balances.py | 4 +- common/models/erc1155_token_id_details.py | 4 +- common/models/erc1155_token_transfers.py | 4 +- common/models/erc20_token_transfers.py | 4 +- common/models/erc721_token_id_changes.py | 4 +- common/models/erc721_token_id_details.py | 4 +- common/models/erc721_token_transfers.py | 4 +- common/models/logs.py | 4 +- common/models/token_balances.py | 4 +- common/models/traces.py | 4 +- common/models/transactions.py | 4 +- .../daily_feature_uniswap_v3_pool_prices.py | 6 +- .../daily_feature_uniswap_v3_token_details.py | 6 +- ...iod_feature_holding_balance_merchantmoe.py | 14 +- ...riod_feature_holding_balance_uniswap_v3.py | 17 +- .../period_feature_merchant_moe_token_bin.py | 10 +- .../period_feature_uniswap_v3_pool_prices.py | 6 +- ...period_feature_uniswap_v3_token_details.py | 6 +- .../models/address_nft_1155_holders.py | 12 +- .../models/address_nft_transfers.py | 8 +- .../models/address_token_holders.py | 11 +- .../models/address_token_transfers.py | 8 +- .../models/address_transactions.py | 28 +- .../models/token_address_nft_inventories.py | 12 +- .../models/af_ens_address_current.py | 6 +- .../custom/hemera_ens/models/af_ens_event.py | 20 +- .../hemera_ens/models/af_ens_node_current.py | 25 +- .../feature_erc1155_token_current_supply.py | 8 +- .../models/feature_erc1155_token_supply.py | 6 +- .../models/feature_merchant_moe_pool.py | 8 +- .../models/feature_merchant_moe_token_bin.py | 4 +- .../feature_merchant_moe_token_current_bin.py | 4 +- .../opensea/models/address_opensea_profile.py | 24 +- .../models/address_opensea_transaction.py | 23 +- .../models/daily_address_opensea_stats.py | 6 +- .../opensea/models/opensea_crypto_mapping.py | 9 +- .../custom/opensea/models/opensea_order.py | 18 +- .../opensea/models/scheduled_metadata.py | 8 +- .../feature_uniswap_v3_collect_fee_records.py | 4 +- .../feature_uniswap_v3_liquidity_records.py | 4 +- .../models/feature_uniswap_v3_pool_prices.py | 4 +- .../models/feature_uniswap_v3_swap_records.py | 4 +- .../feature_uniswap_v3_token_details.py | 4 +- .../user_ops/models/user_operation_results.py | 4 +- migrations/env.py | 62 ++++- migrations/versions/20240704_base_version.py | 34 +-- .../20240725_update_index_table_optimize.py | 8 +- .../versions/20240731_add_user_ops_table.py | 2 +- .../20240802_add_exception_recorder_table.py | 2 +- .../versions/20240802_add_l2_chain_table.py | 2 +- ...20240805_add_column_to_contracts_table.py} | 17 +- ...0240830_add_address_token_deposit_table.py | 2 +- migrations/versions/20240831_add_ens.py | 112 ++------ .../20240906_add_uniswap_v3_enhance_table.py | 12 +- .../versions/20240910_add_address_index.py | 159 ++++++++++++ migrations/versions/20240911_add_opensea.py | 173 +++++++++++++ ...12_add_merchant_and_uniswap_daily_table.py | 242 ++++++++++++++++++ 62 files changed, 947 insertions(+), 291 deletions(-) rename migrations/versions/{20240805_add_transaction_from_address_column_to_contracts.py => 20240805_add_column_to_contracts_table.py} (64%) create mode 100644 migrations/versions/20240910_add_address_index.py create mode 100644 migrations/versions/20240911_add_opensea.py create mode 100644 migrations/versions/20240912_add_merchant_and_uniswap_daily_table.py diff --git a/common/models/__init__.py b/common/models/__init__.py index ec3b55c26..375011649 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -9,9 +9,14 @@ from common.utils.module_loading import import_string, scan_subclass_by_path_patterns from indexer.domain import Domain -model_path_patterns = ["common/models", "indexer/modules/*/models", "indexer/modules/custom/*/models"] - -model_path_exclude = ["indexer/modules/custom/address_index/models"] +model_path_patterns = [ + "common/models", + "indexer/modules/*/models", + "indexer/modules/custom/*/models", + "indexer/aggr_jobs/*/models", +] + +model_path_exclude = [] # db = RouteSQLAlchemy(session_options={"autoflush": False}) db = SQLAlchemy(session_options={"autoflush": False}) diff --git a/common/models/blocks.py b/common/models/blocks.py index 936d768f9..9980e90a3 100644 --- a/common/models/blocks.py +++ b/common/models/blocks.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Type, Union -from sqlalchemy import Column, Index, desc, func +from sqlalchemy import Column, Index, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -40,7 +40,7 @@ class Blocks(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) @staticmethod def model_domain_mapping(): diff --git a/common/models/coin_balances.py b/common/models/coin_balances.py index 1459f29cc..e1f96fb9d 100644 --- a/common/models/coin_balances.py +++ b/common/models/coin_balances.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -16,7 +16,7 @@ class CoinBalances(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("address", "block_number"),) diff --git a/common/models/contract_internal_transactions.py b/common/models/contract_internal_transactions.py index 45ea76def..137638b06 100644 --- a/common/models/contract_internal_transactions.py +++ b/common/models/contract_internal_transactions.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, desc, func +from sqlalchemy import Column, Index, desc, func, text from sqlalchemy.dialects.postgresql import ARRAY, BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TEXT, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -28,7 +28,7 @@ class ContractInternalTransactions(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) @staticmethod def model_domain_mapping(): diff --git a/common/models/contracts.py b/common/models/contracts.py index 5b7c42c9b..23e9114d4 100644 --- a/common/models/contracts.py +++ b/common/models/contracts.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Computed, func +from sqlalchemy import Column, Computed, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, JSONB, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -34,10 +34,11 @@ class Contracts(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) deployed_code_hash = Column( - TIMESTAMP, Computed("encode(digest('0x'||encode(deployed_code, 'hex'), 'sha256'), 'hex')") + VARCHAR, + Computed("encode(digest('0x'||encode(deployed_code, 'hex'), 'sha256'), 'hex')"), ) @staticmethod diff --git a/common/models/current_token_balances.py b/common/models/current_token_balances.py index 197dcb1e6..e7b8e4b2e 100644 --- a/common/models/current_token_balances.py +++ b/common/models/current_token_balances.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel @@ -19,7 +19,7 @@ class CurrentTokenBalances(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("address", "token_address", "token_id"),) diff --git a/common/models/erc1155_token_id_details.py b/common/models/erc1155_token_id_details.py index 7b879f186..1a1fbcc3d 100644 --- a/common/models/erc1155_token_id_details.py +++ b/common/models/erc1155_token_id_details.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSONB, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -20,7 +20,7 @@ class ERC1155TokenIdDetails(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) diff --git a/common/models/erc1155_token_transfers.py b/common/models/erc1155_token_transfers.py index 1c508d844..c7d529f80 100644 --- a/common/models/erc1155_token_transfers.py +++ b/common/models/erc1155_token_transfers.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -23,7 +23,7 @@ class ERC1155TokenTransfers(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("transaction_hash", "block_hash", "log_index", "token_id"),) diff --git a/common/models/erc20_token_transfers.py b/common/models/erc20_token_transfers.py index 8813426b7..83cc811c1 100644 --- a/common/models/erc20_token_transfers.py +++ b/common/models/erc20_token_transfers.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -22,7 +22,7 @@ class ERC20TokenTransfers(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("transaction_hash", "block_hash", "log_index"),) diff --git a/common/models/erc721_token_id_changes.py b/common/models/erc721_token_id_changes.py index 61beff04f..cc2acf0d2 100644 --- a/common/models/erc721_token_id_changes.py +++ b/common/models/erc721_token_id_changes.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -18,7 +18,7 @@ class ERC721TokenIdChanges(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("token_address", "token_id", "block_number"),) diff --git a/common/models/erc721_token_id_details.py b/common/models/erc721_token_id_details.py index 751767a0d..94433d908 100644 --- a/common/models/erc721_token_id_details.py +++ b/common/models/erc721_token_id_details.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSONB, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -20,7 +20,7 @@ class ERC721TokenIdDetails(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) diff --git a/common/models/erc721_token_transfers.py b/common/models/erc721_token_transfers.py index c66e5a67f..158990c99 100644 --- a/common/models/erc721_token_transfers.py +++ b/common/models/erc721_token_transfers.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, JSONB, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -22,7 +22,7 @@ class ERC721TokenTransfers(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("transaction_hash", "block_hash", "log_index"),) diff --git a/common/models/logs.py b/common/models/logs.py index b5d95fcdc..ba1d2ba47 100644 --- a/common/models/logs.py +++ b/common/models/logs.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Type -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, TIMESTAMP from common.models import HemeraModel, general_converter @@ -26,7 +26,7 @@ class Logs(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("transaction_hash", "block_hash", "log_index"),) diff --git a/common/models/token_balances.py b/common/models/token_balances.py index 178fabe69..dd4cbd0b4 100644 --- a/common/models/token_balances.py +++ b/common/models/token_balances.py @@ -1,6 +1,6 @@ from typing import Type -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -29,7 +29,7 @@ class AddressTokenBalances(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("address", "token_address", "token_id", "block_number"),) diff --git a/common/models/traces.py b/common/models/traces.py index f357ffd15..b2531167a 100644 --- a/common/models/traces.py +++ b/common/models/traces.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, desc, func +from sqlalchemy import Column, Index, desc, func, text from sqlalchemy.dialects.postgresql import ARRAY, BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TEXT, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -31,7 +31,7 @@ class Traces(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) @staticmethod def model_domain_mapping(): diff --git a/common/models/transactions.py b/common/models/transactions.py index 6b97a6a44..165a0da48 100644 --- a/common/models/transactions.py +++ b/common/models/transactions.py @@ -1,7 +1,7 @@ from datetime import datetime from typing import Type -from sqlalchemy import Column, Computed, Index, asc, desc, func +from sqlalchemy import Column, Computed, Index, asc, desc, func, text from sqlalchemy.dialects.postgresql import ARRAY, BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TEXT, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -51,7 +51,7 @@ class Transactions(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) @staticmethod def model_domain_mapping(): diff --git a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py index 4a95c5751..609edc1bd 100644 --- a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py +++ b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py @@ -1,5 +1,5 @@ -from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -14,6 +14,8 @@ class DailyFeatureUniswapV3PoolPrices(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = (PrimaryKeyConstraint("block_date", "pool_address"),) + # could be replaced by partition in case of huge amount data Index("af_uniswap_v3_pool_prices_daily_block_date_index", DailyFeatureUniswapV3PoolPrices.block_date) diff --git a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py index 15620e3c8..18e62fa64 100644 --- a/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py +++ b/indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py @@ -1,5 +1,5 @@ -from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -16,6 +16,8 @@ class DailyFeatureUniswapV3TokenDeatils(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = (PrimaryKeyConstraint("block_date", "position_token_address", "token_id"),) + # could be replaced by partition in case of huge amount data Index("af_uniswap_v3_token_data_daily_index", DailyFeatureUniswapV3TokenDeatils.block_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py index 9b40ae69a..97a6d791b 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py @@ -1,5 +1,5 @@ -from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, String, func -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel @@ -8,20 +8,24 @@ class PeriodFeatureHoldingBalanceMerchantmoe(HemeraModel): __tablename__ = "af_holding_balance_merchantmoe_period" period_date = Column(DATE, primary_key=True, nullable=False) - protocol_id = Column(String, primary_key=True, nullable=False) + protocol_id = Column(VARCHAR, primary_key=True, nullable=False) position_token_address = Column(BYTEA, primary_key=True, nullable=False) token_id = Column(NUMERIC, primary_key=True, nullable=False) wallet_address = Column(BYTEA, primary_key=True, nullable=False) token0_address = Column(BYTEA, nullable=False) - token0_symbol = Column(String, nullable=False) + token0_symbol = Column(VARCHAR, nullable=False) token0_balance = Column(NUMERIC(100, 18)) token1_address = Column(BYTEA, nullable=False) - token1_symbol = Column(String, nullable=False) + token1_symbol = Column(VARCHAR, nullable=False) token1_balance = Column(NUMERIC(100, 18)) create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = ( + PrimaryKeyConstraint("period_date", "protocol_id", "position_token_address", "token_id", "wallet_address"), + ) + Index("af_holding_balance_merchantmoe_period_period_date", PeriodFeatureHoldingBalanceMerchantmoe.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py index 63fd4c58f..40e515f12 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py @@ -1,30 +1,29 @@ -from flask_sqlalchemy import SQLAlchemy -from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, String, create_engine, func -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP, VARCHAR -from common.models import HemeraModel, general_converter - -# db = SQLAlchemy(session_options={"autoflush": False}) +from common.models import HemeraModel class PeriodFeatureHoldingBalanceUniswapV3(HemeraModel): __tablename__ = "af_holding_balance_uniswap_v3_period" period_date = Column(DATE, primary_key=True, nullable=False) - protocol_id = Column(String, primary_key=True, nullable=False) + protocol_id = Column(VARCHAR, primary_key=True, nullable=False) position_token_address = Column(BYTEA, primary_key=True, nullable=False) token_id = Column(INTEGER, primary_key=True, nullable=False) wallet_address = Column(BYTEA, nullable=False) token0_address = Column(BYTEA, nullable=False) - token0_symbol = Column(String, nullable=False) + token0_symbol = Column(VARCHAR, nullable=False) token0_balance = Column(NUMERIC(100, 18)) token1_address = Column(BYTEA, nullable=False) - token1_symbol = Column(String, nullable=False) + token1_symbol = Column(VARCHAR, nullable=False) token1_balance = Column(NUMERIC(100, 18)) create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = (PrimaryKeyConstraint("period_date", "protocol_id", "position_token_address", "token_id"),) + # could be replaced by partition in case of huge amount data Index("af_holding_balance_uniswap_v3_period_period_date", PeriodFeatureHoldingBalanceUniswapV3.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py b/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py index abd01ee78..46dbe9e1f 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py @@ -1,9 +1,7 @@ -from datetime import datetime +from sqlalchemy import Column, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP -from sqlalchemy import DATE, Column, Index, PrimaryKeyConstraint, desc, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP - -from common.models import HemeraModel, general_converter +from common.models import HemeraModel class PeriodFeatureMerChantMoeTokenBinRecords(HemeraModel): @@ -17,3 +15,5 @@ class PeriodFeatureMerChantMoeTokenBinRecords(HemeraModel): reserve1_bin = Column(NUMERIC(100)) create_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("period_date", "position_token_address", "token_id"),) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py index f8d39901d..be5f423fd 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py @@ -1,5 +1,5 @@ -from sqlalchemy import DATE, TIMESTAMP, Column, Index, func -from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -13,6 +13,8 @@ class PeriodFeatureUniswapV3PoolPrices(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = (PrimaryKeyConstraint("period_date", "pool_address"),) + # could be replaced by partition in case of huge amount data Index("af_uniswap_v3_pool_prices_period_period_date_index", PeriodFeatureUniswapV3PoolPrices.period_date) diff --git a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py index 0d168f908..114408d1b 100644 --- a/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py +++ b/indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py @@ -1,5 +1,5 @@ -from sqlalchemy import DATE, TIMESTAMP, Column, Computed, Index, func -from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -16,6 +16,8 @@ class PeriodFeatureUniswapV3TokenDeatils(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) + __table_args__ = (PrimaryKeyConstraint("period_date", "position_token_address", "token_id"),) + # could be replaced by partition in case of huge amount data Index("af_uniswap_v3_token_data_period_date_index", PeriodFeatureUniswapV3TokenDeatils.period_date) diff --git a/indexer/modules/custom/address_index/models/address_nft_1155_holders.py b/indexer/modules/custom/address_index/models/address_nft_1155_holders.py index bceb89b20..341da183e 100644 --- a/indexer/modules/custom/address_index/models/address_nft_1155_holders.py +++ b/indexer/modules/custom/address_index/models/address_nft_1155_holders.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import TIMESTAMP, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC +from sqlalchemy import Column, Index, desc, func +from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -26,3 +26,11 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index( + "address_nft_1155_holders_token_address_balance_of_idx", + AddressNftTokenHolders.token_address, + AddressNftTokenHolders.token_id, + desc(AddressNftTokenHolders.balance_of), +) diff --git a/indexer/modules/custom/address_index/models/address_nft_transfers.py b/indexer/modules/custom/address_index/models/address_nft_transfers.py index 776c72026..97821d6f1 100644 --- a/indexer/modules/custom/address_index/models/address_nft_transfers.py +++ b/indexer/modules/custom/address_index/models/address_nft_transfers.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import INT, NUMERIC, SMALLINT, TEXT, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, TIMESTAMP +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC, SMALLINT, TIMESTAMP from common.models import HemeraModel, general_converter @@ -10,8 +10,8 @@ class AddressNftTransfers(HemeraModel): __tablename__ = "address_nft_transfers" address = Column(BYTEA, primary_key=True) - block_number = Column(INT, primary_key=True) - log_index = Column(INT, primary_key=True) + block_number = Column(INTEGER, primary_key=True) + log_index = Column(INTEGER, primary_key=True) transaction_hash = Column(BYTEA) block_timestamp = Column(TIMESTAMP, primary_key=True) block_hash = Column(BYTEA, primary_key=True) diff --git a/indexer/modules/custom/address_index/models/address_token_holders.py b/indexer/modules/custom/address_index/models/address_token_holders.py index 8826a8ba3..0e1c58035 100644 --- a/indexer/modules/custom/address_index/models/address_token_holders.py +++ b/indexer/modules/custom/address_index/models/address_token_holders.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import TIMESTAMP, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC +from sqlalchemy import Column, Index, desc, func +from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -25,3 +25,10 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index( + "address_token_holders_token_address_balance_of_idx", + AddressTokenHolders.token_address, + desc(AddressTokenHolders.balance_of), +) diff --git a/indexer/modules/custom/address_index/models/address_token_transfers.py b/indexer/modules/custom/address_index/models/address_token_transfers.py index 728eb5260..159ee0541 100644 --- a/indexer/modules/custom/address_index/models/address_token_transfers.py +++ b/indexer/modules/custom/address_index/models/address_token_transfers.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import INT, SMALLINT, TEXT, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC, SMALLINT, TIMESTAMP from common.models import HemeraModel, general_converter @@ -10,8 +10,8 @@ class AddressTokenTransfers(HemeraModel): __tablename__ = "address_token_transfers" address = Column(BYTEA, primary_key=True) - block_number = Column(INT, primary_key=True) - log_index = Column(INT, primary_key=True) + block_number = Column(INTEGER, primary_key=True) + log_index = Column(INTEGER, primary_key=True) transaction_hash = Column(BYTEA) block_timestamp = Column(TIMESTAMP, primary_key=True) block_hash = Column(BYTEA, primary_key=True) diff --git a/indexer/modules/custom/address_index/models/address_transactions.py b/indexer/modules/custom/address_index/models/address_transactions.py index 7b08e9034..5446443e6 100644 --- a/indexer/modules/custom/address_index/models/address_transactions.py +++ b/indexer/modules/custom/address_index/models/address_transactions.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import INT, NUMERIC, SMALLINT, TEXT, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, TIMESTAMP +from sqlalchemy import Column, Index, desc, func +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC, SMALLINT, TEXT, TIMESTAMP from common.models import HemeraModel, general_converter @@ -10,8 +10,8 @@ class AddressTransactions(HemeraModel): __tablename__ = "address_transactions" address = Column(BYTEA, primary_key=True) - block_number = Column(INT, primary_key=True) - transaction_index = Column(INT, primary_key=True) + block_number = Column(INTEGER, primary_key=True) + transaction_index = Column(INTEGER, primary_key=True) transaction_hash = Column(BYTEA) block_timestamp = Column(TIMESTAMP, primary_key=True) block_hash = Column(BYTEA) @@ -19,7 +19,7 @@ class AddressTransactions(HemeraModel): related_address = Column(BYTEA) value = Column(NUMERIC(100)) transaction_fee = Column(NUMERIC(100)) - receipt_status = Column(INT) + receipt_status = Column(INTEGER) method = Column(TEXT) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) @@ -34,3 +34,21 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index( + "address_transactions_address_block_timestamp_block_number_t_idx", + AddressTransactions.address, + desc(AddressTransactions.block_timestamp), + desc(AddressTransactions.block_number), + desc(AddressTransactions.transaction_index), +) + +Index( + "address_transactions_address_txn_type_block_timestamp_block_idx", + AddressTransactions.address, + AddressTransactions.txn_type, + desc(AddressTransactions.block_timestamp), + desc(AddressTransactions.block_number), + desc(AddressTransactions.transaction_index), +) diff --git a/indexer/modules/custom/address_index/models/token_address_nft_inventories.py b/indexer/modules/custom/address_index/models/token_address_nft_inventories.py index 9ecc53839..580d090f2 100644 --- a/indexer/modules/custom/address_index/models/token_address_nft_inventories.py +++ b/indexer/modules/custom/address_index/models/token_address_nft_inventories.py @@ -1,7 +1,7 @@ from datetime import datetime -from sqlalchemy import TIMESTAMP, Column, PrimaryKeyConstraint, func -from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -27,3 +27,11 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index( + "token_address_nft_inventories_wallet_address_token_address__idx", + TokenAddressNftInventories.wallet_address, + TokenAddressNftInventories.token_address, + TokenAddressNftInventories.token_id, +) diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py b/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py index 5a767584e..2cb108c66 100644 --- a/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py +++ b/indexer/modules/custom/hemera_ens/models/af_ens_address_current.py @@ -1,5 +1,5 @@ -from sqlalchemy import BIGINT, TIMESTAMP, Column, String, func -from sqlalchemy.dialects.postgresql import BYTEA +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, TIMESTAMP, VARCHAR from common.models import HemeraModel from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ens_general_converter @@ -9,7 +9,7 @@ class ENSAddress(HemeraModel): __tablename__ = "af_ens_address_current" address = Column(BYTEA, primary_key=True) - name = Column(String) + name = Column(VARCHAR) reverse_node = Column(BYTEA) block_number = Column(BIGINT) create_time = Column(TIMESTAMP, server_default=func.now()) diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_event.py b/indexer/modules/custom/hemera_ens/models/af_ens_event.py index dc20c8266..5d00ae7da 100644 --- a/indexer/modules/custom/hemera_ens/models/af_ens_event.py +++ b/indexer/modules/custom/hemera_ens/models/af_ens_event.py @@ -1,5 +1,5 @@ -from sqlalchemy import BIGINT, NUMERIC, TIMESTAMP, Column, Index, Integer, PrimaryKeyConstraint, String, func -from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func, text +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ens_general_converter @@ -9,15 +9,15 @@ class ENSMiddle(HemeraModel): __tablename__ = "af_ens_event" transaction_hash = Column(BYTEA, primary_key=True) - transaction_index = Column(Integer, nullable=True) - log_index = Column(Integer, primary_key=True) + transaction_index = Column(INTEGER, nullable=False) + log_index = Column(INTEGER, primary_key=True) block_number = Column(BIGINT) block_hash = Column(BYTEA) block_timestamp = Column(TIMESTAMP) - method = Column(String) - event_name = Column(String) - topic0 = Column(String) + method = Column(VARCHAR) + event_name = Column(VARCHAR) + topic0 = Column(VARCHAR) from_address = Column(BYTEA) to_address = Column(BYTEA) # namehash of .eth @@ -26,7 +26,7 @@ class ENSMiddle(HemeraModel): node = Column(BYTEA) # keccak of name label = Column(BYTEA) - name = Column(String) + name = Column(VARCHAR) expires = Column(TIMESTAMP) @@ -38,13 +38,13 @@ class ENSMiddle(HemeraModel): reverse_base_node = Column(BYTEA) reverse_node = Column(BYTEA) reverse_label = Column(BYTEA) - reverse_name = Column(String) + reverse_name = Column(VARCHAR) token_id = Column(NUMERIC(100)) w_token_id = Column(NUMERIC(100)) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("transaction_hash", "log_index", name="ens_tnx_log_index"),) diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py index ddd8d50de..debda66c0 100644 --- a/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py +++ b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py @@ -2,8 +2,9 @@ from typing import Type from psycopg2._json import Json -from sqlalchemy import Column, Index, String, UniqueConstraint, func -from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, NUMERIC, TIMESTAMP +from sqlalchemy import Column, Index, text +from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, NUMERIC, TEXT, TIMESTAMP, VARCHAR +from sqlalchemy.sql import func from common.models import HemeraModel, get_column_type from indexer.domain import Domain @@ -34,7 +35,7 @@ def ens_general_converter(table: Type[HemeraModel], data: Domain, is_update=Fals converted_data[key] = [bytes.fromhex(address[2:]) for address in getattr(data, key)] elif isinstance(column_type, JSONB) and getattr(data, key) is not None: converted_data[key] = Json(getattr(data, key)) - elif isinstance(column_type, String): + elif isinstance(column_type, VARCHAR): converted_data[key] = getattr(data, key).replace("\x00", "") if getattr(data, key) else None else: converted_data[key] = getattr(data, key) @@ -55,7 +56,7 @@ class ENSRecord(HemeraModel): token_id = Column(NUMERIC(100)) w_token_id = Column(NUMERIC(100)) first_owned_by = Column(BYTEA) - name = Column(String) + name = Column(VARCHAR) registration = Column(TIMESTAMP) expires = Column(TIMESTAMP) address = Column(BYTEA) @@ -63,8 +64,6 @@ class ENSRecord(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) - __table_args__ = (UniqueConstraint("node"),) - @staticmethod def model_domain_mapping(): return [ @@ -96,4 +95,16 @@ def model_domain_mapping(): Index("ens_idx_address", ENSRecord.address) -Index("ens_idx_name", ENSRecord.name) +# Index("ens_idx_name_md5", text("md5(name)")) +Index("ens_idx_name_md5", func.md5(ENSRecord.name), unique=False) + +# because of sqlalchemy doesn't recognize 'english' with datatype REGCONFIG +# alembic could not track this index +# before sqlalchemy support this case, we suggest running this sql manually + +# Index('ens_idx_name_full_text', +# func.to_tsvector('english', (ENSRecord.name)), postgresql_using='gin') + +# CREATE INDEX ens_idx_name_full_text +# ON af_ens_node_current +# USING gin (to_tsvector('english’, name::text)); diff --git a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py index 213509f85..4b3db4fca 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py +++ b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py @@ -1,7 +1,5 @@ -from datetime import datetime - -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP +from sqlalchemy import Column, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -18,7 +16,7 @@ class FeatureErc1155TokenCurrentSupplyStatus(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id"),) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py index 83c93ac03..67a302c46 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py +++ b/indexer/modules/custom/merchant_moe/models/feature_erc1155_token_supply.py @@ -1,6 +1,4 @@ -from datetime import datetime - -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -17,7 +15,7 @@ class FeatureErc1155TokenSupplyRecords(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py index 9a5b216e0..48ab772b8 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py @@ -1,7 +1,5 @@ -from datetime import datetime - -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP +from sqlalchemy import Column, PrimaryKeyConstraint, func, text +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, TIMESTAMP from common.models import HemeraModel, general_converter @@ -16,7 +14,7 @@ class FeatureMerChantMoePools(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("position_token_address"),) diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py index 0d5c95375..7634574ee 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_bin.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -17,7 +17,7 @@ class FeatureMerChantMoeTokenBinRecords(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py index 257073afb..4e491b160 100644 --- a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_token_current_bin.py @@ -1,7 +1,7 @@ from datetime import datetime from sqlalchemy import Column, Index, PrimaryKeyConstraint, asc, desc, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -18,7 +18,7 @@ class FeatureMerChantMoeTokenBinCurrentStatus(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) + __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id"),) @staticmethod def model_domain_mapping(): diff --git a/indexer/modules/custom/opensea/models/address_opensea_profile.py b/indexer/modules/custom/opensea/models/address_opensea_profile.py index a205dcd85..998f1052b 100644 --- a/indexer/modules/custom/opensea/models/address_opensea_profile.py +++ b/indexer/modules/custom/opensea/models/address_opensea_profile.py @@ -1,5 +1,5 @@ -from sqlalchemy import NUMERIC, Column, func -from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, JSONB, TIMESTAMP +from sqlalchemy import Column, Computed, func, text +from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, JSONB, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -8,12 +8,12 @@ class AddressOpenseaProfile(HemeraModel): __tablename__ = "af_opensea_profile" address = Column(BYTEA, primary_key=True) - buy_txn_count = Column(BIGINT, default=0) - sell_txn_count = Column(BIGINT, default=0) - swap_txn_count = Column(BIGINT, default=0) - buy_opensea_order_count = Column(BIGINT, default=0) - sell_opensea_order_count = Column(BIGINT, default=0) - swap_opensea_order_count = Column(BIGINT, default=0) + buy_txn_count = Column(INTEGER, server_default=text("0")) + sell_txn_count = Column(INTEGER, server_default=text("0")) + swap_txn_count = Column(INTEGER, server_default=text("0")) + buy_opensea_order_count = Column(INTEGER, server_default=text("0")) + sell_opensea_order_count = Column(INTEGER, server_default=text("0")) + swap_opensea_order_count = Column(INTEGER, server_default=text("0")) buy_nft_stats = Column(JSONB) sell_nft_stats = Column(JSONB) buy_volume_usd = Column(NUMERIC) @@ -22,6 +22,8 @@ class AddressOpenseaProfile(HemeraModel): update_time = Column(TIMESTAMP, server_default=func.now()) first_transaction_hash = Column(BYTEA) first_block_timestamp = Column(TIMESTAMP) - txn_count = Column(BIGINT, default=0) - opensea_order_count = Column(BIGINT, default=0) - volume_usd = Column(NUMERIC, default=0) + txn_count = Column(INTEGER, Computed("(buy_txn_count + sell_txn_count) + swap_txn_count")) + opensea_order_count = Column( + INTEGER, Computed("(buy_opensea_order_count + sell_opensea_order_count) + swap_opensea_order_count") + ) + volume_usd = Column(NUMERIC, server_default=text("0")) diff --git a/indexer/modules/custom/opensea/models/address_opensea_transaction.py b/indexer/modules/custom/opensea/models/address_opensea_transaction.py index 44592e5fe..46223ecd5 100644 --- a/indexer/modules/custom/opensea/models/address_opensea_transaction.py +++ b/indexer/modules/custom/opensea/models/address_opensea_transaction.py @@ -1,5 +1,5 @@ -from sqlalchemy import VARCHAR, Column, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP +from sqlalchemy import Column, Index, desc, func, text +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSONB, SMALLINT, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -27,7 +27,7 @@ class AddressOpenseaTransactions(HemeraModel): log_index = Column(BIGINT, primary_key=True) block_timestamp = Column(TIMESTAMP) block_hash = Column(BYTEA, primary_key=True) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) protocol_version = Column(VARCHAR, server_default="1.6") @staticmethod @@ -40,3 +40,20 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index( + "af_opensea__transactions_address_block_timestamp_idx", + AddressOpenseaTransactions.address, + desc(AddressOpenseaTransactions.block_timestamp), +) + +Index("af_opensea__transactions_block_timestamp_idx", desc(AddressOpenseaTransactions.block_timestamp)) + +Index( + "af_opensea__transactions_address_block_number_log_index_blo_idx", + AddressOpenseaTransactions.address, + desc(AddressOpenseaTransactions.block_number), + desc(AddressOpenseaTransactions.log_index), + desc(AddressOpenseaTransactions.block_timestamp), +) diff --git a/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py index 131d701bb..a4c7956ab 100644 --- a/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py +++ b/indexer/modules/custom/opensea/models/daily_address_opensea_stats.py @@ -1,5 +1,5 @@ -from sqlalchemy import INTEGER, NUMERIC, Column, Date, func -from sqlalchemy.dialects.postgresql import BYTEA, JSONB, TIMESTAMP +from sqlalchemy import Column, func +from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, JSONB, NUMERIC, TIMESTAMP from common.models import HemeraModel @@ -8,7 +8,7 @@ class DailyAddressOpenseaTransactions(HemeraModel): __tablename__ = "af_opensea_daily_transactions" address = Column(BYTEA, primary_key=True) - block_date = Column(Date, primary_key=True) + block_date = Column(DATE, primary_key=True) buy_txn_count = Column(INTEGER) sell_txn_count = Column(INTEGER) diff --git a/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py b/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py index 309d1bb31..830540ead 100644 --- a/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py +++ b/indexer/modules/custom/opensea/models/opensea_crypto_mapping.py @@ -1,4 +1,5 @@ -from sqlalchemy import INTEGER, VARCHAR, Column +from sqlalchemy import Column, text +from sqlalchemy.dialects.postgresql import INTEGER, VARCHAR from common.models import HemeraModel @@ -6,7 +7,7 @@ class OpenseaCryptoTokenMapping(HemeraModel): __tablename__ = "af_opensea_na_crypto_token_mapping" - address_var = Column(VARCHAR, primary_key=True) + id = Column(INTEGER, primary_key=True, autoincrement=True) + address_var = Column(VARCHAR(42)) price_symbol = Column(VARCHAR) - - decimals = Column(INTEGER) + decimals = Column(INTEGER, server_default=text("18"), nullable=False) diff --git a/indexer/modules/custom/opensea/models/opensea_order.py b/indexer/modules/custom/opensea/models/opensea_order.py index 1a048c9b0..3a93a35f4 100644 --- a/indexer/modules/custom/opensea/models/opensea_order.py +++ b/indexer/modules/custom/opensea/models/opensea_order.py @@ -1,5 +1,5 @@ -from sqlalchemy import VARCHAR, Column, func -from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, JSON, JSONB, SMALLINT, TIMESTAMP +from sqlalchemy import Column, Index, PrimaryKeyConstraint, func, text +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, JSON, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -12,21 +12,22 @@ class OpenseaOrders(HemeraModel): offerer = Column(BYTEA) recipient = Column(BYTEA) - offer = Column(JSONB) - consideration = Column(JSONB) + offer = Column(JSON) + consideration = Column(JSON) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) transaction_hash = Column(BYTEA) block_number = Column(BIGINT, primary_key=True) - log_index = Column(BIGINT, primary_key=True) + log_index = Column(INTEGER, primary_key=True) block_timestamp = Column(TIMESTAMP) block_hash = Column(BYTEA, primary_key=True) - reorg = Column(BOOLEAN, default=False) - + reorg = Column(BOOLEAN, server_default=text("false")) protocol_version = Column(VARCHAR, server_default="1.6") + __table_args__ = (PrimaryKeyConstraint("block_number", "log_index", "block_hash"),) + @staticmethod def model_domain_mapping(): return [ @@ -37,3 +38,6 @@ def model_domain_mapping(): "converter": general_converter, } ] + + +Index("idx_order_hash", OpenseaOrders.order_hash) diff --git a/indexer/modules/custom/opensea/models/scheduled_metadata.py b/indexer/modules/custom/opensea/models/scheduled_metadata.py index bbda8a1bf..7fb5035c4 100644 --- a/indexer/modules/custom/opensea/models/scheduled_metadata.py +++ b/indexer/modules/custom/opensea/models/scheduled_metadata.py @@ -1,5 +1,5 @@ -from sqlalchemy import Column, Integer, String -from sqlalchemy.dialects.postgresql import TIMESTAMP +from sqlalchemy import Column +from sqlalchemy.dialects.postgresql import INTEGER, TIMESTAMP, VARCHAR from common.models import HemeraModel @@ -7,7 +7,7 @@ class ScheduledMetadata(HemeraModel): __tablename__ = "af_opensea_na_scheduled_metadata" - id = Column(Integer, primary_key=True) - dag_id = Column(String) + id = Column(INTEGER, primary_key=True) + dag_id = Column(VARCHAR) execution_date = Column(TIMESTAMP) last_data_timestamp = Column(TIMESTAMP) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py index d570662d0..49959ff7f 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -25,7 +25,7 @@ class UniswapV3CollectFeeRecords(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = ( PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py index fcc09beb0..2731c43b7 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -28,7 +28,7 @@ class UniswapV3TokenLiquidityRecords(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = ( PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py index f794facbd..5ffd00f41 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -17,7 +17,7 @@ class UniswapV3PoolPrices(HemeraModel): factory_address = Column(BYTEA) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"),) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py index 27428f7d8..cf8c917f8 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -29,7 +29,7 @@ class UniswapV3PoolSwapRecords(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("pool_address", "transaction_hash", "log_index"),) diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py index 03e42fbdc..146513de2 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py @@ -1,6 +1,6 @@ from datetime import datetime -from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP from common.models import HemeraModel, general_converter @@ -19,7 +19,7 @@ class UniswapV3TokenDetails(HemeraModel): create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now()) - reorg = Column(BOOLEAN, default=False) + reorg = Column(BOOLEAN, server_default=text("false")) __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"),) diff --git a/indexer/modules/user_ops/models/user_operation_results.py b/indexer/modules/user_ops/models/user_operation_results.py index 6d7878b3f..ca8944379 100644 --- a/indexer/modules/user_ops/models/user_operation_results.py +++ b/indexer/modules/user_ops/models/user_operation_results.py @@ -1,7 +1,7 @@ from typing import Type from sqlalchemy import Column, Index -from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, INTEGER, NUMERIC, TEXT, TIMESTAMP, VARCHAR +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP, VARCHAR from common.models import HemeraModel, general_converter @@ -29,7 +29,7 @@ class UserOperationResult(HemeraModel): transactions_hash = Column(BYTEA) transactions_index = Column(INTEGER) - block_number = Column(INTEGER) + block_number = Column(BIGINT) block_timestamp = Column(TIMESTAMP) bundler = Column(VARCHAR(42)) start_log_index = Column(INTEGER) diff --git a/migrations/env.py b/migrations/env.py index c3dade7f2..23f849e42 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,5 +1,8 @@ +import re + from alembic import context from sqlalchemy import engine_from_config, pool +from sqlalchemy.sql.schema import SchemaItem from common.models import db, import_all_models @@ -21,12 +24,62 @@ # target_metadata = mymodel.Base.metadata target_metadata = db.metadata - # other values from the config, defined by the needs of env.py, # can be acquired: # my_important_option = config.get_main_option("my_important_option") # ... etc. +IGNORE_DB_TABLE = ["transactions_multi", "hemera_address_transactions", "address_transactions_all", "chosen_address"] +PARTITION_TABLES = [ + "contract_internal_transactions", + "erc20_token_transfers", + "erc721_token_transfers", + "logs", + "traces", + "transactions", +] + + +def table_able_to_track(**kwargs) -> bool: + name = kwargs["name"] + reflected = kwargs["reflected"] + if reflected: + return not any(name.startswith(table) for table in IGNORE_DB_TABLE) + return True + + +def do_not_track_partition_table(**kwargs) -> bool: + name = kwargs["name"] + reflected = kwargs["reflected"] + object_type = kwargs["object_type"] + + if reflected and object_type == "table": + partition_patterns = [ + re.compile(r"^(.+)_partition_\d{6}$"), + re.compile(r"^(.+)_(\d{4})_(\d{2})$"), + re.compile(r"^(.+)_p(\d{4})_(\d{2})$"), + re.compile(r"^(.+)_p(\d{1,2})$"), + re.compile(r"^(.+)_default$"), + ] + for pattern in partition_patterns: + match = pattern.match(name) + + if match: + return False + return True + + +tracking_list = [table_able_to_track, do_not_track_partition_table] + + +def custom_table_tracking( + obj: SchemaItem, name: str, object_type: str, reflected: bool, compare_to: SchemaItem +) -> bool: + for tracking in tracking_list: + if not tracking(obj=obj, name=name, object_type=object_type, reflected=reflected, compare_to=compare_to): + return False + return True + def run_migrations_offline() -> None: """Run migrations in 'offline' mode. @@ -46,6 +99,7 @@ def run_migrations_offline() -> None: target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, + include_object=custom_table_tracking, ) with context.begin_transaction(): @@ -66,7 +120,11 @@ def run_migrations_online() -> None: ) with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) + context.configure( + connection=connection, + target_metadata=target_metadata, + include_object=custom_table_tracking, + ) with context.begin_transaction(): context.run_migrations() diff --git a/migrations/versions/20240704_base_version.py b/migrations/versions/20240704_base_version.py index 504cfcdd4..937422fc9 100644 --- a/migrations/versions/20240704_base_version.py +++ b/migrations/versions/20240704_base_version.py @@ -29,7 +29,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "block_number"), ) op.create_table( @@ -43,7 +43,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "token_address", "token_id", "block_number"), ) op.create_table( @@ -82,7 +82,7 @@ def upgrade() -> None: sa.Column("withdrawals_root", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("hash"), ) op.create_index("blocks_number_index", "blocks", [sa.text("number DESC")], unique=False) @@ -107,7 +107,7 @@ def upgrade() -> None: sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("trace_id"), ) op.create_index( @@ -156,7 +156,7 @@ def upgrade() -> None: sa.Column("proxy_standard", sa.VARCHAR(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address"), ) op.create_table( @@ -170,7 +170,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("token_address", "wallet_address", "token_id"), ) op.create_index( @@ -190,7 +190,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "token_id"), ) op.create_index( @@ -213,7 +213,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("transaction_hash", "log_index"), ) op.create_index( @@ -243,7 +243,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("token_address", "wallet_address"), ) op.create_index( @@ -265,7 +265,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("transaction_hash", "log_index"), ) op.create_index( @@ -295,7 +295,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("token_address", "wallet_address"), ) op.create_index( @@ -313,7 +313,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "token_id", "block_number"), ) op.create_index( @@ -333,7 +333,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "token_id"), ) op.create_index( @@ -356,7 +356,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("transaction_hash", "log_index"), ) op.create_index( @@ -404,7 +404,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("log_index", "transaction_hash"), ) op.create_index( @@ -478,7 +478,7 @@ def upgrade() -> None: sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("trace_id"), ) op.create_index( @@ -523,7 +523,7 @@ def upgrade() -> None: sa.Column("revert_reason", sa.TEXT(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("hash"), ) op.create_index( diff --git a/migrations/versions/20240725_update_index_table_optimize.py b/migrations/versions/20240725_update_index_table_optimize.py index f137b57f6..4e581b563 100644 --- a/migrations/versions/20240725_update_index_table_optimize.py +++ b/migrations/versions/20240725_update_index_table_optimize.py @@ -32,7 +32,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("address", "token_address", "token_id"), ) op.create_index( @@ -765,7 +765,7 @@ def downgrade() -> None: ), sa.Column("create_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), - sa.Column("reorg", sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint( "token_address", "wallet_address", @@ -804,7 +804,7 @@ def downgrade() -> None: ), sa.Column("create_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), - sa.Column("reorg", sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("token_address", "wallet_address", name="erc20_token_holders_pkey"), ) op.create_index( @@ -832,7 +832,7 @@ def downgrade() -> None: ), sa.Column("create_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), autoincrement=False, nullable=True), - sa.Column("reorg", sa.BOOLEAN(), autoincrement=False, nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("token_address", "wallet_address", name="erc721_token_holders_pkey"), ) op.create_index( diff --git a/migrations/versions/20240731_add_user_ops_table.py b/migrations/versions/20240731_add_user_ops_table.py index 07ee4f990..197d3a831 100644 --- a/migrations/versions/20240731_add_user_ops_table.py +++ b/migrations/versions/20240731_add_user_ops_table.py @@ -41,7 +41,7 @@ def upgrade() -> None: sa.Column("signature", postgresql.BYTEA(), nullable=True), sa.Column("transactions_hash", postgresql.BYTEA(), nullable=True), sa.Column("transactions_index", sa.INTEGER(), nullable=True), - sa.Column("block_number", sa.INTEGER(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("bundler", sa.VARCHAR(length=42), nullable=True), sa.Column("start_log_index", sa.INTEGER(), nullable=True), diff --git a/migrations/versions/20240802_add_exception_recorder_table.py b/migrations/versions/20240802_add_exception_recorder_table.py index 85710cb06..7f655b18a 100644 --- a/migrations/versions/20240802_add_exception_recorder_table.py +++ b/migrations/versions/20240802_add_exception_recorder_table.py @@ -1,7 +1,7 @@ """add exception recorder table Revision ID: 040e5251f45d -Revises: aa99dd347ef1 +Revises: 9a1e927f02bb Create Date: 2024-08-02 17:57:31.418456 """ diff --git a/migrations/versions/20240802_add_l2_chain_table.py b/migrations/versions/20240802_add_l2_chain_table.py index 6a2b2dec1..d2439e763 100644 --- a/migrations/versions/20240802_add_l2_chain_table.py +++ b/migrations/versions/20240802_add_l2_chain_table.py @@ -1,7 +1,7 @@ """Describe your changes here Revision ID: e3a3e2114b9c -Revises: 9a1e927f02bb +Revises: 040e5251f45d Create Date: 2024-08-02 11:00:08.496753 """ diff --git a/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py b/migrations/versions/20240805_add_column_to_contracts_table.py similarity index 64% rename from migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py rename to migrations/versions/20240805_add_column_to_contracts_table.py index efae9f6e0..554be922c 100644 --- a/migrations/versions/20240805_add_transaction_from_address_column_to_contracts.py +++ b/migrations/versions/20240805_add_column_to_contracts_table.py @@ -1,7 +1,7 @@ -"""add transaction_from_address column to contracts table +"""add column to contracts table Revision ID: 832fa52da346 -Revises: 040e5251f45d +Revises: aa99dd347ef1 Create Date: 2024-08-05 19:33:42.496838 """ @@ -21,6 +21,18 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### + op.execute("CREATE EXTENSION IF NOT EXISTS pgcrypto;") + op.add_column( + "contracts", + sa.Column( + "deployed_code_hash", + sa.VARCHAR(), + sa.Computed( + "encode(digest('0x'||encode(deployed_code, 'hex'), 'sha256'), 'hex')", + ), + nullable=True, + ), + ) op.add_column("contracts", sa.Column("transaction_from_address", postgresql.BYTEA(), nullable=True)) # ### end Alembic commands ### @@ -28,4 +40,5 @@ def upgrade() -> None: def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.drop_column("contracts", "transaction_from_address") + op.drop_column("contracts", "deployed_code_hash") # ### end Alembic commands ### diff --git a/migrations/versions/20240830_add_address_token_deposit_table.py b/migrations/versions/20240830_add_address_token_deposit_table.py index 4e143f638..2f1183191 100644 --- a/migrations/versions/20240830_add_address_token_deposit_table.py +++ b/migrations/versions/20240830_add_address_token_deposit_table.py @@ -33,7 +33,7 @@ def upgrade() -> None: sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("transaction_hash"), ) op.create_index( diff --git a/migrations/versions/20240831_add_ens.py b/migrations/versions/20240831_add_ens.py index cfcd678d1..49846dd4a 100644 --- a/migrations/versions/20240831_add_ens.py +++ b/migrations/versions/20240831_add_ens.py @@ -1,7 +1,7 @@ """ens Revision ID: 43d14640a8ac -Revises: 2359a28d63cb +Revises: 6c2eecd6316b Create Date: 2024-09-05 11:08:15.501786 """ @@ -24,31 +24,31 @@ def upgrade() -> None: op.create_table( "af_ens_address_current", sa.Column("address", postgresql.BYTEA(), nullable=False), - sa.Column("name", sa.String(), nullable=True), + sa.Column("name", sa.VARCHAR(), nullable=True), sa.Column("reverse_node", postgresql.BYTEA(), nullable=True), sa.Column("block_number", sa.BIGINT(), nullable=True), - sa.Column("create_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("update_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.PrimaryKeyConstraint("address"), ) op.create_table( "af_ens_event", sa.Column("transaction_hash", postgresql.BYTEA(), nullable=False), - sa.Column("transaction_index", sa.Integer(), nullable=True), - sa.Column("log_index", sa.Integer(), nullable=False), + sa.Column("transaction_index", sa.INTEGER(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), sa.Column("block_number", sa.BIGINT(), nullable=True), sa.Column("block_hash", postgresql.BYTEA(), nullable=True), - sa.Column("block_timestamp", sa.TIMESTAMP(), nullable=True), - sa.Column("method", sa.String(), nullable=True), - sa.Column("event_name", sa.String(), nullable=True), - sa.Column("topic0", sa.String(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("method", sa.VARCHAR(), nullable=True), + sa.Column("event_name", sa.VARCHAR(), nullable=True), + sa.Column("topic0", sa.VARCHAR(), nullable=True), sa.Column("from_address", postgresql.BYTEA(), nullable=True), sa.Column("to_address", postgresql.BYTEA(), nullable=True), sa.Column("base_node", postgresql.BYTEA(), nullable=True), sa.Column("node", postgresql.BYTEA(), nullable=True), sa.Column("label", postgresql.BYTEA(), nullable=True), - sa.Column("name", sa.String(), nullable=True), - sa.Column("expires", sa.TIMESTAMP(), nullable=True), + sa.Column("name", sa.VARCHAR(), nullable=True), + sa.Column("expires", postgresql.TIMESTAMP(), nullable=True), sa.Column("owner", postgresql.BYTEA(), nullable=True), sa.Column("resolver", postgresql.BYTEA(), nullable=True), sa.Column("registrant", postgresql.BYTEA(), nullable=True), @@ -56,12 +56,12 @@ def upgrade() -> None: sa.Column("reverse_base_node", postgresql.BYTEA(), nullable=True), sa.Column("reverse_node", postgresql.BYTEA(), nullable=True), sa.Column("reverse_label", postgresql.BYTEA(), nullable=True), - sa.Column("reverse_name", sa.String(), nullable=True), + sa.Column("reverse_name", sa.VARCHAR(), nullable=True), sa.Column("token_id", sa.NUMERIC(precision=100), nullable=True), sa.Column("w_token_id", sa.NUMERIC(precision=100), nullable=True), - sa.Column("create_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("update_time", sa.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), sa.PrimaryKeyConstraint("transaction_hash", "log_index", name="ens_tnx_log_index"), ) op.create_index("ens_event_address", "af_ens_event", ["from_address"], unique=False) @@ -81,91 +81,15 @@ def upgrade() -> None: sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.PrimaryKeyConstraint("node"), - sa.UniqueConstraint("node"), ) op.create_index("ens_idx_address", "af_ens_node_current", ["address"], unique=False) - op.create_index("ens_idx_name", "af_ens_node_current", ["name"], unique=False) - op.create_table( - "af_opensea__transactions", - sa.Column("address", postgresql.BYTEA(), nullable=False), - sa.Column("is_offer", sa.BOOLEAN(), nullable=False), - sa.Column("related_address", postgresql.BYTEA(), nullable=True), - sa.Column("transaction_type", sa.SMALLINT(), nullable=True), - sa.Column("order_hash", postgresql.BYTEA(), nullable=True), - sa.Column("zone", postgresql.BYTEA(), nullable=True), - sa.Column("offer", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("consideration", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("fee", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), - sa.Column("block_number", sa.BIGINT(), nullable=False), - sa.Column("log_index", sa.BIGINT(), nullable=False), - sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), - sa.Column("block_hash", postgresql.BYTEA(), nullable=False), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("address", "is_offer", "block_number", "log_index", "block_hash"), - ) - op.create_table( - "af_opensea_daily_transactions", - sa.Column("address", postgresql.BYTEA(), nullable=False), - sa.Column("related_address", postgresql.BYTEA(), nullable=True), - sa.Column("transaction_type", sa.SMALLINT(), nullable=True), - sa.Column("block_date", sa.Date(), nullable=False), - sa.Column("buy_txn_count", sa.INTEGER(), nullable=True), - sa.Column("sell_txn_count", sa.INTEGER(), nullable=True), - sa.Column("swap_txn_count", sa.INTEGER(), nullable=True), - sa.Column("buy_txn_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("sell_txn_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("buy_txn_volume_usd", sa.NUMERIC(), nullable=True), - sa.Column("sell_txn_volume_usd", sa.NUMERIC(), nullable=True), - sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.PrimaryKeyConstraint("address", "block_date"), - ) - op.create_table( - "af_opensea_na_orders", - sa.Column("order_hash", postgresql.BYTEA(), nullable=True), - sa.Column("zone", postgresql.BYTEA(), nullable=True), - sa.Column("offerer", postgresql.BYTEA(), nullable=True), - sa.Column("recipient", postgresql.BYTEA(), nullable=True), - sa.Column("offer", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("consideration", postgresql.JSONB(astext_type=sa.Text()), nullable=True), - sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), - sa.Column("block_number", sa.BIGINT(), nullable=False), - sa.Column("log_index", sa.BIGINT(), nullable=False), - sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), - sa.Column("block_hash", postgresql.BYTEA(), nullable=False), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), - sa.PrimaryKeyConstraint("block_number", "log_index", "block_hash"), - ) - op.drop_table("token_prices") - op.drop_table("token_hourly_prices") + op.create_index("ens_idx_name_md5", "af_ens_node_current", [sa.text("md5(name)")], unique=False) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "token_hourly_prices", - sa.Column("symbol", sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column("timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False), - sa.Column("price", sa.NUMERIC(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint("symbol", "timestamp", name="token_hourly_prices_pkey"), - ) - op.create_table( - "token_prices", - sa.Column("symbol", sa.VARCHAR(), autoincrement=False, nullable=False), - sa.Column("timestamp", postgresql.TIMESTAMP(), autoincrement=False, nullable=False), - sa.Column("price", sa.NUMERIC(), autoincrement=False, nullable=True), - sa.PrimaryKeyConstraint("symbol", "timestamp", name="token_prices_pkey"), - ) - op.drop_table("af_opensea_na_orders") - op.drop_table("af_opensea_daily_transactions") - op.drop_table("af_opensea__transactions") - op.drop_index("ens_idx_name", table_name="af_ens_node_current") + op.drop_index("ens_idx_name_md5", table_name="af_ens_node_current") op.drop_index("ens_idx_address", table_name="af_ens_node_current") op.drop_table("af_ens_node_current") op.drop_index("ens_idx_block_number_log_index", table_name="af_ens_event") diff --git a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py index ad6dfbec6..0908bbef5 100644 --- a/migrations/versions/20240906_add_uniswap_v3_enhance_table.py +++ b/migrations/versions/20240906_add_uniswap_v3_enhance_table.py @@ -1,7 +1,7 @@ """add uniswap v3 enhance table Revision ID: f4efa18760cc -Revises: 2359a28d63cb +Revises: 43d14640a8ac Create Date: 2024-09-06 13:24:28.201489 """ @@ -44,7 +44,7 @@ def upgrade() -> None: sa.Column("factory_address", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"), ) op.create_table( @@ -67,7 +67,7 @@ def upgrade() -> None: sa.Column("token1_address", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("pool_address", "transaction_hash", "log_index"), ) op.create_table( @@ -102,7 +102,7 @@ def upgrade() -> None: sa.Column("token1_address", postgresql.BYTEA(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), ) op.create_index( @@ -165,7 +165,7 @@ def upgrade() -> None: sa.Column("liquidity", sa.NUMERIC(precision=100), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"), ) op.create_index( @@ -198,7 +198,7 @@ def upgrade() -> None: sa.Column("action_type", sa.VARCHAR(), nullable=True), sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), - sa.Column("reorg", sa.BOOLEAN(), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), nullable=True, server_default=sa.text("false")), sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number", "log_index"), ) op.create_index( diff --git a/migrations/versions/20240910_add_address_index.py b/migrations/versions/20240910_add_address_index.py new file mode 100644 index 000000000..9ad0da86d --- /dev/null +++ b/migrations/versions/20240910_add_address_index.py @@ -0,0 +1,159 @@ +"""add address index + +Revision ID: e8f78802f27a +Revises: f4efa18760cc +Create Date: 2024-09-10 16:27:52.477748 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "e8f78802f27a" +down_revision: Union[str, None] = "f4efa18760cc" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "address_nft_1155_holders", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("balance_of", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "token_address", "token_id"), + ) + op.create_index( + "address_nft_1155_holders_token_address_balance_of_idx", + "address_nft_1155_holders", + ["token_address", "token_id", sa.text("balance_of DESC")], + unique=False, + ) + op.create_table( + "address_nft_transfers", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("block_number", sa.INTEGER(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=False), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("token_address", postgresql.BYTEA(), nullable=True), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("transfer_type", sa.SMALLINT(), nullable=True), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("value", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "block_number", "log_index", "block_timestamp", "block_hash", "token_id"), + ) + op.create_table( + "address_token_holders", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("token_address", postgresql.BYTEA(), nullable=False), + sa.Column("balance_of", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "token_address"), + ) + op.create_index( + "address_token_holders_token_address_balance_of_idx", + "address_token_holders", + ["token_address", sa.text("balance_of DESC")], + unique=False, + ) + op.create_table( + "address_token_transfers", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("block_number", sa.INTEGER(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=False), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("token_address", postgresql.BYTEA(), nullable=True), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("transfer_type", sa.SMALLINT(), nullable=True), + sa.Column("value", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "block_number", "log_index", "block_timestamp", "block_hash"), + ) + op.create_table( + "address_transactions", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("block_number", sa.INTEGER(), nullable=False), + sa.Column("transaction_index", sa.INTEGER(), nullable=False), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=False), + sa.Column("block_hash", postgresql.BYTEA(), nullable=True), + sa.Column("txn_type", sa.SMALLINT(), nullable=True), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("value", sa.NUMERIC(precision=100), nullable=True), + sa.Column("transaction_fee", sa.NUMERIC(precision=100), nullable=True), + sa.Column("receipt_status", sa.INTEGER(), nullable=True), + sa.Column("method", sa.TEXT(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "block_number", "transaction_index", "block_timestamp"), + ) + op.create_index( + "address_transactions_address_block_timestamp_block_number_t_idx", + "address_transactions", + ["address", sa.text("block_timestamp DESC"), sa.text("block_number DESC"), sa.text("transaction_index DESC")], + unique=False, + ) + op.create_index( + "address_transactions_address_txn_type_block_timestamp_block_idx", + "address_transactions", + [ + "address", + "txn_type", + sa.text("block_timestamp DESC"), + sa.text("block_number DESC"), + sa.text("transaction_index DESC"), + ], + unique=False, + ) + op.create_table( + "token_address_nft_inventories", + sa.Column("token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("token_address", "token_id"), + ) + op.create_index( + "token_address_nft_inventories_wallet_address_token_address__idx", + "token_address_nft_inventories", + ["wallet_address", "token_address", "token_id"], + unique=False, + ) + + +# ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index( + "token_address_nft_inventories_wallet_address_token_address__idx", table_name="token_address_nft_inventories" + ) + op.drop_table("token_address_nft_inventories") + op.drop_index("address_transactions_address_txn_type_block_timestamp_block_idx", table_name="address_transactions") + op.drop_index("address_transactions_address_block_timestamp_block_number_t_idx", table_name="address_transactions") + op.drop_table("address_transactions") + op.drop_table("address_token_transfers") + op.drop_index("address_token_holders_token_address_balance_of_idx", table_name="address_token_holders") + op.drop_table("address_token_holders") + op.drop_table("address_nft_transfers") + op.drop_index("address_nft_1155_holders_token_address_balance_of_idx", table_name="address_nft_1155_holders") + op.drop_table("address_nft_1155_holders") + # ### end Alembic commands ### diff --git a/migrations/versions/20240911_add_opensea.py b/migrations/versions/20240911_add_opensea.py new file mode 100644 index 000000000..282881c6f --- /dev/null +++ b/migrations/versions/20240911_add_opensea.py @@ -0,0 +1,173 @@ +"""add opensea + +Revision ID: 3dd9b90d2e31 +Revises: e8f78802f27a +Create Date: 2024-09-11 11:30:31.566920 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "3dd9b90d2e31" +down_revision: Union[str, None] = "e8f78802f27a" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "af_opensea__transactions", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("is_offer", sa.BOOLEAN(), nullable=False), + sa.Column("related_address", postgresql.BYTEA(), nullable=True), + sa.Column("transaction_type", sa.SMALLINT(), nullable=True), + sa.Column("order_hash", postgresql.BYTEA(), nullable=True), + sa.Column("zone", postgresql.BYTEA(), nullable=True), + sa.Column("offer", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("consideration", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("fee", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.BIGINT(), nullable=False), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), + sa.Column("protocol_version", sa.VARCHAR(), server_default="1.6", nullable=True), + sa.PrimaryKeyConstraint("address", "is_offer", "block_number", "log_index", "block_hash"), + ) + op.create_index( + "af_opensea__transactions_address_block_number_log_index_blo_idx", + "af_opensea__transactions", + ["address", sa.text("block_number DESC"), sa.text("log_index DESC"), sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_index( + "af_opensea__transactions_address_block_timestamp_idx", + "af_opensea__transactions", + ["address", sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_index( + "af_opensea__transactions_block_timestamp_idx", + "af_opensea__transactions", + [sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_table( + "af_opensea_daily_transactions", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("block_date", sa.DATE(), nullable=False), + sa.Column("buy_txn_count", sa.INTEGER(), nullable=True), + sa.Column("sell_txn_count", sa.INTEGER(), nullable=True), + sa.Column("swap_txn_count", sa.INTEGER(), nullable=True), + sa.Column("buy_opensea_order_count", sa.INTEGER(), nullable=True), + sa.Column("sell_opensea_order_count", sa.INTEGER(), nullable=True), + sa.Column("swap_opensea_order_count", sa.INTEGER(), nullable=True), + sa.Column("buy_nft_stats", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("sell_nft_stats", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("buy_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("sell_volume_crypto", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("buy_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("sell_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("address", "block_date"), + ) + op.create_table( + "af_opensea_na_crypto_token_mapping", + sa.Column("id", sa.INTEGER(), autoincrement=True, nullable=False), + sa.Column("address_var", sa.VARCHAR(length=42), nullable=True), + sa.Column("price_symbol", sa.VARCHAR(), nullable=True), + sa.Column("decimals", sa.INTEGER(), server_default=sa.text("18"), nullable=False), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "af_opensea_na_orders", + sa.Column("order_hash", postgresql.BYTEA(), nullable=True), + sa.Column("zone", postgresql.BYTEA(), nullable=True), + sa.Column("offerer", postgresql.BYTEA(), nullable=True), + sa.Column("recipient", postgresql.BYTEA(), nullable=True), + sa.Column("offer", postgresql.JSON(astext_type=sa.Text()), nullable=True), + sa.Column("consideration", postgresql.JSON(astext_type=sa.Text()), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("log_index", sa.INTEGER(), nullable=False), + sa.Column("block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column("block_hash", postgresql.BYTEA(), nullable=False), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), + sa.Column("protocol_version", sa.VARCHAR(), server_default="1.6", nullable=True), + sa.PrimaryKeyConstraint("block_number", "log_index", "block_hash"), + ) + op.create_index("idx_order_hash", "af_opensea_na_orders", ["order_hash"], unique=False) + op.create_table( + "af_opensea_na_scheduled_metadata", + sa.Column("id", sa.INTEGER(), nullable=False), + sa.Column("dag_id", sa.VARCHAR(), nullable=True), + sa.Column("execution_date", postgresql.TIMESTAMP(), nullable=True), + sa.Column("last_data_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.PrimaryKeyConstraint("id"), + ) + op.create_table( + "af_opensea_profile", + sa.Column("address", postgresql.BYTEA(), nullable=False), + sa.Column("buy_txn_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("sell_txn_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("swap_txn_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("buy_opensea_order_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("sell_opensea_order_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("swap_opensea_order_count", sa.INTEGER(), server_default=sa.text("0"), nullable=True), + sa.Column("buy_nft_stats", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("sell_nft_stats", postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column("buy_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("sell_volume_usd", sa.NUMERIC(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("first_transaction_hash", postgresql.BYTEA(), nullable=True), + sa.Column("first_block_timestamp", postgresql.TIMESTAMP(), nullable=True), + sa.Column( + "txn_count", + sa.INTEGER(), + sa.Computed( + "(buy_txn_count + sell_txn_count) + swap_txn_count", + ), + nullable=True, + ), + sa.Column( + "opensea_order_count", + sa.INTEGER(), + sa.Computed( + "(buy_opensea_order_count + sell_opensea_order_count) + swap_opensea_order_count", + ), + nullable=True, + ), + sa.Column("volume_usd", sa.NUMERIC(), server_default=sa.text("0"), nullable=True), + sa.PrimaryKeyConstraint("address"), + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table("af_opensea_profile") + op.drop_table("af_opensea_na_scheduled_metadata") + op.drop_index("idx_order_hash", table_name="af_opensea_na_orders") + op.drop_table("af_opensea_na_orders") + op.drop_table("af_opensea_na_crypto_token_mapping") + op.drop_table("af_opensea_daily_transactions") + op.drop_index("af_opensea__transactions_block_timestamp_idx", table_name="af_opensea__transactions") + op.drop_index("af_opensea__transactions_address_block_timestamp_idx", table_name="af_opensea__transactions") + op.drop_index( + "af_opensea__transactions_address_block_number_log_index_blo_idx", table_name="af_opensea__transactions" + ) + op.drop_table("af_opensea__transactions") + # ### end Alembic commands ### diff --git a/migrations/versions/20240912_add_merchant_and_uniswap_daily_table.py b/migrations/versions/20240912_add_merchant_and_uniswap_daily_table.py new file mode 100644 index 000000000..086263e0d --- /dev/null +++ b/migrations/versions/20240912_add_merchant_and_uniswap_daily_table.py @@ -0,0 +1,242 @@ +"""add merchant-moe and uniswap daily table + +Revision ID: c609922eae7a +Revises: 3dd9b90d2e31 +Create Date: 2024-09-12 14:44:05.528650 + +""" + +from typing import Sequence, Union + +import sqlalchemy as sa +from alembic import op +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = "c609922eae7a" +down_revision: Union[str, None] = "3dd9b90d2e31" +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table( + "af_merchant_moe_pools", + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("token0_address", postgresql.BYTEA(), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), + sa.PrimaryKeyConstraint("position_token_address"), + ) + op.create_table( + "af_merchant_moe_token_bin_current", + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("reserve0_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("reserve1_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("position_token_address", "token_id"), + ) + op.create_index( + "af_merchant_moe_token_bin_current_token_id_index", + "af_merchant_moe_token_bin_current", + [sa.text("position_token_address DESC"), sa.text("token_id ASC")], + unique=False, + ) + op.create_table( + "af_merchant_moe_token_bin_hist", + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("reserve0_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("reserve1_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), + sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"), + ) + op.create_index( + "af_merchant_moe_token_bin_hist_token_block_desc_index", + "af_merchant_moe_token_bin_hist", + [sa.text("position_token_address DESC"), sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_table( + "af_merchant_moe_token_supply_current", + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=True), + sa.Column("block_number", sa.BIGINT(), nullable=True), + sa.Column("total_supply", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("position_token_address", "token_id"), + ) + op.create_table( + "af_merchant_moe_token_supply_hist", + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("block_timestamp", sa.BIGINT(), nullable=False), + sa.Column("block_number", sa.BIGINT(), nullable=False), + sa.Column("total_supply", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.Column("reorg", sa.BOOLEAN(), server_default=sa.text("false"), nullable=True), + sa.PrimaryKeyConstraint("position_token_address", "token_id", "block_timestamp", "block_number"), + ) + op.create_index( + "af_merchant_moe_token_supply_hist_token_block_desc_index", + "af_merchant_moe_token_supply_hist", + [sa.text("position_token_address DESC"), sa.text("block_timestamp DESC")], + unique=False, + ) + op.create_table( + "af_holding_balance_merchantmoe_period", + sa.Column("period_date", sa.DATE(), nullable=False), + sa.Column("protocol_id", sa.VARCHAR(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=False), + sa.Column("token0_address", postgresql.BYTEA(), nullable=False), + sa.Column("token0_symbol", sa.VARCHAR(), nullable=False), + sa.Column("token0_balance", sa.NUMERIC(precision=100, scale=18), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=False), + sa.Column("token1_symbol", sa.VARCHAR(), nullable=False), + sa.Column("token1_balance", sa.NUMERIC(precision=100, scale=18), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("period_date", "protocol_id", "position_token_address", "token_id", "wallet_address"), + ) + op.create_index( + "af_holding_balance_merchantmoe_period_period_date", + "af_holding_balance_merchantmoe_period", + ["period_date"], + unique=False, + ) + op.create_table( + "af_holding_balance_uniswap_v3_period", + sa.Column("period_date", sa.DATE(), nullable=False), + sa.Column("protocol_id", sa.VARCHAR(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.INTEGER(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=False), + sa.Column("token0_address", postgresql.BYTEA(), nullable=False), + sa.Column("token0_symbol", sa.VARCHAR(), nullable=False), + sa.Column("token0_balance", sa.NUMERIC(precision=100, scale=18), nullable=True), + sa.Column("token1_address", postgresql.BYTEA(), nullable=False), + sa.Column("token1_symbol", sa.VARCHAR(), nullable=False), + sa.Column("token1_balance", sa.NUMERIC(precision=100, scale=18), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("period_date", "protocol_id", "position_token_address", "token_id"), + ) + op.create_index( + "af_holding_balance_uniswap_v3_period_period_date", + "af_holding_balance_uniswap_v3_period", + ["period_date"], + unique=False, + ) + op.create_table( + "af_merchant_moe_token_bin_hist_period", + sa.Column("period_date", sa.DATE(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.NUMERIC(precision=100), nullable=False), + sa.Column("reserve0_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("reserve1_bin", sa.NUMERIC(precision=100), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("period_date", "position_token_address", "token_id"), + ) + op.create_table( + "af_uniswap_v3_pool_prices_daily", + sa.Column("block_date", sa.DATE(), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("sqrt_price_x96", sa.NUMERIC(precision=78), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("block_date", "pool_address"), + ) + op.create_index( + "af_uniswap_v3_pool_prices_daily_block_date_index", + "af_uniswap_v3_pool_prices_daily", + ["block_date"], + unique=False, + ) + op.create_table( + "af_uniswap_v3_pool_prices_period", + sa.Column("period_date", sa.DATE(), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("sqrt_price_x96", sa.NUMERIC(precision=78), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("period_date", "pool_address"), + ) + op.create_index( + "af_uniswap_v3_pool_prices_period_period_date_index", + "af_uniswap_v3_pool_prices_period", + ["period_date"], + unique=False, + ) + op.create_table( + "af_uniswap_v3_token_data_daily", + sa.Column("block_date", sa.DATE(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.INTEGER(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("liquidity", sa.NUMERIC(precision=78), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("block_date", "position_token_address", "token_id"), + ) + op.create_index( + "af_uniswap_v3_token_data_daily_index", "af_uniswap_v3_token_data_daily", ["block_date"], unique=False + ) + op.create_table( + "af_uniswap_v3_token_data_period", + sa.Column("period_date", sa.DATE(), nullable=False), + sa.Column("position_token_address", postgresql.BYTEA(), nullable=False), + sa.Column("token_id", sa.INTEGER(), nullable=False), + sa.Column("wallet_address", postgresql.BYTEA(), nullable=False), + sa.Column("pool_address", postgresql.BYTEA(), nullable=False), + sa.Column("liquidity", sa.NUMERIC(precision=78), nullable=True), + sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), + sa.PrimaryKeyConstraint("period_date", "position_token_address", "token_id"), + ) + op.create_index( + "af_uniswap_v3_token_data_period_date_index", "af_uniswap_v3_token_data_period", ["period_date"], unique=False + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index("af_uniswap_v3_token_data_period_date_index", table_name="af_uniswap_v3_token_data_period") + op.drop_table("af_uniswap_v3_token_data_period") + op.drop_index("af_uniswap_v3_token_data_daily_index", table_name="af_uniswap_v3_token_data_daily") + op.drop_table("af_uniswap_v3_token_data_daily") + op.drop_index("af_uniswap_v3_pool_prices_period_period_date_index", table_name="af_uniswap_v3_pool_prices_period") + op.drop_table("af_uniswap_v3_pool_prices_period") + op.drop_index("af_uniswap_v3_pool_prices_daily_block_date_index", table_name="af_uniswap_v3_pool_prices_daily") + op.drop_table("af_uniswap_v3_pool_prices_daily") + op.drop_table("af_merchant_moe_token_bin_hist_period") + op.drop_index("af_holding_balance_uniswap_v3_period_period_date", table_name="af_holding_balance_uniswap_v3_period") + op.drop_table("af_holding_balance_uniswap_v3_period") + op.drop_index( + "af_holding_balance_merchantmoe_period_period_date", table_name="af_holding_balance_merchantmoe_period" + ) + op.drop_table("af_holding_balance_merchantmoe_period") + op.drop_index( + "af_merchant_moe_token_supply_hist_token_block_desc_index", table_name="af_merchant_moe_token_supply_hist" + ) + op.drop_table("af_merchant_moe_token_supply_hist") + op.drop_table("af_merchant_moe_token_supply_current") + op.drop_index("af_merchant_moe_token_bin_hist_token_block_desc_index", table_name="af_merchant_moe_token_bin_hist") + op.drop_table("af_merchant_moe_token_bin_hist") + op.drop_index("af_merchant_moe_token_bin_current_token_id_index", table_name="af_merchant_moe_token_bin_current") + op.drop_table("af_merchant_moe_token_bin_current") + op.drop_table("af_merchant_moe_pools") + # ### end Alembic commands ### From 8dc9c30653cbdf926e3a529a28f6412b34383463 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:43:02 +0800 Subject: [PATCH 48/70] add parameter auto-upgrade-db (#133) --- cli/load.py | 11 ++++++++++- cli/reorg.py | 11 ++++++++++- cli/stream.py | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/cli/load.py b/cli/load.py index df30231f0..b4d55a494 100644 --- a/cli/load.py +++ b/cli/load.py @@ -224,6 +224,14 @@ def wrapper(*args, **kwargs): "e.g redis. means cache data will store in redis, redis://localhost:6379" "or memory. means cache data will store in memory, memory", ) +@click.option( + "--auto-upgrade-db", + default=True, + show_default=True, + type=bool, + envvar="AUTO_UPGRADE_DB", + help="Whether to automatically run database migration scripts to update the database to the latest version.", +) @calculate_execution_time def load( provider_uri, @@ -246,6 +254,7 @@ def load( source_path=None, sync_recorder="file_sync_record", cache="memory", + auto_upgrade_db=True, ): configure_logging(log_file) configure_signals() @@ -262,7 +271,7 @@ def load( config = {"blocks_per_file": blocks_per_file, "source_path": source_path} if postgres_url: - service = PostgreSQLService(postgres_url, db_version=db_version) + service = PostgreSQLService(postgres_url, db_version=db_version, init_schema=auto_upgrade_db) config["db_service"] = service exception_recorder.init_pg_service(service) else: diff --git a/cli/reorg.py b/cli/reorg.py index 3e1892195..8bc81ab52 100644 --- a/cli/reorg.py +++ b/cli/reorg.py @@ -108,6 +108,14 @@ envvar="MULTI_CALL_ENABLE", ) @click.option("--cache", default=None, show_default=True, type=str, envvar="CACHE", help="Cache") +@click.option( + "--auto-upgrade-db", + default=True, + show_default=True, + type=bool, + envvar="AUTO_UPGRADE_DB", + help="Whether to automatically run database migration scripts to update the database to the latest version.", +) def reorg( provider_uri, debug_provider_uri, @@ -121,6 +129,7 @@ def reorg( log_file=None, cache=None, config_file=None, + auto_upgrade_db=True, ): configure_logging(log_file) configure_signals() @@ -132,7 +141,7 @@ def reorg( # build postgresql service if postgres_url: - service = PostgreSQLService(postgres_url, db_version=db_version) + service = PostgreSQLService(postgres_url, db_version=db_version, init_schema=auto_upgrade_db) config = {"db_service": service} exception_recorder.init_pg_service(service) else: diff --git a/cli/stream.py b/cli/stream.py index fec1f7050..66f219e1d 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -264,6 +264,14 @@ def wrapper(*args, **kwargs): envvar="FORCE_FILTER_MODE", help="Force the filter mode to be enabled, even if no filters job are provided.", ) +@click.option( + "--auto-upgrade-db", + default=True, + show_default=True, + type=bool, + envvar="AUTO_UPGRADE_DB", + help="Whether to automatically run database migration scripts to update the database to the latest version.", +) @calculate_execution_time def stream( provider_uri, @@ -291,6 +299,7 @@ def stream( multicall=True, config_file=None, force_filter_mode=False, + auto_upgrade_db=True, ): configure_logging(log_file) configure_signals() @@ -309,7 +318,7 @@ def stream( } if postgres_url: - service = PostgreSQLService(postgres_url, db_version=db_version) + service = PostgreSQLService(postgres_url, db_version=db_version, init_schema=auto_upgrade_db) config["db_service"] = service exception_recorder.init_pg_service(service) else: From f22abfce58299005ef44ff1b570d8bff684f4b7a Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:06:21 +0800 Subject: [PATCH 49/70] fixed merchant moe job name --- .../export_merchant_moe_1155_token_holding_detail_job.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py index d55ef746b..d675d8fda 100644 --- a/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py +++ b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py @@ -316,7 +316,7 @@ def get_exist_pools(db_service): history_pools = set() if result is not None: for item in result: - history_pools.add("0x" + item.token_address.hex()) + history_pools.add("0x" + item.position_token_address.hex()) except Exception as e: print(e) raise e From 478999e6359dca3c2ed5838b53fd15492b8fbc29 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:17:22 +0800 Subject: [PATCH 50/70] bugfixed (#141) add ens record block_number --- indexer/modules/custom/hemera_ens/ens_domain.py | 3 +++ indexer/modules/custom/hemera_ens/ens_handler.py | 3 +++ .../custom/hemera_ens/models/af_ens_node_current.py | 11 ++++++----- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/indexer/modules/custom/hemera_ens/ens_domain.py b/indexer/modules/custom/hemera_ens/ens_domain.py index 028d760e5..57612f424 100644 --- a/indexer/modules/custom/hemera_ens/ens_domain.py +++ b/indexer/modules/custom/hemera_ens/ens_domain.py @@ -56,6 +56,7 @@ class ENSRegisterD(FilterData): node: Optional[str] = None token_id: Optional[str] = None w_token_id: Optional[str] = None + block_number: Optional[int] = None @dataclass @@ -63,6 +64,7 @@ class ENSNameRenewD(FilterData): node: Optional[str] = None expires: Optional[datetime] = None + block_number: Optional[int] = None @dataclass @@ -70,6 +72,7 @@ class ENSAddressChangeD(FilterData): node: Optional[str] = None address: Optional[str] = None + block_number: Optional[int] = None """for ens_address""" diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index 0e198b008..92b16650e 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -298,15 +298,18 @@ def resolve_middle(self, record): base_node=record["base_node"], token_id=record["token_id"], w_token_id=record["w_token_id"], + block_number=record["block_number"], ) if event_name == "NameRenewed": return ENSNameRenewD( node=record["node"], expires=record["expires"], + block_number=record["block_number"], ) if event_name == "AddressChanged": return ENSAddressChangeD( node=record["node"], address=address, + block_number=record["block_number"], ) diff --git a/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py index debda66c0..b8e4b7a0b 100644 --- a/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py +++ b/indexer/modules/custom/hemera_ens/models/af_ens_node_current.py @@ -2,7 +2,7 @@ from typing import Type from psycopg2._json import Json -from sqlalchemy import Column, Index, text +from sqlalchemy import BIGINT, Column, Index, text from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, NUMERIC, TEXT, TIMESTAMP, VARCHAR from sqlalchemy.sql import func @@ -60,6 +60,7 @@ class ENSRecord(HemeraModel): registration = Column(TIMESTAMP) expires = Column(TIMESTAMP) address = Column(BYTEA) + block_number = Column(BIGINT) create_time = Column(TIMESTAMP, server_default=func.now()) update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) @@ -70,25 +71,25 @@ def model_domain_mapping(): { "domain": "ENSRegisterD", "conflict_do_update": True, - "update_strategy": None, + "update_strategy": "EXCLUDED.block_number > af_ens_node_current.block_number", "converter": ens_general_converter, }, { "domain": "ENSRegisterTokenD", "conflict_do_update": True, - "update_strategy": None, + "update_strategy": "EXCLUDED.block_number > af_ens_node_current.block_number", "converter": ens_general_converter, }, { "domain": "ENSNameRenewD", "conflict_do_update": True, - "update_strategy": None, + "update_strategy": "EXCLUDED.block_number > af_ens_node_current.block_number", "converter": ens_general_converter, }, { "domain": "ENSAddressChangeD", "conflict_do_update": True, - "update_strategy": None, + "update_strategy": "EXCLUDED.block_number > af_ens_node_current.block_number", "converter": ens_general_converter, }, ] From 95ee8a1e5df6b60f8e515e7df5b85298c50ec1d0 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 12 Sep 2024 18:20:05 +0800 Subject: [PATCH 51/70] ens api index hit (#142) --- api/app/af_ens/routes.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index cd11bb3e9..75017a208 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -9,6 +9,7 @@ from common.models import db from common.models.current_token_balances import CurrentTokenBalances from common.models.erc721_token_id_changes import ERC721TokenIdChanges +from common.models.erc721_token_id_details import ERC721TokenIdDetails from common.models.erc721_token_transfers import ERC721TokenTransfers from common.models.erc1155_token_transfers import ERC1155TokenTransfers from common.utils.config import get_config @@ -40,13 +41,29 @@ def get(self, address): dn = datetime.now() # current_address holds 721 & 1155 tokens - all_721_owns = db.session.query(ERC721TokenIdChanges).filter(ERC721TokenIdChanges.token_owner == address).all() + ens_token_address = bytes.fromhex("57F1887A8BF19B14FC0DF6FD9B2ACC9AF147EA85") + all_721_owns = ( + db.session.query(ERC721TokenIdDetails) + .filter( + and_( + ERC721TokenIdDetails.token_owner == address, ERC721TokenIdDetails.token_address == ens_token_address + ) + ) + .all() + ) all_721_ids = [r.token_id for r in all_721_owns] all_owned = ( db.session.query(ENSRecord).filter(and_(ENSRecord.token_id.in_(all_721_ids), ENSRecord.expires >= dn)).all() ) res["ens_holdings"] = [r.name for r in all_owned if r.name and r.name.endswith(".eth")] - all_1155_owns = db.session.query(CurrentTokenBalances).filter(CurrentTokenBalances.address == address).all() + ens_1155_address = bytes.fromhex("d4416b13d2b3a9abae7acd5d6c2bbdbe25686401") + all_1155_owns = ( + db.session.query(CurrentTokenBalances) + .filter( + and_(CurrentTokenBalances.address == address, CurrentTokenBalances.token_address == ens_1155_address) + ) + .all() + ) all_1155_ids = [r.token_id for r in all_1155_owns] all_owned_1155_ens = ( db.session.query(ENSRecord) From 4804073515326aac86a706156dd62e24d2b45efd Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 13 Sep 2024 19:03:56 +0800 Subject: [PATCH 52/70] fixed uniswap v3 api name --- indexer/modules/custom/uniswap_v3/endpoints/routes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/indexer/modules/custom/uniswap_v3/endpoints/routes.py b/indexer/modules/custom/uniswap_v3/endpoints/routes.py index ad88fdba0..e062cc6e6 100644 --- a/indexer/modules/custom/uniswap_v3/endpoints/routes.py +++ b/indexer/modules/custom/uniswap_v3/endpoints/routes.py @@ -68,18 +68,18 @@ def get(self, wallet_address): erc20_infos["0x" + data.address.hex()] = data for token in tokenIds: - nft_address = "0x" + token.nft_address.hex() + nft_address = "0x" + token.position_token_address.hex() token_id = token.token_id key = (nft_address, token_id) token_id_infos[key] = token result = [] for holding in holdings: - nft_address = "0x" + holding.nft_address.hex() + position_token_address = "0x" + holding.position_token_address.hex() token_id = holding.token_id pool_address = "0x" + holding.pool_address.hex() sqrt_price = pool_price_map[pool_address] - token_id_info = token_id_infos[(nft_address, token_id)] + token_id_info = token_id_infos[(position_token_address, token_id)] pool_info = pool_infos[pool_address] token0_address = "0x" + pool_info.token0_address.hex() token1_address = "0x" + pool_info.token1_address.hex() @@ -101,7 +101,7 @@ def get(self, wallet_address): ) result.append( { - "nft_address": nft_address, + "nft_address": position_token_address, "token_id": str(token_id), "token0": { "token0_symbol": token0_info.symbol, From d8a797ae029b1ea79e47dee52a990220427a9912 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Sat, 14 Sep 2024 12:06:45 +0800 Subject: [PATCH 53/70] Adjust ens api (#143) * ens api index hit --- api/app/af_ens/routes.py | 51 ++++++++++++++----- indexer/modules/custom/hemera_ens/ens_conf.py | 5 ++ .../modules/custom/hemera_ens/ens_handler.py | 6 ++- .../modules/custom/hemera_ens/extractors.py | 20 ++++---- 4 files changed, 58 insertions(+), 24 deletions(-) diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index 75017a208..a0ae063db 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -8,7 +8,6 @@ from api.app.af_ens.action_types import OperationType from common.models import db from common.models.current_token_balances import CurrentTokenBalances -from common.models.erc721_token_id_changes import ERC721TokenIdChanges from common.models.erc721_token_id_details import ERC721TokenIdDetails from common.models.erc721_token_transfers import ERC721TokenTransfers from common.models.erc1155_token_transfers import ERC1155TokenTransfers @@ -31,6 +30,7 @@ def get(self, address): "primary_name": None, "be_resolved_by_ens": [], "ens_holdings": [], + "ens_holdings_total": None, "first_register_time": None, "first_set_primary_name": None, } @@ -52,10 +52,18 @@ def get(self, address): .all() ) all_721_ids = [r.token_id for r in all_721_owns] - all_owned = ( - db.session.query(ENSRecord).filter(and_(ENSRecord.token_id.in_(all_721_ids), ENSRecord.expires >= dn)).all() - ) - res["ens_holdings"] = [r.name for r in all_owned if r.name and r.name.endswith(".eth")] + all_owned = db.session.query(ENSRecord).filter(and_(ENSRecord.token_id.in_(all_721_ids))).all() + all_owned_map = {r.token_id: r for r in all_owned} + for id in all_721_ids: + r = all_owned_map.get(id) + res["ens_holdings"].append( + { + "name": r.name if r else None, + "is_expire": r.expires < dn if r and r.expires else True, + "type": "ERC721", + "token_id": str(id), + } + ) ens_1155_address = bytes.fromhex("d4416b13d2b3a9abae7acd5d6c2bbdbe25686401") all_1155_owns = ( db.session.query(CurrentTokenBalances) @@ -70,7 +78,14 @@ def get(self, address): .filter(and_(ENSRecord.expires >= dn, ENSRecord.w_token_id.in_(all_1155_ids))) .all() ) - res["ens_holdings"].extend([r.name for r in all_owned_1155_ens if r.name and r.name.endswith(".eth")]) + res["ens_holdings"].extend( + [ + {"name": r.name, "is_expire": r.expires < dn, "type": "ERC1155", "token_id": str(r.w_token_id)} + for r in all_owned_1155_ens + if r.name and r.name.endswith(".eth") + ] + ) + res["ens_holdings_total"] = len(res["ens_holdings"]) primary_address_row = db.session.query(ENSAddress).filter(ENSAddress.address == address).first() if primary_address_row: primary_record = db.session.query(ENSRecord).filter(ENSRecord.name == primary_address_row.name).first() @@ -79,14 +94,26 @@ def get(self, address): else: res["primary_name"] = w3.ens.name(w3.to_checksum_address(w3.to_hex(address))) - be_resolved_ens = ( - db.session.query(ENSRecord).filter(and_(ENSRecord.address == address, ENSRecord.expires >= dn)).all() - ) - res["be_resolved_by_ens"] = [r.name for r in be_resolved_ens if r.name and r.name.endswith(".eth")] + be_resolved_ens = db.session.query(ENSRecord).filter(and_(ENSRecord.address == address)).all() + res["be_resolved_by_ens"] = [ + { + "name": r.name, + "is_expire": r.expires < dn if r and r.expires else True, + } + for r in be_resolved_ens + if r.name and r.name.endswith(".eth") + ] + res["be_resolved_by_ens_total"] = len(res["be_resolved_by_ens"]) first_register = ( db.session.query(ENSMiddle) - .filter(and_(ENSMiddle.from_address == address, ENSMiddle.event_name == "NameRegistered")) + .filter( + and_( + ENSMiddle.from_address == address, + or_(ENSMiddle.event_name == "NameRegistered", ENSMiddle.event_name == "HashRegistered"), + ) + ) + .order_by(ENSMiddle.block_number) .first() ) if first_register: @@ -215,7 +242,7 @@ def get_action_type(record): } if record.method == "setName" or record.event_name == "NameChanged": return {"action_type": OperationType.SET_PRIMARY_NAME.value} - if record.event_name == "NameRegistered": + if record.event_name == "NameRegistered" or record.event_name == "HashRegistered": return {"action_type": OperationType.REGISTER.value} if record.event_name == "NameRenewed": return {"action_type": OperationType.RENEW.value, "expires": datetime_to_string(record.expires)} diff --git a/indexer/modules/custom/hemera_ens/ens_conf.py b/indexer/modules/custom/hemera_ens/ens_conf.py index bd77edef9..5a245c236 100644 --- a/indexer/modules/custom/hemera_ens/ens_conf.py +++ b/indexer/modules/custom/hemera_ens/ens_conf.py @@ -46,6 +46,11 @@ "0x5d81ce189bf5267e70fb555916ffbc3d4c7b2245": "EnsBatchRenew", "0x6109dd117aa5486605fc85e040ab00163a75c662": "RegistrarMigration", "0x6090a6e47849629b7245dfa1ca21d94cd15878ef": "ENS: Old Registrar", + "0xfca3df2bcd9c3d594923ffae0f132bfa1e8297c4": "NOT Verified", + "0xfc70fd608b3fb13a145e5d0e700b0fc00c10c5ef": "ENSVisionBulk", + "0xc55079a6ab7a123746806748d847228afe1d2397": "BulkRegister", + "0x000000000000509081d6fcd3ee63e791ad1db763": "NOT Verified", + "0xc71930b83cbabfeb35bedaaf2122e41595179b14": "NOT Verified", } diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index 92b16650e..4400040a1 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -45,6 +45,8 @@ def build_contract_map(self): contract_object_map = {} for ad_lower in CONTRACT_NAME_MAP: if ad_lower not in abi_map: + # keep it, not use decode + contract_object_map[ad_lower] = None continue abi = abi_map[ad_lower] contract = self.w3.eth.contract(address=Web3.to_checksum_address(ad_lower), abi=abi) @@ -54,6 +56,8 @@ def build_contract_map(self): function_map = defaultdict(dict) for contract_address, contract in contract_object_map.items(): + if not contract: + continue abi_events = [abi for abi in contract.abi if abi["type"] == "event"] for event in abi_events: sig = self.get_signature_of_event(event) @@ -99,8 +103,6 @@ def __init__(self, ens_conf_loader): self.extractors = [extractor() for extractor in BaseExtractor.__subclasses__()] def is_ens_address(self, address): - if address == "0x00000000008794027c69c26d2a048dbec09de67c": - return True return address.lower() in self.ens_conf_loader.contract_object_map def get_event_name(self, sig): diff --git a/indexer/modules/custom/hemera_ens/extractors.py b/indexer/modules/custom/hemera_ens/extractors.py index 373cc3d30..db4d52d59 100644 --- a/indexer/modules/custom/hemera_ens/extractors.py +++ b/indexer/modules/custom/hemera_ens/extractors.py @@ -103,7 +103,7 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, ens_middle.event_name = event_data["_event"] token_id = None w_token_id = None - for sl in prev_logs: + for sl in prev_logs[::-1]: if ( sl["address"] == "0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85" and (sl["topic2"]) == log["topic2"] @@ -147,21 +147,21 @@ def extract(self, address, tp0, log, ens_middle, contract_object_map, event_map, node = None label = None base_node = None - for log in prev_logs: + for sl in prev_logs[::-1]: if ( - log["address"] == "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e" - and log["topic0"] == RegisterExtractor.tp_register_with_token + sl["address"] == "0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e" + and sl["topic0"] == RegisterExtractor.tp_register_with_token ): - base_node = log["topic1"] - label = log["topic2"] + base_node = sl["topic1"] + label = sl["topic2"] node = compute_node_label(base_node, label) break elif ( - log["address"] == "0x314159265dd8dbb310642f98f50c066173c1259b" - and log["topic0"] == "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82" + sl["address"] == "0x314159265dd8dbb310642f98f50c066173c1259b" + and sl["topic0"] == "0xce0457fe73731f824cc272376169235128c118b49d344817417c6d108d155e82" ): - base_node = log["topic1"] - label = log["topic2"] + base_node = sl["topic1"] + label = sl["topic2"] node = compute_node_label(base_node, label) break if not node: From cde35c3ce74e006a23864ca561187899cdc7521e Mon Sep 17 00:00:00 2001 From: "godshan.eth" Date: Sat, 14 Sep 2024 15:39:20 +0800 Subject: [PATCH 54/70] Add aci endpoint (#128) * Add ACI Feature endpoints --------- Co-authored-by: ideal93 Co-authored-by: godshan.eth --- api/app/address/__init__.py | 7 + api/app/address/models.py | 43 ++++ api/app/address/routes.py | 207 ++++++++++++++++ api/app/af_ens/routes.py | 44 ++-- api/app/api.py | 3 + api/app/db_service/tokens.py | 14 +- api/app/deposit_to_l2/routes.py | 85 ++++--- api/app/main.py | 7 +- api/app/utils/parse_utils.py | 21 +- .../modules/custom/opensea/endpoint/routes.py | 19 +- .../custom/uniswap_v3/endpoints/routes.py | 232 ++++++++++++++---- 11 files changed, 556 insertions(+), 126 deletions(-) create mode 100644 api/app/address/__init__.py create mode 100644 api/app/address/models.py create mode 100644 api/app/address/routes.py diff --git a/api/app/address/__init__.py b/api/app/address/__init__.py new file mode 100644 index 000000000..c72b9a720 --- /dev/null +++ b/api/app/address/__init__.py @@ -0,0 +1,7 @@ +from flask_restx.namespace import Namespace + +address_features_namespace = Namespace( + "Address Features", + path="/", + description="Address Features API", +) diff --git a/api/app/address/models.py b/api/app/address/models.py new file mode 100644 index 000000000..0355996a7 --- /dev/null +++ b/api/app/address/models.py @@ -0,0 +1,43 @@ +from sqlalchemy import Column, Integer, PrimaryKeyConstraint, func +from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, INTEGER, NUMERIC, TEXT, TIMESTAMP + +from common.models import HemeraModel + + +class AddressBaseProfile(HemeraModel): + __tablename__ = "af_base_profile" + + address = Column(BYTEA, primary_key=True, nullable=False) + init_funding_from_address = Column(BYTEA) + init_funding_value = Column(NUMERIC) + init_funding_transaction_hash = Column(BYTEA) + init_funding_block_timestamp = Column(TIMESTAMP) + init_block_hash = Column(BYTEA) + init_block_number = Column(INTEGER) + creation_code = Column(BYTEA) + deployed_code = Column(BYTEA) + deployed_block_timestamp = Column(TIMESTAMP) + deployed_block_number = Column(INTEGER) + deployed_block_hash = Column(BYTEA) + deployed_transaction_hash = Column(BYTEA) + deployed_internal_transaction_from_address = Column(BYTEA) + deployed_transaction_from_address = Column(BYTEA) + deployed_trace_id = Column(TEXT) + is_contract = Column(BOOLEAN) + first_transaction_hash = Column(BYTEA) + first_block_hash = Column(BYTEA) + first_block_number = Column(INTEGER) + first_block_timestamp = Column(TIMESTAMP) + first_trace_id = Column(TEXT) + first_is_from_address = Column(BOOLEAN) + first_trace_type = Column(TEXT) + first_call_type = Column(TEXT) + + +class ScheduledMetadata(HemeraModel): + __tablename__ = "af_base_na_scheduled_metadata" + + id = Column(Integer, primary_key=True) + dag_id = Column(TEXT) + execution_date = Column(TIMESTAMP) + last_data_timestamp = Column(TIMESTAMP) diff --git a/api/app/address/routes.py b/api/app/address/routes.py new file mode 100644 index 000000000..143ee7f34 --- /dev/null +++ b/api/app/address/routes.py @@ -0,0 +1,207 @@ +from time import time +from typing import Union + +import flask +from flask_restx import Resource +from sqlalchemy import func + +from api.app.address import address_features_namespace +from api.app.address.models import AddressBaseProfile, ScheduledMetadata +from api.app.af_ens.routes import ACIEnsCurrent, ACIEnsDetail +from api.app.cache import cache +from api.app.deposit_to_l2.routes import ACIDepositToL2Current, ACIDepositToL2Transactions +from common.models import db +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict +from indexer.modules.custom.opensea.endpoint.routes import ACIOpenseaProfile, ACIOpenseaTransactions +from indexer.modules.custom.uniswap_v3.endpoints.routes import ( + UniswapV3WalletLiquidityDetail, + UniswapV3WalletLiquidityHolding, + UniswapV3WalletTradingRecords, + UniswapV3WalletTradingSummary, +) + +PAGE_SIZE = 10 +MAX_TRANSACTION = 500000 +MAX_TRANSACTION_WITH_CONDITION = 10000 +MAX_INTERNAL_TRANSACTION = 10000 +MAX_TOKEN_TRANSFER = 10000 + + +def get_address_recent_info(address: bytes, last_timestamp: int) -> dict: + pass + + +def get_address_stats(address: Union[str, bytes]) -> dict: + pass + + +def get_address_profile(address: Union[str, bytes]) -> dict: + """ + Fetch and combine address profile data from both the base profile and recent transactions. + """ + address_bytes = bytes.fromhex(address[2:]) if isinstance(address, str) else address + + # Fetch the base profile + base_profile = db.session.query(AddressBaseProfile).filter_by(address=address_bytes).first() + if not base_profile: + raise APIError("No profile found for this address", code=400) + + # Convert base profile to a dictionary + base_profile_data = as_dict(base_profile) + + # Fetch the latest scheduled metadata timestamp + last_timestamp = db.session.query(func.max(ScheduledMetadata.last_data_timestamp)).scalar() + """ + # Fetch recent transaction data from AddressOpenseaTransactions + recent_data = get_address_recent_info(address_bytes, last_timestamp) + + # Merge recent transaction data with base profile data + for key, value in recent_data.items(): + if key != "address": + base_profile_data[key] += value + + # Fetch the latest transaction + latest_transaction = get_latest_transaction_by_address(address_bytes) + """ + # Combine and return the base profile, recent data, and latest transaction + return base_profile_data + + +@address_features_namespace.route("/v1/aci/
/profile") +class ACIProfiles(Resource): + @cache.cached(timeout=60) + def get(self, address): + address = address.lower() + + profile = get_address_profile(address) + + return profile, 200 + + +@address_features_namespace.route("/v1/aci/
/contract_deployer/profile") +class ACIContractDeployerProfile(Resource): + def get(self, address): + address = address.lower() + return {"deployed_countract_count": 353, "first_deployed_time": "2015-10-06T07:56:55+00:00"} + + +@address_features_namespace.route("/v1/aci/
/contract_deployer/events") +class ACIContractDeployerEvents(Resource): + def get(self, address): + address = address.lower() + + return { + "data": [ + { + "contract_address": "0xfeb016d0d14ac0fa6d69199608b0776d007203b2", + "block_number": "20297335", + "transaction_hash": "0x3a15ac802a8cfc8e2be090fd4d3522ac4382798c82f6a3e3e82716a76f488962", + "block_timestamp": "2024-07-13T11:37:35+00:00", + }, + { + "contract_address": "0x0b19e087493a6ec31661470bd9ba6c49873e97f0", + "block_number": "18325782", + "transaction_hash": "0x9c87cc852d831b39f25d789c79b4ff25d7880202a133d500ced8d59629ab2317", + "block_timestamp": "2023-10-11T07:42:11+00:00", + }, + { + "contract_address": "0xb36082ba6c35490d1e167cc6dd5ad20884a21afb", + "block_number": "17426924", + "transaction_hash": "0xe94f6728f2247bf0157e1cc20e68d862b576e74192775ab7959d079a25ce8512", + "block_timestamp": "2023-06-07T07:09:23+00:00", + }, + { + "contract_address": "0x8e160c8e949967d6b797cdf2a2f38f6344a5c95f", + "block_number": "16553954", + "transaction_hash": "0x9eaf21a7415c44d9c2c925493b35bd518a15ff6d19e0a10d4a95114033c20b65", + "block_timestamp": "2023-02-04T07:32:35+00:00", + }, + { + "contract_address": "0x00a0b1f5be3a7a4b715a2b8d395a76abc0a8e149", + "block_number": "14781402", + "transaction_hash": "0x3839ea3360fcbc0375ea95fd29a8a26c6a2fadd0697aeaaeff4de89512beed8a", + "block_timestamp": "2022-05-15T18:02:18+00:00", + }, + ], + "total": 5, + } + + +@address_features_namespace.route("/v1/aci/
/all_features") +class ACIAllFeatures(Resource): + @cache.cached(timeout=3600, query_string=True) + def get(self, address): + address = address.lower() + feature_list = [ + "ens", + "opensea", + "uniswap_v3_trading", + "uniswap_v3_liquidity", + "deposit_to_l2", + "contract_deployer", + ] + features = flask.request.args.get("features") + if features: + feature_list = features.split(",") + + timer = time() + feature_result = {} + + if "contract_deployer" in feature_list: + # profile = get_address_profile(address) + feature_result["contract_deployer"] = { + "value": ACIContractDeployerProfile.get(self, address), + "events": ACIContractDeployerEvents.get(self, address), + } + + if "ens" in feature_list: + feature_result["ens"] = { + "value": ACIEnsCurrent.get(self, address), + "events": ACIEnsDetail.get(self, address), + } + + if "opensea" in feature_list: + feature_result["opensea"] = { + "value": ACIOpenseaProfile.get(self, address), + "events": ACIOpenseaTransactions.get(self, address), + } + + if "uniswap_v3_liquidity" in feature_list: + holdings = UniswapV3WalletLiquidityHolding.get(self, address) + feature_result["uniswap_v3_liquidity"] = { + "value": {"pool_count": holdings["pool_count"], "total_value_usd": holdings["total_value_usd"]}, + "events": {"data": holdings["data"], "total": holdings["total"]}, + } + + if "uniswap_v3_trading" in feature_list: + feature_result["uniswap_v3_trading"] = { + "value": UniswapV3WalletTradingSummary.get(self, address), + "events": UniswapV3WalletTradingRecords.get(self, address), + } + + if "deposit_to_l2" in feature_list: + feature_result["deposit_to_l2"] = { + "value": ACIDepositToL2Current.get(self, address), + "events": ACIDepositToL2Transactions.get(self, address), + } + + feature_data_list = [] + for feature_id in feature_list: + if feature_result.get(feature_id): + feature_data_list.append( + { + "id": feature_id, + "value": feature_result.get(feature_id).get("value"), + "events": feature_result.get(feature_id).get("events"), + } + ) + + print(time() - timer) + + combined_result = { + "address": address, + "features": feature_data_list, + } + + return combined_result, 200 diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index a0ae063db..25f25c1ae 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -1,8 +1,10 @@ import decimal from datetime import date, datetime +from time import time from flask_restx import Resource from flask_restx.namespace import Namespace +from sqlalchemy.sql import and_, or_ from web3 import Web3 from api.app.af_ens.action_types import OperationType @@ -12,32 +14,35 @@ from common.models.erc721_token_transfers import ERC721TokenTransfers from common.models.erc1155_token_transfers import ERC1155TokenTransfers from common.utils.config import get_config +from common.utils.exception_control import APIError from indexer.modules.custom.hemera_ens.models.af_ens_address_current import ENSAddress from indexer.modules.custom.hemera_ens.models.af_ens_event import ENSMiddle from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ENSRecord app_config = get_config() -from sqlalchemy import and_, or_ w3 = Web3(Web3.HTTPProvider("https://ethereum-rpc.publicnode.com")) af_ens_namespace = Namespace("User Operation Namespace", path="/", description="ENS feature") +PAGE_SIZE = 10 + + @af_ens_namespace.route("/v1/aci/
/ens/current") -class ExplorerUserOperationDetails(Resource): +class ACIEnsCurrent(Resource): def get(self, address): res = { "primary_name": None, - "be_resolved_by_ens": [], + "resolve_to_names": [], "ens_holdings": [], "ens_holdings_total": None, "first_register_time": None, - "first_set_primary_name": None, + "first_set_primary_time": None, } if address: address = bytes.fromhex(address[2:]) else: - return res + raise APIError("Invalid Address Format", code=400) dn = datetime.now() # current_address holds 721 & 1155 tokens @@ -95,7 +100,7 @@ def get(self, address): res["primary_name"] = w3.ens.name(w3.to_checksum_address(w3.to_hex(address))) be_resolved_ens = db.session.query(ENSRecord).filter(and_(ENSRecord.address == address)).all() - res["be_resolved_by_ens"] = [ + res["resolve_to_names"] = [ { "name": r.name, "is_expire": r.expires < dn if r and r.expires else True, @@ -103,7 +108,7 @@ def get(self, address): for r in be_resolved_ens if r.name and r.name.endswith(".eth") ] - res["be_resolved_by_ens_total"] = len(res["be_resolved_by_ens"]) + res["resolve_to_names_total"] = len(res["resolve_to_names"]) first_register = ( db.session.query(ENSMiddle) @@ -126,27 +131,29 @@ def get(self, address): or_(ENSMiddle.method == "setName", ENSMiddle.event_name == "NameChanged"), ) ) + .order_by(ENSMiddle.block_number) .first() ) if first_set_name: - res["first_set_primary_name"] = datetime_to_string(first_set_name.block_timestamp) + res["first_set_primary_time"] = datetime_to_string(first_set_name.block_timestamp) return res @af_ens_namespace.route("/v1/aci/
/ens/detail") -class ExplorerUserOperationDetails(Resource): +class ACIEnsDetail(Resource): def get(self, address): - lis = [] if address: address = bytes.fromhex(address[2:]) else: - return lis + raise APIError("Invalid Address Format", code=400) + events = [] all_records_rows = ( db.session.query(ENSMiddle) .filter(ENSMiddle.from_address == address) - .order_by(ENSMiddle.block_number, ENSMiddle.transaction_index, ENSMiddle.log_index.desc()) + .order_by(ENSMiddle.block_number.desc(), ENSMiddle.log_index.desc()) + .limit(PAGE_SIZE) .all() ) # erc721_ids = list({r.token_id for r in all_records_rows if r.token_id}) @@ -198,9 +205,14 @@ def get(self, address): extras = get_action_type(r) base.update(extras) - lis.append(base) + events.append(base) - return lis + return { + "data": events, + "total": len(events), + "page": 1, + "size": PAGE_SIZE, + } def merge_ens_middle(records): @@ -253,9 +265,9 @@ def get_action_type(record): def datetime_to_string(dt, format="%Y-%m-%d %H:%M:%S"): if isinstance(dt, datetime): - return dt.strftime(format) + return dt.astimezone().isoformat("T", "seconds") elif isinstance(dt, date): - return dt.strftime(format) + return dt.astimezone().isoformat("T", "seconds") elif dt is None: return None else: diff --git a/api/app/api.py b/api/app/api.py index 0a3e6b2c7..678a0a361 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -3,6 +3,7 @@ from flask_restx import Api +from api.app.address.routes import address_features_namespace from api.app.af_ens.routes import af_ens_namespace from api.app.contract.routes import contract_namespace from api.app.deposit_to_l2.routes import token_deposit_namespace @@ -26,3 +27,5 @@ # api.add_namespace(l2_explorer_namespace) api.add_namespace(af_ens_namespace) + +api.add_namespace(address_features_namespace) diff --git a/api/app/db_service/tokens.py b/api/app/db_service/tokens.py index 3b709e14a..cd41d4556 100644 --- a/api/app/db_service/tokens.py +++ b/api/app/db_service/tokens.py @@ -1,4 +1,4 @@ -from sqlalchemy import and_, func, or_ +from sqlalchemy import and_, func, select from api.app.db_service.contracts import get_contracts_by_addresses from api.app.db_service.wallet_addresses import get_token_txn_cnt_by_address @@ -12,6 +12,7 @@ from common.models.erc721_token_transfers import ERC721TokenTransfers from common.models.erc1155_token_transfers import ERC1155TokenTransfers from common.models.scheduled_metadata import ScheduledTokenCountMetadata, ScheduledWalletCountMetadata +from common.models.token_prices import TokenPrices from common.models.tokens import Tokens from common.utils.config import get_config from common.utils.db_utils import build_entities @@ -306,3 +307,14 @@ def get_token_holders_cnt(token_address: str, model, columns="*"): ) return holders_count + + +def get_token_price_map_by_symbol_list(token_symbol_list): + token_price_map = {} + for symbol in token_symbol_list: + token_price = db.session.execute( + select(TokenPrices).where(TokenPrices.symbol == symbol).order_by(TokenPrices.timestamp.desc()).limit(1) + ).scalar() + if token_price: + token_price_map[symbol] = token_price.price + return token_price_map diff --git a/api/app/deposit_to_l2/routes.py b/api/app/deposit_to_l2/routes.py index 99922ba18..d2d7c0f5e 100644 --- a/api/app/deposit_to_l2/routes.py +++ b/api/app/deposit_to_l2/routes.py @@ -1,3 +1,5 @@ +from time import time + import flask from flask_restx import Resource @@ -10,6 +12,7 @@ get_transactions_cnt_by_wallet, ) from api.app.db_service.blocks import get_block_by_hash +from api.app.db_service.tokens import get_token_price_map_by_symbol_list from api.app.deposit_to_l2 import token_deposit_namespace from api.app.utils.parse_utils import parse_deposit_assets, parse_deposit_transactions from common.utils.config import get_config @@ -18,32 +21,31 @@ from common.utils.web3_utils import SUPPORT_CHAINS, chain_id_name_mapping from indexer.modules.custom.deposit_to_l2.models.af_token_deposits__transactions import AFTokenDepositsTransactions +PAGE_SIZE = 10 MAX_TRANSACTION = 500000 MAX_TRANSACTION_WITH_CONDITION = 10000 app_config = get_config() -@token_deposit_namespace.route("/v1/aci/deposit/transactions") -class ExplorerDepositTransactions(Resource): - @cache.cached(timeout=10, query_string=True) - def get(self): +@token_deposit_namespace.route("/v1/aci/
/deposit_to_l2/transactions") +class ACIDepositToL2Transactions(Resource): + def get(self, address): page_index = int(flask.request.args.get("page", 1)) - page_size = int(flask.request.args.get("size", 25)) + page_size = int(flask.request.args.get("size", PAGE_SIZE)) if page_index <= 0 or page_size <= 0: raise APIError("Invalid page or size", code=400) if page_index * page_size > MAX_TRANSACTION: raise APIError(f"Showing the last {MAX_TRANSACTION} records only", code=400) - wallet_address = flask.request.args.get("wallet_address", None) chain = flask.request.args.get("chain", None) contract = flask.request.args.get("contract", None) token = flask.request.args.get("token", None) block = flask.request.args.get("block", None) has_filter = False - if wallet_address or chain or contract or token or block: + if address or chain or contract or token or block: has_filter = True if page_index * page_size > MAX_TRANSACTION_WITH_CONDITION: raise APIError( @@ -53,10 +55,10 @@ def get(self): filter_condition = True - if wallet_address: - wallet_address = wallet_address.lower() - bytes_wallet_address = bytes.fromhex(wallet_address[2:]) - filter_condition = AFTokenDepositsTransactions.wallet_address == bytes_wallet_address + if address: + address = address.lower() + bytes_address = bytes.fromhex(address[2:]) + filter_condition = AFTokenDepositsTransactions.wallet_address == bytes_address elif chain: if chain.isnumeric(): @@ -88,7 +90,6 @@ def get(self): block_number = get_block_by_hash(hash=block, columns=["number"]) filter_condition = AFTokenDepositsTransactions.block_number == block_number - total_records = get_transactions_cnt_by_condition(filter_condition=filter_condition) transactions = get_transactions_by_condition( filter_condition=filter_condition, columns=[ @@ -105,49 +106,59 @@ def get(self): offset=(page_index - 1) * page_size, ) + total_records = get_transactions_cnt_by_condition(filter_condition=filter_condition) transaction_list = parse_deposit_transactions(transactions) return { "data": transaction_list, "total": total_records, - "max_display": min( - (MAX_TRANSACTION_WITH_CONDITION if has_filter else MAX_TRANSACTION), - total_records, - ), "page": page_index, "size": page_size, - }, 200 + } -@token_deposit_namespace.route("/v1/aci//deposit/current") -class ExplorerDepositCurrent(Resource): - - @cache.cached(timeout=10, query_string=True) - def get(self, wallet_address): - if wallet_address is None: +@token_deposit_namespace.route("/v1/aci/
/deposit_to_l2/current") +class ACIDepositToL2Current(Resource): + def get(self, address): + if address is None: raise APIError( - f"parameter 'wallet_address' are required", + f"parameter 'address' are required", code=400, ) - deposit_times = get_transactions_cnt_by_wallet(wallet_address) + deposit_count = get_transactions_cnt_by_wallet(address) - chains = get_deposit_chain_list(wallet_address) + chains = get_deposit_chain_list(address) chain_list = [chain_id_name_mapping[row_to_dict(chain)["chain_id"]] for chain in chains] - assets = get_deposit_assets_list(wallet_address) + assets = get_deposit_assets_list(address) + asset_list = parse_deposit_assets(assets) + token_symbol_list = [] + for asset in asset_list: + token_symbol_list.append(asset["token_symbol"]) + + token_price_map = get_token_price_map_by_symbol_list(list(set(token_symbol_list))) + + total_value_usd = 0 + for asset in asset_list: + if asset["token_symbol"] in token_price_map: + amount_usd = float(asset["amount"]) * float(token_price_map[asset["token_symbol"]]) + asset["amount_usd"] = amount_usd + total_value_usd += amount_usd + return { - "wallet_address": wallet_address, - "deposit_times": deposit_times, + "address": address, + "deposit_count": deposit_count, "chain_list": chain_list, "asset_list": asset_list, - }, 200 + "total_value_usd": total_value_usd, + } -@token_deposit_namespace.route("/v1/aci//deposit/bridge_times") -class ExplorerDepositBridgeTimes(Resource): +@token_deposit_namespace.route("/v1/aci//deposit_to_l2/bridge_times") +class ACIDepositToL2BridgeTimes(Resource): @cache.cached(timeout=10, query_string=True) def get(self, wallet_address): @@ -162,11 +173,11 @@ def get(self, wallet_address): return { "wallet_address": wallet_address, "bridge_times": times, - }, 200 + } -@token_deposit_namespace.route("/v1/aci//deposit/chain_list") -class ExplorerDepositChainList(Resource): +@token_deposit_namespace.route("/v1/aci//deposit_to_l2/chain_list") +class ACIDepositToL2ChainList(Resource): @cache.cached(timeout=10, query_string=True) def get(self, wallet_address): @@ -185,8 +196,8 @@ def get(self, wallet_address): }, 200 -@token_deposit_namespace.route("/v1/aci//deposit/assets_list") -class ExplorerDepositAssetsList(Resource): +@token_deposit_namespace.route("/v1/aci//deposit_to_l2/assets_list") +class ACIDepositToL2AssetsList(Resource): @cache.cached(timeout=10, query_string=True) def get(self, wallet_address): diff --git a/api/app/main.py b/api/app/main.py index 9f25fc29c..e1ccbbc89 100644 --- a/api/app/main.py +++ b/api/app/main.py @@ -1,7 +1,6 @@ #!/usr/bin/python3 # -*- coding: utf-8 -*- -import base64 -import json + import logging from datetime import datetime @@ -20,7 +19,6 @@ config = get_config() logging.basicConfig(level=logging.INFO) -# logging.basicConfig() # logging.getLogger("sqlalchemy.pool").setLevel(logging.DEBUG) app = Flask(__name__) @@ -111,9 +109,6 @@ def _build_cors_prelight_response(): @app.before_request def hook(): - if get_real_ip() in ["182.253.52.70", "37.27.101.162"]: - raise APIError("Forbidden", 403) - if request.method == "OPTIONS": # CORS preflight return _build_cors_prelight_response() diff --git a/api/app/utils/parse_utils.py b/api/app/utils/parse_utils.py index 295bab207..c68202c28 100644 --- a/api/app/utils/parse_utils.py +++ b/api/app/utils/parse_utils.py @@ -2,6 +2,13 @@ from common.utils.format_utils import row_to_dict from common.utils.web3_utils import chain_id_name_mapping +SUPPORT_BRIDGES = { + "0x99c9fc46f92e8a1c0dec1b1747d010903e884be1": { + "bridge_name": "Optimism Bridge", + "bridge_logo": "https://storage.googleapis.com/socialscan-public-asset/bridge/optimism.png", + } +} + def parse_deposit_assets(assets): asset_list = [] @@ -11,11 +18,13 @@ def parse_deposit_assets(assets): token_info = get_token_by_address( asset_dict["token_address"], ["name", "symbol", "decimals", "icon_url", "token_type"] ) - decimals = token_info.decimals if token_info else 18 + decimals = int(token_info.decimals) if token_info else 18 asset_list.append( { "chain": chain_id_name_mapping[asset_dict["chain_id"]], - "bridge": asset_dict["contract_address"], + "bridge_contract_address": asset_dict["contract_address"], + "bridge_name": SUPPORT_BRIDGES[asset_dict["contract_address"]]["bridge_name"], + "bridge_logo": SUPPORT_BRIDGES[asset_dict["contract_address"]]["bridge_logo"], "token": asset_dict["token_address"], "token_name": token_info.name if token_info else None, "token_symbol": token_info.symbol if token_info else None, @@ -37,10 +46,10 @@ def parse_deposit_transactions(transactions): token_info = get_token_by_address( tx_dict["token_address"], ["name", "symbol", "decimals", "icon_url", "token_type"] ) - decimals = token_info.decimals if token_info else 18 - tx_dict["name"] = token_info.name if token_info else None - tx_dict["symbol"] = token_info.symbol if token_info else None - tx_dict["icon_url"] = token_info.icon_url if token_info else None + decimals = int(token_info.decimals) if token_info else 18 + tx_dict["token_name"] = token_info.name if token_info else None + tx_dict["token_symbol"] = token_info.symbol if token_info else None + tx_dict["token_icon_url"] = token_info.icon_url if token_info else None tx_dict["token_type"] = token_info.token_type if token_info else None tx_dict["value"] = "{0:.18f}".format(tx_dict["value"] / 10**decimals).rstrip("0").rstrip(".") diff --git a/indexer/modules/custom/opensea/endpoint/routes.py b/indexer/modules/custom/opensea/endpoint/routes.py index b86d03cfa..7c39645fa 100644 --- a/indexer/modules/custom/opensea/endpoint/routes.py +++ b/indexer/modules/custom/opensea/endpoint/routes.py @@ -25,10 +25,6 @@ ) PAGE_SIZE = 10 -MAX_TRANSACTION = 500000 -MAX_TRANSACTION_WITH_CONDITION = 10000 -MAX_INTERNAL_TRANSACTION = 10000 -MAX_TOKEN_TRANSFER = 10000 def get_opensea_profile(address: Union[str, bytes]) -> dict: @@ -39,7 +35,7 @@ def get_opensea_profile(address: Union[str, bytes]) -> dict: profile = db.session.query(AddressOpenseaProfile).filter_by(address=address_bytes).first() if not profile: - raise APIError("No OpenSea profile found for this address", code=400) + return {} profile_data = as_dict(profile) @@ -239,6 +235,7 @@ def format_opensea_transaction(transaction: Dict[str, Any]) -> Dict[str, Any]: "token_symbol": item["token_symbol"], "amount": item["amount"], "identifier": item["identifier"], + "token_id": item["identifier"], # Naming conversion } formatted_items.append(formatted_item) @@ -306,19 +303,17 @@ def get_latest_opensea_transaction_by_address(address: Union[str, bytes]): @opensea_namespace.route("/v1/aci/
/opensea/profile") -class ExplorerCustomOpenseaAddressProfile(Resource): - @cache.cached(timeout=60) +class ACIOpenseaProfile(Resource): def get(self, address): address = address.lower() profile = get_opensea_profile(address) - return profile, 200 + return profile @opensea_namespace.route("/v1/aci/
/opensea/transactions") -class ExplorerCustomOpenseaAddressTransactions(Resource): - @cache.cached(timeout=10) +class ACIOpenseaTransactions(Resource): def get(self, address): address = address.lower() page_index = int(request.args.get("page", 1)) @@ -340,4 +335,6 @@ def get(self, address): return { "data": transaction_list, "total": total_count, - }, 200 + "page": page_index, + "size": page_size, + } diff --git a/indexer/modules/custom/uniswap_v3/endpoints/routes.py b/indexer/modules/custom/uniswap_v3/endpoints/routes.py index e062cc6e6..9c058fae7 100644 --- a/indexer/modules/custom/uniswap_v3/endpoints/routes.py +++ b/indexer/modules/custom/uniswap_v3/endpoints/routes.py @@ -1,38 +1,138 @@ -import binascii import math -import re -from datetime import datetime, timedelta -from decimal import Decimal, getcontext -from operator import or_ +from datetime import datetime +from time import time from flask import request from flask_restx import Resource -from sqlalchemy import and_, asc, desc, func +from sqlalchemy.sql import select, tuple_ -from api.app import explorer -from api.app.cache import cache -from api.app.explorer import explorer_namespace +from api.app.db_service.tokens import get_token_price_map_by_symbol_list from common.models import db -from common.models import db as postgres_db from common.models.tokens import Tokens from common.utils.exception_control import APIError -from common.utils.format_utils import as_dict, format_to_dict, format_value_for_json, row_to_dict -from common.utils.web3_utils import is_eth_address from indexer.modules.custom.uniswap_v3.endpoints import uniswap_v3_namespace from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_liquidity_records import UniswapV3TokenLiquidityRecords from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pool_current_prices import UniswapV3PoolCurrentPrices from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_swap_records import UniswapV3PoolSwapRecords from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_token_current_status import UniswapV3TokenCurrentStatus from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_tokens import UniswapV3Tokens Q96 = 2**96 +PAGE_SIZE = 10 +STABLE_COINS = { + "0xdac17f958d2ee523a2206206994597c13d831ec7": "USDT", + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48": "USDC", + "0x6b175474e89094c44da98b954eedeac495271d0f": "DAI", +} -@uniswap_v3_namespace.route("/v1/aci//uniswapv3/current_holding") -class UniswapV3WalletHolding(Resource): - def get(self, wallet_address): - wallet_address = wallet_address.lower() - address_bytes = bytes.fromhex(wallet_address[2:]) +NATIVE_COINS = { + "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2": "WETH", +} + + +@uniswap_v3_namespace.route("/v1/aci/
/uniswap_v3_trading/swaps") +class UniswapV3WalletTradingRecords(Resource): + def get(self, address): + address = address.lower() + address_bytes = bytes.fromhex(address[2:]) + + swaps = ( + db.session.execute( + select(UniswapV3PoolSwapRecords) + .where(UniswapV3PoolSwapRecords.transaction_from_address == address_bytes) + .order_by(UniswapV3PoolSwapRecords.block_timestamp.desc()) + .limit(PAGE_SIZE) + ) + .scalars() + .all() + ) + swap_records = [] + token_list = [] + for swap in swaps: + token_list.append(swap.token0_address) + token_list.append(swap.token1_address) + + tokens = db.session.execute(select(Tokens).where(Tokens.address.in_(list(set(token_list))))).scalars().all() + + token_map = {token.address: token for token in tokens} + + for swap in swaps: + token0 = token_map.get(swap.token0_address) + token1 = token_map.get(swap.token1_address) + swap_records.append( + { + "block_number": swap.block_number, + "block_timestamp": datetime.fromtimestamp(swap.block_timestamp).isoformat("T", "seconds"), + "transaction_hash": "0x" + swap.transaction_hash.hex(), + "pool_address": "0x" + swap.pool_address.hex(), + "amount0": "{0:.18f}".format(abs(swap.amount0) / 10**token0.decimals).rstrip("0").rstrip("."), + "amount1": "{0:.18f}".format(abs(swap.amount1) / 10**token1.decimals).rstrip("0").rstrip("."), + "token0_address": "0x" + swap.token0_address.hex(), + "token0_symbol": token0.symbol, + "token0_name": token0.name, + "token0_icon_url": token0.icon_url, + "token1_address": "0x" + swap.token1_address.hex(), + "token1_symbol": token1.symbol, + "token1_name": token1.name, + "token1_icon_url": token1.icon_url, + "action_type": get_swap_action_type(swap), + } + ) + + return { + "data": swap_records, + "total": len(swap_records), + "page": 1, + "szie": PAGE_SIZE, + } + + +@uniswap_v3_namespace.route("/v1/aci/
/uniswap_v3_trading/summary") +class UniswapV3WalletTradingSummary(Resource): + def get(self, address): + address = address.lower() + address_bytes = bytes.fromhex(address[2:]) + + swaps = db.session.execute( + select( + UniswapV3PoolSwapRecords.transaction_hash, + UniswapV3PoolSwapRecords.token0_address, + UniswapV3PoolSwapRecords.token1_address, + UniswapV3PoolSwapRecords.amount0, + UniswapV3PoolSwapRecords.amount1, + ) + .where(UniswapV3PoolSwapRecords.transaction_from_address == address_bytes) + .order_by(UniswapV3PoolSwapRecords.block_timestamp.desc()) + ).all() + + token_list = [] + transaction_hash_list = [] + + for swap in swaps: + token_list.append(swap.token0_address) + token_list.append(swap.token1_address) + transaction_hash_list.append(swap.transaction_hash) + + token_list = list(set(token_list)) + transaction_hash_list = list(set(transaction_hash_list)) + + return { + "trade_count": len(transaction_hash_list), + "trade_asset_count": len(token_list), + "total_volume_usd": 0, + "average_value_usd": 0, + } + + +@uniswap_v3_namespace.route("/v1/aci/
/uniswap_v3_liquidity/current_holding") +class UniswapV3WalletLiquidityHolding(Resource): + def get(self, address): + address = address.lower() + address_bytes = bytes.fromhex(address[2:]) + + # Get all LP holdings holdings = ( db.session.query(UniswapV3TokenCurrentStatus) .filter(UniswapV3TokenCurrentStatus.wallet_address == address_bytes) @@ -40,6 +140,7 @@ def get(self, wallet_address): .all() ) + # Get Pool Price unique_pool_addresses = {holding.pool_address for holding in holdings} pool_prices = ( db.session.query(UniswapV3PoolCurrentPrices) @@ -51,8 +152,21 @@ def get(self, wallet_address): pool_address = "0x" + data.pool_address.hex() pool_price_map[pool_address] = data.sqrt_price_x96 - tokenIds = db.session.query(UniswapV3Tokens).all() + # Get token id info + token_id_list = [(holding.position_token_address, holding.token_id) for holding in holdings] + tokenIds = ( + db.session.query(UniswapV3Tokens) + .filter(tuple_(UniswapV3Tokens.position_token_address, UniswapV3Tokens.token_id).in_(token_id_list)) + .all() + ) token_id_infos = {} + for token in tokenIds: + position_token_address = "0x" + token.position_token_address.hex() + token_id = token.token_id + key = (position_token_address, token_id) + token_id_infos[key] = token + + # Get Token info erc20_tokens = set() pool_infos = {} pools = db.session.query(UniswapV3Pools).filter(UniswapV3Pools.pool_address.in_(unique_pool_addresses)).all() @@ -64,16 +178,16 @@ def get(self, wallet_address): erc20_datas = db.session.query(Tokens).filter(Tokens.address.in_(erc20_tokens)).all() erc20_infos = {} + token_symbol_list = [] for data in erc20_datas: erc20_infos["0x" + data.address.hex()] = data + token_symbol_list.append(data.symbol) - for token in tokenIds: - nft_address = "0x" + token.position_token_address.hex() - token_id = token.token_id - key = (nft_address, token_id) - token_id_infos[key] = token + # Get Token Price + token_price_map = get_token_price_map_by_symbol_list(list(set(token_symbol_list))) result = [] + total_value_usd = 0 for holding in holdings: position_token_address = "0x" + holding.position_token_address.hex() token_id = holding.token_id @@ -99,29 +213,44 @@ def get(self, wallet_address): token0_info.decimals, token1_info.decimals, ) + token0_value_usd = float(amount0_str) * float(token_price_map.get(token0_info.symbol, 0)) + token1_value_usd = float(amount1_str) * float(token_price_map.get(token1_info.symbol, 0)) + total_value_usd += token0_value_usd + total_value_usd += token1_value_usd result.append( { - "nft_address": position_token_address, + "pool_address": pool_address, + "position_token_address": position_token_address, "token_id": str(token_id), + "block_timestamp": datetime.fromtimestamp(holding.block_timestamp).isoformat("T", "seconds"), "token0": { "token0_symbol": token0_info.symbol, + "token0_icon_url": token0_info.icon_url, "token0_balance": amount0_str, + "token0_value_usd": token0_value_usd, }, "token1": { "token1_symbol": token1_info.symbol, + "token1_icon_url": token1_info.icon_url, "token1_balance": amount1_str, + "token1_value_usd": token1_value_usd, }, } ) - return result, 200 + return { + "data": result, + "total": len(result), + "pool_count": len(unique_pool_addresses), + "total_value_usd": total_value_usd, + } -@uniswap_v3_namespace.route("/v1/aci//uniswapv3/first_liquidity_time") +@uniswap_v3_namespace.route("/v1/aci/
/uniswap_v3_liquidity/summary") class UniswapV3WalletLiquidityDetail(Resource): - def get(self, wallet_address): - wallet_address = wallet_address.lower() - address_bytes = bytes.fromhex(wallet_address[2:]) + def get(self, address): + address = address.lower() + address_bytes = bytes.fromhex(address[2:]) first_holding = ( db.session.query(UniswapV3TokenLiquidityRecords) .filter(UniswapV3TokenLiquidityRecords.owner == address_bytes) @@ -129,32 +258,19 @@ def get(self, wallet_address): .first() ) if not first_holding: - return { - "message": "no provide history", - }, 200 - dt_object = datetime.utcfromtimestamp(first_holding.block_timestamp) - first_provide_time = dt_object.strftime("%Y-%m-%d %H:%M:%S") - return { - "first_provide_time": first_provide_time, - "token_id": str(first_holding.token_id), - "transaction_hash": "0x" + first_holding.transaction_hash.hex(), - "token0_address": "0x" + first_holding.token0_address.hex(), - "token1_address": "0x" + first_holding.token1_address.hex(), - }, 200 + return {} - -@uniswap_v3_namespace.route("/v1/aci//uniswapv3/provide_liquidity_pool_count") -class UniswapV3WalletLiquidityDetail(Resource): - def get(self, wallet_address): - wallet_address = wallet_address.lower() - address_bytes = bytes.fromhex(wallet_address[2:]) - count = ( + pool_count = ( db.session.query(UniswapV3TokenLiquidityRecords.pool_address) .filter(UniswapV3TokenLiquidityRecords.owner == address_bytes) .distinct() .count() ) - return {"provide_pool_count": count}, 200 + return { + "first_provide_time": datetime.fromtimestamp(first_holding.block_timestamp).isoformat("T", "seconds"), + "pool_count": pool_count, + "total_value_usd": 0, + }, 200 def get_tick_at_sqrt_ratio(sqrt_price_x96): @@ -189,3 +305,21 @@ def get_token_amounts(liquidity, sqrt_price_x96, tick_low, tick_high, token0_dec amount0_str = f"{amount0_human:.{token0_decimal}f}" amount1_str = f"{amount1_human:.{token1_decimal}f}" return [amount0_str, amount1_str] + + +def get_swap_action_type(swap: UniswapV3PoolSwapRecords): + token0_address_str = "0x" + swap.token0_address.hex() + token1_address_str = "0x" + swap.token1_address.hex() + + if token0_address_str in STABLE_COINS and token1_address_str in STABLE_COINS: + return "swap" + elif token0_address_str in STABLE_COINS: + return "buy" if swap.amount0 > 0 else "sell" + elif token1_address_str in STABLE_COINS: + return "buy" if swap.amount1 > 0 else "sell" + elif token0_address_str in NATIVE_COINS: + return "buy" if swap.amount0 > 0 else "sell" + elif token1_address_str in NATIVE_COINS: + return "buy" if swap.amount1 > 0 else "sell" + + return "swap" From 6a06c8b70d746e18014d91f26be3b0e56628d4fd Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Wed, 18 Sep 2024 11:15:57 +0800 Subject: [PATCH 55/70] multicall workers (#150) --- indexer/utils/token_fetcher.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indexer/utils/token_fetcher.py b/indexer/utils/token_fetcher.py index 83437b6bd..cbe307088 100644 --- a/indexer/utils/token_fetcher.py +++ b/indexer/utils/token_fetcher.py @@ -60,6 +60,7 @@ def __init__(self, web3, kwargs=None, logger=None): self.batch_size = kwargs["batch_size"] self._is_batch = kwargs["batch_size"] > 1 self._is_multi_call = kwargs["multicall"] + self._works = kwargs["max_workers"] if not self._is_multi_call: self.logger.info("multicall is disabled") self.net = None @@ -297,7 +298,7 @@ def _prepare_token_balance_parameters(self, tokens): @calculate_execution_time def fetch_result(self, chunks): - res = list(make_request_concurrent(self.make_request, chunks)) + res = list(make_request_concurrent(self.make_request, chunks, self._works)) return res @calculate_execution_time From 743457aa950fdd8ad83312e655901cf1cbbc6ed2 Mon Sep 17 00:00:00 2001 From: li xiang Date: Wed, 18 Sep 2024 13:20:12 +0800 Subject: [PATCH 56/70] Feat/add internal transaction address indexer (#144) --- .github/workflows/test.yaml | 2 + api/app/address/routes.py | 57 ++-- common/models/__init__.py | 4 +- enumeration/entity_type.py | 13 +- .../domain/contract_internal_transaction.py | 7 + .../hemera_address_postgres_item_exporter.py | 4 + indexer/jobs/base_job.py | 9 +- .../custom/address_index/address_index_job.py | 254 ++++++++++-------- .../domain/address_contract_operation.py | 32 +++ .../domain/address_internal_transaction.py | 32 +++ .../custom/address_index/endpoint/__init__.py | 0 .../custom/address_index/endpoint/routes.py | 185 +++++++++++++ .../models/address_contract_operation.py | 51 ++++ .../models/address_index_daily_stats.py | 54 ++++ .../models/address_internal_transaciton.py | 51 ++++ .../models/scheduled_metadata.py | 13 + indexer/specification/specification.py | 2 +- indexer/utils/token_fetcher.py | 6 +- 18 files changed, 615 insertions(+), 161 deletions(-) create mode 100644 indexer/modules/custom/address_index/domain/address_contract_operation.py create mode 100644 indexer/modules/custom/address_index/domain/address_internal_transaction.py create mode 100644 indexer/modules/custom/address_index/endpoint/__init__.py create mode 100644 indexer/modules/custom/address_index/endpoint/routes.py create mode 100644 indexer/modules/custom/address_index/models/address_contract_operation.py create mode 100644 indexer/modules/custom/address_index/models/address_index_daily_stats.py create mode 100644 indexer/modules/custom/address_index/models/address_internal_transaciton.py create mode 100644 indexer/modules/custom/address_index/models/scheduled_metadata.py diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index aab32f926..5df56ad3c 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -26,6 +26,8 @@ jobs: run: echo $PYTHONPATH - name: Test with pytest + env: + LINEA_PUBLIC_NODE_RPC_URL: ${{ vars.LINEA_PUBLIC_NODE_RPC_URL }} run: | export PYTHONPATH=$(pwd) make test indexer \ No newline at end of file diff --git a/api/app/address/routes.py b/api/app/address/routes.py index 143ee7f34..1a4cd6a07 100644 --- a/api/app/address/routes.py +++ b/api/app/address/routes.py @@ -12,7 +12,12 @@ from api.app.deposit_to_l2.routes import ACIDepositToL2Current, ACIDepositToL2Transactions from common.models import db from common.utils.exception_control import APIError -from common.utils.format_utils import as_dict +from common.utils.format_utils import as_dict, format_to_dict +from indexer.modules.custom.address_index.endpoint.routes import ( + get_address_contract_operations, + get_address_deploy_contract_count, + get_address_first_deploy_contract_time, +) from indexer.modules.custom.opensea.endpoint.routes import ACIOpenseaProfile, ACIOpenseaTransactions from indexer.modules.custom.uniswap_v3.endpoints.routes import ( UniswapV3WalletLiquidityDetail, @@ -83,49 +88,25 @@ def get(self, address): class ACIContractDeployerProfile(Resource): def get(self, address): address = address.lower() - return {"deployed_countract_count": 353, "first_deployed_time": "2015-10-06T07:56:55+00:00"} + return { + "deployed_countract_count": get_address_deploy_contract_count(address), + "first_deployed_time": get_address_first_deploy_contract_time(address), + } @address_features_namespace.route("/v1/aci/
/contract_deployer/events") class ACIContractDeployerEvents(Resource): def get(self, address): address = address.lower() - - return { - "data": [ - { - "contract_address": "0xfeb016d0d14ac0fa6d69199608b0776d007203b2", - "block_number": "20297335", - "transaction_hash": "0x3a15ac802a8cfc8e2be090fd4d3522ac4382798c82f6a3e3e82716a76f488962", - "block_timestamp": "2024-07-13T11:37:35+00:00", - }, - { - "contract_address": "0x0b19e087493a6ec31661470bd9ba6c49873e97f0", - "block_number": "18325782", - "transaction_hash": "0x9c87cc852d831b39f25d789c79b4ff25d7880202a133d500ced8d59629ab2317", - "block_timestamp": "2023-10-11T07:42:11+00:00", - }, - { - "contract_address": "0xb36082ba6c35490d1e167cc6dd5ad20884a21afb", - "block_number": "17426924", - "transaction_hash": "0xe94f6728f2247bf0157e1cc20e68d862b576e74192775ab7959d079a25ce8512", - "block_timestamp": "2023-06-07T07:09:23+00:00", - }, - { - "contract_address": "0x8e160c8e949967d6b797cdf2a2f38f6344a5c95f", - "block_number": "16553954", - "transaction_hash": "0x9eaf21a7415c44d9c2c925493b35bd518a15ff6d19e0a10d4a95114033c20b65", - "block_timestamp": "2023-02-04T07:32:35+00:00", - }, - { - "contract_address": "0x00a0b1f5be3a7a4b715a2b8d395a76abc0a8e149", - "block_number": "14781402", - "transaction_hash": "0x3839ea3360fcbc0375ea95fd29a8a26c6a2fadd0697aeaaeff4de89512beed8a", - "block_timestamp": "2022-05-15T18:02:18+00:00", - }, - ], - "total": 5, - } + page_index = int(flask.request.args.get("page", 1)) + page_size = int(flask.request.args.get("size", PAGE_SIZE)) + limit = page_size + offset = (page_index - 1) * page_size + events = get_address_contract_operations(address, limit=limit, offset=offset) + res = [] + for event in events: + res.append(format_to_dict(event)) + return {"data": res} @address_features_namespace.route("/v1/aci/
/all_features") diff --git a/common/models/__init__.py b/common/models/__init__.py index 375011649..02f10e184 100644 --- a/common/models/__init__.py +++ b/common/models/__init__.py @@ -4,7 +4,7 @@ from flask_sqlalchemy import SQLAlchemy from psycopg2._json import Json from sqlalchemy import NUMERIC -from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSONB, TIMESTAMP +from sqlalchemy.dialects.postgresql import ARRAY, BYTEA, JSON, JSONB, TIMESTAMP from common.utils.module_loading import import_string, scan_subclass_by_path_patterns from indexer.domain import Domain @@ -54,7 +54,7 @@ def general_converter(table: Type[HemeraModel], data: Domain, is_update=False): converted_data[key] = datetime.utcfromtimestamp(getattr(data, key)) elif isinstance(column_type, ARRAY) and isinstance(column_type.item_type, BYTEA): converted_data[key] = [bytes.fromhex(address[2:]) for address in getattr(data, key)] - elif isinstance(column_type, JSONB) and getattr(data, key) is not None: + elif isinstance(column_type, JSONB) or isinstance(column_type, JSON) and getattr(data, key) is not None: converted_data[key] = Json(getattr(data, key)) elif isinstance(column_type, NUMERIC) and isinstance(getattr(data, key), str): converted_data[key] = None diff --git a/enumeration/entity_type.py b/enumeration/entity_type.py index 5de4d2ed3..79cf45e83 100644 --- a/enumeration/entity_type.py +++ b/enumeration/entity_type.py @@ -3,7 +3,6 @@ from indexer.domain.block import Block, UpdateBlockInternalCount from indexer.domain.block_ts_mapper import BlockTsMapper -from indexer.domain.coin_balance import CoinBalance from indexer.domain.contract import Contract from indexer.domain.contract_internal_transaction import ContractInternalTransaction from indexer.domain.current_token_balance import CurrentTokenBalance @@ -15,12 +14,10 @@ from indexer.domain.trace import Trace from indexer.domain.transaction import Transaction from indexer.modules.custom.address_index.domain import * +from indexer.modules.custom.address_index.domain.address_contract_operation import AddressContractOperation +from indexer.modules.custom.address_index.domain.address_internal_transaction import AddressInternalTransaction from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder -from indexer.modules.custom.all_features_value_record import ( - AllFeatureValueRecordBlueChipHolders, - AllFeatureValueRecordUniswapV3Pool, - AllFeatureValueRecordUniswapV3Token, -) +from indexer.modules.custom.all_features_value_record import AllFeatureValueRecordBlueChipHolders from indexer.modules.custom.blue_chip.domain.feature_blue_chip import BlueChipHolder from indexer.modules.custom.deposit_to_l2.domain.address_token_deposit import AddressTokenDeposit from indexer.modules.custom.deposit_to_l2.domain.token_deposit_transaction import TokenDepositTransaction @@ -123,7 +120,7 @@ def generate_output_types(entity_types): if entity_types & EntityType.EXPLORER_TRACE: yield Trace yield Contract - yield CoinBalance + # yield CoinBalance yield ContractInternalTransaction yield UpdateBlockInternalCount @@ -157,6 +154,8 @@ def generate_output_types(entity_types): yield TokenAddressNftInventory yield AddressTransaction yield AddressNft1155Holder + yield AddressContractOperation + yield AddressInternalTransaction if entity_types & EntityType.BLUE_CHIP: yield Block diff --git a/indexer/domain/contract_internal_transaction.py b/indexer/domain/contract_internal_transaction.py index 2d7de6d5b..8a4a5da7a 100644 --- a/indexer/domain/contract_internal_transaction.py +++ b/indexer/domain/contract_internal_transaction.py @@ -24,6 +24,8 @@ class ContractInternalTransaction(Domain): transaction_index: int transaction_hash: str trace_index: int + input: str + output: str @staticmethod def from_rpc(trace_dict: dict): @@ -45,4 +47,9 @@ def from_rpc(trace_dict: dict): transaction_index=trace_dict["transaction_index"], transaction_hash=trace_dict["transaction_hash"], trace_index=trace_dict["trace_index"], + input=trace_dict["input"], + output=trace_dict["output"], ) + + def is_contract_creation(self): + return self.trace_type == "create" or self.trace_type == "create2" diff --git a/indexer/exporters/hemera_address_postgres_item_exporter.py b/indexer/exporters/hemera_address_postgres_item_exporter.py index 029066ebf..f00cc7739 100644 --- a/indexer/exporters/hemera_address_postgres_item_exporter.py +++ b/indexer/exporters/hemera_address_postgres_item_exporter.py @@ -11,6 +11,8 @@ from indexer.domain.token import Token from indexer.exporters.base_exporter import BaseExporter, group_by_item_type from indexer.modules.custom.address_index.domain import * +from indexer.modules.custom.address_index.domain.address_contract_operation import AddressContractOperation +from indexer.modules.custom.address_index.domain.address_internal_transaction import AddressInternalTransaction from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder logger = logging.getLogger(__name__) @@ -27,6 +29,8 @@ class HemeraAddressPostgresItemExporter(BaseExporter): AddressTokenHolder, Token, TokenAddressNftInventory, + AddressContractOperation, + AddressInternalTransaction, ] def __init__(self, output, chain_id): diff --git a/indexer/jobs/base_job.py b/indexer/jobs/base_job.py index f545ee6dc..a611f1a1c 100644 --- a/indexer/jobs/base_job.py +++ b/indexer/jobs/base_job.py @@ -8,6 +8,7 @@ from common.converter.pg_converter import domain_model_mapping from common.utils.exception_control import FastShutdownError from common.utils.format_utils import to_snake_case +from indexer.domain import Domain from indexer.utils.reorg import should_reorg @@ -150,7 +151,13 @@ def _collect_domains(self, domains): self._collect_domain(domain) def _get_domain(self, domain): - return self._data_buff[domain.type()] + return self._data_buff[domain.type()] if domain.type() in self._data_buff else [] + + def _get_domains(self, domains: list[Domain]): + res = [] + for domain in domains: + res += self._data_buff[domain.type()] + return res def _process(self, **kwargs): pass diff --git a/indexer/modules/custom/address_index/address_index_job.py b/indexer/modules/custom/address_index/address_index_job.py index cfdc32e21..a3bf49b4d 100644 --- a/indexer/modules/custom/address_index/address_index_job.py +++ b/indexer/modules/custom/address_index/address_index_job.py @@ -1,11 +1,9 @@ -import json import logging from enum import Enum from itertools import groupby from typing import List, Union -from eth_abi import abi - +from indexer.domain.contract_internal_transaction import ContractInternalTransaction from indexer.domain.token_id_infos import UpdateERC721TokenIdDetail from indexer.domain.token_transfer import ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer from indexer.domain.transaction import Transaction @@ -13,19 +11,26 @@ from indexer.jobs.base_job import ExtensionJob from indexer.jobs.export_token_balances_job import extract_token_parameters from indexer.jobs.export_token_id_infos_job import generate_token_id_info +from indexer.modules.custom.address_index.domain.address_contract_operation import AddressContractOperation +from indexer.modules.custom.address_index.domain.address_internal_transaction import AddressInternalTransaction from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder from indexer.modules.custom.address_index.domain.address_nft_transfer import AddressNftTransfer from indexer.modules.custom.address_index.domain.address_token_holder import AddressTokenHolder from indexer.modules.custom.address_index.domain.address_token_transfer import AddressTokenTransfer from indexer.modules.custom.address_index.domain.address_transaction import AddressTransaction from indexer.modules.custom.address_index.domain.token_address_nft_inventory import TokenAddressNftInventory -from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc_without_block_number from indexer.utils.token_fetcher import TokenFetcher -from indexer.utils.utils import ZERO_ADDRESS, rpc_response_to_result, zip_rpc_response +from indexer.utils.utils import ZERO_ADDRESS logger = logging.getLogger(__name__) +class InternalTransactionType(Enum): + SELF_CALL = 0 + SENDER = 1 + RECEIVER = 2 + + class AddressTransactionType(Enum): SELF_CALL = 0 @@ -56,6 +61,100 @@ class AddressNftTransferType(Enum): MINTER = 4 +def create_address_internal_transaction( + internal_transaction: ContractInternalTransaction, + address: str, + txn_type: int, + related_address: str, + transaction_receipt_status: int, +): + yield AddressInternalTransaction( + address=address, + trace_id=internal_transaction.trace_id, + block_number=internal_transaction.block_number, + transaction_index=internal_transaction.transaction_index, + transaction_hash=internal_transaction.transaction_hash, + block_timestamp=internal_transaction.block_timestamp, + block_hash=internal_transaction.block_hash, + error=internal_transaction.error, + status=int(internal_transaction.status or 0), + input_method=(internal_transaction.input or "")[2:10], + value=internal_transaction.value, + gas=internal_transaction.gas, + gas_used=internal_transaction.gas_used, + trace_type=internal_transaction.trace_type, + call_type=internal_transaction.call_type, + txn_type=txn_type, + related_address=related_address, + transaction_receipt_status=transaction_receipt_status, + ) + + +def create_address_contract_operation( + internal_transaction: ContractInternalTransaction, + address: str, + contract_address: str, + transaction_receipt_status: int, +): + yield AddressContractOperation( + address=address, + trace_from_address=internal_transaction.from_address, + contract_address=contract_address, + trace_id=internal_transaction.trace_id, + block_number=internal_transaction.block_number, + transaction_index=internal_transaction.transaction_index, + transaction_hash=internal_transaction.transaction_hash, + block_timestamp=internal_transaction.block_timestamp, + block_hash=internal_transaction.block_hash, + error=internal_transaction.error, + status=int(internal_transaction.status or 0), + creation_code=internal_transaction.input, + deployed_code=internal_transaction.output, + gas=internal_transaction.gas, + gas_used=internal_transaction.gas_used, + trace_type=internal_transaction.trace_type, + call_type=internal_transaction.call_type, + transaction_receipt_status=transaction_receipt_status, + ) + + +def internal_transactions_to_address_internal_transactions( + internal_transactions: List[ContractInternalTransaction], transaction_dict: dict[str, Transaction] +) -> list[Union[AddressInternalTransaction, AddressContractOperation]]: + for internal_transaction in internal_transactions: + if internal_transaction.from_address != internal_transaction.to_address: + yield from create_address_internal_transaction( + internal_transaction, + internal_transaction.from_address, + InternalTransactionType.SENDER.value, + internal_transaction.to_address, + transaction_dict[internal_transaction.transaction_hash].receipt.status, + ) + if internal_transaction.is_contract_creation(): + yield from create_address_contract_operation( + internal_transaction, + transaction_dict[internal_transaction.transaction_hash].from_address, + internal_transaction.to_address, + transaction_dict[internal_transaction.transaction_hash].receipt.status, + ) + if internal_transaction.to_address is not None: + yield from create_address_internal_transaction( + internal_transaction, + internal_transaction.to_address, + InternalTransactionType.RECEIVER.value, + internal_transaction.from_address, + transaction_dict[internal_transaction.transaction_hash].receipt.status, + ) + else: + yield from create_address_internal_transaction( + internal_transaction, + internal_transaction.from_address, + InternalTransactionType.SELF_CALL.value, + internal_transaction.to_address, + transaction_dict[internal_transaction.transaction_hash].receipt.status, + ) + + def create_address_transaction(transaction, address, txn_type, related_address, transaction_fee): return AddressTransaction( address=address, @@ -216,6 +315,8 @@ class AddressIndexerJob(ExtensionJob): AddressNftTransfer, AddressTokenHolder, AddressNft1155Holder, + AddressInternalTransaction, + AddressContractOperation, ] def __init__(self, **kwargs): @@ -228,47 +329,7 @@ def __init__(self, **kwargs): self.token_fetcher = TokenFetcher(self._web3, kwargs) self._is_multi_call = kwargs["multicall"] - def _collect_all_token_transfers(self): - token_transfers = [] - if ERC20TokenTransfer.type() in self._data_buff: - token_transfers += self._data_buff[ERC20TokenTransfer.type()] - - if ERC721TokenTransfer.type() in self._data_buff: - token_transfers += self._data_buff[ERC721TokenTransfer.type()] - - if ERC1155TokenTransfer.type() in self._data_buff: - token_transfers += self._data_buff[ERC1155TokenTransfer.type()] - - return token_transfers - - def _collect(self, **kwargs): - token_transfers = self._collect_all_token_transfers() - - all_token_parameters = extract_token_parameters(token_transfers, "latest") - all_token_parameters.sort(key=lambda x: (x["address"], x["token_address"], x.get("token_id") or 0)) - parameters = [ - list(group)[-1] - for key, group in groupby(all_token_parameters, lambda x: (x["address"], x["token_address"], x["token_id"])) - ] - - all_owner_parameters = generate_token_id_info(self._data_buff[ERC721TokenTransfer.type()], [], "latest") - all_owner_parameters.sort(key=lambda x: (x["address"], x["token_id"])) - owner_parameters = [ - list(group)[-1] for key, group in groupby(all_owner_parameters, lambda x: (x["address"], x["token_id"])) - ] - - if self._is_multi_call: - self._collect_balance_batch(parameters) - self._collect_owner_batch(owner_parameters) - else: - self._batch_work_executor.execute(parameters, self._collect_balance_batch, total_items=len(parameters)) - self._batch_work_executor.wait() - self._batch_work_executor.execute( - owner_parameters, self._collect_owner_batch, total_items=len(owner_parameters) - ) - self._batch_work_executor.wait() - - def _collect_owner_batch(self, token_list): + def __collect_owner_batch(self, token_list): items = self.token_fetcher.fetch_token_ids_info(token_list) update_erc721_token_id_details = [] for item in items: @@ -290,7 +351,7 @@ def _collect_owner_batch(self, token_list): ) ) - def _collect_balance_batch(self, parameters): + def __collect_balance_batch(self, parameters): token_balances = self.token_fetcher.fetch_token_balance(parameters) token_balances.sort(key=lambda x: (x["address"], x["token_address"])) for token_balance in token_balances: @@ -312,10 +373,42 @@ def _collect_balance_batch(self, parameters): ) ) + def _collect(self, **kwargs): + # Get all token transfers + token_transfers = self._get_domains([ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer]) + + # Generate token transfer parameters + all_token_parameters = extract_token_parameters(token_transfers, "latest") + all_token_parameters.sort(key=lambda x: (x["address"], x["token_address"], x.get("token_id") or 0)) + parameters = [ + list(group)[-1] + for key, group in groupby(all_token_parameters, lambda x: (x["address"], x["token_address"], x["token_id"])) + ] + + # Generate token owner parameters + all_owner_parameters = generate_token_id_info(self._data_buff[ERC721TokenTransfer.type()], [], "latest") + all_owner_parameters.sort(key=lambda x: (x["address"], x["token_id"])) + owner_parameters = [ + list(group)[-1] for key, group in groupby(all_owner_parameters, lambda x: (x["address"], x["token_id"])) + ] + + if self._is_multi_call: + self.__collect_balance_batch(parameters) + self.__collect_owner_batch(owner_parameters) + else: + self._batch_work_executor.execute(parameters, self.__collect_balance_batch, total_items=len(parameters)) + self._batch_work_executor.wait() + self._batch_work_executor.execute( + owner_parameters, self.__collect_owner_batch, total_items=len(owner_parameters) + ) + self._batch_work_executor.wait() + def _process(self, **kwargs): transactions = self._get_domain(Transaction) self._collect_domains(list(transactions_to_address_transactions(transactions))) + transaction_dict = {transaction.hash: transaction for transaction in transactions} + token_transfers = self._get_domain(ERC20TokenTransfer) self._collect_domains(list(erc20_transfers_to_address_token_transfers(token_transfers))) @@ -325,68 +418,7 @@ def _process(self, **kwargs): erc1155_transfers = self._get_domain(ERC1155TokenTransfer) self._collect_domains(list(nft_transfers_to_address_nft_transfers(erc1155_transfers))) - -def token_owner_rpc_requests(make_requests, tokens, is_batch): - for idx, token in enumerate(tokens): - token["request_id"] = idx - - token_balance_rpc = list(generate_eth_call_json_rpc_without_block_number(tokens)) - - if is_batch: - response = make_requests(params=json.dumps(token_balance_rpc)) - else: - response = [make_requests(params=json.dumps(token_balance_rpc[0]))] - - token_owners = [] - for data in list(zip_rpc_response(tokens, response)): - result = rpc_response_to_result(data[1]) - wallet_address = None - - try: - if result: - wallet_address = abi.decode(["address"], bytes.fromhex(result[2:]))[0] - except Exception as e: - logger.warning(f"Decoding token balance value failed. " f"rpc response: {result}. " f"exception: {e}. ") - - token_owners.append( - { - "token_id": data[0]["token_id"], - "token_address": data[0]["token_address"].lower(), - "wallet_address": wallet_address, - } + internal_transactions = self._get_domain(ContractInternalTransaction) + self._collect_domains( + list(internal_transactions_to_address_internal_transactions(internal_transactions, transaction_dict)) ) - - return token_owners - - -def token_balances_rpc_requests(make_requests, tokens, is_batch): - for idx, token in enumerate(tokens): - token["request_id"] = idx - - token_balance_rpc = list(generate_eth_call_json_rpc_without_block_number(tokens)) - - if is_batch: - response = make_requests(params=json.dumps(token_balance_rpc)) - else: - response = [make_requests(params=json.dumps(token_balance_rpc[0]))] - - token_balances = [] - for data in list(zip_rpc_response(tokens, response)): - result = rpc_response_to_result(data[1]) - balance = None - - try: - if result: - balance = abi.decode(["uint256"], bytes.fromhex(result[2:]))[0] - except Exception as e: - logger.warning(f"Decoding token balance value failed. " f"rpc response: {result}. " f"exception: {e}. ") - - token_balances.append( - { - "address": data[0]["address"].lower(), - "token_address": data[0]["token_address"].lower(), - "balance_of": balance, - } - ) - - return token_balances diff --git a/indexer/modules/custom/address_index/domain/address_contract_operation.py b/indexer/modules/custom/address_index/domain/address_contract_operation.py new file mode 100644 index 000000000..020823352 --- /dev/null +++ b/indexer/modules/custom/address_index/domain/address_contract_operation.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass + +from indexer.domain import Domain + + +@dataclass +class AddressContractOperation(Domain): + address: str + + trace_from_address: str + contract_address: str + + trace_id: str + block_number: int + transaction_index: int + transaction_hash: str + block_timestamp: int + block_hash: str + + error: str + status: int + + creation_code: str + deployed_code: str + + gas: int + gas_used: int + + trace_type: str + call_type: str + + transaction_receipt_status: int diff --git a/indexer/modules/custom/address_index/domain/address_internal_transaction.py b/indexer/modules/custom/address_index/domain/address_internal_transaction.py new file mode 100644 index 000000000..713d8ef7d --- /dev/null +++ b/indexer/modules/custom/address_index/domain/address_internal_transaction.py @@ -0,0 +1,32 @@ +from dataclasses import dataclass + +from indexer.domain import Domain + + +@dataclass +class AddressInternalTransaction(Domain): + address: str + + trace_id: str + block_number: int + transaction_index: int + transaction_hash: str + block_timestamp: int + block_hash: str + + error: str + status: int + + input_method: str + + value: int + gas: int + gas_used: int + + trace_type: str + call_type: str + + txn_type: int + related_address: str + + transaction_receipt_status: int diff --git a/indexer/modules/custom/address_index/endpoint/__init__.py b/indexer/modules/custom/address_index/endpoint/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/address_index/endpoint/routes.py b/indexer/modules/custom/address_index/endpoint/routes.py new file mode 100644 index 000000000..adfa2c06b --- /dev/null +++ b/indexer/modules/custom/address_index/endpoint/routes.py @@ -0,0 +1,185 @@ +from datetime import datetime +from typing import List, Union + +from sqlalchemy import and_, func + +from api.app.address.models import ScheduledMetadata +from common.models import db +from common.utils.format_utils import as_dict, format_to_dict +from indexer.modules.custom.address_index.models.address_contract_operation import AddressContractOperations +from indexer.modules.custom.address_index.models.address_index_daily_stats import AddressIndexDailyStats + +PAGE_SIZE = 10 + + +def get_address_first_deploy_contract_time(address: Union[str, bytes]) -> datetime: + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + first_deploy_contract = ( + db.session.query(AddressContractOperations.block_timestamp) + .filter( + AddressContractOperations.address == address, + AddressContractOperations.trace_type.in_(["create", "create2"]), + ) + .order_by(AddressContractOperations.block_timestamp) + .first() + ) + return format_to_dict(first_deploy_contract[0]) if first_deploy_contract else None + + +def get_address_deploy_contract_count(address: Union[str, bytes]) -> int: + last_timestamp = db.session.query(func.max(ScheduledMetadata.last_data_timestamp)).scalar() + if not last_timestamp: + return get_address_deploy_contract_count_before_date(address) + else: + return get_address_hist_deploy_contract_count( + address, end_time=last_timestamp + ) + get_address_deploy_contract_count_before_date(address, start_time=last_timestamp) + + +def get_address_hist_deploy_contract_count( + address: Union[str, bytes], start_time: datetime = None, end_time: datetime = None +) -> int: + return get_address_hist_contract_stats(address, start_time, end_time).get("contract_creation_count", 0) + + +def get_address_deploy_contract_count_before_date(address: Union[str, bytes], start_time: datetime = None) -> int: + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + if start_time: + count = ( + db.session.query(AddressContractOperations) + .filter( + AddressContractOperations.address == address, + AddressContractOperations.block_timestamp >= start_time, + AddressContractOperations.trace_type in ["create", "create2"], + ) + .count() + ) + else: + count = db.session.query(AddressContractOperations).filter(AddressContractOperations.address == address).count() + return count + + +def get_address_contract_operations(address: Union[str, bytes], limit=5, offset=0) -> list[dict]: + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + transactions = ( + db.session.query(AddressContractOperations) + .order_by( + AddressContractOperations.block_number.desc(), + AddressContractOperations.transaction_index.desc(), + AddressContractOperations.trace_id.desc(), + ) + .filter(AddressContractOperations.address == address) + .limit(limit) + .offset(offset) + .all() + ) + return transactions + + +def get_address_hist_transaction_stats( + address: Union[str, bytes], start_time: datetime = None, end_time: datetime = None +): + return get_address_hist_stats( + address, + [ + "transaction_count", + "transaction_in_count", + "transaction_out_count", + "transaction_self_count", + "transaction_in_value", + "transaction_out_value", + "transaction_self_value", + "transaction_in_fee", + "transaction_out_fee", + "transaction_self_fee", + ], + start_time, + end_time, + ) + + +def get_address_hist_internal_transaction_stats( + address: Union[str, bytes], start_time: datetime = None, end_time: datetime = None +): + return get_address_hist_stats( + address, + [ + "internal_transaction_count", + "internal_transaction_in_count", + "internal_transaction_out_count", + "internal_transaction_self_count", + "internal_transaction_in_value", + "internal_transaction_out_value", + "internal_transaction_self_value", + ], + start_time, + end_time, + ) + + +def get_address_hist_token_transfer_stats( + address: Union[str, bytes], start_time: datetime = None, end_time: datetime = None +): + return get_address_hist_stats( + address, + [ + "erc20_transfer_count", + "erc20_transfer_in_count", + "erc20_transfer_out_count", + "erc20_transfer_self_count", + "nft_transfer_count", + "nft_transfer_in_count", + "nft_transfer_out_count", + "nft_transfer_self_count", + ], + start_time, + end_time, + ) + + +def get_address_hist_contract_stats(address: Union[str, bytes], start_time: datetime = None, end_time: datetime = None): + return get_address_hist_stats( + address, + [ + "contract_creation_count", + "contract_destruction_count", + "contract_operation_count", + ], + start_time, + end_time, + ) + + +def get_address_hist_stats( + address: Union[str, bytes], attr: Union[str, List[str]], start_time: datetime = None, end_time: datetime = None +): + if isinstance(address, str): + address = bytes.fromhex(address[2:]) + + if isinstance(attr, str): + attr = [attr] + + query = db.session.query(AddressIndexDailyStats.block_date) + + for a in attr: + if hasattr(AddressIndexDailyStats, a): + query = query.add_columns(func.sum(getattr(AddressIndexDailyStats, a)).label(a)) + else: + raise ValueError(f"Invalid attribute: {a}") + + filters = [AddressIndexDailyStats.address == address] + if start_time: + filters.append(AddressIndexDailyStats.block_date >= start_time.date()) + if end_time: + filters.append(AddressIndexDailyStats.block_date <= end_time.date()) + + query = query.filter(and_(*filters)) + + query = query.group_by(AddressIndexDailyStats.block_date) + + result = query.one_or_none() + + return as_dict(result) diff --git a/indexer/modules/custom/address_index/models/address_contract_operation.py b/indexer/modules/custom/address_index/models/address_contract_operation.py new file mode 100644 index 000000000..91f26f39b --- /dev/null +++ b/indexer/modules/custom/address_index/models/address_contract_operation.py @@ -0,0 +1,51 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, desc, func +from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, INTEGER, NUMERIC, SMALLINT, TEXT, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class AddressContractOperations(HemeraModel): + __tablename__ = "address_contract_operations" + + address = Column(BYTEA, primary_key=True) + trace_from_address = Column(BYTEA) + contract_address = Column(BYTEA) + trace_id = Column(TEXT, primary_key=True) + block_number = Column(INTEGER, primary_key=True) + transaction_index = Column(INTEGER, primary_key=True) + transaction_hash = Column(BYTEA) + block_timestamp = Column(TIMESTAMP, primary_key=True) + block_hash = Column(BYTEA) + error = Column(TEXT) + status = Column(INTEGER) + creation_code = Column(BYTEA) + deployed_code = Column(BYTEA) + gas = Column(NUMERIC(100)) + gas_used = Column(NUMERIC(100)) + trace_type = Column(TEXT) + call_type = Column(TEXT) + transaction_receipt_status = Column(INTEGER) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "AddressContractOperation", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] + + +Index( + "address_contract_operations_address_block_timestamp_block_number_t_idx", + AddressContractOperations.address, + desc(AddressContractOperations.block_timestamp), + desc(AddressContractOperations.block_number), + desc(AddressContractOperations.transaction_index), +) diff --git a/indexer/modules/custom/address_index/models/address_index_daily_stats.py b/indexer/modules/custom/address_index/models/address_index_daily_stats.py new file mode 100644 index 000000000..c6dd7cc99 --- /dev/null +++ b/indexer/modules/custom/address_index/models/address_index_daily_stats.py @@ -0,0 +1,54 @@ +from sqlalchemy import DATE, Column +from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, INTEGER, NUMERIC + +from common.models import HemeraModel + + +class AddressIndexDailyStats(HemeraModel): + __tablename__ = "af_index_daily_stats" + + address = Column(BYTEA, primary_key=True) + block_date = Column(DATE, primary_key=True) + + transaction_in_count = Column(INTEGER) + transaction_out_count = Column(INTEGER) + transaction_self_count = Column(INTEGER) + + transaction_in_value = Column(BIGINT) + transaction_out_value = Column(BIGINT) + transaction_self_value = Column(BIGINT) + + transaction_in_fee = Column(NUMERIC) + transaction_out_fee = Column(NUMERIC) + transaction_self_fee = Column(NUMERIC) + + internal_transaction_in_count = Column(INTEGER) + internal_transaction_out_count = Column(INTEGER) + internal_transaction_self_count = Column(INTEGER) + + internal_transaction_in_value = Column(BIGINT) + internal_transaction_out_value = Column(BIGINT) + internal_transaction_self_value = Column(BIGINT) + + erc20_transfer_in_count = Column(INTEGER) + erc20_transfer_out_count = Column(INTEGER) + erc20_transfer_self_count = Column(INTEGER) + + nft_transfer_in_count = Column(INTEGER) + nft_transfer_out_count = Column(INTEGER) + nft_transfer_self_count = Column(INTEGER) + + contract_creation_count = Column(INTEGER) + contract_destruction_count = Column(INTEGER) + contract_operation_count = Column(INTEGER) + + transaction_count = Column( + INTEGER, + ) + internal_transaction_count = Column( + INTEGER, + ) + erc20_transfer_count = Column( + INTEGER, + ) + nft_transfer_count = Column(INTEGER) diff --git a/indexer/modules/custom/address_index/models/address_internal_transaciton.py b/indexer/modules/custom/address_index/models/address_internal_transaciton.py new file mode 100644 index 000000000..a2e97735a --- /dev/null +++ b/indexer/modules/custom/address_index/models/address_internal_transaciton.py @@ -0,0 +1,51 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, desc, func +from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, INTEGER, NUMERIC, SMALLINT, TEXT, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class AddressInternalTransactions(HemeraModel): + __tablename__ = "address_internal_transactions" + + address = Column(BYTEA, primary_key=True) + trace_id = Column(TEXT, primary_key=True) + block_number = Column(INTEGER, primary_key=True) + transaction_index = Column(INTEGER, primary_key=True) + transaction_hash = Column(BYTEA) + block_timestamp = Column(TIMESTAMP, primary_key=True) + block_hash = Column(BYTEA) + error = Column(TEXT) + status = Column(INTEGER) + input_method = Column(TEXT) + value = Column(NUMERIC(100)) + gas = Column(NUMERIC(100)) + gas_used = Column(NUMERIC(100)) + trace_type = Column(TEXT) + call_type = Column(TEXT) + txn_type = Column(SMALLINT) + related_address = Column(BYTEA) + transaction_receipt_status = Column(INTEGER) + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "AddressInternalTransaction", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] + + +Index( + "address_internal_transactions_address_block_timestamp_block_number_t_idx", + AddressInternalTransactions.address, + desc(AddressInternalTransactions.block_timestamp), + desc(AddressInternalTransactions.block_number), + desc(AddressInternalTransactions.transaction_index), +) diff --git a/indexer/modules/custom/address_index/models/scheduled_metadata.py b/indexer/modules/custom/address_index/models/scheduled_metadata.py new file mode 100644 index 000000000..22b29e67d --- /dev/null +++ b/indexer/modules/custom/address_index/models/scheduled_metadata.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column +from sqlalchemy.dialects.postgresql import INTEGER, TIMESTAMP, VARCHAR + +from common.models import HemeraModel + + +class ScheduledMetadata(HemeraModel): + __tablename__ = "af_index_na_scheduled_metadata" + + id = Column(INTEGER, primary_key=True) + dag_id = Column(VARCHAR) + execution_date = Column(TIMESTAMP) + last_data_timestamp = Column(TIMESTAMP) diff --git a/indexer/specification/specification.py b/indexer/specification/specification.py index 67377078e..3c517e9da 100644 --- a/indexer/specification/specification.py +++ b/indexer/specification/specification.py @@ -105,7 +105,7 @@ def to_filter_params(self): class TransactionHashSpecification(Specification): def __init__(self, hashes: List[str]): - self.hashes = hashes + self.hashes = set(hashes) def is_satisfied_by(self, item: Transaction): return item.hash in self.hashes diff --git a/indexer/utils/token_fetcher.py b/indexer/utils/token_fetcher.py index cbe307088..53a6c673b 100644 --- a/indexer/utils/token_fetcher.py +++ b/indexer/utils/token_fetcher.py @@ -151,7 +151,11 @@ def create_token_detail(self, token_info, value, decode_flag): try: if token_info["is_get_token_uri"]: - token_uri = decode_string(value) if decode_flag else value + try: + token_uri = decode_string(value) if decode_flag else None + except Exception as e: + token_uri = None + logging.error(f"decode token uri failed, token_info={token_info}, value={value}") if token_info["token_type"] == "ERC721": return [ERC721TokenIdDetail(**common_args, token_uri=token_uri)] else: From a928d3c953d79b99c730f37924dcca0386fe47da Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:20:51 +0800 Subject: [PATCH 57/70] Add Agni Feature (#138) --- .../modules/custom/uniswap_v3/agni_config.ini | 3 + .../custom/uniswap_v3/agni_pool_job.py | 322 +++++++++++ .../custom/uniswap_v3/agni_token_job.py | 523 ++++++++++++++++++ .../modules/custom/uniswap_v3/constants.py | 130 +++++ .../uniswap_v3/domain/feature_uniswap_v3.py | 45 ++ .../feature_uniswap_v3_collect_fee_records.py | 6 + .../feature_uniswap_v3_liquidity_records.py | 6 + .../feature_uniswap_v3_pool_current_prices.py | 6 + .../models/feature_uniswap_v3_pool_prices.py | 6 + .../models/feature_uniswap_v3_pools.py | 8 +- .../models/feature_uniswap_v3_swap_records.py | 6 + ...feature_uniswap_v3_token_current_status.py | 6 + .../feature_uniswap_v3_token_details.py | 6 + .../models/feature_uniswap_v3_tokens.py | 8 +- 14 files changed, 1079 insertions(+), 2 deletions(-) create mode 100644 indexer/modules/custom/uniswap_v3/agni_config.ini create mode 100644 indexer/modules/custom/uniswap_v3/agni_pool_job.py create mode 100644 indexer/modules/custom/uniswap_v3/agni_token_job.py diff --git a/indexer/modules/custom/uniswap_v3/agni_config.ini b/indexer/modules/custom/uniswap_v3/agni_config.ini new file mode 100644 index 000000000..25a7458fc --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/agni_config.ini @@ -0,0 +1,3 @@ +[5000] +nft_address = 0x218bf598d1453383e2f4aa7b14ffb9bfb102d637 +factory_address = 0x25780dc8fc3cfbd75f33bfdab65e969b603b2035 \ No newline at end of file diff --git a/indexer/modules/custom/uniswap_v3/agni_pool_job.py b/indexer/modules/custom/uniswap_v3/agni_pool_job.py new file mode 100644 index 000000000..1cc0f2070 --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/agni_pool_job.py @@ -0,0 +1,322 @@ +import configparser +import json +import logging +import os +from collections import defaultdict +from dataclasses import fields +from itertools import groupby +from operator import attrgetter +from typing import Dict, List + +import eth_abi +from web3 import Web3 + +from indexer.domain.log import Log +from indexer.domain.transaction import Transaction +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.uniswap_v3 import constants, util +from indexer.modules.custom.uniswap_v3.constants import AGNI_ABI +from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import ( + AgniV3Pool, + AgniV3PoolCurrentPrice, + AgniV3PoolPrice, + AgniV3SwapEvent, +) +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) + + +class AgniPoolJob(FilterTransactionDataJob): + dependency_types = [Transaction, Log] + output_types = [ + AgniV3Pool, + AgniV3PoolPrice, + AgniV3PoolCurrentPrice, + AgniV3SwapEvent, + ] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._service = kwargs["config"].get("db_service") + self._chain_id = common_utils.get_chain_id(self._web3) + self._load_config("agni_config.ini", self._chain_id) + self._exist_pools = get_exist_pools(self._service, self._position_token_address) + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + self._abi_list = AGNI_ABI + self._create_pool_topic0 = constants.UNISWAP_V3_CREATE_POOL_TOPIC0 + self._pool_price_topic0_list = constants.AGNI_POOL_PRICE_TOPIC0_LIST + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(addresses=[self._factory_address], topics=[self._create_pool_topic0]), + TopicSpecification(topics=self._pool_price_topic0_list), + ] + ) + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + chain_id_str = str(chain_id) + try: + chain_config = config[chain_id_str] + except KeyError: + return + try: + self._position_token_address = chain_config.get("nft_address").lower() + self._factory_address = chain_config.get("factory_address").lower() + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + def _collect(self, **kwargs): + logs = self._data_buff[Log.type()] + self._batch_work_executor.execute(logs, self._collect_pool_batch, len(logs)) + self._batch_work_executor.wait() + + collected_pools = self._data_buff[AgniV3Pool.type()] + for data in collected_pools: + self._exist_pools[data.pool_address] = data + transactions = self._data_buff[Transaction.type()] + self._transaction_hash_from_dict = {} + for transaction in transactions: + self._transaction_hash_from_dict[transaction.hash] = transaction.from_address + self._batch_work_executor.execute(logs, self._collect_price_batch, len(logs)) + self._batch_work_executor.wait() + self._transaction_hash_from_dict = {} + self._process_current_pool_prices() + + def _collect_pool_batch(self, logs): + for log in logs: + address = log.address + current_topic0 = log.topic0 + if self._factory_address != address or self._create_pool_topic0 != current_topic0: + continue + entity = decode_pool_created(self._position_token_address, self._factory_address, log) + self._collect_item(AgniV3Pool.type(), entity) + + def _collect_price_batch(self, logs): + unique_logs = set() + for log in logs: + if log.address not in self._exist_pools: + continue + # Collect swap logs + if log.topic0 == constants.UNISWAP_V3_POOL_SWAP_TOPIC0: + transaction_hash = log.transaction_hash + part1, part2, part3, part4, part5 = split_swap_data_hex_string(log.data) + amount0 = util.parse_hex_to_int256(part1) + amount1 = util.parse_hex_to_int256(part2) + sqrt_price_x96 = util.parse_hex_to_int256(part3) + liquidity = util.parse_hex_to_int256(part4) + tick = util.parse_hex_to_int256(part5) + pool_data = self._exist_pools[log.address] + self._collect_item( + AgniV3SwapEvent.type(), + AgniV3SwapEvent( + pool_address=log.address, + position_token_address=self._position_token_address, + transaction_hash=transaction_hash, + transaction_from_address=self._transaction_hash_from_dict[transaction_hash], + log_index=log.log_index, + block_number=log.block_number, + block_timestamp=log.block_timestamp, + sender=util.parse_hex_to_address(log.topic1), + recipient=util.parse_hex_to_address(log.topic2), + amount0=amount0, + amount1=amount1, + liquidity=liquidity, + tick=tick, + sqrt_price_x96=sqrt_price_x96, + token0_address=pool_data.token0_address, + token1_address=pool_data.token1_address, + ), + ) + log_tuple = (log.address, log.block_number, log.block_timestamp) + unique_logs.add(log_tuple) + requests = [ + {"pool_address": address, "block_number": block_number, "block_timestamp": block_timestamp} + for address, block_number, block_timestamp in unique_logs + ] + pool_prices = slot0_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + requests, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + for data in pool_prices: + detail = AgniV3PoolPrice( + factory_address=self._factory_address, + pool_address=data["pool_address"], + sqrt_price_x96=data["sqrtPriceX96"], + tick=data["tick"], + block_number=data["block_number"], + block_timestamp=data["block_timestamp"], + ) + self._collect_item(AgniV3PoolPrice.type(), detail) + + def _process(self, **kwargs): + self._data_buff[AgniV3Pool.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3PoolPrice.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3PoolCurrentPrice.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3SwapEvent.type()].sort(key=lambda x: x.block_number) + + def _process_current_pool_prices(self): + prices = self._data_buff[AgniV3PoolPrice.type()] + self._data_buff[AgniV3PoolPrice.type()] = [] + unique_prices = {} + for price in prices: + key = (price.pool_address, price.block_number) + unique_prices[key] = price + + for price in unique_prices.values(): + self._collect_item(AgniV3PoolPrice.type(), price) + + sorted_prices = sorted(unique_prices.values(), key=lambda x: (x.pool_address, x.block_number)) + current_prices = [ + max(group, key=attrgetter("block_number")) + for _, group in groupby(sorted_prices, key=attrgetter("pool_address")) + ] + for data in current_prices: + self._collect_item(AgniV3PoolCurrentPrice.type(), self.create_current_price_status(data)) + + @staticmethod + def create_current_price_status(detail: AgniV3PoolPrice) -> AgniV3PoolPrice: + return AgniV3PoolCurrentPrice( + **{field.name: getattr(detail, field.name) for field in fields(AgniV3PoolCurrentPrice)} + ) + + +def decode_pool_created(position_token_address, factory_address, log): + token0_address = util.parse_hex_to_address(log.topic1) + token1_address = util.parse_hex_to_address(log.topic2) + fee = util.parse_hex_to_int256(log.topic3) + tick_hex, pool_hex = split_hex_string(log.data) + pool_address = util.parse_hex_to_address(pool_hex) + tick_spacing = util.parse_hex_to_int256(tick_hex) + return AgniV3Pool( + position_token_address=position_token_address, + factory_address=factory_address, + pool_address=pool_address, + token0_address=token0_address, + token1_address=token1_address, + fee=fee, + tick_spacing=tick_spacing, + block_number=log.block_number, + block_timestamp=log.block_timestamp, + ) + + +def split_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + + if len(hex_string) == 128: + part1 = hex_string[:64] + part2 = hex_string[64:] + return part1, part2 + else: + raise ValueError("The data is not belong to Agni Factory") + + +def get_exist_pools(db_service, position_token_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Pools) + .filter(UniswapV3Pools.position_token_address == bytes.fromhex(position_token_address[2:])) + .all() + ) + history_pools = {} + if result is not None: + for item in result: + pool_key = "0x" + item.pool_address.hex() + history_pools[pool_key] = AgniV3Pool( + position_token_address="0x" + item.position_token_address.hex(), + pool_address=pool_key, + token0_address="0x" + item.token0_address.hex(), + token1_address="0x" + item.token1_address.hex(), + factory_address="0x" + item.factory_address.hex(), + fee=item.fee, + tick_spacing=item.tick_spacing, + block_number=item.block_number, + block_timestamp=item.block_timestamp, + ) + except Exception as e: + raise e + finally: + session.close() + + return history_pools + + +def slot0_rpc_requests(web3, make_requests, requests, is_batch, abi_list, batch_size, max_worker): + if len(requests) == 0: + return [] + fn_name = "slot0" + function_abi = next((abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), None) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = common_utils.build_no_input_method_data(web3, requests, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + pool = data[0] + value = result[2:] if result is not None else None + try: + part1, part2 = get_price_and_tick_from_hex(value) + pool["sqrtPriceX96"] = part1 + pool["tick"] = part2 + except Exception as e: + logger.error(f"Decoding {fn_name} failed. " f"rpc response: {result}. " f"exception: {e}") + token_infos.append(pool) + return token_infos + + +def get_price_and_tick_from_hex(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + part1 = hex_string[:64] + part2 = hex_string[64:128] + return util.parse_hex_to_int256(part1), util.parse_hex_to_int256(part2) + + +def split_swap_data_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + if len(hex_string) == 320: + part1 = hex_string[:64] + part2 = hex_string[64:128] + part3 = hex_string[128:192] + part4 = hex_string[192:256] + part5 = hex_string[256:] + return part1, part2, part3, part4, part5 + else: + raise ValueError("The data length is not suitable for this operation.") diff --git a/indexer/modules/custom/uniswap_v3/agni_token_job.py b/indexer/modules/custom/uniswap_v3/agni_token_job.py new file mode 100644 index 000000000..f31a211ed --- /dev/null +++ b/indexer/modules/custom/uniswap_v3/agni_token_job.py @@ -0,0 +1,523 @@ +import configparser +import json +import logging +import os +import queue +from concurrent.futures import ThreadPoolExecutor, as_completed +from dataclasses import fields + +import eth_abi +from web3 import Web3 + +from indexer.domain.log import Log +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.uniswap_v3 import constants, util +from indexer.modules.custom.uniswap_v3.constants import AGNI_ABI +from indexer.modules.custom.uniswap_v3.domain.feature_uniswap_v3 import ( + AgniV3Pool, + AgniV3Token, + AgniV3TokenCollectFee, + AgniV3TokenCurrentStatus, + AgniV3TokenDetail, + AgniV3TokenUpdateLiquidity, +) +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_pools import UniswapV3Pools +from indexer.modules.custom.uniswap_v3.models.feature_uniswap_v3_tokens import UniswapV3Tokens +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc +from indexer.utils.utils import rpc_response_to_result, zip_rpc_response + +logger = logging.getLogger(__name__) + + +class AgniTokenJob(FilterTransactionDataJob): + dependency_types = [Log, AgniV3Pool] + output_types = [ + AgniV3Pool, + AgniV3Token, + AgniV3TokenDetail, + AgniV3TokenCurrentStatus, + AgniV3TokenCollectFee, + AgniV3TokenUpdateLiquidity, + ] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._chain_id = common_utils.get_chain_id(self._web3) + self._service = kwargs["config"].get("db_service") + self._load_config("agni_config.ini", self._chain_id) + self._abi_list = AGNI_ABI + self._liquidity_token_id_blocks = queue.Queue() + self._exist_token_ids = get_exist_token_ids(self._service, self._position_token_address) + self._exist_pool_infos = get_exist_pools(self._service, self._position_token_address) + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + chain_id_str = str(chain_id) + try: + chain_config = config[chain_id_str] + except KeyError: + return + try: + self._position_token_address = chain_config.get("nft_address").lower() + self._factory_address = chain_config.get("factory_address").lower() + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(addresses=[self._position_token_address]), + ] + ) + + def _collect(self, **kwargs): + # get new pool + collected_pools = self._data_buff[AgniV3Pool.type()] + for data in collected_pools: + self._exist_pool_infos[data.pool_address] = data + info_pool_dict = {} + for data in self._exist_pool_infos.values(): + key = (data.token0_address, data.token1_address, data.fee) + info_pool_dict[key] = data.pool_address + + # collect token_id's data + logs = self._data_buff[Log.type()] + early_token_id_data = {} + need_collect_token_id_data = [] + need_collect_token_id_set = set() + for log in logs: + topic0 = log.topic0 + address = log.address + block_number = log.block_number + block_timestamp = log.block_timestamp + if address != self._position_token_address: + continue + if topic0 == constants.TRANSFER_TOPIC0: + token_id_hex = log.topic3 + elif ( + topic0 == constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_TOKEN_COLLECT_FEE_TOPIC0 + ): + token_id_hex = log.topic1 + else: + continue + token_id = util.parse_hex_to_int256(token_id_hex) + key = (token_id, block_number, block_timestamp) + data = {"token_id": token_id, "block_number": block_number, "block_timestamp": block_timestamp} + if key not in need_collect_token_id_set: + need_collect_token_id_data.append(data) + need_collect_token_id_set.add(key) + if token_id in early_token_id_data: + early_block_number, early_block_timestamp = early_token_id_data[token_id] + if block_number < early_block_number: + early_token_id_data[token_id] = (block_number, block_timestamp) + else: + early_token_id_data[token_id] = (block_number, block_timestamp) + + if len(need_collect_token_id_data) == 0: + return + + # call owners + owner_info = owner_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + need_collect_token_id_data, + self._position_token_address, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + # call positions + token_infos = positions_rpc_requests( + self._web3, + self._batch_web3_provider.make_request, + owner_info, + self._position_token_address, + self._is_batch, + self._abi_list, + self._batch_size, + self._max_worker, + ) + token_id_current_status = {} + token_owner_dict = {} + for data in token_infos: + token_id = data["token_id"] + block_number = data["block_number"] + block_timestamp = data["block_timestamp"] + if token_id not in self._exist_token_ids: + # need save token_info + if "fee" not in data: + continue + fee = data["fee"] + key = (data["token0"], data["token1"], fee) + pool_address = info_pool_dict[key] + tick_lower = (data["tickLower"],) + tick_upper = (data["tickUpper"],) + self._collect_item( + AgniV3Token.type(), + AgniV3Token( + position_token_address=self._position_token_address, + token_id=token_id, + pool_address=pool_address, + tick_lower=tick_lower, + tick_upper=tick_upper, + fee=fee, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + self._exist_token_ids[token_id] = pool_address + else: + pool_address = self._exist_token_ids[token_id] + wallet_address = constants.ZERO_ADDRESS + if "owner" in data: + wallet_address = data["owner"] + token_owner_dict.setdefault(token_id, {})[block_number] = wallet_address + liquidity = 0 + if "liquidity" in data: + liquidity = data["liquidity"] + detail = AgniV3TokenDetail( + position_token_address=self._position_token_address, + pool_address=pool_address, + token_id=token_id, + wallet_address=wallet_address, + liquidity=liquidity, + block_number=block_number, + block_timestamp=block_timestamp, + ) + self._collect_item(AgniV3TokenDetail.type(), detail) + token_id = detail.token_id + if token_id not in token_id_current_status or block_number > token_id_current_status[token_id].block_number: + token_id_current_status[token_id] = create_token_status(detail) + + for data in token_id_current_status.values(): + self._collect_item(AgniV3TokenCurrentStatus.type(), data) + + # collect fee and liquidity + for log in logs: + if log.address != self._position_token_address: + continue + topic0 = log.topic0 + block_number = log.block_number + block_timestamp = log.block_timestamp + if topic0 not in ( + constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0, + constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0, + constants.UNISWAP_V3_TOKEN_COLLECT_FEE_TOPIC0, + ): + continue + token_id = util.parse_hex_to_int256(log.topic1) + owner = constants.ZERO_ADDRESS + if token_id in token_owner_dict: + if block_number in token_owner_dict[token_id]: + owner = token_owner_dict[token_id][block_number] + if token_id not in self._exist_token_ids: + logger.error( + f"the token id {token_id} block_number = {block_number} transaction_hash = {log.transaction_hash} is not collected" + ) + continue + pool_address = self._exist_token_ids[token_id] + pool_info = self._exist_pool_infos[pool_address] + if ( + topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + or topic0 == constants.UNISWAP_V3_ADD_LIQUIDITY_TOPIC0 + ): + liquidity_hex, amount0_hex, amount1_hex = split_hex_string(log.data) + action_type = ( + constants.DECREASE_TYPE + if topic0 == constants.UNISWAP_V3_REMOVE_LIQUIDITY_TOPIC0 + else constants.INCREASE_TYPE + ) + self._collect_item( + AgniV3TokenUpdateLiquidity.type(), + AgniV3TokenUpdateLiquidity( + position_token_address=self._position_token_address, + token_id=token_id, + owner=owner, + action_type=action_type, + transaction_hash=log.transaction_hash, + liquidity=util.parse_hex_to_int256(liquidity_hex), + amount0=util.parse_hex_to_int256(amount0_hex), + amount1=util.parse_hex_to_int256(amount1_hex), + pool_address=pool_address, + token0_address=pool_info.token0_address, + token1_address=pool_info.token1_address, + log_index=log.log_index, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + else: + recipient_hex, amount0_hex, amount1_hex = split_hex_string(log.data) + self._collect_item( + AgniV3TokenCollectFee.type(), + AgniV3TokenCollectFee( + position_token_address=self._position_token_address, + token_id=token_id, + owner=owner, + transaction_hash=log.transaction_hash, + recipient=util.parse_hex_to_address(recipient_hex), + amount0=util.parse_hex_to_int256(amount0_hex), + amount1=util.parse_hex_to_int256(amount1_hex), + pool_address=pool_address, + token0_address=pool_info.token0_address, + token1_address=pool_info.token1_address, + log_index=log.log_index, + block_number=block_number, + block_timestamp=block_timestamp, + ), + ) + + def _process(self, **kwargs): + self._data_buff[AgniV3Token.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3TokenDetail.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3TokenCurrentStatus.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3TokenUpdateLiquidity.type()].sort(key=lambda x: x.block_number) + self._data_buff[AgniV3TokenCollectFee.type()].sort(key=lambda x: x.block_number) + + +def get_exist_pools(db_service, position_token_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Pools) + .filter(UniswapV3Pools.position_token_address == bytes.fromhex(position_token_address[2:])) + .all() + ) + history_pools = {} + if result is not None: + for item in result: + pool_key = "0x" + item.pool_address.hex() + history_pools[pool_key] = AgniV3Pool( + pool_address=pool_key, + position_token_address="0x" + item.position_token_address.hex(), + factory_address="0x" + item.factory_address.hex(), + token0_address="0x" + item.token0_address.hex(), + token1_address="0x" + item.token1_address.hex(), + fee=item.fee, + tick_spacing=item.tick_spacing, + block_number=item.block_number, + block_timestamp=item.block_timestamp, + ) + except Exception as e: + raise e + finally: + session.close() + + return history_pools + + +def get_exist_token_ids(db_service, position_token_address): + if not db_service: + return {} + + session = db_service.get_service_session() + try: + result = ( + session.query(UniswapV3Tokens.token_id, UniswapV3Tokens.pool_address) + .filter( + UniswapV3Tokens.position_token_address == bytes.fromhex(position_token_address[2:]), + ) + .all() + ) + history_token = {} + if result is not None: + for item in result: + token_id = item.token_id + history_token[token_id] = "0x" + item.pool_address.hex() + except Exception as e: + raise e + finally: + session.close() + return history_token + + +def build_token_id_method_data(web3, token_ids, nft_address, fn, abi_list): + parameters = [] + contract = web3.eth.contract(address=Web3.to_checksum_address(nft_address), abi=abi_list) + + for idx, token in enumerate(token_ids): + token_data = { + "request_id": idx, + "param_to": nft_address, + "param_number": hex(token["block_number"]), + } + token.update(token_data) + + try: + # Encode the ABI for the specific token_id + data = contract.encodeABI(fn_name=fn, args=[token["token_id"]]) + token["param_data"] = data + except Exception as e: + logger.error( + f"Encoding token id {token['token_id']} for function {fn} failed. " + f"NFT address: {nft_address}. " + f"Exception: {e}." + ) + + parameters.append(token) + return parameters + + +def positions_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list, batch_size, max_workers): + if len(requests) == 0: + return [] + + fn_name = "positions" + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + def process_batch(batch): + if is_batch: + response = make_requests(params=json.dumps(batch)) + else: + response = [make_requests(params=json.dumps(batch[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token["nonce"] = decoded_data[0] + token["operator"] = decoded_data[1] + token["token0"] = decoded_data[2] + token["token1"] = decoded_data[3] + token["fee"] = decoded_data[4] + token["tickLower"] = decoded_data[5] + token["tickUpper"] = decoded_data[6] + token["liquidity"] = decoded_data[7] + token["feeGrowthInside0LastX128"] = decoded_data[8] + token["feeGrowthInside1LastX128"] = decoded_data[9] + token["tokensOwed0"] = decoded_data[10] + token["tokensOwed1"] = decoded_data[11] + + except Exception as e: + logger.error( + f"Decoding positions failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + all_token_infos = [] + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = [] + for i in range(0, len(token_name_rpc), batch_size): + batch = token_name_rpc[i : i + batch_size] + futures.append(executor.submit(process_batch, batch)) + + for future in as_completed(futures): + try: + result = future.result() + all_token_infos.extend(result) + except Exception as e: + logger.error(f"Batch processing failed with exception: {e}") + + return all_token_infos + + +def owner_rpc_requests(web3, make_requests, requests, nft_address, is_batch, abi_list, batch_size, max_workers): + if len(requests) == 0: + return [] + + fn_name = "ownerOf" + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = build_token_id_method_data(web3, requests, nft_address, fn_name, abi_list) + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + + def process_batch(batch): + if is_batch: + response = make_requests(params=json.dumps(batch)) + else: + response = [make_requests(params=json.dumps(batch[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token["owner"] = decoded_data[0] + + except Exception as e: + logger.error( + f"Decoding ownerOf failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + all_token_infos = [] + with ThreadPoolExecutor(max_workers=max_workers) as executor: + futures = [] + for i in range(0, len(token_name_rpc), batch_size): + batch = token_name_rpc[i : i + batch_size] + futures.append(executor.submit(process_batch, batch)) + + for future in as_completed(futures): + try: + result = future.result() + all_token_infos.extend(result) + except Exception as e: + logger.error(f"Batch processing failed with exception: {e}") + + return all_token_infos + + +def create_token_status(detail: AgniV3TokenDetail) -> AgniV3TokenCurrentStatus: + return AgniV3TokenCurrentStatus(**{field.name: getattr(detail, field.name) for field in fields(AgniV3TokenDetail)}) + + +def split_hex_string(hex_string): + if hex_string.startswith("0x"): + hex_string = hex_string[2:] + + if len(hex_string) == 192: + part1 = hex_string[:64] + part2 = hex_string[64:128] + part3 = hex_string[128:] + return part1, part2, part3 + else: + raise ValueError("The data is not belong to Uniswap-V3 Liquidity") diff --git a/indexer/modules/custom/uniswap_v3/constants.py b/indexer/modules/custom/uniswap_v3/constants.py index 90efe91a0..8d25f4455 100644 --- a/indexer/modules/custom/uniswap_v3/constants.py +++ b/indexer/modules/custom/uniswap_v3/constants.py @@ -136,3 +136,133 @@ # swap "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", ] + + +AGNI_POOL_PRICE_TOPIC0_LIST = [ + # Initialize pool + "0x98636036cb66a9c19a37435efc1e90142190214e8abeb821bdba3f2990dd4c95", + # mint + "0x7a53080ba414158be7ec69b987b5fb7d07dee101fe85488f0853ae16239d0bde", + # burn + "0x0c396cd989a39f4459b5fa1aed6a9a8dcdbc45908acfd67e028cd568da98982c", + # swap + "0x19b47279256b2a23a1665c810c8d55a1758940ee09377d4f8d26497a3577dc83", +] + + +AGNI_ABI = [ + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "positions", + "outputs": [ + {"internalType": "uint96", "name": "nonce", "type": "uint96"}, + {"internalType": "address", "name": "operator", "type": "address"}, + {"internalType": "address", "name": "token0", "type": "address"}, + {"internalType": "address", "name": "token1", "type": "address"}, + {"internalType": "uint24", "name": "fee", "type": "uint24"}, + {"internalType": "int24", "name": "tickLower", "type": "int24"}, + {"internalType": "int24", "name": "tickUpper", "type": "int24"}, + {"internalType": "uint128", "name": "liquidity", "type": "uint128"}, + {"internalType": "uint256", "name": "feeGrowthInside0LastX128", "type": "uint256"}, + {"internalType": "uint256", "name": "feeGrowthInside1LastX128", "type": "uint256"}, + {"internalType": "uint128", "name": "tokensOwed0", "type": "uint128"}, + {"internalType": "uint128", "name": "tokensOwed1", "type": "uint128"}, + ], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [ + {"internalType": "address", "name": "", "type": "address"}, + {"internalType": "address", "name": "", "type": "address"}, + {"internalType": "uint24", "name": "", "type": "uint24"}, + ], + "name": "getPool", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "slot0", + "outputs": [ + {"internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}, + {"internalType": "int24", "name": "tick", "type": "int24"}, + {"internalType": "uint16", "name": "observationIndex", "type": "uint16"}, + {"internalType": "uint16", "name": "observationCardinality", "type": "uint16"}, + {"internalType": "uint16", "name": "observationCardinalityNext", "type": "uint16"}, + {"internalType": "uint32", "name": "feeProtocol", "type": "uint32"}, + {"internalType": "bool", "name": "unlocked", "type": "bool"}, + ], + "stateMutability": "view", + "type": "function", + }, + { + "anonymous": False, + "inputs": [ + {"indexed": True, "internalType": "address", "name": "token0", "type": "address"}, + {"indexed": True, "internalType": "address", "name": "token1", "type": "address"}, + {"indexed": True, "internalType": "uint24", "name": "fee", "type": "uint24"}, + {"indexed": False, "internalType": "int24", "name": "tickSpacing", "type": "int24"}, + {"indexed": False, "internalType": "address", "name": "pool", "type": "address"}, + ], + "name": "PoolCreated", + "type": "event", + }, + { + "anonymous": False, + "inputs": [ + {"indexed": True, "internalType": "address", "name": "sender", "type": "address"}, + {"indexed": True, "internalType": "address", "name": "recipient", "type": "address"}, + {"indexed": False, "internalType": "int256", "name": "amount0", "type": "int256"}, + {"indexed": False, "internalType": "int256", "name": "amount1", "type": "int256"}, + {"indexed": False, "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160"}, + {"indexed": False, "internalType": "uint128", "name": "liquidity", "type": "uint128"}, + {"indexed": False, "internalType": "int24", "name": "tick", "type": "int24"}, + ], + "name": "Swap", + "type": "event", + }, + { + "inputs": [{"internalType": "uint256", "name": "tokenId", "type": "uint256"}], + "name": "ownerOf", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "factory", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "fee", + "outputs": [{"internalType": "uint24", "name": "", "type": "uint24"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "token0", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "token1", + "outputs": [{"internalType": "address", "name": "", "type": "address"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "tickSpacing", + "outputs": [{"internalType": "int24", "name": "", "type": "int24"}], + "stateMutability": "view", + "type": "function", + }, +] diff --git a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py index 742e52d01..e9a3a5223 100644 --- a/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py +++ b/indexer/modules/custom/uniswap_v3/domain/feature_uniswap_v3.py @@ -124,3 +124,48 @@ class UniswapV3TokenCollectFee(FilterData): log_index: int block_number: int block_timestamp: int + + +@dataclass +class AgniV3Pool(UniswapV3Pool): + pass + + +@dataclass +class AgniV3Token(UniswapV3Token): + pass + + +@dataclass +class AgniV3PoolPrice(UniswapV3PoolPrice): + pass + + +@dataclass +class AgniV3TokenDetail(UniswapV3TokenDetail): + pass + + +@dataclass +class AgniV3PoolCurrentPrice(UniswapV3PoolCurrentPrice): + pass + + +@dataclass +class AgniV3TokenCurrentStatus(UniswapV3TokenCurrentStatus): + pass + + +@dataclass +class AgniV3SwapEvent(UniswapV3SwapEvent): + pass + + +@dataclass +class AgniV3TokenUpdateLiquidity(UniswapV3TokenUpdateLiquidity): + pass + + +@dataclass +class AgniV3TokenCollectFee(UniswapV3TokenCollectFee): + pass diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py index 49959ff7f..e4beb78ec 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_collect_fee_records.py @@ -40,6 +40,12 @@ def model_domain_mapping(): "update_strategy": None, "converter": general_converter, }, + { + "domain": "AgniV3TokenCollectFee", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py index 2731c43b7..7a782bf72 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_liquidity_records.py @@ -43,6 +43,12 @@ def model_domain_mapping(): "update_strategy": None, "converter": general_converter, }, + { + "domain": "AgniV3TokenUpdateLiquidity", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py index 9201022b6..aad665489 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_current_prices.py @@ -28,4 +28,10 @@ def model_domain_mapping(): "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_pool_prices_current.block_number", "converter": general_converter, }, + { + "domain": "AgniV3PoolCurrentPrice", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_pool_prices_current.block_number", + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py index 5ffd00f41..cd4cdee64 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pool_prices.py @@ -30,4 +30,10 @@ def model_domain_mapping(): "update_strategy": None, "converter": general_converter, }, + { + "domain": "AgniV3PoolPrice", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py index ffdf02648..1889805f5 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_pools.py @@ -35,5 +35,11 @@ def model_domain_mapping(): "conflict_do_update": True, "update_strategy": None, "converter": general_converter, - } + }, + { + "domain": "AgniV3Pool", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py index cf8c917f8..ea05a00c5 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_swap_records.py @@ -42,4 +42,10 @@ def model_domain_mapping(): "update_strategy": None, "converter": general_converter, }, + { + "domain": "AgniV3SwapEvent", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py index 3a99a6a07..1d8cda61b 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_current_status.py @@ -31,6 +31,12 @@ def model_domain_mapping(): "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_token_data_current.block_number", "converter": general_converter, }, + { + "domain": "AgniV3TokenCurrentStatus", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_uniswap_v3_token_data_current.block_number", + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py index 146513de2..58c8df8a0 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_token_details.py @@ -32,6 +32,12 @@ def model_domain_mapping(): "update_strategy": None, "converter": general_converter, }, + { + "domain": "AgniV3TokenDetail", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] diff --git a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py index 58355ee7b..e0600ab2f 100644 --- a/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py +++ b/indexer/modules/custom/uniswap_v3/models/feature_uniswap_v3_tokens.py @@ -33,7 +33,13 @@ def model_domain_mapping(): "conflict_do_update": True, "update_strategy": None, "converter": general_converter, - } + }, + { + "domain": "AgniV3Token", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, ] From 9a9ab5c65109310d2bacee288ce44578dec5d369 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Wed, 18 Sep 2024 13:21:28 +0800 Subject: [PATCH 58/70] Fix Ens API (#148) --- api/app/af_ens/routes.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index 25f25c1ae..f627f4dbd 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -57,7 +57,11 @@ def get(self, address): .all() ) all_721_ids = [r.token_id for r in all_721_owns] - all_owned = db.session.query(ENSRecord).filter(and_(ENSRecord.token_id.in_(all_721_ids))).all() + all_owned = ( + db.session.query(ENSRecord) + .filter(and_(ENSRecord.token_id.in_(all_721_ids), ENSRecord.w_token_id.is_(None))) + .all() + ) all_owned_map = {r.token_id: r for r in all_owned} for id in all_721_ids: r = all_owned_map.get(id) From c0c22c907970f4ec7cff82cc8f86874805111ef9 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Wed, 18 Sep 2024 18:49:43 +0800 Subject: [PATCH 59/70] api fix (#155) --- api/app/af_ens/routes.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/api/app/af_ens/routes.py b/api/app/af_ens/routes.py index f627f4dbd..cd09e47ba 100644 --- a/api/app/af_ens/routes.py +++ b/api/app/af_ens/routes.py @@ -39,9 +39,9 @@ def get(self, address): "first_register_time": None, "first_set_primary_time": None, } - if address: + try: address = bytes.fromhex(address[2:]) - else: + except Exception: raise APIError("Invalid Address Format", code=400) dn = datetime.now() @@ -97,9 +97,7 @@ def get(self, address): res["ens_holdings_total"] = len(res["ens_holdings"]) primary_address_row = db.session.query(ENSAddress).filter(ENSAddress.address == address).first() if primary_address_row: - primary_record = db.session.query(ENSRecord).filter(ENSRecord.name == primary_address_row.name).first() - if primary_record: - res["primary_name"] = primary_record.name + res["primary_name"] = primary_address_row.name else: res["primary_name"] = w3.ens.name(w3.to_checksum_address(w3.to_hex(address))) @@ -147,9 +145,9 @@ def get(self, address): @af_ens_namespace.route("/v1/aci/
/ens/detail") class ACIEnsDetail(Resource): def get(self, address): - if address: + try: address = bytes.fromhex(address[2:]) - else: + except Exception: raise APIError("Invalid Address Format", code=400) events = [] @@ -194,11 +192,11 @@ def get(self, address): for r in all_rows: name = None - if hasattr(r, "node") and r.node: + if hasattr(r, "node") and r.node and r.node in node_name_map: name = node_name_map[r.node] - elif hasattr(r, "token_id") and r.token_id: + elif hasattr(r, "token_id") and r.token_id and r.token_id in token_id_name_map: name = token_id_name_map[r.token_id] - elif hasattr(r, "w_token_id") and r.w_token_id: + elif hasattr(r, "w_token_id") and r.w_token_id and r.w_token_id in token_id_name_map: name = token_id_name_map[r.w_token_id] base = { "block_number": r.block_number, From 776912b116a774c3c413b647388e31026343db17 Mon Sep 17 00:00:00 2001 From: will0x0909 <166356797+will0x0909@users.noreply.github.com> Date: Thu, 19 Sep 2024 15:52:21 +0800 Subject: [PATCH 60/70] Ens setname bugfix (#158) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add contract😒 --- indexer/modules/custom/hemera_ens/ens_conf.py | 2 + .../modules/custom/hemera_ens/ens_handler.py | 38 ++++++++++++++++++- 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/indexer/modules/custom/hemera_ens/ens_conf.py b/indexer/modules/custom/hemera_ens/ens_conf.py index 5a245c236..b71d17b4c 100644 --- a/indexer/modules/custom/hemera_ens/ens_conf.py +++ b/indexer/modules/custom/hemera_ens/ens_conf.py @@ -51,6 +51,8 @@ "0xc55079a6ab7a123746806748d847228afe1d2397": "BulkRegister", "0x000000000000509081d6fcd3ee63e791ad1db763": "NOT Verified", "0xc71930b83cbabfeb35bedaaf2122e41595179b14": "NOT Verified", + "0xb3f4598d27bc18f3a7e155dba5e31083c9e1ec3f": "NOT Verified", + "0xfc0a4a934410f34a9bb8b4f28bed6b960c943a7e": "NOT Verified", } diff --git a/indexer/modules/custom/hemera_ens/ens_handler.py b/indexer/modules/custom/hemera_ens/ens_handler.py index 4400040a1..eb18c44b4 100644 --- a/indexer/modules/custom/hemera_ens/ens_handler.py +++ b/indexer/modules/custom/hemera_ens/ens_handler.py @@ -197,6 +197,42 @@ def process(self, transaction, logs): method="setName", ) ] + elif method == "setNameForAddr": + d_tnx = self.decode_transaction(transaction) + ens_middle = AttrDict(dic) + ens_middle.log_index = -1 + name = None + if d_tnx[1].get("name"): + name = d_tnx[1]["name"] + if not name or len(name) - 4 != name.find("."): + return [] + address = None + if d_tnx[1].get("addr"): + address = d_tnx[1]["addr"] + if not address: + return [] + ens_middle.reverse_name = name + ens_middle.node = namehash(name) + ens_middle.address = address.lower() + return [ + ENSMiddleD( + transaction_hash=ens_middle.transaction_hash, + log_index=ens_middle.log_index, + transaction_index=ens_middle.transaction_index, + block_number=ens_middle.block_number, + block_hash=ens_middle.block_hash, + block_timestamp=ens_middle.block_timestamp, + from_address=ens_middle.from_address, + to_address=ens_middle.to_address, + reverse_name=ens_middle.reverse_name, + address=ens_middle.address, + node=ens_middle.node, + reverse_node=None, + reverse_base_node=REVERSE_BASE_NODE, + event_name=None, + method="setNameForAddr", + ) + ] res = [] start = 0 for idx, single_log in enumerate(logs): @@ -282,7 +318,7 @@ def resolve_middle(self, record): node = record.get("node") if not node: raise Exception("pass") - if event_name == "NameChanged" or record["method"] == "setName": + if event_name == "NameChanged" or record["method"] == "setName" or record["method"] == "setNameForAddr": return ENSAddressD( address=address, reverse_node=record["reverse_node"], From 94ec10e3838423862ef93802f8f4dd17aca6e43a Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 20 Sep 2024 14:36:58 +0800 Subject: [PATCH 61/70] fix erc1155_token_transfer pkey (#160) --- .../versions/20240725_update_index_table_optimize.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/migrations/versions/20240725_update_index_table_optimize.py b/migrations/versions/20240725_update_index_table_optimize.py index 4e581b563..472a55cbd 100644 --- a/migrations/versions/20240725_update_index_table_optimize.py +++ b/migrations/versions/20240725_update_index_table_optimize.py @@ -210,6 +210,12 @@ def upgrade() -> None: ["token_address", "to_address"], unique=False, ) + op.drop_constraint("erc1155_token_transfers_pkey", "erc1155_token_transfers", type_="primary") + op.create_primary_key( + "erc1155_token_transfers_pkey", + "erc1155_token_transfers", + ["transaction_hash", "block_hash", "log_index", "token_id"], + ) op.alter_column( "erc20_token_transfers", "block_hash", @@ -671,6 +677,12 @@ def downgrade() -> None: existing_type=sa.NUMERIC(precision=78, scale=0), nullable=True, ) + op.drop_constraint("erc1155_token_transfers_pkey", "erc1155_token_transfers", type_="primary") + op.create_primary_key( + "erc1155_token_transfers_pkey", + "erc1155_token_transfers", + ["transaction_hash", "log_index"], + ) op.drop_index("erc1155_detail_desc_address_id_index", table_name="erc1155_token_id_details") op.drop_constraint("erc1155_token_id_details_pkey", "erc1155_token_id_details", type_="primary") op.alter_column("erc1155_token_id_details", "token_address", new_column_name="address") From 9e4c32c9b2e235ac2b0ca5c263042fe0e6a82777 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:07:13 +0800 Subject: [PATCH 62/70] Bugfix/reorg retry clear buffer (#161) * clear buffer before job retry * clear buffer before job retry --- indexer/controller/scheduler/reorg_scheduler.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indexer/controller/scheduler/reorg_scheduler.py b/indexer/controller/scheduler/reorg_scheduler.py index 071ede0d8..fb1113dfb 100644 --- a/indexer/controller/scheduler/reorg_scheduler.py +++ b/indexer/controller/scheduler/reorg_scheduler.py @@ -84,11 +84,11 @@ def __init__( self.instantiate_jobs() @staticmethod - def get_data_buff(self): + def get_data_buff(): return BaseJob._data_buff @staticmethod - def clear_data_buff(self): + def clear_data_buff(): BaseJob._data_buff.clear() def discover_and_register_job_classes(self): @@ -159,9 +159,9 @@ def instantiate_jobs(self): self.jobs.append(export_reorg_job) def run_jobs(self, start_block, end_block): + self.clear_data_buff() for job in self.jobs: job.run(start_block=start_block, end_block=end_block) - # TODO: clean data buffer after all jobs are run def get_required_job_classes(self, output_types): required_job_classes = set() From 73ccacf53906f05ceb95b2b882e1cb22d409312b Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:32:40 +0800 Subject: [PATCH 63/70] Bugfix/reorg recursion exceeded (#162) * fixed recursion depth exceeded while continuous waking up uncompleted reorg job * optimized code --- cli/reorg.py | 18 +++++++++++++++++- indexer/controller/reorg_controller.py | 14 ++------------ 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/cli/reorg.py b/cli/reorg.py index 8bc81ab52..74fd4655a 100644 --- a/cli/reorg.py +++ b/cli/reorg.py @@ -185,4 +185,20 @@ def reorg( config=config, ) - controller.action(block_number=block_number) + job = None + while True: + if job: + controller.action( + job_id=job.job_id, + block_number=job.last_fixed_block_number - 1, + remains=job.remain_process, + ) + else: + controller.action(block_number=block_number) + + job = controller.wake_up_next_job() + if job: + logging.info(f"Waking up uncompleted job: {job.job_id}.") + else: + logging.info("No more uncompleted jobs to wake-up, reorg process will terminate.") + break diff --git a/indexer/controller/reorg_controller.py b/indexer/controller/reorg_controller.py index efd6d0190..3d98b7c46 100644 --- a/indexer/controller/reorg_controller.py +++ b/indexer/controller/reorg_controller.py @@ -91,8 +91,6 @@ def action(self, job_id=None, block_number=None, remains=None, retry_errors=True logging.info(f"Reorging mission start from block No.{block_number} and ranges {remains} has been completed.") - self.wake_up_next_job() - def _do_fixing(self, fix_block, retry_errors=True): tries, tries_reset = 0, True while True: @@ -191,19 +189,11 @@ def wake_up_next_job(self): ) except Exception as e: logging.error(f"Wake up uncompleted job error: {e}.") + raise e finally: session.close() - if job: - try: - self.action( - job_id=job.job_id, - block_number=job.last_fixed_block_number - 1, - remains=job.remain_process, - ) - except Exception as e: - if job: - logging.error(f"Catch exception while waking up job: {job.job_id}. error: {e}") + return job def check_block_been_synced(self, block_number): session = self.db_service.get_service_session() From aa0c7f791c5144b2c396c2b6236c21595c446e54 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 20 Sep 2024 18:33:11 +0800 Subject: [PATCH 64/70] change job dependency and fix dataclass init (#163) --- .../custom/deposit_to_l2/deposit_to_l2_job.py | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py b/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py index ede1c3478..e160d3438 100644 --- a/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py +++ b/indexer/modules/custom/deposit_to_l2/deposit_to_l2_job.py @@ -9,7 +9,7 @@ from common.utils.cache_utils import BlockToLiveDict, TimeToLiveDict from common.utils.exception_control import FastShutdownError -from indexer.domain.block import Block +from indexer.domain.transaction import Transaction from indexer.jobs import FilterTransactionDataJob from indexer.modules.custom.deposit_to_l2.deposit_parser import parse_deposit_transaction_function, token_parse_mapping from indexer.modules.custom.deposit_to_l2.domain.address_token_deposit import AddressTokenDeposit @@ -21,7 +21,7 @@ class DepositToL2Job(FilterTransactionDataJob): - dependency_types = [Block] + dependency_types = [Transaction] output_types = [TokenDepositTransaction, AddressTokenDeposit] able_to_reorg = True @@ -86,7 +86,11 @@ def _process(self, **kwargs): transactions = list( filter( self._filter.get_or_specification().is_satisfied_by, - [transaction for block in self._data_buff[Block.type()] for transaction in block.transactions], + [ + transaction + for transaction in self._data_buff[Transaction.type()] + if transaction.receipt and transaction.receipt.status != 0 + ], ) ) deposit_tokens = parse_deposit_transaction_function( @@ -162,13 +166,13 @@ def check_history_deposit_from_db( deposit = ( AddressTokenDeposit( - wallet_address=history_deposit.wallet_address, + wallet_address="0x" + history_deposit.wallet_address.hex(), chain_id=history_deposit.chain_id, - contract_address=history_deposit.contract_address, - token_address=history_deposit.token_address, - value=history_deposit.value, + contract_address="0x" + history_deposit.contract_address.hex(), + token_address="0x" + history_deposit.token_address.hex(), + value=int(history_deposit.value), block_number=history_deposit.block_number, - block_timestamp=history_deposit.block_timestamp, + block_timestamp=int(round(history_deposit.block_timestamp.timestamp())), ) if history_deposit else None From 5908a6cf7a17f302912e92c48f559abf9dd2d71d Mon Sep 17 00:00:00 2001 From: li xiang Date: Mon, 23 Sep 2024 10:47:48 +0800 Subject: [PATCH 65/70] build ci (#154) * Use poetry --- .github/workflows/{test.yaml => ci.yaml} | 12 +- Dockerfile | 22 +- Makefile | 8 +- VERSION | 1 - __init__.py | 5 +- cli/__init__.py | 5 +- docker-compose/docker-compose.yaml | 15 +- docker-compose/hemera-indexer.env | 2 +- hemera_api.py | 18 - indexer/domain/token_id_infos.py | 2 +- indexer/tests/jobs/__init__.py | 0 indexer/tests/jobs/test_token_id_infos_job.py | 96 - indexer/utils/token_fetcher.py | 4 +- poetry.lock | 3571 +++++++++++++++++ pyproject.toml | 124 +- 15 files changed, 3676 insertions(+), 209 deletions(-) rename .github/workflows/{test.yaml => ci.yaml} (66%) delete mode 100644 VERSION delete mode 100644 hemera_api.py create mode 100644 indexer/tests/jobs/__init__.py create mode 100644 poetry.lock diff --git a/.github/workflows/test.yaml b/.github/workflows/ci.yaml similarity index 66% rename from .github/workflows/test.yaml rename to .github/workflows/ci.yaml index 5df56ad3c..606485c69 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/ci.yaml @@ -1,4 +1,4 @@ -name: Python application test +name: Python application unit test on: [pull_request] @@ -16,8 +16,10 @@ jobs: - name: Install dependencies run: | - python -m pip install --upgrade pip - pip install -e ".[dev]" + pip install --upgrade pip + pip install poetry + poetry update + poetry install -v - name: Set PYTHONPATH run: echo "PYTHONPATH=\$PYTHONPATH:$(pwd)" >> $GITHUB_ENV @@ -25,9 +27,9 @@ jobs: - name: Verify PYTHONPATH run: echo $PYTHONPATH - - name: Test with pytest + - name: Unit Test with pytest env: - LINEA_PUBLIC_NODE_RPC_URL: ${{ vars.LINEA_PUBLIC_NODE_RPC_URL }} + LINEA_PUBLIC_NODE_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_RPC_URL }}' run: | export PYTHONPATH=$(pwd) make test indexer \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index f842789ca..d07d2d2c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,22 @@ -FROM python:3.9-slim +FROM python:3.9-slim AS builder + +ENV PYTHONUNBUFFERED=1 +ENV PYTHONDONTWRITEBYTECODE=1 -WORKDIR /app +RUN pip install poetry && poetry config virtualenvs.in-project true +WORKDIR "/app" COPY . . -RUN pip install --no-cache-dir build && \ - python -m build && \ - pip install dist/*.whl && \ - rm dist/*.whl +RUN poetry install +RUN poetry build + +FROM python:3.9-slim + +WORKDIR "/app" -ENV PYTHONPATH=/app:$PYTHONPATH +COPY --from=builder /app/migrations ./migrations +COPY --from=builder /app/dist/*.whl . +RUN pip install *.whl ENTRYPOINT ["hemera"] \ No newline at end of file diff --git a/Makefile b/Makefile index 04e4e910b..5e89ec8ee 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION := $(shell cat VERSION) +VERSION := $(shell poetry version -s) BUILD := `git rev-parse --short=7 HEAD` SERVICES = .PHONY: all build image test @@ -10,14 +10,14 @@ RESET=\033[0m image: - docker build $(IMAGE_FLAGS) --network host -t hemera-protocol:$(VERSION)-$(BUILD) . -q + docker build $(IMAGE_FLAGS) --network host -t hemera-protocol:$(VERSION)-$(BUILD) . --no-cache echo "Built image hemera-protocol:$(VERSION)-$(BUILD)" test: @if [ "$(filter-out $@,$(MAKECMDGOALS))" = "" ]; then \ - pytest -vv; \ + poetry run pytest -vv; \ else \ - pytest -vv -m $(filter-out $@,$(MAKECMDGOALS)); \ + poetry run pytest -vv -m $(filter-out $@,$(MAKECMDGOALS)); \ fi PRE_COMMIT_INSTALLED := $(shell command -v pre-commit > /dev/null 2>&1 && echo yes || echo no) diff --git a/VERSION b/VERSION deleted file mode 100644 index 0d91a54c7..000000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -0.3.0 diff --git a/__init__.py b/__init__.py index dc11949ff..41ad41f70 100644 --- a/__init__.py +++ b/__init__.py @@ -1,4 +1,5 @@ from pathlib import Path -VERSION_FILE = Path(__file__).parent / "VERSION" -__version__ = VERSION_FILE.read_text().strip() +import tomli + +__version__ = "0.3.0" diff --git a/cli/__init__.py b/cli/__init__.py index d45a9f66d..cc6a0d37d 100644 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -11,10 +11,11 @@ logging_basic_config() +from importlib import metadata + def get_version(): - version_file = Path(__file__).parent.parent / "VERSION" - return version_file.read_text().strip() + return metadata.version("hemera") @click.group() diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 9e4e73d20..d00efb88c 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -9,7 +9,7 @@ x-common-settings: &common-settings networks: - hemera volumes: - - ./output:/app/output + - hemera-output:/app/output depends_on: postgresql: condition: service_healthy @@ -19,13 +19,20 @@ services: <<: *common-settings container_name: indexer environment: + - AUTO_UPGRADE_DB=true - ENTITY_TYPES=EXPLORER_BASE,EXPLORER_TOKEN - SYNC_RECORDER=pg:main_recorder hemera-trace-indexer: <<: *common-settings + depends_on: + postgresql: + condition: service_healthy + hemera-main-indexer: + condition: service_started container_name: indexer-trace environment: + - AUTO_UPGRADE_DB=false - ENTITY_TYPES=EXPLORER_TRACE - SYNC_RECORDER=pg:trace_recorder @@ -59,7 +66,7 @@ services: networks: - hemera volumes: - - ./postgres:/var/lib/postgresql/data + - hemera-postgres:/var/lib/postgresql/data redis: image: redis:6 @@ -69,5 +76,9 @@ services: networks: - hemera +volumes: + hemera-postgres: + hemera-output: + networks: hemera: diff --git a/docker-compose/hemera-indexer.env b/docker-compose/hemera-indexer.env index fddb64165..2d75dbd6d 100644 --- a/docker-compose/hemera-indexer.env +++ b/docker-compose/hemera-indexer.env @@ -1,7 +1,7 @@ PROVIDER_URI=https://eth.llamarpc.com DEBUG_PROVIDER_URI=https://eth.llamarpc.com START_BLOCK=20159954 -POSTGRES_URL=postgresql+psycopg2://user:Ts6YZSrGegXr8PPONtHcSLAHDGd1fjwHBjFZ6WVNpgiOmGf7ghYXl0I1@postgresql:5432/postgres +POSTGRES_URL=postgresql://user:Ts6YZSrGegXr8PPONtHcSLAHDGd1fjwHBjFZ6WVNpgiOmGf7ghYXl0I1@postgresql:5432/postgres OUTPUT=postgres # This only works when you run this for the very first time. If the container has been created, you will need to remove the volume and recreate the container diff --git a/hemera_api.py b/hemera_api.py deleted file mode 100644 index 456e793d6..000000000 --- a/hemera_api.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Web Server Gateway Interface""" - -################## -# FOR PRODUCTION -# https://www.datascienceblog.net/post/programming/flask-api-development/ -#################### -from api.app.main import app - -if __name__ == "__main__": - #################### - # FOR DEVELOPMENT - #################### - - # from app.socialscan import models as w3w_socialscan_models - - # w3w_socialscan_models.postgres_db.create_all(app=app) - - app.run("0.0.0.0", 8082, threaded=True, debug=True) diff --git a/indexer/domain/token_id_infos.py b/indexer/domain/token_id_infos.py index 9ca088bc8..3c8b36e01 100644 --- a/indexer/domain/token_id_infos.py +++ b/indexer/domain/token_id_infos.py @@ -28,7 +28,7 @@ def from_token_dict(token_dict: dict): class ERC721TokenIdDetail(Domain): token_address: str token_id: int - token_uri: str + token_uri: Optional[str] block_number: int block_timestamp: int token_uri_info: Optional[str] = None diff --git a/indexer/tests/jobs/__init__.py b/indexer/tests/jobs/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/tests/jobs/test_token_id_infos_job.py b/indexer/tests/jobs/test_token_id_infos_job.py index 7d0d83d64..069298f9a 100644 --- a/indexer/tests/jobs/test_token_id_infos_job.py +++ b/indexer/tests/jobs/test_token_id_infos_job.py @@ -336,99 +336,3 @@ def test_export_token_id_info_job_on_linea_mul(): ) job_scheduler.clear_data_buff() - - -@pytest.mark.indexer -@pytest.mark.indexer_exporter -@pytest.mark.serial -def test_export_token_id_info_job_on_linea_mul(): - job_scheduler = JobScheduler( - batch_web3_provider=ThreadLocalProxy( - lambda: get_provider_from_uri( - LINEA_PUBLIC_NODE_RPC_URL, - batch=True, - ) - ), - batch_web3_debug_provider=ThreadLocalProxy( - lambda: get_provider_from_uri( - LINEA_PUBLIC_NODE_RPC_URL, - batch=True, - ) - ), - item_exporters=[ConsoleItemExporter()], - batch_size=10, - debug_batch_size=1, - max_workers=1, - config={}, - required_output_types=[ - ERC721TokenIdChange, - ERC721TokenIdDetail, - UpdateERC721TokenIdDetail, - ERC1155TokenIdDetail, - UpdateERC1155TokenIdDetail, - ], - multicall=True, - ) - - job_scheduler.run_jobs( - start_block=8494071, - end_block=8494071, - ) - - data_buff = job_scheduler.get_data_buff() - - erc721_token_id_changes = data_buff[ERC721TokenIdChange.type()] - assert len(erc721_token_id_changes) == 1 - assert ( - ERC721TokenIdChange( - token_address="0x6e84390dcc5195414ec91a8c56a5c91021b95704", - token_id=110042221770367602542853534930234725702383442308140339620523913150618217206456, - token_owner="0xa53cca02f98d590819141aa85c891e2af713c223", - block_number=8494071, - block_timestamp=1724397109, - ) - in erc721_token_id_changes - ) - - erc721_token_id_details = data_buff[ERC721TokenIdDetail.type()] - assert len(erc721_token_id_details) == 1 - assert ( - ERC721TokenIdDetail( - token_address="0x6e84390dcc5195414ec91a8c56a5c91021b95704", - token_id=110042221770367602542853534930234725702383442308140339620523913150618217206456, - token_uri="", - block_number=8494071, - block_timestamp=1724397109, - token_uri_info=None, - ) - in erc721_token_id_details - ) - - update_erc721_token_id_details = data_buff[UpdateERC721TokenIdDetail.type()] - assert len(update_erc721_token_id_details) == 1 - assert ( - UpdateERC721TokenIdDetail( - token_address="0x6e84390dcc5195414ec91a8c56a5c91021b95704", - token_id=110042221770367602542853534930234725702383442308140339620523913150618217206456, - token_owner="0xa53cca02f98d590819141aa85c891e2af713c223", - block_number=8494071, - block_timestamp=1724397109, - ) - in update_erc721_token_id_details - ) - - erc1155_token_id_details = data_buff[ERC1155TokenIdDetail.type()] - assert len(erc1155_token_id_details) == 1 - assert ( - ERC1155TokenIdDetail( - token_address="0xa53cca02f98d590819141aa85c891e2af713c223", - token_id=54780668040604116915679158082040366453838453357839560563054770201457212183923, - token_uri="ens-metadata-service.appspot.com/name/0x{id}", - block_number=8494071, - block_timestamp=1724397109, - token_uri_info=None, - ) - in erc1155_token_id_details - ) - - job_scheduler.clear_data_buff() diff --git a/indexer/utils/token_fetcher.py b/indexer/utils/token_fetcher.py index 53a6c673b..8c166b573 100644 --- a/indexer/utils/token_fetcher.py +++ b/indexer/utils/token_fetcher.py @@ -152,7 +152,7 @@ def create_token_detail(self, token_info, value, decode_flag): if token_info["is_get_token_uri"]: try: - token_uri = decode_string(value) if decode_flag else None + token_uri = decode_string(value) if decode_flag else value except Exception as e: token_uri = None logging.error(f"decode token uri failed, token_info={token_info}, value={value}") @@ -172,7 +172,7 @@ def create_token_detail(self, token_info, value, decode_flag): return [UpdateERC1155TokenIdDetail(**common_args, token_supply=token_supply)] except Exception as e: exception_recorder.log( - block_number=token_info.block_number, + block_number=token_info["block_number"], dataclass=to_snake_case("token_id_info"), message_type="decode_token_id_info_fail", message=str(e), diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 000000000..90c4db327 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,3571 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.0" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.0-py3-none-any.whl", hash = "sha256:7ce92076e249169a13c2f49320d1967425eaf1f407522d707d59cac7628d62bd"}, + {file = "aiohappyeyeballs-2.4.0.tar.gz", hash = "sha256:55a1714f084e63d49639800f95716da97a1f173d46a16dfcfda0016abb93b6b2"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.5" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, + {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, + {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, + {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, + {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, + {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, + {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, + {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, + {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, + {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, + {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, + {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, + {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, + {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, + {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, + {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, + {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, + {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, + {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, + {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, + {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, + {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, + {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, + {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, + {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, + {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, + {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, + {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, + {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, + {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, + {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, + {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alembic" +version = "1.13.2" +description = "A database migration tool for SQLAlchemy." +optional = false +python-versions = ">=3.8" +files = [ + {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, + {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, +] + +[package.dependencies] +importlib-metadata = {version = "*", markers = "python_version < \"3.9\""} +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} +Mako = "*" +SQLAlchemy = ">=1.3.0" +typing-extensions = ">=4" + +[package.extras] +tz = ["backports.zoneinfo"] + +[[package]] +name = "aniso8601" +version = "9.0.1" +description = "A library for parsing ISO 8601 strings." +optional = false +python-versions = "*" +files = [ + {file = "aniso8601-9.0.1-py2.py3-none-any.whl", hash = "sha256:1d2b7ef82963909e93c4f24ce48d4de9e66009a21bf1c1e1c85bdd0812fe412f"}, + {file = "aniso8601-9.0.1.tar.gz", hash = "sha256:72e3117667eedf66951bb2d93f4296a56b94b078a8a95905a052611fb3f1b973"}, +] + +[package.extras] +dev = ["black", "coverage", "isort", "pre-commit", "pyenchant", "pylint"] + +[[package]] +name = "async-timeout" +version = "4.0.3" +description = "Timeout context manager for asyncio programs" +optional = false +python-versions = ">=3.7" +files = [ + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, +] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "bitarray" +version = "2.9.2" +description = "efficient arrays of booleans -- C extension" +optional = false +python-versions = "*" +files = [ + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:917905de565d9576eb20f53c797c15ba88b9f4f19728acabec8d01eee1d3756a"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b35bfcb08b7693ab4bf9059111a6e9f14e07d57ac93cd967c420db58ab9b71e1"}, + {file = "bitarray-2.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ea1923d2e7880f9e1959e035da661767b5a2e16a45dfd57d6aa831e8b65ee1bf"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e0b63a565e8a311cc8348ff1262d5784df0f79d64031d546411afd5dd7ef67d"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cf0620da2b81946d28c0b16f3e3704d38e9837d85ee4f0652816e2609aaa4fed"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:79a9b8b05f2876c7195a2b698c47528e86a73c61ea203394ff8e7a4434bda5c8"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:345c76b349ff145549652436235c5532e5bfe9db690db6f0a6ad301c62b9ef21"}, + {file = "bitarray-2.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e2936f090bf3f4d1771f44f9077ebccdbc0415d2b598d51a969afcb519df505"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:f9346e98fc2abcef90b942973087e2462af6d3e3710e82938078d3493f7fef52"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e6ec283d4741befb86e8c3ea2e9ac1d17416c956d392107e45263e736954b1f7"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:962892646599529917ef26266091e4cb3077c88b93c3833a909d68dcc971c4e3"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e8da5355d7d75a52df5b84750989e34e39919ec7e59fafc4c104cc1607ab2d31"}, + {file = "bitarray-2.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:603e7d640e54ad764d2b4da6b61e126259af84f253a20f512dd10689566e5478"}, + {file = "bitarray-2.9.2-cp310-cp310-win32.whl", hash = "sha256:f00079f8e69d75c2a417de7961a77612bb77ef46c09bc74607d86de4740771ef"}, + {file = "bitarray-2.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:1bb33673e7f7190a65f0a940c1ef63266abdb391f4a3e544a47542d40a81f536"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fe71fd4b76380c2772f96f1e53a524da7063645d647a4fcd3b651bdd80ca0f2e"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d527172919cdea1e13994a66d9708a80c3d33dedcf2f0548e4925e600fef3a3a"}, + {file = "bitarray-2.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:052c5073bdcaa9dd10628d99d37a2f33ec09364b86dd1f6281e2d9f8d3db3060"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e064caa55a6ed493aca1eda06f8b3f689778bc780a75e6ad7724642ba5dc62f7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:508069a04f658210fdeee85a7a0ca84db4bcc110cbb1d21f692caa13210f24a7"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4da73ebd537d75fa7bccfc2228fcaedea0803f21dd9d0bf0d3b67fef3c4af294"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cb378eaa65cd43098f11ff5d27e48ee3b956d2c00d2d6b5bfc2a09fe183be47"}, + {file = "bitarray-2.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d14c790b91f6cbcd9b718f88ed737c78939980c69ac8c7f03dd7e60040c12951"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7eea9318293bc0ea6447e9ebfba600a62f3428bea7e9c6d42170ae4f481dbab3"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b76ffec27c7450b8a334f967366a9ebadaea66ee43f5b530c12861b1a991f503"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:76b76a07d4ee611405045c6950a1e24c4362b6b44808d4ad6eea75e0dbc59af4"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:c7d16beeaaab15b075990cd26963d6b5b22e8c5becd131781514a00b8bdd04bd"}, + {file = "bitarray-2.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60df43e868a615c7e15117a1e1c2e5e11f48f6457280eba6ddf8fbefbec7da99"}, + {file = "bitarray-2.9.2-cp311-cp311-win32.whl", hash = "sha256:e788608ed7767b7b3bbde6d49058bccdf94df0de9ca75d13aa99020cc7e68095"}, + {file = "bitarray-2.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:a23397da092ef0a8cfe729571da64c2fc30ac18243caa82ac7c4f965087506ff"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:90e3a281ffe3897991091b7c46fca38c2675bfd4399ffe79dfeded6c52715436"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:bed637b674db5e6c8a97a4a321e3e4d73e72d50b5c6b29950008a93069cc64cd"}, + {file = "bitarray-2.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e49066d251dbbe4e6e3a5c3937d85b589e40e2669ad0eef41a00f82ec17d844b"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4344e96642e2211fb3a50558feff682c31563a4c64529a931769d40832ca79"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aeb60962ec4813c539a59fbd4f383509c7222b62c3fb1faa76b54943a613e33a"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ed0f7982f10581bb16553719e5e8f933e003f5b22f7d25a68bdb30fac630a6ff"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c71d1cabdeee0cdda4669168618f0e46b7dace207b29da7b63aaa1adc2b54081"}, + {file = "bitarray-2.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b0ef2d0a6f1502d38d911d25609b44c6cc27bee0a4363dd295df78b075041b60"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6f71d92f533770fb027388b35b6e11988ab89242b883f48a6fe7202d238c61f8"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ba0734aa300757c924f3faf8148e1b8c247176a0ac8e16aefdf9c1eb19e868f7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:d91406f413ccbf4af6ab5ae7bc78f772a95609f9ddd14123db36ef8c37116d95"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:87abb7f80c0a042f3fe8e5264da1a2756267450bb602110d5327b8eaff7682e7"}, + {file = "bitarray-2.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b558ce85579b51a2e38703877d1e93b7728a7af664dd45a34e833534f0b755d"}, + {file = "bitarray-2.9.2-cp312-cp312-win32.whl", hash = "sha256:dac2399ee2889fbdd3472bfc2ede74c34cceb1ccf29a339964281a16eb1d3188"}, + {file = "bitarray-2.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:48a30d718d1a6dfc22a49547450107abe8f4afdf2abdcbe76eb9ed88edc49498"}, + {file = "bitarray-2.9.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2c6be1b651fad8f3adb7a5aa12c65b612cd9b89530969af941844ae680f7d981"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5b399ae6ab975257ec359f03b48fc00b1c1cd109471e41903548469b8feae5c"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0b3543c8a1cb286ad105f11c25d8d0f712f41c5c55f90be39f0e5a1376c7d0b0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03adaacb79e2fb8f483ab3a67665eec53bb3fd0cd5dbd7358741aef124688db3"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ae5b0657380d2581e13e46864d147a52c1e2bbac9f59b59c576e42fa7d10cf0"}, + {file = "bitarray-2.9.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c1f4bf6ea8eb9d7f30808c2e9894237a96650adfecbf5f3643862dc5982f89e"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:a8873089be2aa15494c0f81af1209f6e1237d762c5065bc4766c1b84321e1b50"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:677e67f50e2559efc677a4366707070933ad5418b8347a603a49a070890b19bc"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:a620d8ce4ea2f1c73c6b6b1399e14cb68c6915e2be3fad5808c2998ed55b4acf"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:64115ccabbdbe279c24c367b629c6b1d3da9ed36c7420129e27c338a3971bfee"}, + {file = "bitarray-2.9.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5d6fb422772e75385b76ad1c52f45a68bd4efafd8be8d0061c11877be74c4d43"}, + {file = "bitarray-2.9.2-cp36-cp36m-win32.whl", hash = "sha256:852e202875dd6dfd6139ce7ec4e98dac2b17d8d25934dc99900831e81c3adaef"}, + {file = "bitarray-2.9.2-cp36-cp36m-win_amd64.whl", hash = "sha256:7dfefdcb0dc6a3ba9936063cec65a74595571b375beabe18742b3d91d087eefd"}, + {file = "bitarray-2.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b306c4cf66912511422060f7f5e1149c8bdb404f8e00e600561b0749fdd45659"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a09c4f81635408e3387348f415521d4b94198c562c23330f560596a6aaa26eaf"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5361413fd2ecfdf44dc8f065177dc6aba97fa80a91b815586cb388763acf7f8d"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e8a9475d415ef1eaae7942df6f780fa4dcd48fce32825eda591a17abba869299"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9b87baa7bfff9a5878fcc1bffe49ecde6e647a72a64b39a69cd8a2992a43a34"}, + {file = "bitarray-2.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb6b86cfdfc503e92cb71c68766a24565359136961642504a7cc9faf936d9c88"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd56b8ae87ebc71bcacbd73615098e8a8de952ecbb5785b6b4e2b07da8a06e1f"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3fa909cfd675004aed8b4cc9df352415933656e0155a6209d878b7cb615c787e"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:b069ca9bf728e0c5c5b60e00a89df9af34cc170c695c3bfa3b372d8f40288efb"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:6067f2f07a7121749858c7daa93c8774325c91590b3e81a299621e347740c2ae"}, + {file = "bitarray-2.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:321841cdad1dd0f58fe62e80e9c9c7531f8ebf8be93f047401e930dc47425b1e"}, + {file = "bitarray-2.9.2-cp37-cp37m-win32.whl", hash = "sha256:54e16e32e60973bb83c315de9975bc1bcfc9bd50bb13001c31da159bc49b0ca1"}, + {file = "bitarray-2.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:f4dcadb7b8034aa3491ee8f5a69b3d9ba9d7d1e55c3cc1fc45be313e708277f8"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c8919fdbd3bb596b104388b56ae4b266eb28da1f2f7dff2e1f9334a21840fe96"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eb7a9d8a2e400a1026de341ad48e21670a6261a75b06df162c5c39b0d0e7c8f4"}, + {file = "bitarray-2.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6ec84668dd7b937874a2b2c293cd14ba84f37be0d196dead852e0ada9815d807"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2de9a31c34e543ae089fd2a5ced01292f725190e379921384f695e2d7184bd3"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9521f49ae121a17c0a41e5112249e6fa7f6a571245b1118de81fb86e7c1bc1ce"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6cc6545d6d76542aee3d18c1c9485fb7b9812b8df4ebe52c4535ec42081b48f"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:856bbe1616425f71c0df5ef2e8755e878d9504d5a531acba58ab4273c52c117a"}, + {file = "bitarray-2.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d4bba8042ea6ab331ade91bc435d81ad72fddb098e49108610b0ce7780c14e68"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:a035da89c959d98afc813e3c62f052690d67cfd55a36592f25d734b70de7d4b0"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:6d70b1579da7fb71be5a841a1f965d19aca0ef27f629cfc07d06b09aafd0a333"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:405b83bed28efaae6d86b6ab287c75712ead0adbfab2a1075a1b7ab47dad4d62"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:7eb8be687c50da0b397d5e0ab7ca200b5ebb639e79a9f5e285851d1944c94be9"}, + {file = "bitarray-2.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eceb551dfeaf19c609003a69a0cf8264b0efd7abc3791a11dfabf4788daf0d19"}, + {file = "bitarray-2.9.2-cp38-cp38-win32.whl", hash = "sha256:bb198c6ed1edbcdaf3d1fa3c9c9d1cdb7e179a5134ef5ee660b53cdec43b34e7"}, + {file = "bitarray-2.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:648d2f2685590b0103c67a937c2fb9e09bcc8dfb166f0c7c77bd341902a6f5b3"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ea816dc8f8e65841a8bbdd30e921edffeeb6f76efe6a1eb0da147b60d539d1cf"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4d0e32530f941c41eddfc77600ec89b65184cb909c549336463a738fab3ed285"}, + {file = "bitarray-2.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a22266fb416a3b6c258bf7f83c9fe531ba0b755a56986a81ad69dc0f3bcc070"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc6d3e80dd8239850f2604833ff3168b28909c8a9357abfed95632cccd17e3e7"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f135e804986b12bf14f2cd1eb86674c47dea86c4c5f0fa13c88978876b97ebe6"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87580c7f7d14f7ec401eda7adac1e2a25e95153e9c339872c8ae61b3208819a1"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64b433e26993127732ac7b66a7821b2537c3044355798de7c5fcb0af34b8296f"}, + {file = "bitarray-2.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e497c535f2a9b68c69d36631bf2dba243e05eb343b00b9c7bbdc8c601c6802d"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e40b3cb9fa1edb4e0175d7c06345c49c7925fe93e39ef55ecb0bc40c906b0c09"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f2f8692f95c9e377eb19ca519d30d1f884b02feb7e115f798de47570a359e43f"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f0b84fc50b6dbeced4fa390688c07c10a73222810fb0e08392bd1a1b8259de36"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d656ad38c942e38a470ddbce26b5020e08e1a7ea86b8fd413bb9024b5189993a"}, + {file = "bitarray-2.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6ab0f1dbfe5070db98771a56aa14797595acd45a1af9eadfb193851a270e7996"}, + {file = "bitarray-2.9.2-cp39-cp39-win32.whl", hash = "sha256:0a99b23ac845a9ea3157782c97465e6ae026fe0c7c4c1ed1d88f759fd6ea52d9"}, + {file = "bitarray-2.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:9bbcfc7c279e8d74b076e514e669b683f77b4a2a328585b3f16d4c5259c91222"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:43847799461d8ba71deb4d97b47250c2c2fb66d82cd3cb8b4caf52bb97c03034"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f44381b0a4bdf64416082f4f0e7140377ae962c0ced6f983c6d7bbfc034040"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a484061616fb4b158b80789bd3cb511f399d2116525a8b29b6334c68abc2310f"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ff9e38356cc803e06134cf8ae9758e836ccd1b793135ef3db53c7c5d71e93bc"}, + {file = "bitarray-2.9.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b44105792fbdcfbda3e26ee88786790fda409da4c71f6c2b73888108cf8f062f"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7e913098de169c7fc890638ce5e171387363eb812579e637c44261460ac00aa2"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6fe315355cdfe3ed22ef355b8bdc81a805ca4d0949d921576560e5b227a1112"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f708e91fdbe443f3bec2df394ed42328fb9b0446dff5cb4199023ac6499e09fd"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b7b09489b71f9f1f64c0fa0977e250ec24500767dab7383ba9912495849cadf"}, + {file = "bitarray-2.9.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:128cc3488176145b9b137fdcf54c1c201809bbb8dd30b260ee40afe915843b43"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21f21e7f56206be346bdbda2a6bdb2165a5e6a11821f88fd4911c5a6bbbdc7e2"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f4dd3af86dd8a617eb6464622fb64ca86e61ce99b59b5c35d8cd33f9c30603d"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6465de861aff7a2559f226b37982007417eab8c3557543879987f58b453519bd"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbaf2bb71d6027152d603f1d5f31e0dfd5e50173d06f877bec484e5396d4594b"}, + {file = "bitarray-2.9.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2f32948c86e0d230a296686db28191b67ed229756f84728847daa0c7ab7406e3"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:be94e5a685e60f9d24532af8fe5c268002e9016fa80272a94727f435de3d1003"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5cc9381fd54f3c23ae1039f977bfd6d041a5c3c1518104f616643c3a5a73b15"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cd926e8ae4d1ed1ac4a8f37212a62886292f692bc1739fde98013bf210c2d175"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:461a3dafb9d5fda0bb3385dc507d78b1984b49da3fe4c6d56c869a54373b7008"}, + {file = "bitarray-2.9.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:393cb27fd859af5fd9c16eb26b1c59b17b390ff66b3ae5d0dd258270191baf13"}, + {file = "bitarray-2.9.2.tar.gz", hash = "sha256:a8f286a51a32323715d77755ed959f94bef13972e9a2fe71b609e40e6d27957e"}, +] + +[[package]] +name = "black" +version = "21.12b0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.6.2" +files = [ + {file = "black-21.12b0-py3-none-any.whl", hash = "sha256:a615e69ae185e08fdd73e4715e260e2479c861b5740057fde6e8b4e3b7dd589f"}, + {file = "black-21.12b0.tar.gz", hash = "sha256:77b80f693a569e2e527958459634f18df9b0ba2625ba4e0c2d5da5be42e6f2b3"}, +] + +[package.dependencies] +click = ">=7.1.2" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0,<1" +platformdirs = ">=2" +tomli = ">=0.2.6,<2.0.0" +typing-extensions = [ + {version = ">=3.10.0.0", markers = "python_version < \"3.10\""}, + {version = ">=3.10.0.0,<3.10.0.1 || >3.10.0.1", markers = "python_version >= \"3.10\""}, +] + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +python2 = ["typed-ast (>=1.4.3)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "blinker" +version = "1.8.2" +description = "Fast, simple object-to-object and broadcast signaling" +optional = false +python-versions = ">=3.8" +files = [ + {file = "blinker-1.8.2-py3-none-any.whl", hash = "sha256:1779309f71bf239144b9399d06ae925637cf6634cf6bd131104184531bf67c01"}, + {file = "blinker-1.8.2.tar.gz", hash = "sha256:8f77b09d3bf7c795e969e9486f39c2c5e9c39d4ee07424be2bc594ece9642d83"}, +] + +[[package]] +name = "cachelib" +version = "0.9.0" +description = "A collection of cache libraries in the same API interface." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachelib-0.9.0-py3-none-any.whl", hash = "sha256:811ceeb1209d2fe51cd2b62810bd1eccf70feba5c52641532498be5c675493b3"}, + {file = "cachelib-0.9.0.tar.gz", hash = "sha256:38222cc7c1b79a23606de5c2607f4925779e37cdcea1c2ad21b8bae94b5425a5"}, +] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "ckzg" +version = "1.0.2" +description = "Python bindings for C-KZG-4844" +optional = false +python-versions = "*" +files = [ + {file = "ckzg-1.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bdd082bc0f2a595e3546658ecbe1ff78fe65b0ab7e619a8197a62d94f46b5b46"}, + {file = "ckzg-1.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:50ca4af4e2f1a1e8b0a7e97b3aef39dedbb0d52d90866ece424f13f8df1b5972"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e9dc671b0a307ea65d0a216ca496c272dd3c1ed890ddc2a306da49b0d8ffc83"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d95e97a0d0f7758119bb905fb5688222b1556de465035614883c42fe4a047d1f"}, + {file = "ckzg-1.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:27261672154cbd477d84d289845b0022fbdbe2ba45b7a2a2051c345fa04c8334"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c16d5ee1ddbbbad0367ff970b3ec9f6d1879e9f928023beda59ae9e16ad99e4c"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:09043738b029bdf4fdc82041b395cfc6f5b5cf63435e5d4d685d24fd14c834d3"}, + {file = "ckzg-1.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3c0afa232d2312e3101aaddb6971b486b0038a0f9171500bc23143f5749eff55"}, + {file = "ckzg-1.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:96e8281b6d58cf91b9559e1bd38132161d63467500838753364c68e825df2e2c"}, + {file = "ckzg-1.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b874167de1d6de72890a2ad5bd9aa7adbddc41c3409923b59cf4ef27f83f79da"}, + {file = "ckzg-1.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3d2ccd68b0743e20e853e31a08da490a8d38c7f12b9a0c4ee63ef5afa0dc2427"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e8d534ddbe785c44cf1cd62ee32d78b4310d66dd70e42851f5468af655b81f5"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c732cda00c76b326f39ae97edfc6773dd231b7c77288b38282584a7aee77c3a7"}, + {file = "ckzg-1.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abc5a27284db479ead4c053ff086d6e222914f1b0aa08b80eabfa116dbed4f7a"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e6bd5006cb3e802744309450183087a6594d50554814eee19065f7064dff7b05"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3594470134eda7adf2813ad3f1da55ced98c8a393262f47ce3890c5afa05b23e"}, + {file = "ckzg-1.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fea56f39e48b60c1ff6f751c47489e353d1bd95cae65c429cf5f87735d794431"}, + {file = "ckzg-1.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:f769eb2e1056ca396462460079f6849c778f58884bb24b638ff7028dd2120b65"}, + {file = "ckzg-1.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e3cb2f8c767aee57e88944f90848e8689ce43993b9ff21589cfb97a562208fe7"}, + {file = "ckzg-1.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b29889f5bc5db530f766871c0ff4133e7270ecf63aaa3ca756d3b2731980802"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bfcc70fb76b3d36125d646110d5001f2aa89c1c09ff5537a4550cdb7951f44d4"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ca8a256cdd56d06bc5ef24caac64845240dbabca402c5a1966d519b2514b4ec"}, + {file = "ckzg-1.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ea91b0236384f93ad1df01d530672f09e254bd8c3cf097ebf486aebb97f6c8c"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:65311e72780105f239d1d66512629a9f468b7c9f2609b8567fc68963ac638ef9"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0d7600ce7a73ac41d348712d0c1fe5e4cb6caa329377064cfa3a6fd8fbffb410"}, + {file = "ckzg-1.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:19893ee7bd7da8688382cb134cb9ee7bce5c38e3a9386e3ed99bb010487d2d17"}, + {file = "ckzg-1.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:c3e1a9a72695e777497e95bb2213316a1138f82d1bb5d67b9c029a522d24908e"}, + {file = "ckzg-1.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:a2f59da9cb82b6a4be615f2561a255731eededa7ecd6ba4b2f2dedfc918ef137"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c915e1f2ef51657c3255d8b1e2aea6e0b93348ae316b2b79eaadfb17ad8f514e"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcc0d2031fcabc4be37e9e602c926ef9347238d2f58c1b07e0c147f60b9e760b"}, + {file = "ckzg-1.0.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cdaad2745425d7708e76e8e56a52fdaf5c5cc1cfefd5129d24ff8dbe06a012d"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:1ec775649daade1b93041aac9c1660c2ad9828b57ccd2eeb5a3074d8f05e544a"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:02f9cc3e38b3702ec5895a1ebf927fd02b8f5c2f93c7cb9e438581b5b74472c8"}, + {file = "ckzg-1.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0e816af31951b5e94e6bc069f21fe783427c190526e0437e16c4488a34ddcacc"}, + {file = "ckzg-1.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:651ba33ee2d7fefff14ca519a72996b733402f8b043fbfef12d5fe2a442d86d8"}, + {file = "ckzg-1.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:489763ad92e2175fb6ab455411f03ec104c630470d483e11578bf2e00608f283"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:69e1376284e9a5094d7c4d3e552202d6b32a67c5acc461b0b35718d8ec5c7363"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb9d0b09ca1bdb5955b626d6645f811424ae0fcab47699a1a938a3ce0438c25f"}, + {file = "ckzg-1.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d87a121ace8feb6c9386f247e7e36ef55e584fc8a6b1bc2c60757a59c1efe364"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:97c27153fab853f017fed159333b27beeb2e0da834c92c9ecdc26d0e5c3983b3"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b26799907257c39471cb3665f66f7630797140131606085c2c94a7094ab6ddf2"}, + {file = "ckzg-1.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:283a40c625222560fda3dcb912b666f7d50f9502587b73c4358979f519f1c961"}, + {file = "ckzg-1.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:5f029822d27c52b9c3dbe5706408b099da779f10929be0422a09a34aa026a872"}, + {file = "ckzg-1.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:edaea8fb50b01c6c19768d9305ad365639a8cd804754277d5108dcae4808f00b"}, + {file = "ckzg-1.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:27be65c88d5d773a30e6f198719cefede7e25cad807384c3d65a09c11616fc9d"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9ac729c5c6f3d2c030c0bc8c9e10edc253e36f002cfe227292035009965d349"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1528bc2b95aac6d184a90b023602c40d7b11b577235848c1b5593c00cf51d37"}, + {file = "ckzg-1.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:071dc7fc179316ce1bfabaa056156e4e84f312c4560ab7b9529a3b9a84019df3"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:895044069de7010be6c7ee703f03fd7548267a0823cf60b9dd26ec50267dd9e8"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ed8c99cd3d9af596470e0481fd58931007288951719bad026f0dd486dd0ec11"}, + {file = "ckzg-1.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:74d87eafe561d4bfb544a4f3419d26c56ad7de00f39789ef0fdb09515544d12e"}, + {file = "ckzg-1.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:54d71e5ca416bd51c543f9f51e426e6792f8a0280b83aef92faad1b826f401ea"}, + {file = "ckzg-1.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:da2d9988781a09a4577ee7ea8f51fe4a94b4422789a523164f5ba3118566ad41"}, + {file = "ckzg-1.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d9e030af7d6acdcb356fddfb095048bc8e880fe4cd70ff2206c64f33bf384a0d"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:145ae31c3d499d1950567bd636dc5b24292b600296b9deb5523bc20d8f7b51c3"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d81e68e84d80084da298471ad5eaddfcc1cf73545cb24e9453550c8186870982"}, + {file = "ckzg-1.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c67064bbbeba1a6892c9c80b3d0c2a540ff48a5ca5356fdb2a8d998b264e43e6"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:99694917eb6decefc0d330d9887a89ea770824b2fa76eb830bab5fe57ea5c20c"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fca227ce0ce3427254a113fdb3aed5ecd99c1fc670cb0c60cc8a2154793678e4"}, + {file = "ckzg-1.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a66a690d3d1801085d11de6825df47a99b465ff32dbe90be4a3c9f43c577da96"}, + {file = "ckzg-1.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:272adfe471380d10e4a0e1639d877e504555079a60233dd82249c799b15be81e"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f37be0054ebb4b8ac6e6d5267290b239b09e7ddc611776051b4c3c4032d161ba"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:611c03a170f0f746180eeb0cc28cdc6f954561b8eb9013605a046de86520ee6b"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b2f0ab341f3c33702ce64e1c101116c7462a25686d0b1a0193ca654ad4f96e"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab29fc61fbd32096b82b02e6b18ae0d7423048d3540b7b90805b16ae10bdb769"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e43741e7453262aa3ba1754623d7864250b33751bd850dd548e3ed6bd1911093"}, + {file = "ckzg-1.0.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:155eacc237cb28c9eafda1c47a89e6e4550f1c2e711f2eee21e0bb2f4df75546"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d31d7fbe396a51f43375e38c31bc3a96c7996882582f95f3fcfd54acfa7b3ce6"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d3d049186c9966e9140de39a9979d7adcfe22f8b02d2852c94d3c363235cc18"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88728fbd410d61bd5d655ac50b842714c38bc34ff717f73592132d28911fc88e"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:052d302058d72431acc9dd4a9c76854c8dfce10c698deef5252884e32a1ac7bf"}, + {file = "ckzg-1.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:633110a9431231664be2ad32baf10971547f18289d33967654581b9ae9c94a7e"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f439c9e5297ae29a700f6d55de1525e2e295dbbb7366f0974c8702fca9e536b9"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:94f7eb080c00c0ccbd4fafad69f0b35b624a6a229a28e11d365b60b58a072832"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f876783ec654b7b9525503c2a0a1b086e5d4f52ff65cac7e8747769b0c2e5468"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7e039800e50592580171830e788ef4a1d6bb54300d074ae9f9119e92aefc568"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a8cccf0070a29bc01493179db2e61220ee1a6cb17f8ea41c68a2f043ace87f"}, + {file = "ckzg-1.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f86cef801d7b0838e17b6ee2f2c9e747447d91ad1220a701baccdf7ef11a3c8"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2433a89af4158beddebbdd66fae95b34d40f2467bee8dc40df0333de5e616b5f"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c49d5dc0918ad912777720035f9820bdbb6c7e7d1898e12506d44ab3c938d525"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:331d49bc72430a3f85ea6ecb55a0d0d65f66a21d61af5783b465906a741366d5"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e86627bc33bc63b8de869d7d5bfa9868619a4f3e4e7082103935c52f56c66b5"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab6a2ba2706b5eaa1ce6bc7c4e72970bf9587e2e0e482e5fb4df1996bccb7a40"}, + {file = "ckzg-1.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:8bca5e7c38d913fabc24ad09545f78ba23cfc13e1ac8250644231729ca908549"}, + {file = "ckzg-1.0.2.tar.gz", hash = "sha256:4295acc380f8d42ebea4a4a0a68c424a322bb335a33bad05c72ead8cbb28d118"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "cytoolz" +version = "0.12.3" +description = "Cython implementation of Toolz: High performance functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cytoolz-0.12.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bbe58e26c84b163beba0fbeacf6b065feabc8f75c6d3fe305550d33f24a2d346"}, + {file = "cytoolz-0.12.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c51b66ada9bfdb88cf711bf350fcc46f82b83a4683cf2413e633c31a64df6201"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e70d9c615e5c9dc10d279d1e32e846085fe1fd6f08d623ddd059a92861f4e3dd"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a83f4532707963ae1a5108e51fdfe1278cc8724e3301fee48b9e73e1316de64f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d028044524ee2e815f36210a793c414551b689d4f4eda28f8bbb0883ad78bf5f"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c2875bcd1397d0627a09a4f9172fa513185ad302c63758efc15b8eb33cc2a98"}, + {file = "cytoolz-0.12.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:131ff4820e5d64a25d7ad3c3556f2d8aa65c66b3f021b03f8a8e98e4180dd808"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04afa90d9d9d18394c40d9bed48c51433d08b57c042e0e50c8c0f9799735dcbd"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:dc1ca9c610425f9854323669a671fc163300b873731584e258975adf50931164"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:bfa3f8e01bc423a933f2e1c510cbb0632c6787865b5242857cc955cae220d1bf"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:f702e295dddef5f8af4a456db93f114539b8dc2a7a9bc4de7c7e41d169aa6ec3"}, + {file = "cytoolz-0.12.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0fbad1fb9bb47e827d00e01992a099b0ba79facf5e5aa453be066033232ac4b5"}, + {file = "cytoolz-0.12.3-cp310-cp310-win32.whl", hash = "sha256:8587c3c3dbe78af90c5025288766ac10dc2240c1e76eb0a93a4e244c265ccefd"}, + {file = "cytoolz-0.12.3-cp310-cp310-win_amd64.whl", hash = "sha256:9e45803d9e75ef90a2f859ef8f7f77614730f4a8ce1b9244375734567299d239"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3ac4f2fb38bbc67ff1875b7d2f0f162a247f43bd28eb7c9d15e6175a982e558d"}, + {file = "cytoolz-0.12.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0cf1e1e96dd86829a0539baf514a9c8473a58fbb415f92401a68e8e52a34ecd5"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a438701c6141dd34eaf92e9e9a1f66e23a22f7840ef8a371eba274477de85d"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6b6f11b0d7ed91be53166aeef2a23a799e636625675bb30818f47f41ad31821"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a7fde09384d23048a7b4ac889063761e44b89a0b64015393e2d1d21d5c1f534a"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d3bfe45173cc8e6c76206be3a916d8bfd2214fb2965563e288088012f1dabfc"}, + {file = "cytoolz-0.12.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27513a5d5b6624372d63313574381d3217a66e7a2626b056c695179623a5cb1a"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d294e5e81ff094fe920fd545052ff30838ea49f9e91227a55ecd9f3ca19774a0"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:727b01a2004ddb513496507a695e19b5c0cfebcdfcc68349d3efd92a1c297bf4"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:fe1e1779a39dbe83f13886d2b4b02f8c4b10755e3c8d9a89b630395f49f4f406"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:de74ef266e2679c3bf8b5fc20cee4fc0271ba13ae0d9097b1491c7a9bcadb389"}, + {file = "cytoolz-0.12.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:9e04d22049233394e0b08193aca9737200b4a2afa28659d957327aa780ddddf2"}, + {file = "cytoolz-0.12.3-cp311-cp311-win32.whl", hash = "sha256:20d36430d8ac809186736fda735ee7d595b6242bdb35f69b598ef809ebfa5605"}, + {file = "cytoolz-0.12.3-cp311-cp311-win_amd64.whl", hash = "sha256:780c06110f383344d537f48d9010d79fa4f75070d214fc47f389357dd4f010b6"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:86923d823bd19ce35805953b018d436f6b862edd6a7c8b747a13d52b39ed5716"}, + {file = "cytoolz-0.12.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3e61acfd029bfb81c2c596249b508dfd2b4f72e31b7b53b62e5fb0507dd7293"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd728f4e6051af6af234651df49319da1d813f47894d4c3c8ab7455e01703a37"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fe8c6267caa7ec67bcc37e360f0d8a26bc3bdce510b15b97f2f2e0143bdd3673"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:99462abd8323c52204a2a0ce62454ce8fa0f4e94b9af397945c12830de73f27e"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da125221b1fa25c690fcd030a54344cecec80074df018d906fc6a99f46c1e3a6"}, + {file = "cytoolz-0.12.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c18e351956f70db9e2d04ff02f28e9a41839250d3f936a4c8a1eabd1c3094d2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:921e6d2440ac758c4945c587b1d1d9b781b72737ac0c0ca5d5e02ca1db8bded2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:1651a9bd591a8326329ce1d6336f3129161a36d7061a4d5ea9e5377e033364cf"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:8893223b87c2782bd59f9c4bd5c7bf733edd8728b523c93efb91d7468b486528"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:e4d2961644153c5ae186db964aa9f6109da81b12df0f1d3494b4e5cf2c332ee2"}, + {file = "cytoolz-0.12.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:71b6eb97f6695f7ba8ce69c49b707a351c5f46fd97f5aeb5f6f2fb0d6e72b887"}, + {file = "cytoolz-0.12.3-cp312-cp312-win32.whl", hash = "sha256:cee3de65584e915053412cd178729ff510ad5f8f585c21c5890e91028283518f"}, + {file = "cytoolz-0.12.3-cp312-cp312-win_amd64.whl", hash = "sha256:9eef0d23035fa4dcfa21e570961e86c375153a7ee605cdd11a8b088c24f707f6"}, + {file = "cytoolz-0.12.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d9a38332cfad2a91e89405b7c18b3f00e2edc951c225accbc217597d3e4e9fde"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f501ae1353071fa5d6677437bbeb1aeb5622067dce0977cedc2c5ec5843b202"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:56f899758146a52e2f8cfb3fb6f4ca19c1e5814178c3d584de35f9e4d7166d91"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800f0526adf9e53d3c6acda748f4def1f048adaa780752f154da5cf22aa488a2"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0976a3fcb81d065473173e9005848218ce03ddb2ec7d40dd6a8d2dba7f1c3ae"}, + {file = "cytoolz-0.12.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c835eab01466cb67d0ce6290601ebef2d82d8d0d0a285ed0d6e46989e4a7a71a"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:4fba0616fcd487e34b8beec1ad9911d192c62e758baa12fcb44448b9b6feae22"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6f6e8207d732651e0204779e1ba5a4925c93081834570411f959b80681f8d333"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8119bf5961091cfe644784d0bae214e273b3b3a479f93ee3baab97bbd995ccfe"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:7ad1331cb68afeec58469c31d944a2100cee14eac221553f0d5218ace1a0b25d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:92c53d508fb8a4463acc85b322fa24734efdc66933a5c8661bdc862103a3373d"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win32.whl", hash = "sha256:2c6dd75dae3d84fa8988861ab8b1189d2488cb8a9b8653828f9cd6126b5e7abd"}, + {file = "cytoolz-0.12.3-cp37-cp37m-win_amd64.whl", hash = "sha256:caf07a97b5220e6334dd32c8b6d8b2bd255ca694eca5dfe914bb5b880ee66cdb"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ed0cfb9326747759e2ad81cb6e45f20086a273b67ac3a4c00b19efcbab007c60"}, + {file = "cytoolz-0.12.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:96a5a0292575c3697121f97cc605baf2fd125120c7dcdf39edd1a135798482ca"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b76f2f50a789c44d6fd7f773ec43d2a8686781cd52236da03f7f7d7998989bee"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2905fdccacc64b4beba37f95cab9d792289c80f4d70830b70de2fc66c007ec01"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1ebe23028eac51251f22ba01dba6587d30aa9c320372ca0c14eeab67118ec3f"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c715404a3825e37fe3966fe84c5f8a1f036e7640b2a02dbed96cac0c933451"}, + {file = "cytoolz-0.12.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bac0adffc1b6b6a4c5f1fd1dd2161afb720bcc771a91016dc6bdba59af0a5d3"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:37441bf4a2a4e2e0fe9c3b0ea5e72db352f5cca03903977ffc42f6f6c5467be9"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:f04037302049cb30033f7fa4e1d0e44afe35ed6bfcf9b380fc11f2a27d3ed697"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f37b60e66378e7a116931d7220f5352186abfcc950d64856038aa2c01944929c"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:ec9be3e4b6f86ea8b294d34c990c99d2ba6c526ef1e8f46f1d52c263d4f32cd7"}, + {file = "cytoolz-0.12.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0e9199c9e3fbf380a92b8042c677eb9e7ed4bccb126de5e9c0d26f5888d96788"}, + {file = "cytoolz-0.12.3-cp38-cp38-win32.whl", hash = "sha256:18cd61e078bd6bffe088e40f1ed02001387c29174750abce79499d26fa57f5eb"}, + {file = "cytoolz-0.12.3-cp38-cp38-win_amd64.whl", hash = "sha256:765b8381d4003ceb1a07896a854eee2c31ebc950a4ae17d1e7a17c2a8feb2a68"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b4a52dd2a36b0a91f7aa50ca6c8509057acc481a24255f6cb07b15d339a34e0f"}, + {file = "cytoolz-0.12.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:581f1ce479769fe7eeb9ae6d87eadb230df8c7c5fff32138162cdd99d7fb8fc3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46f505d4c6eb79585c8ad0b9dc140ef30a138c880e4e3b40230d642690e36366"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59276021619b432a5c21c01cda8320b9cc7dbc40351ffc478b440bfccd5bbdd3"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e44f4c25e1e7cf6149b499c74945a14649c8866d36371a2c2d2164e4649e7755"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c64f8e60c1dd69e4d5e615481f2d57937746f4a6be2d0f86e9e7e3b9e2243b5e"}, + {file = "cytoolz-0.12.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33c63186f3bf9d7ef1347bc0537bb9a0b4111a0d7d6e619623cabc18fef0dc3b"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fdddb9d988405f24035234f1e8d1653ab2e48cc2404226d21b49a129aefd1d25"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6986632d8a969ea1e720990c818dace1a24c11015fd7c59b9fea0b65ef71f726"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0ba1cbc4d9cd7571c917f88f4a069568e5121646eb5d82b2393b2cf84712cf2a"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:7d267ffc9a36c0a9a58c7e0adc9fa82620f22e4a72533e15dd1361f57fc9accf"}, + {file = "cytoolz-0.12.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:95e878868a172a41fbf6c505a4b967309e6870e22adc7b1c3b19653d062711fa"}, + {file = "cytoolz-0.12.3-cp39-cp39-win32.whl", hash = "sha256:8e21932d6d260996f7109f2a40b2586070cb0a0cf1d65781e156326d5ebcc329"}, + {file = "cytoolz-0.12.3-cp39-cp39-win_amd64.whl", hash = "sha256:0d8edfbc694af6c9bda4db56643fb8ed3d14e47bec358c2f1417de9a12d6d1fb"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55f9bd1ae6c2a27eda5abe2a0b65a83029d2385c5a1da7b8ef47af5905d7e905"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d271393c378282727f1231d40391ae93b93ddc0997448acc21dd0cb6a1e56d"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee98968d6a66ee83a8ceabf31182189ab5d8598998c8ce69b6d5843daeb2db60"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01cfb8518828c1189200c02a5010ea404407fb18fd5589e29c126e84bbeadd36"}, + {file = "cytoolz-0.12.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:456395d7aec01db32bf9e6db191d667347c78d8d48e77234521fa1078f60dabb"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:cd88028bb897fba99ddd84f253ca6bef73ecb7bdf3f3cf25bc493f8f97d3c7c5"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59b19223e7f7bd7a73ec3aa6fdfb73b579ff09c2bc0b7d26857eec2d01a58c76"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a79d72b08048a0980a59457c239555f111ac0c8bdc140c91a025f124104dbb4"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1dd70141b32b717696a72b8876e86bc9c6f8eff995c1808e299db3541213ff82"}, + {file = "cytoolz-0.12.3-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:a1445c91009eb775d479e88954c51d0b4cf9a1e8ce3c503c2672d17252882647"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ca6a9a9300d5bda417d9090107c6d2b007683efc59d63cc09aca0e7930a08a85"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be6feb903d2a08a4ba2e70e950e862fd3be9be9a588b7c38cee4728150a52918"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b6f43f086e5a965d33d62a145ae121b4ccb6e0789ac0acc895ce084fec8c65"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:534fa66db8564d9b13872d81d54b6b09ae592c585eb826aac235bd6f1830f8ad"}, + {file = "cytoolz-0.12.3-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:fea649f979def23150680de1bd1d09682da3b54932800a0f90f29fc2a6c98ba8"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a447247ed312dd64e3a8d9483841ecc5338ee26d6e6fbd29cd373ed030db0240"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba3f843aa89f35467b38c398ae5b980a824fdbdb94065adc6ec7c47a0a22f4c7"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:582c22f97a380211fb36a7b65b1beeb84ea11d82015fa84b054be78580390082"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47feb089506fc66e1593cd9ade3945693a9d089a445fbe9a11385cab200b9f22"}, + {file = "cytoolz-0.12.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:ba9002d2f043943744a9dc8e50a47362bcb6e6f360dc0a1abcb19642584d87bb"}, + {file = "cytoolz-0.12.3.tar.gz", hash = "sha256:4503dc59f4ced53a54643272c61dc305d1dbbfbd7d6bdf296948de9f34c3a282"}, +] + +[package.dependencies] +toolz = ">=0.8.0" + +[package.extras] +cython = ["cython"] + +[[package]] +name = "dataclass-wizard" +version = "0.22.3" +description = "Marshal dataclasses to/from JSON. Use field properties with initial values. Construct a dataclass schema with JSON input." +optional = false +python-versions = "*" +files = [ + {file = "dataclass-wizard-0.22.3.tar.gz", hash = "sha256:4c46591782265058f1148cfd1f54a3a91221e63986fdd04c9d59f4ced61f4424"}, + {file = "dataclass_wizard-0.22.3-py2.py3-none-any.whl", hash = "sha256:63751203e54b9b9349212cc185331da73c1adc99c51312575eb73bb5c00c1962"}, +] + +[package.dependencies] +typing-extensions = {version = ">=3.7.4.2", markers = "python_version <= \"3.9\""} + +[package.extras] +dev = ["Sphinx (==5.3.0)", "bump2version (==1.0.1)", "coverage (>=6.2)", "dataclass-factory (==2.12)", "dataclasses-json (==0.5.6)", "flake8 (>=3)", "jsons (==1.6.1)", "pip (>=21.3.1)", "pytest (==7.0.1)", "pytest-cov (==3.0.0)", "pytest-mock (>=3.6.1)", "pytimeparse (==1.1.8)", "sphinx-issues (==3.0.1)", "sphinx-issues (==4.0.0)", "tox (==3.24.5)", "twine (==3.8.0)", "watchdog[watchmedo] (==2.1.6)", "wheel (==0.37.1)", "wheel (==0.42.0)"] +timedelta = ["pytimeparse (>=1.1.7)"] +yaml = ["PyYAML (>=5.3)"] + +[[package]] +name = "deprecated" +version = "1.2.14" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "Deprecated-1.2.14-py2.py3-none-any.whl", hash = "sha256:6fac8b097794a90302bdbb17b9b815e732d3c4720583ff1b198499d78470466c"}, + {file = "Deprecated-1.2.14.tar.gz", hash = "sha256:e5323eb936458dccc2582dc6f9c322c852a775a27065ff2b0c4970b9d53d01b3"}, +] + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest-Cov", "bump2version (<1)", "sphinx (<2)", "tox"] + +[[package]] +name = "dunamai" +version = "1.22.0" +description = "Dynamic version generation" +optional = false +python-versions = ">=3.5" +files = [ + {file = "dunamai-1.22.0-py3-none-any.whl", hash = "sha256:eab3894b31e145bd028a74b13491c57db01986a7510482c9b5fff3b4e53d77b7"}, + {file = "dunamai-1.22.0.tar.gz", hash = "sha256:375a0b21309336f0d8b6bbaea3e038c36f462318c68795166e31f9873fdad676"}, +] + +[package.dependencies] +packaging = ">=20.9" + +[[package]] +name = "et-xmlfile" +version = "1.1.0" +description = "An implementation of lxml.xmlfile for the standard library" +optional = false +python-versions = ">=3.6" +files = [ + {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, + {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, +] + +[[package]] +name = "eth-abi" +version = "5.1.0" +description = "eth_abi: Python utilities for working with Ethereum ABI definitions, especially encoding and decoding" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_abi-5.1.0-py3-none-any.whl", hash = "sha256:84cac2626a7db8b7d9ebe62b0fdca676ab1014cc7f777189e3c0cd721a4c16d8"}, + {file = "eth_abi-5.1.0.tar.gz", hash = "sha256:33ddd756206e90f7ddff1330cc8cac4aa411a824fe779314a0a52abea2c8fc14"}, +] + +[package.dependencies] +eth-typing = ">=3.0.0" +eth-utils = ">=2.0.0" +parsimonious = ">=0.10.0,<0.11.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "hypothesis (>=4.18.2,<5.0.0)", "pytest (>=7.0.0)", "pytest-pythonpath (>=0.7.1)", "pytest-timeout (>=2.0.0)", "pytest-xdist (>=2.4.0)"] +tools = ["hypothesis (>=4.18.2,<5.0.0)"] + +[[package]] +name = "eth-account" +version = "0.11.3" +description = "eth-account: Sign Ethereum transactions and messages with local private keys" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_account-0.11.3-py3-none-any.whl", hash = "sha256:16cf58aabc65171fc206489899b7e5546e3215e1a4debc12dbd55345c979081e"}, + {file = "eth_account-0.11.3.tar.gz", hash = "sha256:a712a9534638a7cfaa4cc069f1b9d5cefeee70362cfc3a7b0a2534ee61ce76c9"}, +] + +[package.dependencies] +bitarray = ">=2.4.0" +ckzg = ">=0.4.3,<2" +eth-abi = ">=4.0.0-b.2" +eth-keyfile = ">=0.6.0" +eth-keys = ">=0.4.0" +eth-rlp = ">=0.3.0" +eth-utils = ">=2.0.0" +hexbytes = ">=0.1.0,<0.4.0" +rlp = ">=1.0.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "coverage", "hypothesis (>=4.18.0,<5)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["coverage", "hypothesis (>=4.18.0,<5)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-hash" +version = "0.7.0" +description = "eth-hash: The Ethereum hashing function, keccak256, sometimes (erroneously) called sha3" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-hash-0.7.0.tar.gz", hash = "sha256:bacdc705bfd85dadd055ecd35fd1b4f846b671add101427e089a4ca2e8db310a"}, + {file = "eth_hash-0.7.0-py3-none-any.whl", hash = "sha256:b8d5a230a2b251f4a291e3164a23a14057c4a6de4b0aa4a16fa4dc9161b57e2f"}, +] + +[package.dependencies] +pycryptodome = {version = ">=3.6.6,<4", optional = true, markers = "extra == \"pycryptodome\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +pycryptodome = ["pycryptodome (>=3.6.6,<4)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keyfile" +version = "0.8.1" +description = "eth-keyfile: A library for handling the encrypted keyfiles used to store ethereum private keys" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keyfile-0.8.1-py3-none-any.whl", hash = "sha256:65387378b82fe7e86d7cb9f8d98e6d639142661b2f6f490629da09fddbef6d64"}, + {file = "eth_keyfile-0.8.1.tar.gz", hash = "sha256:9708bc31f386b52cca0969238ff35b1ac72bd7a7186f2a84b86110d3c973bec1"}, +] + +[package.dependencies] +eth-keys = ">=0.4.0" +eth-utils = ">=2" +pycryptodome = ">=3.6.6,<4" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-keys" +version = "0.5.1" +description = "eth-keys: Common API for Ethereum key operations" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_keys-0.5.1-py3-none-any.whl", hash = "sha256:ad13d920a2217a49bed3a1a7f54fb0980f53caf86d3bbab2139fd3330a17b97e"}, + {file = "eth_keys-0.5.1.tar.gz", hash = "sha256:2b587e4bbb9ac2195215a7ab0c0fb16042b17d4ec50240ed670bbb8f53da7a48"}, +] + +[package.dependencies] +eth-typing = ">=3" +eth-utils = ">=2" + +[package.extras] +coincurve = ["coincurve (>=12.0.0)"] +dev = ["asn1tools (>=0.146.2)", "build (>=0.9.0)", "bumpversion (>=0.5.3)", "coincurve (>=12.0.0)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "ipython", "pre-commit (>=3.4.0)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["towncrier (>=21,<22)"] +test = ["asn1tools (>=0.146.2)", "eth-hash[pysha3]", "factory-boy (>=3.0.1)", "hypothesis (>=5.10.3)", "pyasn1 (>=0.4.5)", "pytest (>=7.0.0)"] + +[[package]] +name = "eth-rlp" +version = "1.0.1" +description = "eth-rlp: RLP definitions for common Ethereum objects in Python" +optional = false +python-versions = ">=3.8, <4" +files = [ + {file = "eth-rlp-1.0.1.tar.gz", hash = "sha256:d61dbda892ee1220f28fb3663c08f6383c305db9f1f5624dc585c9cd05115027"}, + {file = "eth_rlp-1.0.1-py3-none-any.whl", hash = "sha256:dd76515d71654277377d48876b88e839d61553aaf56952e580bb7cebef2b1517"}, +] + +[package.dependencies] +eth-utils = ">=2.0.0" +hexbytes = ">=0.1.0,<1" +rlp = ">=0.6.0" +typing-extensions = {version = ">=4.0.1", markers = "python_version <= \"3.11\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["eth-hash[pycryptodome]", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-typing" +version = "4.4.0" +description = "eth-typing: Common type annotations for ethereum python packages" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_typing-4.4.0-py3-none-any.whl", hash = "sha256:a5e30a6e69edda7b1d1e96e9d71bab48b9bb988a77909d8d1666242c5562f841"}, + {file = "eth_typing-4.4.0.tar.gz", hash = "sha256:93848083ac6bb4c20cc209ea9153a08b0a528be23337c889f89e1e5ffbe9807d"}, +] + +[package.dependencies] +typing-extensions = ">=4.5.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "eth-utils" +version = "4.1.1" +description = "eth-utils: Common utility functions for python code that interacts with Ethereum" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "eth_utils-4.1.1-py3-none-any.whl", hash = "sha256:ccbbac68a6d65cb6e294c5bcb6c6a5cec79a241c56dc5d9c345ed788c30f8534"}, + {file = "eth_utils-4.1.1.tar.gz", hash = "sha256:71c8d10dec7494aeed20fa7a4d52ec2ce4a2e52fdce80aab4f5c3c19f3648b25"}, +] + +[package.dependencies] +cytoolz = {version = ">=0.10.1", markers = "implementation_name == \"cpython\""} +eth-hash = ">=0.3.1" +eth-typing = ">=3.0.0" +toolz = {version = ">0.8.2", markers = "implementation_name == \"pypy\""} + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "eth-hash[pycryptodome]", "hypothesis (>=4.43.0)", "ipython", "mypy (==1.5.1)", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +test = ["hypothesis (>=4.43.0)", "mypy (==1.5.1)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "ethereum-dasm" +version = "0.1.5" +description = "An ethereum bytecode disassembler with static and dynamic analysis features" +optional = false +python-versions = "*" +files = [ + {file = "ethereum_dasm-0.1.5-py3-none-any.whl", hash = "sha256:998aff7fbc1ef70210c1a34ffa2788e25f4e014c61e164411f6e4b4c2fdf2907"}, +] + +[package.dependencies] +colorama = "*" +evmdasm = "*" +requests = "*" +tabulate = "*" + +[package.extras] +abidecoder = ["ethereum-input-decoder", "pyetherchain"] +mythril = ["mythril"] + +[[package]] +name = "evmdasm" +version = "0.1.10" +description = "A lightweight ethereum evm bytecode asm instruction registry and disassembler library." +optional = false +python-versions = "*" +files = [ + {file = "evmdasm-0.1.10-py3-none-any.whl", hash = "sha256:30b7ef0c7b13edcaed7ee04a31fac7e24735610fa24b6803cd81706d93db1c61"}, + {file = "evmdasm-0.1.10.tar.gz", hash = "sha256:b7a699740ab56bee605e0ffff51c72d49033755f41d40d001a4769567e906d78"}, +] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "flake8" +version = "3.9.2" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" +files = [ + {file = "flake8-3.9.2-py2.py3-none-any.whl", hash = "sha256:bf8fd333346d844f616e8d47905ef3a3384edae6b4e9beb0c5101e25e3110907"}, + {file = "flake8-3.9.2.tar.gz", hash = "sha256:07528381786f2a6237b061f6e96610a4167b226cb926e2aa2b6b1d78057c576b"}, +] + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.7.0,<2.8.0" +pyflakes = ">=2.3.0,<2.4.0" + +[[package]] +name = "flask" +version = "3.0.3" +description = "A simple framework for building complex web applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask-3.0.3-py3-none-any.whl", hash = "sha256:34e815dfaa43340d1d15a5c3a02b8476004037eb4840b34910c6e21679d288f3"}, + {file = "flask-3.0.3.tar.gz", hash = "sha256:ceb27b0af3823ea2737928a4d99d125a06175b8512c445cbd9a9ce200ef76842"}, +] + +[package.dependencies] +blinker = ">=1.6.2" +click = ">=8.1.3" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.1.2" +Jinja2 = ">=3.1.2" +Werkzeug = ">=3.0.0" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-caching" +version = "2.3.0" +description = "Adds caching support to Flask applications." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask_Caching-2.3.0-py3-none-any.whl", hash = "sha256:51771c75682e5abc1483b78b96d9131d7941dc669b073852edfa319dd4e29b6e"}, + {file = "flask_caching-2.3.0.tar.gz", hash = "sha256:d7e4ca64a33b49feb339fcdd17e6ba25f5e01168cf885e53790e885f83a4d2cf"}, +] + +[package.dependencies] +cachelib = ">=0.9.0,<0.10.0" +Flask = "*" + +[[package]] +name = "flask-cors" +version = "3.0.9" +description = "A Flask extension adding a decorator for CORS support" +optional = false +python-versions = "*" +files = [ + {file = "Flask-Cors-3.0.9.tar.gz", hash = "sha256:6bcfc100288c5d1bcb1dbb854babd59beee622ffd321e444b05f24d6d58466b8"}, + {file = "Flask_Cors-3.0.9-py2.py3-none-any.whl", hash = "sha256:cee4480aaee421ed029eaa788f4049e3e26d15b5affb6a880dade6bafad38324"}, +] + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + +[[package]] +name = "flask-limiter" +version = "3.8.0" +description = "Rate limiting for flask applications" +optional = false +python-versions = ">=3.8" +files = [ + {file = "Flask_Limiter-3.8.0-py3-none-any.whl", hash = "sha256:0ab44f586d8cc349412791711b6cbafe8f86e7b60ad9e8f24f2686009f00900e"}, + {file = "flask_limiter-3.8.0.tar.gz", hash = "sha256:686f8b4a75404e47b91565a795c70d29f69c145f6907f1f32522e962b134dada"}, +] + +[package.dependencies] +Flask = ">=2" +limits = ">=3.13" +ordered-set = ">4,<5" +rich = ">=12,<14" +typing-extensions = ">=4" + +[package.extras] +memcached = ["limits[memcached]"] +mongodb = ["limits[mongodb]"] +redis = ["limits[redis]"] + +[[package]] +name = "flask-restx" +version = "1.3.0" +description = "Fully featured framework for fast, easy and documented API development with Flask" +optional = false +python-versions = "*" +files = [ + {file = "flask-restx-1.3.0.tar.gz", hash = "sha256:4f3d3fa7b6191fcc715b18c201a12cd875176f92ba4acc61626ccfd571ee1728"}, + {file = "flask_restx-1.3.0-py2.py3-none-any.whl", hash = "sha256:636c56c3fb3f2c1df979e748019f084a938c4da2035a3e535a4673e4fc177691"}, +] + +[package.dependencies] +aniso8601 = ">=0.82" +Flask = ">=0.8,<2.0.0 || >2.0.0" +importlib-resources = "*" +jsonschema = "*" +pytz = "*" +werkzeug = "!=2.0.0" + +[package.extras] +dev = ["Faker (==2.0.0)", "black", "blinker", "invoke (==2.2.0)", "mock (==3.0.5)", "pytest (==7.0.1)", "pytest-benchmark (==3.4.1)", "pytest-cov (==4.0.0)", "pytest-flask (==1.3.0)", "pytest-mock (==3.6.1)", "pytest-profiling (==1.7.0)", "setuptools", "tox", "twine (==3.8.0)", "tzlocal"] +doc = ["Sphinx (==5.3.0)", "alabaster (==0.7.12)", "sphinx-issues (==3.0.1)"] +test = ["Faker (==2.0.0)", "blinker", "invoke (==2.2.0)", "mock (==3.0.5)", "pytest (==7.0.1)", "pytest-benchmark (==3.4.1)", "pytest-cov (==4.0.0)", "pytest-flask (==1.3.0)", "pytest-mock (==3.6.1)", "pytest-profiling (==1.7.0)", "setuptools", "twine (==3.8.0)", "tzlocal"] + +[[package]] +name = "flask-sqlalchemy" +version = "3.1.1" +description = "Add SQLAlchemy support to your Flask application." +optional = false +python-versions = ">=3.8" +files = [ + {file = "flask_sqlalchemy-3.1.1-py3-none-any.whl", hash = "sha256:4ba4be7f419dc72f4efd8802d69974803c37259dd42f3913b0dcf75c9447e0a0"}, + {file = "flask_sqlalchemy-3.1.1.tar.gz", hash = "sha256:e4b68bb881802dda1a7d878b2fc84c06d1ee57fb40b874d3dc97dabfa36b8312"}, +] + +[package.dependencies] +flask = ">=2.2.5" +sqlalchemy = ">=2.0.16" + +[[package]] +name = "frozenlist" +version = "1.4.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f9aa1878d1083b276b0196f2dfbe00c9b7e752475ed3b682025ff20c1c1f51ac"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29acab3f66f0f24674b7dc4736477bcd4bc3ad4b896f5f45379a67bce8b96868"}, + {file = "frozenlist-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:74fb4bee6880b529a0c6560885fce4dc95936920f9f20f53d99a213f7bf66776"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:590344787a90ae57d62511dd7c736ed56b428f04cd8c161fcc5e7232c130c69a"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:068b63f23b17df8569b7fdca5517edef76171cf3897eb68beb01341131fbd2ad"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c849d495bf5154cd8da18a9eb15db127d4dba2968d88831aff6f0331ea9bd4c"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9750cc7fe1ae3b1611bb8cfc3f9ec11d532244235d75901fb6b8e42ce9229dfe"}, + {file = "frozenlist-1.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9b2de4cf0cdd5bd2dee4c4f63a653c61d2408055ab77b151c1957f221cabf2a"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0633c8d5337cb5c77acbccc6357ac49a1770b8c487e5b3505c57b949b4b82e98"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:27657df69e8801be6c3638054e202a135c7f299267f1a55ed3a598934f6c0d75"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f9a3ea26252bd92f570600098783d1371354d89d5f6b7dfd87359d669f2109b5"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:4f57dab5fe3407b6c0c1cc907ac98e8a189f9e418f3b6e54d65a718aaafe3950"}, + {file = "frozenlist-1.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e02a0e11cf6597299b9f3bbd3f93d79217cb90cfd1411aec33848b13f5c656cc"}, + {file = "frozenlist-1.4.1-cp310-cp310-win32.whl", hash = "sha256:a828c57f00f729620a442881cc60e57cfcec6842ba38e1b19fd3e47ac0ff8dc1"}, + {file = "frozenlist-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:f56e2333dda1fe0f909e7cc59f021eba0d2307bc6f012a1ccf2beca6ba362439"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:a0cb6f11204443f27a1628b0e460f37fb30f624be6051d490fa7d7e26d4af3d0"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b46c8ae3a8f1f41a0d2ef350c0b6e65822d80772fe46b653ab6b6274f61d4a49"}, + {file = "frozenlist-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fde5bd59ab5357e3853313127f4d3565fc7dad314a74d7b5d43c22c6a5ed2ced"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:722e1124aec435320ae01ee3ac7bec11a5d47f25d0ed6328f2273d287bc3abb0"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2471c201b70d58a0f0c1f91261542a03d9a5e088ed3dc6c160d614c01649c106"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c757a9dd70d72b076d6f68efdbb9bc943665ae954dad2801b874c8c69e185068"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f146e0911cb2f1da549fc58fc7bcd2b836a44b79ef871980d605ec392ff6b0d2"}, + {file = "frozenlist-1.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9c515e7914626b2a2e1e311794b4c35720a0be87af52b79ff8e1429fc25f19"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c302220494f5c1ebeb0912ea782bcd5e2f8308037b3c7553fad0e48ebad6ad82"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:442acde1e068288a4ba7acfe05f5f343e19fac87bfc96d89eb886b0363e977ec"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:1b280e6507ea8a4fa0c0a7150b4e526a8d113989e28eaaef946cc77ffd7efc0a"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:fe1a06da377e3a1062ae5fe0926e12b84eceb8a50b350ddca72dc85015873f74"}, + {file = "frozenlist-1.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db9e724bebd621d9beca794f2a4ff1d26eed5965b004a97f1f1685a173b869c2"}, + {file = "frozenlist-1.4.1-cp311-cp311-win32.whl", hash = "sha256:e774d53b1a477a67838a904131c4b0eef6b3d8a651f8b138b04f748fccfefe17"}, + {file = "frozenlist-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:fb3c2db03683b5767dedb5769b8a40ebb47d6f7f45b1b3e3b4b51ec8ad9d9825"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1979bc0aeb89b33b588c51c54ab0161791149f2461ea7c7c946d95d5f93b56ae"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cc7b01b3754ea68a62bd77ce6020afaffb44a590c2289089289363472d13aedb"}, + {file = "frozenlist-1.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c9c92be9fd329ac801cc420e08452b70e7aeab94ea4233a4804f0915c14eba9b"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c3894db91f5a489fc8fa6a9991820f368f0b3cbdb9cd8849547ccfab3392d86"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ba60bb19387e13597fb059f32cd4d59445d7b18b69a745b8f8e5db0346f33480"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8aefbba5f69d42246543407ed2461db31006b0f76c4e32dfd6f42215a2c41d09"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:780d3a35680ced9ce682fbcf4cb9c2bad3136eeff760ab33707b71db84664e3a"}, + {file = "frozenlist-1.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9acbb16f06fe7f52f441bb6f413ebae6c37baa6ef9edd49cdd567216da8600cd"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:23b701e65c7b36e4bf15546a89279bd4d8675faabc287d06bbcfac7d3c33e1e6"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3e0153a805a98f5ada7e09826255ba99fb4f7524bb81bf6b47fb702666484ae1"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:dd9b1baec094d91bf36ec729445f7769d0d0cf6b64d04d86e45baf89e2b9059b"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:1a4471094e146b6790f61b98616ab8e44f72661879cc63fa1049d13ef711e71e"}, + {file = "frozenlist-1.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5667ed53d68d91920defdf4035d1cdaa3c3121dc0b113255124bcfada1cfa1b8"}, + {file = "frozenlist-1.4.1-cp312-cp312-win32.whl", hash = "sha256:beee944ae828747fd7cb216a70f120767fc9f4f00bacae8543c14a6831673f89"}, + {file = "frozenlist-1.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:64536573d0a2cb6e625cf309984e2d873979709f2cf22839bf2d61790b448ad5"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:20b51fa3f588ff2fe658663db52a41a4f7aa6c04f6201449c6c7c476bd255c0d"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:410478a0c562d1a5bcc2f7ea448359fcb050ed48b3c6f6f4f18c313a9bdb1826"}, + {file = "frozenlist-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c6321c9efe29975232da3bd0af0ad216800a47e93d763ce64f291917a381b8eb"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f6a4533887e189dae092f1cf981f2e3885175f7a0f33c91fb5b7b682b6bab6"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6eb73fa5426ea69ee0e012fb59cdc76a15b1283d6e32e4f8dc4482ec67d1194d"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fbeb989b5cc29e8daf7f976b421c220f1b8c731cbf22b9130d8815418ea45887"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32453c1de775c889eb4e22f1197fe3bdfe457d16476ea407472b9442e6295f7a"}, + {file = "frozenlist-1.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:693945278a31f2086d9bf3df0fe8254bbeaef1fe71e1351c3bd730aa7d31c41b"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1d0ce09d36d53bbbe566fe296965b23b961764c0bcf3ce2fa45f463745c04701"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3a670dc61eb0d0eb7080890c13de3066790f9049b47b0de04007090807c776b0"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:dca69045298ce5c11fd539682cff879cc1e664c245d1c64da929813e54241d11"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a06339f38e9ed3a64e4c4e43aec7f59084033647f908e4259d279a52d3757d09"}, + {file = "frozenlist-1.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b7f2f9f912dca3934c1baec2e4585a674ef16fe00218d833856408c48d5beee7"}, + {file = "frozenlist-1.4.1-cp38-cp38-win32.whl", hash = "sha256:e7004be74cbb7d9f34553a5ce5fb08be14fb33bc86f332fb71cbe5216362a497"}, + {file = "frozenlist-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:5a7d70357e7cee13f470c7883a063aae5fe209a493c57d86eb7f5a6f910fae09"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:bfa4a17e17ce9abf47a74ae02f32d014c5e9404b6d9ac7f729e01562bbee601e"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b7e3ed87d4138356775346e6845cccbe66cd9e207f3cd11d2f0b9fd13681359d"}, + {file = "frozenlist-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c99169d4ff810155ca50b4da3b075cbde79752443117d89429595c2e8e37fed8"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:edb678da49d9f72c9f6c609fbe41a5dfb9a9282f9e6a2253d5a91e0fc382d7c0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6db4667b187a6742b33afbbaf05a7bc551ffcf1ced0000a571aedbb4aa42fc7b"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55fdc093b5a3cb41d420884cdaf37a1e74c3c37a31f46e66286d9145d2063bd0"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82e8211d69a4f4bc360ea22cd6555f8e61a1bd211d1d5d39d3d228b48c83a897"}, + {file = "frozenlist-1.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89aa2c2eeb20957be2d950b85974b30a01a762f3308cd02bb15e1ad632e22dc7"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9d3e0c25a2350080e9319724dede4f31f43a6c9779be48021a7f4ebde8b2d742"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7268252af60904bf52c26173cbadc3a071cece75f873705419c8681f24d3edea"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0c250a29735d4f15321007fb02865f0e6b6a41a6b88f1f523ca1596ab5f50bd5"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:96ec70beabbd3b10e8bfe52616a13561e58fe84c0101dd031dc78f250d5128b9"}, + {file = "frozenlist-1.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:23b2d7679b73fe0e5a4560b672a39f98dfc6f60df63823b0a9970525325b95f6"}, + {file = "frozenlist-1.4.1-cp39-cp39-win32.whl", hash = "sha256:a7496bfe1da7fb1a4e1cc23bb67c58fab69311cc7d32b5a99c2007b4b2a0e932"}, + {file = "frozenlist-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e6a20a581f9ce92d389a8c7d7c3dd47c81fd5d6e655c8dddf341e14aa48659d0"}, + {file = "frozenlist-1.4.1-py3-none-any.whl", hash = "sha256:04ced3e6a46b4cfffe20f9ae482818e34eba9b5fb0ce4056e4cc9b6e212d09b7"}, + {file = "frozenlist-1.4.1.tar.gz", hash = "sha256:c037a86e8513059a2613aaba4d817bb90b9d9b6b69aace3ce9c877e8c8ed402b"}, +] + +[[package]] +name = "greenlet" +version = "3.1.0" +description = "Lightweight in-process concurrent programming" +optional = false +python-versions = ">=3.7" +files = [ + {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, + {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, + {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, + {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, + {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, + {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, + {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, + {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, + {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, + {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, + {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, + {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, + {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, + {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, + {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, + {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, + {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, + {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, + {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, + {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, + {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0"}, + {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6"}, + {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a"}, + {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df"}, + {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637"}, + {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954"}, + {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3"}, + {file = "greenlet-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00"}, + {file = "greenlet-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6"}, + {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, + {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, + {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, + {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, + {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, + {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, + {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, + {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, + {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, + {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, + {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, + {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, + {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, +] + +[package.extras] +docs = ["Sphinx", "furo"] +test = ["objgraph", "psutil"] + +[[package]] +name = "hexbytes" +version = "0.3.1" +description = "hexbytes: Python `bytes` subclass that decodes hex, with a readable console output" +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "hexbytes-0.3.1-py3-none-any.whl", hash = "sha256:383595ad75026cf00abd570f44b368c6cdac0c6becfae5c39ff88829877f8a59"}, + {file = "hexbytes-0.3.1.tar.gz", hash = "sha256:a3fe35c6831ee8fafd048c4c086b986075fc14fd46258fa24ecb8d65745f9a9d"}, +] + +[package.extras] +dev = ["black (>=22)", "bumpversion (>=0.5.3)", "eth-utils (>=1.0.1,<3)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "hypothesis (>=3.44.24,<=6.31.6)", "ipython", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)", "pytest (>=7.0.0)", "pytest-watch (>=4.1.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +doc = ["sphinx (>=5.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +lint = ["black (>=22)", "flake8 (==6.0.0)", "flake8-bugbear (==23.3.23)", "isort (>=5.10.1)", "mypy (==0.971)", "pydocstyle (>=5.0.0)"] +test = ["eth-utils (>=1.0.1,<3)", "hypothesis (>=3.44.24,<=6.31.6)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "importlib-resources" +version = "6.4.5" +description = "Read resources from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_resources-6.4.5-py3-none-any.whl", hash = "sha256:ac29d5f956f01d5e4bb63102a5a19957f1b9175e45649977264a1416783bb717"}, + {file = "importlib_resources-6.4.5.tar.gz", hash = "sha256:980862a1d16c9e147a59603677fa2aa5fd82b87f223b6cb870695bcfce830065"}, +] + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["jaraco.test (>=5.4)", "pytest (>=6,!=8.1.*)", "zipp (>=3.17)"] +type = ["pytest-mypy"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + +[[package]] +name = "jinja2" +version = "3.1.4" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"}, + {file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jsonschema" +version = "4.23.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema-4.23.0-py3-none-any.whl", hash = "sha256:fbadb6f8b144a8f8cf9f0b89ba94501d143e50411a1278633f56a7acf7fd5566"}, + {file = "jsonschema-4.23.0.tar.gz", hash = "sha256:d71497fef26351a33265337fa77ffeb82423f3ea21283cd9467bb03999266bc4"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +jsonschema-specifications = ">=2023.03.6" +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +referencing = ">=0.28.4" +rpds-py = ">=0.7.1" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=24.6.0)"] + +[[package]] +name = "jsonschema-specifications" +version = "2023.12.1" +description = "The JSON Schema meta-schemas and vocabularies, exposed as a Registry" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jsonschema_specifications-2023.12.1-py3-none-any.whl", hash = "sha256:87e4fdf3a94858b8a2ba2778d9ba57d8a9cafca7c7489c46ba0d30a8bc6a9c3c"}, + {file = "jsonschema_specifications-2023.12.1.tar.gz", hash = "sha256:48a76787b3e70f5ed53f1160d2b81f586e4ca6d1548c5de7085d1682674764cc"}, +] + +[package.dependencies] +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +referencing = ">=0.31.0" + +[[package]] +name = "limits" +version = "3.13.0" +description = "Rate limiting utilities" +optional = false +python-versions = ">=3.8" +files = [ + {file = "limits-3.13.0-py3-none-any.whl", hash = "sha256:9767f7233da4255e9904b79908a728e8ec0984c0b086058b4cbbd309aea553f6"}, + {file = "limits-3.13.0.tar.gz", hash = "sha256:6571b0c567bfa175a35fed9f8a954c0c92f1c3200804282f1b8f1de4ad98a953"}, +] + +[package.dependencies] +deprecated = ">=1.2" +importlib-resources = ">=1.3" +packaging = ">=21,<25" +typing-extensions = "*" + +[package.extras] +all = ["aetcd", "coredis (>=3.4.0,<5)", "emcache (>=0.6.1)", "emcache (>=1)", "etcd3", "motor (>=3,<4)", "pymemcache (>3,<5.0.0)", "pymongo (>4.1,<5)", "redis (>3,!=4.5.2,!=4.5.3,<6.0.0)", "redis (>=4.2.0,!=4.5.2,!=4.5.3)"] +async-etcd = ["aetcd"] +async-memcached = ["emcache (>=0.6.1)", "emcache (>=1)"] +async-mongodb = ["motor (>=3,<4)"] +async-redis = ["coredis (>=3.4.0,<5)"] +etcd = ["etcd3"] +memcached = ["pymemcache (>3,<5.0.0)"] +mongodb = ["pymongo (>4.1,<5)"] +redis = ["redis (>3,!=4.5.2,!=4.5.3,<6.0.0)"] +rediscluster = ["redis (>=4.2.0,!=4.5.2,!=4.5.3)"] + +[[package]] +name = "lru-dict" +version = "1.2.0" +description = "An Dict like LRU container." +optional = false +python-versions = "*" +files = [ + {file = "lru-dict-1.2.0.tar.gz", hash = "sha256:13c56782f19d68ddf4d8db0170041192859616514c706b126d0df2ec72a11bd7"}, + {file = "lru_dict-1.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:de906e5486b5c053d15b7731583c25e3c9147c288ac8152a6d1f9bccdec72641"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:604d07c7604b20b3130405d137cae61579578b0e8377daae4125098feebcb970"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:203b3e78d03d88f491fa134f85a42919020686b6e6f2d09759b2f5517260c651"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:020b93870f8c7195774cbd94f033b96c14f51c57537969965c3af300331724fe"}, + {file = "lru_dict-1.2.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1184d91cfebd5d1e659d47f17a60185bbf621635ca56dcdc46c6a1745d25df5c"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fc42882b554a86e564e0b662da47b8a4b32fa966920bd165e27bb8079a323bc1"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:18ee88ada65bd2ffd483023be0fa1c0a6a051ef666d1cd89e921dcce134149f2"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:756230c22257597b7557eaef7f90484c489e9ba78e5bb6ab5a5bcfb6b03cb075"}, + {file = "lru_dict-1.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c4da599af36618881748b5db457d937955bb2b4800db891647d46767d636c408"}, + {file = "lru_dict-1.2.0-cp310-cp310-win32.whl", hash = "sha256:35a142a7d1a4fd5d5799cc4f8ab2fff50a598d8cee1d1c611f50722b3e27874f"}, + {file = "lru_dict-1.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:6da5b8099766c4da3bf1ed6e7d7f5eff1681aff6b5987d1258a13bd2ed54f0c9"}, + {file = "lru_dict-1.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b20b7c9beb481e92e07368ebfaa363ed7ef61e65ffe6e0edbdbaceb33e134124"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22147367b296be31cc858bf167c448af02435cac44806b228c9be8117f1bfce4"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:34a3091abeb95e707f381a8b5b7dc8e4ee016316c659c49b726857b0d6d1bd7a"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:877801a20f05c467126b55338a4e9fa30e2a141eb7b0b740794571b7d619ee11"}, + {file = "lru_dict-1.2.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7d3336e901acec897bcd318c42c2b93d5f1d038e67688f497045fc6bad2c0be7"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8dafc481d2defb381f19b22cc51837e8a42631e98e34b9e0892245cc96593deb"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:87bbad3f5c3de8897b8c1263a9af73bbb6469fb90e7b57225dad89b8ef62cd8d"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:25f9e0bc2fe8f41c2711ccefd2871f8a5f50a39e6293b68c3dec576112937aad"}, + {file = "lru_dict-1.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:ae301c282a499dc1968dd633cfef8771dd84228ae9d40002a3ea990e4ff0c469"}, + {file = "lru_dict-1.2.0-cp311-cp311-win32.whl", hash = "sha256:c9617583173a29048e11397f165501edc5ae223504a404b2532a212a71ecc9ed"}, + {file = "lru_dict-1.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6b7a031e47421d4b7aa626b8c91c180a9f037f89e5d0a71c4bb7afcf4036c774"}, + {file = "lru_dict-1.2.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ea2ac3f7a7a2f32f194c84d82a034e66780057fd908b421becd2f173504d040e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd46c94966f631a81ffe33eee928db58e9fbee15baba5923d284aeadc0e0fa76"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:086ce993414f0b28530ded7e004c77dc57c5748fa6da488602aa6e7f79e6210e"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df25a426446197488a6702954dcc1de511deee20c9db730499a2aa83fddf0df1"}, + {file = "lru_dict-1.2.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c53b12b89bd7a6c79f0536ff0d0a84fdf4ab5f6252d94b24b9b753bd9ada2ddf"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:f9484016e6765bd295708cccc9def49f708ce07ac003808f69efa386633affb9"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:d0f7ec902a0097ac39f1922c89be9eaccf00eb87751e28915320b4f72912d057"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:981ef3edc82da38d39eb60eae225b88a538d47b90cce2e5808846fd2cf64384b"}, + {file = "lru_dict-1.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:e25b2e90a032dc248213af7f3f3e975e1934b204f3b16aeeaeaff27a3b65e128"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win32.whl", hash = "sha256:59f3df78e94e07959f17764e7fa7ca6b54e9296953d2626a112eab08e1beb2db"}, + {file = "lru_dict-1.2.0-cp36-cp36m-win_amd64.whl", hash = "sha256:de24b47159e07833aeab517d9cb1c3c5c2d6445cc378b1c2f1d8d15fb4841d63"}, + {file = "lru_dict-1.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d0dd4cd58220351233002f910e35cc01d30337696b55c6578f71318b137770f9"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87bdc291718bbdf9ea4be12ae7af26cbf0706fa62c2ac332748e3116c5510a7"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05fb8744f91f58479cbe07ed80ada6696ec7df21ea1740891d4107a8dd99a970"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00f6e8a3fc91481b40395316a14c94daa0f0a5de62e7e01a7d589f8d29224052"}, + {file = "lru_dict-1.2.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5b172fce0a0ffc0fa6d282c14256d5a68b5db1e64719c2915e69084c4b6bf555"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e707d93bae8f0a14e6df1ae8b0f076532b35f00e691995f33132d806a88e5c18"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b9ec7a4a0d6b8297102aa56758434fb1fca276a82ed7362e37817407185c3abb"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:f404dcc8172da1f28da9b1f0087009578e608a4899b96d244925c4f463201f2a"}, + {file = "lru_dict-1.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:1171ad3bff32aa8086778be4a3bdff595cc2692e78685bcce9cb06b96b22dcc2"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win32.whl", hash = "sha256:0c316dfa3897fabaa1fe08aae89352a3b109e5f88b25529bc01e98ac029bf878"}, + {file = "lru_dict-1.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:5919dd04446bc1ee8d6ecda2187deeebfff5903538ae71083e069bc678599446"}, + {file = "lru_dict-1.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:fbf36c5a220a85187cacc1fcb7dd87070e04b5fc28df7a43f6842f7c8224a388"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:712e71b64da181e1c0a2eaa76cd860265980cd15cb0e0498602b8aa35d5db9f8"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f54908bf91280a9b8fa6a8c8f3c2f65850ce6acae2852bbe292391628ebca42f"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3838e33710935da2ade1dd404a8b936d571e29268a70ff4ca5ba758abb3850df"}, + {file = "lru_dict-1.2.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5d5a5f976b39af73324f2b793862859902ccb9542621856d51a5993064f25e4"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bda3a9afd241ee0181661decaae25e5336ce513ac268ab57da737eacaa7871f"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:bd2cd1b998ea4c8c1dad829fc4fa88aeed4dee555b5e03c132fc618e6123f168"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:b55753ee23028ba8644fd22e50de7b8f85fa60b562a0fafaad788701d6131ff8"}, + {file = "lru_dict-1.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e51fa6a203fa91d415f3b2900e5748ec8e06ad75777c98cc3aeb3983ca416d7"}, + {file = "lru_dict-1.2.0-cp38-cp38-win32.whl", hash = "sha256:cd6806313606559e6c7adfa0dbeb30fc5ab625f00958c3d93f84831e7a32b71e"}, + {file = "lru_dict-1.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:5d90a70c53b0566084447c3ef9374cc5a9be886e867b36f89495f211baabd322"}, + {file = "lru_dict-1.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a3ea7571b6bf2090a85ff037e6593bbafe1a8598d5c3b4560eb56187bcccb4dc"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:287c2115a59c1c9ed0d5d8ae7671e594b1206c36ea9df2fca6b17b86c468ff99"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5ccfd2291c93746a286c87c3f895165b697399969d24c54804ec3ec559d4e43"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b710f0f4d7ec4f9fa89dfde7002f80bcd77de8024017e70706b0911ea086e2ef"}, + {file = "lru_dict-1.2.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5345bf50e127bd2767e9fd42393635bbc0146eac01f6baf6ef12c332d1a6a329"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:291d13f85224551913a78fe695cde04cbca9dcb1d84c540167c443eb913603c9"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d5bb41bc74b321789803d45b124fc2145c1b3353b4ad43296d9d1d242574969b"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:0facf49b053bf4926d92d8d5a46fe07eecd2af0441add0182c7432d53d6da667"}, + {file = "lru_dict-1.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:987b73a06bcf5a95d7dc296241c6b1f9bc6cda42586948c9dabf386dc2bef1cd"}, + {file = "lru_dict-1.2.0-cp39-cp39-win32.whl", hash = "sha256:231d7608f029dda42f9610e5723614a35b1fff035a8060cf7d2be19f1711ace8"}, + {file = "lru_dict-1.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:71da89e134747e20ed5b8ad5b4ee93fc5b31022c2b71e8176e73c5a44699061b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:21b3090928c7b6cec509e755cc3ab742154b33660a9b433923bd12c37c448e3e"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aaecd7085212d0aa4cd855f38b9d61803d6509731138bf798a9594745953245b"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ead83ac59a29d6439ddff46e205ce32f8b7f71a6bd8062347f77e232825e3d0a"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:312b6b2a30188586fe71358f0f33e4bac882d33f5e5019b26f084363f42f986f"}, + {file = "lru_dict-1.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:b30122e098c80e36d0117810d46459a46313421ce3298709170b687dc1240b02"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f010cfad3ab10676e44dc72a813c968cd586f37b466d27cde73d1f7f1ba158c2"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20f5f411f7751ad9a2c02e80287cedf69ae032edd321fe696e310d32dd30a1f8"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:afdadd73304c9befaed02eb42f5f09fdc16288de0a08b32b8080f0f0f6350aa6"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7ab0c10c4fa99dc9e26b04e6b62ac32d2bcaea3aad9b81ec8ce9a7aa32b7b1b"}, + {file = "lru_dict-1.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:edad398d5d402c43d2adada390dd83c74e46e020945ff4df801166047013617e"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:91d577a11b84387013815b1ad0bb6e604558d646003b44c92b3ddf886ad0f879"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb12f19cdf9c4f2d9aa259562e19b188ff34afab28dd9509ff32a3f1c2c29326"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e4c85aa8844bdca3c8abac3b7f78da1531c74e9f8b3e4890c6e6d86a5a3f6c0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c6acbd097b15bead4de8e83e8a1030bb4d8257723669097eac643a301a952f0"}, + {file = "lru_dict-1.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:b6613daa851745dd22b860651de930275be9d3e9373283a2164992abacb75b62"}, +] + +[package.extras] +test = ["pytest"] + +[[package]] +name = "mako" +version = "1.3.5" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "Mako-1.3.5-py3-none-any.whl", hash = "sha256:260f1dbc3a519453a9c856dedfe4beb4e50bd5a26d96386cb6c80856556bb91a"}, + {file = "Mako-1.3.5.tar.gz", hash = "sha256:48dbc20568c1d276a2698b36d968fa76161bf127194907ea6fc594fa81f943bc"}, +] + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = "*" +files = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "mmh3" +version = "5.0.0" +description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." +optional = false +python-versions = ">=3.8" +files = [ + {file = "mmh3-5.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e65e16c9de793bfa334355c60fccc859faf1c0b707d847252841cee72b5309df"}, + {file = "mmh3-5.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66d5423c65aebe94f244e8204cc8b39a4b970e342fb619621376af90c5f9a421"}, + {file = "mmh3-5.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5bfc15d11bcc5ce96254f5399582104c566a1eeeb91072ff0a0de92b24f704ff"}, + {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1616cb621cab3bc5b58dba9605311c346b45e732818f12f943a803b9a641f09c"}, + {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3e3d94553b321b26952d507dace15509463604ead98fece710237ca8cb5983d"}, + {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b46b6d5c0fcd2620ebc699099fed6fda0101805d7d4c274fa2b2c384e8b9e8b9"}, + {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a2a9cd368f2a15f06f2749db764e410b7dc260822319f169a52c649da078bf6"}, + {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:903cdb66f8d8de26a0a6c737a13664388b5ed14813f84793bccbba44ffa09fa2"}, + {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7653d8177e4241a5f7913a675636ccc701722741b153b0a43c82464a4a865752"}, + {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a9154a0a32de54b812d178541468cce4c73b112fbd8b5b0a4add92cfda69c390"}, + {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5f814a621fd0e567a121ace724ad57f28fb9972acfd54c854749ee91d5c4905c"}, + {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:333057bcf12804a8cae12adb2a5cef3bc50fc8de044ad6a01ae1918a342d6f0e"}, + {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e713afe276481af7ea0330f40214c71c11486b28754aa2d1cb286b5ee9571dee"}, + {file = "mmh3-5.0.0-cp310-cp310-win32.whl", hash = "sha256:917b72bc8b238286d7e2c586c19d604b3b3e5a93f054020cd15530324b868e6e"}, + {file = "mmh3-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8bcaa5c0fd9c4679639c830b46e52fcc3b84502faa2aa5f3ca1dacb7cdbb1f"}, + {file = "mmh3-5.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:341ad4f902ebb7fc14751fab67fb4eedf800a1744bc9dc214a56e11b62d0bfdd"}, + {file = "mmh3-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:690cb4b36ed7c863680029c2370b4f3a0c3dc8900320eb5ce79a867eb8b37151"}, + {file = "mmh3-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5580c7bf9570458d8096a016f7012453306a14d2e3e4ef1267b9c9c9f506d8a4"}, + {file = "mmh3-5.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f271a043545c86cce51c35e5bb6f52dceb535dcd2d46c2129a9c869b80ec2eaa"}, + {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2400e805018e04718c9367e85d694c036d21a41b37024d5b0dc8ef80cb984cf0"}, + {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3d75183b7d5df178bf01f0d9ac366525fd54e828d799fe3892882b83c13454b"}, + {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec24039151e67c5fc026ce5be8788e3e8093d9ce3e114d7a6175f453521bacc9"}, + {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88513eb686937b26d33fe7ded7ed1a68ede490d326eea2344447c0cc42fb7f97"}, + {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a015a90951b31d45a01c8f92703c028d8d759f71dc31727581c916f31cacbade"}, + {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:337f747eaf6ab78d0be9cbbb763781ae76eb2e4c0e6523e74582877fe827ec51"}, + {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01636d74df6371f192dc5a112f4afc43cb4119a9ea6de704469161fd4ab9e86b"}, + {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3d969f1813b27bd4caac359e0ea0f6b711d252e872c0723124b58d7ac275cb0e"}, + {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f699f2232efe3907a6a05d9661edd2f4045a0e05161dc1087f72957d752a47a"}, + {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16f46dc1774bf3e8305fa33207a502317cd7a592cb79b0d65c84f0fbcf9d9ffa"}, + {file = "mmh3-5.0.0-cp311-cp311-win32.whl", hash = "sha256:2d039b32f4194ac345c0f52441b7ff0a55735a896303a3eb9054e5f8512d84c8"}, + {file = "mmh3-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:47b3823a88c5f7859580688016bff13437fd866f6132e4770b306f0c6edb01a7"}, + {file = "mmh3-5.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:2ee87816b838709bd0fc623e3277736e9465a941d27b14f35d0df1c2006e4438"}, + {file = "mmh3-5.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed23a89a6daeb9b0d4db9fa865b125300516e8d6961f771b2dd97965bf888bce"}, + {file = "mmh3-5.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b020012e30ba0e4b002a150ca1c1d590a7387fac50dfd9b6b5dc66d9d0e61257"}, + {file = "mmh3-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e86246d036da05d54e833cdc342ad9c62f38f2507b14f2c58ce2c271f22d7251"}, + {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f50535009c1aaa44f9702ad14551d12d1755b537fc25a5bd7d46c493ec4bcfaa"}, + {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a567a87026f103778b70f2b19637fb490d9c29dc7d3af9cd46185d48efbd49dc"}, + {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4405f231032b8f8ae93d9a558ddc04b9aa94a59c309cb265ebe1e79ced920e"}, + {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d630fe75ef7d4ab1a95eb824d81dee15ed73020703bf030515f03283cb8f086f"}, + {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361f9d611debc10992888cbace09dbc47391ae8b84b50c995a6d97e6314bb422"}, + {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:11e29156edb1990600cb4b91bcb371a2adf20a0fcb818837fb78408be19e9117"}, + {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e70c4fc340509f6554a1748fe6f539a846fbacf147255048d1702def820a1520"}, + {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aa790a46370eeedb474c462820be78597452f54c5fffe1f810fc8e847faf42a1"}, + {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce678ac7d31e83fecc817ab630cc7254c7826de11fd2b3e8c31a8a5b0b762884"}, + {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee22cd4cad4fc7d4e5282b2729899460b03c388cde0290d411e877249f78a373"}, + {file = "mmh3-5.0.0-cp312-cp312-win32.whl", hash = "sha256:cb1a96488dc8fccf843ccdbdd10faf1939e6e18cd928c2beff17e70c2ab09ec1"}, + {file = "mmh3-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:897ebafa83bbbbb1958ee30cda78c7ad4b12f2b9360f96b22e488eb127b2cb4f"}, + {file = "mmh3-5.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:e6e3334985865ec3bdfcc4ae8df4a1939be048c5ae3ce1c8c309744400f8e4de"}, + {file = "mmh3-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:05b09d98cbdb6ad03eb9c701e87cea005ececd4fd7d2968ff0f5a86af1ef340d"}, + {file = "mmh3-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d4ac0b8d48ce1e7561cf330ec73b9582f6773e40aaf8a771dd51a0bb483ec94f"}, + {file = "mmh3-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5a1c056b65be3077496ed655731eff1f65ee67f2b31f40a027f3171ba0ac20d1"}, + {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a572adb41cf831d79b3b38c7994e5a5bc1a8ea8d7b574ce0c24272fc5abb52df"}, + {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0be4d14ab3a690bac6c127733490493b8698f84eadb9d667be7eb952281c51e4"}, + {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b65b6eabd9e78e2b8eee2b8d4db34d4d2f5b540f2ac06ec0a76a1f1566f0ff7"}, + {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b433656f371af5edf44cf00862e288e08327bb9e90c8aaa5e4e60dfebc62039"}, + {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b12073a58be5e6408c6bd8366bbf6253defe042cdec25ee51f123c944e5a8f"}, + {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:29b2c8eb7a473f6f235c2d9905912a76350dd11b42058364de984264fa4f74ca"}, + {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b178b744685de956ff84b3b2531271824a2e4372aff199ab805e1fdd7f996f5c"}, + {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fcac7a75846aec7bd168c05576dc8c9448a9705165dfa0986d0f48952eca62a4"}, + {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cc0caa0d2800d54384cc048e49e6c2b4a90cba1afff0597d7c2a5006c42b5536"}, + {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:05b10476fd5cfb0fd63ceebf7430612423e7f79e9732e0b344225aa642e12be4"}, + {file = "mmh3-5.0.0-cp313-cp313-win32.whl", hash = "sha256:7101a12a2a4b39d7748d2d83310d5e0415950ccf6b9ec8da1d35af3f50b3ef0e"}, + {file = "mmh3-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:47d9a9c1c48accaf78ddb77669c40c837e90be2ecddd39bf7ef2f8dacff85ca6"}, + {file = "mmh3-5.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9126155ad1a9418920360497a0b44906dce32f0732cb44378ace08c62751dd1e"}, + {file = "mmh3-5.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d8ac8b3f53d2146680a2e16908620f209778bfb6c7a5d1e64b10f496f3c87f7"}, + {file = "mmh3-5.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17c0af11c1c4217b30635d3b3ad03dc61388f14b19a4f74bfcd6f29cfac3a59e"}, + {file = "mmh3-5.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8df414110fdbcf689812bf0ecab83611f97191dea2ab378bd384a6166bf0849b"}, + {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd7a88b40c5c715c9afe6af4034f23a2222045d17215ffdde357733d976fde2"}, + {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:157159c3306e11225111e7d2db6bee1ff81d2286df53d6a305728656cf694076"}, + {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d1ac511ad1775f793b4c34fe40e43e213bc59620fcf7d3046ee7d73dd629f82"}, + {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb92b0e27775b078d469d948341f20a071943b4dacb4950d708a04722a8d6fee"}, + {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e326d9e1756ccacb2a8047d924101c3ed44bb2ac6aa6fc92efbbd1f2858f7b5"}, + {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:566ab00f7bc779d6d887b2d2ec96345483ed7091796d303f5df20cb06b4327ba"}, + {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:8971c3bc630cd3d3f2ba7b0a98b6eb3b5a83089f76e53500c53d3b3132d3437c"}, + {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:493479781554a98492a20bd659dd470608a9ceb5b229efcc4ce03ea4c849e81e"}, + {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:1424a9ffa472d4a9f5581f4eeac118d2c746e1980cbb48b7c1ddd1155e8d0d11"}, + {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8f1c6c36135635c418b3e4e39f9bd3d0ef4afaf3c328c727f94cba0c146abba1"}, + {file = "mmh3-5.0.0-cp38-cp38-win32.whl", hash = "sha256:1d96e0ec13acf1c725b3ba5c932f0f4153238598880d4b32260dd552a34d6576"}, + {file = "mmh3-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:4ac9de75f3782a5c4dae1c60ed69f2a487439398ef694fc2b77c0a4e61268307"}, + {file = "mmh3-5.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:175037381e94d65553631b301c5c6f0cd69c7f8519e634221e7361595c7de0c3"}, + {file = "mmh3-5.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa6393cd40c3687b1d0bd3fbac8f9e81bbeb949bab63268e3756f9061c19d75c"}, + {file = "mmh3-5.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2e818d949829aeb25898db76d5a88411c2cc899c00e1c1c1a30916254f8762e2"}, + {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:072dda5e7e27a01c9c393c0558a210051d49750cd27807b443ba3285793f31bb"}, + {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf6fa9d615f782a4626b0f99ec0a2d755e72b4e01a34503129bcc05488f4c404"}, + {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03c2feef9fa839899065dc29cec2f921b1194bddaa9999e5460f9ab903e0ef24"}, + {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d67a7671b847ad934fea80702665b0903a5e19339713ea7a46f7e8eea21299d0"}, + {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f632b8962474ce3db9fec0d9e2c0038bc1339295796b0e76ac1d27b47bb60f9c"}, + {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1a2b2dc334a27a5d129cfbe848dbbbc22f1a744dcebab70b406528bca874009"}, + {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4ea987eeb860723a0a0217b2e84c327ba5fd143cd8e7e289f008ae95280d30bb"}, + {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d5904ef1621c9ef680fd2ff3c1e433205e05093d26aa42840337630ac20912f1"}, + {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cad6c32ab86e48ac73af45bcd118b931e677e038167c36186ac0d640f76c8b1e"}, + {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3672b5ea6d7c9c5feeb3f75a695b622ee6a82d22897fa0aa4bf1d8ca99ac9c87"}, + {file = "mmh3-5.0.0-cp39-cp39-win32.whl", hash = "sha256:29b311d4f8dc277c8ef716c68b8020fe0e702807ba7f631fe57ad037d35713aa"}, + {file = "mmh3-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8fbe4112955b33e1b4893b144bee9cc5bc476159d1d1cad9b8c0451047b6abde"}, + {file = "mmh3-5.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:4e66a0f85c8a2f54025f3d477a141458b0338f1b519ad0286c704865513c814f"}, + {file = "mmh3-5.0.0.tar.gz", hash = "sha256:60d1713457789c70292f1f04ca984e3175fc66e6c3545582fd2b4af7f5a61c73"}, +] + +[package.extras] +benchmark = ["pymmh3 (==0.0.5)", "pyperf (==2.7.0)", "xxhash (==3.5.0)"] +docs = ["myst-parser (==4.0.0)", "shibuya (==2024.8.30)", "sphinx (==8.0.2)", "sphinx-copybutton (==0.5.2)"] +lint = ["black (==24.8.0)", "clang-format (==18.1.8)", "isort (==5.13.2)", "pylint (==3.2.7)"] +plot = ["matplotlib (==3.9.2)", "pandas (==2.2.2)"] +test = ["pytest (==8.3.3)", "pytest-sugar (==1.0.0)"] +type = ["mypy (==1.11.2)"] + +[[package]] +name = "mpire" +version = "2.10.2" +description = "A Python package for easy multiprocessing, but faster than multiprocessing" +optional = false +python-versions = "*" +files = [ + {file = "mpire-2.10.2-py3-none-any.whl", hash = "sha256:d627707f7a8d02aa4c7f7d59de399dec5290945ddf7fbd36cbb1d6ebb37a51fb"}, + {file = "mpire-2.10.2.tar.gz", hash = "sha256:f66a321e93fadff34585a4bfa05e95bd946cf714b442f51c529038eb45773d97"}, +] + +[package.dependencies] +importlib-resources = {version = "*", markers = "python_version < \"3.9\""} +pygments = ">=2.0" +pywin32 = {version = ">=301", markers = "platform_system == \"Windows\""} +tqdm = ">=4.27" + +[package.extras] +dashboard = ["flask"] +dill = ["multiprocess", "multiprocess (>=0.70.15)"] +docs = ["docutils (==0.17.1)", "sphinx (==3.2.1)", "sphinx-autodoc-typehints (==1.11.0)", "sphinx-rtd-theme (==0.5.0)", "sphinx-versions (==1.0.1)", "sphinxcontrib-images (==0.9.2)"] +testing = ["ipywidgets", "multiprocess", "multiprocess (>=0.70.15)", "numpy", "pywin32 (>=301)", "rich"] + +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "numpy" +version = "1.24.4" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "numpy-1.24.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c0bfb52d2169d58c1cdb8cc1f16989101639b34c7d3ce60ed70b19c63eba0b64"}, + {file = "numpy-1.24.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ed094d4f0c177b1b8e7aa9cba7d6ceed51c0e569a5318ac0ca9a090680a6a1b1"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79fc682a374c4a8ed08b331bef9c5f582585d1048fa6d80bc6c35bc384eee9b4"}, + {file = "numpy-1.24.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ffe43c74893dbf38c2b0a1f5428760a1a9c98285553c89e12d70a96a7f3a4d6"}, + {file = "numpy-1.24.4-cp310-cp310-win32.whl", hash = "sha256:4c21decb6ea94057331e111a5bed9a79d335658c27ce2adb580fb4d54f2ad9bc"}, + {file = "numpy-1.24.4-cp310-cp310-win_amd64.whl", hash = "sha256:b4bea75e47d9586d31e892a7401f76e909712a0fd510f58f5337bea9572c571e"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f136bab9c2cfd8da131132c2cf6cc27331dd6fae65f95f69dcd4ae3c3639c810"}, + {file = "numpy-1.24.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2926dac25b313635e4d6cf4dc4e51c8c0ebfed60b801c799ffc4c32bf3d1254"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:222e40d0e2548690405b0b3c7b21d1169117391c2e82c378467ef9ab4c8f0da7"}, + {file = "numpy-1.24.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7215847ce88a85ce39baf9e89070cb860c98fdddacbaa6c0da3ffb31b3350bd5"}, + {file = "numpy-1.24.4-cp311-cp311-win32.whl", hash = "sha256:4979217d7de511a8d57f4b4b5b2b965f707768440c17cb70fbf254c4b225238d"}, + {file = "numpy-1.24.4-cp311-cp311-win_amd64.whl", hash = "sha256:b7b1fc9864d7d39e28f41d089bfd6353cb5f27ecd9905348c24187a768c79694"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1452241c290f3e2a312c137a9999cdbf63f78864d63c79039bda65ee86943f61"}, + {file = "numpy-1.24.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:04640dab83f7c6c85abf9cd729c5b65f1ebd0ccf9de90b270cd61935eef0197f"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5425b114831d1e77e4b5d812b69d11d962e104095a5b9c3b641a218abcc050e"}, + {file = "numpy-1.24.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd80e219fd4c71fc3699fc1dadac5dcf4fd882bfc6f7ec53d30fa197b8ee22dc"}, + {file = "numpy-1.24.4-cp38-cp38-win32.whl", hash = "sha256:4602244f345453db537be5314d3983dbf5834a9701b7723ec28923e2889e0bb2"}, + {file = "numpy-1.24.4-cp38-cp38-win_amd64.whl", hash = "sha256:692f2e0f55794943c5bfff12b3f56f99af76f902fc47487bdfe97856de51a706"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2541312fbf09977f3b3ad449c4e5f4bb55d0dbf79226d7724211acc905049400"}, + {file = "numpy-1.24.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9667575fb6d13c95f1b36aca12c5ee3356bf001b714fc354eb5465ce1609e62f"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3a86ed21e4f87050382c7bc96571755193c4c1392490744ac73d660e8f564a9"}, + {file = "numpy-1.24.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d11efb4dbecbdf22508d55e48d9c8384db795e1b7b51ea735289ff96613ff74d"}, + {file = "numpy-1.24.4-cp39-cp39-win32.whl", hash = "sha256:6620c0acd41dbcb368610bb2f4d83145674040025e5536954782467100aa8835"}, + {file = "numpy-1.24.4-cp39-cp39-win_amd64.whl", hash = "sha256:befe2bf740fd8373cf56149a5c23a0f601e82869598d41f8e188a0e9869926f8"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:31f13e25b4e304632a4619d0e0777662c2ffea99fcae2029556b17d8ff958aef"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95f7ac6540e95bc440ad77f56e520da5bf877f87dca58bd095288dce8940532a"}, + {file = "numpy-1.24.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e98f220aa76ca2a977fe435f5b04d7b3470c0a2e6312907b37ba6068f26787f2"}, + {file = "numpy-1.24.4.tar.gz", hash = "sha256:80f5e3a4e498641401868df4208b74581206afbee7cf7b8329daae82676d9463"}, +] + +[[package]] +name = "numpy" +version = "2.1.1" +description = "Fundamental package for array computing in Python" +optional = false +python-versions = ">=3.10" +files = [ + {file = "numpy-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8a0e34993b510fc19b9a2ce7f31cb8e94ecf6e924a40c0c9dd4f62d0aac47d9"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:7dd86dfaf7c900c0bbdcb8b16e2f6ddf1eb1fe39c6c8cca6e94844ed3152a8fd"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5889dd24f03ca5a5b1e8a90a33b5a0846d8977565e4ae003a63d22ecddf6782f"}, + {file = "numpy-2.1.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:59ca673ad11d4b84ceb385290ed0ebe60266e356641428c845b39cd9df6713ab"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:13ce49a34c44b6de5241f0b38b07e44c1b2dcacd9e36c30f9c2fcb1bb5135db7"}, + {file = "numpy-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:913cc1d311060b1d409e609947fa1b9753701dac96e6581b58afc36b7ee35af6"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:caf5d284ddea7462c32b8d4a6b8af030b6c9fd5332afb70e7414d7fdded4bfd0"}, + {file = "numpy-2.1.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:57eb525e7c2a8fdee02d731f647146ff54ea8c973364f3b850069ffb42799647"}, + {file = "numpy-2.1.1-cp310-cp310-win32.whl", hash = "sha256:9a8e06c7a980869ea67bbf551283bbed2856915f0a792dc32dd0f9dd2fb56728"}, + {file = "numpy-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:d10c39947a2d351d6d466b4ae83dad4c37cd6c3cdd6d5d0fa797da56f710a6ae"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0"}, + {file = "numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca"}, + {file = "numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e"}, + {file = "numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2"}, + {file = "numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d"}, + {file = "numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f"}, + {file = "numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b"}, + {file = "numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a"}, + {file = "numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313"}, + {file = "numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed"}, + {file = "numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136"}, + {file = "numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb"}, + {file = "numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78"}, + {file = "numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556"}, + {file = "numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b"}, + {file = "numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f"}, + {file = "numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef"}, + {file = "numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c"}, + {file = "numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:7be6a07520b88214ea85d8ac8b7d6d8a1839b0b5cb87412ac9f49fa934eb15d5"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:52ac2e48f5ad847cd43c4755520a2317f3380213493b9d8a4c5e37f3b87df504"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50a95ca3560a6058d6ea91d4629a83a897ee27c00630aed9d933dff191f170cd"}, + {file = "numpy-2.1.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:99f4a9ee60eed1385a86e82288971a51e71df052ed0b2900ed30bc840c0f2e39"}, + {file = "numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd"}, +] + +[[package]] +name = "openpyxl" +version = "3.1.3" +description = "A Python library to read/write Excel 2010 xlsx/xlsm files" +optional = false +python-versions = ">=3.6" +files = [ + {file = "openpyxl-3.1.3-py2.py3-none-any.whl", hash = "sha256:25071b558db709de9e8782c3d3e058af3b23ffb2fc6f40c8f0c45a154eced2c3"}, + {file = "openpyxl-3.1.3.tar.gz", hash = "sha256:8dd482e5350125b2388070bb2477927be2e8ebc27df61178709bc8c8751da2f9"}, +] + +[package.dependencies] +et-xmlfile = "*" + +[[package]] +name = "ordered-set" +version = "4.1.0" +description = "An OrderedSet is a custom MutableSet that remembers its order, so that every" +optional = false +python-versions = ">=3.7" +files = [ + {file = "ordered-set-4.1.0.tar.gz", hash = "sha256:694a8e44c87657c59292ede72891eb91d34131f6531463aab3009191c77364a8"}, + {file = "ordered_set-4.1.0-py3-none-any.whl", hash = "sha256:046e1132c71fcf3330438a539928932caf51ddbc582496833e23de611de14562"}, +] + +[package.extras] +dev = ["black", "mypy", "pytest"] + +[[package]] +name = "orjson" +version = "3.10.7" +description = "Fast, correct Python JSON library supporting dataclasses, datetimes, and numpy" +optional = false +python-versions = ">=3.8" +files = [ + {file = "orjson-3.10.7-cp310-cp310-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:74f4544f5a6405b90da8ea724d15ac9c36da4d72a738c64685003337401f5c12"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34a566f22c28222b08875b18b0dfbf8a947e69df21a9ed5c51a6bf91cfb944ac"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bf6ba8ebc8ef5792e2337fb0419f8009729335bb400ece005606336b7fd7bab7"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ac7cf6222b29fbda9e3a472b41e6a5538b48f2c8f99261eecd60aafbdb60690c"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:de817e2f5fc75a9e7dd350c4b0f54617b280e26d1631811a43e7e968fa71e3e9"}, + {file = "orjson-3.10.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:348bdd16b32556cf8d7257b17cf2bdb7ab7976af4af41ebe79f9796c218f7e91"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:479fd0844ddc3ca77e0fd99644c7fe2de8e8be1efcd57705b5c92e5186e8a250"}, + {file = "orjson-3.10.7-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fdf5197a21dd660cf19dfd2a3ce79574588f8f5e2dbf21bda9ee2d2b46924d84"}, + {file = "orjson-3.10.7-cp310-none-win32.whl", hash = "sha256:d374d36726746c81a49f3ff8daa2898dccab6596864ebe43d50733275c629175"}, + {file = "orjson-3.10.7-cp310-none-win_amd64.whl", hash = "sha256:cb61938aec8b0ffb6eef484d480188a1777e67b05d58e41b435c74b9d84e0b9c"}, + {file = "orjson-3.10.7-cp311-cp311-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:7db8539039698ddfb9a524b4dd19508256107568cdad24f3682d5773e60504a2"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:480f455222cb7a1dea35c57a67578848537d2602b46c464472c995297117fa09"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8a9c9b168b3a19e37fe2778c0003359f07822c90fdff8f98d9d2a91b3144d8e0"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8de062de550f63185e4c1c54151bdddfc5625e37daf0aa1e75d2a1293e3b7d9a"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6b0dd04483499d1de9c8f6203f8975caf17a6000b9c0c54630cef02e44ee624e"}, + {file = "orjson-3.10.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b58d3795dafa334fc8fd46f7c5dc013e6ad06fd5b9a4cc98cb1456e7d3558bd6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:33cfb96c24034a878d83d1a9415799a73dc77480e6c40417e5dda0710d559ee6"}, + {file = "orjson-3.10.7-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e724cebe1fadc2b23c6f7415bad5ee6239e00a69f30ee423f319c6af70e2a5c0"}, + {file = "orjson-3.10.7-cp311-none-win32.whl", hash = "sha256:82763b46053727a7168d29c772ed5c870fdae2f61aa8a25994c7984a19b1021f"}, + {file = "orjson-3.10.7-cp311-none-win_amd64.whl", hash = "sha256:eb8d384a24778abf29afb8e41d68fdd9a156cf6e5390c04cc07bbc24b89e98b5"}, + {file = "orjson-3.10.7-cp312-cp312-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:44a96f2d4c3af51bfac6bc4ef7b182aa33f2f054fd7f34cc0ee9a320d051d41f"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76ac14cd57df0572453543f8f2575e2d01ae9e790c21f57627803f5e79b0d3c3"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:bdbb61dcc365dd9be94e8f7df91975edc9364d6a78c8f7adb69c1cdff318ec93"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b48b3db6bb6e0a08fa8c83b47bc169623f801e5cc4f24442ab2b6617da3b5313"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23820a1563a1d386414fef15c249040042b8e5d07b40ab3fe3efbfbbcbcb8864"}, + {file = "orjson-3.10.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0c6a008e91d10a2564edbb6ee5069a9e66df3fbe11c9a005cb411f441fd2c09"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d352ee8ac1926d6193f602cbe36b1643bbd1bbcb25e3c1a657a4390f3000c9a5"}, + {file = "orjson-3.10.7-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d2d9f990623f15c0ae7ac608103c33dfe1486d2ed974ac3f40b693bad1a22a7b"}, + {file = "orjson-3.10.7-cp312-none-win32.whl", hash = "sha256:7c4c17f8157bd520cdb7195f75ddbd31671997cbe10aee559c2d613592e7d7eb"}, + {file = "orjson-3.10.7-cp312-none-win_amd64.whl", hash = "sha256:1d9c0e733e02ada3ed6098a10a8ee0052dd55774de3d9110d29868d24b17faa1"}, + {file = "orjson-3.10.7-cp313-cp313-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:77d325ed866876c0fa6492598ec01fe30e803272a6e8b10e992288b009cbe149"}, + {file = "orjson-3.10.7-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ea2c232deedcb605e853ae1db2cc94f7390ac776743b699b50b071b02bea6fe"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:3dcfbede6737fdbef3ce9c37af3fb6142e8e1ebc10336daa05872bfb1d87839c"}, + {file = "orjson-3.10.7-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:11748c135f281203f4ee695b7f80bb1358a82a63905f9f0b794769483ea854ad"}, + {file = "orjson-3.10.7-cp313-none-win32.whl", hash = "sha256:a7e19150d215c7a13f39eb787d84db274298d3f83d85463e61d277bbd7f401d2"}, + {file = "orjson-3.10.7-cp313-none-win_amd64.whl", hash = "sha256:eef44224729e9525d5261cc8d28d6b11cafc90e6bd0be2157bde69a52ec83024"}, + {file = "orjson-3.10.7-cp38-cp38-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:6ea2b2258eff652c82652d5e0f02bd5e0463a6a52abb78e49ac288827aaa1469"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430ee4d85841e1483d487e7b81401785a5dfd69db5de01314538f31f8fbf7ee1"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4b6146e439af4c2472c56f8540d799a67a81226e11992008cb47e1267a9b3225"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:084e537806b458911137f76097e53ce7bf5806dda33ddf6aaa66a028f8d43a23"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4829cf2195838e3f93b70fd3b4292156fc5e097aac3739859ac0dcc722b27ac0"}, + {file = "orjson-3.10.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1193b2416cbad1a769f868b1749535d5da47626ac29445803dae7cc64b3f5c98"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4e6c3da13e5a57e4b3dca2de059f243ebec705857522f188f0180ae88badd354"}, + {file = "orjson-3.10.7-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c31008598424dfbe52ce8c5b47e0752dca918a4fdc4a2a32004efd9fab41d866"}, + {file = "orjson-3.10.7-cp38-none-win32.whl", hash = "sha256:7122a99831f9e7fe977dc45784d3b2edc821c172d545e6420c375e5a935f5a1c"}, + {file = "orjson-3.10.7-cp38-none-win_amd64.whl", hash = "sha256:a763bc0e58504cc803739e7df040685816145a6f3c8a589787084b54ebc9f16e"}, + {file = "orjson-3.10.7-cp39-cp39-macosx_10_15_x86_64.macosx_11_0_arm64.macosx_10_15_universal2.whl", hash = "sha256:e76be12658a6fa376fcd331b1ea4e58f5a06fd0220653450f0d415b8fd0fbe20"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed350d6978d28b92939bfeb1a0570c523f6170efc3f0a0ef1f1df287cd4f4960"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:144888c76f8520e39bfa121b31fd637e18d4cc2f115727865fdf9fa325b10412"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09b2d92fd95ad2402188cf51573acde57eb269eddabaa60f69ea0d733e789fe9"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5b24a579123fa884f3a3caadaed7b75eb5715ee2b17ab5c66ac97d29b18fe57f"}, + {file = "orjson-3.10.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591bcfe7512353bd609875ab38050efe3d55e18934e2f18950c108334b4ff"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:f4db56635b58cd1a200b0a23744ff44206ee6aa428185e2b6c4a65b3197abdcd"}, + {file = "orjson-3.10.7-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0fa5886854673222618638c6df7718ea7fe2f3f2384c452c9ccedc70b4a510a5"}, + {file = "orjson-3.10.7-cp39-none-win32.whl", hash = "sha256:8272527d08450ab16eb405f47e0f4ef0e5ff5981c3d82afe0efd25dcbef2bcd2"}, + {file = "orjson-3.10.7-cp39-none-win_amd64.whl", hash = "sha256:974683d4618c0c7dbf4f69c95a979734bf183d0658611760017f6e70a145af58"}, + {file = "orjson-3.10.7.tar.gz", hash = "sha256:75ef0640403f945f3a1f9f6400686560dbfb0fb5b16589ad62cd477043c4eee3"}, +] + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "pandas" +version = "2.0.3" +description = "Powerful data structures for data analysis, time series, and statistics" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pandas-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e4c7c9f27a4185304c7caf96dc7d91bc60bc162221152de697c98eb0b2648dd8"}, + {file = "pandas-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f167beed68918d62bffb6ec64f2e1d8a7d297a038f86d4aed056b9493fca407f"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce0c6f76a0f1ba361551f3e6dceaff06bde7514a374aa43e33b588ec10420183"}, + {file = "pandas-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba619e410a21d8c387a1ea6e8a0e49bb42216474436245718d7f2e88a2f8d7c0"}, + {file = "pandas-2.0.3-cp310-cp310-win32.whl", hash = "sha256:3ef285093b4fe5058eefd756100a367f27029913760773c8bf1d2d8bebe5d210"}, + {file = "pandas-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:9ee1a69328d5c36c98d8e74db06f4ad518a1840e8ccb94a4ba86920986bb617e"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b084b91d8d66ab19f5bb3256cbd5ea661848338301940e17f4492b2ce0801fe8"}, + {file = "pandas-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:37673e3bdf1551b95bf5d4ce372b37770f9529743d2498032439371fc7b7eb26"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9cb1e14fdb546396b7e1b923ffaeeac24e4cedd14266c3497216dd4448e4f2d"}, + {file = "pandas-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9cd88488cceb7635aebb84809d087468eb33551097d600c6dad13602029c2df"}, + {file = "pandas-2.0.3-cp311-cp311-win32.whl", hash = "sha256:694888a81198786f0e164ee3a581df7d505024fbb1f15202fc7db88a71d84ebd"}, + {file = "pandas-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6a21ab5c89dcbd57f78d0ae16630b090eec626360085a4148693def5452d8a6b"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9e4da0d45e7f34c069fe4d522359df7d23badf83abc1d1cef398895822d11061"}, + {file = "pandas-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32fca2ee1b0d93dd71d979726b12b61faa06aeb93cf77468776287f41ff8fdc5"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:258d3624b3ae734490e4d63c430256e716f488c4fcb7c8e9bde2d3aa46c29089"}, + {file = "pandas-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9eae3dc34fa1aa7772dd3fc60270d13ced7346fcbcfee017d3132ec625e23bb0"}, + {file = "pandas-2.0.3-cp38-cp38-win32.whl", hash = "sha256:f3421a7afb1a43f7e38e82e844e2bca9a6d793d66c1a7f9f0ff39a795bbc5e02"}, + {file = "pandas-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:69d7f3884c95da3a31ef82b7618af5710dba95bb885ffab339aad925c3e8ce78"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5247fb1ba347c1261cbbf0fcfba4a3121fbb4029d95d9ef4dc45406620b25c8b"}, + {file = "pandas-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:81af086f4543c9d8bb128328b5d32e9986e0c84d3ee673a2ac6fb57fd14f755e"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1994c789bf12a7c5098277fb43836ce090f1073858c10f9220998ac74f37c69b"}, + {file = "pandas-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec591c48e29226bcbb316e0c1e9423622bc7a4eaf1ef7c3c9fa1a3981f89641"}, + {file = "pandas-2.0.3-cp39-cp39-win32.whl", hash = "sha256:04dbdbaf2e4d46ca8da896e1805bc04eb85caa9a82e259e8eed00254d5e0c682"}, + {file = "pandas-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:1168574b036cd8b93abc746171c9b4f1b83467438a5e45909fed645cf8692dbc"}, + {file = "pandas-2.0.3.tar.gz", hash = "sha256:c02f372a88e0d17f36d3093a644c73cfc1788e876a7c4bcb4020a77512e2043c"}, +] + +[package.dependencies] +numpy = [ + {version = ">=1.20.3", markers = "python_version < \"3.10\""}, + {version = ">=1.21.0", markers = "python_version >= \"3.10\" and python_version < \"3.11\""}, + {version = ">=1.23.2", markers = "python_version >= \"3.11\""}, +] +python-dateutil = ">=2.8.2" +pytz = ">=2020.1" +tzdata = ">=2022.1" + +[package.extras] +all = ["PyQt5 (>=5.15.1)", "SQLAlchemy (>=1.4.16)", "beautifulsoup4 (>=4.9.3)", "bottleneck (>=1.3.2)", "brotlipy (>=0.7.0)", "fastparquet (>=0.6.3)", "fsspec (>=2021.07.0)", "gcsfs (>=2021.07.0)", "html5lib (>=1.1)", "hypothesis (>=6.34.2)", "jinja2 (>=3.0.0)", "lxml (>=4.6.3)", "matplotlib (>=3.6.1)", "numba (>=0.53.1)", "numexpr (>=2.7.3)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pandas-gbq (>=0.15.0)", "psycopg2 (>=2.8.6)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "python-snappy (>=0.6.0)", "pyxlsb (>=1.0.8)", "qtpy (>=2.2.0)", "s3fs (>=2021.08.0)", "scipy (>=1.7.1)", "tables (>=3.6.1)", "tabulate (>=0.8.9)", "xarray (>=0.21.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)", "zstandard (>=0.15.2)"] +aws = ["s3fs (>=2021.08.0)"] +clipboard = ["PyQt5 (>=5.15.1)", "qtpy (>=2.2.0)"] +compression = ["brotlipy (>=0.7.0)", "python-snappy (>=0.6.0)", "zstandard (>=0.15.2)"] +computation = ["scipy (>=1.7.1)", "xarray (>=0.21.0)"] +excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.0.7)", "pyxlsb (>=1.0.8)", "xlrd (>=2.0.1)", "xlsxwriter (>=1.4.3)"] +feather = ["pyarrow (>=7.0.0)"] +fss = ["fsspec (>=2021.07.0)"] +gcp = ["gcsfs (>=2021.07.0)", "pandas-gbq (>=0.15.0)"] +hdf5 = ["tables (>=3.6.1)"] +html = ["beautifulsoup4 (>=4.9.3)", "html5lib (>=1.1)", "lxml (>=4.6.3)"] +mysql = ["SQLAlchemy (>=1.4.16)", "pymysql (>=1.0.2)"] +output-formatting = ["jinja2 (>=3.0.0)", "tabulate (>=0.8.9)"] +parquet = ["pyarrow (>=7.0.0)"] +performance = ["bottleneck (>=1.3.2)", "numba (>=0.53.1)", "numexpr (>=2.7.1)"] +plot = ["matplotlib (>=3.6.1)"] +postgresql = ["SQLAlchemy (>=1.4.16)", "psycopg2 (>=2.8.6)"] +spss = ["pyreadstat (>=1.1.2)"] +sql-other = ["SQLAlchemy (>=1.4.16)"] +test = ["hypothesis (>=6.34.2)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"] +xml = ["lxml (>=4.6.3)"] + +[[package]] +name = "parsimonious" +version = "0.10.0" +description = "(Soon to be) the fastest pure-Python PEG parser I could muster" +optional = false +python-versions = "*" +files = [ + {file = "parsimonious-0.10.0-py3-none-any.whl", hash = "sha256:982ab435fabe86519b57f6b35610aa4e4e977e9f02a14353edf4bbc75369fc0f"}, + {file = "parsimonious-0.10.0.tar.gz", hash = "sha256:8281600da180ec8ae35427a4ab4f7b82bfec1e3d1e52f80cb60ea82b9512501c"}, +] + +[package.dependencies] +regex = ">=2022.3.15" + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.6" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.6-py3-none-any.whl", hash = "sha256:73e575e1408ab8103900836b97580d5307456908a03e92031bab39e4554cc3fb"}, + {file = "platformdirs-4.3.6.tar.gz", hash = "sha256:357fb2acbc885b0419afd3ce3ed34564c13c9b95c89360cd9563f73aa5e2b907"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry-dynamic-versioning" +version = "1.4.1" +description = "Plugin for Poetry to enable dynamic versioning based on VCS tags" +optional = false +python-versions = "<4.0,>=3.7" +files = [ + {file = "poetry_dynamic_versioning-1.4.1-py3-none-any.whl", hash = "sha256:44866ccbf869849d32baed4fc5fadf97f786180d8efa1719c88bf17a471bd663"}, + {file = "poetry_dynamic_versioning-1.4.1.tar.gz", hash = "sha256:21584d21ca405aa7d83d23d38372e3c11da664a8742995bdd517577e8676d0e1"}, +] + +[package.dependencies] +dunamai = ">=1.21.0,<2.0.0" +jinja2 = ">=2.11.1,<4" +tomlkit = ">=0.4" + +[package.extras] +plugin = ["poetry (>=1.2.0,<2.0.0)"] + +[[package]] +name = "pottery" +version = "3.0.0" +description = "Redis for Humans." +optional = false +python-versions = ">=3.7, <4" +files = [ + {file = "pottery-3.0.0-py3-none-any.whl", hash = "sha256:0190323bbb1289d40c5cd683feb04c4b8cff76a6c723f3ded9137c8bcc9fb5f8"}, + {file = "pottery-3.0.0.tar.gz", hash = "sha256:adda303e9357442bcac1d4c7f86aa7deec855e0190c101d09448afbcf5676a74"}, +] + +[package.dependencies] +mmh3 = "*" +redis = ">=4,<5" +typing-extensions = "*" + +[[package]] +name = "protobuf" +version = "5.28.2" +description = "" +optional = false +python-versions = ">=3.8" +files = [ + {file = "protobuf-5.28.2-cp310-abi3-win32.whl", hash = "sha256:eeea10f3dc0ac7e6b4933d32db20662902b4ab81bf28df12218aa389e9c2102d"}, + {file = "protobuf-5.28.2-cp310-abi3-win_amd64.whl", hash = "sha256:2c69461a7fcc8e24be697624c09a839976d82ae75062b11a0972e41fd2cd9132"}, + {file = "protobuf-5.28.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:a8b9403fc70764b08d2f593ce44f1d2920c5077bf7d311fefec999f8c40f78b7"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_aarch64.whl", hash = "sha256:35cfcb15f213449af7ff6198d6eb5f739c37d7e4f1c09b5d0641babf2cc0c68f"}, + {file = "protobuf-5.28.2-cp38-abi3-manylinux2014_x86_64.whl", hash = "sha256:5e8a95246d581eef20471b5d5ba010d55f66740942b95ba9b872d918c459452f"}, + {file = "protobuf-5.28.2-cp38-cp38-win32.whl", hash = "sha256:87317e9bcda04a32f2ee82089a204d3a2f0d3c8aeed16568c7daf4756e4f1fe0"}, + {file = "protobuf-5.28.2-cp38-cp38-win_amd64.whl", hash = "sha256:c0ea0123dac3399a2eeb1a1443d82b7afc9ff40241433296769f7da42d142ec3"}, + {file = "protobuf-5.28.2-cp39-cp39-win32.whl", hash = "sha256:ca53faf29896c526863366a52a8f4d88e69cd04ec9571ed6082fa117fac3ab36"}, + {file = "protobuf-5.28.2-cp39-cp39-win_amd64.whl", hash = "sha256:8ddc60bf374785fb7cb12510b267f59067fa10087325b8e1855b898a0d81d276"}, + {file = "protobuf-5.28.2-py3-none-any.whl", hash = "sha256:52235802093bd8a2811abbe8bf0ab9c5f54cca0a751fdd3f6ac2a21438bffece"}, + {file = "protobuf-5.28.2.tar.gz", hash = "sha256:59379674ff119717404f7454647913787034f03fe7049cbef1d74a97bb4593f0"}, +] + +[[package]] +name = "psycopg2-binary" +version = "2.9.9" +description = "psycopg2 - Python-PostgreSQL Database Adapter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"}, + {file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"}, + {file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b0605eaed3eb239e87df0d5e3c6489daae3f7388d455d0c0b4df899519c6a38d"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win32.whl", hash = "sha256:64cf30263844fa208851ebb13b0732ce674d8ec6a0c86a4e160495d299ba3c93"}, + {file = "psycopg2_binary-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:81ff62668af011f9a48787564ab7eded4e9fb17a4a6a74af5ffa6a457400d2ab"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"}, + {file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"}, + {file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"}, + {file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"}, +] + +[[package]] +name = "pycodestyle" +version = "2.7.0" +description = "Python style guide checker" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pycodestyle-2.7.0-py2.py3-none-any.whl", hash = "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068"}, + {file = "pycodestyle-2.7.0.tar.gz", hash = "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef"}, +] + +[[package]] +name = "pycryptodome" +version = "3.20.0" +description = "Cryptographic library for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"}, + {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"}, + {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"}, + {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"}, + {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"}, + {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"}, + {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"}, + {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"}, + {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"}, + {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"}, +] + +[[package]] +name = "pyflakes" +version = "2.3.1" +description = "passive checker of Python programs" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pyflakes-2.3.1-py2.py3-none-any.whl", hash = "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3"}, + {file = "pyflakes-2.3.1.tar.gz", hash = "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pytest" +version = "8.3.3" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"}, + {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pytz" +version = "2024.2" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +files = [ + {file = "pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725"}, + {file = "pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a"}, +] + +[[package]] +name = "pyunormalize" +version = "16.0.0" +description = "Unicode normalization forms (NFC, NFKC, NFD, NFKD). A library independent of the Python core Unicode database." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pyunormalize-16.0.0-py3-none-any.whl", hash = "sha256:c647d95e5d1e2ea9a2f448d1d95d8518348df24eab5c3fd32d2b5c3300a49152"}, + {file = "pyunormalize-16.0.0.tar.gz", hash = "sha256:2e1dfbb4a118154ae26f70710426a52a364b926c9191f764601f5a8cb12761f7"}, +] + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.2" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, + {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8824b5a04a04a047e72eea5cec3bc266db09e35de6bdfe34c9436ac5ee27d237"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c36280e6fb8385e520936c3cb3b8042851904eba0e58d277dca80a5cfed590b"}, + {file = "PyYAML-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec031d5d2feb36d1d1a24380e4db6d43695f3748343d99434e6f5f9156aaa2ed"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:936d68689298c36b53b29f23c6dbb74de12b4ac12ca6cfe0e047bedceea56180"}, + {file = "PyYAML-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23502f431948090f597378482b4812b0caae32c22213aecf3b55325e049a6c68"}, + {file = "PyYAML-6.0.2-cp310-cp310-win32.whl", hash = "sha256:2e99c6826ffa974fe6e27cdb5ed0021786b03fc98e5ee3c5bfe1fd5015f42b99"}, + {file = "PyYAML-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:a4d3091415f010369ae4ed1fc6b79def9416358877534caf6a0fdd2146c87a3e"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774"}, + {file = "PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317"}, + {file = "PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4"}, + {file = "PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e"}, + {file = "PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5"}, + {file = "PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab"}, + {file = "PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425"}, + {file = "PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48"}, + {file = "PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b"}, + {file = "PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4"}, + {file = "PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba"}, + {file = "PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484"}, + {file = "PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc"}, + {file = "PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652"}, + {file = "PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183"}, + {file = "PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563"}, + {file = "PyYAML-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:24471b829b3bf607e04e88d79542a9d48bb037c2267d7927a874e6c205ca7e9a"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7fded462629cfa4b685c5416b949ebad6cec74af5e2d42905d41e257e0869f5"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d84a1718ee396f54f3a086ea0a66d8e552b2ab2017ef8b420e92edbc841c352d"}, + {file = "PyYAML-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9056c1ecd25795207ad294bcf39f2db3d845767be0ea6e6a34d856f006006083"}, + {file = "PyYAML-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:82d09873e40955485746739bcb8b4586983670466c23382c19cffecbf1fd8706"}, + {file = "PyYAML-6.0.2-cp38-cp38-win32.whl", hash = "sha256:43fa96a3ca0d6b1812e01ced1044a003533c47f6ee8aca31724f78e93ccc089a"}, + {file = "PyYAML-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:01179a4a8559ab5de078078f37e5c1a30d76bb88519906844fd7bdea1b7729ff"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:688ba32a1cffef67fd2e9398a2efebaea461578b0923624778664cc1c914db5d"}, + {file = "PyYAML-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8786accb172bd8afb8be14490a16625cbc387036876ab6ba70912730faf8e1f"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8e03406cac8513435335dbab54c0d385e4a49e4945d2909a581c83647ca0290"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f753120cb8181e736c57ef7636e83f31b9c0d1722c516f7e86cf15b7aa57ff12"}, + {file = "PyYAML-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3b1fdb9dc17f5a7677423d508ab4f243a726dea51fa5e70992e59a7411c89d19"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0b69e4ce7a131fe56b7e4d770c67429700908fc0752af059838b1cfb41960e4e"}, + {file = "PyYAML-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a9f8c2e67970f13b16084e04f134610fd1d374bf477b17ec1599185cf611d725"}, + {file = "PyYAML-6.0.2-cp39-cp39-win32.whl", hash = "sha256:6395c297d42274772abc367baaa79683958044e5d3835486c16da75d2a694631"}, + {file = "PyYAML-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:39693e1f8320ae4f43943590b49779ffb98acb81f788220ea932a6b6c51004d8"}, + {file = "pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e"}, +] + +[[package]] +name = "redis" +version = "4.6.0" +description = "Python client for Redis database and key-value store" +optional = false +python-versions = ">=3.7" +files = [ + {file = "redis-4.6.0-py3-none-any.whl", hash = "sha256:e2b03db868160ee4591de3cb90d40ebb50a90dd302138775937f6a42b7ed183c"}, + {file = "redis-4.6.0.tar.gz", hash = "sha256:585dc516b9eb042a619ef0a39c3d7d55fe81bdb4df09a52c9cdde0d07bf1aa7d"}, +] + +[package.dependencies] +async-timeout = {version = ">=4.0.2", markers = "python_full_version <= \"3.11.2\""} + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "referencing" +version = "0.35.1" +description = "JSON Referencing + Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "referencing-0.35.1-py3-none-any.whl", hash = "sha256:eda6d3234d62814d1c64e305c1331c9a3a6132da475ab6382eaa997b21ee75de"}, + {file = "referencing-0.35.1.tar.gz", hash = "sha256:25b42124a6c8b632a425174f24087783efb348a6f1e0008e63cd4466fedf703c"}, +] + +[package.dependencies] +attrs = ">=22.2.0" +rpds-py = ">=0.7.0" + +[[package]] +name = "regex" +version = "2024.9.11" +description = "Alternative regular expression module, to replace re." +optional = false +python-versions = ">=3.8" +files = [ + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1494fa8725c285a81d01dc8c06b55287a1ee5e0e382d8413adc0a9197aac6408"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0e12c481ad92d129c78f13a2a3662317e46ee7ef96c94fd332e1c29131875b7d"}, + {file = "regex-2024.9.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:16e13a7929791ac1216afde26f712802e3df7bf0360b32e4914dca3ab8baeea5"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:46989629904bad940bbec2106528140a218b4a36bb3042d8406980be1941429c"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a906ed5e47a0ce5f04b2c981af1c9acf9e8696066900bf03b9d7879a6f679fc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e9a091b0550b3b0207784a7d6d0f1a00d1d1c8a11699c1a4d93db3fbefc3ad35"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ddcd9a179c0a6fa8add279a4444015acddcd7f232a49071ae57fa6e278f1f71"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b41e1adc61fa347662b09398e31ad446afadff932a24807d3ceb955ed865cc8"}, + {file = "regex-2024.9.11-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ced479f601cd2f8ca1fd7b23925a7e0ad512a56d6e9476f79b8f381d9d37090a"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:635a1d96665f84b292e401c3d62775851aedc31d4f8784117b3c68c4fcd4118d"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c0256beda696edcf7d97ef16b2a33a8e5a875affd6fa6567b54f7c577b30a137"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:3ce4f1185db3fbde8ed8aa223fc9620f276c58de8b0d4f8cc86fd1360829edb6"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:09d77559e80dcc9d24570da3745ab859a9cf91953062e4ab126ba9d5993688ca"}, + {file = "regex-2024.9.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7a22ccefd4db3f12b526eccb129390942fe874a3a9fdbdd24cf55773a1faab1a"}, + {file = "regex-2024.9.11-cp310-cp310-win32.whl", hash = "sha256:f745ec09bc1b0bd15cfc73df6fa4f726dcc26bb16c23a03f9e3367d357eeedd0"}, + {file = "regex-2024.9.11-cp310-cp310-win_amd64.whl", hash = "sha256:01c2acb51f8a7d6494c8c5eafe3d8e06d76563d8a8a4643b37e9b2dd8a2ff623"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2cce2449e5927a0bf084d346da6cd5eb016b2beca10d0013ab50e3c226ffc0df"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3b37fa423beefa44919e009745ccbf353d8c981516e807995b2bd11c2c77d268"}, + {file = "regex-2024.9.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64ce2799bd75039b480cc0360907c4fb2f50022f030bf9e7a8705b636e408fad"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4cc92bb6db56ab0c1cbd17294e14f5e9224f0cc6521167ef388332604e92679"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d05ac6fa06959c4172eccd99a222e1fbf17b5670c4d596cb1e5cde99600674c4"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040562757795eeea356394a7fb13076ad4f99d3c62ab0f8bdfb21f99a1f85664"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6113c008a7780792efc80f9dfe10ba0cd043cbf8dc9a76ef757850f51b4edc50"}, + {file = "regex-2024.9.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e5fb5f77c8745a60105403a774fe2c1759b71d3e7b4ca237a5e67ad066c7199"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:54d9ff35d4515debf14bc27f1e3b38bfc453eff3220f5bce159642fa762fe5d4"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:df5cbb1fbc74a8305b6065d4ade43b993be03dbe0f8b30032cced0d7740994bd"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:7fb89ee5d106e4a7a51bce305ac4efb981536301895f7bdcf93ec92ae0d91c7f"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:a738b937d512b30bf75995c0159c0ddf9eec0775c9d72ac0202076c72f24aa96"}, + {file = "regex-2024.9.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:e28f9faeb14b6f23ac55bfbbfd3643f5c7c18ede093977f1df249f73fd22c7b1"}, + {file = "regex-2024.9.11-cp311-cp311-win32.whl", hash = "sha256:18e707ce6c92d7282dfce370cd205098384b8ee21544e7cb29b8aab955b66fa9"}, + {file = "regex-2024.9.11-cp311-cp311-win_amd64.whl", hash = "sha256:313ea15e5ff2a8cbbad96ccef6be638393041b0a7863183c2d31e0c6116688cf"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b0d0a6c64fcc4ef9c69bd5b3b3626cc3776520a1637d8abaa62b9edc147a58f7"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:49b0e06786ea663f933f3710a51e9385ce0cba0ea56b67107fd841a55d56a231"}, + {file = "regex-2024.9.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5b513b6997a0b2f10e4fd3a1313568e373926e8c252bd76c960f96fd039cd28d"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee439691d8c23e76f9802c42a95cfeebf9d47cf4ffd06f18489122dbb0a7ad64"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a8f877c89719d759e52783f7fe6e1c67121076b87b40542966c02de5503ace42"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23b30c62d0f16827f2ae9f2bb87619bc4fba2044911e2e6c2eb1af0161cdb766"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ab7824093d8f10d44330fe1e6493f756f252d145323dd17ab6b48733ff6c0a"}, + {file = "regex-2024.9.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dee5b4810a89447151999428fe096977346cf2f29f4d5e29609d2e19e0199c9"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:98eeee2f2e63edae2181c886d7911ce502e1292794f4c5ee71e60e23e8d26b5d"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:57fdd2e0b2694ce6fc2e5ccf189789c3e2962916fb38779d3e3521ff8fe7a822"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:d552c78411f60b1fdaafd117a1fca2f02e562e309223b9d44b7de8be451ec5e0"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a0b2b80321c2ed3fcf0385ec9e51a12253c50f146fddb2abbb10f033fe3d049a"}, + {file = "regex-2024.9.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:18406efb2f5a0e57e3a5881cd9354c1512d3bb4f5c45d96d110a66114d84d23a"}, + {file = "regex-2024.9.11-cp312-cp312-win32.whl", hash = "sha256:e464b467f1588e2c42d26814231edecbcfe77f5ac414d92cbf4e7b55b2c2a776"}, + {file = "regex-2024.9.11-cp312-cp312-win_amd64.whl", hash = "sha256:9e8719792ca63c6b8340380352c24dcb8cd7ec49dae36e963742a275dfae6009"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c157bb447303070f256e084668b702073db99bbb61d44f85d811025fcf38f784"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db21ece84dfeefc5d8a3863f101995de646c6cb0536952c321a2650aa202c36"}, + {file = "regex-2024.9.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:220e92a30b426daf23bb67a7962900ed4613589bab80382be09b48896d211e92"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1ae19e64c14c7ec1995f40bd932448713d3c73509e82d8cd7744dc00e29e86"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f47cd43a5bfa48f86925fe26fbdd0a488ff15b62468abb5d2a1e092a4fb10e85"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9d4a76b96f398697fe01117093613166e6aa8195d63f1b4ec3f21ab637632963"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0ea51dcc0835eea2ea31d66456210a4e01a076d820e9039b04ae8d17ac11dee6"}, + {file = "regex-2024.9.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7aaa315101c6567a9a45d2839322c51c8d6e81f67683d529512f5bcfb99c802"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c57d08ad67aba97af57a7263c2d9006d5c404d721c5f7542f077f109ec2a4a29"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8404bf61298bb6f8224bb9176c1424548ee1181130818fcd2cbffddc768bed8"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:dd4490a33eb909ef5078ab20f5f000087afa2a4daa27b4c072ccb3cb3050ad84"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:eee9130eaad130649fd73e5cd92f60e55708952260ede70da64de420cdcad554"}, + {file = "regex-2024.9.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6a2644a93da36c784e546de579ec1806bfd2763ef47babc1b03d765fe560c9f8"}, + {file = "regex-2024.9.11-cp313-cp313-win32.whl", hash = "sha256:e997fd30430c57138adc06bba4c7c2968fb13d101e57dd5bb9355bf8ce3fa7e8"}, + {file = "regex-2024.9.11-cp313-cp313-win_amd64.whl", hash = "sha256:042c55879cfeb21a8adacc84ea347721d3d83a159da6acdf1116859e2427c43f"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:35f4a6f96aa6cb3f2f7247027b07b15a374f0d5b912c0001418d1d55024d5cb4"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:55b96e7ce3a69a8449a66984c268062fbaa0d8ae437b285428e12797baefce7e"}, + {file = "regex-2024.9.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cb130fccd1a37ed894824b8c046321540263013da72745d755f2d35114b81a60"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:323c1f04be6b2968944d730e5c2091c8c89767903ecaa135203eec4565ed2b2b"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:be1c8ed48c4c4065ecb19d882a0ce1afe0745dfad8ce48c49586b90a55f02366"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5b029322e6e7b94fff16cd120ab35a253236a5f99a79fb04fda7ae71ca20ae8"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6fff13ef6b5f29221d6904aa816c34701462956aa72a77f1f151a8ec4f56aeb"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:587d4af3979376652010e400accc30404e6c16b7df574048ab1f581af82065e4"}, + {file = "regex-2024.9.11-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:079400a8269544b955ffa9e31f186f01d96829110a3bf79dc338e9910f794fca"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:f9268774428ec173654985ce55fc6caf4c6d11ade0f6f914d48ef4719eb05ebb"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:23f9985c8784e544d53fc2930fc1ac1a7319f5d5332d228437acc9f418f2f168"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:ae2941333154baff9838e88aa71c1d84f4438189ecc6021a12c7573728b5838e"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:e93f1c331ca8e86fe877a48ad64e77882c0c4da0097f2212873a69bbfea95d0c"}, + {file = "regex-2024.9.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:846bc79ee753acf93aef4184c040d709940c9d001029ceb7b7a52747b80ed2dd"}, + {file = "regex-2024.9.11-cp38-cp38-win32.whl", hash = "sha256:c94bb0a9f1db10a1d16c00880bdebd5f9faf267273b8f5bd1878126e0fbde771"}, + {file = "regex-2024.9.11-cp38-cp38-win_amd64.whl", hash = "sha256:2b08fce89fbd45664d3df6ad93e554b6c16933ffa9d55cb7e01182baaf971508"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:07f45f287469039ffc2c53caf6803cd506eb5f5f637f1d4acb37a738f71dd066"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4838e24ee015101d9f901988001038f7f0d90dc0c3b115541a1365fb439add62"}, + {file = "regex-2024.9.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6edd623bae6a737f10ce853ea076f56f507fd7726bee96a41ee3d68d347e4d16"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c69ada171c2d0e97a4b5aa78fbb835e0ffbb6b13fc5da968c09811346564f0d3"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02087ea0a03b4af1ed6ebab2c54d7118127fee8d71b26398e8e4b05b78963199"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69dee6a020693d12a3cf892aba4808fe168d2a4cef368eb9bf74f5398bfd4ee8"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:297f54910247508e6e5cae669f2bc308985c60540a4edd1c77203ef19bfa63ca"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ecea58b43a67b1b79805f1a0255730edaf5191ecef84dbc4cc85eb30bc8b63b9"}, + {file = "regex-2024.9.11-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eab4bb380f15e189d1313195b062a6aa908f5bd687a0ceccd47c8211e9cf0d4a"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0cbff728659ce4bbf4c30b2a1be040faafaa9eca6ecde40aaff86f7889f4ab39"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:54c4a097b8bc5bb0dfc83ae498061d53ad7b5762e00f4adaa23bee22b012e6ba"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:73d6d2f64f4d894c96626a75578b0bf7d9e56dcda8c3d037a2118fdfe9b1c664"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:e53b5fbab5d675aec9f0c501274c467c0f9a5d23696cfc94247e1fb56501ed89"}, + {file = "regex-2024.9.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:0ffbcf9221e04502fc35e54d1ce9567541979c3fdfb93d2c554f0ca583a19b35"}, + {file = "regex-2024.9.11-cp39-cp39-win32.whl", hash = "sha256:e4c22e1ac1f1ec1e09f72e6c44d8f2244173db7eb9629cc3a346a8d7ccc31142"}, + {file = "regex-2024.9.11-cp39-cp39-win_amd64.whl", hash = "sha256:faa3c142464efec496967359ca99696c896c591c56c53506bac1ad465f66e919"}, + {file = "regex-2024.9.11.tar.gz", hash = "sha256:6c188c307e8433bcb63dc1915022deb553b4203a70722fc542c363bf120a01fd"}, +] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "rich" +version = "13.8.1" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.1-py3-none-any.whl", hash = "sha256:1760a3c0848469b97b558fc61c85233e3dafb69c7a071b4d60c38099d3cd4c06"}, + {file = "rich-13.8.1.tar.gz", hash = "sha256:8260cda28e3db6bf04d2d1ef4dbc03ba80a824c88b0e7668a0f23126a424844a"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rlp" +version = "4.0.1" +description = "rlp: A package for Recursive Length Prefix encoding and decoding" +optional = false +python-versions = "<4,>=3.8" +files = [ + {file = "rlp-4.0.1-py3-none-any.whl", hash = "sha256:ff6846c3c27b97ee0492373aa074a7c3046aadd973320f4fffa7ac45564b0258"}, + {file = "rlp-4.0.1.tar.gz", hash = "sha256:bcefb11013dfadf8902642337923bd0c786dc8a27cb4c21da6e154e52869ecb1"}, +] + +[package.dependencies] +eth-utils = ">=2" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "hypothesis (==5.19.0)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] +docs = ["sphinx (>=6.0.0)", "sphinx-autobuild (>=2021.3.14)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +rust-backend = ["rusty-rlp (>=0.2.1)"] +test = ["hypothesis (==5.19.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] + +[[package]] +name = "rpds-py" +version = "0.20.0" +description = "Python bindings to Rust's persistent data structures (rpds)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rpds_py-0.20.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3ad0fda1635f8439cde85c700f964b23ed5fc2d28016b32b9ee5fe30da5c84e2"}, + {file = "rpds_py-0.20.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9bb4a0d90fdb03437c109a17eade42dfbf6190408f29b2744114d11586611d6f"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c6377e647bbfd0a0b159fe557f2c6c602c159fc752fa316572f012fc0bf67150"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:eb851b7df9dda52dc1415ebee12362047ce771fc36914586b2e9fcbd7d293b3e"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e0f80b739e5a8f54837be5d5c924483996b603d5502bfff79bf33da06164ee2"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a8c94dad2e45324fc74dce25e1645d4d14df9a4e54a30fa0ae8bad9a63928e3"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e604fe73ba048c06085beaf51147eaec7df856824bfe7b98657cf436623daf"}, + {file = "rpds_py-0.20.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:df3de6b7726b52966edf29663e57306b23ef775faf0ac01a3e9f4012a24a4140"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cf258ede5bc22a45c8e726b29835b9303c285ab46fc7c3a4cc770736b5304c9f"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:55fea87029cded5df854ca7e192ec7bdb7ecd1d9a3f63d5c4eb09148acf4a7ce"}, + {file = "rpds_py-0.20.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ae94bd0b2f02c28e199e9bc51485d0c5601f58780636185660f86bf80c89af94"}, + {file = "rpds_py-0.20.0-cp310-none-win32.whl", hash = "sha256:28527c685f237c05445efec62426d285e47a58fb05ba0090a4340b73ecda6dee"}, + {file = "rpds_py-0.20.0-cp310-none-win_amd64.whl", hash = "sha256:238a2d5b1cad28cdc6ed15faf93a998336eb041c4e440dd7f902528b8891b399"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ac2f4f7a98934c2ed6505aead07b979e6f999389f16b714448fb39bbaa86a489"}, + {file = "rpds_py-0.20.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:220002c1b846db9afd83371d08d239fdc865e8f8c5795bbaec20916a76db3318"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8d7919548df3f25374a1f5d01fbcd38dacab338ef5f33e044744b5c36729c8db"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:758406267907b3781beee0f0edfe4a179fbd97c0be2e9b1154d7f0a1279cf8e5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3d61339e9f84a3f0767b1995adfb171a0d00a1185192718a17af6e124728e0f5"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1259c7b3705ac0a0bd38197565a5d603218591d3f6cee6e614e380b6ba61c6f6"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c1dc0f53856b9cc9a0ccca0a7cc61d3d20a7088201c0937f3f4048c1718a209"}, + {file = "rpds_py-0.20.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7e60cb630f674a31f0368ed32b2a6b4331b8350d67de53c0359992444b116dd3"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:dbe982f38565bb50cb7fb061ebf762c2f254ca3d8c20d4006878766e84266272"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:514b3293b64187172bc77c8fb0cdae26981618021053b30d8371c3a902d4d5ad"}, + {file = "rpds_py-0.20.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d0a26ffe9d4dd35e4dfdd1e71f46401cff0181c75ac174711ccff0459135fa58"}, + {file = "rpds_py-0.20.0-cp311-none-win32.whl", hash = "sha256:89c19a494bf3ad08c1da49445cc5d13d8fefc265f48ee7e7556839acdacf69d0"}, + {file = "rpds_py-0.20.0-cp311-none-win_amd64.whl", hash = "sha256:c638144ce971df84650d3ed0096e2ae7af8e62ecbbb7b201c8935c370df00a2c"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:a84ab91cbe7aab97f7446652d0ed37d35b68a465aeef8fc41932a9d7eee2c1a6"}, + {file = "rpds_py-0.20.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:56e27147a5a4c2c21633ff8475d185734c0e4befd1c989b5b95a5d0db699b21b"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2580b0c34583b85efec8c5c5ec9edf2dfe817330cc882ee972ae650e7b5ef739"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b80d4a7900cf6b66bb9cee5c352b2d708e29e5a37fe9bf784fa97fc11504bf6c"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50eccbf054e62a7b2209b28dc7a22d6254860209d6753e6b78cfaeb0075d7bee"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:49a8063ea4296b3a7e81a5dfb8f7b2d73f0b1c20c2af401fb0cdf22e14711a96"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea438162a9fcbee3ecf36c23e6c68237479f89f962f82dae83dc15feeceb37e4"}, + {file = "rpds_py-0.20.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:18d7585c463087bddcfa74c2ba267339f14f2515158ac4db30b1f9cbdb62c8ef"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d4c7d1a051eeb39f5c9547e82ea27cbcc28338482242e3e0b7768033cb083821"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e4df1e3b3bec320790f699890d41c59d250f6beda159ea3c44c3f5bac1976940"}, + {file = "rpds_py-0.20.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2cf126d33a91ee6eedc7f3197b53e87a2acdac63602c0f03a02dd69e4b138174"}, + {file = "rpds_py-0.20.0-cp312-none-win32.whl", hash = "sha256:8bc7690f7caee50b04a79bf017a8d020c1f48c2a1077ffe172abec59870f1139"}, + {file = "rpds_py-0.20.0-cp312-none-win_amd64.whl", hash = "sha256:0e13e6952ef264c40587d510ad676a988df19adea20444c2b295e536457bc585"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:aa9a0521aeca7d4941499a73ad7d4f8ffa3d1affc50b9ea11d992cd7eff18a29"}, + {file = "rpds_py-0.20.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4a1f1d51eccb7e6c32ae89243cb352389228ea62f89cd80823ea7dd1b98e0b91"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a86a9b96070674fc88b6f9f71a97d2c1d3e5165574615d1f9168ecba4cecb24"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6c8ef2ebf76df43f5750b46851ed1cdf8f109d7787ca40035fe19fbdc1acc5a7"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b74b25f024b421d5859d156750ea9a65651793d51b76a2e9238c05c9d5f203a9"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:57eb94a8c16ab08fef6404301c38318e2c5a32216bf5de453e2714c964c125c8"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1940dae14e715e2e02dfd5b0f64a52e8374a517a1e531ad9412319dc3ac7879"}, + {file = "rpds_py-0.20.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d20277fd62e1b992a50c43f13fbe13277a31f8c9f70d59759c88f644d66c619f"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:06db23d43f26478303e954c34c75182356ca9aa7797d22c5345b16871ab9c45c"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b2a5db5397d82fa847e4c624b0c98fe59d2d9b7cf0ce6de09e4d2e80f8f5b3f2"}, + {file = "rpds_py-0.20.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5a35df9f5548fd79cb2f52d27182108c3e6641a4feb0f39067911bf2adaa3e57"}, + {file = "rpds_py-0.20.0-cp313-none-win32.whl", hash = "sha256:fd2d84f40633bc475ef2d5490b9c19543fbf18596dcb1b291e3a12ea5d722f7a"}, + {file = "rpds_py-0.20.0-cp313-none-win_amd64.whl", hash = "sha256:9bc2d153989e3216b0559251b0c260cfd168ec78b1fac33dd485750a228db5a2"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:f2fbf7db2012d4876fb0d66b5b9ba6591197b0f165db8d99371d976546472a24"}, + {file = "rpds_py-0.20.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1e5f3cd7397c8f86c8cc72d5a791071431c108edd79872cdd96e00abd8497d29"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ce9845054c13696f7af7f2b353e6b4f676dab1b4b215d7fe5e05c6f8bb06f965"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c3e130fd0ec56cb76eb49ef52faead8ff09d13f4527e9b0c400307ff72b408e1"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b16aa0107ecb512b568244ef461f27697164d9a68d8b35090e9b0c1c8b27752"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa7f429242aae2947246587d2964fad750b79e8c233a2367f71b554e9447949c"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af0fc424a5842a11e28956e69395fbbeab2c97c42253169d87e90aac2886d751"}, + {file = "rpds_py-0.20.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b8c00a3b1e70c1d3891f0db1b05292747f0dbcfb49c43f9244d04c70fbc40eb8"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40ce74fc86ee4645d0a225498d091d8bc61f39b709ebef8204cb8b5a464d3c0e"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:4fe84294c7019456e56d93e8ababdad5a329cd25975be749c3f5f558abb48253"}, + {file = "rpds_py-0.20.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:338ca4539aad4ce70a656e5187a3a31c5204f261aef9f6ab50e50bcdffaf050a"}, + {file = "rpds_py-0.20.0-cp38-none-win32.whl", hash = "sha256:54b43a2b07db18314669092bb2de584524d1ef414588780261e31e85846c26a5"}, + {file = "rpds_py-0.20.0-cp38-none-win_amd64.whl", hash = "sha256:a1862d2d7ce1674cffa6d186d53ca95c6e17ed2b06b3f4c476173565c862d232"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:3fde368e9140312b6e8b6c09fb9f8c8c2f00999d1823403ae90cc00480221b22"}, + {file = "rpds_py-0.20.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9824fb430c9cf9af743cf7aaf6707bf14323fb51ee74425c380f4c846ea70789"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11ef6ce74616342888b69878d45e9f779b95d4bd48b382a229fe624a409b72c5"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:c52d3f2f82b763a24ef52f5d24358553e8403ce05f893b5347098014f2d9eff2"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9d35cef91e59ebbeaa45214861874bc6f19eb35de96db73e467a8358d701a96c"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d72278a30111e5b5525c1dd96120d9e958464316f55adb030433ea905866f4de"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b4c29cbbba378759ac5786730d1c3cb4ec6f8ababf5c42a9ce303dc4b3d08cda"}, + {file = "rpds_py-0.20.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6632f2d04f15d1bd6fe0eedd3b86d9061b836ddca4c03d5cf5c7e9e6b7c14580"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d0b67d87bb45ed1cd020e8fbf2307d449b68abc45402fe1a4ac9e46c3c8b192b"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ec31a99ca63bf3cd7f1a5ac9fe95c5e2d060d3c768a09bc1d16e235840861420"}, + {file = "rpds_py-0.20.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22e6c9976e38f4d8c4a63bd8a8edac5307dffd3ee7e6026d97f3cc3a2dc02a0b"}, + {file = "rpds_py-0.20.0-cp39-none-win32.whl", hash = "sha256:569b3ea770c2717b730b61998b6c54996adee3cef69fc28d444f3e7920313cf7"}, + {file = "rpds_py-0.20.0-cp39-none-win_amd64.whl", hash = "sha256:e6900ecdd50ce0facf703f7a00df12374b74bbc8ad9fe0f6559947fb20f82364"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:617c7357272c67696fd052811e352ac54ed1d9b49ab370261a80d3b6ce385045"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:9426133526f69fcaba6e42146b4e12d6bc6c839b8b555097020e2b78ce908dcc"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deb62214c42a261cb3eb04d474f7155279c1a8a8c30ac89b7dcb1721d92c3c02"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:fcaeb7b57f1a1e071ebd748984359fef83ecb026325b9d4ca847c95bc7311c92"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d454b8749b4bd70dd0a79f428731ee263fa6995f83ccb8bada706e8d1d3ff89d"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d807dc2051abe041b6649681dce568f8e10668e3c1c6543ebae58f2d7e617855"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3c20f0ddeb6e29126d45f89206b8291352b8c5b44384e78a6499d68b52ae511"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b7f19250ceef892adf27f0399b9e5afad019288e9be756d6919cb58892129f51"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:4f1ed4749a08379555cebf4650453f14452eaa9c43d0a95c49db50c18b7da075"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_i686.whl", hash = "sha256:dcedf0b42bcb4cfff4101d7771a10532415a6106062f005ab97d1d0ab5681c60"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:39ed0d010457a78f54090fafb5d108501b5aa5604cc22408fc1c0c77eac14344"}, + {file = "rpds_py-0.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:bb273176be34a746bdac0b0d7e4e2c467323d13640b736c4c477881a3220a989"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f918a1a130a6dfe1d7fe0f105064141342e7dd1611f2e6a21cd2f5c8cb1cfb3e"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:f60012a73aa396be721558caa3a6fd49b3dd0033d1675c6d59c4502e870fcf0c"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d2b1ad682a3dfda2a4e8ad8572f3100f95fad98cb99faf37ff0ddfe9cbf9d03"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:614fdafe9f5f19c63ea02817fa4861c606a59a604a77c8cdef5aa01d28b97921"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fa518bcd7600c584bf42e6617ee8132869e877db2f76bcdc281ec6a4113a53ab"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f0475242f447cc6cb8a9dd486d68b2ef7fbee84427124c232bff5f63b1fe11e5"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90a4cd061914a60bd51c68bcb4357086991bd0bb93d8aa66a6da7701370708f"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:def7400461c3a3f26e49078302e1c1b38f6752342c77e3cf72ce91ca69fb1bc1"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_aarch64.whl", hash = "sha256:65794e4048ee837494aea3c21a28ad5fc080994dfba5b036cf84de37f7ad5074"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_i686.whl", hash = "sha256:faefcc78f53a88f3076b7f8be0a8f8d35133a3ecf7f3770895c25f8813460f08"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-musllinux_1_2_x86_64.whl", hash = "sha256:5b4f105deeffa28bbcdff6c49b34e74903139afa690e35d2d9e3c2c2fba18cec"}, + {file = "rpds_py-0.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdfc3a892927458d98f3d55428ae46b921d1f7543b89382fdb483f5640daaec8"}, + {file = "rpds_py-0.20.0.tar.gz", hash = "sha256:d72a210824facfdaf8768cf2d7ca25a042c30320b3020de2fa04640920d4e121"}, +] + +[[package]] +name = "ruff" +version = "0.0.235" +description = "An extremely fast Python linter, written in Rust." +optional = false +python-versions = ">=3.7" +files = [ + {file = "ruff-0.0.235-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:50327fe28aa914c4b2e3d06c3e41f47bcfbd595843a26f5f7fda30ca5318755f"}, + {file = "ruff-0.0.235-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:d29966029ff77a1c336004ff3e1effd33db8554ad9ec9f87ff339d0f3d44ae35"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50baf2635584b93c09d1e69bca51041eb4ff584b20b0a443124feb7019591a4e"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:cc67f4e8095ad4af9bdd81f76db9cdc4e07533aeb91037dc3548d1384200de0f"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fa9d2ba750180e3d7c23ee0151c52f1900e601be54ab516283ada368b1bb1672"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:fbd10fbb7643a8334e0f6ca1095a877e2f1fb240bbd0ee23f8196592e0c092d3"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8738cabb41216d467ac92d747380c6c943d74dd4d7d1bf8a3106787ecccbd36f"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:64de46e14c30a6eb9c6a458c62048b1711c46e45ff0468f14118c4d24d2fa750"}, + {file = "ruff-0.0.235-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9b475d800a6f356a7e7afae89a8ce1297e06f365eaa23b9eb80e6cb16a0915f"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:ed8771ab7bbaa9b350eb64a3d6d6628e800800cb15c5c3cc6e3e3217ff67703d"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:e60c855babdc3d8df77ac044fb3f893c2084efebc606726ecb078edc9d3c5702"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9efb9b87b92deeaeb707581a884e1764343165df0d37c3bdc4dc297edd837dce"}, + {file = "ruff-0.0.235-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:856ec6bfda0912f8010e15ffe04c33f2793971452379dfc8bd1f30b849483ede"}, + {file = "ruff-0.0.235-py3-none-win32.whl", hash = "sha256:82cf33ce2a998d1762517cc2e4ec0f79bbd985d005b312f31674411100c41899"}, + {file = "ruff-0.0.235-py3-none-win_amd64.whl", hash = "sha256:4a8b0284d52ea7b486894899cf5ba705c7b03a9d5fa780d55ac99ab64d3967ad"}, + {file = "ruff-0.0.235.tar.gz", hash = "sha256:270c0c83c01d00370851813edfd1502f2146a0a0b4e75b723e0c388252840f5a"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "sqlalchemy" +version = "2.0.31" +description = "Database Abstraction Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f2a213c1b699d3f5768a7272de720387ae0122f1becf0901ed6eaa1abd1baf6c"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9fea3d0884e82d1e33226935dac990b967bef21315cbcc894605db3441347443"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3ad7f221d8a69d32d197e5968d798217a4feebe30144986af71ada8c548e9fa"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f2bee229715b6366f86a95d497c347c22ddffa2c7c96143b59a2aa5cc9eebbc"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:cd5b94d4819c0c89280b7c6109c7b788a576084bf0a480ae17c227b0bc41e109"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:750900a471d39a7eeba57580b11983030517a1f512c2cb287d5ad0fcf3aebd58"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win32.whl", hash = "sha256:7bd112be780928c7f493c1a192cd8c5fc2a2a7b52b790bc5a84203fb4381c6be"}, + {file = "SQLAlchemy-2.0.31-cp310-cp310-win_amd64.whl", hash = "sha256:5a48ac4d359f058474fadc2115f78a5cdac9988d4f99eae44917f36aa1476327"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f68470edd70c3ac3b6cd5c2a22a8daf18415203ca1b036aaeb9b0fb6f54e8298"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e2c38c2a4c5c634fe6c3c58a789712719fa1bf9b9d6ff5ebfce9a9e5b89c1ca"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd15026f77420eb2b324dcb93551ad9c5f22fab2c150c286ef1dc1160f110203"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2196208432deebdfe3b22185d46b08f00ac9d7b01284e168c212919891289396"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:352b2770097f41bff6029b280c0e03b217c2dcaddc40726f8f53ed58d8a85da4"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:56d51ae825d20d604583f82c9527d285e9e6d14f9a5516463d9705dab20c3740"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win32.whl", hash = "sha256:6e2622844551945db81c26a02f27d94145b561f9d4b0c39ce7bfd2fda5776dac"}, + {file = "SQLAlchemy-2.0.31-cp311-cp311-win_amd64.whl", hash = "sha256:ccaf1b0c90435b6e430f5dd30a5aede4764942a695552eb3a4ab74ed63c5b8d3"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3b74570d99126992d4b0f91fb87c586a574a5872651185de8297c6f90055ae42"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6f77c4f042ad493cb8595e2f503c7a4fe44cd7bd59c7582fd6d78d7e7b8ec52c"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cd1591329333daf94467e699e11015d9c944f44c94d2091f4ac493ced0119449"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74afabeeff415e35525bf7a4ecdab015f00e06456166a2eba7590e49f8db940e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:b9c01990d9015df2c6f818aa8f4297d42ee71c9502026bb074e713d496e26b67"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66f63278db425838b3c2b1c596654b31939427016ba030e951b292e32b99553e"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win32.whl", hash = "sha256:0b0f658414ee4e4b8cbcd4a9bb0fd743c5eeb81fc858ca517217a8013d282c96"}, + {file = "SQLAlchemy-2.0.31-cp312-cp312-win_amd64.whl", hash = "sha256:fa4b1af3e619b5b0b435e333f3967612db06351217c58bfb50cee5f003db2a5a"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:f43e93057cf52a227eda401251c72b6fbe4756f35fa6bfebb5d73b86881e59b0"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d337bf94052856d1b330d5fcad44582a30c532a2463776e1651bd3294ee7e58b"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c06fb43a51ccdff3b4006aafee9fcf15f63f23c580675f7734245ceb6b6a9e05"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:b6e22630e89f0e8c12332b2b4c282cb01cf4da0d26795b7eae16702a608e7ca1"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:79a40771363c5e9f3a77f0e28b3302801db08040928146e6808b5b7a40749c88"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win32.whl", hash = "sha256:501ff052229cb79dd4c49c402f6cb03b5a40ae4771efc8bb2bfac9f6c3d3508f"}, + {file = "SQLAlchemy-2.0.31-cp37-cp37m-win_amd64.whl", hash = "sha256:597fec37c382a5442ffd471f66ce12d07d91b281fd474289356b1a0041bdf31d"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:dc6d69f8829712a4fd799d2ac8d79bdeff651c2301b081fd5d3fe697bd5b4ab9"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23b9fbb2f5dd9e630db70fbe47d963c7779e9c81830869bd7d137c2dc1ad05fb"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a21c97efcbb9f255d5c12a96ae14da873233597dfd00a3a0c4ce5b3e5e79704"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26a6a9837589c42b16693cf7bf836f5d42218f44d198f9343dd71d3164ceeeac"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc251477eae03c20fae8db9c1c23ea2ebc47331bcd73927cdcaecd02af98d3c3"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2fd17e3bb8058359fa61248c52c7b09a97cf3c820e54207a50af529876451808"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win32.whl", hash = "sha256:c76c81c52e1e08f12f4b6a07af2b96b9b15ea67ccdd40ae17019f1c373faa227"}, + {file = "SQLAlchemy-2.0.31-cp38-cp38-win_amd64.whl", hash = "sha256:4b600e9a212ed59355813becbcf282cfda5c93678e15c25a0ef896b354423238"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b6cf796d9fcc9b37011d3f9936189b3c8074a02a4ed0c0fbbc126772c31a6d4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:78fe11dbe37d92667c2c6e74379f75746dc947ee505555a0197cfba9a6d4f1a4"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2fc47dc6185a83c8100b37acda27658fe4dbd33b7d5e7324111f6521008ab4fe"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a41514c1a779e2aa9a19f67aaadeb5cbddf0b2b508843fcd7bafdf4c6864005"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:afb6dde6c11ea4525318e279cd93c8734b795ac8bb5dda0eedd9ebaca7fa23f1"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3f9faef422cfbb8fd53716cd14ba95e2ef655400235c3dfad1b5f467ba179c8c"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win32.whl", hash = "sha256:fc6b14e8602f59c6ba893980bea96571dd0ed83d8ebb9c4479d9ed5425d562e9"}, + {file = "SQLAlchemy-2.0.31-cp39-cp39-win_amd64.whl", hash = "sha256:3cb8a66b167b033ec72c3812ffc8441d4e9f5f78f5e31e54dcd4c90a4ca5bebc"}, + {file = "SQLAlchemy-2.0.31-py3-none-any.whl", hash = "sha256:69f3e3c08867a8e4856e92d7afb618b95cdee18e0bc1647b77599722c9a28911"}, + {file = "SQLAlchemy-2.0.31.tar.gz", hash = "sha256:b607489dd4a54de56984a0c7656247504bd5523d9d0ba799aef59d4add009484"}, +] + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version < \"3.13\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} +typing-extensions = ">=4.6.0" + +[package.extras] +aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"] +aioodbc = ["aioodbc", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4,!=0.2.6)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2,!=1.1.5)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)"] +mysql = ["mysqlclient (>=1.4.0)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=8)"] +oracle-oracledb = ["oracledb (>=1.0.1)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.29.1)"] +postgresql-psycopg = ["psycopg (>=3.0.7)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +postgresql-psycopgbinary = ["psycopg[binary] (>=3.0.7)"] +pymysql = ["pymysql"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "tabulate" +version = "0.9.0" +description = "Pretty-print tabular data" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, + {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, +] + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tomli" +version = "1.2.3" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "tomli-1.2.3-py3-none-any.whl", hash = "sha256:e3069e4be3ead9668e21cb9b074cd948f7b3113fd9c8bba083f48247aab8b11c"}, + {file = "tomli-1.2.3.tar.gz", hash = "sha256:05b6166bff487dc068d322585c7ea4ef78deed501cc124060e0f238e89a9231f"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "toolz" +version = "0.12.1" +description = "List processing tools and functional utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "toolz-0.12.1-py3-none-any.whl", hash = "sha256:d22731364c07d72eea0a0ad45bafb2c2937ab6fd38a3507bf55eae8744aa7d85"}, + {file = "toolz-0.12.1.tar.gz", hash = "sha256:ecca342664893f177a13dac0e6b41cbd8ac25a358e5f215316d43e2100224f4d"}, +] + +[[package]] +name = "tqdm" +version = "4.66.5" +description = "Fast, Extensible Progress Meter" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tqdm-4.66.5-py3-none-any.whl", hash = "sha256:90279a3770753eafc9194a0364852159802111925aa30eb3f9d85b0e805ac7cd"}, + {file = "tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "tzdata" +version = "2024.1" +description = "Provider of IANA time zone data" +optional = false +python-versions = ">=2" +files = [ + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "web3" +version = "6.20.3" +description = "web3.py" +optional = false +python-versions = ">=3.7.2" +files = [ + {file = "web3-6.20.3-py3-none-any.whl", hash = "sha256:529fbb33f2476ce8185f7a2ed7e2e07c4c28621b0e89b845fbfdcaea9571286d"}, + {file = "web3-6.20.3.tar.gz", hash = "sha256:c69dbf1a61ace172741d06990e60afc7f55f303eac087e7235f382df3047d017"}, +] + +[package.dependencies] +aiohttp = ">=3.7.4.post0" +ckzg = "<2" +eth-abi = ">=4.0.0" +eth-account = ">=0.8.0,<0.13" +eth-hash = {version = ">=0.5.1", extras = ["pycryptodome"]} +eth-typing = ">=3.0.0,<4.2.0 || >4.2.0,<5.0.0" +eth-utils = ">=2.1.0,<5" +hexbytes = ">=0.1.0,<0.4.0" +jsonschema = ">=4.0.0" +lru-dict = ">=1.1.6,<1.3.0" +protobuf = ">=4.21.6" +pyunormalize = ">=15.0.0" +pywin32 = {version = ">=223", markers = "platform_system == \"Windows\""} +requests = ">=2.16.0" +typing-extensions = ">=4.0.1" +websockets = ">=10.0.0" + +[package.extras] +dev = ["build (>=0.9.0)", "bumpversion", "eth-tester[py-evm] (>=0.11.0b1,<0.12.0b1)", "eth-tester[py-evm] (>=0.9.0b1,<0.10.0b1)", "flaky (>=3.7.0)", "hypothesis (>=3.31.2)", "importlib-metadata (<5.0)", "ipfshttpclient (==0.8.0a2)", "pre-commit (>=2.21.0)", "py-geth (>=3.14.0,<4)", "pytest (>=7.0.0)", "pytest-asyncio (>=0.21.2,<0.23)", "pytest-mock (>=1.10)", "pytest-watch (>=4.2)", "pytest-xdist (>=1.29)", "setuptools (>=38.6.0)", "sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=3.18.0)", "tqdm (>4.32)", "twine (>=1.13)", "when-changed (>=0.3.0)"] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] +ipfs = ["ipfshttpclient (==0.8.0a2)"] +tester = ["eth-tester[py-evm] (>=0.11.0b1,<0.12.0b1)", "eth-tester[py-evm] (>=0.9.0b1,<0.10.0b1)", "py-geth (>=3.14.0,<4)"] + +[[package]] +name = "websockets" +version = "13.0.1" +description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, + {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, + {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, + {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, + {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, + {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, + {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, + {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, + {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, + {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, + {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, + {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, + {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, + {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, + {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, + {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, + {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, + {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, + {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, + {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, + {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, + {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, + {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, + {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, + {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, + {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, + {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, + {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, + {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, + {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, + {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, + {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, + {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, + {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, + {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, + {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, +] + +[[package]] +name = "werkzeug" +version = "3.0.3" +description = "The comprehensive WSGI web application library." +optional = false +python-versions = ">=3.8" +files = [ + {file = "werkzeug-3.0.3-py3-none-any.whl", hash = "sha256:fc9645dc43e03e4d630d23143a04a7f947a9a3b5727cd535fdfe155a17cc48c8"}, + {file = "werkzeug-3.0.3.tar.gz", hash = "sha256:097e5bfda9f0aba8da6b8545146def481d06aa7d3266e7448e2cccf67dd8bd18"}, +] + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog (>=2.3)"] + +[[package]] +name = "wrapt" +version = "1.16.0" +description = "Module for decorators, wrappers and monkey patching." +optional = false +python-versions = ">=3.6" +files = [ + {file = "wrapt-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ffa565331890b90056c01db69c0fe634a776f8019c143a5ae265f9c6bc4bd6d4"}, + {file = "wrapt-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e4fdb9275308292e880dcbeb12546df7f3e0f96c6b41197e0cf37d2826359020"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bb2dee3874a500de01c93d5c71415fcaef1d858370d405824783e7a8ef5db440"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a88e6010048489cda82b1326889ec075a8c856c2e6a256072b28eaee3ccf487"}, + {file = "wrapt-1.16.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac83a914ebaf589b69f7d0a1277602ff494e21f4c2f743313414378f8f50a4cf"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:73aa7d98215d39b8455f103de64391cb79dfcad601701a3aa0dddacf74911d72"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:807cc8543a477ab7422f1120a217054f958a66ef7314f76dd9e77d3f02cdccd0"}, + {file = "wrapt-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bf5703fdeb350e36885f2875d853ce13172ae281c56e509f4e6eca049bdfb136"}, + {file = "wrapt-1.16.0-cp310-cp310-win32.whl", hash = "sha256:f6b2d0c6703c988d334f297aa5df18c45e97b0af3679bb75059e0e0bd8b1069d"}, + {file = "wrapt-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:decbfa2f618fa8ed81c95ee18a387ff973143c656ef800c9f24fb7e9c16054e2"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1a5db485fe2de4403f13fafdc231b0dbae5eca4359232d2efc79025527375b09"}, + {file = "wrapt-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:75ea7d0ee2a15733684badb16de6794894ed9c55aa5e9903260922f0482e687d"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a452f9ca3e3267cd4d0fcf2edd0d035b1934ac2bd7e0e57ac91ad6b95c0c6389"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:43aa59eadec7890d9958748db829df269f0368521ba6dc68cc172d5d03ed8060"}, + {file = "wrapt-1.16.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72554a23c78a8e7aa02abbd699d129eead8b147a23c56e08d08dfc29cfdddca1"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d2efee35b4b0a347e0d99d28e884dfd82797852d62fcd7ebdeee26f3ceb72cf3"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6dcfcffe73710be01d90cae08c3e548d90932d37b39ef83969ae135d36ef3956"}, + {file = "wrapt-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:eb6e651000a19c96f452c85132811d25e9264d836951022d6e81df2fff38337d"}, + {file = "wrapt-1.16.0-cp311-cp311-win32.whl", hash = "sha256:66027d667efe95cc4fa945af59f92c5a02c6f5bb6012bff9e60542c74c75c362"}, + {file = "wrapt-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:aefbc4cb0a54f91af643660a0a150ce2c090d3652cf4052a5397fb2de549cd89"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:5eb404d89131ec9b4f748fa5cfb5346802e5ee8836f57d516576e61f304f3b7b"}, + {file = "wrapt-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:9090c9e676d5236a6948330e83cb89969f433b1943a558968f659ead07cb3b36"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:94265b00870aa407bd0cbcfd536f17ecde43b94fb8d228560a1e9d3041462d73"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2058f813d4f2b5e3a9eb2eb3faf8f1d99b81c3e51aeda4b168406443e8ba809"}, + {file = "wrapt-1.16.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:98b5e1f498a8ca1858a1cdbffb023bfd954da4e3fa2c0cb5853d40014557248b"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:14d7dc606219cdd7405133c713f2c218d4252f2a469003f8c46bb92d5d095d81"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:49aac49dc4782cb04f58986e81ea0b4768e4ff197b57324dcbd7699c5dfb40b9"}, + {file = "wrapt-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:418abb18146475c310d7a6dc71143d6f7adec5b004ac9ce08dc7a34e2babdc5c"}, + {file = "wrapt-1.16.0-cp312-cp312-win32.whl", hash = "sha256:685f568fa5e627e93f3b52fda002c7ed2fa1800b50ce51f6ed1d572d8ab3e7fc"}, + {file = "wrapt-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:dcdba5c86e368442528f7060039eda390cc4091bfd1dca41e8046af7c910dda8"}, + {file = "wrapt-1.16.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d462f28826f4657968ae51d2181a074dfe03c200d6131690b7d65d55b0f360f8"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a33a747400b94b6d6b8a165e4480264a64a78c8a4c734b62136062e9a248dd39"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3646eefa23daeba62643a58aac816945cadc0afaf21800a1421eeba5f6cfb9c"}, + {file = "wrapt-1.16.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ebf019be5c09d400cf7b024aa52b1f3aeebeff51550d007e92c3c1c4afc2a40"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:0d2691979e93d06a95a26257adb7bfd0c93818e89b1406f5a28f36e0d8c1e1fc"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:1acd723ee2a8826f3d53910255643e33673e1d11db84ce5880675954183ec47e"}, + {file = "wrapt-1.16.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:bc57efac2da352a51cc4658878a68d2b1b67dbe9d33c36cb826ca449d80a8465"}, + {file = "wrapt-1.16.0-cp36-cp36m-win32.whl", hash = "sha256:da4813f751142436b075ed7aa012a8778aa43a99f7b36afe9b742d3ed8bdc95e"}, + {file = "wrapt-1.16.0-cp36-cp36m-win_amd64.whl", hash = "sha256:6f6eac2360f2d543cc875a0e5efd413b6cbd483cb3ad7ebf888884a6e0d2e966"}, + {file = "wrapt-1.16.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a0ea261ce52b5952bf669684a251a66df239ec6d441ccb59ec7afa882265d593"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bd2d7ff69a2cac767fbf7a2b206add2e9a210e57947dd7ce03e25d03d2de292"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9159485323798c8dc530a224bd3ffcf76659319ccc7bbd52e01e73bd0241a0c5"}, + {file = "wrapt-1.16.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a86373cf37cd7764f2201b76496aba58a52e76dedfaa698ef9e9688bfd9e41cf"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:73870c364c11f03ed072dda68ff7aea6d2a3a5c3fe250d917a429c7432e15228"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b935ae30c6e7400022b50f8d359c03ed233d45b725cfdd299462f41ee5ffba6f"}, + {file = "wrapt-1.16.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:db98ad84a55eb09b3c32a96c576476777e87c520a34e2519d3e59c44710c002c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win32.whl", hash = "sha256:9153ed35fc5e4fa3b2fe97bddaa7cbec0ed22412b85bcdaf54aeba92ea37428c"}, + {file = "wrapt-1.16.0-cp37-cp37m-win_amd64.whl", hash = "sha256:66dfbaa7cfa3eb707bbfcd46dab2bc6207b005cbc9caa2199bcbc81d95071a00"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1dd50a2696ff89f57bd8847647a1c363b687d3d796dc30d4dd4a9d1689a706f0"}, + {file = "wrapt-1.16.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:44a2754372e32ab315734c6c73b24351d06e77ffff6ae27d2ecf14cf3d229202"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e9723528b9f787dc59168369e42ae1c3b0d3fadb2f1a71de14531d321ee05b0"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbed418ba5c3dce92619656802cc5355cb679e58d0d89b50f116e4a9d5a9603e"}, + {file = "wrapt-1.16.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:941988b89b4fd6b41c3f0bfb20e92bd23746579736b7343283297c4c8cbae68f"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6a42cd0cfa8ffc1915aef79cb4284f6383d8a3e9dcca70c445dcfdd639d51267"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:1ca9b6085e4f866bd584fb135a041bfc32cab916e69f714a7d1d397f8c4891ca"}, + {file = "wrapt-1.16.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5e49454f19ef621089e204f862388d29e6e8d8b162efce05208913dde5b9ad6"}, + {file = "wrapt-1.16.0-cp38-cp38-win32.whl", hash = "sha256:c31f72b1b6624c9d863fc095da460802f43a7c6868c5dda140f51da24fd47d7b"}, + {file = "wrapt-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:490b0ee15c1a55be9c1bd8609b8cecd60e325f0575fc98f50058eae366e01f41"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9b201ae332c3637a42f02d1045e1d0cccfdc41f1f2f801dafbaa7e9b4797bfc2"}, + {file = "wrapt-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2076fad65c6736184e77d7d4729b63a6d1ae0b70da4868adeec40989858eb3fb"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5cd603b575ebceca7da5a3a251e69561bec509e0b46e4993e1cac402b7247b8"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47cfad9e9bbbed2339081f4e346c93ecd7ab504299403320bf85f7f85c7d46c"}, + {file = "wrapt-1.16.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8212564d49c50eb4565e502814f694e240c55551a5f1bc841d4fcaabb0a9b8a"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5f15814a33e42b04e3de432e573aa557f9f0f56458745c2074952f564c50e664"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db2e408d983b0e61e238cf579c09ef7020560441906ca990fe8412153e3b291f"}, + {file = "wrapt-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:edfad1d29c73f9b863ebe7082ae9321374ccb10879eeabc84ba3b69f2579d537"}, + {file = "wrapt-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed867c42c268f876097248e05b6117a65bcd1e63b779e916fe2e33cd6fd0d3c3"}, + {file = "wrapt-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:eb1b046be06b0fce7249f1d025cd359b4b80fc1c3e24ad9eca33e0dcdb2e4a35"}, + {file = "wrapt-1.16.0-py3-none-any.whl", hash = "sha256:6906c4100a8fcbf2fa735f6059214bb13b97f75b1a61777fcf6432121ef12ef1"}, + {file = "wrapt-1.16.0.tar.gz", hash = "sha256:5f370f952971e7d17c7d1ead40e49f32345a7f7a5373571ef44d800d06b1899d"}, +] + +[[package]] +name = "yarl" +version = "1.11.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, + {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, + {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, + {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, + {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, + {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, + {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, + {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, + {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, + {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, + {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, + {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, + {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, + {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, + {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, + {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, + {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, + {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, + {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, + {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, + {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, + {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, + {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, + {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, + {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, + {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, + {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, + {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, + {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, + {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, + {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, + {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, + {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8,<4" +content-hash = "c6d2b4d641ecbe9fac79477c371e11635b10c70e6f6d74ea6ad271de6e04491a" diff --git a/pyproject.toml b/pyproject.toml index f0c75a537..c531e1312 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,17 @@ -[project] +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry_dynamic_versioning.backend" + +[tool.poetry] name = "hemera" -dynamic = ["version"] +description = "Tools for exporting Ethereum blockchain data to JSON/CSV file and postgresql" +version = "0.3.0" authors = [ - {name = "xuzh", email = "zihao.xu@thehemera.com"}, - {name = "shanshuo0918", email = "shuo.shan@lifo.ai"} + "xuzh ", + "shanshuo0918 " ] -description = "Tools for exporting Ethereum blockchain data to JSON/CSV file and postgresql" readme = "README.md" -requires-python = ">=3.8,<4" -license = {text = "MIT"} +license = "MIT" classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", @@ -20,74 +23,61 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] keywords = ["ethereum", "indexer", "explorer", "hemera"] - -dependencies = [ - "web3>=6.8.0, <7", - "eth-utils>=4.0.0", - "eth-abi>=5.0.1", - "python-dateutil>=2.8.0,<3", - "click>=8.0.4,<9", - "ethereum-dasm==0.1.5", - "requests", - "sqlalchemy==2.0.31", - "psycopg2-binary==2.9.9", - "alembic", - "pandas", - "Flask==3.0.3", - "Flask-Caching==2.3.0", - "Flask-Cors==3.0.9", - "flask-limiter==3.8.0", - "flask-restx==1.3.0", - "Flask-SQLAlchemy==3.1.1", - "blinker>=1.8.2", - "Werkzeug==3.0.3", - "openpyxl==3.1.3", - "redis>=4,<6", - "urllib3==2.2.2", - "dataclass-wizard==0.22.3", - "pottery==3.0.0", - "eth_typing>=2.2.0,<5", - "orjson==3.10.7", - "mpire==2.10.2", - "PyYAML==6.0.2", +packages = [ + { include = "cli" }, + { include = "common" }, + { include = "api" }, + { include = "indexer" }, + { include = "enumeration" }, ] -[tool.setuptools.dynamic] -version = {file = "VERSION"} - -[project.optional-dependencies] -streaming = ["grpcio==1.46.3"] -dev = ["pytest>=7.0.0"] - -[project.urls] -"Homepage" = "https://github.com/HemeraProtocol/hemera-indexer" -"Bug Reports" = "https://github.com/HemeraProtocol/hemera-indexer/issues" -"Source" = "https://github.com/HemeraProtocol/hemera-indexer" - -[project.scripts] -hemera = "hemera.cli:cli" - -[tool.setuptools] -packages = ["cli", "common", "api", "indexer"] - -[tool.setuptools.package-data] -hemera = ["*.json", "*.yaml"] - -[build-system] -requires = ["setuptools>=61.0", "wheel"] -build-backend = "setuptools.build_meta" - [tool.poetry.dependencies] -python = "^3.9" -requests = "^2.25.1" +python = ">=3.8,<4" +web3 = ">=6.8.0,<7" +eth-utils = ">=4.0.0" +eth-abi = ">=5.0.1" +python-dateutil = ">=2.8.0,<3" +click = ">=8.0.4,<9" +ethereum-dasm = "0.1.5" +requests = "*" +sqlalchemy = "2.0.31" +psycopg2-binary = "2.9.9" +alembic = "*" +pandas = "*" +Flask = "3.0.3" +Flask-Caching = "2.3.0" +Flask-Cors = "3.0.9" +flask-limiter = "3.8.0" +flask-restx = "1.3.0" +Flask-SQLAlchemy = "3.1.1" +blinker = ">=1.8.2" +Werkzeug = "3.0.3" +openpyxl = "3.1.3" +redis = ">=4,<6" +urllib3 = ">=2.2.2" +dataclass-wizard = "0.22.3" +pottery = "3.0.0" +eth_typing = ">=2.2.0,<5" +orjson = "3.10.7" +mpire = "2.10.2" +PyYAML = "6.0.2" +poetry_dynamic_versioning = ">=1.2.0" -[tool.poetry.dev-dependencies] -pytest = "^7.0.0" +[tool.poetry.group.dev.dependencies] +pytest = ">=7.0.0" black = "^21.5b0" isort = "^5.9.1" flake8 = "^3.9.2" ruff = "^0.0.235" +[tool.poetry.scripts] +hemera = "cli:cli" + +[tool.poetry.urls] +"Homepage" = "https://github.com/HemeraProtocol/hemera-indexer" +"Bug Reports" = "https://github.com/HemeraProtocol/hemera-indexer/issues" +"Source" = "https://github.com/HemeraProtocol/hemera-indexer" + [tool.black] line-length = 120 target-version = ["py38", "py39", "py310", "py311", "py312"] @@ -111,7 +101,5 @@ markers = [ "indexer_jobs: Tests related to the indexer jobs", "indexer_jobs_user_ops: Tests related to the indexer jobs user ops", "indexer_address_index: Tests related to the indexer address index", - - # API related "explorer_api: Test explorer related API", ] \ No newline at end of file From a2f1aaa48f27de962121066663169730f5c6bc95 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:40:27 +0800 Subject: [PATCH 66/70] add merchant moe pool data (#157) * add merchant moe pool data --- .../modules/custom/merchant_moe/constants.py | 16 ++ .../merchant_moe/domain/merchant_moe.py | 18 +++ ...chant_moe_1155_token_holding_detail_job.py | 143 +++++++++++++++++- ...feature_merchant_moe_pool_record_status.py | 32 ++++ .../feature_merchant_moe_pool_records.py | 32 ++++ 5 files changed, 233 insertions(+), 8 deletions(-) create mode 100644 indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_record_status.py create mode 100644 indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_records.py diff --git a/indexer/modules/custom/merchant_moe/constants.py b/indexer/modules/custom/merchant_moe/constants.py index 60de165e9..b27deb48e 100644 --- a/indexer/modules/custom/merchant_moe/constants.py +++ b/indexer/modules/custom/merchant_moe/constants.py @@ -30,6 +30,20 @@ "stateMutability": "view", "type": "function", }, + { + "inputs": [], + "name": "getActiveId", + "outputs": [{"internalType": "uint24", "name": "activeId", "type": "uint24"}], + "stateMutability": "view", + "type": "function", + }, + { + "inputs": [], + "name": "getBinStep", + "outputs": [{"internalType": "uint24", "name": "binStep", "type": "uint16"}], + "stateMutability": "view", + "type": "function", + }, ] LIQUIDITY_LIST = [ @@ -39,4 +53,6 @@ "0xa32e146844d6144a22e94c586715a1317d58a8aa3581ec33d040113ddcb24350", # TransferBatch "0x4a39dc06d4c0dbc64b70af90fd698a233a518aa5d07e595d983b8c0526c8f7fb", + # Swap + "0xad7d6f97abf51ce18e17a38f4d70e975be9c0708474987bb3e26ad21bd93ca70", ] diff --git a/indexer/modules/custom/merchant_moe/domain/merchant_moe.py b/indexer/modules/custom/merchant_moe/domain/merchant_moe.py index 5c30172a2..6297b4f4c 100644 --- a/indexer/modules/custom/merchant_moe/domain/merchant_moe.py +++ b/indexer/modules/custom/merchant_moe/domain/merchant_moe.py @@ -48,3 +48,21 @@ class MerchantMoeErc1155TokenCurrentSupply(FilterData): total_supply: int block_number: int block_timestamp: int + + +@dataclass +class MerChantMoePoolRecord(FilterData): + pool_address: str + active_id: int + bin_step: int + block_number: int + block_timestamp: int + + +@dataclass +class MerChantMoePoolCurrentStatu(FilterData): + pool_address: str + active_id: int + bin_step: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py index d675d8fda..825a4a2bb 100644 --- a/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py +++ b/indexer/modules/custom/merchant_moe/export_merchant_moe_1155_token_holding_detail_job.py @@ -6,6 +6,7 @@ import eth_abi +from indexer.domain.log import Log from indexer.domain.token_balance import TokenBalance from indexer.executors.batch_work_executor import BatchWorkExecutor from indexer.jobs import FilterTransactionDataJob @@ -16,6 +17,8 @@ MerchantMoeErc1155TokenCurrentSupply, MerchantMoeErc1155TokenSupply, MerChantMoePool, + MerChantMoePoolCurrentStatu, + MerChantMoePoolRecord, MerChantMoeTokenBin, MerChantMoeTokenCurrentBin, ) @@ -29,13 +32,15 @@ class ExportMerchantMoe1155LiquidityJob(FilterTransactionDataJob): - dependency_types = [TokenBalance] + dependency_types = [TokenBalance, Log] output_types = [ MerchantMoeErc1155TokenSupply, MerchantMoeErc1155TokenCurrentSupply, MerChantMoeTokenBin, MerChantMoeTokenCurrentBin, MerChantMoePool, + MerChantMoePoolCurrentStatu, + MerChantMoePoolRecord, ] able_to_reorg = True @@ -63,15 +68,23 @@ def _collect(self, **kwargs): if self._need_collected_list is None or len(self._need_collected_list) == 0: return token_balances = self._data_buff[TokenBalance.type()] - if token_balances is None or len(token_balances) == 0: - return - self._batch_work_executor.execute( - token_balances, self._collect_batch, total_items=len(token_balances), split_method=split_token_balances - ) + if token_balances is not None and len(token_balances) > 0: + self._batch_work_executor.execute( + token_balances, + self._collect_token_batch, + total_items=len(token_balances), + split_method=split_token_balances, + ) - self._batch_work_executor.wait() + self._batch_work_executor.wait() + logs = self._data_buff[Log.type()] + if logs is not None and len(logs) > 0: + self._batch_work_executor.execute( + logs, self._collect_pool_batch, total_items=len(logs), split_method=split_logs + ) + self._batch_work_executor.wait() - def _collect_batch(self, token_balances) -> None: + def _collect_token_batch(self, token_balances) -> None: if token_balances is None or len(token_balances) == 0: return token_address = next(iter(token_balances)) @@ -208,12 +221,70 @@ def _collect_batch(self, token_balances) -> None: for data in current_token_bin_dict.values(): self._collect_item(MerChantMoeTokenCurrentBin.type(), data) + def _collect_pool_batch(self, log_dict) -> None: + if log_dict is None or len(log_dict) == 0: + return + token_address = next(iter(log_dict)) + logs = log_dict[token_address] + if logs is None or len(logs) == 0: + return + blocks_set = set() + for log in logs: + token_address = log.address + if token_address not in self._exist_pool: + continue + block_number = log.block_number + block_timestamp = log.block_timestamp + blocks_set.add((token_address, block_number, block_timestamp)) + + pool_array = [] + for token_address, block_number, block_timestamp in blocks_set: + pool_array.append( + { + "block_number": block_number, + "block_timestamp": block_timestamp, + "token_address": token_address, + } + ) + if pool_array is None or len(pool_array) == 0: + return + bin_step_array = batch_get_pool_bin_step( + self._web3, self._batch_web3_provider.make_request, pool_array, self._is_batch, constants.ABI_LIST + ) + + active_id_array = batch_get_pool_active_id( + self._web3, self._batch_web3_provider.make_request, bin_step_array, self._is_batch, constants.ABI_LIST + ) + current_pool_data = None + for data in active_id_array: + entity = MerChantMoePoolRecord( + pool_address=data["token_address"], + active_id=data["getActiveId"], + bin_step=data["getBinStep"], + block_number=data["block_number"], + block_timestamp=data["block_timestamp"], + ) + if current_pool_data is None or current_pool_data.block_number < entity.block_number: + current_pool_data = MerChantMoePoolCurrentStatu( + pool_address=entity.pool_address, + active_id=entity.active_id, + bin_step=entity.bin_step, + block_number=entity.block_number, + block_timestamp=entity.block_timestamp, + ) + self._collect_item(MerChantMoePoolRecord.type(), entity) + if current_pool_data: + self._collect_item(MerChantMoePoolCurrentStatu.type(), current_pool_data) + def _process(self, **kwargs): self._data_buff[MerchantMoeErc1155TokenSupply.type()].sort(key=lambda x: x.block_number) self._data_buff[MerChantMoeTokenBin.type()].sort(key=lambda x: x.block_number) self._data_buff[MerchantMoeErc1155TokenCurrentSupply.type()].sort(key=lambda x: x.block_number) self._data_buff[MerChantMoeTokenCurrentBin.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerChantMoePoolCurrentStatu.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerChantMoePoolRecord.type()].sort(key=lambda x: x.block_number) + self._data_buff[MerChantMoePool.type()].sort(key=lambda x: x.block_number) def split_token_balances(token_balances): @@ -225,6 +296,15 @@ def split_token_balances(token_balances): yield {token_address: data} +def split_logs(logs): + log_dict = defaultdict(list) + for data in logs: + log_dict[data.address].append(data) + + for address, data in log_dict.items(): + yield {address: data} + + def batch_get_bin(web3, make_requests, requests, nft_address, is_batch, abi_list): fn_name = "getBin" if len(requests) == 0: @@ -306,6 +386,53 @@ def batch_get_total_supply(web3, make_requests, requests, nft_address, is_batch, return token_infos +def batch_get_pool_bin_step(web3, make_requests, requests, is_batch, abi_list): + return batch_get_pool_int(web3, make_requests, requests, is_batch, abi_list, "getBinStep") + + +def batch_get_pool_active_id(web3, make_requests, requests, is_batch, abi_list): + return batch_get_pool_int(web3, make_requests, requests, is_batch, abi_list, "getActiveId") + + +def batch_get_pool_int(web3, make_requests, requests, is_batch, abi_list, fn_name): + if len(requests) == 0: + return [] + function_abi = next( + (abi for abi in abi_list if abi["name"] == fn_name and abi["type"] == "function"), + None, + ) + outputs = function_abi["outputs"] + output_types = [output["type"] for output in outputs] + + parameters = common_utils.build_no_input_method_data(web3, requests, fn_name, abi_list, "token_address") + + token_name_rpc = list(generate_eth_call_json_rpc(parameters)) + if is_batch: + response = make_requests(params=json.dumps(token_name_rpc)) + else: + response = [make_requests(params=json.dumps(token_name_rpc[0]))] + + token_infos = [] + for data in list(zip_rpc_response(parameters, response)): + result = rpc_response_to_result(data[1]) + token = data[0] + value = result[2:] if result is not None else None + try: + decoded_data = eth_abi.decode(output_types, bytes.fromhex(value)) + token[fn_name] = decoded_data[0] + + except Exception as e: + logger.error( + f"Decoding token info failed. " + f"token: {token}. " + f"fn: {fn_name}. " + f"rpc response: {result}. " + f"exception: {e}" + ) + token_infos.append(token) + return token_infos + + def get_exist_pools(db_service): if not db_service: return [] diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_record_status.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_record_status.py new file mode 100644 index 000000000..721002b65 --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_record_status.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureMerChantMoePoolRecordStatus(HemeraModel): + __tablename__ = "af_merchant_moe_pool_data_current" + pool_address = Column(BYTEA, primary_key=True) + block_timestamp = Column(BIGINT) + block_number = Column(BIGINT) + active_id = Column(BIGINT) + bin_step = Column(BIGINT) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("pool_address"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerChantMoePoolCurrentStatu", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_merchant_moe_pool_data_current.block_number", + "converter": general_converter, + } + ] diff --git a/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_records.py b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_records.py new file mode 100644 index 000000000..59f8eb05d --- /dev/null +++ b/indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_records.py @@ -0,0 +1,32 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP + +from common.models import HemeraModel, general_converter + + +class FeatureMerChantMoePoolRecords(HemeraModel): + __tablename__ = "af_merchant_moe_pool_data_hist" + pool_address = Column(BYTEA, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + block_number = Column(BIGINT, primary_key=True) + active_id = Column(BIGINT) + bin_step = Column(BIGINT) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "MerChantMoePoolRecord", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + } + ] From 95f1896dbc92e854c11f25cce04fc84fab6e85e9 Mon Sep 17 00:00:00 2001 From: Lifowyy <112999725+Lifowyy@users.noreply.github.com> Date: Fri, 27 Sep 2024 16:44:25 +0800 Subject: [PATCH 67/70] Feature/deposit or transfer sum (#136) * add fbtc staking feature --- api/app/api.py | 2 + .../modules/custom/staking_fbtc/__init__.py | 0 .../modules/custom/staking_fbtc/config.ini | 19 ++ .../custom/staking_fbtc/domain/__init__.py | 0 .../domain/feature_staked_fbtc_detail.py | 48 +++++ .../custom/staking_fbtc/endpoints/__init__.py | 5 + .../custom/staking_fbtc/endpoints/routes.py | 60 +++++++ .../export_staked_fbtc_detail_job.py | 167 ++++++++++++++++++ .../export_transferred_fbtc_detail_job.py | 166 +++++++++++++++++ .../custom/staking_fbtc/models/__init__.py | 0 .../feature_staked_fbtc_detail_records.py | 53 ++++++ .../feature_staked_fbtc_detail_status.py | 50 ++++++ indexer/modules/custom/staking_fbtc/utils.py | 76 ++++++++ 13 files changed, 646 insertions(+) create mode 100644 indexer/modules/custom/staking_fbtc/__init__.py create mode 100644 indexer/modules/custom/staking_fbtc/config.ini create mode 100644 indexer/modules/custom/staking_fbtc/domain/__init__.py create mode 100644 indexer/modules/custom/staking_fbtc/domain/feature_staked_fbtc_detail.py create mode 100644 indexer/modules/custom/staking_fbtc/endpoints/__init__.py create mode 100644 indexer/modules/custom/staking_fbtc/endpoints/routes.py create mode 100644 indexer/modules/custom/staking_fbtc/export_staked_fbtc_detail_job.py create mode 100644 indexer/modules/custom/staking_fbtc/export_transferred_fbtc_detail_job.py create mode 100644 indexer/modules/custom/staking_fbtc/models/__init__.py create mode 100644 indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_records.py create mode 100644 indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_status.py create mode 100644 indexer/modules/custom/staking_fbtc/utils.py diff --git a/api/app/api.py b/api/app/api.py index 678a0a361..a2321fbe9 100644 --- a/api/app/api.py +++ b/api/app/api.py @@ -11,6 +11,7 @@ from api.app.user_operation.routes import user_operation_namespace from indexer.modules.custom.merchant_moe.endpoints.routes import merchant_moe_namespace from indexer.modules.custom.opensea.endpoint.routes import opensea_namespace +from indexer.modules.custom.staking_fbtc.endpoints.routes import staking_namespace from indexer.modules.custom.uniswap_v3.endpoints.routes import uniswap_v3_namespace # from api.app.l2_explorer.routes import l2_explorer_namespace @@ -23,6 +24,7 @@ api.add_namespace(uniswap_v3_namespace) api.add_namespace(token_deposit_namespace) api.add_namespace(user_operation_namespace) +api.add_namespace(staking_namespace) api.add_namespace(merchant_moe_namespace) # api.add_namespace(l2_explorer_namespace) diff --git a/indexer/modules/custom/staking_fbtc/__init__.py b/indexer/modules/custom/staking_fbtc/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/staking_fbtc/config.ini b/indexer/modules/custom/staking_fbtc/config.ini new file mode 100644 index 000000000..034d6e30f --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/config.ini @@ -0,0 +1,19 @@ +[1] +STAKED_PROTOCOL_DICT = {"0x3d9bcca8bc7d438a4c5171435f41a0af5d5e6083": "pump_btc"} +STAKED_TOPIC0_DICT = {"0x3d9bcca8bc7d438a4c5171435f41a0af5d5e6083": "0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a"} +STAKED_ABI_DICT = {"0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a": {"anonymous": False, "inputs": [{"indexed": True, "internalType": "address", "name": "user", "type": "address"}, {"indexed": False, "internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "Stake", "type": "event"}} +TRANSFERRED_CONTRACTS_DICT = {"0x047d41f2544b7f63a8e991af2068a363d210d6da": "bed_rock","0x1ff7d7c0a7d8e94046708c611dec5056a9d2b823": "solv","0xab13b8eecf5aa2460841d75da5d5d861fd5b8a39":"mezo","0x19b5cc75846bf6286d599ec116536a333c4c2c14":"fuel","0x604dd02d620633ae427888d41bfd15e38483736e":"astherus","0xf047ab4c75cebf0eb9ed34ae2c186f3611aeafa6":"zircuit","0x40328669bc9e3780dfa0141dbc87450a4af6ea11":"karak"} +FBTC_ADDRESS = 0xc96de26018a54d51c097160568752c4e3bd6c364 +[5000] +STAKED_PROTOCOL_DICT = {"0xd6ab15b2458b6ec3e94ce210174d860fdbdd6b96": "pump_btc"} +STAKED_TOPIC0_DICT = {"0xd6ab15b2458b6ec3e94ce210174d860fdbdd6b96": "0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a"} +STAKED_ABI_DICT = {"0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a": {"anonymous": False, "inputs": [{"indexed": True, "internalType": "address", "name": "user", "type": "address"}, {"indexed": False, "internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "Stake", "type": "event"}} +TRANSFERRED_CONTRACTS_DICT = {"0xf9775085d726e782e83585033b58606f7731ab18": "bed_rock","0x900637b3258e6b86fe2e713fbca4510ad934ee7e": "solv","0x0a5e1fe85be84430c6eb482512046a04b25d2484":"pell","0x27272698e0962a4bdf33f70a53d6aea3fef217c4":"minterest","0x233493e9dc68e548ac27e4933a600a3a4682c0c3":"init_capital","0x7fa704e73262e5a9f48382087f69c6aba0408eaa":"init_capital"} +FBTC_ADDRESS = 0xc96de26018a54d51c097160568752c4e3bd6c364 +MINTEREST_LEND_AND_REDEEM_TOKEN = 0x27272698e0962a4bdf33f70a53d6aea3fef217c4 +[56] +STAKED_PROTOCOL_DICT = {"0x2b4b9047c9fea54705218388bfc7aa7bada4bb5e": "pump_btc"} +STAKED_TOPIC0_DICT = {"0x2b4b9047c9fea54705218388bfc7aa7bada4bb5e": "0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a"} +STAKED_ABI_DICT = {"0xebedb8b3c678666e7f36970bc8f57abf6d8fa2e828c0da91ea5b75bf68ed101a": {"anonymous": False, "inputs": [{"indexed": True, "internalType": "address", "name": "user", "type": "address"}, {"indexed": False, "internalType": "uint256", "name": "amount", "type": "uint256"}], "name": "Stake", "type": "event"}} +FBTC_ADDRESS = 0xc96de26018a54d51c097160568752c4e3bd6c364 +TRANSFERRED_CONTRACTS_DICT = {"0x128463a60784c4d3f46c23af3f65ed859ba87974":"astherus","0x84e5c854a7ff9f49c888d69deca578d406c26800":"bed_rock"} diff --git a/indexer/modules/custom/staking_fbtc/domain/__init__.py b/indexer/modules/custom/staking_fbtc/domain/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/staking_fbtc/domain/feature_staked_fbtc_detail.py b/indexer/modules/custom/staking_fbtc/domain/feature_staked_fbtc_detail.py new file mode 100644 index 000000000..c17d6048b --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/domain/feature_staked_fbtc_detail.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass +from typing import Optional + +from indexer.domain import Domain, FilterData + + +@dataclass +class StakedFBTCDetail(FilterData): + vault_address: str + protocol_id: str + wallet_address: str + amount: int + changed_amount: int + block_number: int + block_timestamp: int + + +@dataclass +class StakedFBTCCurrentStatus(FilterData): + vault_address: str + protocol_id: str + wallet_address: str + amount: int + changed_amount: int + block_number: int + block_timestamp: int + + +@dataclass +class TransferredFBTCDetail(FilterData): + vault_address: str + protocol_id: str + wallet_address: str + amount: int + changed_amount: int + block_number: int + block_timestamp: int + + +@dataclass +class TransferredFBTCCurrentStatus(FilterData): + vault_address: str + protocol_id: str + wallet_address: str + amount: int + changed_amount: int + block_number: int + block_timestamp: int diff --git a/indexer/modules/custom/staking_fbtc/endpoints/__init__.py b/indexer/modules/custom/staking_fbtc/endpoints/__init__.py new file mode 100644 index 000000000..900f66325 --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/endpoints/__init__.py @@ -0,0 +1,5 @@ +#!/usr/bin/python3 +# -*- coding: utf-8 -*- +from flask_restx.namespace import Namespace + +staking_namespace = Namespace("Staking Explorer", path="/", description="Staking Feature Explorer API") diff --git a/indexer/modules/custom/staking_fbtc/endpoints/routes.py b/indexer/modules/custom/staking_fbtc/endpoints/routes.py new file mode 100644 index 000000000..24eb546dd --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/endpoints/routes.py @@ -0,0 +1,60 @@ +import binascii +import math +import re +from datetime import datetime, timedelta +from decimal import Decimal, getcontext +from operator import or_ + +from flask import request +from flask_restx import Resource +from sqlalchemy import and_, asc, desc, func + +from api.app import explorer +from api.app.cache import cache +from api.app.explorer import explorer_namespace +from common.models import db +from common.models import db as postgres_db +from common.models.tokens import Tokens +from common.utils.exception_control import APIError +from common.utils.format_utils import as_dict, format_to_dict, format_value_for_json, row_to_dict +from common.utils.web3_utils import is_eth_address +from indexer.modules.custom.staking_fbtc.endpoints import staking_namespace +from indexer.modules.custom.staking_fbtc.models.feature_staked_fbtc_detail_records import FeatureStakedFBTCDetailRecords + +FBTC_ADDRESS = "0xc96de26018a54d51c097160568752c4e3bd6c364" + + +@staking_namespace.route("/v1/aci//staking/current_holding") +class StakingWalletHolding(Resource): + def get(self, wallet_address): + wallet_address = wallet_address.lower() + address_bytes = bytes.fromhex(wallet_address[2:]) + results = ( + db.session.query( + FeatureStakedFBTCDetailRecords.vault_address, + func.max(FeatureStakedFBTCDetailRecords.protocol_id).label("protocol_id"), + func.sum(FeatureStakedFBTCDetailRecords.amount).label("total_amount"), + ) + .filter(FeatureStakedFBTCDetailRecords.wallet_address == address_bytes) + .group_by(FeatureStakedFBTCDetailRecords.contract_address) + .all() + ) + + erc20_data = db.session.query(Tokens).filter(Tokens.address == bytes.fromhex(FBTC_ADDRESS[2:])).first() + erc20_infos = {} + result = [] + for holding in results: + contract_address = "0x" + holding.vault_address.hex() + total_amount = holding.total_amount + protocol_id = holding.protocol_id + token_amount = total_amount / (10**erc20_data.decimals) + result.append( + { + "protocol_id": protocol_id, + "vault_address": contract_address, + "total_amount": str(total_amount), + "token_amount": str(token_amount), + } + ) + + return result, 200 diff --git a/indexer/modules/custom/staking_fbtc/export_staked_fbtc_detail_job.py b/indexer/modules/custom/staking_fbtc/export_staked_fbtc_detail_job.py new file mode 100644 index 000000000..ae073366c --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/export_staked_fbtc_detail_job.py @@ -0,0 +1,167 @@ +import ast +import configparser +import logging +import os +from collections import defaultdict +from typing import Any, Dict, List, Tuple + +from indexer.domain.log import Log +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.staking_fbtc import utils +from indexer.modules.custom.staking_fbtc.domain.feature_staked_fbtc_detail import ( + StakedFBTCCurrentStatus, + StakedFBTCDetail, +) +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs +from indexer.utils.abi import decode_log + +logger = logging.getLogger(__name__) + + +class ExportLockedFBTCDetailJob(FilterTransactionDataJob): + dependency_types = [Log] + output_types = [StakedFBTCDetail, StakedFBTCCurrentStatus] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + self._chain_id = common_utils.get_chain_id(self._web3) + self._load_config("config.ini", self._chain_id) + self._service = kwargs["config"].get("db_service") + self._current_holdings = utils.get_staked_fbtc_status( + self._service, list(self.staked_abi_dict.keys()), int(kwargs["config"].get("start_block")) + ) + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + + try: + staked_protocol_dict_str = config.get(str(chain_id), "STAKED_PROTOCOL_DICT") + self.staked_protocol_dict = ast.literal_eval(staked_protocol_dict_str) + + staked_topic0_dict_str = config.get(str(chain_id), "STAKED_TOPIC0_DICT") + self.staked_topic0_dict = ast.literal_eval(staked_topic0_dict_str) + + staked_abi_dict_str = config.get(str(chain_id), "STAKED_ABI_DICT") + self.staked_abi_dict = ast.literal_eval(staked_abi_dict_str) + + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + except (SyntaxError, ValueError) as e: + raise ValueError(f"Error parsing configuration in {filename}: {str(e)}") + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(topics=list(self.staked_abi_dict.keys())), + ] + ) + + def _collect(self, **kwargs): + logs = self._data_buff[Log.type()] + staked_details, current_status_list, current_holdings = collect_detail( + logs, self._current_holdings, self.staked_abi_dict, self.staked_protocol_dict + ) + for data in staked_details: + self._collect_item(StakedFBTCDetail.type(), data) + for data in current_status_list: + self._collect_item(StakedFBTCCurrentStatus.type(), data) + self._current_holdings = current_holdings + + def _process(self, **kwargs): + self._data_buff[StakedFBTCDetail.type()].sort(key=lambda x: x.block_number) + self._data_buff[StakedFBTCCurrentStatus.type()].sort(key=lambda x: x.block_number) + + +def collect_detail( + logs: List[Log], + current_holdings: Dict[str, Dict[str, StakedFBTCCurrentStatus]], + staked_abi_dict: Dict[str, Any], + staked_protocol_dict: Dict[str, str], +) -> Tuple[List[StakedFBTCDetail], List[StakedFBTCCurrentStatus], Dict[str, Dict[str, StakedFBTCCurrentStatus]]]: + current_status = defaultdict( + lambda: defaultdict( + lambda: StakedFBTCCurrentStatus( + vault_address="", + protocol_id="", + wallet_address="", + amount=0, + changed_amount=0, + block_number=0, + block_timestamp=0, + ) + ) + ) + for contract_address, wallet_dict in current_holdings.items(): + for wallet_address, status in wallet_dict.items(): + current_status[contract_address][wallet_address] = status + + logs_by_address = defaultdict(lambda: defaultdict(list)) + for log in logs: + if log.topic0 in staked_abi_dict and log.address in staked_protocol_dict: + logs_by_address[log.address][log.block_number].append(log) + + staked_details = [] + + for address, blocks in logs_by_address.items(): + protocol_id = staked_protocol_dict[address] + + for block_number in sorted(blocks.keys()): + block_logs = blocks[block_number] + block_changes = defaultdict(int) + block_timestamp = block_logs[0].block_timestamp + + for log in block_logs: + decode_staked = decode_log(staked_abi_dict[log.topic0], log) + wallet_address = decode_staked["user"] + amount = decode_staked["amount"] + block_changes[wallet_address] += amount + + for wallet_address, change in block_changes.items(): + current_amount = current_status[address][wallet_address].amount + new_amount = current_amount + change + + current_status[address][wallet_address] = StakedFBTCCurrentStatus( + vault_address=address, + protocol_id=protocol_id, + wallet_address=wallet_address, + amount=new_amount, + changed_amount=change, + block_number=block_number, + block_timestamp=block_timestamp, + ) + + staked_details.append( + StakedFBTCDetail( + vault_address=address, + protocol_id=protocol_id, + wallet_address=wallet_address, + amount=new_amount, + changed_amount=change, + block_number=block_number, + block_timestamp=block_timestamp, + ) + ) + + current_status_list = [status for address_dict in current_status.values() for status in address_dict.values()] + + updated_current_holdings = {} + for contract_address, wallet_dict in current_status.items(): + updated_current_holdings[contract_address] = {} + for wallet_address, status in wallet_dict.items(): + updated_current_holdings[contract_address][wallet_address] = status + + return staked_details, current_status_list, updated_current_holdings diff --git a/indexer/modules/custom/staking_fbtc/export_transferred_fbtc_detail_job.py b/indexer/modules/custom/staking_fbtc/export_transferred_fbtc_detail_job.py new file mode 100644 index 000000000..0366e2178 --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/export_transferred_fbtc_detail_job.py @@ -0,0 +1,166 @@ +import ast +import configparser +import logging +import os +from collections import defaultdict +from typing import Dict, List, Tuple + +from indexer.domain.token_transfer import ERC20TokenTransfer +from indexer.executors.batch_work_executor import BatchWorkExecutor +from indexer.jobs import FilterTransactionDataJob +from indexer.modules.custom import common_utils +from indexer.modules.custom.staking_fbtc import utils +from indexer.modules.custom.staking_fbtc.domain.feature_staked_fbtc_detail import ( + TransferredFBTCCurrentStatus, + TransferredFBTCDetail, +) +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs + +logger = logging.getLogger(__name__) + + +class ExportTransferredFBTCDetailJob(FilterTransactionDataJob): + dependency_types = [ERC20TokenTransfer] + output_types = [TransferredFBTCDetail, TransferredFBTCCurrentStatus] + able_to_reorg = True + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._batch_work_executor = BatchWorkExecutor( + kwargs["batch_size"], + kwargs["max_workers"], + job_name=self.__class__.__name__, + ) + self._is_batch = kwargs["batch_size"] > 1 + self._batch_size = kwargs["batch_size"] + self._max_worker = kwargs["max_workers"] + self._chain_id = common_utils.get_chain_id(self._web3) + self._load_config("config.ini", self._chain_id) + self._service = kwargs["config"].get("db_service") + self._current_holdings = utils.get_transferred_fbtc_status( + self._service, list(self._transferred_protocol_dict.keys()), int(kwargs["config"].get("start_block")) + ) + + def _load_config(self, filename, chain_id): + base_path = os.path.dirname(os.path.abspath(__file__)) + full_path = os.path.join(base_path, filename) + config = configparser.ConfigParser() + config.read(full_path) + + try: + self._fbtc_address = config.get(str(chain_id), "FBTC_ADDRESS").lower() + transferred_protocol_dict_str = config.get(str(chain_id), "TRANSFERRED_CONTRACTS_DICT") + self._transferred_protocol_dict = ast.literal_eval(transferred_protocol_dict_str) + + except (configparser.NoOptionError, configparser.NoSectionError) as e: + raise ValueError(f"Missing required configuration in {filename}: {str(e)}") + + def get_filter(self): + return TransactionFilterByLogs( + [ + TopicSpecification(addresses=[self._fbtc_address]), + ] + ) + + def _collect(self, **kwargs): + token_transfers = self._data_buff[ERC20TokenTransfer.type()] + + transferred_details, current_status_list, updated_current_holdings = process_token_transfers( + token_transfers, self._current_holdings, self._transferred_protocol_dict, self._fbtc_address + ) + + for data in transferred_details: + self._collect_item(TransferredFBTCDetail.type(), data) + for data in current_status_list: + self._collect_item(TransferredFBTCCurrentStatus.type(), data) + self._current_holdings = updated_current_holdings + + def _process(self, **kwargs): + self._data_buff[TransferredFBTCDetail.type()].sort(key=lambda x: x.block_number) + self._data_buff[TransferredFBTCCurrentStatus.type()].sort(key=lambda x: x.block_number) + + +def process_token_transfers( + token_transfers: List[ERC20TokenTransfer], + current_holdings: Dict[str, Dict[str, TransferredFBTCCurrentStatus]], + transferred_protocol_dict: Dict[str, str], + fbtc_address: str, +) -> Tuple[ + List[TransferredFBTCDetail], List[TransferredFBTCCurrentStatus], Dict[str, Dict[str, TransferredFBTCCurrentStatus]] +]: + current_status = defaultdict( + lambda: defaultdict( + lambda: TransferredFBTCCurrentStatus( + vault_address="", + protocol_id="", + wallet_address="", + amount=0, + changed_amount=0, + block_number=0, + block_timestamp=0, + ) + ) + ) + for contract_address, wallet_dict in current_holdings.items(): + for wallet_address, status in wallet_dict.items(): + current_status[contract_address][wallet_address] = status + + transfers_by_address = defaultdict(lambda: defaultdict(list)) + for transfer in token_transfers: + if transfer.token_address == fbtc_address: + if transfer.from_address in transferred_protocol_dict: + transfers_by_address[transfer.from_address][transfer.block_number].append(transfer) + if transfer.to_address in transferred_protocol_dict: + transfers_by_address[transfer.to_address][transfer.block_number].append(transfer) + + transferred_details = [] + + for address, blocks in transfers_by_address.items(): + protocol_id = transferred_protocol_dict[address] + + for block_number in sorted(blocks.keys()): + block_transfers = blocks[block_number] + block_changes = defaultdict(int) + block_timestamp = block_transfers[0].block_timestamp + + for entity in block_transfers: + if entity.from_address == address: + block_changes[entity.to_address] -= entity.value + if entity.to_address == address: + block_changes[entity.from_address] += entity.value + + for wallet_address, change in block_changes.items(): + current_amount = current_status[address][wallet_address].amount + new_amount = current_amount + change + + current_status[address][wallet_address] = TransferredFBTCCurrentStatus( + vault_address=address, + protocol_id=protocol_id, + wallet_address=wallet_address, + amount=new_amount, + changed_amount=change, + block_number=block_number, + block_timestamp=block_timestamp, + ) + + transferred_details.append( + TransferredFBTCDetail( + vault_address=address, + protocol_id=protocol_id, + wallet_address=wallet_address, + amount=new_amount, + changed_amount=change, + block_number=block_number, + block_timestamp=block_timestamp, + ) + ) + + current_status_list = [status for address_dict in current_status.values() for status in address_dict.values()] + + updated_current_holdings = {} + for contract_address, wallet_dict in current_status.items(): + updated_current_holdings[contract_address] = {} + for wallet_address, status in wallet_dict.items(): + updated_current_holdings[contract_address][wallet_address] = status + + return transferred_details, current_status_list, updated_current_holdings diff --git a/indexer/modules/custom/staking_fbtc/models/__init__.py b/indexer/modules/custom/staking_fbtc/models/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_records.py b/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_records.py new file mode 100644 index 000000000..782a2c8f2 --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_records.py @@ -0,0 +1,53 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR + +from common.models import HemeraModel, general_converter + + +class FeatureStakedFBTCDetailRecords(HemeraModel): + __tablename__ = "af_staked_fbtc_detail_hist" + vault_address = Column(BYTEA, primary_key=True) + wallet_address = Column(BYTEA, primary_key=True) + block_number = Column(BIGINT, primary_key=True) + block_timestamp = Column(BIGINT, primary_key=True) + amount = Column(NUMERIC(100)) + changed_amount = Column(NUMERIC(100)) + protocol_id = Column(VARCHAR) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + reorg = Column(BOOLEAN, default=False) + + __table_args__ = (PrimaryKeyConstraint("vault_address", "wallet_address", "block_timestamp", "block_number"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "StakedFBTCDetail", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + { + "domain": "TransferredFBTCDetail", + "conflict_do_update": True, + "update_strategy": None, + "converter": general_converter, + }, + ] + + +Index( + "af_staked_fbtc_detail_hist_wallet_block_desc_index", + desc(FeatureStakedFBTCDetailRecords.wallet_address), + desc(FeatureStakedFBTCDetailRecords.block_timestamp), +) + +Index( + "af_staked_fbtc_detail_hist_protocol_block_desc_index", + desc(FeatureStakedFBTCDetailRecords.protocol_id), + desc(FeatureStakedFBTCDetailRecords.block_timestamp), +) diff --git a/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_status.py b/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_status.py new file mode 100644 index 000000000..52b4125b1 --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/models/feature_staked_fbtc_detail_status.py @@ -0,0 +1,50 @@ +from datetime import datetime + +from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func +from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP, VARCHAR + +from common.models import HemeraModel, general_converter + + +class FeatureStakedFBTCDetailStatus(HemeraModel): + __tablename__ = "af_staked_fbtc_current" + vault_address = Column(BYTEA, primary_key=True) + wallet_address = Column(BYTEA, primary_key=True) + block_number = Column(BIGINT) + block_timestamp = Column(BIGINT) + amount = Column(NUMERIC(100)) + changed_amount = Column(NUMERIC(100)) + protocol_id = Column(VARCHAR) + + create_time = Column(TIMESTAMP, server_default=func.now()) + update_time = Column(TIMESTAMP, server_default=func.now()) + + __table_args__ = (PrimaryKeyConstraint("vault_address", "wallet_address"),) + + @staticmethod + def model_domain_mapping(): + return [ + { + "domain": "StakedFBTCCurrentStatus", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_staked_fbtc_current.block_number", + "converter": general_converter, + }, + { + "domain": "TransferredFBTCCurrentStatus", + "conflict_do_update": True, + "update_strategy": "EXCLUDED.block_number > af_staked_fbtc_current.block_number", + "converter": general_converter, + }, + ] + + +Index( + "af_staked_fbtc_current_wallet_block_desc_index", + desc(FeatureStakedFBTCDetailStatus.wallet_address), +) + +Index( + "af_staked_fbtc_current_protocol_block_desc_index", + desc(FeatureStakedFBTCDetailStatus.protocol_id), +) diff --git a/indexer/modules/custom/staking_fbtc/utils.py b/indexer/modules/custom/staking_fbtc/utils.py new file mode 100644 index 000000000..87881909b --- /dev/null +++ b/indexer/modules/custom/staking_fbtc/utils.py @@ -0,0 +1,76 @@ +from sqlalchemy import and_, func + +from indexer.modules.custom.staking_fbtc.domain.feature_staked_fbtc_detail import ( + StakedFBTCCurrentStatus, + TransferredFBTCCurrentStatus, +) +from indexer.modules.custom.staking_fbtc.models.feature_staked_fbtc_detail_records import FeatureStakedFBTCDetailRecords + + +def get_current_status_generic(db_service, contract_list, block_number, status_class): + if not db_service: + return {} + bytea_address_list = [bytes.fromhex(address[2:]) for address in contract_list] + + session = db_service.get_service_session() + try: + latest_blocks = ( + session.query( + FeatureStakedFBTCDetailRecords.vault_address, + FeatureStakedFBTCDetailRecords.wallet_address, + func.max(FeatureStakedFBTCDetailRecords.block_number).label("max_block_number"), + ) + .filter(FeatureStakedFBTCDetailRecords.vault_address.in_(bytea_address_list)) + .filter(FeatureStakedFBTCDetailRecords.block_number < block_number) + .group_by(FeatureStakedFBTCDetailRecords.vault_address, FeatureStakedFBTCDetailRecords.wallet_address) + .subquery() + ) + + result = ( + session.query(FeatureStakedFBTCDetailRecords) + .join( + latest_blocks, + and_( + FeatureStakedFBTCDetailRecords.vault_address == latest_blocks.c.vault_address, + FeatureStakedFBTCDetailRecords.wallet_address == latest_blocks.c.wallet_address, + FeatureStakedFBTCDetailRecords.block_number == latest_blocks.c.max_block_number, + ), + ) + .all() + ) + + current_status_map = {} + if result is not None: + for item in result: + contract_address = "0x" + item.vault_address.hex() + wallet_address = "0x" + item.wallet_address.hex() + + if contract_address not in current_status_map: + current_status_map[contract_address] = {} + + current_status_map[contract_address][wallet_address] = status_class( + vault_address=contract_address, + protocol_id=item.protocol_id, + wallet_address=wallet_address, + amount=item.amount, + block_number=item.block_number, + block_timestamp=item.block_timestamp, + ) + + except Exception as e: + print(e) + raise e + finally: + session.close() + + return current_status_map + + +# Usage for StakedFBTCCurrentStatus +def get_staked_fbtc_status(db_service, staked_contract_list, block_number): + return get_current_status_generic(db_service, staked_contract_list, block_number, StakedFBTCCurrentStatus) + + +# Usage for TransferredFBTCCurrentStatus +def get_transferred_fbtc_status(db_service, transferred_contract_list, block_number): + return get_current_status_generic(db_service, transferred_contract_list, block_number, TransferredFBTCCurrentStatus) From 1ef6fd04bbad7ac20708e0ee3c65484df5c1c1f5 Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:57:11 +0800 Subject: [PATCH 68/70] new feature: use parameter to control log level (#149) * new feature: use parameter to control log level --- cli/load.py | 11 ++++++++++- cli/reorg.py | 11 ++++++++++- cli/stream.py | 11 ++++++++++- indexer/utils/logging_utils.py | 21 ++++++++++++--------- 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/cli/load.py b/cli/load.py index b4d55a494..313bdc563 100644 --- a/cli/load.py +++ b/cli/load.py @@ -232,6 +232,14 @@ def wrapper(*args, **kwargs): envvar="AUTO_UPGRADE_DB", help="Whether to automatically run database migration scripts to update the database to the latest version.", ) +@click.option( + "--log-level", + default="INFO", + show_default=True, + type=str, + envvar="LOG_LEVEL", + help="Set the logging output level.", +) @calculate_execution_time def load( provider_uri, @@ -255,8 +263,9 @@ def load( sync_recorder="file_sync_record", cache="memory", auto_upgrade_db=True, + log_level="INFO", ): - configure_logging(log_file) + configure_logging(log_level=log_level, log_file=log_file) configure_signals() provider_uri = pick_random_provider_uri(provider_uri) debug_provider_uri = pick_random_provider_uri(debug_provider_uri) diff --git a/cli/reorg.py b/cli/reorg.py index 74fd4655a..052aa4ed9 100644 --- a/cli/reorg.py +++ b/cli/reorg.py @@ -116,6 +116,14 @@ envvar="AUTO_UPGRADE_DB", help="Whether to automatically run database migration scripts to update the database to the latest version.", ) +@click.option( + "--log-level", + default="INFO", + show_default=True, + type=str, + envvar="LOG_LEVEL", + help="Set the logging output level.", +) def reorg( provider_uri, debug_provider_uri, @@ -130,8 +138,9 @@ def reorg( cache=None, config_file=None, auto_upgrade_db=True, + log_level="INFO", ): - configure_logging(log_file) + configure_logging(log_level=log_level, log_file=log_file) configure_signals() provider_uri = pick_random_provider_uri(provider_uri) diff --git a/cli/stream.py b/cli/stream.py index 66f219e1d..b64d12bba 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -272,6 +272,14 @@ def wrapper(*args, **kwargs): envvar="AUTO_UPGRADE_DB", help="Whether to automatically run database migration scripts to update the database to the latest version.", ) +@click.option( + "--log-level", + default="INFO", + show_default=True, + type=str, + envvar="LOG_LEVEL", + help="Set the logging output level.", +) @calculate_execution_time def stream( provider_uri, @@ -300,8 +308,9 @@ def stream( config_file=None, force_filter_mode=False, auto_upgrade_db=True, + log_level="INFO", ): - configure_logging(log_file) + configure_logging(log_level, log_file) configure_signals() provider_uri = pick_random_provider_uri(provider_uri) debug_provider_uri = pick_random_provider_uri(debug_provider_uri) diff --git a/indexer/utils/logging_utils.py b/indexer/utils/logging_utils.py index 336a2b2f0..408faaae5 100644 --- a/indexer/utils/logging_utils.py +++ b/indexer/utils/logging_utils.py @@ -2,16 +2,15 @@ import os import signal import sys +from fileinput import filename -def logging_basic_config(filename=None): +def logging_basic_config(log_level=logging.INFO, log_file=None): format = "%(asctime)s - %(name)s [%(levelname)s] - %(message)s" if filename is not None: - logging.basicConfig(level=logging.INFO, format=format, filename=filename) + logging.basicConfig(level=log_level, format=format, filename=log_file) else: - logging.basicConfig(level=logging.INFO, format=format) - - logging.getLogger("ethereum_dasm.evmdasm").setLevel(logging.ERROR) + logging.basicConfig(level=log_level, format=format) def configure_signals(): @@ -22,13 +21,17 @@ def sigterm_handler(_signo, _stack_frame): signal.signal(signal.SIGTERM, sigterm_handler) -def configure_logging(filename): - if filename: - log_dir = os.path.dirname(filename) +def configure_logging(log_level="INFO", log_file=None): + if log_file: + log_dir = os.path.dirname(log_file) if not os.path.exists(log_dir): os.makedirs(log_dir) for handler in logging.root.handlers[:]: logging.root.removeHandler(handler) - logging_basic_config(filename=filename) + + level = logging.getLevelName(log_level) + if level is str: + raise ValueError("Unknown log level: %r" % log_level) + logging_basic_config(log_level=level, log_file=log_file) From b435b82032a27cdc493e603466b67e5f405bb0cf Mon Sep 17 00:00:00 2001 From: xuzh2024 <167734725+xuzh2024@users.noreply.github.com> Date: Fri, 27 Sep 2024 17:57:47 +0800 Subject: [PATCH 69/70] add merchant moe table scripts (#172) --- .../20240927_add_merchant_moe_table.py | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 migrations/versions/20240927_add_merchant_moe_table.py diff --git a/migrations/versions/20240927_add_merchant_moe_table.py b/migrations/versions/20240927_add_merchant_moe_table.py new file mode 100644 index 000000000..4c59ceecb --- /dev/null +++ b/migrations/versions/20240927_add_merchant_moe_table.py @@ -0,0 +1,87 @@ +"""add merchant moe table + +Revision ID: 67015d9fa59b +Revises: c609922eae7a +Create Date: 2024-09-27 17:00:46.320469 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision: str = '67015d9fa59b' +down_revision: Union[str, None] = 'c609922eae7a' +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('af_merchant_moe_pool_data_current', + sa.Column('pool_address', postgresql.BYTEA(), nullable=False), + sa.Column('block_timestamp', sa.BIGINT(), nullable=True), + sa.Column('block_number', sa.BIGINT(), nullable=True), + sa.Column('active_id', sa.BIGINT(), nullable=True), + sa.Column('bin_step', sa.BIGINT(), nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('update_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('reorg', sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint('pool_address') + ) + op.create_table('af_merchant_moe_pool_data_hist', + sa.Column('pool_address', postgresql.BYTEA(), nullable=False), + sa.Column('block_timestamp', sa.BIGINT(), nullable=False), + sa.Column('block_number', sa.BIGINT(), nullable=False), + sa.Column('active_id', sa.BIGINT(), nullable=True), + sa.Column('bin_step', sa.BIGINT(), nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('update_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('reorg', sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint('pool_address', 'block_timestamp', 'block_number') + ) + op.create_table('af_staked_fbtc_current', + sa.Column('vault_address', postgresql.BYTEA(), nullable=False), + sa.Column('wallet_address', postgresql.BYTEA(), nullable=False), + sa.Column('block_number', sa.BIGINT(), nullable=True), + sa.Column('block_timestamp', sa.BIGINT(), nullable=True), + sa.Column('amount', sa.NUMERIC(precision=100), nullable=True), + sa.Column('changed_amount', sa.NUMERIC(precision=100), nullable=True), + sa.Column('protocol_id', sa.VARCHAR(), nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('update_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.PrimaryKeyConstraint('vault_address', 'wallet_address') + ) + op.create_index('af_staked_fbtc_current_protocol_block_desc_index', 'af_staked_fbtc_current', [sa.text('protocol_id DESC')], unique=False) + op.create_index('af_staked_fbtc_current_wallet_block_desc_index', 'af_staked_fbtc_current', [sa.text('wallet_address DESC')], unique=False) + op.create_table('af_staked_fbtc_detail_hist', + sa.Column('vault_address', postgresql.BYTEA(), nullable=False), + sa.Column('wallet_address', postgresql.BYTEA(), nullable=False), + sa.Column('block_number', sa.BIGINT(), nullable=False), + sa.Column('block_timestamp', sa.BIGINT(), nullable=False), + sa.Column('amount', sa.NUMERIC(precision=100), nullable=True), + sa.Column('changed_amount', sa.NUMERIC(precision=100), nullable=True), + sa.Column('protocol_id', sa.VARCHAR(), nullable=True), + sa.Column('create_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('update_time', postgresql.TIMESTAMP(), server_default=sa.text('now()'), nullable=True), + sa.Column('reorg', sa.BOOLEAN(), nullable=True), + sa.PrimaryKeyConstraint('vault_address', 'wallet_address', 'block_timestamp', 'block_number') + ) + op.create_index('af_staked_fbtc_detail_hist_protocol_block_desc_index', 'af_staked_fbtc_detail_hist', [sa.text('protocol_id DESC'), sa.text('block_timestamp DESC')], unique=False) + op.create_index('af_staked_fbtc_detail_hist_wallet_block_desc_index', 'af_staked_fbtc_detail_hist', [sa.text('wallet_address DESC'), sa.text('block_timestamp DESC')], unique=False) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_index('af_staked_fbtc_detail_hist_wallet_block_desc_index', table_name='af_staked_fbtc_detail_hist') + op.drop_index('af_staked_fbtc_detail_hist_protocol_block_desc_index', table_name='af_staked_fbtc_detail_hist') + op.drop_table('af_staked_fbtc_detail_hist') + op.drop_index('af_staked_fbtc_current_wallet_block_desc_index', table_name='af_staked_fbtc_current') + op.drop_index('af_staked_fbtc_current_protocol_block_desc_index', table_name='af_staked_fbtc_current') + op.drop_table('af_staked_fbtc_current') + op.drop_table('af_merchant_moe_pool_data_hist') + op.drop_table('af_merchant_moe_pool_data_current') + # ### end Alembic commands ### From 29513c799df1036b3e6282e0ecf1d452709311a7 Mon Sep 17 00:00:00 2001 From: li xiang Date: Sun, 29 Sep 2024 10:32:23 +0800 Subject: [PATCH 70/70] Feat/add filter token transfer job (#167) * Add postgresql service on CI * Fix address holder repeat * Add user_defined_config * Support WETH deposit & withdraw convert to transfers * Migrate FilterTransactionDataJob to BaseJob file(indexer.jobs.filter_transaction_data_job.FilterTransactionDataJob -> indexer.jobs.base_job.FilterTransactionDataJob) --- .github/workflows/ci.yaml | 39 +- .github/workflows/ut.yaml | 38 + Makefile | 7 +- cli/__init__.py | 2 + cli/init_db.py | 16 + cli/stream.py | 13 +- .../indexer-config.yaml | 4 + indexer/controller/scheduler/job_scheduler.py | 3 +- .../controller/scheduler/reorg_scheduler.py | 2 +- indexer/domain/token_transfer.py | 8 +- indexer/jobs/__init__.py | 4 +- indexer/jobs/base_job.py | 16 + indexer/jobs/export_blocks_job.py | 29 +- .../jobs/export_tokens_and_transfers_job.py | 57 +- indexer/jobs/filter_transaction_data_job.py | 14 - .../bridge/arbitrum/arb_bridge_on_l1_job.py | 2 +- .../bridge/arbitrum/arb_bridge_on_l2_job.py | 2 +- .../bedrock/bedrock_bridge_on_l1_job.py | 2 +- .../bedrock/bedrock_bridge_on_l2_job.py | 2 +- .../custom/address_index/address_index_job.py | 39 +- .../modules/user_ops/export_user_ops_job.py | 2 +- indexer/tests/domain/test_token_transfers.py | 56 +- .../jobs/test_export_token_transfers_job.py | 84 +- poetry.lock | 893 +++++++++--------- pyproject.toml | 3 + 25 files changed, 801 insertions(+), 536 deletions(-) create mode 100644 .github/workflows/ut.yaml create mode 100644 cli/init_db.py rename indexer-config.yaml => config/indexer-config.yaml (91%) delete mode 100644 indexer/jobs/filter_transaction_data_job.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 606485c69..2086e44dd 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -1,12 +1,31 @@ -name: Python application unit test +name: Hemera Indexer Continuous Integration on: [pull_request] jobs: - test: - + build: runs-on: ubuntu-latest - + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: hemera + POSTGRES_PASSWORD: password + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + ports: + - 5432:5432 + env: + ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL }}' + ETHEREUM_PUBLIC_NODE_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_RPC_URL }}' + LINEA_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_DEBUG_RPC_URL }}' + LINEA_PUBLIC_NODE_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_RPC_URL }}' + POSTGRES_USER: hemera + POSTGRES_PASSWORD: password + POSTGRES_URL: postgresql://hemera:password@localhost:5432/hemera steps: - uses: actions/checkout@v2 - name: Set up Python @@ -20,6 +39,7 @@ jobs: pip install poetry poetry update poetry install -v + poetry show - name: Set PYTHONPATH run: echo "PYTHONPATH=\$PYTHONPATH:$(pwd)" >> $GITHUB_ENV @@ -27,9 +47,12 @@ jobs: - name: Verify PYTHONPATH run: echo $PYTHONPATH - - name: Unit Test with pytest - env: - LINEA_PUBLIC_NODE_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_RPC_URL }}' + - name: Init database + run: | + export PYTHONPATH=$(pwd) + make init_db + + - name: Pipeline Test with pytest run: | export PYTHONPATH=$(pwd) - make test indexer \ No newline at end of file + make test indexer diff --git a/.github/workflows/ut.yaml b/.github/workflows/ut.yaml new file mode 100644 index 000000000..a715be13c --- /dev/null +++ b/.github/workflows/ut.yaml @@ -0,0 +1,38 @@ +name: Python application unit test + +on: [pull_request] + +jobs: + test: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.9' + + - name: Install dependencies + run: | + pip install --upgrade pip + pip install poetry + poetry update + poetry install -v + + - name: Set PYTHONPATH + run: echo "PYTHONPATH=\$PYTHONPATH:$(pwd)" >> $GITHUB_ENV + + - name: Verify PYTHONPATH + run: echo $PYTHONPATH + + - name: Unit Test with pytest + env: + ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL }}' + ETHEREUM_PUBLIC_NODE_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_RPC_URL }}' + LINEA_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_DEBUG_RPC_URL }}' + LINEA_PUBLIC_NODE_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_RPC_URL }}' + run: | + export PYTHONPATH=$(pwd) + make test indexer \ No newline at end of file diff --git a/Makefile b/Makefile index 5e89ec8ee..1acb04f4b 100644 --- a/Makefile +++ b/Makefile @@ -20,6 +20,7 @@ test: poetry run pytest -vv -m $(filter-out $@,$(MAKECMDGOALS)); \ fi + PRE_COMMIT_INSTALLED := $(shell command -v pre-commit > /dev/null 2>&1 && echo yes || echo no) format: @@ -28,4 +29,8 @@ ifeq ($(PRE_COMMIT_INSTALLED),yes) @pre-commit run --all-files else @echo "Please install pre-commit in your local machine(pip install pre-commit or brew install pre-commit)" -endif \ No newline at end of file +endif + +init_db: + @echo "Initializing database..." + poetry run python -m hemera.py init_db \ No newline at end of file diff --git a/cli/__init__.py b/cli/__init__.py index cc6a0d37d..21a4449f4 100644 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -4,6 +4,7 @@ from cli.aggregates import aggregates from cli.api import api +from cli.init_db import init_db from cli.load import load from cli.reorg import reorg from cli.stream import stream @@ -30,3 +31,4 @@ def cli(ctx): cli.add_command(api, "api") cli.add_command(aggregates, "aggregates") cli.add_command(reorg, "reorg") +cli.add_command(init_db, "init_db") diff --git a/cli/init_db.py b/cli/init_db.py new file mode 100644 index 000000000..c9eb5fe1b --- /dev/null +++ b/cli/init_db.py @@ -0,0 +1,16 @@ +import click + +from common.services.postgresql_service import PostgreSQLService + + +@click.command(context_settings=dict(help_option_names=["-h", "--help"])) +@click.option( + "-pg", + "--postgres-url", + type=str, + required=False, + envvar="POSTGRES_URL", + help="The required postgres connection url." "e.g. postgresql+psycopg2://postgres:admin@127.0.0.1:5432/ethereum", +) +def init_db(postgres_url): + PostgreSQLService(postgres_url, db_version="head", init_schema=True) diff --git a/cli/stream.py b/cli/stream.py index b64d12bba..8c020ddfc 100644 --- a/cli/stream.py +++ b/cli/stream.py @@ -334,20 +334,29 @@ def stream( logging.warning("No postgres url provided. Exception recorder will not be useful.") if config_file: + file_based_config = {} if not os.path.exists(config_file): raise click.ClickException(f"Config file {config_file} not found") with open(config_file, "r") as f: if config_file.endswith(".json"): import json - config.update(json.load(f)) + file_based_config = json.load(f) elif config_file.endswith(".yaml") or config_file.endswith(".yml"): import yaml - config.update(yaml.safe_load(f)) + file_based_config = yaml.safe_load(f) else: raise click.ClickException(f"Config file {config_file} is not supported)") + if file_based_config.get("chain_id") != config["chain_id"]: + raise click.ClickException( + f"Config file {config_file} is not compatible with chain_id {config['chain_id']}" + ) + else: + logging.info(f"Loading config from file: {config_file}, chain_id: {config['chain_id']}") + config.update(file_based_config) + if output_types is None: entity_types = calculate_entity_value(entity_types) output_types = list(set(generate_output_types(entity_types))) diff --git a/indexer-config.yaml b/config/indexer-config.yaml similarity index 91% rename from indexer-config.yaml rename to config/indexer-config.yaml index 21b2ec9a0..049e857a6 100644 --- a/indexer-config.yaml +++ b/config/indexer-config.yaml @@ -1,3 +1,4 @@ +chain_id: 1 opensea_job: seaport_1_6: contract_address: "0x0000000000000068f116a894984e2db1123eb395" @@ -28,3 +29,6 @@ opensea_job: fee_addresses: - "0x0000a26b00c1f0df003000390027140000faa719" - "0x5b3256965e7c3cf26e11fcaf296dfc8807c01073" + +export_tokens_and_transfers_job: + weth_address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" diff --git a/indexer/controller/scheduler/job_scheduler.py b/indexer/controller/scheduler/job_scheduler.py index 409f755f7..d12b36861 100644 --- a/indexer/controller/scheduler/job_scheduler.py +++ b/indexer/controller/scheduler/job_scheduler.py @@ -11,10 +11,9 @@ from enumeration.record_level import RecordLevel from indexer.exporters.console_item_exporter import ConsoleItemExporter from indexer.jobs import CSVSourceJob -from indexer.jobs.base_job import BaseExportJob, BaseJob, ExtensionJob +from indexer.jobs.base_job import BaseExportJob, BaseJob, ExtensionJob, FilterTransactionDataJob from indexer.jobs.check_block_consensus_job import CheckBlockConsensusJob from indexer.jobs.export_blocks_job import ExportBlocksJob -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob from indexer.utils.abi import bytes_to_hex_str from indexer.utils.exception_recorder import ExceptionRecorder diff --git a/indexer/controller/scheduler/reorg_scheduler.py b/indexer/controller/scheduler/reorg_scheduler.py index fb1113dfb..3cb82a9a4 100644 --- a/indexer/controller/scheduler/reorg_scheduler.py +++ b/indexer/controller/scheduler/reorg_scheduler.py @@ -8,10 +8,10 @@ from common.models.tokens import Tokens from common.services.postgresql_service import session_scope from common.utils.module_loading import import_submodules +from indexer.jobs import FilterTransactionDataJob from indexer.jobs.base_job import BaseExportJob, BaseJob, ExtensionJob from indexer.jobs.export_blocks_job import ExportBlocksJob from indexer.jobs.export_reorg_job import ExportReorgJob -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob from indexer.utils.abi import bytes_to_hex_str import_submodules("indexer.modules") diff --git a/indexer/domain/token_transfer.py b/indexer/domain/token_transfer.py index 553c379da..717936647 100644 --- a/indexer/domain/token_transfer.py +++ b/indexer/domain/token_transfer.py @@ -309,10 +309,10 @@ def extract_transfer_from_log(log: Log) -> List[TokenTransfer]: if topic == transfer_event.get_signature(): token_transfers = handle_transfer_event(log) - # elif topic == deposit_event.get_signature(): - # token_transfers = handle_deposit_event(log) - # elif topic == withdraw_event.get_signature(): - # token_transfers = handle_withdraw_event(log) + elif topic == deposit_event.get_signature(): + token_transfers = handle_deposit_event(log) + elif topic == withdraw_event.get_signature(): + token_transfers = handle_withdraw_event(log) elif topic == single_transfer_event.get_signature(): token_transfers = handle_transfer_single_event(log) elif topic == batch_transfer_event.get_signature(): diff --git a/indexer/jobs/__init__.py b/indexer/jobs/__init__.py index 0822bbca1..8376c8b7c 100644 --- a/indexer/jobs/__init__.py +++ b/indexer/jobs/__init__.py @@ -2,15 +2,16 @@ "CSVSourceJob", "ExportBlocksJob", "ExportTransactionsAndLogsJob", - "FilterTransactionDataJob", "ExportTokensAndTransfersJob", "ExportTokenIdInfosJob", "ExportTokenBalancesJob", "ExportTracesJob", "ExportContractsJob", "ExportCoinBalancesJob", + "FilterTransactionDataJob", ] +from indexer.jobs.base_job import FilterTransactionDataJob from indexer.jobs.csv_source_job import CSVSourceJob from indexer.jobs.export_blocks_job import ExportBlocksJob from indexer.jobs.export_coin_balances_job import ExportCoinBalancesJob @@ -20,4 +21,3 @@ from indexer.jobs.export_tokens_and_transfers_job import ExportTokensAndTransfersJob from indexer.jobs.export_traces_job import ExportTracesJob from indexer.jobs.export_transactions_and_logs_job import ExportTransactionsAndLogsJob -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob diff --git a/indexer/jobs/base_job.py b/indexer/jobs/base_job.py index a611f1a1c..fef81eea1 100644 --- a/indexer/jobs/base_job.py +++ b/indexer/jobs/base_job.py @@ -9,6 +9,7 @@ from common.utils.exception_control import FastShutdownError from common.utils.format_utils import to_snake_case from indexer.domain import Domain +from indexer.domain.transaction import Transaction from indexer.utils.reorg import should_reorg @@ -70,6 +71,9 @@ def __init__(self, **kwargs): self.logger = logging.getLogger(self.__class__.__name__) self._is_batch = kwargs["batch_size"] > 1 if kwargs.get("batch_size") else False self._reorg = kwargs["reorg"] if kwargs.get("reorg") else False + + self._chain_id = kwargs.get("chain_id") or (self._web3.eth.chain_id if self._batch_web3_provider else None) + self._should_reorg = False self._should_reorg_type = set() self._service = kwargs["config"].get("db_service", None) @@ -196,3 +200,15 @@ class BaseExportJob(BaseJob): class ExtensionJob(BaseJob): pass + + +class FilterTransactionDataJob(ExtensionJob): + dependency_types = [Transaction] + output_types = [] + is_filter = True + + def get_filter(self): + raise NotImplementedError + + def get_filter_transactions(self): + return list(filter(self.get_filter().is_satisfied_by, self._data_buff[Transaction.type()])) diff --git a/indexer/jobs/export_blocks_job.py b/indexer/jobs/export_blocks_job.py index 951e2396a..e9f3e5c63 100644 --- a/indexer/jobs/export_blocks_job.py +++ b/indexer/jobs/export_blocks_job.py @@ -76,21 +76,22 @@ def _collect(self, **kwargs): is_only_log_filter = True filter_blocks = set() + if self._is_filter: + for filter in self._filters: + if isinstance(filter, TransactionFilterByLogs): + for filter_param in filter.get_eth_log_filters_params(): + filter_param.update({"fromBlock": self._start_block, "toBlock": self._end_block}) + logs = self._web3.eth.get_logs(filter_param) + filter_blocks.update(set([log["blockNumber"] for log in logs])) + transaction_hashes = set([log["transactionHash"] for log in logs]) + transaction_hashes = [h.hex() for h in transaction_hashes] + self._specification |= TransactionHashSpecification(transaction_hashes) + elif isinstance(filter, TransactionFilterByTransactionInfo): + is_only_log_filter = False + self._specification |= filter.get_or_specification() + else: + raise ValueError(f"Unsupported filter type: {type(filter)}") - for filter in self._filters: - if isinstance(filter, TransactionFilterByLogs): - for filter_param in filter.get_eth_log_filters_params(): - filter_param.update({"fromBlock": self._start_block, "toBlock": self._end_block}) - logs = self._web3.eth.get_logs(filter_param) - filter_blocks.update(set([log["blockNumber"] for log in logs])) - transaction_hashes = set([log["transactionHash"] for log in logs]) - transaction_hashes = [h.hex() for h in transaction_hashes] - self._specification |= TransactionHashSpecification(transaction_hashes) - elif isinstance(filter, TransactionFilterByTransactionInfo): - is_only_log_filter = False - self._specification |= filter.get_or_specification() - else: - raise ValueError(f"Unsupported filter type: {type(filter)}") if self._is_filter and is_only_log_filter: blocks = list(filter_blocks) total_items = len(blocks) diff --git a/indexer/jobs/export_tokens_and_transfers_job.py b/indexer/jobs/export_tokens_and_transfers_job.py index 0e79c38ff..947f5f120 100644 --- a/indexer/jobs/export_tokens_and_transfers_job.py +++ b/indexer/jobs/export_tokens_and_transfers_job.py @@ -16,11 +16,17 @@ ERC721TokenTransfer, ERC1155TokenTransfer, TokenTransfer, + batch_transfer_event, + deposit_event, extract_transfer_from_log, + single_transfer_event, + transfer_event, + withdraw_event, ) from indexer.executors.batch_work_executor import BatchWorkExecutor -from indexer.jobs.base_job import BaseExportJob +from indexer.jobs.base_job import FilterTransactionDataJob from indexer.modules.bridge.signature import function_abi_to_4byte_selector_str +from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs from indexer.utils.abi import encode_abi from indexer.utils.exception_recorder import ExceptionRecorder from indexer.utils.json_rpc_requests import generate_eth_call_json_rpc_without_block_number @@ -99,7 +105,7 @@ } -class ExportTokensAndTransfersJob(BaseExportJob): +class ExportTokensAndTransfersJob(FilterTransactionDataJob): output_transfer_types = [ ERC20TokenTransfer, ERC721TokenTransfer, @@ -121,17 +127,50 @@ def __init__(self, **kwargs): ) self._is_batch = kwargs["batch_size"] > 1 - self._token_addresses = set() - self._token_transfers = [] + self.weth_address = self.user_defined_config.get("weth_address") or "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" + self.filter_token_address = self.user_defined_config.get("filter_token_address") or [] + + def get_filter(self): + filters = [] + filters.append( + TopicSpecification( + addresses=self.filter_token_address, + topics=[ + transfer_event.get_signature(), + single_transfer_event.get_signature(), + batch_transfer_event.get_signature(), + ], + ) + ) - def _end(self): - super()._end() - self._token_addresses.clear() - self._token_transfers.clear() + if self.weth_address: + filters.append( + TopicSpecification( + addresses=[self.weth_address], + topics=[deposit_event.get_signature(), withdraw_event.get_signature()], + ), + ) + return TransactionFilterByLogs(filters) def _collect(self, **kwargs): + + filtered_logs = [ + log + for log in self._data_buff[Log.type()] + if log.topic0 + in [ + transfer_event.get_signature(), + single_transfer_event.get_signature(), + batch_transfer_event.get_signature(), + ] + or ( + log.topic0 in [deposit_event.get_signature(), withdraw_event.get_signature()] + and log.address == self.weth_address + ) + ] + self._batch_work_executor.execute( - self._data_buff[Log.type()], + filtered_logs, self._extract_batch, total_items=len(self._data_buff[Log.type()]), ) diff --git a/indexer/jobs/filter_transaction_data_job.py b/indexer/jobs/filter_transaction_data_job.py deleted file mode 100644 index ed04448c1..000000000 --- a/indexer/jobs/filter_transaction_data_job.py +++ /dev/null @@ -1,14 +0,0 @@ -from indexer.domain.transaction import Transaction -from indexer.jobs.base_job import ExtensionJob - - -class FilterTransactionDataJob(ExtensionJob): - dependency_types = [Transaction] - output_types = [] - is_filter = True - - def get_filter(self): - raise NotImplementedError - - def get_filter_transactions(self): - return list(filter(self.get_filter().is_satisfied_by, self._data_buff[Transaction.type()])) diff --git a/indexer/modules/bridge/arbitrum/arb_bridge_on_l1_job.py b/indexer/modules/bridge/arbitrum/arb_bridge_on_l1_job.py index 6085cb0f6..11242f4f1 100644 --- a/indexer/modules/bridge/arbitrum/arb_bridge_on_l1_job.py +++ b/indexer/modules/bridge/arbitrum/arb_bridge_on_l1_job.py @@ -2,7 +2,7 @@ from enumeration.entity_type import EntityType from indexer.domain.transaction import Transaction -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob +from indexer.jobs import FilterTransactionDataJob from indexer.modules.bridge.arbitrum.arb_parser import * from indexer.modules.bridge.arbitrum.arb_rlp import calculate_deposit_tx_id, calculate_submit_retryable_id from indexer.modules.bridge.domain.arbitrum import ArbitrumL1ToL2TransactionOnL1, ArbitrumL2ToL1TransactionOnL1 diff --git a/indexer/modules/bridge/arbitrum/arb_bridge_on_l2_job.py b/indexer/modules/bridge/arbitrum/arb_bridge_on_l2_job.py index b7c12c7e2..6d3795553 100644 --- a/indexer/modules/bridge/arbitrum/arb_bridge_on_l2_job.py +++ b/indexer/modules/bridge/arbitrum/arb_bridge_on_l2_job.py @@ -1,6 +1,6 @@ from enumeration.entity_type import EntityType from indexer.domain.transaction import Transaction -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob +from indexer.jobs import FilterTransactionDataJob from indexer.modules.bridge.arbitrum.arb_parser import * from indexer.modules.bridge.domain.arbitrum import ArbitrumL1ToL2TransactionOnL2, ArbitrumL2ToL1TransactionOnL2 from indexer.specification.specification import TopicSpecification, TransactionFilterByLogs diff --git a/indexer/modules/bridge/bedrock/bedrock_bridge_on_l1_job.py b/indexer/modules/bridge/bedrock/bedrock_bridge_on_l1_job.py index 418179790..d1609568c 100644 --- a/indexer/modules/bridge/bedrock/bedrock_bridge_on_l1_job.py +++ b/indexer/modules/bridge/bedrock/bedrock_bridge_on_l1_job.py @@ -2,7 +2,7 @@ from enumeration.entity_type import EntityType from indexer.domain.transaction import Transaction -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob +from indexer.jobs import FilterTransactionDataJob from indexer.modules.bridge.bedrock.parser.bedrock_bridge_parser import ( BEDROCK_EVENT_ABI_SIGNATURE_MAPPING, parse_propose_l2_output, diff --git a/indexer/modules/bridge/bedrock/bedrock_bridge_on_l2_job.py b/indexer/modules/bridge/bedrock/bedrock_bridge_on_l2_job.py index 2da1cbf3f..f61f84acf 100644 --- a/indexer/modules/bridge/bedrock/bedrock_bridge_on_l2_job.py +++ b/indexer/modules/bridge/bedrock/bedrock_bridge_on_l2_job.py @@ -2,7 +2,7 @@ from enumeration.entity_type import EntityType from indexer.domain.transaction import Transaction -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob +from indexer.jobs import FilterTransactionDataJob from indexer.modules.bridge.bedrock.parser.bedrock_bridge_parser import ( BEDROCK_EVENT_ABI_SIGNATURE_MAPPING, parse_message_passed_event, diff --git a/indexer/modules/custom/address_index/address_index_job.py b/indexer/modules/custom/address_index/address_index_job.py index a3bf49b4d..5b0efe13b 100644 --- a/indexer/modules/custom/address_index/address_index_job.py +++ b/indexer/modules/custom/address_index/address_index_job.py @@ -20,7 +20,7 @@ from indexer.modules.custom.address_index.domain.address_transaction import AddressTransaction from indexer.modules.custom.address_index.domain.token_address_nft_inventory import TokenAddressNftInventory from indexer.utils.token_fetcher import TokenFetcher -from indexer.utils.utils import ZERO_ADDRESS +from indexer.utils.utils import ZERO_ADDRESS, distinct_collections_by_group logger = logging.getLogger(__name__) @@ -404,6 +404,43 @@ def _collect(self, **kwargs): self._batch_work_executor.wait() def _process(self, **kwargs): + # Sort address holder + self._data_buff[AddressTokenHolder.type()] = distinct_collections_by_group( + [ + AddressTokenHolder( + address=token_balance.address, + token_address=token_balance.token_address, + balance_of=token_balance.balance_of, + ) + for token_balance in self._data_buff[AddressTokenHolder.type()] + ], + group_by=["address", "token_address", "balance_of"], + ) + + self._data_buff[TokenAddressNftInventory.type()] = distinct_collections_by_group( + [ + TokenAddressNftInventory( + token_address=nft_owner.token_address, + token_id=nft_owner.token_id, + wallet_address=nft_owner.wallet_address, + ) + for nft_owner in self._data_buff[TokenAddressNftInventory.type()] + ], + group_by=["token_address", "token_id", "wallet_address"], + ) + + self._data_buff[AddressNft1155Holder.type()] = distinct_collections_by_group( + [ + AddressNft1155Holder( + address=nft_owner.address, + token_address=nft_owner.token_address, + token_id=nft_owner.token_id, + balance_of=nft_owner.balance_of, + ) + for nft_owner in self._data_buff[AddressNft1155Holder.type()] + ], + group_by=["address", "token_address", "token_id", "balance_of"], + ) transactions = self._get_domain(Transaction) self._collect_domains(list(transactions_to_address_transactions(transactions))) diff --git a/indexer/modules/user_ops/export_user_ops_job.py b/indexer/modules/user_ops/export_user_ops_job.py index 4f1827b08..fc11eb612 100644 --- a/indexer/modules/user_ops/export_user_ops_job.py +++ b/indexer/modules/user_ops/export_user_ops_job.py @@ -7,7 +7,7 @@ from indexer.domain.log import Log from indexer.domain.transaction import Transaction -from indexer.jobs.filter_transaction_data_job import FilterTransactionDataJob +from indexer.jobs import FilterTransactionDataJob from indexer.modules.user_ops.domain.user_operations import UserOperationsResult from indexer.specification.specification import ( ToAddressSpecification, diff --git a/indexer/tests/domain/test_token_transfers.py b/indexer/tests/domain/test_token_transfers.py index bdfa2e583..f18283743 100644 --- a/indexer/tests/domain/test_token_transfers.py +++ b/indexer/tests/domain/test_token_transfers.py @@ -56,20 +56,20 @@ def test_erc20_token_deposit(): ) token_transfers = extract_transfer_from_log(log) - assert len(token_transfers) == 0 - # token_transfer = token_transfers[0] - - # assert token_transfer.transaction_hash == "0xa997e7b311a972a5a1f6f99bee98eaca3f719c549f2a756e0a74d76ed6061028" - # assert token_transfer.log_index == 29 - # assert token_transfer.from_address == ZERO_ADDRESS - # assert token_transfer.to_address == "0x86d169ffe8f1ac313abea5fa64aad51725ceaf32" - # assert token_transfer.token_id is None - # assert token_transfer.value == 22459469892398894 - # assert token_transfer.token_type == "ERC20" - # assert token_transfer.token_address == "0x6b175474e89094c44da98b954eedeac495271d0f" - # assert token_transfer.block_number == 20425048 - # assert token_transfer.block_hash == "0x6db7768a30446e0a6d00c624d4ec1d17e5eabd8b4cb464396900b967fd9a6058" - # assert token_transfer.block_timestamp == 1722382175 + assert len(token_transfers) == 1 + token_transfer = token_transfers[0] + + assert token_transfer.transaction_hash == "0xa997e7b311a972a5a1f6f99bee98eaca3f719c549f2a756e0a74d76ed6061028" + assert token_transfer.log_index == 29 + assert token_transfer.from_address == ZERO_ADDRESS + assert token_transfer.to_address == "0x86d169ffe8f1ac313abea5fa64aad51725ceaf32" + assert token_transfer.token_id is None + assert token_transfer.value == 22459469892398894 + assert token_transfer.token_type == "ERC20" + assert token_transfer.token_address == "0x6b175474e89094c44da98b954eedeac495271d0f" + assert token_transfer.block_number == 20425048 + assert token_transfer.block_hash == "0x6db7768a30446e0a6d00c624d4ec1d17e5eabd8b4cb464396900b967fd9a6058" + assert token_transfer.block_timestamp == 1722382175 @pytest.mark.indexer @@ -90,20 +90,20 @@ def test_erc20_token_withdraw(): token_transfers = extract_transfer_from_log(log) - assert len(token_transfers) == 0 - # token_transfer = token_transfers[0] - - # assert token_transfer.transaction_hash == "0x01bf14796bb1c6ba3c5fdc599ddad343b04222e252e77d474aa617943bb3d69b" - # assert token_transfer.log_index == 438 - # assert token_transfer.from_address == "0x7a250d5630b4cf539739df2c5dacb4c659f2488d" - # assert token_transfer.to_address == ZERO_ADDRESS - # assert token_transfer.token_id is None - # assert token_transfer.value == 49885153365440229 - # assert token_transfer.token_type == "ERC20" - # assert token_transfer.token_address == "0x6b175474e89094c44da98b954eedeac495271d0f" - # assert token_transfer.block_number == 20425106 - # assert token_transfer.block_hash == "0x4ce31db1374c8b6e3065b03daa9a26d545f7c0c76321e255b9b48ec567ec0f2f" - # assert token_transfer.block_timestamp == 1722411671 + assert len(token_transfers) == 1 + token_transfer = token_transfers[0] + + assert token_transfer.transaction_hash == "0x01bf14796bb1c6ba3c5fdc599ddad343b04222e252e77d474aa617943bb3d69b" + assert token_transfer.log_index == 438 + assert token_transfer.from_address == "0x7a250d5630b4cf539739df2c5dacb4c659f2488d" + assert token_transfer.to_address == ZERO_ADDRESS + assert token_transfer.token_id is None + assert token_transfer.value == 49885153365440229 + assert token_transfer.token_type == "ERC20" + assert token_transfer.token_address == "0x6b175474e89094c44da98b954eedeac495271d0f" + assert token_transfer.block_number == 20425106 + assert token_transfer.block_hash == "0x4ce31db1374c8b6e3065b03daa9a26d545f7c0c76321e255b9b48ec567ec0f2f" + assert token_transfer.block_timestamp == 1722411671 @pytest.mark.indexer diff --git a/indexer/tests/jobs/test_export_token_transfers_job.py b/indexer/tests/jobs/test_export_token_transfers_job.py index de83d256b..3f92d8d43 100644 --- a/indexer/tests/jobs/test_export_token_transfers_job.py +++ b/indexer/tests/jobs/test_export_token_transfers_job.py @@ -1,11 +1,12 @@ import pytest from indexer.controller.scheduler.job_scheduler import JobScheduler -from indexer.domain.token_transfer import ERC721TokenTransfer, ERC1155TokenTransfer +from indexer.domain.token_transfer import ERC20TokenTransfer, ERC721TokenTransfer, ERC1155TokenTransfer from indexer.exporters.console_item_exporter import ConsoleItemExporter -from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL +from indexer.tests import ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL, ETHEREUM_PUBLIC_NODE_RPC_URL, LINEA_PUBLIC_NODE_RPC_URL from indexer.utils.provider import get_provider_from_uri from indexer.utils.thread_local_proxy import ThreadLocalProxy +from indexer.utils.utils import ZERO_ADDRESS @pytest.mark.indexer @@ -32,3 +33,82 @@ def test_export_job(): data_buff = job_scheduler.get_data_buff() job_scheduler.clear_data_buff() + + +@pytest.mark.indexer +@pytest.mark.indexer_exporter +@pytest.mark.serial +def test_export_weth_depoist_transfer_job(): + job_scheduler = JobScheduler( + batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), + batch_web3_debug_provider=ThreadLocalProxy( + lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL, batch=True) + ), + item_exporters=[ConsoleItemExporter()], + batch_size=100, + debug_batch_size=1, + max_workers=5, + config={"export_tokens_and_transfers_job": {"weth_address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"}}, + required_output_types=[ERC20TokenTransfer], + ) + + job_scheduler.run_jobs( + start_block=20757606, + end_block=20757606, + ) + + data_buff = job_scheduler.get_data_buff() + deposit_eth_token_transfers = [ + transfer + for transfer in data_buff[ERC20TokenTransfer.type()] + if transfer.block_number == 20757606 and transfer.log_index == 162 + ] + withdraw_eth_token_transfers = [ + transfer + for transfer in data_buff[ERC20TokenTransfer.type()] + if transfer.block_number == 20757606 and transfer.log_index == 167 + ] + assert len(deposit_eth_token_transfers) == 1 + assert deposit_eth_token_transfers[0].from_address == ZERO_ADDRESS + assert len(withdraw_eth_token_transfers) == 1 + assert withdraw_eth_token_transfers[0].to_address == ZERO_ADDRESS + print(withdraw_eth_token_transfers) + assert len(data_buff[ERC20TokenTransfer.type()]) == 275 + job_scheduler.clear_data_buff() + + +@pytest.mark.indexer +@pytest.mark.indexer_exporter +@pytest.mark.serial +def test_export_weth_depoist_transfer_with_wrong_config_job(): + job_scheduler = JobScheduler( + batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), + batch_web3_debug_provider=ThreadLocalProxy( + lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL, batch=True) + ), + item_exporters=[ConsoleItemExporter()], + batch_size=100, + debug_batch_size=1, + max_workers=5, + config={"export_tokens_and_transfers_job": {"weth_address": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead1111"}}, + required_output_types=[ERC20TokenTransfer], + ) + + job_scheduler.run_jobs( + start_block=20757606, + end_block=20757606, + ) + + data_buff = job_scheduler.get_data_buff() + deposit_eth_token_transfers = [ + transfer + for transfer in data_buff[ERC20TokenTransfer.type()] + if transfer.block_number == 20757606 and transfer.log_index == 162 + ] + withdraw_eth_token_transfers = [ + transfer + for transfer in data_buff[ERC20TokenTransfer.type()] + if transfer.block_number == 20757606 and transfer.log_index == 167 + ] + assert len(deposit_eth_token_transfers) == 0 + assert len(withdraw_eth_token_transfers) == 0 diff --git a/poetry.lock b/poetry.lock index 90c4db327..f10379cab 100644 --- a/poetry.lock +++ b/poetry.lock @@ -13,102 +13,102 @@ files = [ [[package]] name = "aiohttp" -version = "3.10.5" +version = "3.10.6" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:18a01eba2574fb9edd5f6e5fb25f66e6ce061da5dab5db75e13fe1558142e0a3"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:94fac7c6e77ccb1ca91e9eb4cb0ac0270b9fb9b289738654120ba8cebb1189c6"}, - {file = "aiohttp-3.10.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2f1f1c75c395991ce9c94d3e4aa96e5c59c8356a15b1c9231e783865e2772699"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f7acae3cf1a2a2361ec4c8e787eaaa86a94171d2417aae53c0cca6ca3118ff6"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:94c4381ffba9cc508b37d2e536b418d5ea9cfdc2848b9a7fea6aebad4ec6aac1"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c31ad0c0c507894e3eaa843415841995bf8de4d6b2d24c6e33099f4bc9fc0d4f"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0912b8a8fadeb32ff67a3ed44249448c20148397c1ed905d5dac185b4ca547bb"}, - {file = "aiohttp-3.10.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0d93400c18596b7dc4794d48a63fb361b01a0d8eb39f28800dc900c8fbdaca91"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d00f3c5e0d764a5c9aa5a62d99728c56d455310bcc288a79cab10157b3af426f"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d742c36ed44f2798c8d3f4bc511f479b9ceef2b93f348671184139e7d708042c"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:814375093edae5f1cb31e3407997cf3eacefb9010f96df10d64829362ae2df69"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:8224f98be68a84b19f48e0bdc14224b5a71339aff3a27df69989fa47d01296f3"}, - {file = "aiohttp-3.10.5-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d9a487ef090aea982d748b1b0d74fe7c3950b109df967630a20584f9a99c0683"}, - {file = "aiohttp-3.10.5-cp310-cp310-win32.whl", hash = "sha256:d9ef084e3dc690ad50137cc05831c52b6ca428096e6deb3c43e95827f531d5ef"}, - {file = "aiohttp-3.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:66bf9234e08fe561dccd62083bf67400bdbf1c67ba9efdc3dac03650e97c6088"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8c6a4e5e40156d72a40241a25cc226051c0a8d816610097a8e8f517aeacd59a2"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c634a3207a5445be65536d38c13791904fda0748b9eabf908d3fe86a52941cf"}, - {file = "aiohttp-3.10.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4aff049b5e629ef9b3e9e617fa6e2dfeda1bf87e01bcfecaf3949af9e210105e"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1942244f00baaacaa8155eca94dbd9e8cc7017deb69b75ef67c78e89fdad3c77"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e04a1f2a65ad2f93aa20f9ff9f1b672bf912413e5547f60749fa2ef8a644e061"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7f2bfc0032a00405d4af2ba27f3c429e851d04fad1e5ceee4080a1c570476697"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:424ae21498790e12eb759040bbb504e5e280cab64693d14775c54269fd1d2bb7"}, - {file = "aiohttp-3.10.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:975218eee0e6d24eb336d0328c768ebc5d617609affaca5dbbd6dd1984f16ed0"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:4120d7fefa1e2d8fb6f650b11489710091788de554e2b6f8347c7a20ceb003f5"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b90078989ef3fc45cf9221d3859acd1108af7560c52397ff4ace8ad7052a132e"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ba5a8b74c2a8af7d862399cdedce1533642fa727def0b8c3e3e02fcb52dca1b1"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:02594361128f780eecc2a29939d9dfc870e17b45178a867bf61a11b2a4367277"}, - {file = "aiohttp-3.10.5-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8fb4fc029e135859f533025bc82047334e24b0d489e75513144f25408ecaf058"}, - {file = "aiohttp-3.10.5-cp311-cp311-win32.whl", hash = "sha256:e1ca1ef5ba129718a8fc827b0867f6aa4e893c56eb00003b7367f8a733a9b072"}, - {file = "aiohttp-3.10.5-cp311-cp311-win_amd64.whl", hash = "sha256:349ef8a73a7c5665cca65c88ab24abe75447e28aa3bc4c93ea5093474dfdf0ff"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:305be5ff2081fa1d283a76113b8df7a14c10d75602a38d9f012935df20731487"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3a1c32a19ee6bbde02f1cb189e13a71b321256cc1d431196a9f824050b160d5a"}, - {file = "aiohttp-3.10.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:61645818edd40cc6f455b851277a21bf420ce347baa0b86eaa41d51ef58ba23d"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c225286f2b13bab5987425558baa5cbdb2bc925b2998038fa028245ef421e75"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ba01ebc6175e1e6b7275c907a3a36be48a2d487549b656aa90c8a910d9f3178"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8eaf44ccbc4e35762683078b72bf293f476561d8b68ec8a64f98cf32811c323e"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1c43eb1ab7cbf411b8e387dc169acb31f0ca0d8c09ba63f9eac67829585b44f"}, - {file = "aiohttp-3.10.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de7a5299827253023c55ea549444e058c0eb496931fa05d693b95140a947cb73"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4790f0e15f00058f7599dab2b206d3049d7ac464dc2e5eae0e93fa18aee9e7bf"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:44b324a6b8376a23e6ba25d368726ee3bc281e6ab306db80b5819999c737d820"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:0d277cfb304118079e7044aad0b76685d30ecb86f83a0711fc5fb257ffe832ca"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:54d9ddea424cd19d3ff6128601a4a4d23d54a421f9b4c0fff740505813739a91"}, - {file = "aiohttp-3.10.5-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4f1c9866ccf48a6df2b06823e6ae80573529f2af3a0992ec4fe75b1a510df8a6"}, - {file = "aiohttp-3.10.5-cp312-cp312-win32.whl", hash = "sha256:dc4826823121783dccc0871e3f405417ac116055bf184ac04c36f98b75aacd12"}, - {file = "aiohttp-3.10.5-cp312-cp312-win_amd64.whl", hash = "sha256:22c0a23a3b3138a6bf76fc553789cb1a703836da86b0f306b6f0dc1617398abc"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7f6b639c36734eaa80a6c152a238242bedcee9b953f23bb887e9102976343092"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f29930bc2921cef955ba39a3ff87d2c4398a0394ae217f41cb02d5c26c8b1b77"}, - {file = "aiohttp-3.10.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f489a2c9e6455d87eabf907ac0b7d230a9786be43fbe884ad184ddf9e9c1e385"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:123dd5b16b75b2962d0fff566effb7a065e33cd4538c1692fb31c3bda2bfb972"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b98e698dc34966e5976e10bbca6d26d6724e6bdea853c7c10162a3235aba6e16"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3b9162bab7e42f21243effc822652dc5bb5e8ff42a4eb62fe7782bcbcdfacf6"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1923a5c44061bffd5eebeef58cecf68096e35003907d8201a4d0d6f6e387ccaa"}, - {file = "aiohttp-3.10.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d55f011da0a843c3d3df2c2cf4e537b8070a419f891c930245f05d329c4b0689"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:afe16a84498441d05e9189a15900640a2d2b5e76cf4efe8cbb088ab4f112ee57"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:f8112fb501b1e0567a1251a2fd0747baae60a4ab325a871e975b7bb67e59221f"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:1e72589da4c90337837fdfe2026ae1952c0f4a6e793adbbfbdd40efed7c63599"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4d46c7b4173415d8e583045fbc4daa48b40e31b19ce595b8d92cf639396c15d5"}, - {file = "aiohttp-3.10.5-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:33e6bc4bab477c772a541f76cd91e11ccb6d2efa2b8d7d7883591dfb523e5987"}, - {file = "aiohttp-3.10.5-cp313-cp313-win32.whl", hash = "sha256:c58c6837a2c2a7cf3133983e64173aec11f9c2cd8e87ec2fdc16ce727bcf1a04"}, - {file = "aiohttp-3.10.5-cp313-cp313-win_amd64.whl", hash = "sha256:38172a70005252b6893088c0f5e8a47d173df7cc2b2bd88650957eb84fcf5022"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:f6f18898ace4bcd2d41a122916475344a87f1dfdec626ecde9ee802a711bc569"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5ede29d91a40ba22ac1b922ef510aab871652f6c88ef60b9dcdf773c6d32ad7a"}, - {file = "aiohttp-3.10.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:673f988370f5954df96cc31fd99c7312a3af0a97f09e407399f61583f30da9bc"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58718e181c56a3c02d25b09d4115eb02aafe1a732ce5714ab70326d9776457c3"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4b38b1570242fbab8d86a84128fb5b5234a2f70c2e32f3070143a6d94bc854cf"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:074d1bff0163e107e97bd48cad9f928fa5a3eb4b9d33366137ffce08a63e37fe"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd31f176429cecbc1ba499d4aba31aaccfea488f418d60376b911269d3b883c5"}, - {file = "aiohttp-3.10.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7384d0b87d4635ec38db9263e6a3f1eb609e2e06087f0aa7f63b76833737b471"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8989f46f3d7ef79585e98fa991e6ded55d2f48ae56d2c9fa5e491a6e4effb589"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:c83f7a107abb89a227d6c454c613e7606c12a42b9a4ca9c5d7dad25d47c776ae"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:cde98f323d6bf161041e7627a5fd763f9fd829bcfcd089804a5fdce7bb6e1b7d"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:676f94c5480d8eefd97c0c7e3953315e4d8c2b71f3b49539beb2aa676c58272f"}, - {file = "aiohttp-3.10.5-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:2d21ac12dc943c68135ff858c3a989f2194a709e6e10b4c8977d7fcd67dfd511"}, - {file = "aiohttp-3.10.5-cp38-cp38-win32.whl", hash = "sha256:17e997105bd1a260850272bfb50e2a328e029c941c2708170d9d978d5a30ad9a"}, - {file = "aiohttp-3.10.5-cp38-cp38-win_amd64.whl", hash = "sha256:1c19de68896747a2aa6257ae4cf6ef59d73917a36a35ee9d0a6f48cff0f94db8"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7e2fe37ac654032db1f3499fe56e77190282534810e2a8e833141a021faaab0e"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f5bf3ead3cb66ab990ee2561373b009db5bc0e857549b6c9ba84b20bc462e172"}, - {file = "aiohttp-3.10.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1b2c16a919d936ca87a3c5f0e43af12a89a3ce7ccbce59a2d6784caba945b68b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad146dae5977c4dd435eb31373b3fe9b0b1bf26858c6fc452bf6af394067e10b"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8c5c6fa16412b35999320f5c9690c0f554392dc222c04e559217e0f9ae244b92"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:95c4dc6f61d610bc0ee1edc6f29d993f10febfe5b76bb470b486d90bbece6b22"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da452c2c322e9ce0cfef392e469a26d63d42860f829026a63374fde6b5c5876f"}, - {file = "aiohttp-3.10.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:898715cf566ec2869d5cb4d5fb4be408964704c46c96b4be267442d265390f32"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:391cc3a9c1527e424c6865e087897e766a917f15dddb360174a70467572ac6ce"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:380f926b51b92d02a34119d072f178d80bbda334d1a7e10fa22d467a66e494db"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce91db90dbf37bb6fa0997f26574107e1b9d5ff939315247b7e615baa8ec313b"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9093a81e18c45227eebe4c16124ebf3e0d893830c6aca7cc310bfca8fe59d857"}, - {file = "aiohttp-3.10.5-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:ee40b40aa753d844162dcc80d0fe256b87cba48ca0054f64e68000453caead11"}, - {file = "aiohttp-3.10.5-cp39-cp39-win32.whl", hash = "sha256:03f2645adbe17f274444953bdea69f8327e9d278d961d85657cb0d06864814c1"}, - {file = "aiohttp-3.10.5-cp39-cp39-win_amd64.whl", hash = "sha256:d17920f18e6ee090bdd3d0bfffd769d9f2cb4c8ffde3eb203777a3895c128862"}, - {file = "aiohttp-3.10.5.tar.gz", hash = "sha256:f071854b47d39591ce9a17981c46790acb30518e2f83dfca8db2dfa091178691"}, + {file = "aiohttp-3.10.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:682836fc672972cc3101cc9e30d49c5f7e8f1d010478d46119fe725a4545acfd"}, + {file = "aiohttp-3.10.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:289fa8a20018d0d5aa9e4b35d899bd51bcb80f0d5f365d9a23e30dac3b79159b"}, + {file = "aiohttp-3.10.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8617c96a20dd57e7e9d398ff9d04f3d11c4d28b1767273a5b1a018ada5a654d3"}, + {file = "aiohttp-3.10.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bdbeff1b062751c2a2a55b171f7050fb7073633c699299d042e962aacdbe1a07"}, + {file = "aiohttp-3.10.6-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7ea35d849cdd4a9268f910bff4497baebbc1aa3f2f625fd8ccd9ac99c860c621"}, + {file = "aiohttp-3.10.6-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473961b3252f3b949bb84873d6e268fb6d8aa0ccc6eb7404fa58c76a326bb8e1"}, + {file = "aiohttp-3.10.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d2665c5df629eb2f981dab244c01bfa6cdc185f4ffa026639286c4d56fafb54"}, + {file = "aiohttp-3.10.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:25d92f794f1332f656e3765841fc2b7ad5c26c3f3d01e8949eeb3495691cf9f4"}, + {file = "aiohttp-3.10.6-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9bd6b2033993d5ae80883bb29b83fb2b432270bbe067c2f53cc73bb57c46065f"}, + {file = "aiohttp-3.10.6-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:d7f408c43f5e75ea1edc152fb375e8f46ef916f545fb66d4aebcbcfad05e2796"}, + {file = "aiohttp-3.10.6-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:cf8b8560aa965f87bf9c13bf9fed7025993a155ca0ce8422da74bf46d18c2f5f"}, + {file = "aiohttp-3.10.6-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14477c4e52e2f17437b99893fd220ffe7d7ee41df5ebf931a92b8ca82e6fd094"}, + {file = "aiohttp-3.10.6-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:fb138fbf9f53928e779650f5ed26d0ea1ed8b2cab67f0ea5d63afa09fdc07593"}, + {file = "aiohttp-3.10.6-cp310-cp310-win32.whl", hash = "sha256:9843d683b8756971797be171ead21511d2215a2d6e3c899c6e3107fbbe826791"}, + {file = "aiohttp-3.10.6-cp310-cp310-win_amd64.whl", hash = "sha256:f8b8e49fe02f744d38352daca1dbef462c3874900bd8166516f6ea8e82b5aacf"}, + {file = "aiohttp-3.10.6-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f52e54fd776ad0da1006708762213b079b154644db54bcfc62f06eaa5b896402"}, + {file = "aiohttp-3.10.6-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:995ab1a238fd0d19dc65f2d222e5eb064e409665c6426a3e51d5101c1979ee84"}, + {file = "aiohttp-3.10.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0749c4d5a08a802dd66ecdf59b2df4d76b900004017468a7bb736c3b5a3dd902"}, + {file = "aiohttp-3.10.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e05b39158f2af0e2438cc2075cfc271f4ace0c3cc4a81ec95b27a0432e161951"}, + {file = "aiohttp-3.10.6-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a9f196c970db2dcde4f24317e06615363349dc357cf4d7a3b0716c20ac6d7bcd"}, + {file = "aiohttp-3.10.6-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:47647c8af04a70e07a2462931b0eba63146a13affa697afb4ecbab9d03a480ce"}, + {file = "aiohttp-3.10.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:669c0efe7e99f6d94d63274c06344bd0e9c8daf184ce5602a29bc39e00a18720"}, + {file = "aiohttp-3.10.6-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9721cdd83a994225352ca84cd537760d41a9da3c0eacb3ff534747ab8fba6d0"}, + {file = "aiohttp-3.10.6-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:0b82c8ebed66ce182893e7c0b6b60ba2ace45b1df104feb52380edae266a4850"}, + {file = "aiohttp-3.10.6-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:b169f8e755e541b72e714b89a831b315bbe70db44e33fead28516c9e13d5f931"}, + {file = "aiohttp-3.10.6-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:0be3115753baf8b4153e64f9aa7bf6c0c64af57979aa900c31f496301b374570"}, + {file = "aiohttp-3.10.6-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:e1f80cd17d81a404b6e70ef22bfe1870bafc511728397634ad5f5efc8698df56"}, + {file = "aiohttp-3.10.6-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6419728b08fb6380c66a470d2319cafcec554c81780e2114b7e150329b9a9a7f"}, + {file = "aiohttp-3.10.6-cp311-cp311-win32.whl", hash = "sha256:bd294dcdc1afdc510bb51d35444003f14e327572877d016d576ac3b9a5888a27"}, + {file = "aiohttp-3.10.6-cp311-cp311-win_amd64.whl", hash = "sha256:bf861da9a43d282d6dd9dcd64c23a0fccf2c5aa5cd7c32024513c8c79fb69de3"}, + {file = "aiohttp-3.10.6-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:2708baccdc62f4b1251e59c2aac725936a900081f079b88843dabcab0feeeb27"}, + {file = "aiohttp-3.10.6-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7475da7a5e2ccf1a1c86c8fee241e277f4874c96564d06f726d8df8e77683ef7"}, + {file = "aiohttp-3.10.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02108326574ff60267b7b35b17ac5c0bbd0008ccb942ce4c48b657bb90f0b8aa"}, + {file = "aiohttp-3.10.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:029a019627b37fa9eac5c75cc54a6bb722c4ebbf5a54d8c8c0fb4dd8facf2702"}, + {file = "aiohttp-3.10.6-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a637d387db6fdad95e293fab5433b775fd104ae6348d2388beaaa60d08b38c4"}, + {file = "aiohttp-3.10.6-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dc1a16f3fc1944c61290d33c88dc3f09ba62d159b284c38c5331868425aca426"}, + {file = "aiohttp-3.10.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:81b292f37969f9cc54f4643f0be7dacabf3612b3b4a65413661cf6c350226787"}, + {file = "aiohttp-3.10.6-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0754690a3a26e819173a34093798c155bafb21c3c640bff13be1afa1e9d421f9"}, + {file = "aiohttp-3.10.6-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:164ecd32e65467d86843dbb121a6666c3deb23b460e3f8aefdcaacae79eb718a"}, + {file = "aiohttp-3.10.6-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438c5863feb761f7ca3270d48c292c334814459f61cc12bab5ba5b702d7c9e56"}, + {file = "aiohttp-3.10.6-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:ba18573bb1de1063d222f41de64a0d3741223982dcea863b3f74646faf618ec7"}, + {file = "aiohttp-3.10.6-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:c82a94ddec996413a905f622f3da02c4359952aab8d817c01cf9915419525e95"}, + {file = "aiohttp-3.10.6-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:92351aa5363fc3c1f872ca763f86730ced32b01607f0c9662b1fa711087968d0"}, + {file = "aiohttp-3.10.6-cp312-cp312-win32.whl", hash = "sha256:3e15e33bfc73fa97c228f72e05e8795e163a693fd5323549f49367c76a6e5883"}, + {file = "aiohttp-3.10.6-cp312-cp312-win_amd64.whl", hash = "sha256:fe517113fe4d35d9072b826c3e147d63c5f808ca8167d450b4f96c520c8a1d8d"}, + {file = "aiohttp-3.10.6-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:482f74057ea13d387a7549d7a7ecb60e45146d15f3e58a2d93a0ad2d5a8457cd"}, + {file = "aiohttp-3.10.6-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:03fa40d1450ee5196e843315ddf74a51afc7e83d489dbfc380eecefea74158b1"}, + {file = "aiohttp-3.10.6-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e52e59ed5f4cc3a3acfe2a610f8891f216f486de54d95d6600a2c9ba1581f4d"}, + {file = "aiohttp-3.10.6-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2b3935a22c9e41a8000d90588bed96cf395ef572dbb409be44c6219c61d900d"}, + {file = "aiohttp-3.10.6-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bef1480ee50f75abcfcb4b11c12de1005968ca9d0172aec4a5057ba9f2b644f"}, + {file = "aiohttp-3.10.6-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:671745ea7db19693ce867359d503772177f0b20fa8f6ee1e74e00449f4c4151d"}, + {file = "aiohttp-3.10.6-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b50b367308ca8c12e0b50cba5773bc9abe64c428d3fd2bbf5cd25aab37c77bf"}, + {file = "aiohttp-3.10.6-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a504d7cdb431a777d05a124fd0b21efb94498efa743103ea01b1e3136d2e4fb"}, + {file = "aiohttp-3.10.6-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:66bc81361131763660b969132a22edce2c4d184978ba39614e8f8f95db5c95f8"}, + {file = "aiohttp-3.10.6-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:27cf19a38506e2e9f12fc17e55f118f04897b0a78537055d93a9de4bf3022e3d"}, + {file = "aiohttp-3.10.6-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3468b39f977a11271517c6925b226720e148311039a380cc9117b1e2258a721f"}, + {file = "aiohttp-3.10.6-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:9d26da22a793dfd424be1050712a70c0afd96345245c29aced1e35dbace03413"}, + {file = "aiohttp-3.10.6-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:844d48ff9173d0b941abed8b2ea6a412f82b56d9ab1edb918c74000c15839362"}, + {file = "aiohttp-3.10.6-cp313-cp313-win32.whl", hash = "sha256:2dd56e3c43660ed3bea67fd4c5025f1ac1f9ecf6f0b991a6e5efe2e678c490c5"}, + {file = "aiohttp-3.10.6-cp313-cp313-win_amd64.whl", hash = "sha256:c91781d969fbced1993537f45efe1213bd6fccb4b37bfae2a026e20d6fbed206"}, + {file = "aiohttp-3.10.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4407a80bca3e694f2d2a523058e20e1f9f98a416619e04f6dc09dc910352ac8b"}, + {file = "aiohttp-3.10.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1cb045ec5961f51af3e2c08cd6fe523f07cc6e345033adee711c49b7b91bb954"}, + {file = "aiohttp-3.10.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4fabdcdc781a36b8fd7b2ca9dea8172f29a99e11d00ca0f83ffeb50958da84a1"}, + {file = "aiohttp-3.10.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79a9f42efcc2681790595ab3d03c0e52d01edc23a0973ea09f0dc8d295e12b8e"}, + {file = "aiohttp-3.10.6-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cca776a440795db437d82c07455761c85bbcf3956221c3c23b8c93176c278ce7"}, + {file = "aiohttp-3.10.6-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5582de171f0898139cf51dd9fcdc79b848e28d9abd68e837f0803fc9f30807b1"}, + {file = "aiohttp-3.10.6-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:370e2d47575c53c817ee42a18acc34aad8da4dbdaac0a6c836d58878955f1477"}, + {file = "aiohttp-3.10.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:444d1704e2af6b30766debed9be8a795958029e552fe77551355badb1944012c"}, + {file = "aiohttp-3.10.6-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:40271a2a375812967401c9ca8077de9368e09a43a964f4dce0ff603301ec9358"}, + {file = "aiohttp-3.10.6-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f3af26f86863fad12e25395805bb0babbd49d512806af91ec9708a272b696248"}, + {file = "aiohttp-3.10.6-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4752df44df48fd42b80f51d6a97553b482cda1274d9dc5df214a3a1aa5d8f018"}, + {file = "aiohttp-3.10.6-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:2cd5290ab66cfca2f90045db2cc6434c1f4f9fbf97c9f1c316e785033782e7d2"}, + {file = "aiohttp-3.10.6-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:3427031064b0d5c95647e6369c4aa3c556402f324a3e18107cb09517abe5f962"}, + {file = "aiohttp-3.10.6-cp38-cp38-win32.whl", hash = "sha256:614fc21e86adc28e4165a6391f851a6da6e9cbd7bb232d0df7718b453a89ee98"}, + {file = "aiohttp-3.10.6-cp38-cp38-win_amd64.whl", hash = "sha256:58c5d7318a136a3874c78717dd6de57519bc64f6363c5827c2b1cb775bea71dd"}, + {file = "aiohttp-3.10.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5db26bbca8e7968c4c977a0c640e0b9ce7224e1f4dcafa57870dc6ee28e27de6"}, + {file = "aiohttp-3.10.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3fb4216e3ec0dbc01db5ba802f02ed78ad8f07121be54eb9e918448cc3f61b7c"}, + {file = "aiohttp-3.10.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a976ef488f26e224079deb3d424f29144c6d5ba4ded313198169a8af8f47fb82"}, + {file = "aiohttp-3.10.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a86610174de8a85a920e956e2d4f9945e7da89f29a00e95ac62a4a414c4ef4e"}, + {file = "aiohttp-3.10.6-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:217791c6a399cc4f2e6577bb44344cba1f5714a2aebf6a0bea04cfa956658284"}, + {file = "aiohttp-3.10.6-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ba3662d41abe2eab0eeec7ee56f33ef4e0b34858f38abf24377687f9e1fb00a5"}, + {file = "aiohttp-3.10.6-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d4dfa5ad4bce9ca30a76117fbaa1c1decf41ebb6c18a4e098df44298941566f9"}, + {file = "aiohttp-3.10.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0009258e97502936d3bd5bf2ced15769629097d0abb81e6495fba1047824fe0"}, + {file = "aiohttp-3.10.6-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0a75d5c9fb4f06c41d029ae70ad943c3a844c40c0a769d12be4b99b04f473d3d"}, + {file = "aiohttp-3.10.6-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8198b7c002aae2b40b2d16bfe724b9a90bcbc9b78b2566fc96131ef4e382574d"}, + {file = "aiohttp-3.10.6-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:4611db8c907f90fe86be112efdc2398cd7b4c8eeded5a4f0314b70fdea8feab0"}, + {file = "aiohttp-3.10.6-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ff99ae06eef85c7a565854826114ced72765832ee16c7e3e766c5e4c5b98d20e"}, + {file = "aiohttp-3.10.6-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7641920bdcc7cd2d3ddfb8bb9133a6c9536b09dbd49490b79e125180b2d25b93"}, + {file = "aiohttp-3.10.6-cp39-cp39-win32.whl", hash = "sha256:e2e7d5591ea868d5ec82b90bbeb366a198715672841d46281b623e23079593db"}, + {file = "aiohttp-3.10.6-cp39-cp39-win_amd64.whl", hash = "sha256:b504c08c45623bf5c7ca41be380156d925f00199b3970efd758aef4a77645feb"}, + {file = "aiohttp-3.10.6.tar.gz", hash = "sha256:d2578ef941be0c2ba58f6f421a703527d08427237ed45ecb091fed6f83305336"}, ] [package.dependencies] @@ -118,7 +118,7 @@ async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} attrs = ">=17.3.0" frozenlist = ">=1.1.1" multidict = ">=4.5,<7.0" -yarl = ">=1.0,<2.0" +yarl = ">=1.12.0,<2.0" [package.extras] speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] @@ -139,13 +139,13 @@ frozenlist = ">=1.1.0" [[package]] name = "alembic" -version = "1.13.2" +version = "1.13.3" description = "A database migration tool for SQLAlchemy." optional = false python-versions = ">=3.8" files = [ - {file = "alembic-1.13.2-py3-none-any.whl", hash = "sha256:6b8733129a6224a9a711e17c99b08462dbf7cc9670ba8f2e2ae9af860ceb1953"}, - {file = "alembic-1.13.2.tar.gz", hash = "sha256:1ff0ae32975f4fd96028c39ed9bb3c867fe3af956bd7bb37343b54c9fe7445ef"}, + {file = "alembic-1.13.3-py3-none-any.whl", hash = "sha256:908e905976d15235fae59c9ac42c4c5b75cfcefe3d27c0fbf7ae15a37715d80e"}, + {file = "alembic-1.13.3.tar.gz", hash = "sha256:203503117415561e203aa14541740643a611f641517f0209fcae63e9fa09f1a2"}, ] [package.dependencies] @@ -1232,77 +1232,84 @@ files = [ [[package]] name = "greenlet" -version = "3.1.0" +version = "3.1.1" description = "Lightweight in-process concurrent programming" optional = false python-versions = ">=3.7" files = [ - {file = "greenlet-3.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a814dc3100e8a046ff48faeaa909e80cdb358411a3d6dd5293158425c684eda8"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a771dc64fa44ebe58d65768d869fcfb9060169d203446c1d446e844b62bdfdca"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0e49a65d25d7350cca2da15aac31b6f67a43d867448babf997fe83c7505f57bc"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2cd8518eade968bc52262d8c46727cfc0826ff4d552cf0430b8d65aaf50bb91d"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76dc19e660baea5c38e949455c1181bc018893f25372d10ffe24b3ed7341fb25"}, - {file = "greenlet-3.1.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c0a5b1c22c82831f56f2f7ad9bbe4948879762fe0d59833a4a71f16e5fa0f682"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2651dfb006f391bcb240635079a68a261b227a10a08af6349cba834a2141efa1"}, - {file = "greenlet-3.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:3e7e6ef1737a819819b1163116ad4b48d06cfdd40352d813bb14436024fcda99"}, - {file = "greenlet-3.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:ffb08f2a1e59d38c7b8b9ac8083c9c8b9875f0955b1e9b9b9a965607a51f8e54"}, - {file = "greenlet-3.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9730929375021ec90f6447bff4f7f5508faef1c02f399a1953870cdb78e0c345"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:713d450cf8e61854de9420fb7eea8ad228df4e27e7d4ed465de98c955d2b3fa6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4c3446937be153718250fe421da548f973124189f18fe4575a0510b5c928f0cc"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ddc7bcedeb47187be74208bc652d63d6b20cb24f4e596bd356092d8000da6d6"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44151d7b81b9391ed759a2f2865bbe623ef00d648fed59363be2bbbd5154656f"}, - {file = "greenlet-3.1.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6cea1cca3be76c9483282dc7760ea1cc08a6ecec1f0b6ca0a94ea0d17432da19"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:619935a44f414274a2c08c9e74611965650b730eb4efe4b2270f91df5e4adf9a"}, - {file = "greenlet-3.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:221169d31cada333a0c7fd087b957c8f431c1dba202c3a58cf5a3583ed973e9b"}, - {file = "greenlet-3.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:01059afb9b178606b4b6e92c3e710ea1635597c3537e44da69f4531e111dd5e9"}, - {file = "greenlet-3.1.0-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:24fc216ec7c8be9becba8b64a98a78f9cd057fd2dc75ae952ca94ed8a893bf27"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d07c28b85b350564bdff9f51c1c5007dfb2f389385d1bc23288de51134ca303"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:243a223c96a4246f8a30ea470c440fe9db1f5e444941ee3c3cd79df119b8eebf"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:26811df4dc81271033a7836bc20d12cd30938e6bd2e9437f56fa03da81b0f8fc"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9d86401550b09a55410f32ceb5fe7efcd998bd2dad9e82521713cb148a4a15f"}, - {file = "greenlet-3.1.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:26d9c1c4f1748ccac0bae1dbb465fb1a795a75aba8af8ca871503019f4285e2a"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:cd468ec62257bb4544989402b19d795d2305eccb06cde5da0eb739b63dc04665"}, - {file = "greenlet-3.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a53dfe8f82b715319e9953330fa5c8708b610d48b5c59f1316337302af5c0811"}, - {file = "greenlet-3.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:28fe80a3eb673b2d5cc3b12eea468a5e5f4603c26aa34d88bf61bba82ceb2f9b"}, - {file = "greenlet-3.1.0-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:76b3e3976d2a452cba7aa9e453498ac72240d43030fdc6d538a72b87eaff52fd"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:655b21ffd37a96b1e78cc48bf254f5ea4b5b85efaf9e9e2a526b3c9309d660ca"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c6f4c2027689093775fd58ca2388d58789009116844432d920e9147f91acbe64"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:76e5064fd8e94c3f74d9fd69b02d99e3cdb8fc286ed49a1f10b256e59d0d3a0b"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a4bf607f690f7987ab3291406e012cd8591a4f77aa54f29b890f9c331e84989"}, - {file = "greenlet-3.1.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:037d9ac99540ace9424cb9ea89f0accfaff4316f149520b4ae293eebc5bded17"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:90b5bbf05fe3d3ef697103850c2ce3374558f6fe40fd57c9fac1bf14903f50a5"}, - {file = "greenlet-3.1.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:726377bd60081172685c0ff46afbc600d064f01053190e4450857483c4d44484"}, - {file = "greenlet-3.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:d46d5069e2eeda111d6f71970e341f4bd9aeeee92074e649ae263b834286ecc0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:81eeec4403a7d7684b5812a8aaa626fa23b7d0848edb3a28d2eb3220daddcbd0"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4a3dae7492d16e85ea6045fd11cb8e782b63eac8c8d520c3a92c02ac4573b0a6"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4b5ea3664eed571779403858d7cd0a9b0ebf50d57d2cdeafc7748e09ef8cd81a"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22f4e26400f7f48faef2d69c20dc055a1f3043d330923f9abe08ea0aecc44df"}, - {file = "greenlet-3.1.0-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:13ff8c8e54a10472ce3b2a2da007f915175192f18e6495bad50486e87c7f6637"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9671e7282d8c6fcabc32c0fb8d7c0ea8894ae85cee89c9aadc2d7129e1a9954"}, - {file = "greenlet-3.1.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:184258372ae9e1e9bddce6f187967f2e08ecd16906557c4320e3ba88a93438c3"}, - {file = "greenlet-3.1.0-cp37-cp37m-win32.whl", hash = "sha256:a0409bc18a9f85321399c29baf93545152d74a49d92f2f55302f122007cfda00"}, - {file = "greenlet-3.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:9eb4a1d7399b9f3c7ac68ae6baa6be5f9195d1d08c9ddc45ad559aa6b556bce6"}, - {file = "greenlet-3.1.0-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:a8870983af660798dc1b529e1fd6f1cefd94e45135a32e58bd70edd694540f33"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfcfb73aed40f550a57ea904629bdaf2e562c68fa1164fa4588e752af6efdc3f"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f9482c2ed414781c0af0b35d9d575226da6b728bd1a720668fa05837184965b7"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d58ec349e0c2c0bc6669bf2cd4982d2f93bf067860d23a0ea1fe677b0f0b1e09"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd65695a8df1233309b701dec2539cc4b11e97d4fcc0f4185b4a12ce54db0491"}, - {file = "greenlet-3.1.0-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:665b21e95bc0fce5cab03b2e1d90ba9c66c510f1bb5fdc864f3a377d0f553f6b"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3c59a06c2c28a81a026ff11fbf012081ea34fb9b7052f2ed0366e14896f0a1d"}, - {file = "greenlet-3.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415b9494ff6240b09af06b91a375731febe0090218e2898d2b85f9b92abcda0"}, - {file = "greenlet-3.1.0-cp38-cp38-win32.whl", hash = "sha256:1544b8dd090b494c55e60c4ff46e238be44fdc472d2589e943c241e0169bcea2"}, - {file = "greenlet-3.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:7f346d24d74c00b6730440f5eb8ec3fe5774ca8d1c9574e8e57c8671bb51b910"}, - {file = "greenlet-3.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:db1b3ccb93488328c74e97ff888604a8b95ae4f35f4f56677ca57a4fc3a4220b"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:44cd313629ded43bb3b98737bba2f3e2c2c8679b55ea29ed73daea6b755fe8e7"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fad7a051e07f64e297e6e8399b4d6a3bdcad3d7297409e9a06ef8cbccff4f501"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c3967dcc1cd2ea61b08b0b276659242cbce5caca39e7cbc02408222fb9e6ff39"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d45b75b0f3fd8d99f62eb7908cfa6d727b7ed190737dec7fe46d993da550b81a"}, - {file = "greenlet-3.1.0-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d004db911ed7b6218ec5c5bfe4cf70ae8aa2223dffbb5b3c69e342bb253cb28"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9505a0c8579899057cbefd4ec34d865ab99852baf1ff33a9481eb3924e2da0b"}, - {file = "greenlet-3.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fd6e94593f6f9714dbad1aaba734b5ec04593374fa6638df61592055868f8b8"}, - {file = "greenlet-3.1.0-cp39-cp39-win32.whl", hash = "sha256:d0dd943282231480aad5f50f89bdf26690c995e8ff555f26d8a5b9887b559bcc"}, - {file = "greenlet-3.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:ac0adfdb3a21dc2a24ed728b61e72440d297d0fd3a577389df566651fcd08f97"}, - {file = "greenlet-3.1.0.tar.gz", hash = "sha256:b395121e9bbe8d02a750886f108d540abe66075e61e22f7353d9acb0b81be0f0"}, + {file = "greenlet-3.1.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:0bbae94a29c9e5c7e4a2b7f0aae5c17e8e90acbfd3bf6270eeba60c39fce3563"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0fde093fb93f35ca72a556cf72c92ea3ebfda3d79fc35bb19fbe685853869a83"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:36b89d13c49216cadb828db8dfa6ce86bbbc476a82d3a6c397f0efae0525bdd0"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94b6150a85e1b33b40b1464a3f9988dcc5251d6ed06842abff82e42632fac120"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93147c513fac16385d1036b7e5b102c7fbbdb163d556b791f0f11eada7ba65dc"}, + {file = "greenlet-3.1.1-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da7a9bff22ce038e19bf62c4dd1ec8391062878710ded0a845bcf47cc0200617"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b2795058c23988728eec1f36a4e5e4ebad22f8320c85f3587b539b9ac84128d7"}, + {file = "greenlet-3.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ed10eac5830befbdd0c32f83e8aa6288361597550ba669b04c48f0f9a2c843c6"}, + {file = "greenlet-3.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:77c386de38a60d1dfb8e55b8c1101d68c79dfdd25c7095d51fec2dd800892b80"}, + {file = "greenlet-3.1.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:e4d333e558953648ca09d64f13e6d8f0523fa705f51cae3f03b5983489958c70"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fc016b73c94e98e29af67ab7b9a879c307c6731a2c9da0db5a7d9b7edd1159"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d5e975ca70269d66d17dd995dafc06f1b06e8cb1ec1e9ed54c1d1e4a7c4cf26e"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3b2813dc3de8c1ee3f924e4d4227999285fd335d1bcc0d2be6dc3f1f6a318ec1"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e347b3bfcf985a05e8c0b7d462ba6f15b1ee1c909e2dcad795e49e91b152c383"}, + {file = "greenlet-3.1.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9e8f8c9cb53cdac7ba9793c276acd90168f416b9ce36799b9b885790f8ad6c0a"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:62ee94988d6b4722ce0028644418d93a52429e977d742ca2ccbe1c4f4a792511"}, + {file = "greenlet-3.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1776fd7f989fc6b8d8c8cb8da1f6b82c5814957264d1f6cf818d475ec2bf6395"}, + {file = "greenlet-3.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:48ca08c771c268a768087b408658e216133aecd835c0ded47ce955381105ba39"}, + {file = "greenlet-3.1.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:4afe7ea89de619adc868e087b4d2359282058479d7cfb94970adf4b55284574d"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f406b22b7c9a9b4f8aa9d2ab13d6ae0ac3e85c9a809bd590ad53fed2bf70dc79"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3a701fe5a9695b238503ce5bbe8218e03c3bcccf7e204e455e7462d770268aa"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2846930c65b47d70b9d178e89c7e1a69c95c1f68ea5aa0a58646b7a96df12441"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99cfaa2110534e2cf3ba31a7abcac9d328d1d9f1b95beede58294a60348fba36"}, + {file = "greenlet-3.1.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1443279c19fca463fc33e65ef2a935a5b09bb90f978beab37729e1c3c6c25fe9"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b7cede291382a78f7bb5f04a529cb18e068dd29e0fb27376074b6d0317bf4dd0"}, + {file = "greenlet-3.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:23f20bb60ae298d7d8656c6ec6db134bca379ecefadb0b19ce6f19d1f232a942"}, + {file = "greenlet-3.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:7124e16b4c55d417577c2077be379514321916d5790fa287c9ed6f23bd2ffd01"}, + {file = "greenlet-3.1.1-cp313-cp313-macosx_11_0_universal2.whl", hash = "sha256:05175c27cb459dcfc05d026c4232f9de8913ed006d42713cb8a5137bd49375f1"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:935e943ec47c4afab8965954bf49bfa639c05d4ccf9ef6e924188f762145c0ff"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:667a9706c970cb552ede35aee17339a18e8f2a87a51fba2ed39ceeeb1004798a"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b8a678974d1f3aa55f6cc34dc480169d58f2e6d8958895d68845fa4ab566509e"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efc0f674aa41b92da8c49e0346318c6075d734994c3c4e4430b1c3f853e498e4"}, + {file = "greenlet-3.1.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0153404a4bb921f0ff1abeb5ce8a5131da56b953eda6e14b88dc6bbc04d2049e"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:275f72decf9932639c1c6dd1013a1bc266438eb32710016a1c742df5da6e60a1"}, + {file = "greenlet-3.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:c4aab7f6381f38a4b42f269057aee279ab0fc7bf2e929e3d4abfae97b682a12c"}, + {file = "greenlet-3.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:b42703b1cf69f2aa1df7d1030b9d77d3e584a70755674d60e710f0af570f3761"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1695e76146579f8c06c1509c7ce4dfe0706f49c6831a817ac04eebb2fd02011"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7876452af029456b3f3549b696bb36a06db7c90747740c5302f74a9e9fa14b13"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4ead44c85f8ab905852d3de8d86f6f8baf77109f9da589cb4fa142bd3b57b475"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8320f64b777d00dd7ccdade271eaf0cad6636343293a25074cc5566160e4de7b"}, + {file = "greenlet-3.1.1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6510bf84a6b643dabba74d3049ead221257603a253d0a9873f55f6a59a65f822"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:04b013dc07c96f83134b1e99888e7a79979f1a247e2a9f59697fa14b5862ed01"}, + {file = "greenlet-3.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:411f015496fec93c1c8cd4e5238da364e1da7a124bcb293f085bf2860c32c6f6"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47da355d8687fd65240c364c90a31569a133b7b60de111c255ef5b606f2ae291"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:98884ecf2ffb7d7fe6bd517e8eb99d31ff7855a840fa6d0d63cd07c037f6a981"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f1d4aeb8891338e60d1ab6127af1fe45def5259def8094b9c7e34690c8858803"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db32b5348615a04b82240cc67983cb315309e88d444a288934ee6ceaebcad6cc"}, + {file = "greenlet-3.1.1-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dcc62f31eae24de7f8dce72134c8651c58000d3b1868e01392baea7c32c247de"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1d3755bcb2e02de341c55b4fca7a745a24a9e7212ac953f6b3a48d117d7257aa"}, + {file = "greenlet-3.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b8da394b34370874b4572676f36acabac172602abf054cbc4ac910219f3340af"}, + {file = "greenlet-3.1.1-cp37-cp37m-win32.whl", hash = "sha256:a0dfc6c143b519113354e780a50381508139b07d2177cb6ad6a08278ec655798"}, + {file = "greenlet-3.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54558ea205654b50c438029505def3834e80f0869a70fb15b871c29b4575ddef"}, + {file = "greenlet-3.1.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:346bed03fe47414091be4ad44786d1bd8bef0c3fcad6ed3dee074a032ab408a9"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dfc59d69fc48664bc693842bd57acfdd490acafda1ab52c7836e3fc75c90a111"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d21e10da6ec19b457b82636209cbe2331ff4306b54d06fa04b7c138ba18c8a81"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:37b9de5a96111fc15418819ab4c4432e4f3c2ede61e660b1e33971eba26ef9ba"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ef9ea3f137e5711f0dbe5f9263e8c009b7069d8a1acea822bd5e9dae0ae49c8"}, + {file = "greenlet-3.1.1-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:85f3ff71e2e60bd4b4932a043fbbe0f499e263c628390b285cb599154a3b03b1"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:95ffcf719966dd7c453f908e208e14cde192e09fde6c7186c8f1896ef778d8cd"}, + {file = "greenlet-3.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:03a088b9de532cbfe2ba2034b2b85e82df37874681e8c470d6fb2f8c04d7e4b7"}, + {file = "greenlet-3.1.1-cp38-cp38-win32.whl", hash = "sha256:8b8b36671f10ba80e159378df9c4f15c14098c4fd73a36b9ad715f057272fbef"}, + {file = "greenlet-3.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:7017b2be767b9d43cc31416aba48aab0d2309ee31b4dbf10a1d38fb7972bdf9d"}, + {file = "greenlet-3.1.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:396979749bd95f018296af156201d6211240e7a23090f50a8d5d18c370084dc3"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca9d0ff5ad43e785350894d97e13633a66e2b50000e8a183a50a88d834752d42"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f6ff3b14f2df4c41660a7dec01045a045653998784bf8cfcb5a525bdffffbc8f"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:94ebba31df2aa506d7b14866fed00ac141a867e63143fe5bca82a8e503b36437"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73aaad12ac0ff500f62cebed98d8789198ea0e6f233421059fa68a5aa7220145"}, + {file = "greenlet-3.1.1-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:63e4844797b975b9af3a3fb8f7866ff08775f5426925e1e0bbcfe7932059a12c"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7939aa3ca7d2a1593596e7ac6d59391ff30281ef280d8632fa03d81f7c5f955e"}, + {file = "greenlet-3.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0028e725ee18175c6e422797c407874da24381ce0690d6b9396c204c7f7276e"}, + {file = "greenlet-3.1.1-cp39-cp39-win32.whl", hash = "sha256:5e06afd14cbaf9e00899fae69b24a32f2196c19de08fcb9f4779dd4f004e5e7c"}, + {file = "greenlet-3.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:3319aa75e0e0639bc15ff54ca327e8dc7a6fe404003496e3c6925cd3142e0e22"}, + {file = "greenlet-3.1.1.tar.gz", hash = "sha256:4ce3ac6cdb6adf7946475d7ef31777c26d94bccc377e070a7986bd2d5c515467"}, ] [package.extras] @@ -1735,107 +1742,107 @@ files = [ [[package]] name = "mmh3" -version = "5.0.0" +version = "5.0.1" description = "Python extension for MurmurHash (MurmurHash3), a set of fast and robust hash functions." optional = false python-versions = ">=3.8" files = [ - {file = "mmh3-5.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e65e16c9de793bfa334355c60fccc859faf1c0b707d847252841cee72b5309df"}, - {file = "mmh3-5.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:66d5423c65aebe94f244e8204cc8b39a4b970e342fb619621376af90c5f9a421"}, - {file = "mmh3-5.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5bfc15d11bcc5ce96254f5399582104c566a1eeeb91072ff0a0de92b24f704ff"}, - {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1616cb621cab3bc5b58dba9605311c346b45e732818f12f943a803b9a641f09c"}, - {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3e3d94553b321b26952d507dace15509463604ead98fece710237ca8cb5983d"}, - {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b46b6d5c0fcd2620ebc699099fed6fda0101805d7d4c274fa2b2c384e8b9e8b9"}, - {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a2a9cd368f2a15f06f2749db764e410b7dc260822319f169a52c649da078bf6"}, - {file = "mmh3-5.0.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:903cdb66f8d8de26a0a6c737a13664388b5ed14813f84793bccbba44ffa09fa2"}, - {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:7653d8177e4241a5f7913a675636ccc701722741b153b0a43c82464a4a865752"}, - {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:a9154a0a32de54b812d178541468cce4c73b112fbd8b5b0a4add92cfda69c390"}, - {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5f814a621fd0e567a121ace724ad57f28fb9972acfd54c854749ee91d5c4905c"}, - {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:333057bcf12804a8cae12adb2a5cef3bc50fc8de044ad6a01ae1918a342d6f0e"}, - {file = "mmh3-5.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e713afe276481af7ea0330f40214c71c11486b28754aa2d1cb286b5ee9571dee"}, - {file = "mmh3-5.0.0-cp310-cp310-win32.whl", hash = "sha256:917b72bc8b238286d7e2c586c19d604b3b3e5a93f054020cd15530324b868e6e"}, - {file = "mmh3-5.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:9a8bcaa5c0fd9c4679639c830b46e52fcc3b84502faa2aa5f3ca1dacb7cdbb1f"}, - {file = "mmh3-5.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:341ad4f902ebb7fc14751fab67fb4eedf800a1744bc9dc214a56e11b62d0bfdd"}, - {file = "mmh3-5.0.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:690cb4b36ed7c863680029c2370b4f3a0c3dc8900320eb5ce79a867eb8b37151"}, - {file = "mmh3-5.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5580c7bf9570458d8096a016f7012453306a14d2e3e4ef1267b9c9c9f506d8a4"}, - {file = "mmh3-5.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f271a043545c86cce51c35e5bb6f52dceb535dcd2d46c2129a9c869b80ec2eaa"}, - {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2400e805018e04718c9367e85d694c036d21a41b37024d5b0dc8ef80cb984cf0"}, - {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b3d75183b7d5df178bf01f0d9ac366525fd54e828d799fe3892882b83c13454b"}, - {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ec24039151e67c5fc026ce5be8788e3e8093d9ce3e114d7a6175f453521bacc9"}, - {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88513eb686937b26d33fe7ded7ed1a68ede490d326eea2344447c0cc42fb7f97"}, - {file = "mmh3-5.0.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a015a90951b31d45a01c8f92703c028d8d759f71dc31727581c916f31cacbade"}, - {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:337f747eaf6ab78d0be9cbbb763781ae76eb2e4c0e6523e74582877fe827ec51"}, - {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:01636d74df6371f192dc5a112f4afc43cb4119a9ea6de704469161fd4ab9e86b"}, - {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:3d969f1813b27bd4caac359e0ea0f6b711d252e872c0723124b58d7ac275cb0e"}, - {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:9f699f2232efe3907a6a05d9661edd2f4045a0e05161dc1087f72957d752a47a"}, - {file = "mmh3-5.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:16f46dc1774bf3e8305fa33207a502317cd7a592cb79b0d65c84f0fbcf9d9ffa"}, - {file = "mmh3-5.0.0-cp311-cp311-win32.whl", hash = "sha256:2d039b32f4194ac345c0f52441b7ff0a55735a896303a3eb9054e5f8512d84c8"}, - {file = "mmh3-5.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:47b3823a88c5f7859580688016bff13437fd866f6132e4770b306f0c6edb01a7"}, - {file = "mmh3-5.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:2ee87816b838709bd0fc623e3277736e9465a941d27b14f35d0df1c2006e4438"}, - {file = "mmh3-5.0.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ed23a89a6daeb9b0d4db9fa865b125300516e8d6961f771b2dd97965bf888bce"}, - {file = "mmh3-5.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:b020012e30ba0e4b002a150ca1c1d590a7387fac50dfd9b6b5dc66d9d0e61257"}, - {file = "mmh3-5.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:e86246d036da05d54e833cdc342ad9c62f38f2507b14f2c58ce2c271f22d7251"}, - {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f50535009c1aaa44f9702ad14551d12d1755b537fc25a5bd7d46c493ec4bcfaa"}, - {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a567a87026f103778b70f2b19637fb490d9c29dc7d3af9cd46185d48efbd49dc"}, - {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ff4405f231032b8f8ae93d9a558ddc04b9aa94a59c309cb265ebe1e79ced920e"}, - {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d630fe75ef7d4ab1a95eb824d81dee15ed73020703bf030515f03283cb8f086f"}, - {file = "mmh3-5.0.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:361f9d611debc10992888cbace09dbc47391ae8b84b50c995a6d97e6314bb422"}, - {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:11e29156edb1990600cb4b91bcb371a2adf20a0fcb818837fb78408be19e9117"}, - {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e70c4fc340509f6554a1748fe6f539a846fbacf147255048d1702def820a1520"}, - {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:aa790a46370eeedb474c462820be78597452f54c5fffe1f810fc8e847faf42a1"}, - {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:ce678ac7d31e83fecc817ab630cc7254c7826de11fd2b3e8c31a8a5b0b762884"}, - {file = "mmh3-5.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ee22cd4cad4fc7d4e5282b2729899460b03c388cde0290d411e877249f78a373"}, - {file = "mmh3-5.0.0-cp312-cp312-win32.whl", hash = "sha256:cb1a96488dc8fccf843ccdbdd10faf1939e6e18cd928c2beff17e70c2ab09ec1"}, - {file = "mmh3-5.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:897ebafa83bbbbb1958ee30cda78c7ad4b12f2b9360f96b22e488eb127b2cb4f"}, - {file = "mmh3-5.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:e6e3334985865ec3bdfcc4ae8df4a1939be048c5ae3ce1c8c309744400f8e4de"}, - {file = "mmh3-5.0.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:05b09d98cbdb6ad03eb9c701e87cea005ececd4fd7d2968ff0f5a86af1ef340d"}, - {file = "mmh3-5.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:d4ac0b8d48ce1e7561cf330ec73b9582f6773e40aaf8a771dd51a0bb483ec94f"}, - {file = "mmh3-5.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5a1c056b65be3077496ed655731eff1f65ee67f2b31f40a027f3171ba0ac20d1"}, - {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a572adb41cf831d79b3b38c7994e5a5bc1a8ea8d7b574ce0c24272fc5abb52df"}, - {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0be4d14ab3a690bac6c127733490493b8698f84eadb9d667be7eb952281c51e4"}, - {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b65b6eabd9e78e2b8eee2b8d4db34d4d2f5b540f2ac06ec0a76a1f1566f0ff7"}, - {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8b433656f371af5edf44cf00862e288e08327bb9e90c8aaa5e4e60dfebc62039"}, - {file = "mmh3-5.0.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1b12073a58be5e6408c6bd8366bbf6253defe042cdec25ee51f123c944e5a8f"}, - {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:29b2c8eb7a473f6f235c2d9905912a76350dd11b42058364de984264fa4f74ca"}, - {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:b178b744685de956ff84b3b2531271824a2e4372aff199ab805e1fdd7f996f5c"}, - {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fcac7a75846aec7bd168c05576dc8c9448a9705165dfa0986d0f48952eca62a4"}, - {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:cc0caa0d2800d54384cc048e49e6c2b4a90cba1afff0597d7c2a5006c42b5536"}, - {file = "mmh3-5.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:05b10476fd5cfb0fd63ceebf7430612423e7f79e9732e0b344225aa642e12be4"}, - {file = "mmh3-5.0.0-cp313-cp313-win32.whl", hash = "sha256:7101a12a2a4b39d7748d2d83310d5e0415950ccf6b9ec8da1d35af3f50b3ef0e"}, - {file = "mmh3-5.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:47d9a9c1c48accaf78ddb77669c40c837e90be2ecddd39bf7ef2f8dacff85ca6"}, - {file = "mmh3-5.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9126155ad1a9418920360497a0b44906dce32f0732cb44378ace08c62751dd1e"}, - {file = "mmh3-5.0.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:9d8ac8b3f53d2146680a2e16908620f209778bfb6c7a5d1e64b10f496f3c87f7"}, - {file = "mmh3-5.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:17c0af11c1c4217b30635d3b3ad03dc61388f14b19a4f74bfcd6f29cfac3a59e"}, - {file = "mmh3-5.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8df414110fdbcf689812bf0ecab83611f97191dea2ab378bd384a6166bf0849b"}, - {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bd7a88b40c5c715c9afe6af4034f23a2222045d17215ffdde357733d976fde2"}, - {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:157159c3306e11225111e7d2db6bee1ff81d2286df53d6a305728656cf694076"}, - {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1d1ac511ad1775f793b4c34fe40e43e213bc59620fcf7d3046ee7d73dd629f82"}, - {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb92b0e27775b078d469d948341f20a071943b4dacb4950d708a04722a8d6fee"}, - {file = "mmh3-5.0.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e326d9e1756ccacb2a8047d924101c3ed44bb2ac6aa6fc92efbbd1f2858f7b5"}, - {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:566ab00f7bc779d6d887b2d2ec96345483ed7091796d303f5df20cb06b4327ba"}, - {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:8971c3bc630cd3d3f2ba7b0a98b6eb3b5a83089f76e53500c53d3b3132d3437c"}, - {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:493479781554a98492a20bd659dd470608a9ceb5b229efcc4ce03ea4c849e81e"}, - {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:1424a9ffa472d4a9f5581f4eeac118d2c746e1980cbb48b7c1ddd1155e8d0d11"}, - {file = "mmh3-5.0.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:8f1c6c36135635c418b3e4e39f9bd3d0ef4afaf3c328c727f94cba0c146abba1"}, - {file = "mmh3-5.0.0-cp38-cp38-win32.whl", hash = "sha256:1d96e0ec13acf1c725b3ba5c932f0f4153238598880d4b32260dd552a34d6576"}, - {file = "mmh3-5.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:4ac9de75f3782a5c4dae1c60ed69f2a487439398ef694fc2b77c0a4e61268307"}, - {file = "mmh3-5.0.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:175037381e94d65553631b301c5c6f0cd69c7f8519e634221e7361595c7de0c3"}, - {file = "mmh3-5.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa6393cd40c3687b1d0bd3fbac8f9e81bbeb949bab63268e3756f9061c19d75c"}, - {file = "mmh3-5.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2e818d949829aeb25898db76d5a88411c2cc899c00e1c1c1a30916254f8762e2"}, - {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:072dda5e7e27a01c9c393c0558a210051d49750cd27807b443ba3285793f31bb"}, - {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bf6fa9d615f782a4626b0f99ec0a2d755e72b4e01a34503129bcc05488f4c404"}, - {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:03c2feef9fa839899065dc29cec2f921b1194bddaa9999e5460f9ab903e0ef24"}, - {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d67a7671b847ad934fea80702665b0903a5e19339713ea7a46f7e8eea21299d0"}, - {file = "mmh3-5.0.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f632b8962474ce3db9fec0d9e2c0038bc1339295796b0e76ac1d27b47bb60f9c"}, - {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:a1a2b2dc334a27a5d129cfbe848dbbbc22f1a744dcebab70b406528bca874009"}, - {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:4ea987eeb860723a0a0217b2e84c327ba5fd143cd8e7e289f008ae95280d30bb"}, - {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d5904ef1621c9ef680fd2ff3c1e433205e05093d26aa42840337630ac20912f1"}, - {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:cad6c32ab86e48ac73af45bcd118b931e677e038167c36186ac0d640f76c8b1e"}, - {file = "mmh3-5.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:3672b5ea6d7c9c5feeb3f75a695b622ee6a82d22897fa0aa4bf1d8ca99ac9c87"}, - {file = "mmh3-5.0.0-cp39-cp39-win32.whl", hash = "sha256:29b311d4f8dc277c8ef716c68b8020fe0e702807ba7f631fe57ad037d35713aa"}, - {file = "mmh3-5.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:8fbe4112955b33e1b4893b144bee9cc5bc476159d1d1cad9b8c0451047b6abde"}, - {file = "mmh3-5.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:4e66a0f85c8a2f54025f3d477a141458b0338f1b519ad0286c704865513c814f"}, - {file = "mmh3-5.0.0.tar.gz", hash = "sha256:60d1713457789c70292f1f04ca984e3175fc66e6c3545582fd2b4af7f5a61c73"}, + {file = "mmh3-5.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f0a4b4bf05778ed77d820d6e7d0e9bd6beb0c01af10e1ce9233f5d2f814fcafa"}, + {file = "mmh3-5.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac7a391039aeab95810c2d020b69a94eb6b4b37d4e2374831e92db3a0cdf71c6"}, + {file = "mmh3-5.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3a2583b5521ca49756d8d8bceba80627a9cc295f255dcab4e3df7ccc2f09679a"}, + {file = "mmh3-5.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:081a8423fe53c1ac94f87165f3e4c500125d343410c1a0c5f1703e898a3ef038"}, + {file = "mmh3-5.0.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8b4d72713799755dc8954a7d36d5c20a6c8de7b233c82404d122c7c7c1707cc"}, + {file = "mmh3-5.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:389a6fd51efc76d3182d36ec306448559c1244f11227d2bb771bdd0e6cc91321"}, + {file = "mmh3-5.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39f4128edaa074bff721b1d31a72508cba4d2887ee7867f22082e1fe9d4edea0"}, + {file = "mmh3-5.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d5d23a94d91aabba3386b3769048d5f4210fdfef80393fece2f34ba5a7b466c"}, + {file = "mmh3-5.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:16347d038361f8b8f24fd2b7ef378c9b68ddee9f7706e46269b6e0d322814713"}, + {file = "mmh3-5.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:6e299408565af7d61f2d20a5ffdd77cf2ed902460fe4e6726839d59ba4b72316"}, + {file = "mmh3-5.0.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:42050af21ddfc5445ee5a66e73a8fc758c71790305e3ee9e4a85a8e69e810f94"}, + {file = "mmh3-5.0.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:2ae9b1f5ef27ec54659920f0404b7ceb39966e28867c461bfe83a05e8d18ddb0"}, + {file = "mmh3-5.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:50c2495a02045f3047d71d4ae9cdd7a15efc0bcbb7ff17a18346834a8e2d1d19"}, + {file = "mmh3-5.0.1-cp310-cp310-win32.whl", hash = "sha256:c028fa77cddf351ca13b4a56d43c1775652cde0764cadb39120b68f02a23ecf6"}, + {file = "mmh3-5.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c5e741e421ec14400c4aae30890515c201f518403bdef29ae1e00d375bb4bbb5"}, + {file = "mmh3-5.0.1-cp310-cp310-win_arm64.whl", hash = "sha256:b17156d56fabc73dbf41bca677ceb6faed435cc8544f6566d72ea77d8a17e9d0"}, + {file = "mmh3-5.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9a6d5a9b1b923f1643559ba1fc0bf7a5076c90cbb558878d3bf3641ce458f25d"}, + {file = "mmh3-5.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3349b968be555f7334bbcce839da98f50e1e80b1c615d8e2aa847ea4a964a012"}, + {file = "mmh3-5.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1bd3c94b110e55db02ab9b605029f48a2f7f677c6e58c09d44e42402d438b7e1"}, + {file = "mmh3-5.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d47ba84d48608f79adbb10bb09986b6dc33eeda5c2d1bd75d00820081b73bde9"}, + {file = "mmh3-5.0.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c0217987a8b8525c8d9170f66d036dec4ab45cfbd53d47e8d76125791ceb155e"}, + {file = "mmh3-5.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2797063a34e78d1b61639a98b0edec1c856fa86ab80c7ec859f1796d10ba429"}, + {file = "mmh3-5.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8bba16340adcbd47853a2fbe5afdb397549e8f2e79324ff1dced69a3f8afe7c3"}, + {file = "mmh3-5.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:282797957c9f60b51b9d768a602c25f579420cc9af46feb77d457a27823d270a"}, + {file = "mmh3-5.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:e4fb670c29e63f954f9e7a2cdcd57b36a854c2538f579ef62681ccbaa1de2b69"}, + {file = "mmh3-5.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ee7d85438dc6aff328e19ab052086a3c29e8a9b632998a49e5c4b0034e9e8d6"}, + {file = "mmh3-5.0.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7fb5db231f3092444bc13901e6a8d299667126b00636ffbad4a7b45e1051e2f"}, + {file = "mmh3-5.0.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c100dd441703da5ec136b1d9003ed4a041d8a1136234c9acd887499796df6ad8"}, + {file = "mmh3-5.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:71f3b765138260fd7a7a2dba0ea5727dabcd18c1f80323c9cfef97a7e86e01d0"}, + {file = "mmh3-5.0.1-cp311-cp311-win32.whl", hash = "sha256:9a76518336247fd17689ce3ae5b16883fd86a490947d46a0193d47fb913e26e3"}, + {file = "mmh3-5.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:336bc4df2e44271f1c302d289cc3d78bd52d3eed8d306c7e4bff8361a12bf148"}, + {file = "mmh3-5.0.1-cp311-cp311-win_arm64.whl", hash = "sha256:af6522722fbbc5999aa66f7244d0986767a46f1fb05accc5200f75b72428a508"}, + {file = "mmh3-5.0.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:f2730bb263ed9c388e8860438b057a53e3cc701134a6ea140f90443c4c11aa40"}, + {file = "mmh3-5.0.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6246927bc293f6d56724536400b85fb85f5be26101fa77d5f97dd5e2a4c69bf2"}, + {file = "mmh3-5.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fbca322519a6e6e25b6abf43e940e1667cf8ea12510e07fb4919b48a0cd1c411"}, + {file = "mmh3-5.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eae8c19903ed8a1724ad9e67e86f15d198a7a1271a4f9be83d47e38f312ed672"}, + {file = "mmh3-5.0.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a09fd6cc72c07c0c07c3357714234b646d78052487c4a3bd5f7f6e08408cff60"}, + {file = "mmh3-5.0.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2ff8551fee7ae3b11c5d986b6347ade0dccaadd4670ffdb2b944dee120ffcc84"}, + {file = "mmh3-5.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e39694c73a5a20c8bf36dfd8676ed351e5234d55751ba4f7562d85449b21ef3f"}, + {file = "mmh3-5.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eba6001989a92f72a89c7cf382fda831678bd780707a66b4f8ca90239fdf2123"}, + {file = "mmh3-5.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0771f90c9911811cc606a5c7b7b58f33501c9ee896ed68a6ac22c7d55878ecc0"}, + {file = "mmh3-5.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:09b31ed0c0c0920363e96641fac4efde65b1ab62b8df86293142f35a254e72b4"}, + {file = "mmh3-5.0.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5cf4a8deda0235312db12075331cb417c4ba163770edfe789bde71d08a24b692"}, + {file = "mmh3-5.0.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:41f7090a95185ef20ac018581a99337f0cbc84a2135171ee3290a9c0d9519585"}, + {file = "mmh3-5.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b97b5b368fb7ff22194ec5854f5b12d8de9ab67a0f304728c7f16e5d12135b76"}, + {file = "mmh3-5.0.1-cp312-cp312-win32.whl", hash = "sha256:842516acf04da546f94fad52db125ee619ccbdcada179da51c326a22c4578cb9"}, + {file = "mmh3-5.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:d963be0dbfd9fca209c17172f6110787ebf78934af25e3694fe2ba40e55c1e2b"}, + {file = "mmh3-5.0.1-cp312-cp312-win_arm64.whl", hash = "sha256:a5da292ceeed8ce8e32b68847261a462d30fd7b478c3f55daae841404f433c15"}, + {file = "mmh3-5.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:673e3f1c8d4231d6fb0271484ee34cb7146a6499fc0df80788adb56fd76842da"}, + {file = "mmh3-5.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f795a306bd16a52ad578b663462cc8e95500b3925d64118ae63453485d67282b"}, + {file = "mmh3-5.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5ed57a5e28e502a1d60436cc25c76c3a5ba57545f250f2969af231dc1221e0a5"}, + {file = "mmh3-5.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:632c28e7612e909dbb6cbe2fe496201ada4695b7715584005689c5dc038e59ad"}, + {file = "mmh3-5.0.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53fd6bd525a5985e391c43384672d9d6b317fcb36726447347c7fc75bfed34ec"}, + {file = "mmh3-5.0.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dceacf6b0b961a0e499836af3aa62d60633265607aef551b2a3e3c48cdaa5edd"}, + {file = "mmh3-5.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f0738d478fdfb5d920f6aff5452c78f2c35b0eff72caa2a97dfe38e82f93da2"}, + {file = "mmh3-5.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e70285e7391ab88b872e5bef632bad16b9d99a6d3ca0590656a4753d55988af"}, + {file = "mmh3-5.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:27e5fc6360aa6b828546a4318da1a7da6bf6e5474ccb053c3a6aa8ef19ff97bd"}, + {file = "mmh3-5.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:7989530c3c1e2c17bf5a0ec2bba09fd19819078ba90beedabb1c3885f5040b0d"}, + {file = "mmh3-5.0.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:cdad7bee649950da7ecd3cbbbd12fb81f1161072ecbdb5acfa0018338c5cb9cf"}, + {file = "mmh3-5.0.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e143b8f184c1bb58cecd85ab4a4fd6dc65a2d71aee74157392c3fddac2a4a331"}, + {file = "mmh3-5.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e5eb12e886f3646dd636f16b76eb23fc0c27e8ff3c1ae73d4391e50ef60b40f6"}, + {file = "mmh3-5.0.1-cp313-cp313-win32.whl", hash = "sha256:16e6dddfa98e1c2d021268e72c78951234186deb4df6630e984ac82df63d0a5d"}, + {file = "mmh3-5.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:d3ffb792d70b8c4a2382af3598dad6ae0c5bd9cee5b7ffcc99aa2f5fd2c1bf70"}, + {file = "mmh3-5.0.1-cp313-cp313-win_arm64.whl", hash = "sha256:122fa9ec148383f9124292962bda745f192b47bfd470b2af5fe7bb3982b17896"}, + {file = "mmh3-5.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b12bad8c75e6ff5d67319794fb6a5e8c713826c818d47f850ad08b4aa06960c6"}, + {file = "mmh3-5.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e5bbb066538c1048d542246fc347bb7994bdda29a3aea61c22f9f8b57111ce69"}, + {file = "mmh3-5.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eee6134273f64e2a106827cc8fd77e70cc7239a285006fc6ab4977d59b015af2"}, + {file = "mmh3-5.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d04d9aa19d48e4c7bbec9cabc2c4dccc6ff3b2402f856d5bf0de03e10f167b5b"}, + {file = "mmh3-5.0.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79f37da1eed034d06567a69a7988456345c7f29e49192831c3975b464493b16e"}, + {file = "mmh3-5.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:242f77666743337aa828a2bf2da71b6ba79623ee7f93edb11e009f69237c8561"}, + {file = "mmh3-5.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffd943fff690463945f6441a2465555b3146deaadf6a5e88f2590d14c655d71b"}, + {file = "mmh3-5.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:565b15f8d7df43acb791ff5a360795c20bfa68bca8b352509e0fbabd06cc48cd"}, + {file = "mmh3-5.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:fc6aafb867c2030df98ac7760ff76b500359252867985f357bd387739f3d5287"}, + {file = "mmh3-5.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:32898170644d45aa27c974ab0d067809c066205110f5c6d09f47d9ece6978bfe"}, + {file = "mmh3-5.0.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:42865567838d2193eb64e0ef571f678bf361a254fcdef0c5c8e73243217829bd"}, + {file = "mmh3-5.0.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5ff5c1f301c4a8b6916498969c0fcc7e3dbc56b4bfce5cfe3fe31f3f4609e5ae"}, + {file = "mmh3-5.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:be74c2dda8a6f44a504450aa2c3507f8067a159201586fc01dd41ab80efc350f"}, + {file = "mmh3-5.0.1-cp38-cp38-win32.whl", hash = "sha256:5610a842621ff76c04b20b29cf5f809b131f241a19d4937971ba77dc99a7f330"}, + {file = "mmh3-5.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:de15739ac50776fe8aa1ef13f1be46a6ee1fbd45f6d0651084097eb2be0a5aa4"}, + {file = "mmh3-5.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:48e84cf3cc7e8c41bc07de72299a73b92d9e3cde51d97851420055b1484995f7"}, + {file = "mmh3-5.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6dd9dc28c2d168c49928195c2e29b96f9582a5d07bd690a28aede4cc07b0e696"}, + {file = "mmh3-5.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2771a1c56a3d4bdad990309cff5d0a8051f29c8ec752d001f97d6392194ae880"}, + {file = "mmh3-5.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c5ff2a8322ba40951a84411550352fba1073ce1c1d1213bb7530f09aed7f8caf"}, + {file = "mmh3-5.0.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a16bd3ec90682c9e0a343e6bd4c778c09947c8c5395cdb9e5d9b82b2559efbca"}, + {file = "mmh3-5.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d45733a78d68b5b05ff4a823aea51fa664df1d3bf4929b152ff4fd6dea2dd69b"}, + {file = "mmh3-5.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:904285e83cedebc8873b0838ed54c20f7344120be26e2ca5a907ab007a18a7a0"}, + {file = "mmh3-5.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ac4aeb1784e43df728034d0ed72e4b2648db1a69fef48fa58e810e13230ae5ff"}, + {file = "mmh3-5.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:cb3d4f751a0b8b4c8d06ef1c085216c8fddcc8b8c8d72445976b5167a40c6d1e"}, + {file = "mmh3-5.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:8021851935600e60c42122ed1176399d7692df338d606195cd599d228a04c1c6"}, + {file = "mmh3-5.0.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:6182d5924a5efc451900f864cbb021d7e8ad5d524816ca17304a0f663bc09bb5"}, + {file = "mmh3-5.0.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:5f30b834552a4f79c92e3d266336fb87fd92ce1d36dc6813d3e151035890abbd"}, + {file = "mmh3-5.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:cd4383f35e915e06d077df27e04ffd3be7513ec6a9de2d31f430393f67e192a7"}, + {file = "mmh3-5.0.1-cp39-cp39-win32.whl", hash = "sha256:1455fb6b42665a97db8fc66e89a861e52b567bce27ed054c47877183f86ea6e3"}, + {file = "mmh3-5.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:9e26a0f4eb9855a143f5938a53592fa14c2d3b25801c2106886ab6c173982780"}, + {file = "mmh3-5.0.1-cp39-cp39-win_arm64.whl", hash = "sha256:0d0a35a69abdad7549c4030a714bb4ad07902edb3bbe61e1bbc403ded5d678be"}, + {file = "mmh3-5.0.1.tar.gz", hash = "sha256:7dab080061aeb31a6069a181f27c473a1f67933854e36a3464931f2716508896"}, ] [package.extras] @@ -3189,13 +3196,13 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] @@ -3252,97 +3259,97 @@ tester = ["eth-tester[py-evm] (>=0.11.0b1,<0.12.0b1)", "eth-tester[py-evm] (>=0. [[package]] name = "websockets" -version = "13.0.1" +version = "13.1" description = "An implementation of the WebSocket Protocol (RFC 6455 & 7692)" optional = false python-versions = ">=3.8" files = [ - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1841c9082a3ba4a05ea824cf6d99570a6a2d8849ef0db16e9c826acb28089e8f"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c5870b4a11b77e4caa3937142b650fbbc0914a3e07a0cf3131f35c0587489c1c"}, - {file = "websockets-13.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f1d3d1f2eb79fe7b0fb02e599b2bf76a7619c79300fc55f0b5e2d382881d4f7f"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15c7d62ee071fa94a2fc52c2b472fed4af258d43f9030479d9c4a2de885fd543"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6724b554b70d6195ba19650fef5759ef11346f946c07dbbe390e039bcaa7cc3d"}, - {file = "websockets-13.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56a952fa2ae57a42ba7951e6b2605e08a24801a4931b5644dfc68939e041bc7f"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:17118647c0ea14796364299e942c330d72acc4b248e07e639d34b75067b3cdd8"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64a11aae1de4c178fa653b07d90f2fb1a2ed31919a5ea2361a38760192e1858b"}, - {file = "websockets-13.0.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:0617fd0b1d14309c7eab6ba5deae8a7179959861846cbc5cb528a7531c249448"}, - {file = "websockets-13.0.1-cp310-cp310-win32.whl", hash = "sha256:11f9976ecbc530248cf162e359a92f37b7b282de88d1d194f2167b5e7ad80ce3"}, - {file = "websockets-13.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:c3c493d0e5141ec055a7d6809a28ac2b88d5b878bb22df8c621ebe79a61123d0"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:699ba9dd6a926f82a277063603fc8d586b89f4cb128efc353b749b641fcddda7"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf2fae6d85e5dc384bf846f8243ddaa9197f3a1a70044f59399af001fd1f51d4"}, - {file = "websockets-13.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:52aed6ef21a0f1a2a5e310fb5c42d7555e9c5855476bbd7173c3aa3d8a0302f2"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8eb2b9a318542153674c6e377eb8cb9ca0fc011c04475110d3477862f15d29f0"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5df891c86fe68b2c38da55b7aea7095beca105933c697d719f3f45f4220a5e0e"}, - {file = "websockets-13.0.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fac2d146ff30d9dd2fcf917e5d147db037a5c573f0446c564f16f1f94cf87462"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b8ac5b46fd798bbbf2ac6620e0437c36a202b08e1f827832c4bf050da081b501"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:46af561eba6f9b0848b2c9d2427086cabadf14e0abdd9fde9d72d447df268418"}, - {file = "websockets-13.0.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b5a06d7f60bc2fc378a333978470dfc4e1415ee52f5f0fce4f7853eb10c1e9df"}, - {file = "websockets-13.0.1-cp311-cp311-win32.whl", hash = "sha256:556e70e4f69be1082e6ef26dcb70efcd08d1850f5d6c5f4f2bcb4e397e68f01f"}, - {file = "websockets-13.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:67494e95d6565bf395476e9d040037ff69c8b3fa356a886b21d8422ad86ae075"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f9c9e258e3d5efe199ec23903f5da0eeaad58cf6fccb3547b74fd4750e5ac47a"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6b41a1b3b561f1cba8321fb32987552a024a8f67f0d05f06fcf29f0090a1b956"}, - {file = "websockets-13.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f73e676a46b0fe9426612ce8caeca54c9073191a77c3e9d5c94697aef99296af"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f613289f4a94142f914aafad6c6c87903de78eae1e140fa769a7385fb232fdf"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f52504023b1480d458adf496dc1c9e9811df4ba4752f0bc1f89ae92f4f07d0c"}, - {file = "websockets-13.0.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:139add0f98206cb74109faf3611b7783ceafc928529c62b389917a037d4cfdf4"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:47236c13be337ef36546004ce8c5580f4b1150d9538b27bf8a5ad8edf23ccfab"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:c44ca9ade59b2e376612df34e837013e2b273e6c92d7ed6636d0556b6f4db93d"}, - {file = "websockets-13.0.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:9bbc525f4be3e51b89b2a700f5746c2a6907d2e2ef4513a8daafc98198b92237"}, - {file = "websockets-13.0.1-cp312-cp312-win32.whl", hash = "sha256:3624fd8664f2577cf8de996db3250662e259bfbc870dd8ebdcf5d7c6ac0b5185"}, - {file = "websockets-13.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0513c727fb8adffa6d9bf4a4463b2bade0186cbd8c3604ae5540fae18a90cb99"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1ee4cc030a4bdab482a37462dbf3ffb7e09334d01dd37d1063be1136a0d825fa"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dbb0b697cc0655719522406c059eae233abaa3243821cfdfab1215d02ac10231"}, - {file = "websockets-13.0.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:acbebec8cb3d4df6e2488fbf34702cbc37fc39ac7abf9449392cefb3305562e9"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63848cdb6fcc0bf09d4a155464c46c64ffdb5807ede4fb251da2c2692559ce75"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:872afa52a9f4c414d6955c365b6588bc4401272c629ff8321a55f44e3f62b553"}, - {file = "websockets-13.0.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05e70fec7c54aad4d71eae8e8cab50525e899791fc389ec6f77b95312e4e9920"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e82db3756ccb66266504f5a3de05ac6b32f287faacff72462612120074103329"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4e85f46ce287f5c52438bb3703d86162263afccf034a5ef13dbe4318e98d86e7"}, - {file = "websockets-13.0.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3fea72e4e6edb983908f0db373ae0732b275628901d909c382aae3b592589f2"}, - {file = "websockets-13.0.1-cp313-cp313-win32.whl", hash = "sha256:254ecf35572fca01a9f789a1d0f543898e222f7b69ecd7d5381d8d8047627bdb"}, - {file = "websockets-13.0.1-cp313-cp313-win_amd64.whl", hash = "sha256:ca48914cdd9f2ccd94deab5bcb5ac98025a5ddce98881e5cce762854a5de330b"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b74593e9acf18ea5469c3edaa6b27fa7ecf97b30e9dabd5a94c4c940637ab96e"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:132511bfd42e77d152c919147078460c88a795af16b50e42a0bd14f0ad71ddd2"}, - {file = "websockets-13.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:165bedf13556f985a2aa064309baa01462aa79bf6112fbd068ae38993a0e1f1b"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e801ca2f448850685417d723ec70298feff3ce4ff687c6f20922c7474b4746ae"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30d3a1f041360f029765d8704eae606781e673e8918e6b2c792e0775de51352f"}, - {file = "websockets-13.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67648f5e50231b5a7f6d83b32f9c525e319f0ddc841be0de64f24928cd75a603"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:4f0426d51c8f0926a4879390f53c7f5a855e42d68df95fff6032c82c888b5f36"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ef48e4137e8799998a343706531e656fdec6797b80efd029117edacb74b0a10a"}, - {file = "websockets-13.0.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:249aab278810bee585cd0d4de2f08cfd67eed4fc75bde623be163798ed4db2eb"}, - {file = "websockets-13.0.1-cp38-cp38-win32.whl", hash = "sha256:06c0a667e466fcb56a0886d924b5f29a7f0886199102f0a0e1c60a02a3751cb4"}, - {file = "websockets-13.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1f3cf6d6ec1142412d4535adabc6bd72a63f5f148c43fe559f06298bc21953c9"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1fa082ea38d5de51dd409434edc27c0dcbd5fed2b09b9be982deb6f0508d25bc"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4a365bcb7be554e6e1f9f3ed64016e67e2fa03d7b027a33e436aecf194febb63"}, - {file = "websockets-13.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:10a0dc7242215d794fb1918f69c6bb235f1f627aaf19e77f05336d147fce7c37"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59197afd478545b1f73367620407b0083303569c5f2d043afe5363676f2697c9"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d20516990d8ad557b5abeb48127b8b779b0b7e6771a265fa3e91767596d7d97"}, - {file = "websockets-13.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a1a2e272d067030048e1fe41aa1ec8cfbbaabce733b3d634304fa2b19e5c897f"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ad327ac80ba7ee61da85383ca8822ff808ab5ada0e4a030d66703cc025b021c4"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:518f90e6dd089d34eaade01101fd8a990921c3ba18ebbe9b0165b46ebff947f0"}, - {file = "websockets-13.0.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:68264802399aed6fe9652e89761031acc734fc4c653137a5911c2bfa995d6d6d"}, - {file = "websockets-13.0.1-cp39-cp39-win32.whl", hash = "sha256:a5dc0c42ded1557cc7c3f0240b24129aefbad88af4f09346164349391dea8e58"}, - {file = "websockets-13.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:b448a0690ef43db5ef31b3a0d9aea79043882b4632cfc3eaab20105edecf6097"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:faef9ec6354fe4f9a2c0bbb52fb1ff852effc897e2a4501e25eb3a47cb0a4f89"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:03d3f9ba172e0a53e37fa4e636b86cc60c3ab2cfee4935e66ed1d7acaa4625ad"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d450f5a7a35662a9b91a64aefa852f0c0308ee256122f5218a42f1d13577d71e"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3f55b36d17ac50aa8a171b771e15fbe1561217510c8768af3d546f56c7576cdc"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:14b9c006cac63772b31abbcd3e3abb6228233eec966bf062e89e7fa7ae0b7333"}, - {file = "websockets-13.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:b79915a1179a91f6c5f04ece1e592e2e8a6bd245a0e45d12fd56b2b59e559a32"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f40de079779acbcdbb6ed4c65af9f018f8b77c5ec4e17a4b737c05c2db554491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80e4ba642fc87fa532bac07e5ed7e19d56940b6af6a8c61d4429be48718a380f"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a02b0161c43cc9e0232711eff846569fad6ec836a7acab16b3cf97b2344c060"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6aa74a45d4cdc028561a7d6ab3272c8b3018e23723100b12e58be9dfa5a24491"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00fd961943b6c10ee6f0b1130753e50ac5dcd906130dcd77b0003c3ab797d026"}, - {file = "websockets-13.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:d93572720d781331fb10d3da9ca1067817d84ad1e7c31466e9f5e59965618096"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:71e6e5a3a3728886caee9ab8752e8113670936a193284be9d6ad2176a137f376"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:c4a6343e3b0714e80da0b0893543bf9a5b5fa71b846ae640e56e9abc6fbc4c83"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a678532018e435396e37422a95e3ab87f75028ac79570ad11f5bf23cd2a7d8c"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6716c087e4aa0b9260c4e579bb82e068f84faddb9bfba9906cb87726fa2e870"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e33505534f3f673270dd67f81e73550b11de5b538c56fe04435d63c02c3f26b5"}, - {file = "websockets-13.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acab3539a027a85d568c2573291e864333ec9d912675107d6efceb7e2be5d980"}, - {file = "websockets-13.0.1-py3-none-any.whl", hash = "sha256:b80f0c51681c517604152eb6a572f5a9378f877763231fddb883ba2f968e8817"}, - {file = "websockets-13.0.1.tar.gz", hash = "sha256:4d6ece65099411cfd9a48d13701d7438d9c34f479046b34c50ff60bb8834e43e"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:f48c749857f8fb598fb890a75f540e3221d0976ed0bf879cf3c7eef34151acee"}, + {file = "websockets-13.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c7e72ce6bda6fb9409cc1e8164dd41d7c91466fb599eb047cfda72fe758a34a7"}, + {file = "websockets-13.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f779498eeec470295a2b1a5d97aa1bc9814ecd25e1eb637bd9d1c73a327387f6"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4676df3fe46956fbb0437d8800cd5f2b6d41143b6e7e842e60554398432cf29b"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7affedeb43a70351bb811dadf49493c9cfd1ed94c9c70095fd177e9cc1541fa"}, + {file = "websockets-13.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1971e62d2caa443e57588e1d82d15f663b29ff9dfe7446d9964a4b6f12c1e700"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:5f2e75431f8dc4a47f31565a6e1355fb4f2ecaa99d6b89737527ea917066e26c"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58cf7e75dbf7e566088b07e36ea2e3e2bd5676e22216e4cad108d4df4a7402a0"}, + {file = "websockets-13.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c90d6dec6be2c7d03378a574de87af9b1efea77d0c52a8301dd831ece938452f"}, + {file = "websockets-13.1-cp310-cp310-win32.whl", hash = "sha256:730f42125ccb14602f455155084f978bd9e8e57e89b569b4d7f0f0c17a448ffe"}, + {file = "websockets-13.1-cp310-cp310-win_amd64.whl", hash = "sha256:5993260f483d05a9737073be197371940c01b257cc45ae3f1d5d7adb371b266a"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:61fc0dfcda609cda0fc9fe7977694c0c59cf9d749fbb17f4e9483929e3c48a19"}, + {file = "websockets-13.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ceec59f59d092c5007e815def4ebb80c2de330e9588e101cf8bd94c143ec78a5"}, + {file = "websockets-13.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c1dca61c6db1166c48b95198c0b7d9c990b30c756fc2923cc66f68d17dc558fd"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:308e20f22c2c77f3f39caca508e765f8725020b84aa963474e18c59accbf4c02"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62d516c325e6540e8a57b94abefc3459d7dab8ce52ac75c96cad5549e187e3a7"}, + {file = "websockets-13.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87c6e35319b46b99e168eb98472d6c7d8634ee37750d7693656dc766395df096"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5f9fee94ebafbc3117c30be1844ed01a3b177bb6e39088bc6b2fa1dc15572084"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:7c1e90228c2f5cdde263253fa5db63e6653f1c00e7ec64108065a0b9713fa1b3"}, + {file = "websockets-13.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:6548f29b0e401eea2b967b2fdc1c7c7b5ebb3eeb470ed23a54cd45ef078a0db9"}, + {file = "websockets-13.1-cp311-cp311-win32.whl", hash = "sha256:c11d4d16e133f6df8916cc5b7e3e96ee4c44c936717d684a94f48f82edb7c92f"}, + {file = "websockets-13.1-cp311-cp311-win_amd64.whl", hash = "sha256:d04f13a1d75cb2b8382bdc16ae6fa58c97337253826dfe136195b7f89f661557"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9d75baf00138f80b48f1eac72ad1535aac0b6461265a0bcad391fc5aba875cfc"}, + {file = "websockets-13.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9b6f347deb3dcfbfde1c20baa21c2ac0751afaa73e64e5b693bb2b848efeaa49"}, + {file = "websockets-13.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:de58647e3f9c42f13f90ac7e5f58900c80a39019848c5547bc691693098ae1bd"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1b54689e38d1279a51d11e3467dd2f3a50f5f2e879012ce8f2d6943f00e83f0"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf1781ef73c073e6b0f90af841aaf98501f975d306bbf6221683dd594ccc52b6"}, + {file = "websockets-13.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d23b88b9388ed85c6faf0e74d8dec4f4d3baf3ecf20a65a47b836d56260d4b9"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:3c78383585f47ccb0fcf186dcb8a43f5438bd7d8f47d69e0b56f71bf431a0a68"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:d6d300f8ec35c24025ceb9b9019ae9040c1ab2f01cddc2bcc0b518af31c75c14"}, + {file = "websockets-13.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a9dcaf8b0cc72a392760bb8755922c03e17a5a54e08cca58e8b74f6902b433cf"}, + {file = "websockets-13.1-cp312-cp312-win32.whl", hash = "sha256:2f85cf4f2a1ba8f602298a853cec8526c2ca42a9a4b947ec236eaedb8f2dc80c"}, + {file = "websockets-13.1-cp312-cp312-win_amd64.whl", hash = "sha256:38377f8b0cdeee97c552d20cf1865695fcd56aba155ad1b4ca8779a5b6ef4ac3"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a9ab1e71d3d2e54a0aa646ab6d4eebfaa5f416fe78dfe4da2839525dc5d765c6"}, + {file = "websockets-13.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b9d7439d7fab4dce00570bb906875734df13d9faa4b48e261c440a5fec6d9708"}, + {file = "websockets-13.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:327b74e915cf13c5931334c61e1a41040e365d380f812513a255aa804b183418"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:325b1ccdbf5e5725fdcb1b0e9ad4d2545056479d0eee392c291c1bf76206435a"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:346bee67a65f189e0e33f520f253d5147ab76ae42493804319b5716e46dddf0f"}, + {file = "websockets-13.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91a0fa841646320ec0d3accdff5b757b06e2e5c86ba32af2e0815c96c7a603c5"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:18503d2c5f3943e93819238bf20df71982d193f73dcecd26c94514f417f6b135"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:a9cd1af7e18e5221d2878378fbc287a14cd527fdd5939ed56a18df8a31136bb2"}, + {file = "websockets-13.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:70c5be9f416aa72aab7a2a76c90ae0a4fe2755c1816c153c1a2bcc3333ce4ce6"}, + {file = "websockets-13.1-cp313-cp313-win32.whl", hash = "sha256:624459daabeb310d3815b276c1adef475b3e6804abaf2d9d2c061c319f7f187d"}, + {file = "websockets-13.1-cp313-cp313-win_amd64.whl", hash = "sha256:c518e84bb59c2baae725accd355c8dc517b4a3ed8db88b4bc93c78dae2974bf2"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c7934fd0e920e70468e676fe7f1b7261c1efa0d6c037c6722278ca0228ad9d0d"}, + {file = "websockets-13.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:149e622dc48c10ccc3d2760e5f36753db9cacf3ad7bc7bbbfd7d9c819e286f23"}, + {file = "websockets-13.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a569eb1b05d72f9bce2ebd28a1ce2054311b66677fcd46cf36204ad23acead8c"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:95df24ca1e1bd93bbca51d94dd049a984609687cb2fb08a7f2c56ac84e9816ea"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d8dbb1bf0c0a4ae8b40bdc9be7f644e2f3fb4e8a9aca7145bfa510d4a374eeb7"}, + {file = "websockets-13.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:035233b7531fb92a76beefcbf479504db8c72eb3bff41da55aecce3a0f729e54"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:e4450fc83a3df53dec45922b576e91e94f5578d06436871dce3a6be38e40f5db"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:463e1c6ec853202dd3657f156123d6b4dad0c546ea2e2e38be2b3f7c5b8e7295"}, + {file = "websockets-13.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:6d6855bbe70119872c05107e38fbc7f96b1d8cb047d95c2c50869a46c65a8e96"}, + {file = "websockets-13.1-cp38-cp38-win32.whl", hash = "sha256:204e5107f43095012b00f1451374693267adbb832d29966a01ecc4ce1db26faf"}, + {file = "websockets-13.1-cp38-cp38-win_amd64.whl", hash = "sha256:485307243237328c022bc908b90e4457d0daa8b5cf4b3723fd3c4a8012fce4c6"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9b37c184f8b976f0c0a231a5f3d6efe10807d41ccbe4488df8c74174805eea7d"}, + {file = "websockets-13.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:163e7277e1a0bd9fb3c8842a71661ad19c6aa7bb3d6678dc7f89b17fbcc4aeb7"}, + {file = "websockets-13.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4b889dbd1342820cc210ba44307cf75ae5f2f96226c0038094455a96e64fb07a"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:586a356928692c1fed0eca68b4d1c2cbbd1ca2acf2ac7e7ebd3b9052582deefa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7bd6abf1e070a6b72bfeb71049d6ad286852e285f146682bf30d0296f5fbadfa"}, + {file = "websockets-13.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2aad13a200e5934f5a6767492fb07151e1de1d6079c003ab31e1823733ae79"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:df01aea34b6e9e33572c35cd16bae5a47785e7d5c8cb2b54b2acdb9678315a17"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e54affdeb21026329fb0744ad187cf812f7d3c2aa702a5edb562b325191fcab6"}, + {file = "websockets-13.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9ef8aa8bdbac47f4968a5d66462a2a0935d044bf35c0e5a8af152d58516dbeb5"}, + {file = "websockets-13.1-cp39-cp39-win32.whl", hash = "sha256:deeb929efe52bed518f6eb2ddc00cc496366a14c726005726ad62c2dd9017a3c"}, + {file = "websockets-13.1-cp39-cp39-win_amd64.whl", hash = "sha256:7c65ffa900e7cc958cd088b9a9157a8141c991f8c53d11087e6fb7277a03f81d"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5dd6da9bec02735931fccec99d97c29f47cc61f644264eb995ad6c0c27667238"}, + {file = "websockets-13.1-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:2510c09d8e8df777177ee3d40cd35450dc169a81e747455cc4197e63f7e7bfe5"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f1c3cf67185543730888b20682fb186fc8d0fa6f07ccc3ef4390831ab4b388d9"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcc03c8b72267e97b49149e4863d57c2d77f13fae12066622dc78fe322490fe6"}, + {file = "websockets-13.1-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:004280a140f220c812e65f36944a9ca92d766b6cc4560be652a0a3883a79ed8a"}, + {file = "websockets-13.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e2620453c075abeb0daa949a292e19f56de518988e079c36478bacf9546ced23"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9156c45750b37337f7b0b00e6248991a047be4aa44554c9886fe6bdd605aab3b"}, + {file = "websockets-13.1-pp38-pypy38_pp73-macosx_11_0_arm64.whl", hash = "sha256:80c421e07973a89fbdd93e6f2003c17d20b69010458d3a8e37fb47874bd67d51"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:82d0ba76371769d6a4e56f7e83bb8e81846d17a6190971e38b5de108bde9b0d7"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e9875a0143f07d74dc5e1ded1c4581f0d9f7ab86c78994e2ed9e95050073c94d"}, + {file = "websockets-13.1-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11e38ad8922c7961447f35c7b17bffa15de4d17c70abd07bfbe12d6faa3e027"}, + {file = "websockets-13.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4059f790b6ae8768471cddb65d3c4fe4792b0ab48e154c9f0a04cefaabcd5978"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:25c35bf84bf7c7369d247f0b8cfa157f989862c49104c5cf85cb5436a641d93e"}, + {file = "websockets-13.1-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:83f91d8a9bb404b8c2c41a707ac7f7f75b9442a0a876df295de27251a856ad09"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a43cfdcddd07f4ca2b1afb459824dd3c6d53a51410636a2c7fc97b9a8cf4842"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a2ef1381632a2f0cb4efeff34efa97901c9fbc118e01951ad7cfc10601a9bb"}, + {file = "websockets-13.1-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:459bf774c754c35dbb487360b12c5727adab887f1622b8aed5755880a21c4a20"}, + {file = "websockets-13.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:95858ca14a9f6fa8413d29e0a585b31b278388aa775b8a81fa24830123874678"}, + {file = "websockets-13.1-py3-none-any.whl", hash = "sha256:a9a396a6ad26130cdae92ae10c36af09d9bfe6cafe69670fd3b6da9b07b4044f"}, + {file = "websockets-13.1.tar.gz", hash = "sha256:a3b3366087c1bc0a2795111edcadddb8b3b59509d5db5d7ea3fdd69f954a8878"}, ] [[package]] @@ -3443,103 +3450,103 @@ files = [ [[package]] name = "yarl" -version = "1.11.1" +version = "1.12.1" description = "Yet another URL library" optional = false python-versions = ">=3.8" files = [ - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:400cd42185f92de559d29eeb529e71d80dfbd2f45c36844914a4a34297ca6f00"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8258c86f47e080a258993eed877d579c71da7bda26af86ce6c2d2d072c11320d"}, - {file = "yarl-1.11.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2164cd9725092761fed26f299e3f276bb4b537ca58e6ff6b252eae9631b5c96e"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08ea567c16f140af8ddc7cb58e27e9138a1386e3e6e53982abaa6f2377b38cc"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:768ecc550096b028754ea28bf90fde071c379c62c43afa574edc6f33ee5daaec"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2909fa3a7d249ef64eeb2faa04b7957e34fefb6ec9966506312349ed8a7e77bf"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01a8697ec24f17c349c4f655763c4db70eebc56a5f82995e5e26e837c6eb0e49"}, - {file = "yarl-1.11.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e286580b6511aac7c3268a78cdb861ec739d3e5a2a53b4809faef6b49778eaff"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:4179522dc0305c3fc9782549175c8e8849252fefeb077c92a73889ccbcd508ad"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:27fcb271a41b746bd0e2a92182df507e1c204759f460ff784ca614e12dd85145"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f61db3b7e870914dbd9434b560075e0366771eecbe6d2b5561f5bc7485f39efd"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:c92261eb2ad367629dc437536463dc934030c9e7caca861cc51990fe6c565f26"}, - {file = "yarl-1.11.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d95b52fbef190ca87d8c42f49e314eace4fc52070f3dfa5f87a6594b0c1c6e46"}, - {file = "yarl-1.11.1-cp310-cp310-win32.whl", hash = "sha256:489fa8bde4f1244ad6c5f6d11bb33e09cf0d1d0367edb197619c3e3fc06f3d91"}, - {file = "yarl-1.11.1-cp310-cp310-win_amd64.whl", hash = "sha256:476e20c433b356e16e9a141449f25161e6b69984fb4cdbd7cd4bd54c17844998"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:946eedc12895873891aaceb39bceb484b4977f70373e0122da483f6c38faaa68"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:21a7c12321436b066c11ec19c7e3cb9aec18884fe0d5b25d03d756a9e654edfe"}, - {file = "yarl-1.11.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c35f493b867912f6fda721a59cc7c4766d382040bdf1ddaeeaa7fa4d072f4675"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25861303e0be76b60fddc1250ec5986c42f0a5c0c50ff57cc30b1be199c00e63"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4b53f73077e839b3f89c992223f15b1d2ab314bdbdf502afdc7bb18e95eae27"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:327c724b01b8641a1bf1ab3b232fb638706e50f76c0b5bf16051ab65c868fac5"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4307d9a3417eea87715c9736d050c83e8c1904e9b7aada6ce61b46361b733d92"}, - {file = "yarl-1.11.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:48a28bed68ab8fb7e380775f0029a079f08a17799cb3387a65d14ace16c12e2b"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:067b961853c8e62725ff2893226fef3d0da060656a9827f3f520fb1d19b2b68a"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8215f6f21394d1f46e222abeb06316e77ef328d628f593502d8fc2a9117bde83"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:498442e3af2a860a663baa14fbf23fb04b0dd758039c0e7c8f91cb9279799bff"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:69721b8effdb588cb055cc22f7c5105ca6fdaa5aeb3ea09021d517882c4a904c"}, - {file = "yarl-1.11.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e969fa4c1e0b1a391f3fcbcb9ec31e84440253325b534519be0d28f4b6b533e"}, - {file = "yarl-1.11.1-cp311-cp311-win32.whl", hash = "sha256:7d51324a04fc4b0e097ff8a153e9276c2593106a811704025bbc1d6916f45ca6"}, - {file = "yarl-1.11.1-cp311-cp311-win_amd64.whl", hash = "sha256:15061ce6584ece023457fb8b7a7a69ec40bf7114d781a8c4f5dcd68e28b5c53b"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a4264515f9117be204935cd230fb2a052dd3792789cc94c101c535d349b3dab0"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f41fa79114a1d2eddb5eea7b912d6160508f57440bd302ce96eaa384914cd265"}, - {file = "yarl-1.11.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:02da8759b47d964f9173c8675710720b468aa1c1693be0c9c64abb9d8d9a4867"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9361628f28f48dcf8b2f528420d4d68102f593f9c2e592bfc842f5fb337e44fd"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b91044952da03b6f95fdba398d7993dd983b64d3c31c358a4c89e3c19b6f7aef"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74db2ef03b442276d25951749a803ddb6e270d02dda1d1c556f6ae595a0d76a8"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e975a2211952a8a083d1b9d9ba26472981ae338e720b419eb50535de3c02870"}, - {file = "yarl-1.11.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8aef97ba1dd2138112890ef848e17d8526fe80b21f743b4ee65947ea184f07a2"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a7915ea49b0c113641dc4d9338efa9bd66b6a9a485ffe75b9907e8573ca94b84"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:504cf0d4c5e4579a51261d6091267f9fd997ef58558c4ffa7a3e1460bd2336fa"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3de5292f9f0ee285e6bd168b2a77b2a00d74cbcfa420ed078456d3023d2f6dff"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a34e1e30f1774fa35d37202bbeae62423e9a79d78d0874e5556a593479fdf239"}, - {file = "yarl-1.11.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:66b63c504d2ca43bf7221a1f72fbe981ff56ecb39004c70a94485d13e37ebf45"}, - {file = "yarl-1.11.1-cp312-cp312-win32.whl", hash = "sha256:a28b70c9e2213de425d9cba5ab2e7f7a1c8ca23a99c4b5159bf77b9c31251447"}, - {file = "yarl-1.11.1-cp312-cp312-win_amd64.whl", hash = "sha256:17b5a386d0d36fb828e2fb3ef08c8829c1ebf977eef88e5367d1c8c94b454639"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:1fa2e7a406fbd45b61b4433e3aa254a2c3e14c4b3186f6e952d08a730807fa0c"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:750f656832d7d3cb0c76be137ee79405cc17e792f31e0a01eee390e383b2936e"}, - {file = "yarl-1.11.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0b8486f322d8f6a38539136a22c55f94d269addb24db5cb6f61adc61eabc9d93"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3fce4da3703ee6048ad4138fe74619c50874afe98b1ad87b2698ef95bf92c96d"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed653638ef669e0efc6fe2acb792275cb419bf9cb5c5049399f3556995f23c7"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:18ac56c9dd70941ecad42b5a906820824ca72ff84ad6fa18db33c2537ae2e089"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:688654f8507464745ab563b041d1fb7dab5d9912ca6b06e61d1c4708366832f5"}, - {file = "yarl-1.11.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4973eac1e2ff63cf187073cd4e1f1148dcd119314ab79b88e1b3fad74a18c9d5"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:964a428132227edff96d6f3cf261573cb0f1a60c9a764ce28cda9525f18f7786"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:6d23754b9939cbab02c63434776df1170e43b09c6a517585c7ce2b3d449b7318"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c2dc4250fe94d8cd864d66018f8344d4af50e3758e9d725e94fecfa27588ff82"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09696438cb43ea6f9492ef237761b043f9179f455f405279e609f2bc9100212a"}, - {file = "yarl-1.11.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:999bfee0a5b7385a0af5ffb606393509cfde70ecca4f01c36985be6d33e336da"}, - {file = "yarl-1.11.1-cp313-cp313-win32.whl", hash = "sha256:ce928c9c6409c79e10f39604a7e214b3cb69552952fbda8d836c052832e6a979"}, - {file = "yarl-1.11.1-cp313-cp313-win_amd64.whl", hash = "sha256:501c503eed2bb306638ccb60c174f856cc3246c861829ff40eaa80e2f0330367"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dae7bd0daeb33aa3e79e72877d3d51052e8b19c9025ecf0374f542ea8ec120e4"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3ff6b1617aa39279fe18a76c8d165469c48b159931d9b48239065767ee455b2b"}, - {file = "yarl-1.11.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3257978c870728a52dcce8c2902bf01f6c53b65094b457bf87b2644ee6238ddc"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f351fa31234699d6084ff98283cb1e852270fe9e250a3b3bf7804eb493bd937"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8aef1b64da41d18026632d99a06b3fefe1d08e85dd81d849fa7c96301ed22f1b"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7175a87ab8f7fbde37160a15e58e138ba3b2b0e05492d7351314a250d61b1591"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba444bdd4caa2a94456ef67a2f383710928820dd0117aae6650a4d17029fa25e"}, - {file = "yarl-1.11.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0ea9682124fc062e3d931c6911934a678cb28453f957ddccf51f568c2f2b5e05"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:8418c053aeb236b20b0ab8fa6bacfc2feaaf7d4683dd96528610989c99723d5f"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:61a5f2c14d0a1adfdd82258f756b23a550c13ba4c86c84106be4c111a3a4e413"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f3a6d90cab0bdf07df8f176eae3a07127daafcf7457b997b2bf46776da2c7eb7"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:077da604852be488c9a05a524068cdae1e972b7dc02438161c32420fb4ec5e14"}, - {file = "yarl-1.11.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:15439f3c5c72686b6c3ff235279630d08936ace67d0fe5c8d5bbc3ef06f5a420"}, - {file = "yarl-1.11.1-cp38-cp38-win32.whl", hash = "sha256:238a21849dd7554cb4d25a14ffbfa0ef380bb7ba201f45b144a14454a72ffa5a"}, - {file = "yarl-1.11.1-cp38-cp38-win_amd64.whl", hash = "sha256:67459cf8cf31da0e2cbdb4b040507e535d25cfbb1604ca76396a3a66b8ba37a6"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:884eab2ce97cbaf89f264372eae58388862c33c4f551c15680dd80f53c89a269"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8a336eaa7ee7e87cdece3cedb395c9657d227bfceb6781295cf56abcd3386a26"}, - {file = "yarl-1.11.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:87f020d010ba80a247c4abc335fc13421037800ca20b42af5ae40e5fd75e7909"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:637c7ddb585a62d4469f843dac221f23eec3cbad31693b23abbc2c366ad41ff4"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:48dfd117ab93f0129084577a07287376cc69c08138694396f305636e229caa1a"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e0ae31fb5ccab6eda09ba1494e87eb226dcbd2372dae96b87800e1dcc98804"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f46f81501160c28d0c0b7333b4f7be8983dbbc161983b6fb814024d1b4952f79"}, - {file = "yarl-1.11.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04293941646647b3bfb1719d1d11ff1028e9c30199509a844da3c0f5919dc520"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:250e888fa62d73e721f3041e3a9abf427788a1934b426b45e1b92f62c1f68366"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e8f63904df26d1a66aabc141bfd258bf738b9bc7bc6bdef22713b4f5ef789a4c"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:aac44097d838dda26526cffb63bdd8737a2dbdf5f2c68efb72ad83aec6673c7e"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:267b24f891e74eccbdff42241c5fb4f974de2d6271dcc7d7e0c9ae1079a560d9"}, - {file = "yarl-1.11.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6907daa4b9d7a688063ed098c472f96e8181733c525e03e866fb5db480a424df"}, - {file = "yarl-1.11.1-cp39-cp39-win32.whl", hash = "sha256:14438dfc5015661f75f85bc5adad0743678eefee266ff0c9a8e32969d5d69f74"}, - {file = "yarl-1.11.1-cp39-cp39-win_amd64.whl", hash = "sha256:94d0caaa912bfcdc702a4204cd5e2bb01eb917fc4f5ea2315aa23962549561b0"}, - {file = "yarl-1.11.1-py3-none-any.whl", hash = "sha256:72bf26f66456baa0584eff63e44545c9f0eaed9b73cb6601b647c91f14c11f38"}, - {file = "yarl-1.11.1.tar.gz", hash = "sha256:1bb2d9e212fb7449b8fb73bc461b51eaa17cc8430b4a87d87be7b25052d92f53"}, + {file = "yarl-1.12.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:64c5b0f2b937fe40d0967516eee5504b23cb247b8b7ffeba7213a467d9646fdc"}, + {file = "yarl-1.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2e430ac432f969ef21770645743611c1618362309e3ad7cab45acd1ad1a540ff"}, + {file = "yarl-1.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3e26e64f42bce5ddf9002092b2c37b13071c2e6413d5c05f9fa9de58ed2f7749"}, + {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0103c52f8dfe5d573c856322149ddcd6d28f51b4d4a3ee5c4b3c1b0a05c3d034"}, + {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b63465b53baeaf2122a337d4ab57d6bbdd09fcadceb17a974cfa8a0300ad9c67"}, + {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17d4dc4ff47893a06737b8788ed2ba2f5ac4e8bb40281c8603920f7d011d5bdd"}, + {file = "yarl-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b54949267bd5704324397efe9fbb6aa306466dee067550964e994d309db5f1"}, + {file = "yarl-1.12.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10b690cd78cbaca2f96a7462f303fdd2b596d3978b49892e4b05a7567c591572"}, + {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c85ab016e96a975afbdb9d49ca90f3bca9920ef27c64300843fe91c3d59d8d20"}, + {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:c1caa5763d1770216596e0a71b5567f27aac28c95992110212c108ec74589a48"}, + {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:595bbcdbfc4a9c6989d7489dca8510cba053ff46b16c84ffd95ac8e90711d419"}, + {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e64f0421892a207d3780903085c1b04efeb53b16803b23d947de5a7261b71355"}, + {file = "yarl-1.12.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:319c206e83e46ec2421b25b300c8482b6fe8a018baca246be308c736d9dab267"}, + {file = "yarl-1.12.1-cp310-cp310-win32.whl", hash = "sha256:da045bd1147d12bd43fb032296640a7cc17a7f2eaba67495988362e99db24fd2"}, + {file = "yarl-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:aebbd47df77190ada603157f0b3670d578c110c31746ecc5875c394fdcc59a99"}, + {file = "yarl-1.12.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:28389a68981676bf74e2e199fe42f35d1aa27a9c98e3a03e6f58d2d3d054afe1"}, + {file = "yarl-1.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f736f54565f8dd7e3ab664fef2bc461d7593a389a7f28d4904af8d55a91bd55f"}, + {file = "yarl-1.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dee0496d5f1a8f57f0f28a16f81a2033fc057a2cf9cd710742d11828f8c80e2"}, + {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8981a94a27ac520a398302afb74ae2c0be1c3d2d215c75c582186a006c9e7b0"}, + {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ff54340fc1129e8e181827e2234af3ff659b4f17d9bbe77f43bc19e6577fadec"}, + {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:54c8cee662b5f8c30ad7eedfc26123f845f007798e4ff1001d9528fe959fd23c"}, + {file = "yarl-1.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e97a29b37830ba1262d8dfd48ddb5b28ad4d3ebecc5d93a9c7591d98641ec737"}, + {file = "yarl-1.12.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c89894cc6f6ddd993813e79244b36b215c14f65f9e4f1660b1f2ba9e5594b95"}, + {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:712ba8722c0699daf186de089ddc4677651eb9875ed7447b2ad50697522cbdd9"}, + {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:6e9a9f50892153bad5046c2a6df153224aa6f0573a5a8ab44fc54a1e886f6e21"}, + {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1d4017e78fb22bc797c089b746230ad78ecd3cdb215bc0bd61cb72b5867da57e"}, + {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:f494c01b28645c431239863cb17af8b8d15b93b0d697a0320d5dd34cd9d7c2fa"}, + {file = "yarl-1.12.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:de4544b1fb29cf14870c4e2b8a897c0242449f5dcebd3e0366aa0aa3cf58a23a"}, + {file = "yarl-1.12.1-cp311-cp311-win32.whl", hash = "sha256:7564525a4673fde53dee7d4c307a961c0951918f0b8c7f09b2c9e02067cf6504"}, + {file = "yarl-1.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:f23bb1a7a6e8e8b612a164fdd08e683bcc16c76f928d6dbb7bdbee2374fbfee6"}, + {file = "yarl-1.12.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:a3e2aff8b822ab0e0bdbed9f50494b3a35629c4b9488ae391659973a37a9f53f"}, + {file = "yarl-1.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:22dda2799c8d39041d731e02bf7690f0ef34f1691d9ac9dfcb98dd1e94c8b058"}, + {file = "yarl-1.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:18c2a7757561f05439c243f517dbbb174cadfae3a72dee4ae7c693f5b336570f"}, + {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:835010cc17d0020e7931d39e487d72c8e01c98e669b6896a8b8c9aa8ca69a949"}, + {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e2254fe137c4a360b0a13173a56444f756252c9283ba4d267ca8e9081cd140ea"}, + {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f6a071d2c3d39b4104f94fc08ab349e9b19b951ad4b8e3b6d7ea92d6ef7ccaf8"}, + {file = "yarl-1.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73a183042ae0918c82ce2df38c3db2409b0eeae88e3afdfc80fb67471a95b33b"}, + {file = "yarl-1.12.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:326b8a079a9afcac0575971e56dabdf7abb2ea89a893e6949b77adfeb058b50e"}, + {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:126309c0f52a2219b3d1048aca00766429a1346596b186d51d9fa5d2070b7b13"}, + {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:ba1c779b45a399cc25f511c681016626f69e51e45b9d350d7581998722825af9"}, + {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:af1107299cef049ad00a93df4809517be432283a0847bcae48343ebe5ea340dc"}, + {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:20d817c0893191b2ab0ba30b45b77761e8dfec30a029b7c7063055ca71157f84"}, + {file = "yarl-1.12.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d4f818f6371970d6a5d1e42878389bbfb69dcde631e4bbac5ec1cb11158565ca"}, + {file = "yarl-1.12.1-cp312-cp312-win32.whl", hash = "sha256:0ac33d22b2604b020569a82d5f8a03ba637ba42cc1adf31f616af70baf81710b"}, + {file = "yarl-1.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:fd24996e12e1ba7c397c44be75ca299da14cde34d74bc5508cce233676cc68d0"}, + {file = "yarl-1.12.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dea360778e0668a7ad25d7727d03364de8a45bfd5d808f81253516b9f2217765"}, + {file = "yarl-1.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1f50a37aeeb5179d293465e522fd686080928c4d89e0ff215e1f963405ec4def"}, + {file = "yarl-1.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0274b1b7a9c9c32b7bf250583e673ff99fb9fccb389215841e2652d9982de740"}, + {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a4f3ab9eb8ab2d585ece959c48d234f7b39ac0ca1954a34d8b8e58a52064bdb3"}, + {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8d31dd0245d88cf7239e96e8f2a99f815b06e458a5854150f8e6f0e61618d41b"}, + {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a96198d5d26f40557d986c1253bfe0e02d18c9d9b93cf389daf1a3c9f7c755fa"}, + {file = "yarl-1.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ddae504cfb556fe220efae65e35be63cd11e3c314b202723fc2119ce19f0ca2e"}, + {file = "yarl-1.12.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bce00f3b1f7f644faae89677ca68645ed5365f1c7f874fdd5ebf730a69640d38"}, + {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:eee5ff934b0c9f4537ff9596169d56cab1890918004791a7a06b879b3ba2a7ef"}, + {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:4ea99e64b2ad2635e0f0597b63f5ea6c374791ff2fa81cdd4bad8ed9f047f56f"}, + {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:5c667b383529520b8dd6bd496fc318678320cb2a6062fdfe6d3618da6b8790f6"}, + {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d920401941cb898ef089422e889759dd403309eb370d0e54f1bdf6ca07fef603"}, + {file = "yarl-1.12.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:501a1576716032cc6d48c7c47bcdc42d682273415a8f2908e7e72cb4625801f3"}, + {file = "yarl-1.12.1-cp313-cp313-win32.whl", hash = "sha256:24416bb5e221e29ddf8aac5b97e94e635ca2c5be44a1617ad6fe32556df44294"}, + {file = "yarl-1.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:71af3766bb46738d12cc288d9b8de7ef6f79c31fd62757e2b8a505fe3680b27f"}, + {file = "yarl-1.12.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:c924deab8105f86980983eced740433fb7554a7f66db73991affa4eda99d5402"}, + {file = "yarl-1.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5fb475a4cdde582c9528bb412b98f899680492daaba318231e96f1a0a1bb0d53"}, + {file = "yarl-1.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:36ee0115b9edca904153a66bb74a9ff1ce38caff015de94eadfb9ba8e6ecd317"}, + {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2631c9d7386bd2d4ce24ecc6ebf9ae90b3efd713d588d90504eaa77fec4dba01"}, + {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2376d8cf506dffd0e5f2391025ae8675b09711016656590cb03b55894161fcfa"}, + {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:24197ba3114cc85ddd4091e19b2ddc62650f2e4a899e51b074dfd52d56cf8c72"}, + {file = "yarl-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfdf419bf5d3644f94cd7052954fc233522f5a1b371fc0b00219ebd9c14d5798"}, + {file = "yarl-1.12.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8112f640a4f7e7bf59f7cabf0d47a29b8977528c521d73a64d5cc9e99e48a174"}, + {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:607d12f0901f6419a8adceb139847c42c83864b85371f58270e42753f9780fa6"}, + {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:664380c7ed524a280b6a2d5d9126389c3e96cd6e88986cdb42ca72baa27421d6"}, + {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:0d0a5e87bc48d76dfcfc16295201e9812d5f33d55b4a0b7cad1025b92bf8b91b"}, + {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:eff6bac402719c14e17efe845d6b98593c56c843aca6def72080fbede755fd1f"}, + {file = "yarl-1.12.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:22839d1d1eab9e4b427828a88a22beb86f67c14d8ff81175505f1cc8493f3500"}, + {file = "yarl-1.12.1-cp38-cp38-win32.whl", hash = "sha256:717f185086bb9d817d4537dd18d5df5d657598cd00e6fc22e4d54d84de266c1d"}, + {file = "yarl-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:71978ba778948760cff528235c951ea0ef7a4f9c84ac5a49975f8540f76c3f73"}, + {file = "yarl-1.12.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:30ffc046ebddccb3c4cac72c1a3e1bc343492336f3ca86d24672e90ccc5e788a"}, + {file = "yarl-1.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f10954b233d4df5cc3137ffa5ced97f8894152df817e5d149bf05a0ef2ab8134"}, + {file = "yarl-1.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2e912b282466444023610e4498e3795c10e7cfd641744524876239fcf01d538d"}, + {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6af871f70cfd5b528bd322c65793b5fd5659858cdfaa35fbe563fb99b667ed1f"}, + {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c3e4e1f7b08d1ec6b685ccd3e2d762219c550164fbf524498532e39f9413436e"}, + {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9a7ee79183f0b17dcede8b6723e7da2ded529cf159a878214be9a5d3098f5b1e"}, + {file = "yarl-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96c8ff1e1dd680e38af0887927cab407a4e51d84a5f02ae3d6eb87233036c763"}, + {file = "yarl-1.12.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e9905fc2dc1319e4c39837b906a024cf71b1261cc66b0cd89678f779c0c61f5"}, + {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:01549468858b87d36f967c97d02e6e54106f444aeb947ed76f8f71f85ed07cec"}, + {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:96b34830bd6825ca0220bf005ea99ac83eb9ce51301ddb882dcf613ae6cd95fb"}, + {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2aee7594d2c2221c717a8e394bbed4740029df4c0211ceb0f04815686e99c795"}, + {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:15871130439ad10abb25a4631120d60391aa762b85fcab971411e556247210a0"}, + {file = "yarl-1.12.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:838dde2cb570cfbb4cab8a876a0974e8b90973ea40b3ac27a79b8a74c8a2db15"}, + {file = "yarl-1.12.1-cp39-cp39-win32.whl", hash = "sha256:eacbcf30efaca7dc5cb264228ffecdb95fdb1e715b1ec937c0ce6b734161e0c8"}, + {file = "yarl-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:76a59d1b63de859398bc7764c860a769499511463c1232155061fe0147f13e01"}, + {file = "yarl-1.12.1-py3-none-any.whl", hash = "sha256:dc3192a81ecd5ff954cecd690327badd5a84d00b877e1573f7c9097ce13e5bfb"}, + {file = "yarl-1.12.1.tar.gz", hash = "sha256:5b860055199aec8d6fe4dcee3c5196ce506ca198a50aab0059ffd26e8e815828"}, ] [package.dependencies] diff --git a/pyproject.toml b/pyproject.toml index c531e1312..8af2937d6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,4 +102,7 @@ markers = [ "indexer_jobs_user_ops: Tests related to the indexer jobs user ops", "indexer_address_index: Tests related to the indexer address index", "explorer_api: Test explorer related API", + "indexer_jobs_day_mining: Tests related to the indexer day mining", + "ens: Tests related to the indexer ENS", + "pipeline: Continuous Integration" ] \ No newline at end of file