├── .dockerignore ├── .flake8 ├── .github └── workflows │ ├── ci.yaml │ ├── code-style-check.yaml │ ├── push-image.yaml │ └── ut.yaml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── api ├── __init__.py ├── app │ ├── __init__.py │ ├── address │ │ ├── __init__.py │ │ ├── features.py │ │ ├── models.py │ │ └── routes.py │ ├── api.py │ ├── cache.py │ ├── config.py │ ├── contract │ │ ├── __init__.py │ │ ├── contract_verify.py │ │ └── routes.py │ ├── db_service │ │ ├── __init__.py │ │ ├── af_token_deposit.py │ │ ├── blocks.py │ │ ├── contract_internal_transactions.py │ │ ├── contracts.py │ │ ├── daily_transactions_aggregates.py │ │ ├── logs.py │ │ ├── tokens.py │ │ ├── traces.py │ │ ├── transactions.py │ │ └── wallet_addresses.py │ ├── ens │ │ ├── __init__.py │ │ └── ens.py │ ├── explorer │ │ ├── __init__.py │ │ └── routes.py │ ├── l2_explorer │ │ ├── __init__.py │ │ └── routes.py │ ├── limiter.py │ ├── main.py │ ├── token │ │ └── __init__.py │ ├── user_operation │ │ ├── __init__.py │ │ └── routes.py │ └── utils │ │ ├── __init__.py │ │ ├── fill_info.py │ │ ├── format_utils.py │ │ ├── parse_utils.py │ │ ├── token_utils.py │ │ ├── utils.py │ │ └── web3_utils.py └── tests │ ├── app │ └── test_cyber_mainnet_explorer.py │ └── conftest.py ├── cli ├── __init__.py ├── aggregates.py ├── api.py ├── db.py ├── logo.py ├── reorg.py └── stream.py ├── codecov.yml ├── common ├── __init__.py ├── converter │ ├── __init__.py │ └── pg_converter.py ├── models │ ├── __init__.py │ ├── all_features_value_records.py │ ├── block_timestamp_mapper.py │ ├── blocks.py │ ├── bridge.py │ ├── coin_balances.py │ ├── contract_internal_transactions.py │ ├── contracts.py │ ├── current_token_balances.py │ ├── erc1155_token_id_details.py │ ├── erc1155_token_transfers.py │ ├── erc20_token_transfers.py │ ├── erc721_token_id_changes.py │ ├── erc721_token_id_details.py │ ├── erc721_token_transfers.py │ ├── exception_records.py │ ├── fix_record.py │ ├── logs.py │ ├── period_wallet_addresses_aggregates.py │ ├── scheduled_metadata.py │ ├── sync_record.py │ ├── token_balances.py │ ├── token_hourly_price.py │ ├── token_prices.py │ ├── tokens.py │ ├── traces.py │ └── transactions.py ├── services │ ├── __init__.py │ ├── hemera_postgresql_service.py │ ├── postgresql_service.py │ └── sqlalchemy_session.py └── utils │ ├── __init__.py │ ├── abi_code_utils.py │ ├── bridge_utils.py │ ├── cache_utils.py │ ├── config.py │ ├── db_utils.py │ ├── exception_control.py │ ├── file_utils.py │ ├── format_utils.py │ ├── module_loading.py │ └── web3_utils.py ├── config ├── indexer-config-base.yaml ├── indexer-config-bsc.yaml ├── indexer-config-cyber-prod.yaml ├── indexer-config-eth.yaml ├── indexer-config-mantle.yaml ├── indexer-config-morph-mainnet-prod-bridge-l1.yaml ├── indexer-config-morph-mainnet-prod.yaml ├── indexer-config-template.yaml └── indexer-config.yaml ├── docker-compose ├── docker-compose.yaml └── hemera-indexer.env ├── docs ├── AWS.md ├── README.md └── images │ └── aws │ ├── aws-launch-instance.png │ ├── disk-size.png │ ├── ec2-portal.png │ ├── key-pair.png │ └── launch-instance.jpg ├── enumeration ├── __init__.py ├── entity_type.py ├── record_level.py ├── schedule_mode.py └── token_type.py ├── hemera.py ├── indexer ├── __init__.py ├── aggr_jobs │ ├── __init__.py │ ├── aggr_base_job.py │ ├── aggr_job_scheduler.py │ ├── disorder_jobs │ │ ├── daily_feature_holding_balance_uniswap_v3.sql │ │ ├── daily_wallet_addresses_aggregates.sql │ │ ├── disorder_job.py │ │ └── models │ │ │ ├── daily_feature_uniswap_v3_pool_prices.py │ │ │ └── daily_feature_uniswap_v3_token_details.py │ ├── order_jobs │ │ ├── models │ │ │ ├── period_feature_holding_balance_merchantmoe.py │ │ │ ├── period_feature_holding_balance_uniswap_v3.py │ │ │ ├── period_feature_merchant_moe_token_bin.py │ │ │ ├── period_feature_uniswap_v3_pool_prices.py │ │ │ └── period_feature_uniswap_v3_token_details.py │ │ ├── order_job.py │ │ ├── period_feature_holding_balance_merchantmoe.sql │ │ ├── period_feature_holding_balance_uniswap_v3.sql │ │ └── period_wallet_addresses_aggregates.sql │ └── utils.py ├── cache │ └── cache_dict.py ├── controller │ ├── __init__.py │ ├── aggregates_controller.py │ ├── base_controller.py │ ├── dispatcher │ │ ├── __init__.py │ │ ├── aggregates_dispatcher.py │ │ └── base_dispatcher.py │ ├── reorg_controller.py │ ├── scheduler │ │ ├── __init__.py │ │ ├── job_scheduler.py │ │ └── reorg_scheduler.py │ └── stream_controller.py ├── domain │ ├── __init__.py │ ├── block.py │ ├── block_ts_mapper.py │ ├── coin_balance.py │ ├── contract.py │ ├── contract_internal_transaction.py │ ├── current_token_balance.py │ ├── log.py │ ├── receipt.py │ ├── token.py │ ├── token_balance.py │ ├── token_id_infos.py │ ├── token_transfer.py │ ├── trace.py │ └── transaction.py ├── executors │ ├── __init__.py │ ├── batch_work_executor.py │ └── bounded_executor.py ├── exporters │ ├── __init__.py │ ├── base_exporter.py │ ├── console_item_exporter.py │ ├── csv_file_item_exporter.py │ ├── hemera_address_postgres_item_exporter.py │ ├── item_exporter.py │ ├── json_file_item_exporter.py │ └── postgres_item_exporter.py ├── jobs │ ├── __init__.py │ ├── base_job.py │ ├── check_block_consensus_job.py │ ├── export_blocks_job.py │ ├── export_coin_balances_job.py │ ├── export_contracts_job.py │ ├── export_reorg_job.py │ ├── export_token_balances_job.py │ ├── export_token_id_infos_job.py │ ├── export_tokens_and_transfers_job.py │ ├── export_traces_job.py │ ├── export_transactions_and_logs_job.py │ └── source_job │ │ ├── __init__.py │ │ ├── csv_source_job.py │ │ └── pg_source_job.py ├── modules │ ├── __init__.py │ ├── bridge │ │ ├── README.md │ │ ├── __init__.py │ │ ├── arbitrum │ │ │ ├── __init__.py │ │ │ ├── arb_bridge_on_l1_job.py │ │ │ ├── arb_bridge_on_l2_job.py │ │ │ ├── arb_conf.py │ │ │ ├── arb_network.py │ │ │ ├── arb_parser.py │ │ │ └── arb_rlp.py │ │ ├── bedrock │ │ │ ├── __init__.py │ │ │ ├── bedrock_bridge_on_l1_job.py │ │ │ ├── bedrock_bridge_on_l2_job.py │ │ │ └── parser │ │ │ │ ├── __init__.py │ │ │ │ ├── bedrock_bridge_parser.py │ │ │ │ └── function_parser │ │ │ │ ├── __init__.py │ │ │ │ ├── finalize_bridge_erc20.py │ │ │ │ ├── finalize_bridge_erc721.py │ │ │ │ └── finalize_bridge_eth.py │ │ ├── bridge_utils.py │ │ ├── domain │ │ │ ├── __init__.py │ │ │ ├── arbitrum.py │ │ │ ├── morph.py │ │ │ └── op_bedrock.py │ │ ├── items.py │ │ └── morphl2 │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ ├── event.py │ │ │ └── function.py │ │ │ ├── morph_bridge_on_l1_job.py │ │ │ ├── morph_bridge_on_l2_job.py │ │ │ └── parser │ │ │ ├── deposited_transaction.py │ │ │ └── parser.py │ ├── custom │ │ ├── README.md │ │ ├── __init__.py │ │ ├── address_index │ │ │ ├── __init__.py │ │ │ ├── address_index_job.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ ├── address_contract_operation.py │ │ │ │ ├── address_internal_transaction.py │ │ │ │ ├── address_nft_1155_holders.py │ │ │ │ ├── address_nft_transfer.py │ │ │ │ ├── address_token_holder.py │ │ │ │ ├── address_token_transfer.py │ │ │ │ ├── address_transaction.py │ │ │ │ └── token_address_nft_inventory.py │ │ │ ├── endpoint │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── address_contract_operation.py │ │ │ │ ├── address_index_daily_stats.py │ │ │ │ ├── address_index_stats.py │ │ │ │ ├── address_internal_transaciton.py │ │ │ │ ├── address_nft_1155_holders.py │ │ │ │ ├── address_nft_transfers.py │ │ │ │ ├── address_token_holders.py │ │ │ │ ├── address_token_transfers.py │ │ │ │ ├── address_transactions.py │ │ │ │ ├── scheduled_metadata.py │ │ │ │ ├── token_address_index.py │ │ │ │ ├── token_address_index_daily_stats.py │ │ │ │ └── token_address_nft_inventories.py │ │ │ ├── schemas │ │ │ │ ├── __init__.py │ │ │ │ └── api.py │ │ │ └── utils │ │ │ │ ├── __init__.py │ │ │ │ ├── helpers.py │ │ │ │ └── score.py │ │ ├── all_features_value_record.py │ │ ├── blue_chip │ │ │ ├── __init__.py │ │ │ ├── constants.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── feature_blue_chip.py │ │ │ ├── export_blue_chip_job.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ └── feature_blue_chip_holders.py │ │ ├── common_utils.py │ │ ├── cyber_id │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ │ ├── event.py │ │ │ │ └── function.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ └── cyber_domain.py │ │ │ ├── export_cyber_id_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ └── cyber_models.py │ │ │ ├── sql │ │ │ │ └── cyber_id_table_migration.sql │ │ │ └── utils.py │ │ ├── day_mining │ │ │ ├── __init__.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── current_traits_activeness.py │ │ │ ├── export_activeness.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ └── current_traits_activeness.py │ │ ├── demo_job │ │ │ ├── __init__.py │ │ │ ├── demo_job.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── erc721_token_mint.py │ │ │ └── models │ │ │ │ └── erc721_token_mint.py │ │ ├── deposit_to_l2 │ │ │ ├── __init__.py │ │ │ ├── config.ini │ │ │ ├── deposit_parser.py │ │ │ ├── deposit_to_l2_job.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ ├── address_token_deposit.py │ │ │ │ └── token_deposit_transaction.py │ │ │ ├── endpoint │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_token_deposits__transactions.py │ │ │ │ └── af_token_deposits_current.py │ │ ├── eigen_layer │ │ │ ├── __init__.py │ │ │ ├── abi.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ └── eigen_layer_domain.py │ │ │ ├── eigen_layer_job.py │ │ │ ├── endpoint │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_eigen_layer_address_current.py │ │ │ │ └── af_eigen_layer_records.py │ │ │ └── sql │ │ │ │ └── eigen_layer_table_migration.sql │ │ ├── etherfi │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ │ ├── __init__.py │ │ │ │ ├── event.py │ │ │ │ └── functions.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ ├── eeth.py │ │ │ │ └── lrts.py │ │ │ ├── export_etherfi_share_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── eeth.py │ │ │ │ └── lrts.py │ │ │ └── sql │ │ │ │ └── ether_fi_sql_migration.sql │ │ ├── feature_type.py │ │ ├── hemera_ens │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ │ ├── 0x00000000000c2e074ec69a0dfb2997ba6c7d2e1e.json │ │ │ │ ├── 0x00000000008794027c69c26d2a048dbec09de67c.json │ │ │ │ ├── 0x084b1c3c81545d370f3634392de611caabff8148.json │ │ │ │ ├── 0x0904dac3347ea47d208f3fd67402d039a3b99859.json │ │ │ │ ├── 0x1d6552e8f46fd509f3918a174fe62c34b42564ae.json │ │ │ │ ├── 0x1da022710df5002339274aadee8d58218e9d6ab5.json │ │ │ │ ├── 0x226159d592e2b063810a10ebf6dcbada94ed68b8.json │ │ │ │ ├── 0x231b0ee14048e9dccd1d247744d114a4eb5e8e63.json │ │ │ │ ├── 0x253553366da8546fc250f225fe3d25d0c782303b.json │ │ │ │ ├── 0x27c9b34eb43523447d3e1bcf26f009d814522687.json │ │ │ │ ├── 0x283af0b28c62c092c9727f1ee09c02ca627eb7f5.json │ │ │ │ ├── 0x314159265dd8dbb310642f98f50c066173c1259b.json │ │ │ │ ├── 0x323a76393544d5ecca80cd6ef2a560c6a395b7e3.json │ │ │ │ ├── 0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json │ │ │ │ ├── 0x4976fb03c32e5b8cfe2b6ccb31c09ba78ebaba41.json │ │ │ │ ├── 0x4fe4e666be5752f1fdd210f4ab5de2cc26e3e0e8.json │ │ │ │ ├── 0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85.json │ │ │ │ ├── 0x6090a6e47849629b7245dfa1ca21d94cd15878ef.json │ │ │ │ ├── 0x60c7c2a24b5e86c38639fd1586917a8fef66a56d.json │ │ │ │ ├── 0x6109dd117aa5486605fc85e040ab00163a75c662.json │ │ │ │ ├── 0x690f0581ececcf8389c223170778cd9d029606f2.json │ │ │ │ ├── 0x705bfbcfccde554e11df213bf6d463ea00dd57cc.json │ │ │ │ ├── 0x9062c0a6dbd6108336bcbe4593a3d1ce05512069.json │ │ │ │ ├── 0x911143d946ba5d467bfc476491fdb235fef4d667.json │ │ │ │ ├── 0x9b6d20f524367d7e98ed849d37fc662402dca7fb.json │ │ │ │ ├── 0xa2c122be93b0074270ebee7f6b7292c7deb45047.json │ │ │ │ ├── 0xa2f428617a523837d4adc81c67a296d42fd95e86.json │ │ │ │ ├── 0xa58e81fe9b61b5c3fe2afd33cf304c454abfc7cb.json │ │ │ │ ├── 0xab528d626ec275e3fad363ff1393a41f581c5897.json │ │ │ │ ├── 0xb1377e4f32e6746444970823d5506f98f5a04201.json │ │ │ │ ├── 0xb6e040c9ecaae172a89bd561c5f73e1c48d28cd9.json │ │ │ │ ├── 0xb9d374d0fe3d8341155663fae31b7beae0ae233a.json │ │ │ │ ├── 0xc1735677a60884abbcf72295e88d47764beda282.json │ │ │ │ ├── 0xc32659651d137a18b79925449722855aa327231d.json │ │ │ │ ├── 0xd4416b13d2b3a9abae7acd5d6c2bbdbe25686401.json │ │ │ │ ├── 0xd7a029db2585553978190db5e85ec724aa4df23f.json │ │ │ │ ├── 0xdaaf96c344f63131acadd0ea35170e7892d3dfba.json │ │ │ │ ├── 0xe65d8aaf34cb91087d1598e0a15b582f57f217d9.json │ │ │ │ ├── 0xf0ad5cad05e10572efceb849f6ff0c68f9700455.json │ │ │ │ ├── 0xf7c83bd0c50e7a72b55a39fe0dabf5e3a330d749.json │ │ │ │ ├── 0xfe89cc7abb2c4183683ab71653c4cdc9b02d44b7.json │ │ │ │ ├── 0xff252725f6122a92551a5fa9a6b6bf10eb0be035.json │ │ │ │ └── 0xffc8ca4e83416b7e0443ff430cc245646434b647.json │ │ │ ├── endpoint │ │ │ │ ├── __init__.py │ │ │ │ ├── action_types.py │ │ │ │ └── routes.py │ │ │ ├── ens_abi.py │ │ │ ├── ens_conf.py │ │ │ ├── ens_domain.py │ │ │ ├── ens_handler.py │ │ │ ├── ens_hash.py │ │ │ ├── export_ens_job.py │ │ │ ├── extractors.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_ens_address_current.py │ │ │ │ ├── af_ens_event.py │ │ │ │ └── af_ens_node_current.py │ │ │ └── util.py │ │ ├── init_capital │ │ │ ├── __init__.py │ │ │ ├── abi.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ └── init_capital_domains.py │ │ │ ├── endpoints │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── export_init_capital_job.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ └── init_capital_models.py │ │ ├── karak │ │ │ ├── __init__.py │ │ │ ├── endpoints │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── export_karak_job.py │ │ │ ├── karak_abi.py │ │ │ ├── karak_conf.py │ │ │ ├── karak_domain.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_karak_address_current.py │ │ │ │ ├── af_karak_records.py │ │ │ │ └── af_karak_vault_token.py │ │ │ └── sql │ │ │ │ └── karak_table_migration.sql │ │ ├── lido │ │ │ ├── README.md │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ │ ├── __init__.py │ │ │ │ ├── event.py │ │ │ │ └── functions.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ └── seth.py │ │ │ ├── export_lido_share_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ └── seth.py │ │ │ └── sql │ │ │ │ └── lido_sql_migration.sql │ │ ├── merchant_moe │ │ │ ├── __init__.py │ │ │ ├── abi.py │ │ │ ├── constants.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ ├── erc1155_token_holding.py │ │ │ │ └── merchant_moe.py │ │ │ ├── endpoints │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── export_merchant_moe_1155_token_holding_detail_job.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ ├── feature_erc1155_token_current_holdings.py │ │ │ │ ├── feature_erc1155_token_current_supply.py │ │ │ │ ├── feature_erc1155_token_holding.py │ │ │ │ ├── feature_erc1155_token_supply.py │ │ │ │ ├── feature_merchant_moe_pool.py │ │ │ │ ├── feature_merchant_moe_pool_record_status.py │ │ │ │ ├── feature_merchant_moe_pool_records.py │ │ │ │ ├── feature_merchant_moe_token_bin.py │ │ │ │ └── feature_merchant_moe_token_current_bin.py │ │ ├── opensea │ │ │ ├── __init__.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ ├── address_opensea_transactions.py │ │ │ │ └── opensea_order.py │ │ │ ├── endpoint │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── models │ │ │ │ ├── address_opensea_profile.py │ │ │ │ ├── address_opensea_transaction.py │ │ │ │ ├── daily_address_opensea_stats.py │ │ │ │ ├── opensea_crypto_mapping.py │ │ │ │ ├── opensea_order.py │ │ │ │ └── scheduled_metadata.py │ │ │ ├── opensea_job.py │ │ │ └── parser │ │ │ │ ├── __init__.py │ │ │ │ └── opensea_contract_parser.py │ │ ├── pendle │ │ │ ├── __init__.py │ │ │ ├── abi │ │ │ │ ├── event.py │ │ │ │ └── function.py │ │ │ ├── domains │ │ │ │ ├── __init__.py │ │ │ │ └── market.py │ │ │ ├── export_pendle_balance_job.py │ │ │ ├── export_pendle_pools_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ └── market.py │ │ │ └── sql │ │ │ │ └── pendle_sql_migration.sql │ │ ├── project_contracts │ │ │ ├── __init__.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── project_contract_domain.py │ │ │ ├── export_project_contracts_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── project_contract.py │ │ │ │ └── projects.py │ │ │ └── sql │ │ │ │ └── project_contract_table_migration.sql │ │ ├── staking_fbtc │ │ │ ├── __init__.py │ │ │ ├── config.ini │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ ├── af_staked_transferred_balance.py │ │ │ │ └── feature_staked_fbtc_detail.py │ │ │ ├── endpoints │ │ │ │ ├── __init__.py │ │ │ │ └── routes.py │ │ │ ├── export_staked_fbtc_detail_job.py │ │ │ ├── export_staked_transferred_balance_job.py │ │ │ ├── export_transferred_fbtc_detail_job.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_staked_transferred_balance_current.py │ │ │ │ ├── af_staked_transferred_balance_hist.py │ │ │ │ ├── feature_staked_fbtc_detail_records.py │ │ │ │ └── feature_staked_fbtc_detail_status.py │ │ │ └── utils.py │ │ ├── stats │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ ├── daily_addresses_stats.py │ │ │ │ ├── daily_blocks_stats.py │ │ │ │ ├── daily_bridge_transactions_stats.py │ │ │ │ ├── daily_tokens_stats.py │ │ │ │ └── daily_transactions_stats.py │ │ ├── total_supply │ │ │ ├── __init__.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── erc20_total_supply.py │ │ │ ├── export_erc20_total_supply_job.py │ │ │ └── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_erc20_total_supply_current.py │ │ │ │ └── af_erc20_total_supply_hist.py │ │ ├── uniswap_v2 │ │ │ ├── __init__.py │ │ │ ├── aerodrome_abi.py │ │ │ ├── domain │ │ │ │ ├── __init__.py │ │ │ │ └── feature_uniswap_v2.py │ │ │ ├── models │ │ │ │ ├── __init__.py │ │ │ │ ├── af_uniswap_v2_swap_event.py │ │ │ │ └── feature_uniswap_v2_pools.py │ │ │ ├── uniswapv2_abi.py │ │ │ ├── uniswapv2_pools.py │ │ │ ├── uniswapv2_swap_event.py │ │ │ └── uniswapv2_total_supply.py │ │ └── uniswap_v3 │ │ │ ├── __init__.py │ │ │ ├── agni_abi.py │ │ │ ├── constants.py │ │ │ ├── domains │ │ │ ├── __init__.py │ │ │ └── feature_uniswap_v3.py │ │ │ ├── endpoints │ │ │ ├── __init__.py │ │ │ └── routes.py │ │ │ ├── models │ │ │ ├── __init__.py │ │ │ ├── feature_uniswap_v3_collect_fee_records.py │ │ │ ├── feature_uniswap_v3_liquidity_records.py │ │ │ ├── feature_uniswap_v3_pool_current_prices.py │ │ │ ├── feature_uniswap_v3_pool_prices.py │ │ │ ├── feature_uniswap_v3_pools.py │ │ │ ├── feature_uniswap_v3_swap_records.py │ │ │ ├── feature_uniswap_v3_token_current_status.py │ │ │ ├── feature_uniswap_v3_token_details.py │ │ │ └── feature_uniswap_v3_tokens.py │ │ │ ├── swapsicle_abi.py │ │ │ ├── tests │ │ │ └── __init__.py │ │ │ ├── uniswap_v3_pool_job.py │ │ │ ├── uniswap_v3_pool_price_job.py │ │ │ ├── uniswap_v3_token_job.py │ │ │ ├── uniswapv3_abi.py │ │ │ └── util.py │ └── user_ops │ │ ├── __init__.py │ │ ├── domain │ │ ├── __init__.py │ │ └── user_operations.py │ │ ├── export_user_ops_job.py │ │ └── models │ │ ├── __init__.py │ │ └── user_operation_results.py ├── specification │ └── specification.py ├── tests │ ├── __init__.py │ ├── address_index │ │ └── test_address_index_job.py │ ├── bridge │ │ ├── __init__.py │ │ ├── arbitrum │ │ │ ├── __init__.py │ │ │ ├── arbitrum_bridge_parser_test.py │ │ │ ├── jobs │ │ │ │ ├── __init__.py │ │ │ │ ├── test_arb_eth.py │ │ │ │ └── test_dodo_test_arb_sepolia.py │ │ │ └── rlp_test.py │ │ ├── bedrock │ │ │ ├── __init__.py │ │ │ ├── jobs │ │ │ │ └── test_fetch_op_bedrock_bridge_on_data_job.py │ │ │ └── parser │ │ │ │ ├── function_parser │ │ │ │ ├── __init__.py │ │ │ │ ├── test_finalize_bridge_erc20.py │ │ │ │ ├── test_finalize_bridge_erc721.py │ │ │ │ └── test_finalize_bridge_eth.py │ │ │ │ └── test_bedrock_bridge_parser.py │ │ └── morphl2 │ │ │ └── jobs │ │ │ └── test_fetch_morph_bridge_on_l1.py │ ├── custom_jobs │ │ ├── test_export_etherfi_share.py │ │ └── test_export_lido_share.py │ ├── day_mining │ │ ├── __init__.py │ │ └── test_export_day_mining.py │ ├── domain │ │ └── test_token_transfers.py │ ├── ens │ │ └── test_namehash.py │ ├── jobs │ │ ├── __init__.py │ │ ├── test_export_blocks_job.py │ │ ├── test_export_coin_balances_job.py │ │ ├── test_export_contracts_job.py │ │ ├── test_export_token_balances_and_holders.py │ │ ├── test_export_token_transfers_job.py │ │ ├── test_export_trace_job.py │ │ ├── test_export_transactions_job.py │ │ └── test_token_id_infos_job.py │ ├── json_rpc_to_dataclass.py │ ├── user_ops │ │ ├── __init__.py │ │ └── test_export_user_ops_job.py │ ├── utils.py │ └── utils │ │ ├── __init__.py │ │ ├── test_multicall_helper.py │ │ └── test_utils.py └── utils │ ├── __init__.py │ ├── abi.py │ ├── abi_setting.py │ ├── atomic_counter.py │ ├── collection_utils.py │ ├── exception_recorder.py │ ├── json_rpc_requests.py │ ├── limit_reader.py │ ├── logging_utils.py │ ├── multicall_hemera │ ├── __init__.py │ ├── abi.py │ ├── call.py │ ├── constants.py │ ├── multi_call.py │ ├── multi_call_helper.py │ └── util.py │ ├── parameter_utils.py │ ├── progress_logger.py │ ├── provider.py │ ├── reorg.py │ ├── rpc_utils.py │ ├── sync_recorder.py │ ├── thread_local_proxy.py │ └── token_fetcher.py ├── licenses ├── LICENSE-ethereum-etl.txt └── LICENSE-multicall.txt ├── migrations ├── env.py ├── manual_versions │ ├── 20240704_base_version.sql │ ├── 20240708_tokens_table_add_column_block_number.sql │ ├── 20240716_add_api_server_table.sql │ ├── 20240725_update_index_table_optimize.sql │ ├── 20240726_modify_sync_record_table.sql │ ├── 20240731_add_feature_records_and_uniswap_v3_.sql │ ├── 20240731_add_user_ops_table.sql │ ├── 20240802_add_exception_recorder_table.sql │ ├── 20240802_add_l2_chain_table.sql │ ├── 20240802_add_uniswap_v2_table.sql │ ├── 20240805_add_column_to_contracts_table.sql │ ├── 20240806_add_current_traits_activeness.sql │ ├── 20240808_add_blue_chip_holding.sql │ ├── 20240813_add_daily_wallet_address_tables.sql │ ├── 20240827_add_token_price_table.sql │ ├── 20240830_add_address_token_deposit_table.sql │ ├── 20240831_add_ens.sql │ ├── 20240906_add_uniswap_v3_enhance_table.sql │ ├── 20240910_add_address_index.sql │ ├── 20240911_add_opensea.sql │ ├── 20240912_add_merchant_and_uniswap_daily_table.sql │ ├── 20240927_add_merchant_moe_table.sql │ ├── 20241017_earlier_table_change.sql │ ├── 20241105_add_address_index_and_stats.sql │ ├── 20241121_add_failure_records_table.sql │ └── 20241128_update_table_for_0.6.0.sql ├── script.py.mako └── versions │ ├── 20240704_base_version.py │ ├── 20240708_tokens_table_add_column_block_number.py │ ├── 20240716_add_api_server_table.py │ ├── 20240725_update_index_table_optimize.py │ ├── 20240726_modify_sync_record_table.py │ ├── 20240731_add_feature_records_and_uniswap_v3_.py │ ├── 20240731_add_user_ops_table.py │ ├── 20240802_add_exception_recorder_table.py │ ├── 20240802_add_l2_chain_table.py │ ├── 20240802_add_uniswap_v2_table.py │ ├── 20240805_add_column_to_contracts_table.py │ ├── 20240806_add_current_traits_activeness.py │ ├── 20240808_add_blue_chip_holding.py │ ├── 20240813_add_daily_wallet_address_tables.py │ ├── 20240827_add_token_price_table.py │ ├── 20240830_add_address_token_deposit_table.py │ ├── 20240831_add_ens.py │ ├── 20240906_add_uniswap_v3_enhance_table.py │ ├── 20240910_add_address_index.py │ ├── 20240911_add_opensea.py │ ├── 20240912_add_merchant_and_uniswap_daily_table.py │ ├── 20240927_add_merchant_moe_table.py │ ├── 20241017_earlier_table_change.py │ ├── 20241105_add_address_index_and_stats.py │ ├── 20241121_add_failure_records_table.py │ └── 20241128_update_table_for_0.6.0.py ├── poetry.lock ├── pyproject.toml ├── resource └── hemera.ini.example ├── scheduler ├── __init__.py └── scheduler.py └── setup.py /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | 4 | __pycache__ 5 | *.pyc 6 | *.pyo 7 | *.pyd 8 | .Python 9 | env 10 | pip-log.txt 11 | pip-delete-this-directory.txt 12 | .tox 13 | .coverage 14 | .coverage.* 15 | .cache 16 | nosetests.xml 17 | coverage.xml 18 | *.cover 19 | *.log 20 | .mypy_cache 21 | .pytest_cache 22 | .hypothesis 23 | 24 | venv 25 | *.venv 26 | env/ 27 | venv/ 28 | ENV/ 29 | 30 | .vscode 31 | .idea 32 | 33 | .DS_Store 34 | Thumbs.db 35 | 36 | 37 | dist 38 | build 39 | *.egg-info 40 | 41 | *.swp 42 | *.bak 43 | *.tmp 44 | *~ 45 | 46 | sync_record* 47 | 48 | docker-compose/ -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | exclude = 4 | *.egg-info, 5 | *.pyc, 6 | .git, 7 | .venv*, 8 | build, 9 | docs/*, 10 | dist, 11 | docker, 12 | venv*, 13 | .venv*, 14 | whitelist.py, 15 | tasks.py 16 | ignore = 17 | E126 18 | E203 19 | E231 20 | E701 21 | E704 22 | F405 23 | N801 24 | N802 25 | N803 26 | N806 27 | N815 28 | W503 -------------------------------------------------------------------------------- /.github/workflows/code-style-check.yaml: -------------------------------------------------------------------------------- 1 | name: Code Quality Check 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | pre-commit: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Python 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: '3.9' 21 | 22 | - name: Install dependencies 23 | run: | 24 | python -m pip install --upgrade pip 25 | pip install pre-commit 26 | 27 | - name: Run pre-commit 28 | run: make format 29 | -------------------------------------------------------------------------------- /.github/workflows/ut.yaml: -------------------------------------------------------------------------------- 1 | name: Python application unit test 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | test: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Set up Python 17 | uses: actions/setup-python@v3 18 | with: 19 | python-version: '3.9' 20 | 21 | - name: Install dependencies 22 | run: | 23 | pip install --upgrade pip 24 | pip install poetry 25 | poetry update 26 | poetry install -v 27 | 28 | - name: Set PYTHONPATH 29 | run: echo "PYTHONPATH=\$PYTHONPATH:$(pwd)" >> $GITHUB_ENV 30 | 31 | - name: Verify PYTHONPATH 32 | run: echo $PYTHONPATH 33 | 34 | - name: Unit Test with pytest 35 | env: 36 | ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL }}' 37 | ETHEREUM_PUBLIC_NODE_RPC_URL: '${{ secrets.ETHEREUM_PUBLIC_NODE_RPC_URL }}' 38 | LINEA_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_DEBUG_RPC_URL }}' 39 | LINEA_PUBLIC_NODE_RPC_URL: '${{ secrets.LINEA_PUBLIC_NODE_RPC_URL }}' 40 | MANTLE_PUBLIC_NODE_RPC_URL: '${{ secrets.MANTLE_PUBLIC_NODE_RPC_URL }}' 41 | MANTLE_PUBLIC_NODE_DEBUG_RPC_URL: '${{ secrets.MANTLE_PUBLIC_NODE_DEBUG_RPC_URL }}' 42 | run: | 43 | export PYTHONPATH=$(pwd) 44 | make test indexer 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # Distribution / packaging 7 | .Python 8 | env/ 9 | build/ 10 | develop-eggs/ 11 | dist/ 12 | downloads/ 13 | eggs/ 14 | .eggs/ 15 | lib/ 16 | lib64/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # JetBrains IDE 26 | .idea/ 27 | 28 | # Installer logs 29 | pip-log.txt 30 | pip-delete-this-directory.txt 31 | 32 | # dotenv 33 | .env 34 | 35 | # virtualenv 36 | .venv 37 | venv/ 38 | ENV/ 39 | 40 | # Output Data Files 41 | *.json 42 | *.csv 43 | docker-compose/hemera-indexer.env 44 | docker-compose/data/ 45 | docker-compose/output/ 46 | docker-compose/postgres/ 47 | 48 | # Local Config 49 | resource/hemera.ini 50 | sync_record 51 | alembic.ini 52 | !indexer/modules/custom/hemera_ens/abi/*.json 53 | !indexer/modules/custom/cyber_id/abi/*.json 54 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.1.0 4 | hooks: 5 | - id: check-yaml 6 | - id: end-of-file-fixer 7 | types: 8 | - python 9 | - id: trailing-whitespace 10 | - id: check-added-large-files 11 | 12 | - repo: https://github.com/psf/black 13 | rev: 24.4.2 14 | hooks: 15 | - id: black 16 | 17 | - repo: https://github.com/pre-commit/mirrors-isort 18 | rev: v5.9.3 19 | hooks: 20 | - id: isort 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim AS builder 2 | 3 | ENV PYTHONUNBUFFERED=1 4 | ENV PYTHONDONTWRITEBYTECODE=1 5 | 6 | RUN pip install poetry && poetry config virtualenvs.in-project true 7 | 8 | WORKDIR "/app" 9 | COPY . . 10 | 11 | RUN poetry install 12 | RUN poetry build 13 | 14 | FROM python:3.9-slim 15 | 16 | WORKDIR "/app" 17 | 18 | COPY --from=builder /app/migrations ./migrations 19 | COPY --from=builder /app/dist/*.whl . 20 | RUN pip install *.whl 21 | 22 | ENTRYPOINT ["hemera"] -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Hemera Indexer 2 | Copyright 2024, Hemera Protocol. 3 | 4 | This product includes software developed by the Hemera Protocol. 5 | For more information, visit: https://www.thehemera.com/ -------------------------------------------------------------------------------- /api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/__init__.py -------------------------------------------------------------------------------- /api/app/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/app/__init__.py -------------------------------------------------------------------------------- /api/app/address/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_restx.namespace import Namespace 2 | 3 | address_features_namespace = Namespace( 4 | "Address Features", 5 | path="/", 6 | description="Address Features API", 7 | ) 8 | -------------------------------------------------------------------------------- /api/app/address/features.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | feature_router = {} 4 | 5 | 6 | class FeatureRegistry: 7 | def __init__(self): 8 | self.features = {} 9 | self.feature_list = [] 10 | 11 | def register(self, feature_name, subcategory): 12 | def decorator(f): 13 | if feature_name not in self.features: 14 | self.features[feature_name] = {} 15 | self.feature_list.append(feature_name) 16 | self.features[feature_name][subcategory] = f 17 | 18 | @wraps(f) 19 | def wrapper(*args, **kwargs): 20 | return f(*args, **kwargs) 21 | 22 | return wrapper 23 | 24 | return decorator 25 | 26 | 27 | feature_registry = FeatureRegistry() 28 | register_feature = feature_registry.register 29 | -------------------------------------------------------------------------------- /api/app/cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import redis 5 | from flask_caching import Cache 6 | 7 | from common.utils.config import get_config 8 | 9 | app_config = get_config() 10 | # Use cache 11 | cache = Cache() 12 | 13 | 14 | class RedisDb: 15 | def __init__(self, host, cache_type=""): 16 | if cache_type == "RedisClusterCache": 17 | self.enable_cache = True 18 | self.r = redis.RedisCluster( 19 | host=host, 20 | port=6379, 21 | ssl=True, 22 | ) 23 | elif cache_type == "RedisCache": 24 | self.enable_cache = True 25 | self.r = redis.Redis( 26 | host=host, 27 | port=6379, 28 | ssl=True, 29 | decode_responses=True, 30 | ) 31 | else: 32 | self.enable_cache = False 33 | self.r = None 34 | 35 | def handle_redis_token(self, key, value=None): 36 | if value: 37 | self.r.set(key, value, ex=3600) 38 | else: 39 | redis_token = self.r.get(key) 40 | return redis_token 41 | 42 | def set_init_value(self, key, value): 43 | self.r.set(key, value) 44 | 45 | def get_next_increment_value(self, key): 46 | return self.r.incr(key) 47 | 48 | 49 | redis_db = RedisDb("127.0.0.1", "local") 50 | -------------------------------------------------------------------------------- /api/app/contract/__init__.py: -------------------------------------------------------------------------------- 1 | from flask_restx.namespace import Namespace 2 | 3 | contract_namespace = Namespace( 4 | "Explorer Contract Parser", 5 | path="/", 6 | description="Explorer Contract Parser API", 7 | ) 8 | -------------------------------------------------------------------------------- /api/app/db_service/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/app/db_service/__init__.py -------------------------------------------------------------------------------- /api/app/db_service/contracts.py: -------------------------------------------------------------------------------- 1 | from common.models import db 2 | from common.models.contracts import Contracts 3 | from common.utils.db_utils import build_entities 4 | from common.utils.format_utils import hex_str_to_bytes 5 | 6 | 7 | def get_contract_by_address(address: str, columns="*"): 8 | bytes_address = hex_str_to_bytes(address) 9 | entities = build_entities(Contracts, columns) 10 | 11 | contract = db.session.query(Contracts).with_entities(*entities).filter(Contracts.address == bytes_address).first() 12 | 13 | return contract 14 | 15 | 16 | def get_contracts_by_addresses(address_list: list[bytes], columns="*"): 17 | entities = build_entities(Contracts, columns) 18 | contracts = ( 19 | db.session.query(Contracts) 20 | .with_entities(*entities) 21 | .filter(Contracts.address.in_(list(set(address_list)))) 22 | .all() 23 | ) 24 | 25 | return contracts 26 | -------------------------------------------------------------------------------- /api/app/db_service/daily_transactions_aggregates.py: -------------------------------------------------------------------------------- 1 | from common.models import db 2 | from common.utils.db_utils import build_entities 3 | from indexer.modules.custom.stats.models.daily_transactions_stats import DailyTransactionsStats 4 | 5 | 6 | def get_daily_transactions_cnt(columns="*", limit=10): 7 | entities = build_entities(DailyTransactionsStats, columns) 8 | 9 | results = ( 10 | db.session.query(DailyTransactionsStats) 11 | .with_entities(*entities) 12 | .order_by(DailyTransactionsStats.block_date.desc()) 13 | .limit(limit) 14 | ) 15 | 16 | return results 17 | -------------------------------------------------------------------------------- /api/app/db_service/logs.py: -------------------------------------------------------------------------------- 1 | from common.models import db 2 | from common.models.logs import Logs 3 | from common.models.transactions import Transactions 4 | from common.utils.format_utils import hex_str_to_bytes 5 | 6 | 7 | def get_logs_with_input_by_hash(hash, columns="*"): 8 | bytes_hash = hex_str_to_bytes(hash.lower()) 9 | # Always get FUll Logs now 10 | 11 | logs = ( 12 | db.session.query(Logs) 13 | .filter(Logs.transaction_hash == bytes_hash, Logs.block_timestamp == Transactions.block_timestamp) 14 | .join(Transactions, Logs.transaction_hash == Transactions.hash) 15 | .add_columns(Transactions.input) 16 | .all() 17 | ) 18 | 19 | return logs 20 | 21 | 22 | def get_logs_with_input_by_address(address: str, limit=None, offset=None): 23 | address_bytes = hex_str_to_bytes(address.lower()) 24 | 25 | statement = ( 26 | db.session.query(Logs) 27 | .filter(Logs.address == address_bytes) 28 | .join(Transactions, Logs.transaction_hash == Transactions.hash) 29 | .add_columns(Transactions.input) 30 | ) 31 | 32 | if limit is not None: 33 | statement = statement.limit(limit) 34 | 35 | if offset is not None: 36 | statement = statement.offset(offset) 37 | 38 | logs = statement.all() 39 | 40 | return logs 41 | -------------------------------------------------------------------------------- /api/app/db_service/traces.py: -------------------------------------------------------------------------------- 1 | from common.models import db 2 | from common.models.traces import Traces 3 | from common.utils.db_utils import build_entities 4 | from common.utils.format_utils import hex_str_to_bytes 5 | 6 | 7 | def get_traces_by_transaction_hash(transaction_hash, columns="*"): 8 | transaction_hash = hex_str_to_bytes(transaction_hash) 9 | entities = build_entities(Traces, columns) 10 | 11 | traces = ( 12 | db.session.query(Traces) 13 | .with_entities(*entities) 14 | .filter(Traces.transaction_hash == transaction_hash) 15 | .order_by(Traces.trace_address) 16 | .all() 17 | ) 18 | 19 | return traces 20 | 21 | 22 | def get_traces_by_condition(filter_condition=None, columns="*", limit=1): 23 | entities = build_entities(Traces, columns) 24 | 25 | traces = db.session.query(Traces).with_entities(*entities).filter(filter_condition).limit(limit).all() 26 | 27 | return traces 28 | -------------------------------------------------------------------------------- /api/app/ens/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/app/ens/__init__.py -------------------------------------------------------------------------------- /api/app/explorer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | explorer_namespace = Namespace("Blockchain Explorer", path="/", description="Blockchain Explorer API") 7 | -------------------------------------------------------------------------------- /api/app/l2_explorer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | l2_explorer_namespace = Namespace( 7 | "Blockchain Explorer L2 Endpoint", 8 | path="/", 9 | description="Blockchain Explorer L2 API", 10 | ) 11 | -------------------------------------------------------------------------------- /api/app/limiter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask import request 5 | from flask_limiter import Limiter 6 | 7 | 8 | def get_real_ip() -> str: 9 | remote_address = request.remote_addr 10 | forward_address = request.headers.get("gateway-forwarded-ip") 11 | # current_app.logger.info(f"remote_address: {remote_address}") 12 | # current_app.logger.info(f"gateway-forwarded-ip: {forward_address}") 13 | # if forward_address: 14 | # remote_address = forward_address 15 | # current_app.logger.info(f"remote_address: {remote_address}") 16 | return forward_address or remote_address 17 | 18 | 19 | # https://flask-limiter.readthedocs.io/en/stable/index.html 20 | limiter = Limiter( 21 | key_func=get_real_ip, 22 | default_limits=["1800 per hour", "180 per minute"], 23 | storage_uri="memory://", 24 | ) 25 | -------------------------------------------------------------------------------- /api/app/token/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/app/token/__init__.py -------------------------------------------------------------------------------- /api/app/user_operation/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | user_operation_namespace = Namespace("User Operation Namespace", path="/", description="User Operation API") 7 | -------------------------------------------------------------------------------- /api/app/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/api/app/utils/__init__.py -------------------------------------------------------------------------------- /api/app/utils/web3_utils.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Optional 3 | 4 | from web3 import Web3 5 | 6 | from api.app.cache import app_config, cache 7 | from common.utils.abi_code_utils import decode_data 8 | 9 | w3 = Web3(Web3.HTTPProvider(app_config.rpc)) 10 | 11 | 12 | @cache.memoize(600) 13 | def get_balance(address) -> Decimal: 14 | try: 15 | if not w3.is_address(address): 16 | return Decimal(0) 17 | address = w3.to_checksum_address(address) 18 | balance = w3.eth.get_balance(address) 19 | return Decimal(balance) 20 | except Exception as e: 21 | return Decimal(0) 22 | 23 | 24 | @cache.memoize(600) 25 | def get_code(address) -> Optional[str]: 26 | try: 27 | if not w3.is_address(address): 28 | return None 29 | address = w3.to_checksum_address(address) 30 | code = w3.eth.get_code(address) 31 | return code.hex() 32 | except Exception as e: 33 | return None 34 | 35 | 36 | @cache.memoize(300) 37 | def get_gas_price(): 38 | try: 39 | return w3.eth.gas_price 40 | except Exception as e: 41 | return 0 42 | 43 | 44 | def get_storage_at(contract_address, location): 45 | try: 46 | contract_address = w3.to_checksum_address(contract_address) 47 | data = w3.eth.get_storage_at(contract_address, location) 48 | return decode_data(["address"], data)[0] 49 | except Exception as e: 50 | print(e) 51 | return None 52 | -------------------------------------------------------------------------------- /api/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from api.app.config import * 4 | from common.utils.config import set_config 5 | 6 | 7 | @pytest.fixture(scope="module") 8 | def test_client(): 9 | app_config = AppConfig( 10 | api_modules=[ 11 | APIModule.EXPLORER, 12 | ], 13 | env="ut", 14 | chain="test", 15 | contract_service="", 16 | db_read_sql_alchemy_database_config=DatabaseConfig( 17 | host="localhost", 18 | port=5432, 19 | database="indexer_test", 20 | username="postgres", 21 | password="admin", 22 | ), 23 | db_write_sql_alchemy_database_config=DatabaseConfig( 24 | host="localhost", 25 | port=5432, 26 | database="indexer_test", 27 | username="postgres", 28 | password="admin", 29 | ), 30 | db_common_sql_alchemy_database_config=DatabaseConfig( 31 | host="localhost", 32 | port=5432, 33 | database="indexer_test", 34 | username="postgres", 35 | password="admin", 36 | ), 37 | rpc="https://story-network.rpc.caldera.xyz/http", 38 | ) 39 | set_config(app_config) 40 | from api.app.main import app 41 | 42 | with app.test_client() as testing_client: 43 | with app.app_context(): 44 | yield testing_client 45 | -------------------------------------------------------------------------------- /cli/__init__.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from cli.aggregates import aggregates 4 | from cli.api import api 5 | from cli.db import db 6 | from cli.reorg import reorg 7 | from cli.stream import stream 8 | from indexer.utils.logging_utils import logging_basic_config 9 | 10 | logging_basic_config() 11 | 12 | from importlib import metadata 13 | 14 | 15 | def get_version(): 16 | return metadata.version("hemera") 17 | 18 | 19 | @click.group() 20 | @click.version_option(version=get_version()) 21 | @click.pass_context 22 | def cli(ctx): 23 | pass 24 | 25 | 26 | cli.add_command(stream, "stream") 27 | cli.add_command(api, "api") 28 | cli.add_command(aggregates, "aggregates") 29 | cli.add_command(reorg, "reorg") 30 | cli.add_command(db, "db") 31 | -------------------------------------------------------------------------------- /cli/api.py: -------------------------------------------------------------------------------- 1 | import click 2 | 3 | from cli.logo import print_logo 4 | 5 | 6 | @click.command(context_settings=dict(help_option_names=["-h", "--help"])) 7 | def api(): 8 | print_logo() 9 | from api.app.main import app 10 | 11 | app.run("0.0.0.0", 8082, threaded=True, debug=True) 12 | -------------------------------------------------------------------------------- /cli/logo.py: -------------------------------------------------------------------------------- 1 | def print_logo(): 2 | print( 3 | """ 4 | ██╗ ██╗███████╗███╗ ███╗███████╗██████╗ █████╗ 5 | ██║ ██║██╔════╝████╗ ████║██╔════╝██╔══██╗██╔══██╗ 6 | ███████║█████╗ ██╔████╔██║█████╗ ██████╔╝███████║ 7 | ██╔══██║██╔══╝ ██║╚██╔╝██║██╔══╝ ██╔══██╗██╔══██║ 8 | ██║ ██║███████╗██║ ╚═╝ ██║███████╗██║ ██║██║ ██║ 9 | ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ 10 | """ 11 | ) 12 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | --- 2 | codecov: 3 | branch: main 4 | max_report_age: 6h 5 | require_ci_to_pass: true 6 | notify: 7 | wait_for_ci: false 8 | after_n_builds: 1 9 | 10 | coverage: 11 | precision: 1 12 | round: down 13 | range: 65..90 14 | status: 15 | project: 16 | default: 17 | target: 80% 18 | threshold: 1% 19 | base: auto 20 | paths: 21 | - "indexer" 22 | - "api" 23 | - "common" 24 | branches: 25 | - "*" 26 | if_not_found: success 27 | if_ci_failed: error 28 | informational: false 29 | only_pulls: true 30 | 31 | patch: 32 | default: 33 | target: 85% 34 | threshold: 0% 35 | base: auto 36 | branches: 37 | - "*" 38 | if_no_uploads: error 39 | if_not_found: success 40 | if_ci_failed: error 41 | only_pulls: true 42 | paths: 43 | - "indexer" 44 | - "api" 45 | - "common" 46 | 47 | ignore: 48 | - "indexer/tests/**/*" -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/common/__init__.py -------------------------------------------------------------------------------- /common/converter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/common/converter/__init__.py -------------------------------------------------------------------------------- /common/converter/pg_converter.py: -------------------------------------------------------------------------------- 1 | from common.models import HemeraModel, model_path_patterns 2 | from common.utils.module_loading import import_string, scan_subclass_by_path_patterns 3 | 4 | 5 | def scan_convert_config(): 6 | class_mapping = scan_subclass_by_path_patterns(model_path_patterns, HemeraModel) 7 | 8 | config_mapping = {} 9 | for class_name, path in class_mapping.items(): 10 | full_class_path = path["cls_import_path"] 11 | module = import_string(full_class_path) 12 | 13 | module_configs = module.model_domain_mapping() 14 | if module_configs: 15 | for config in module_configs: 16 | config_mapping[config["domain"]] = { 17 | "table": module, 18 | "conflict_do_update": config["conflict_do_update"], 19 | "update_strategy": config["update_strategy"], 20 | "converter": config["converter"], 21 | } 22 | return config_mapping 23 | 24 | 25 | domain_model_mapping = scan_convert_config() 26 | -------------------------------------------------------------------------------- /common/models/block_timestamp_mapper.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, desc 2 | from sqlalchemy.dialects.postgresql import BIGINT, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.domain.block_ts_mapper import BlockTsMapper 6 | 7 | 8 | class BlockTimestampMapper(HemeraModel): 9 | __tablename__ = "block_ts_mapper" 10 | ts = Column(BIGINT, primary_key=True) 11 | block_number = Column(BIGINT) 12 | timestamp = Column(TIMESTAMP) 13 | 14 | @staticmethod 15 | def model_domain_mapping(): 16 | return [ 17 | { 18 | "domain": BlockTsMapper, 19 | "conflict_do_update": False, 20 | "update_strategy": None, 21 | "converter": general_converter, 22 | } 23 | ] 24 | 25 | 26 | Index("block_ts_mapper_idx", desc(BlockTimestampMapper.block_number)) 27 | -------------------------------------------------------------------------------- /common/models/coin_balances.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, desc, func, text 2 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.domain.coin_balance import CoinBalance 6 | 7 | 8 | class CoinBalances(HemeraModel): 9 | __tablename__ = "address_coin_balances" 10 | 11 | address = Column(BYTEA, primary_key=True) 12 | balance = Column(NUMERIC(100)) 13 | block_number = Column(BIGINT, primary_key=True) 14 | block_timestamp = Column(TIMESTAMP) 15 | 16 | create_time = Column(TIMESTAMP, server_default=func.now()) 17 | update_time = Column(TIMESTAMP, server_default=func.now()) 18 | reorg = Column(BOOLEAN, server_default=text("false")) 19 | 20 | __table_args__ = (PrimaryKeyConstraint("address", "block_number"),) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": CoinBalance, 27 | "conflict_do_update": False, 28 | "update_strategy": None, 29 | "converter": general_converter, 30 | } 31 | ] 32 | 33 | 34 | Index( 35 | "coin_balance_address_number_desc_index", 36 | desc(CoinBalances.address), 37 | desc(CoinBalances.block_number), 38 | ) 39 | -------------------------------------------------------------------------------- /common/models/exception_records.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | from sqlalchemy import Column 4 | from sqlalchemy.dialects.postgresql import BIGINT, JSONB, TIMESTAMP, VARCHAR 5 | 6 | from common.models import HemeraModel 7 | 8 | 9 | class ExceptionRecords(HemeraModel): 10 | __tablename__ = "exception_records" 11 | 12 | id = Column(BIGINT, primary_key=True, autoincrement=True) 13 | block_number = Column(BIGINT) 14 | dataclass = Column(VARCHAR) 15 | level = Column(VARCHAR) 16 | message_type = Column(VARCHAR) 17 | message = Column(VARCHAR) 18 | exception_env = Column(JSONB) 19 | 20 | record_time = Column(TIMESTAMP, default=datetime.utcnow) 21 | -------------------------------------------------------------------------------- /common/models/fix_record.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, INTEGER, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class FixRecord(HemeraModel): 8 | __tablename__ = "fix_record" 9 | job_id = Column(INTEGER, primary_key=True) 10 | start_block_number = Column(BIGINT) 11 | last_fixed_block_number = Column(BIGINT) 12 | remain_process = Column(INTEGER) 13 | job_status = Column(VARCHAR) 14 | create_time = Column(TIMESTAMP, server_default=func.now()) 15 | update_time = Column(TIMESTAMP) 16 | -------------------------------------------------------------------------------- /common/models/scheduled_metadata.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, DateTime 2 | from sqlalchemy.dialects.postgresql import INTEGER, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class ScheduledMetadata(HemeraModel): 8 | __tablename__ = "af_index_na_scheduled_metadata" 9 | __table_args__ = {"extend_existing": True} 10 | id = Column(INTEGER, primary_key=True) 11 | dag_id = Column(VARCHAR) 12 | execution_date = Column(DateTime) 13 | last_data_timestamp = Column(DateTime) 14 | -------------------------------------------------------------------------------- /common/models/sync_record.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class SyncRecord(HemeraModel): 8 | __tablename__ = "sync_record" 9 | mission_sign = Column(VARCHAR, primary_key=True) 10 | last_block_number = Column(BIGINT) 11 | update_time = Column(TIMESTAMP) 12 | -------------------------------------------------------------------------------- /common/models/token_hourly_price.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, DateTime, Numeric, String 2 | 3 | from common.models import HemeraModel 4 | 5 | 6 | class TokenHourlyPrices(HemeraModel): 7 | symbol = Column(String, primary_key=True) 8 | timestamp = Column(DateTime, primary_key=True) 9 | price = Column(Numeric) 10 | 11 | 12 | class CoinPrices(HemeraModel): 13 | block_date = Column(DateTime, primary_key=True) 14 | price = Column(Numeric) 15 | -------------------------------------------------------------------------------- /common/models/token_prices.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, DateTime, Numeric, String 2 | 3 | from common.models import HemeraModel 4 | 5 | 6 | class TokenPrices(HemeraModel): 7 | symbol = Column(String, primary_key=True) 8 | timestamp = Column(DateTime, primary_key=True) 9 | price = Column(Numeric) 10 | -------------------------------------------------------------------------------- /common/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/common/services/__init__.py -------------------------------------------------------------------------------- /common/services/hemera_postgresql_service.py: -------------------------------------------------------------------------------- 1 | from psycopg2 import pool 2 | from sqlalchemy import create_engine 3 | from sqlalchemy.orm import sessionmaker 4 | 5 | 6 | class HemeraPostgreSQLService(object): 7 | instance = None 8 | 9 | def __new__(cls, *args, **kwargs): 10 | if cls.instance is None: 11 | cls.instance = super().__new__(cls) 12 | return cls.instance 13 | 14 | def __init__(self, jdbc_url): 15 | self.engine = create_engine( 16 | jdbc_url, 17 | pool_size=10, 18 | max_overflow=10, 19 | pool_timeout=30, 20 | pool_recycle=60, 21 | connect_args={"application_name": "hemera_indexer"}, 22 | ) 23 | self.jdbc_url = jdbc_url 24 | self.connection_pool = pool.SimpleConnectionPool(1, 10, jdbc_url) 25 | 26 | self.Session = sessionmaker(bind=self.engine) 27 | 28 | def get_conn(self): 29 | return self.connection_pool.getconn() 30 | 31 | def release_conn(self, conn): 32 | self.connection_pool.putconn(conn) 33 | 34 | def get_service_engine(self): 35 | return self.engine 36 | 37 | def get_service_session(self): 38 | return self.Session() 39 | 40 | def get_service_connection(self): 41 | return self.engine.connect() 42 | -------------------------------------------------------------------------------- /common/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/common/utils/__init__.py -------------------------------------------------------------------------------- /common/utils/db_utils.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import text 2 | 3 | from common.models import db 4 | from common.utils.config import get_config 5 | 6 | app_config = get_config() 7 | 8 | 9 | def build_entities(model, columns): 10 | if columns == "*": 11 | entities = [attr for attr in model.__table__.columns] 12 | else: 13 | entities = [] 14 | for column in columns: 15 | if isinstance(column, tuple): 16 | col, alias = column 17 | entities.append(getattr(model, col).label(alias)) 18 | else: 19 | entities.append(getattr(model, column)) 20 | 21 | return entities 22 | 23 | 24 | def get_total_row_count(table): 25 | estimate_transaction = db.session.execute( 26 | text( 27 | f""" 28 | SELECT reltuples::bigint AS estimate FROM pg_class where oid = '{app_config.db_read_sql_alchemy_database_config.schema}.{table}'::regclass; 29 | """ 30 | ) 31 | ).fetchone() 32 | return estimate_transaction[0] 33 | -------------------------------------------------------------------------------- /config/indexer-config-base.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 8453 2 | 3 | uniswap_v3_job: 4 | # empty means no filter. only work in price job 5 | pool_address: 6 | 7 | # works in price\token job 8 | jobs: 9 | - type: uniswapv3 # uniswapv3` 10 | factory address: '0x33128a8fc17869897dce68ed026d694621f6fdfd' 11 | position_token_address: '0x03a520b32c04bf3beef7beb72e919cf822ed34f1' 12 | 13 | - type: uniswapv3 # pancake 14 | factory address: '0x0bfbcf9fa4f9c56b0f40a671ad40e0805a091865' 15 | position_token_address: '0x46a15b0b27311cedf172ab29e4f4766fbe7f4364' -------------------------------------------------------------------------------- /config/indexer-config-bsc.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 56 2 | 3 | uniswap_v3_job: 4 | # empty means no filter. only work in price job 5 | pool_address: 6 | - '0xe2bb11d6b6a39e55762f5e14d632f0981198b3a7' 7 | 8 | # works in price\token job 9 | jobs: 10 | - type: swapsicle # thena` 11 | factory address: '0x306f06c147f064a010530292a1eb6737c3e378e4' 12 | position_token_address: '0xa51adb08cbe6ae398046a23bec013979816b77ab' 13 | 14 | export_tokens_and_transfers_job: 15 | filter_token_address: 16 | - '0xa51adb08cbe6ae398046a23bec013979816b77ab' -------------------------------------------------------------------------------- /config/indexer-config-cyber-prod.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 7560 2 | export_cyber_id_job: 3 | cyber_id_reverse_registrar_contract_address: "0x79502da131357333d61c39b7411d01df54591961" 4 | cyber_id_public_resolver_contract_address: "0xfb2f304c1fcd6b053ee033c03293616d5121944b" 5 | cyber_id_token_contract_address: "0xc137be6b59e824672aada673e55cf4d150669af8" 6 | -------------------------------------------------------------------------------- /config/indexer-config-eth.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 1 2 | uniswap_v3_pool_job: 3 | position_token_address: '0xc36442b4a4522e871399cd717abdd847ab11fe88' 4 | factory_address: '0x1f98431c8ad98523631ae4a59f267346ea31f984' 5 | 6 | eigen_layer_job: 7 | STRATEGY_MANAGER: 8 | address: '0x858646372cc42e1a627fce94aa7a7033e7cf075a' 9 | DELEGATION: 10 | address: '0x39053d51b77dc0d36036fc1fcc8cb819df8ef37a' 11 | 12 | export_lido_share_job: 13 | seth_address: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84' 14 | 15 | export_ether_fi_share_job: 16 | eeth_address: '0x35fa164735182de50811e8e2e824cfb9b6118ac2' 17 | liquidity_pool_address: '0x308861A430be4cce5502d0A12724771Fc6DaF216' 18 | lrts: 19 | - token: '0x939778D83b46B456224A33Fb59630B11DEC56663' 20 | accountant: '0xEB440B36f61Bf62E0C54C622944545f159C3B790' 21 | - token: '0x657e8C867D8B37dCC18fA4Caead9C45EB088C642' 22 | accountant: '0x1b293DC39F94157fA0D1D36d7e0090C8B8B8c13F' 23 | - token: '0x917ceE801a67f933F2e6b33fC0cD1ED2d5909D88' 24 | accountant: '0xbe16605B22a7faCEf247363312121670DFe5afBE' 25 | - token: '0x7223442cad8e9cA474fC40109ab981608F8c4273' 26 | accountant: '0x126af21dc55C300B7D0bBfC4F3898F558aE8156b' 27 | 28 | pendle_pools_job: 29 | pendle_market_factory_address: '0x27b1dacd74688af24a64bd3c9c1b143118740784' 30 | pendle_market_factory_v3_address: '0x6fcf753f2c67b83f7b09746bbc4fa0047b35d050' 31 | 32 | -------------------------------------------------------------------------------- /config/indexer-config-morph-mainnet-prod-bridge-l1.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 1 2 | morph_bridge_on_l1_job: 3 | l1_message_queue_oracle_contract_address: "0x3931ade842f5bb8763164bdd81e5361dce6cc1ef" 4 | l1_cross_domain_messenger_contract_address: "0xdc71366effa760804dcfc3edf87fa2a6f1623304" 5 | 6 | -------------------------------------------------------------------------------- /config/indexer-config-morph-mainnet-prod.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 2818 2 | morph_bridge_on_l2_job: 3 | l2_cross_domain_messenger_contract_address: "0x5300000000000000000000000000000000000007" 4 | -------------------------------------------------------------------------------- /config/indexer-config-template.yaml: -------------------------------------------------------------------------------- 1 | chain_id: 1 2 | demo_job: 3 | contract_address: 4 | - "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d" -------------------------------------------------------------------------------- /docker-compose/hemera-indexer.env: -------------------------------------------------------------------------------- 1 | PROVIDER_URI=https://ethereum.publicnode.com 2 | DEBUG_PROVIDER_URI=https://ethereum.publicnode.com 3 | START_BLOCK=20159954 4 | POSTGRES_URL=postgresql://user:Ts6YZSrGegXr8PPONtHcSLAHDGd1fjwHBjFZ6WVNpgiOmGf7ghYXl0I1@postgresql:5432/postgres 5 | OUTPUT=postgres 6 | 7 | # 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 8 | # for it to take effect. 9 | POSTGRES_USER=user 10 | POSTGRES_PASSWORD=Ts6YZSrGegXr8PPONtHcSLAHDGd1fjwHBjFZ6WVNpgiOmGf7ghYXl0I1 11 | POSTGRES_DB=postgres 12 | -------------------------------------------------------------------------------- /docs/AWS.md: -------------------------------------------------------------------------------- 1 | #### Create an AWS EC2 Instance 2 | 1. Navigate to EC2 console 3 | ![ClickLaunch](images/aws/ec2-portal.png) 4 | 2. Launch an AWS Instance 5 | - Select ubuntu as the operating system 6 | ![Launch](images/aws/launch-instance.jpg) 7 | - Select the ssh key pair for you to log into the VM later 8 | ![SSH Key](images/aws/key-pair.png) 9 | - Change the disk size that fits your need. Checkout our [Readme](README.md) on specifications. 10 | ![Disk Size](images/aws/disk-size.png) 11 | - [Optional] Expose postgres port by following the [instructions](https://medium.com/yavar/postgresql-allowing-remote-login-to-the-database-e6345b23f743) 12 | - Click Launch 13 | 3. Once the instance is created, ssh into the instance and follow instructions in [Configure Hemera Indexer](https://github.com/HemeraProtocol/hemera-indexer/tree/master/docs#configure-hemera-indexer) section. 14 | -------------------------------------------------------------------------------- /docs/images/aws/aws-launch-instance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/docs/images/aws/aws-launch-instance.png -------------------------------------------------------------------------------- /docs/images/aws/disk-size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/docs/images/aws/disk-size.png -------------------------------------------------------------------------------- /docs/images/aws/ec2-portal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/docs/images/aws/ec2-portal.png -------------------------------------------------------------------------------- /docs/images/aws/key-pair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/docs/images/aws/key-pair.png -------------------------------------------------------------------------------- /docs/images/aws/launch-instance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/docs/images/aws/launch-instance.jpg -------------------------------------------------------------------------------- /enumeration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/enumeration/__init__.py -------------------------------------------------------------------------------- /enumeration/record_level.py: -------------------------------------------------------------------------------- 1 | class RecordLevel: 2 | Debug = "Debug" 3 | INFO = "Info" 4 | WARN = "Warning" 5 | ERROR = "Error" 6 | FATAL = "Fatal" 7 | -------------------------------------------------------------------------------- /enumeration/schedule_mode.py: -------------------------------------------------------------------------------- 1 | class ScheduleMode: 2 | STREAM = "stream" 3 | LOAD = "load" 4 | REORG = "reorg" 5 | -------------------------------------------------------------------------------- /enumeration/token_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TokenType(Enum): 5 | ERC20 = "ERC20" 6 | ERC721 = "ERC721" 7 | ERC1155 = "ERC1155" 8 | ERC404 = "ERC404" 9 | -------------------------------------------------------------------------------- /hemera.py: -------------------------------------------------------------------------------- 1 | from cli import cli 2 | 3 | cli() 4 | -------------------------------------------------------------------------------- /indexer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/__init__.py -------------------------------------------------------------------------------- /indexer/aggr_jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/aggr_jobs/__init__.py -------------------------------------------------------------------------------- /indexer/aggr_jobs/aggr_base_job.py: -------------------------------------------------------------------------------- 1 | import os 2 | from datetime import datetime, timedelta 3 | 4 | 5 | class AggrBaseJob: 6 | sql_folder = "" 7 | 8 | def run(self, **kwargs): 9 | pass 10 | 11 | def get_sql_content(self, file_name, start_date, end_date): 12 | base_dir = os.path.dirname(__file__) 13 | if not file_name.endswith(".sql"): 14 | file_name += ".sql" 15 | file_path = os.path.join(base_dir, self.sql_folder, file_name) 16 | 17 | with open(file_path, "r") as f: 18 | sql_template = f.read() 19 | sql = sql_template.format(start_date=start_date, end_date=end_date) 20 | return sql 21 | 22 | @staticmethod 23 | def generate_date_pairs(start_date, end_date): 24 | start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") 25 | end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") 26 | 27 | date_pairs = [] 28 | current_date = start_date_obj 29 | while current_date < end_date_obj: 30 | next_date = current_date + timedelta(days=1) 31 | if next_date <= end_date_obj: 32 | date_pairs.append((current_date.strftime("%Y-%m-%d"), next_date.strftime("%Y-%m-%d"))) 33 | current_date = next_date 34 | 35 | return date_pairs 36 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/aggr_job_scheduler.py: -------------------------------------------------------------------------------- 1 | """ 2 | This scheduler can handle complex relationship dependencies, etc. The current example shows 3 | AggrDisorderJob -> AggrOrderJob 4 | """ 5 | 6 | from indexer.aggr_jobs.disorder_jobs.disorder_job import AggrDisorderJob 7 | from indexer.aggr_jobs.order_jobs.order_job import AggrOrderJob 8 | 9 | 10 | class AggrJobScheduler: 11 | def __init__(self, config): 12 | self.config = config 13 | self.jobs = self.instantiate_jobs() 14 | 15 | def run_jobs(self, start_date, end_date): 16 | for job in self.jobs: 17 | job.run(start_date=start_date, end_date=end_date) 18 | 19 | def instantiate_jobs(self): 20 | jobs = [] 21 | for job_class in [AggrDisorderJob, AggrOrderJob]: 22 | job = job_class( 23 | config=self.config, 24 | ) 25 | jobs.append(job) 26 | return jobs 27 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/disorder_jobs/daily_feature_holding_balance_uniswap_v3.sql: -------------------------------------------------------------------------------- 1 | begin; 2 | delete 3 | from af_uniswap_v3_token_data_daily 4 | where block_date >= '{start_date}' 5 | and block_date < '{end_date}'; 6 | insert into af_uniswap_v3_token_data_daily 7 | select position_token_address, 8 | TO_TIMESTAMP(block_timestamp)::DATE as block_date, 9 | token_id, 10 | wallet_address, 11 | pool_address, 12 | liquidity 13 | from (select *, row_number() over (partition by nft_address, token_id order by block_timestamp desc) rn 14 | from af_uniswap_v3_token_data_hist 15 | where TO_TIMESTAMP(block_timestamp) >= '{start_date}' 16 | and TO_TIMESTAMP(block_timestamp) < '{end_date}') t 17 | where rn = 1; 18 | 19 | 20 | delete 21 | from af_uniswap_v3_pool_prices_daily 22 | where block_date >= '{start_date}' 23 | and block_date < '{end_date}'; 24 | insert into af_uniswap_v3_pool_prices_daily 25 | select pool_address, 26 | TO_TIMESTAMP(block_timestamp)::DATE as block_date, 27 | sqrt_price_x96 28 | from (select *, row_number() over (partition by pool_address order by block_timestamp desc) rn 29 | from af_uniswap_v3_pool_prices_hist 30 | where TO_TIMESTAMP(block_timestamp) >= '{start_date}' 31 | and TO_TIMESTAMP(block_timestamp) < '{end_date}') t 32 | where rn = 1; 33 | commit -------------------------------------------------------------------------------- /indexer/aggr_jobs/disorder_jobs/disorder_job.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import text 2 | 3 | from indexer.aggr_jobs.aggr_base_job import AggrBaseJob 4 | from indexer.executors.batch_work_executor import BatchWorkExecutor 5 | 6 | 7 | class AggrDisorderJob(AggrBaseJob): 8 | sql_folder = "disorder_jobs" 9 | 10 | def __init__(self, **kwargs): 11 | config = kwargs["config"] 12 | self.db_service = config["db_service"] 13 | self._batch_work_executor = BatchWorkExecutor(5, 5) 14 | 15 | def run(self, **kwargs): 16 | start_date = kwargs["start_date"] 17 | end_date = kwargs["end_date"] 18 | 19 | execute_sql_list = [] 20 | date_pairs = self.generate_date_pairs(start_date, end_date) 21 | for date_pair in date_pairs: 22 | start_date, end_date = date_pair 23 | sql_content = self.get_sql_content("daily_wallet_addresses_aggregates", start_date, end_date) 24 | execute_sql_list.append(sql_content) 25 | 26 | self._batch_work_executor.execute(execute_sql_list, self.execute_sql, total_items=len(execute_sql_list)) 27 | self._batch_work_executor.wait() 28 | 29 | def execute_sql(self, sql_contents): 30 | session = self.db_service.Session() 31 | for sql_content in sql_contents: 32 | session.execute(text(sql_content)) 33 | session.commit() 34 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_pool_prices.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyFeatureUniswapV3PoolPrices(HemeraModel): 8 | __tablename__ = "af_uniswap_v3_pool_prices_daily" 9 | 10 | block_date = Column(DATE, primary_key=True, nullable=False) 11 | pool_address = Column(BYTEA, primary_key=True, nullable=False) 12 | 13 | sqrt_price_x96 = Column(NUMERIC(78)) 14 | 15 | create_time = Column(TIMESTAMP, server_default=func.now()) 16 | 17 | __table_args__ = (PrimaryKeyConstraint("block_date", "pool_address"),) 18 | 19 | 20 | # could be replaced by partition in case of huge amount data 21 | Index("af_uniswap_v3_pool_prices_daily_block_date_index", DailyFeatureUniswapV3PoolPrices.block_date) 22 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/disorder_jobs/models/daily_feature_uniswap_v3_token_details.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyFeatureUniswapV3TokenDeatils(HemeraModel): 8 | __tablename__ = "af_uniswap_v3_token_data_daily" 9 | 10 | block_date = Column(DATE, primary_key=True, nullable=False) 11 | position_token_address = Column(BYTEA, primary_key=True, nullable=False) 12 | token_id = Column(INTEGER, primary_key=True, nullable=False) 13 | wallet_address = Column(BYTEA, nullable=False) 14 | pool_address = Column(BYTEA, nullable=False) 15 | liquidity = Column(NUMERIC(78)) 16 | 17 | create_time = Column(TIMESTAMP, server_default=func.now()) 18 | 19 | __table_args__ = (PrimaryKeyConstraint("block_date", "position_token_address", "token_id"),) 20 | 21 | 22 | # could be replaced by partition in case of huge amount data 23 | Index("af_uniswap_v3_token_data_daily_index", DailyFeatureUniswapV3TokenDeatils.block_date) 24 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_merchantmoe.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class PeriodFeatureHoldingBalanceMerchantmoe(HemeraModel): 8 | __tablename__ = "af_holding_balance_merchantmoe_period" 9 | 10 | period_date = Column(DATE, primary_key=True, nullable=False) 11 | protocol_id = Column(VARCHAR, primary_key=True, nullable=False) 12 | position_token_address = Column(BYTEA, primary_key=True, nullable=False) 13 | token_id = Column(NUMERIC, primary_key=True, nullable=False) 14 | wallet_address = Column(BYTEA, primary_key=True, nullable=False) 15 | 16 | token0_address = Column(BYTEA, nullable=False) 17 | token0_symbol = Column(VARCHAR, nullable=False) 18 | token0_balance = Column(NUMERIC(100, 18)) 19 | 20 | token1_address = Column(BYTEA, nullable=False) 21 | token1_symbol = Column(VARCHAR, nullable=False) 22 | token1_balance = Column(NUMERIC(100, 18)) 23 | 24 | create_time = Column(TIMESTAMP, server_default=func.now()) 25 | 26 | __table_args__ = ( 27 | PrimaryKeyConstraint("period_date", "protocol_id", "position_token_address", "token_id", "wallet_address"), 28 | ) 29 | 30 | 31 | Index("af_holding_balance_merchantmoe_period_period_date", PeriodFeatureHoldingBalanceMerchantmoe.period_date) 32 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/models/period_feature_holding_balance_uniswap_v3.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class PeriodFeatureHoldingBalanceUniswapV3(HemeraModel): 8 | __tablename__ = "af_holding_balance_uniswap_v3_period" 9 | 10 | period_date = Column(DATE, primary_key=True, nullable=False) 11 | protocol_id = Column(VARCHAR, primary_key=True, nullable=False) 12 | pool_address = Column(BYTEA, primary_key=True, nullable=False) 13 | token_id = Column(INTEGER, primary_key=True, nullable=False) 14 | wallet_address = Column(BYTEA, nullable=False) 15 | token0_address = Column(BYTEA, nullable=False) 16 | token0_symbol = Column(VARCHAR, nullable=False) 17 | token0_balance = Column(NUMERIC(100, 18)) 18 | 19 | token1_address = Column(BYTEA, nullable=False) 20 | token1_symbol = Column(VARCHAR, nullable=False) 21 | token1_balance = Column(NUMERIC(100, 18)) 22 | 23 | create_time = Column(TIMESTAMP, server_default=func.now()) 24 | 25 | __table_args__ = (PrimaryKeyConstraint("period_date", "protocol_id", "pool_address", "token_id"),) 26 | 27 | 28 | # could be replaced by partition in case of huge amount data 29 | Index("af_holding_balance_uniswap_v3_period_period_date", PeriodFeatureHoldingBalanceUniswapV3.period_date) 30 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/models/period_feature_merchant_moe_token_bin.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class PeriodFeatureMerchantMoeTokenBinRecords(HemeraModel): 8 | __tablename__ = "af_merchant_moe_token_bin_hist_period" 9 | 10 | period_date = Column(DATE, primary_key=True) 11 | position_token_address = Column(BYTEA, primary_key=True) 12 | token_id = Column(NUMERIC(100), primary_key=True) 13 | 14 | reserve0_bin = Column(NUMERIC(100)) 15 | reserve1_bin = Column(NUMERIC(100)) 16 | 17 | create_time = Column(TIMESTAMP, server_default=func.now()) 18 | 19 | __table_args__ = (PrimaryKeyConstraint("period_date", "position_token_address", "token_id"),) 20 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_pool_prices.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class PeriodFeatureUniswapV3PoolPrices(HemeraModel): 8 | __tablename__ = "af_uniswap_v3_pool_prices_period" 9 | 10 | period_date = Column(DATE, primary_key=True, nullable=False) 11 | pool_address = Column(BYTEA, primary_key=True, nullable=False) 12 | sqrt_price_x96 = Column(NUMERIC(78)) 13 | 14 | create_time = Column(TIMESTAMP, server_default=func.now()) 15 | 16 | __table_args__ = (PrimaryKeyConstraint("period_date", "pool_address"),) 17 | 18 | 19 | # could be replaced by partition in case of huge amount data 20 | Index("af_uniswap_v3_pool_prices_period_period_date_index", PeriodFeatureUniswapV3PoolPrices.period_date) 21 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/models/period_feature_uniswap_v3_token_details.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class PeriodFeatureUniswapV3TokenDeatils(HemeraModel): 8 | __tablename__ = "af_uniswap_v3_token_data_period" 9 | 10 | period_date = Column(DATE, primary_key=True, nullable=False) 11 | position_token_address = Column(BYTEA, primary_key=True, nullable=False) 12 | token_id = Column(INTEGER, primary_key=True, nullable=False) 13 | wallet_address = Column(BYTEA, nullable=False) 14 | pool_address = Column(BYTEA, nullable=False) 15 | liquidity = Column(NUMERIC(78)) 16 | 17 | create_time = Column(TIMESTAMP, server_default=func.now()) 18 | 19 | __table_args__ = (PrimaryKeyConstraint("period_date", "position_token_address", "token_id"),) 20 | 21 | 22 | # could be replaced by partition in case of huge amount data 23 | Index("af_uniswap_v3_token_data_period_date_index", PeriodFeatureUniswapV3TokenDeatils.period_date) 24 | -------------------------------------------------------------------------------- /indexer/aggr_jobs/order_jobs/order_job.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import text 2 | 3 | from indexer.aggr_jobs.aggr_base_job import AggrBaseJob 4 | 5 | 6 | class AggrOrderJob(AggrBaseJob): 7 | sql_folder = "order_jobs" 8 | 9 | def __init__(self, **kwargs): 10 | config = kwargs["config"] 11 | self.db_service = config["db_service"] 12 | 13 | def run(self, **kwargs): 14 | start_date = kwargs["start_date"] 15 | end_date = kwargs["end_date"] 16 | 17 | session = self.db_service.Session() 18 | 19 | date_pairs = self.generate_date_pairs(start_date, end_date) 20 | for date_pair in date_pairs: 21 | start_date, end_date = date_pair 22 | sql_content = self.get_sql_content("period_wallet_addresses_aggregates", start_date, end_date) 23 | session.execute(text(sql_content)) 24 | session.commit() 25 | -------------------------------------------------------------------------------- /indexer/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/controller/__init__.py -------------------------------------------------------------------------------- /indexer/controller/aggregates_controller.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta 2 | 3 | from indexer.controller.base_controller import BaseController 4 | 5 | 6 | class AggregatesController(BaseController): 7 | def __init__(self, job_dispatcher): 8 | self.job_dispatcher = job_dispatcher 9 | 10 | def action(self, start_date, end_date, date_batch_size): 11 | date_batches = self.split_date_range(start_date, end_date, date_batch_size) 12 | for date_batch in date_batches: 13 | start_date, end_date = date_batch 14 | self.job_dispatcher.run(start_date, end_date) 15 | 16 | @staticmethod 17 | def split_date_range(start_date, end_date, batch_size): 18 | start_date_obj = datetime.strptime(start_date, "%Y-%m-%d") 19 | end_date_obj = datetime.strptime(end_date, "%Y-%m-%d") 20 | 21 | date_ranges = [] 22 | while start_date_obj < end_date_obj: 23 | batch_end_date = min(start_date_obj + timedelta(days=batch_size - 1), end_date_obj) 24 | date_ranges.append((start_date_obj.strftime("%Y-%m-%d"), batch_end_date.strftime("%Y-%m-%d"))) 25 | start_date_obj = batch_end_date + timedelta(days=1) 26 | 27 | return date_ranges 28 | -------------------------------------------------------------------------------- /indexer/controller/base_controller.py: -------------------------------------------------------------------------------- 1 | class BaseController(object): 2 | 3 | def action(self, *args, **kwargs): 4 | pass 5 | 6 | def shutdown(self): 7 | pass 8 | -------------------------------------------------------------------------------- /indexer/controller/dispatcher/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/controller/dispatcher/__init__.py -------------------------------------------------------------------------------- /indexer/controller/dispatcher/aggregates_dispatcher.py: -------------------------------------------------------------------------------- 1 | from indexer.aggr_jobs.aggr_job_scheduler import AggrJobScheduler 2 | from indexer.controller.dispatcher.base_dispatcher import BaseDispatcher 3 | 4 | 5 | class AggregatesDispatcher(BaseDispatcher): 6 | def __init__(self, config): 7 | super().__init__() 8 | self._job_scheduler = AggrJobScheduler(config=config) 9 | 10 | def run(self, start_date, end_date): 11 | self._job_scheduler.run_jobs(start_date=start_date, end_date=end_date) 12 | -------------------------------------------------------------------------------- /indexer/controller/dispatcher/base_dispatcher.py: -------------------------------------------------------------------------------- 1 | class BaseDispatcher(object): 2 | _db_service = None 3 | 4 | def __init__(self, service=None): 5 | self._db_service = service 6 | 7 | def run(self, *args, **kwargs): 8 | pass 9 | -------------------------------------------------------------------------------- /indexer/controller/scheduler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/controller/scheduler/__init__.py -------------------------------------------------------------------------------- /indexer/domain/block_ts_mapper.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Tuple 3 | 4 | from indexer.domain import Domain 5 | 6 | 7 | @dataclass 8 | class BlockTsMapper(Domain): 9 | block_number: int 10 | timestamp: int 11 | 12 | def __init__(self, mapper: Tuple[int, int]): 13 | self.block_number = mapper[1] 14 | self.timestamp = mapper[0] 15 | -------------------------------------------------------------------------------- /indexer/domain/coin_balance.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class CoinBalance(Domain): 8 | address: str 9 | balance: int 10 | block_number: int 11 | block_timestamp: int 12 | 13 | def __init__(self, coin_balance: dict): 14 | self.dict_to_entity(coin_balance) 15 | -------------------------------------------------------------------------------- /indexer/domain/contract.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class Contract(Domain): 8 | address: str 9 | name: str 10 | contract_creator: str 11 | creation_code: str 12 | deployed_code: str 13 | block_number: int 14 | block_hash: str 15 | block_timestamp: int 16 | transaction_index: int 17 | transaction_hash: str 18 | transaction_from_address: str 19 | 20 | def __init__(self, contract: dict): 21 | self.dict_to_entity(contract) 22 | 23 | def fill_transaction_from_address(self, address: str): 24 | self.transaction_from_address = address 25 | 26 | 27 | def extract_contract_from_trace(trace): 28 | contract = { 29 | "address": trace.to_address, 30 | "contract_creator": trace.from_address, 31 | "creation_code": trace.input, 32 | "deployed_code": trace.output, 33 | "block_number": trace.block_number, 34 | "block_hash": trace.block_hash, 35 | "block_timestamp": trace.block_timestamp, 36 | "transaction_index": trace.transaction_index, 37 | "transaction_hash": trace.transaction_hash, 38 | } 39 | 40 | return contract 41 | -------------------------------------------------------------------------------- /indexer/domain/current_token_balance.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class CurrentTokenBalance(Domain): 8 | address: str 9 | token_id: int 10 | token_type: str 11 | token_address: str 12 | balance: int 13 | block_number: int 14 | block_timestamp: int 15 | -------------------------------------------------------------------------------- /indexer/domain/token.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from indexer.domain import Domain 5 | 6 | 7 | @dataclass 8 | class Token(Domain): 9 | address: str 10 | token_type: str 11 | name: Optional[str] 12 | symbol: Optional[str] 13 | decimals: Optional[int] 14 | block_number: int 15 | total_supply: Optional[int] = None 16 | 17 | 18 | @dataclass 19 | class UpdateToken(Domain): 20 | address: str 21 | block_number: int 22 | total_supply: Optional[int] = None 23 | -------------------------------------------------------------------------------- /indexer/domain/token_balance.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class TokenBalance(Domain): 8 | address: str 9 | token_id: int 10 | token_type: str 11 | token_address: str 12 | balance: int 13 | block_number: int 14 | block_timestamp: int 15 | -------------------------------------------------------------------------------- /indexer/executors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/executors/__init__.py -------------------------------------------------------------------------------- /indexer/executors/bounded_executor.py: -------------------------------------------------------------------------------- 1 | from concurrent.futures import ThreadPoolExecutor 2 | from threading import BoundedSemaphore 3 | 4 | 5 | class BoundedExecutor: 6 | """BoundedExecutor behaves as a ThreadPoolExecutor which will block on 7 | calls to submit() once the limit given as "bound" work items are queued for 8 | execution. 9 | :param bound: Integer - the maximum number of items in the work queue 10 | :param max_workers: Integer - the size of the thread pool 11 | """ 12 | 13 | def __init__(self, bound, max_workers): 14 | self._delegate = ThreadPoolExecutor(max_workers=max_workers) 15 | self._semaphore = BoundedSemaphore(bound + max_workers) 16 | 17 | """See concurrent.futures.Executor#submit""" 18 | 19 | def submit(self, fn, *args, **kwargs): 20 | self._semaphore.acquire() 21 | try: 22 | future = self._delegate.submit(fn, *args, **kwargs) 23 | except Exception as e: 24 | self._semaphore.release() 25 | raise e 26 | else: 27 | future.add_done_callback(lambda x: self._semaphore.release()) 28 | return future 29 | 30 | """See concurrent.futures.Executor#shutdown""" 31 | 32 | def shutdown(self, wait=True): 33 | self._delegate.shutdown(wait) 34 | -------------------------------------------------------------------------------- /indexer/exporters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/exporters/__init__.py -------------------------------------------------------------------------------- /indexer/exporters/base_exporter.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from typing import List 3 | 4 | from indexer.domain import Domain 5 | 6 | 7 | class BaseExporter(object): 8 | def open(self): 9 | pass 10 | 11 | def close(self): 12 | pass 13 | 14 | def export_items(self, items, **kwargs): 15 | pass 16 | 17 | def export_item(self, item, **kwargs): 18 | pass 19 | 20 | def batch_finish(self): 21 | pass 22 | 23 | 24 | def group_by_item_type(items: List[Domain]): 25 | result = collections.defaultdict(list) 26 | for item in items: 27 | key = item.__class__ 28 | result[key].append(item) 29 | 30 | return result 31 | -------------------------------------------------------------------------------- /indexer/exporters/console_item_exporter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from indexer.exporters.base_exporter import BaseExporter 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | 8 | class ConsoleItemExporter(BaseExporter): 9 | 10 | def export_items(self, items, **kwargs): 11 | for item in items: 12 | self.export_item(item, **kwargs) 13 | 14 | def export_item(self, item, **kwargs): 15 | print(item) 16 | 17 | def batch_finish(self): 18 | logging.info("Batch finished") 19 | -------------------------------------------------------------------------------- /indexer/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "CSVSourceJob", 3 | "PGSourceJob", 4 | "ExportBlocksJob", 5 | "ExportTransactionsAndLogsJob", 6 | "ExportTokensAndTransfersJob", 7 | "ExportTokenIdInfosJob", 8 | "ExportTokenBalancesJob", 9 | "ExportTracesJob", 10 | "ExportContractsJob", 11 | "ExportCoinBalancesJob", 12 | "FilterTransactionDataJob", 13 | ] 14 | 15 | from indexer.jobs.base_job import FilterTransactionDataJob 16 | from indexer.jobs.export_blocks_job import ExportBlocksJob 17 | from indexer.jobs.export_coin_balances_job import ExportCoinBalancesJob 18 | from indexer.jobs.export_contracts_job import ExportContractsJob 19 | from indexer.jobs.export_token_balances_job import ExportTokenBalancesJob 20 | from indexer.jobs.export_token_id_infos_job import ExportTokenIdInfosJob 21 | from indexer.jobs.export_tokens_and_transfers_job import ExportTokensAndTransfersJob 22 | from indexer.jobs.export_traces_job import ExportTracesJob 23 | from indexer.jobs.export_transactions_and_logs_job import ExportTransactionsAndLogsJob 24 | from indexer.jobs.source_job.csv_source_job import CSVSourceJob 25 | from indexer.jobs.source_job.pg_source_job import PGSourceJob 26 | -------------------------------------------------------------------------------- /indexer/jobs/source_job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/jobs/source_job/__init__.py -------------------------------------------------------------------------------- /indexer/modules/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/README.md -------------------------------------------------------------------------------- /indexer/modules/bridge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/arbitrum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/arbitrum/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/arbitrum/arb_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/7/10 17:46 4 | # @Author will 5 | # @File arb_conf.py 6 | # @Brief 7 | # env = { 8 | # 'l2_chain_id': 42161, 9 | # 'transaction_batch_offset': 22207816 10 | # } 11 | # env = { 12 | # 'l2_chain_id': 53457, 13 | # 'transaction_batch_offset': 0 14 | # } 15 | NETWORK = { 16 | "dodo-test->arb-sepolia": {"l1": [], "l2": [], "batch": []}, 17 | "arbitrum->eth": { 18 | "l1": [ 19 | "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", 20 | "0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a", 21 | "0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840", 22 | ], 23 | "l2": [ 24 | "0x000000000000000000000000000000000000006e", 25 | "0x0000000000000000000000000000000000000064", 26 | "0x5288c571Fd7aD117beA99bF60FE0846C4E84F933", 27 | "0x09e9222E96E7B4AE2a407B98d48e330053351EEe", 28 | "0x096760F208390250649E3e8763348E783AEF5562", 29 | "0x6c411aD3E74De3E7Bd422b94A27770f5B86C623B", 30 | "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", 31 | ], 32 | "batch": [ 33 | "0x5eF0D09d1E6204141B4d37530808eD19f60FBa35", 34 | "0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6", 35 | ], 36 | }, 37 | } 38 | -------------------------------------------------------------------------------- /indexer/modules/bridge/bedrock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/bedrock/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/bedrock/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/bedrock/parser/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/morphl2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/bridge/morphl2/__init__.py -------------------------------------------------------------------------------- /indexer/modules/bridge/morphl2/parser/deposited_transaction.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | 5 | @dataclass 6 | class DepositedTransaction: 7 | msg_hash: Optional[str] = None 8 | version: Optional[int] = None 9 | index: Optional[int] = None 10 | block_number: Optional[int] = None 11 | block_timestamp: Optional[int] = None 12 | block_hash: Optional[str] = None 13 | transaction_hash: Optional[str] = None 14 | from_address: Optional[str] = None 15 | to_address: Optional[str] = None 16 | local_token_address: Optional[str] = None 17 | remote_token_address: Optional[str] = None 18 | bridge_from_address: Optional[str] = None 19 | bridge_to_address: Optional[str] = None 20 | amount: Optional[int] = None 21 | extra_info: Optional[dict] = None 22 | _type: Optional[int] = 0 23 | sender: Optional[str] = None 24 | target: Optional[str] = None 25 | data: Optional[str] = None 26 | -------------------------------------------------------------------------------- /indexer/modules/custom/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/__init__.py: -------------------------------------------------------------------------------- 1 | from functools import wraps 2 | 3 | from flask_restx.namespace import Namespace 4 | 5 | address_profile_namespace = Namespace( 6 | "Address Profile", 7 | path="/", 8 | description="Address profile API", 9 | ) 10 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = [ 2 | "AddressNftTransfer", 3 | "AddressTokenHolder", 4 | "AddressTokenTransfer", 5 | "TokenAddressNftInventory", 6 | "AddressTransaction", 7 | ] 8 | 9 | from indexer.modules.custom.address_index.domain.address_nft_transfer import AddressNftTransfer 10 | from indexer.modules.custom.address_index.domain.address_token_holder import AddressTokenHolder 11 | from indexer.modules.custom.address_index.domain.address_token_transfer import AddressTokenTransfer 12 | from indexer.modules.custom.address_index.domain.address_transaction import AddressTransaction 13 | from indexer.modules.custom.address_index.domain.token_address_nft_inventory import TokenAddressNftInventory 14 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_contract_operation.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressContractOperation(Domain): 8 | address: str 9 | 10 | trace_from_address: str 11 | contract_address: str 12 | 13 | trace_id: str 14 | block_number: int 15 | transaction_index: int 16 | transaction_hash: str 17 | block_timestamp: int 18 | block_hash: str 19 | 20 | error: str 21 | status: int 22 | 23 | creation_code: str 24 | deployed_code: str 25 | 26 | gas: int 27 | gas_used: int 28 | 29 | trace_type: str 30 | call_type: str 31 | 32 | transaction_receipt_status: int 33 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_internal_transaction.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressInternalTransaction(Domain): 8 | address: str 9 | 10 | trace_id: str 11 | block_number: int 12 | transaction_index: int 13 | transaction_hash: str 14 | block_timestamp: int 15 | block_hash: str 16 | 17 | error: str 18 | status: int 19 | 20 | input_method: str 21 | 22 | value: int 23 | gas: int 24 | gas_used: int 25 | 26 | trace_type: str 27 | call_type: str 28 | 29 | txn_type: int 30 | related_address: str 31 | 32 | transaction_receipt_status: int 33 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_nft_1155_holders.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressNft1155Holder(Domain): 8 | address: str 9 | token_address: str 10 | token_id: int 11 | balance_of: str 12 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_nft_transfer.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressNftTransfer(Domain): 8 | address: str 9 | block_number: int 10 | log_index: int 11 | transaction_hash: str 12 | block_timestamp: int 13 | block_hash: str 14 | token_address: str 15 | related_address: str 16 | transfer_type: int 17 | token_id: int 18 | value: int 19 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_token_holder.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressTokenHolder(Domain): 8 | address: str 9 | token_address: str 10 | balance_of: str 11 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_token_transfer.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressTokenTransfer(Domain): 8 | address: str 9 | block_number: int 10 | log_index: int 11 | transaction_hash: str 12 | block_timestamp: int 13 | block_hash: str 14 | token_address: str 15 | related_address: str 16 | transfer_type: int 17 | value: int 18 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/address_transaction.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressTransaction(Domain): 8 | address: str 9 | block_number: int 10 | transaction_index: int 11 | transaction_hash: str 12 | block_timestamp: int 13 | block_hash: str 14 | txn_type: int 15 | related_address: str 16 | value: int 17 | transaction_fee: int 18 | receipt_status: int 19 | method: str 20 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/domain/token_address_nft_inventory.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class TokenAddressNftInventory(Domain): 8 | token_address: str 9 | token_id: int 10 | wallet_address: str 11 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/endpoint/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/address_index/endpoint/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/address_index/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/address_nft_1155_holders.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, desc, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.address_index.domain.address_nft_1155_holders import AddressNft1155Holder 6 | 7 | 8 | class AddressNftTokenHolders(HemeraModel): 9 | __tablename__ = "address_nft_1155_holders" 10 | 11 | address = Column(BYTEA, primary_key=True) 12 | token_address = Column(BYTEA, primary_key=True) 13 | token_id = Column(NUMERIC(100), primary_key=True) 14 | balance_of = Column(NUMERIC(100)) 15 | create_time = Column(TIMESTAMP, server_default=func.now()) 16 | update_time = Column(TIMESTAMP, server_default=func.now()) 17 | 18 | @staticmethod 19 | def model_domain_mapping(): 20 | return [ 21 | { 22 | "domain": AddressNft1155Holder, 23 | "conflict_do_update": True, 24 | "update_strategy": None, 25 | "converter": general_converter, 26 | } 27 | ] 28 | 29 | 30 | Index( 31 | "address_nft_1155_holders_token_address_balance_of_idx", 32 | AddressNftTokenHolders.token_address, 33 | AddressNftTokenHolders.token_id, 34 | desc(AddressNftTokenHolders.balance_of), 35 | ) 36 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/address_nft_transfers.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC, SMALLINT, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.address_index.domain import AddressNftTransfer 6 | 7 | 8 | class AddressNftTransfers(HemeraModel): 9 | __tablename__ = "address_nft_transfers" 10 | 11 | address = Column(BYTEA, primary_key=True) 12 | block_number = Column(INTEGER, primary_key=True) 13 | log_index = Column(INTEGER, primary_key=True) 14 | transaction_hash = Column(BYTEA, primary_key=True) 15 | block_timestamp = Column(TIMESTAMP, primary_key=True) 16 | block_hash = Column(BYTEA, primary_key=True) 17 | token_address = Column(BYTEA) 18 | related_address = Column(BYTEA) 19 | transfer_type = Column(SMALLINT) 20 | token_id = Column(NUMERIC(100), primary_key=True) 21 | value = Column(NUMERIC(100)) 22 | create_time = Column(TIMESTAMP, server_default=func.now()) 23 | update_time = Column(TIMESTAMP, server_default=func.now()) 24 | 25 | @staticmethod 26 | def model_domain_mapping(): 27 | return [ 28 | { 29 | "domain": AddressNftTransfer, 30 | "conflict_do_update": True, 31 | "update_strategy": None, 32 | "converter": general_converter, 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/address_token_holders.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, desc, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.address_index.domain import AddressTokenHolder 6 | 7 | 8 | class AddressTokenHolders(HemeraModel): 9 | __tablename__ = "address_token_holders" 10 | 11 | address = Column(BYTEA, primary_key=True) 12 | token_address = Column(BYTEA, primary_key=True) 13 | balance_of = Column(NUMERIC(100)) 14 | create_time = Column(TIMESTAMP, server_default=func.now()) 15 | update_time = Column(TIMESTAMP, server_default=func.now()) 16 | 17 | @staticmethod 18 | def model_domain_mapping(): 19 | return [ 20 | { 21 | "domain": AddressTokenHolder, 22 | "conflict_do_update": True, 23 | "update_strategy": None, 24 | "converter": general_converter, 25 | } 26 | ] 27 | 28 | 29 | Index( 30 | "address_token_holders_token_address_balance_of_idx", 31 | AddressTokenHolders.token_address, 32 | desc(AddressTokenHolders.balance_of), 33 | ) 34 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/address_token_transfers.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, NUMERIC, SMALLINT, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.address_index.domain import AddressTokenTransfer 6 | 7 | 8 | class AddressTokenTransfers(HemeraModel): 9 | __tablename__ = "address_token_transfers" 10 | 11 | address = Column(BYTEA, primary_key=True) 12 | block_number = Column(INTEGER, primary_key=True) 13 | log_index = Column(INTEGER, primary_key=True) 14 | transaction_hash = Column(BYTEA, primary_key=True) 15 | block_timestamp = Column(TIMESTAMP, primary_key=True) 16 | block_hash = Column(BYTEA, primary_key=True) 17 | token_address = Column(BYTEA) 18 | related_address = Column(BYTEA) 19 | transfer_type = Column(SMALLINT) 20 | value = Column(NUMERIC(100)) 21 | create_time = Column(TIMESTAMP, server_default=func.now()) 22 | update_time = Column(TIMESTAMP, server_default=func.now()) 23 | 24 | @staticmethod 25 | def model_domain_mapping(): 26 | return [ 27 | { 28 | "domain": AddressTokenTransfer, 29 | "conflict_do_update": True, 30 | "update_strategy": None, 31 | "converter": general_converter, 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/scheduled_metadata.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import INTEGER, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class ScheduledMetadata(HemeraModel): 8 | __tablename__ = "af_index_na_scheduled_metadata" 9 | __table_args__ = {"extend_existing": True} 10 | id = Column(INTEGER, primary_key=True) 11 | dag_id = Column(VARCHAR) 12 | execution_date = Column(TIMESTAMP) 13 | last_data_timestamp = Column(TIMESTAMP) 14 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/token_address_index.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import DATE, Column, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, INTEGER, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class TokenAddressIndexStats(HemeraModel): 8 | __tablename__ = "af_index_token_address_stats" 9 | 10 | address = Column(BYTEA, primary_key=True) 11 | 12 | token_holder_count = Column(INTEGER) 13 | token_transfer_count = Column(INTEGER) 14 | 15 | update_time = Column(TIMESTAMP, server_onupdate=func.now()) 16 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/token_address_index_daily_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class TokenAddressIndexStats(HemeraModel): 8 | __tablename__ = "af_index_token_address_daily_stats" 9 | 10 | address = Column(BYTEA, primary_key=True) 11 | 12 | token_holder_count = Column(INTEGER) 13 | token_transfer_count = Column(INTEGER) 14 | 15 | update_time = Column(TIMESTAMP, server_onupdate=func.now()) 16 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/models/token_address_nft_inventories.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.address_index.domain import TokenAddressNftInventory 6 | 7 | 8 | class TokenAddressNftInventories(HemeraModel): 9 | __tablename__ = "token_address_nft_inventories" 10 | 11 | token_address = Column(BYTEA, primary_key=True) 12 | token_id = Column(NUMERIC(100), primary_key=True) 13 | wallet_address = Column(BYTEA) 14 | create_time = Column(TIMESTAMP, server_default=func.now()) 15 | update_time = Column(TIMESTAMP, server_default=func.now()) 16 | 17 | __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) 18 | 19 | @staticmethod 20 | def model_domain_mapping(): 21 | return [ 22 | { 23 | "domain": TokenAddressNftInventory, 24 | "conflict_do_update": True, 25 | "update_strategy": None, 26 | "converter": general_converter, 27 | } 28 | ] 29 | 30 | 31 | Index( 32 | "token_address_nft_inventories_wallet_address_token_address__idx", 33 | TokenAddressNftInventories.wallet_address, 34 | TokenAddressNftInventories.token_address, 35 | TokenAddressNftInventories.token_id, 36 | ) 37 | -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/address_index/schemas/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/address_index/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/address_index/utils/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/blue_chip/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/constants.py: -------------------------------------------------------------------------------- 1 | BLUE_CHIP_PROJECTS = [ 2 | "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d".lower(), 3 | "0x60e4d786628fea6478f785a6d7e704777c86a7c6".lower(), 4 | "0xed5af388653567af2f388e6224dc7c4b3241c544".lower(), 5 | "0x8a90cab2b38dba80c64b7734e58ee1db38b8992e".lower(), 6 | "0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b".lower(), 7 | "0xbd3531da5cf5857e7cfaa92426877b022e612cf8".lower(), 8 | ] 9 | -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/blue_chip/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/domain/feature_blue_chip.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class BlueChipHolder(FilterData): 8 | wallet_address: str 9 | hold_detail: dict 10 | current_count: int 11 | called_block_number: int 12 | called_block_timestamp: int 13 | -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/blue_chip/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/blue_chip/models/feature_blue_chip_holders.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, JSONB, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.blue_chip.domain.feature_blue_chip import BlueChipHolder 6 | 7 | 8 | class FeatureBlueChipHolders(HemeraModel): 9 | __tablename__ = "feature_blue_chip_holders" 10 | wallet_address = Column(BYTEA, primary_key=True) 11 | 12 | hold_detail = Column(JSONB) 13 | 14 | current_count = Column(BIGINT) 15 | 16 | called_block_number = Column(BIGINT) 17 | called_block_timestamp = Column(TIMESTAMP) 18 | 19 | create_time = Column(TIMESTAMP, server_default=func.now()) 20 | update_time = Column(TIMESTAMP, server_default=func.now()) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": BlueChipHolder, 27 | "conflict_do_update": True, 28 | "update_strategy": None, 29 | "converter": general_converter, 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/cyber_id/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/cyber_id/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/domains/cyber_domain.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from datetime import datetime 3 | 4 | from indexer.domain import FilterData 5 | 6 | 7 | @dataclass 8 | class CyberAddressD(FilterData): 9 | address: str 10 | reverse_node: str 11 | name: str 12 | block_number: int 13 | 14 | 15 | @dataclass 16 | class CyberIDRegisterD(FilterData): 17 | label: str 18 | token_id: int 19 | node: str 20 | cost: int 21 | block_number: int 22 | registration: datetime 23 | 24 | 25 | @dataclass 26 | class CyberAddressChangedD(FilterData): 27 | node: str 28 | address: str 29 | block_number: int 30 | -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/cyber_id/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/sql/cyber_id_table_migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE IF NOT EXISTS cyber_address ( 4 | address BYTEA NOT NULL, 5 | name VARCHAR, 6 | reverse_node BYTEA, 7 | block_number BIGINT, 8 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 9 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 10 | PRIMARY KEY (address) 11 | ); 12 | 13 | CREATE TABLE IF NOT EXISTS cyber_id_record ( 14 | node BYTEA NOT NULL, 15 | token_id NUMERIC(100), 16 | label VARCHAR, 17 | registration TIMESTAMP WITHOUT TIME ZONE, 18 | address BYTEA, 19 | block_number BIGINT, 20 | cost NUMERIC(100), 21 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 22 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 23 | PRIMARY KEY (node) 24 | ); 25 | 26 | COMMIT; -------------------------------------------------------------------------------- /indexer/modules/custom/cyber_id/utils.py: -------------------------------------------------------------------------------- 1 | from ens.auto import ns 2 | from ens.constants import EMPTY_SHA3_BYTES 3 | from ens.utils import Web3, address_to_reverse_domain, is_empty_name 4 | from hexbytes import HexBytes 5 | 6 | 7 | def get_reverse_node(address): 8 | address = address_to_reverse_domain(address) 9 | return ns.namehash(address) 10 | 11 | 12 | def label_to_hash(label: str) -> HexBytes: 13 | if "." in label: 14 | raise ValueError(f"Cannot generate hash for label {label!r} with a '.'") 15 | return Web3().keccak(text=label) 16 | 17 | 18 | def get_node(name): 19 | node = EMPTY_SHA3_BYTES 20 | if not is_empty_name(name): 21 | labels = name.split(".") 22 | for label in reversed(labels): 23 | label_hash = label_to_hash(label) 24 | assert isinstance(label_hash, bytes) 25 | assert isinstance(node, bytes) 26 | node = Web3().keccak(node + label_hash) 27 | return node.hex() 28 | -------------------------------------------------------------------------------- /indexer/modules/custom/day_mining/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/day_mining/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/day_mining/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/day_mining/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/day_mining/domain/current_traits_activeness.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from indexer.domain import Domain 5 | 6 | 7 | @dataclass 8 | class CurrentTraitsActiveness(Domain): 9 | block_number: int 10 | address: str 11 | value: dict 12 | update_time: Optional[int] = None 13 | -------------------------------------------------------------------------------- /indexer/modules/custom/day_mining/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/day_mining/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/day_mining/models/current_traits_activeness.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, JSONB, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.day_mining.domain.current_traits_activeness import CurrentTraitsActiveness 6 | 7 | 8 | class CurrentTraitsActivenessModel(HemeraModel): 9 | __tablename__ = "current_traits_activeness" 10 | block_number = Column(BIGINT, primary_key=True) 11 | address = Column(BYTEA, primary_key=True) 12 | 13 | value = Column(JSONB) 14 | 15 | create_time = Column(TIMESTAMP, server_default=func.now()) 16 | update_time = Column(TIMESTAMP, server_default=func.now()) 17 | 18 | __table_args__ = (PrimaryKeyConstraint("address", "block_number"),) 19 | 20 | @staticmethod 21 | def model_domain_mapping(): 22 | return [ 23 | { 24 | "domain": CurrentTraitsActiveness, 25 | "conflict_do_update": True, 26 | "update_strategy": None, 27 | "converter": general_converter, 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /indexer/modules/custom/demo_job/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/demo_job/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/demo_job/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/demo_job/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/demo_job/domain/erc721_token_mint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from dataclasses import dataclass 5 | 6 | from indexer.domain import FilterData 7 | 8 | 9 | @dataclass 10 | class ERC721TokenMint(FilterData): 11 | address: str 12 | token_address: str 13 | token_id: int 14 | block_number: int 15 | block_timestamp: int 16 | transaction_hash: str 17 | log_index: int 18 | -------------------------------------------------------------------------------- /indexer/modules/custom/demo_job/models/erc721_token_mint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from sqlalchemy import Column, Index, PrimaryKeyConstraint, func, text 5 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, INTEGER, NUMERIC, TIMESTAMP 6 | 7 | from common.models import HemeraModel, general_converter 8 | 9 | 10 | class ERC721TokenMint(HemeraModel): 11 | __tablename__ = "erc721_token_mint" 12 | 13 | address = Column(BYTEA) 14 | token_address = Column(BYTEA, primary_key=True) 15 | token_id = Column(NUMERIC(100), primary_key=True) 16 | 17 | block_number = Column(BIGINT) 18 | block_timestamp = Column(TIMESTAMP) 19 | transaction_hash = Column(BYTEA) 20 | log_index = Column(INTEGER) 21 | 22 | create_time = Column(TIMESTAMP, server_default=func.now()) 23 | update_time = Column(TIMESTAMP, server_default=func.now()) 24 | reorg = Column(BOOLEAN, server_default=text("false")) 25 | 26 | __table_args__ = (PrimaryKeyConstraint("token_address", "token_id"),) 27 | 28 | @staticmethod 29 | def model_domain_mapping(): 30 | return [ 31 | { 32 | "domain": "ERC721TokenMint", 33 | "conflict_do_update": True, 34 | "update_strategy": None, 35 | "converter": general_converter, 36 | }, 37 | ] 38 | 39 | 40 | Index( 41 | "erc721_token_mint_address_id_index", 42 | ERC721TokenMint.token_address, 43 | ERC721TokenMint.token_id, 44 | ) 45 | -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/deposit_to_l2/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/deposit_to_l2/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/domain/address_token_deposit.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class AddressTokenDeposit(FilterData): 8 | wallet_address: str 9 | chain_id: int 10 | contract_address: str 11 | token_address: str 12 | value: int 13 | block_number: int 14 | block_timestamp: int 15 | -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/domain/token_deposit_transaction.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class TokenDepositTransaction(FilterData): 8 | transaction_hash: str 9 | wallet_address: str 10 | chain_id: int 11 | contract_address: str 12 | token_address: str 13 | value: int 14 | block_number: int 15 | block_timestamp: int 16 | -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/endpoint/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | token_deposit_namespace = Namespace("Token Deposit Namespace", path="/", description="Token Deposit API") 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/deposit_to_l2/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/deposit_to_l2/models/af_token_deposits_current.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.deposit_to_l2.domain.address_token_deposit import AddressTokenDeposit 6 | 7 | 8 | class AFTokenDepositsCurrent(HemeraModel): 9 | __tablename__ = "af_token_deposits_current" 10 | wallet_address = Column(BYTEA, primary_key=True) 11 | chain_id = Column(BIGINT, primary_key=True) 12 | contract_address = Column(BYTEA, primary_key=True) 13 | token_address = Column(BYTEA, primary_key=True) 14 | value = Column(NUMERIC(100)) 15 | 16 | block_number = Column(BIGINT) 17 | block_timestamp = Column(TIMESTAMP) 18 | 19 | create_time = Column(TIMESTAMP, server_default=func.now()) 20 | update_time = Column(TIMESTAMP, server_default=func.now()) 21 | 22 | __table_args__ = (PrimaryKeyConstraint("wallet_address", "token_address", "contract_address", "chain_id"),) 23 | 24 | @staticmethod 25 | def model_domain_mapping(): 26 | return [ 27 | { 28 | "domain": AddressTokenDeposit, 29 | "conflict_do_update": True, 30 | "update_strategy": "EXCLUDED.block_number > af_token_deposits_current.block_number", 31 | "converter": general_converter, 32 | } 33 | ] 34 | -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/23 17:12 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/eigen_layer/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/domains/eigen_layer_domain.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from indexer.domain import Domain 5 | 6 | 7 | @dataclass 8 | class EigenLayerAction(Domain): 9 | transaction_hash: str 10 | log_index: int 11 | transaction_index: int 12 | internal_idx: Optional[int] = 0 13 | block_number: Optional[int] = None 14 | block_timestamp: Optional[int] = None 15 | event_name: Optional[str] = None 16 | 17 | token: Optional[str] = None 18 | strategy: Optional[str] = None 19 | staker: Optional[str] = None 20 | shares: Optional[int] = None 21 | withdrawer: Optional[str] = None 22 | withdrawroot: Optional[str] = None 23 | 24 | 25 | @dataclass 26 | class EigenLayerAddressCurrent(Domain): 27 | address: Optional[str] = None 28 | strategy: Optional[str] = None 29 | token: Optional[str] = None 30 | deposit_amount: Optional[int] = None 31 | start_withdraw_amount: Optional[int] = None 32 | finish_withdraw_amount: Optional[int] = None 33 | 34 | 35 | def eigen_layer_address_current_factory(): 36 | return EigenLayerAddressCurrent( 37 | address=None, 38 | strategy=None, 39 | token=None, 40 | deposit_amount=0, 41 | start_withdraw_amount=0, 42 | finish_withdraw_amount=0, 43 | ) 44 | -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/endpoint/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | eigen_layer_namespace = Namespace("Eigen Layer Namespace", path="/", description="Eigen Layer API") 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/eigen_layer/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/eigen_layer/sql/eigen_layer_table_migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE IF NOT EXISTS af_eigen_layer_address_current ( 4 | address BYTEA NOT NULL, 5 | strategy BYTEA NOT NULL, 6 | token BYTEA, 7 | deposit_amount NUMERIC(100), 8 | start_withdraw_amount NUMERIC(100), 9 | finish_withdraw_amount NUMERIC(100), 10 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 11 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 12 | PRIMARY KEY (address, strategy) 13 | ); 14 | 15 | CREATE TABLE IF NOT EXISTS af_eigen_layer_records ( 16 | transaction_hash BYTEA NOT NULL, 17 | log_index INTEGER NOT NULL, 18 | internal_idx INTEGER NOT NULL, 19 | block_number BIGINT, 20 | block_timestamp TIMESTAMP WITHOUT TIME ZONE, 21 | method VARCHAR, 22 | event_name VARCHAR, 23 | strategy BYTEA, 24 | token BYTEA, 25 | staker BYTEA, 26 | shares NUMERIC(100), 27 | withdrawer BYTEA, 28 | withdrawroot BYTEA, 29 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 30 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 31 | reorg BOOLEAN DEFAULT false, 32 | PRIMARY KEY (transaction_hash, log_index, internal_idx) 33 | ); 34 | 35 | COMMIT; -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/etherfi/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/abi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/etherfi/abi/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/abi/functions.py: -------------------------------------------------------------------------------- 1 | from common.utils.abi_code_utils import Function 2 | 3 | get_shares_func = Function( 4 | { 5 | "inputs": [{"internalType": "address", "name": "", "type": "address"}], 6 | "name": "shares", 7 | "outputs": [{"internalType": "uint256", "name": "shares", "type": "uint256"}], 8 | "stateMutability": "view", 9 | "type": "function", 10 | } 11 | ) 12 | 13 | total_shares_func = Function( 14 | { 15 | "inputs": [], 16 | "name": "totalShares", 17 | "outputs": [{"internalType": "uint256", "name": "totalShares", "type": "uint256"}], 18 | "stateMutability": "view", 19 | "type": "function", 20 | }, 21 | ) 22 | 23 | total_value_in_lp_func = Function( 24 | { 25 | "inputs": [], 26 | "name": "totalValueInLp", 27 | "outputs": [{"name": "totalValueInLp", "type": "uint128"}], 28 | "stateMutability": "view", 29 | "type": "function", 30 | } 31 | ) 32 | 33 | total_value_out_lp_func = Function( 34 | { 35 | "inputs": [], 36 | "name": "totalValueOutOfLp", 37 | "outputs": [{"name": "totalValueOutOfLp", "type": "uint128"}], 38 | "stateMutability": "view", 39 | "type": "function", 40 | } 41 | ) 42 | -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/etherfi/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/domains/eeth.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class EtherFiShareBalanceD(Domain): 8 | address: str 9 | token_address: str 10 | shares: int 11 | block_number: int 12 | 13 | 14 | @dataclass 15 | class EtherFiShareBalanceCurrentD(Domain): 16 | address: str 17 | token_address: str 18 | shares: int 19 | block_number: int 20 | 21 | 22 | @dataclass 23 | class EtherFiPositionValuesD(Domain): 24 | block_number: int 25 | total_share: int 26 | total_value_out_lp: int 27 | total_value_in_lp: int 28 | -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/domains/lrts.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class EtherFiLrtExchangeRateD(Domain): 8 | exchange_rate: int 9 | token_address: str 10 | block_number: int 11 | -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/etherfi/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/etherfi/models/lrts.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func, text 2 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.etherfi.domains.lrts import EtherFiLrtExchangeRateD 6 | 7 | 8 | class EtherFiLrtExchangeRate(HemeraModel): 9 | __tablename__ = "af_ether_fi_lrt_exchange_rate" 10 | 11 | token_address = Column(BYTEA, primary_key=True) 12 | exchange_rate = Column(NUMERIC(100)) 13 | block_number = Column(BIGINT, primary_key=True) 14 | 15 | create_time = Column(TIMESTAMP, server_default=func.now()) 16 | update_time = Column(TIMESTAMP, server_default=func.now()) 17 | reorg = Column(BOOLEAN, server_default=text("false")) 18 | 19 | __table_args__ = (PrimaryKeyConstraint("token_address", "block_number"),) 20 | 21 | @staticmethod 22 | def model_domain_mapping(): 23 | return [ 24 | { 25 | "domain": EtherFiLrtExchangeRateD, 26 | "conflict_do_update": True, 27 | "update_strategy": None, 28 | "converter": general_converter, 29 | } 30 | ] 31 | -------------------------------------------------------------------------------- /indexer/modules/custom/feature_type.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class FeatureType(Enum): 5 | UNISWAP_V3_POOLS = 1 6 | UNISWAP_V3_TOKENS = 2 7 | DAY_MINING = 3 8 | UNISWAP_V2_INFO = 4 9 | BLUE_CHIP_HOLDING = 5 10 | MERCHANT_MOE_1155_LIQUIDITY = 6 11 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/__init__.py: -------------------------------------------------------------------------------- 1 | import codecs 2 | 3 | from eth_abi import encoding 4 | from eth_abi.decoding import StringDecoder 5 | from eth_abi.registry import BaseEquals, registry 6 | 7 | 8 | class CompatibleStringDecoder(StringDecoder): 9 | 10 | @staticmethod 11 | def decoder_fn(data, handle_string_errors="strict"): 12 | try: 13 | return data.decode("utf-8", errors=handle_string_errors) 14 | except UnicodeDecodeError: 15 | try: 16 | return data.decode("latin-1", errors=handle_string_errors) 17 | except UnicodeDecodeError: 18 | return codecs.decode(data, "hex") 19 | 20 | 21 | lifo_registry = registry.copy() 22 | lifo_registry.unregister("string") 23 | 24 | lifo_registry.register( 25 | BaseEquals("string"), 26 | encoding.TextStringEncoder, 27 | CompatibleStringDecoder, 28 | label="string", 29 | ) 30 | 31 | 32 | from .ens_conf import CONTRACT_NAME_MAP 33 | from .ens_handler import EnsConfLoader, EnsHandler 34 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/abi/0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "contract ENS", 7 | "name": "_ens", 8 | "type": "address" 9 | } 10 | ], 11 | "stateMutability": "nonpayable", 12 | "type": "constructor" 13 | }, 14 | { 15 | "inputs": [ 16 | { 17 | "internalType": "address[]", 18 | "name": "addresses", 19 | "type": "address[]" 20 | } 21 | ], 22 | "name": "getNames", 23 | "outputs": [ 24 | { 25 | "internalType": "string[]", 26 | "name": "r", 27 | "type": "string[]" 28 | } 29 | ], 30 | "stateMutability": "view", 31 | "type": "function" 32 | } 33 | ], 34 | "address": "0x3671ae578e63fdf66ad4f3e12cc0c0d71ac7510c" 35 | } -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/abi/0x690f0581ececcf8389c223170778cd9d029606f2.json: -------------------------------------------------------------------------------- 1 | { 2 | "abi": [ 3 | { 4 | "inputs": [ 5 | { 6 | "internalType": "address", 7 | "name": "_masterCopy", 8 | "type": "address" 9 | } 10 | ], 11 | "payable": false, 12 | "stateMutability": "nonpayable", 13 | "type": "constructor" 14 | }, 15 | { 16 | "payable": true, 17 | "stateMutability": "payable", 18 | "type": "fallback" 19 | } 20 | ], 21 | "address": "0x690f0581ececcf8389c223170778cd9d029606f2" 22 | } -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/endpoint/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | af_ens_namespace = Namespace("Hemera ENS Namespace", path="/", description="ENS feature") 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/endpoint/action_types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/12 10:22 4 | # @Author will 5 | # @File action_types.py 6 | # @Brief 7 | from enum import Enum 8 | 9 | 10 | class OperationType(Enum): 11 | REGISTER = "register" 12 | SET_PRIMARY_NAME = "set_primary_name" 13 | SET_RESOLVED_ADDRESS = "set_resolved_address" 14 | RENEW = "renew" 15 | TRANSFER = "transfer" 16 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/ens_abi.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | 4 | 5 | def get_absolute_path(relative_path): 6 | current_dir = os.path.dirname(os.path.abspath(__file__)) 7 | absolute_path = os.path.join(current_dir, relative_path) 8 | return absolute_path 9 | 10 | 11 | abi_map = {} 12 | 13 | relative_path = "abi" 14 | absolute_path = get_absolute_path(relative_path) 15 | fs = os.listdir(absolute_path) 16 | for a_f in fs: 17 | with open(os.path.join(absolute_path, a_f), "r") as data_file: 18 | dic = json.load(data_file) 19 | abi_map[dic["address"].lower()] = json.dumps(dic["abi"]) 20 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/ens_hash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/5/23 17:18 4 | # @Author will 5 | # @File ens_hash.py 6 | # @Brief 7 | from eth_utils import keccak 8 | from web3 import Web3 9 | 10 | 11 | def namehash(name): 12 | """ 13 | Calculate the namehash of an ENS name. 14 | 15 | :param name: The ENS name to hash (e.g., 'example.eth'). 16 | :return: The namehash as a hexadecimal string. 17 | """ 18 | node = b"\x00" * 32 19 | if name: 20 | labels = name.split(".") 21 | for label in reversed(labels): 22 | node = keccak(node + keccak(label.encode("utf-8"))) 23 | return Web3.to_hex(node) 24 | 25 | 26 | def get_label(name): 27 | return Web3.to_hex(keccak(name.encode("utf-8"))) 28 | 29 | 30 | def compute_node_label(base_node, label): 31 | base_node = base_node.lower() 32 | if base_node.startswith("0x"): 33 | base_node = base_node[2:] 34 | label = label.lower() 35 | if label.startswith("0x"): 36 | label = label[2:] 37 | 38 | node = keccak(bytes.fromhex(base_node) + bytes.fromhex(label)) 39 | return Web3.to_hex(node).lower() 40 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/models/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/6 14:56 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/models/af_ens_address_current.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | from indexer.modules.custom.hemera_ens.ens_domain import ENSAddressD 6 | from indexer.modules.custom.hemera_ens.models.af_ens_node_current import ens_general_converter 7 | 8 | 9 | class ENSAddress(HemeraModel): 10 | __tablename__ = "af_ens_address_current" 11 | 12 | address = Column(BYTEA, primary_key=True) 13 | name = Column(VARCHAR) 14 | reverse_node = Column(BYTEA) 15 | block_number = Column(BIGINT) 16 | create_time = Column(TIMESTAMP, server_default=func.now()) 17 | update_time = Column(TIMESTAMP, server_default=func.now(), onupdate=func.now()) 18 | 19 | @staticmethod 20 | def model_domain_mapping(): 21 | return [ 22 | { 23 | "domain": ENSAddressD, 24 | "conflict_do_update": True, 25 | "update_strategy": "EXCLUDED.block_number > af_ens_address_current.block_number", 26 | "converter": ens_general_converter, 27 | } 28 | ] 29 | -------------------------------------------------------------------------------- /indexer/modules/custom/hemera_ens/util.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | 3 | 4 | def convert_str_ts(st): 5 | if not st: 6 | return None 7 | if isinstance(st, int): 8 | return datetime.fromtimestamp(st).strftime("%Y-%m-%d %H:%M:%S") 9 | if st: 10 | try: 11 | dt = datetime.strptime(st, "%Y-%m-%d %H:%M:%S %Z") 12 | except ValueError: 13 | try: 14 | dt = datetime.strptime(st, "%Y-%m-%d %H:%M:%S.%f %Z") 15 | except ValueError: 16 | return None 17 | if dt: 18 | return dt.strftime("%Y-%m-%d %H:%M:%S") 19 | return None 20 | -------------------------------------------------------------------------------- /indexer/modules/custom/init_capital/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/init_capital/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/init_capital/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/init_capital/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/init_capital/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | init_capital_namespace = Namespace("Init Capital Namespace", path="/", description="Init Capital API") 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/init_capital/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/init_capital/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/karak/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/19 15:18 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | """Currently, this job only support Deposit, StartWithDraw, FinishWithDraw, more events coming soon""" 8 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/19 15:18 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/endpoints/routes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/20 15:01 4 | # @Author will 5 | # @File routes.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/karak_conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/23 13:31 4 | # @Author will 5 | # @File karak_conf.py.py 6 | # @Brief 7 | CHAIN_CONTRACT = { 8 | 1: { 9 | "DEPOSIT": { 10 | # address is all vaults 11 | "topic": "0xdcbc1c05240f31ff3ad067ef1ee35ce4997762752e3a095284754544f4c709d7", 12 | }, 13 | "TRANSFER": { 14 | # address is all vaults 15 | "topic": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", 16 | }, 17 | "START_WITHDRAW": { 18 | "address": "0xafa904152e04abff56701223118be2832a4449e0", 19 | "topic": "0x6ee63f530864567ac8a1fcce5050111457154b213c6297ffc622603e8497f7b2", 20 | }, 21 | "FINISH_WITHDRAW": { 22 | "address": "0xafa904152e04abff56701223118be2832a4449e0", 23 | "topic": "0x486508c3c40ef7985dcc1f7d43acb1e77e0059505d1f0e6064674ca655a0c82f", 24 | }, 25 | "NEW_VAULT": { 26 | "address": "0x54e44dbb92dba848ace27f44c0cb4268981ef1cc", 27 | "topic": "0x2cd7a531712f8899004c782d9607e0886d1dbc91bfac7be88dadf6750d9e1419", 28 | "starts_with": "0xf0edf6aa", 29 | }, 30 | "VAULT_SUPERVISOR": "0x54e44dbb92dba848ace27f44c0cb4268981ef1cc", 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/models/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/19 15:18 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/models/af_karak_address_current.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/19 15:24 4 | # @Author will 5 | # @File af_karak_address_current.py 6 | # @Brief 7 | 8 | from sqlalchemy import Column, PrimaryKeyConstraint, func, text 9 | from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, NUMERIC, TIMESTAMP 10 | 11 | from common.models import HemeraModel, general_converter 12 | from indexer.modules.custom.karak.karak_domain import KarakAddressCurrentD 13 | 14 | 15 | class AfKarakAddressCurrent(HemeraModel): 16 | __tablename__ = "af_karak_address_current" 17 | address = Column(BYTEA, primary_key=True) 18 | 19 | vault = Column(BYTEA, primary_key=True) 20 | deposit_amount = Column(NUMERIC(100)) 21 | start_withdraw_amount = Column(NUMERIC(100)) 22 | finish_withdraw_amount = Column(NUMERIC(100)) 23 | 24 | d_s = Column(NUMERIC(100)) 25 | d_f = Column(NUMERIC(100)) 26 | s_f = Column(NUMERIC(100)) 27 | 28 | create_time = Column(TIMESTAMP, server_default=func.now()) 29 | update_time = Column(TIMESTAMP, server_default=func.now()) 30 | 31 | __table_args__ = (PrimaryKeyConstraint("address", "vault"),) 32 | 33 | @staticmethod 34 | def model_domain_mapping(): 35 | return [ 36 | { 37 | "domain": KarakAddressCurrentD, 38 | "conflict_do_update": True, 39 | "update_strategy": None, 40 | "converter": general_converter, 41 | } 42 | ] 43 | -------------------------------------------------------------------------------- /indexer/modules/custom/karak/models/af_karak_vault_token.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/9/20 10:11 4 | # @Author will 5 | # @File af_karak_vault_token.py 6 | # @Brief 7 | from sqlalchemy import Column, PrimaryKeyConstraint, func 8 | from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, TIMESTAMP, VARCHAR 9 | 10 | from common.models import HemeraModel, general_converter 11 | from indexer.modules.custom.karak.karak_domain import KarakVaultTokenD 12 | 13 | 14 | class AfKarakVaultToken(HemeraModel): 15 | __tablename__ = "af_karak_vault_token" 16 | 17 | vault = Column(BYTEA, primary_key=True, nullable=False) 18 | token = Column(BYTEA, primary_key=True, nullable=False) 19 | name = Column(VARCHAR) 20 | symbol = Column(VARCHAR) 21 | asset_type = Column(INTEGER) 22 | 23 | create_time = Column(TIMESTAMP, server_default=func.now()) 24 | update_time = Column(TIMESTAMP, server_default=func.now()) 25 | 26 | __table_args__ = (PrimaryKeyConstraint("vault", "token"),) 27 | 28 | @staticmethod 29 | def model_domain_mapping(): 30 | return [ 31 | { 32 | "domain": KarakVaultTokenD, 33 | "conflict_do_update": True, 34 | "update_strategy": None, 35 | "converter": general_converter, 36 | } 37 | ] 38 | -------------------------------------------------------------------------------- /indexer/modules/custom/lido/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/lido/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/lido/abi/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/lido/abi/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/lido/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/lido/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/lido/domains/seth.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class LidoShareBalanceD(Domain): 8 | address: str 9 | token_address: str 10 | shares: int 11 | block_number: int 12 | 13 | 14 | @dataclass 15 | class LidoShareBalanceCurrentD(Domain): 16 | address: str 17 | token_address: str 18 | shares: int 19 | block_number: int 20 | 21 | 22 | @dataclass 23 | class LidoPositionValuesD(Domain): 24 | block_number: int 25 | total_share: int 26 | buffered_eth: int 27 | consensus_layer: int 28 | deposited_validators: int 29 | cl_validators: int 30 | -------------------------------------------------------------------------------- /indexer/modules/custom/lido/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/lido/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/lido/sql/lido_sql_migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE IF NOT EXISTS "af_lido_position_values" ( 4 | "block_number" int8 NOT NULL, 5 | "total_share" numeric, 6 | "buffered_eth" numeric, 7 | "consensus_layer" numeric, 8 | "deposited_validators" numeric, 9 | "cl_validators" numeric, 10 | "create_time" timestamp DEFAULT now(), 11 | "update_time" timestamp DEFAULT now(), 12 | "reorg" bool DEFAULT false, 13 | PRIMARY KEY ("block_number") 14 | ); 15 | 16 | CREATE TABLE IF NOT EXISTS "af_lido_seth_share_balances" ( 17 | "address" bytea NOT NULL, 18 | "token_address" bytea NOT NULL, 19 | "block_number" int8 NOT NULL, 20 | "create_time" timestamp DEFAULT now(), 21 | "update_time" timestamp DEFAULT now(), 22 | "reorg" bool DEFAULT false, 23 | "shares" numeric, 24 | PRIMARY KEY ("address","token_address","block_number") 25 | ); 26 | 27 | CREATE TABLE IF NOT EXISTS "af_lido_seth_share_balances_current" ( 28 | "address" bytea NOT NULL, 29 | "token_address" bytea NOT NULL, 30 | "shares" numeric, 31 | "block_number" int8 NOT NULL, 32 | "create_time" timestamp DEFAULT now(), 33 | "update_time" timestamp DEFAULT now(), 34 | "reorg" bool DEFAULT false, 35 | PRIMARY KEY ("address","token_address") 36 | ); 37 | 38 | 39 | COMMIT; -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/merchant_moe/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/constants.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/merchant_moe/constants.py -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/merchant_moe/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/domains/erc1155_token_holding.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain, FilterData 4 | 5 | 6 | @dataclass 7 | class MerchantMoeErc1155TokenHolding(FilterData): 8 | position_token_address: str 9 | wallet_address: str 10 | token_id: int 11 | balance: int 12 | block_number: int 13 | block_timestamp: int 14 | 15 | 16 | @dataclass 17 | class MerchantMoeErc1155TokenCurrentHolding(FilterData): 18 | position_token_address: str 19 | wallet_address: str 20 | token_id: int 21 | balance: int 22 | block_number: int 23 | block_timestamp: int 24 | 25 | 26 | @dataclass 27 | class MerchantMoeErc1155TokenSupply(FilterData): 28 | position_token_address: str 29 | token_id: int 30 | total_supply: int 31 | block_number: int 32 | block_timestamp: int 33 | 34 | 35 | @dataclass 36 | class MerchantMoeErc1155TokenCurrentSupply(FilterData): 37 | position_token_address: str 38 | token_id: int 39 | total_supply: int 40 | block_number: int 41 | block_timestamp: int 42 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/domains/merchant_moe.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain, FilterData 4 | 5 | 6 | @dataclass 7 | class MerchantMoeTokenBin(FilterData): 8 | position_token_address: str 9 | token_id: int 10 | reserve0_bin: int 11 | reserve1_bin: int 12 | block_number: int 13 | block_timestamp: int 14 | 15 | 16 | @dataclass 17 | class MerchantMoeTokenCurrentBin(FilterData): 18 | position_token_address: str 19 | token_id: int 20 | reserve0_bin: int 21 | reserve1_bin: int 22 | block_number: int 23 | block_timestamp: int 24 | 25 | 26 | @dataclass 27 | class MerchantMoePool(FilterData): 28 | position_token_address: str 29 | token0_address: str 30 | token1_address: str 31 | block_number: int 32 | block_timestamp: int 33 | 34 | 35 | @dataclass 36 | class MerchantMoePoolRecord(FilterData): 37 | pool_address: str 38 | active_id: int 39 | bin_step: int 40 | block_number: int 41 | block_timestamp: int 42 | 43 | 44 | @dataclass 45 | class MerchantMoePoolCurrentStatus(FilterData): 46 | pool_address: str 47 | active_id: int 48 | bin_step: int 49 | block_number: int 50 | block_timestamp: int 51 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from flask_restx.namespace import Namespace 4 | 5 | merchant_moe_namespace = Namespace("MerchantMoe Data", path="/", description="MerchantMoe Data Feature Explorer API") 6 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/merchant_moe/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/models/feature_erc1155_token_current_supply.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.merchant_moe.domains.erc1155_token_holding import MerchantMoeErc1155TokenCurrentSupply 6 | 7 | 8 | class FeatureErc1155TokenCurrentSupplyStatus(HemeraModel): 9 | __tablename__ = "af_merchant_moe_token_supply_current" 10 | position_token_address = Column(BYTEA, primary_key=True) 11 | token_id = Column(NUMERIC(100), primary_key=True) 12 | block_timestamp = Column(BIGINT) 13 | block_number = Column(BIGINT) 14 | 15 | total_supply = Column(NUMERIC(100)) 16 | 17 | create_time = Column(TIMESTAMP, server_default=func.now()) 18 | update_time = Column(TIMESTAMP, server_default=func.now()) 19 | 20 | __table_args__ = (PrimaryKeyConstraint("position_token_address", "token_id"),) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": MerchantMoeErc1155TokenCurrentSupply, 27 | "conflict_do_update": True, 28 | "update_strategy": "EXCLUDED.block_number > af_merchant_moe_token_supply_current.block_number", 29 | "converter": general_converter, 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func, text 2 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.merchant_moe.domains.merchant_moe import MerchantMoePool 6 | 7 | 8 | class FeatureMerchantMoePools(HemeraModel): 9 | __tablename__ = "af_merchant_moe_pools" 10 | position_token_address = Column(BYTEA, primary_key=True) 11 | block_timestamp = Column(BIGINT) 12 | block_number = Column(BIGINT) 13 | token0_address = Column(BYTEA) 14 | token1_address = Column(BYTEA) 15 | 16 | create_time = Column(TIMESTAMP, server_default=func.now()) 17 | update_time = Column(TIMESTAMP, server_default=func.now()) 18 | reorg = Column(BOOLEAN, server_default=text("false")) 19 | 20 | __table_args__ = (PrimaryKeyConstraint("position_token_address"),) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": MerchantMoePool, 27 | "conflict_do_update": True, 28 | "update_strategy": None, 29 | "converter": general_converter, 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_record_status.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.merchant_moe.domains.merchant_moe import MerchantMoePoolCurrentStatus 6 | 7 | 8 | class FeatureMerchantMoePoolRecordStatus(HemeraModel): 9 | __tablename__ = "af_merchant_moe_pool_data_current" 10 | pool_address = Column(BYTEA, primary_key=True) 11 | block_timestamp = Column(BIGINT) 12 | block_number = Column(BIGINT) 13 | active_id = Column(BIGINT) 14 | bin_step = Column(BIGINT) 15 | 16 | create_time = Column(TIMESTAMP, server_default=func.now()) 17 | update_time = Column(TIMESTAMP, server_default=func.now()) 18 | reorg = Column(BOOLEAN, default=False) 19 | 20 | __table_args__ = (PrimaryKeyConstraint("pool_address"),) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": MerchantMoePoolCurrentStatus, 27 | "conflict_do_update": True, 28 | "update_strategy": "EXCLUDED.block_number > af_merchant_moe_pool_data_current.block_number", 29 | "converter": general_converter, 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/merchant_moe/models/feature_merchant_moe_pool_records.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BOOLEAN, BYTEA, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.merchant_moe.domains.merchant_moe import MerchantMoePoolRecord 6 | 7 | 8 | class FeatureMerchantMoePoolRecords(HemeraModel): 9 | __tablename__ = "af_merchant_moe_pool_data_hist" 10 | pool_address = Column(BYTEA, primary_key=True) 11 | block_timestamp = Column(BIGINT, primary_key=True) 12 | block_number = Column(BIGINT, primary_key=True) 13 | active_id = Column(BIGINT) 14 | bin_step = Column(BIGINT) 15 | 16 | create_time = Column(TIMESTAMP, server_default=func.now()) 17 | update_time = Column(TIMESTAMP, server_default=func.now()) 18 | reorg = Column(BOOLEAN, default=False) 19 | 20 | __table_args__ = (PrimaryKeyConstraint("pool_address", "block_timestamp", "block_number"),) 21 | 22 | @staticmethod 23 | def model_domain_mapping(): 24 | return [ 25 | { 26 | "domain": MerchantMoePoolRecord, 27 | "conflict_do_update": True, 28 | "update_strategy": None, 29 | "converter": general_converter, 30 | } 31 | ] 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/opensea/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/opensea/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/domain/address_opensea_transactions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class AddressOpenseaTransaction(Domain): 8 | address: str 9 | related_address: str 10 | is_offer: bool 11 | transaction_type: int 12 | order_hash: str 13 | zone: str 14 | 15 | offer: dict 16 | consideration: dict 17 | 18 | fee: dict 19 | 20 | transaction_hash: str 21 | block_number: int 22 | log_index: int 23 | block_timestamp: int 24 | block_hash: str 25 | 26 | protocol_version: str = "1.6" 27 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/domain/opensea_order.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import Domain 4 | 5 | 6 | @dataclass 7 | class OpenseaOrder(Domain): 8 | order_hash: str 9 | zone: str 10 | offerer: str 11 | recipient: str 12 | offer: dict 13 | consideration: dict 14 | block_timestamp: int 15 | block_hash: str 16 | transaction_hash: str 17 | log_index: int 18 | block_number: int 19 | 20 | protocol_version: str = "1.6" 21 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/endpoint/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from flask_restx.namespace import Namespace 5 | 6 | opensea_namespace = Namespace("Open Sea Namespace", path="/", description="Open Sea API") 7 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/models/address_opensea_profile.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, Computed, func, text 2 | from sqlalchemy.dialects.postgresql import BYTEA, INTEGER, JSONB, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class AddressOpenseaProfile(HemeraModel): 8 | __tablename__ = "af_opensea_profile" 9 | 10 | address = Column(BYTEA, primary_key=True) 11 | buy_txn_count = Column(INTEGER, server_default=text("0")) 12 | sell_txn_count = Column(INTEGER, server_default=text("0")) 13 | swap_txn_count = Column(INTEGER, server_default=text("0")) 14 | buy_opensea_order_count = Column(INTEGER, server_default=text("0")) 15 | sell_opensea_order_count = Column(INTEGER, server_default=text("0")) 16 | swap_opensea_order_count = Column(INTEGER, server_default=text("0")) 17 | buy_nft_stats = Column(JSONB) 18 | sell_nft_stats = Column(JSONB) 19 | buy_volume_usd = Column(NUMERIC) 20 | sell_volume_usd = Column(NUMERIC) 21 | create_time = Column(TIMESTAMP, server_default=func.now()) 22 | update_time = Column(TIMESTAMP, server_default=func.now()) 23 | first_transaction_hash = Column(BYTEA) 24 | first_block_timestamp = Column(TIMESTAMP) 25 | txn_count = Column(INTEGER, Computed("(buy_txn_count + sell_txn_count) + swap_txn_count")) 26 | opensea_order_count = Column( 27 | INTEGER, Computed("(buy_opensea_order_count + sell_opensea_order_count) + swap_opensea_order_count") 28 | ) 29 | volume_usd = Column(NUMERIC, server_default=text("0")) 30 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/models/daily_address_opensea_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, func 2 | from sqlalchemy.dialects.postgresql import BYTEA, DATE, INTEGER, JSONB, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyAddressOpenseaTransactions(HemeraModel): 8 | __tablename__ = "af_opensea_daily_transactions" 9 | 10 | address = Column(BYTEA, primary_key=True) 11 | block_date = Column(DATE, primary_key=True) 12 | 13 | buy_txn_count = Column(INTEGER) 14 | sell_txn_count = Column(INTEGER) 15 | swap_txn_count = Column(INTEGER) 16 | 17 | buy_opensea_order_count = Column(INTEGER) 18 | sell_opensea_order_count = Column(INTEGER) 19 | swap_opensea_order_count = Column(INTEGER) 20 | 21 | buy_nft_stats = Column(JSONB) 22 | sell_nft_stats = Column(JSONB) 23 | 24 | buy_volume_crypto = Column(JSONB) 25 | sell_volume_crypto = Column(JSONB) 26 | 27 | buy_volume_usd = Column(NUMERIC) 28 | sell_volume_usd = Column(NUMERIC) 29 | 30 | create_time = Column(TIMESTAMP, server_default=func.now()) 31 | update_time = Column(TIMESTAMP, server_default=func.now()) 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/models/opensea_crypto_mapping.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, text 2 | from sqlalchemy.dialects.postgresql import INTEGER, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class OpenseaCryptoTokenMapping(HemeraModel): 8 | __tablename__ = "af_opensea_na_crypto_token_mapping" 9 | 10 | id = Column(INTEGER, primary_key=True, autoincrement=True) 11 | address_var = Column(VARCHAR(42)) 12 | price_symbol = Column(VARCHAR) 13 | decimals = Column(INTEGER, server_default=text("18"), nullable=False) 14 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/models/scheduled_metadata.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import INTEGER, TIMESTAMP, VARCHAR 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class ScheduledMetadata(HemeraModel): 8 | __tablename__ = "af_opensea_na_scheduled_metadata" 9 | 10 | id = Column(INTEGER, primary_key=True) 11 | dag_id = Column(VARCHAR) 12 | execution_date = Column(TIMESTAMP) 13 | last_data_timestamp = Column(TIMESTAMP) 14 | -------------------------------------------------------------------------------- /indexer/modules/custom/opensea/parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/opensea/parser/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/pendle/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/pendle/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/pendle/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/pendle/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/pendle/domains/market.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class PendlePoolD(FilterData): 8 | market_address: str 9 | sy_address: str 10 | pt_address: str 11 | yt_address: str 12 | underlying_asset: str 13 | block_number: int 14 | chain_id: int 15 | 16 | 17 | @dataclass 18 | class PendleUserActiveBalanceD(FilterData): 19 | market_address: str 20 | user_address: str 21 | sy_balance: int 22 | active_balance: int 23 | total_active_supply: int 24 | market_sy_balance: int 25 | block_number: int 26 | chain_id: int 27 | 28 | 29 | @dataclass 30 | class PendleUserActiveBalanceCurrentD(FilterData): 31 | market_address: str 32 | user_address: str 33 | sy_balance: int 34 | active_balance: int 35 | total_active_supply: int 36 | market_sy_balance: int 37 | block_number: int 38 | chain_id: int 39 | -------------------------------------------------------------------------------- /indexer/modules/custom/pendle/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/pendle/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/pendle/sql/pendle_sql_migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | 4 | 5 | CREATE TABLE IF NOT EXISTS "af_pendle_pool" ( 6 | "market_address" bytea NOT NULL, 7 | "sy_address" bytea, 8 | "pt_address" bytea, 9 | "yt_address" bytea, 10 | "block_number" int8, 11 | "chain_id" int8, 12 | "create_time" timestamp DEFAULT CURRENT_TIMESTAMP, 13 | "underlying_asset" bytea, 14 | PRIMARY KEY ("market_address") 15 | ); 16 | 17 | CREATE TABLE IF NOT EXISTS "af_pendle_user_active_balance" ( 18 | "market_address" bytea NOT NULL, 19 | "user_address" bytea NOT NULL, 20 | "sy_balance" numeric, 21 | "active_balance" numeric, 22 | "total_active_supply" numeric, 23 | "market_sy_balance" numeric, 24 | "block_number" int8 NOT NULL, 25 | "chain_id" int8, 26 | "create_time" timestamp DEFAULT CURRENT_TIMESTAMP, 27 | PRIMARY KEY ("market_address","user_address","block_number") 28 | ); 29 | 30 | CREATE TABLE IF NOT EXISTS "af_pendle_user_active_balance_current" ( 31 | "market_address" bytea NOT NULL, 32 | "user_address" bytea NOT NULL, 33 | "sy_balance" numeric, 34 | "active_balance" numeric, 35 | "total_active_supply" numeric, 36 | "market_sy_balance" numeric, 37 | "block_number" int8 NOT NULL, 38 | "chain_id" int8, 39 | "create_time" timestamp DEFAULT CURRENT_TIMESTAMP, 40 | "update_time" timestamp DEFAULT CURRENT_TIMESTAMP, 41 | PRIMARY KEY ("market_address","user_address") 42 | ); 43 | 44 | 45 | COMMIT; -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/project_contracts/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/project_contracts/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/domain/project_contract_domain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/10/14 11:42 4 | # @Author will 5 | # @File project_contract_domain.py 6 | # @Brief 7 | from dataclasses import dataclass 8 | from typing import Optional 9 | 10 | from indexer.domain import FilterData 11 | 12 | 13 | @dataclass 14 | class ProjectContractD(FilterData): 15 | 16 | project_id: Optional[str] = None 17 | chain_id: Optional[int] = None 18 | address: Optional[str] = None 19 | deployer: Optional[str] = None 20 | 21 | transaction_from_address: Optional[str] = None 22 | trace_creator: Optional[str] = None 23 | block_number: Optional[int] = None 24 | block_timestamp: Optional[int] = None 25 | transaction_hash: Optional[str] = None 26 | -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/project_contracts/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/models/projects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/10/14 11:18 4 | # @Author will 5 | # @File projects.py 6 | # @Brief 7 | from sqlalchemy import Column, PrimaryKeyConstraint, func, text 8 | from sqlalchemy.dialects.postgresql import BOOLEAN, BYTEA, INTEGER, JSONB, TIMESTAMP, VARCHAR 9 | 10 | from common.models import HemeraModel 11 | 12 | 13 | class AfProjects(HemeraModel): 14 | __tablename__ = "af_projects" 15 | project_id = Column(VARCHAR, primary_key=True) 16 | name = Column(VARCHAR) 17 | deployer = Column(BYTEA, primary_key=True) 18 | address_type = Column(INTEGER, default=0, comment="0是作为deploy地址不参与统计;1参与统计") 19 | 20 | create_time = Column(TIMESTAMP, server_default=func.now()) 21 | update_time = Column(TIMESTAMP, server_default=func.now()) 22 | 23 | __table_args__ = (PrimaryKeyConstraint("project_id", "deployer"),) 24 | -------------------------------------------------------------------------------- /indexer/modules/custom/project_contracts/sql/project_contract_table_migration.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | CREATE TABLE IF NOT EXISTS af_project_contracts ( 4 | project_id VARCHAR, 5 | chain_id INTEGER, 6 | address BYTEA NOT NULL, 7 | deployer BYTEA, 8 | transaction_from_address BYTEA, 9 | trace_creator BYTEA, 10 | block_number BIGINT, 11 | block_timestamp TIMESTAMP WITHOUT TIME ZONE, 12 | transaction_hash BYTEA, 13 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 14 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 15 | reorg BOOLEAN DEFAULT false, 16 | PRIMARY KEY (address) 17 | ); 18 | 19 | CREATE TABLE IF NOT EXISTS af_projects ( 20 | project_id VARCHAR NOT NULL, 21 | name VARCHAR, 22 | deployer BYTEA NOT NULL, 23 | address_type INTEGER, 24 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 25 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 26 | PRIMARY KEY (project_id, deployer) 27 | ); 28 | 29 | COMMENT ON COLUMN af_projects.address_type IS '0是作为deploy地址不参与统计;1参与统计'; 30 | 31 | COMMIT; -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Not only index FBTC, but also cmETH and even other tokens 3 | """ 4 | -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/staking_fbtc/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/domain/af_staked_transferred_balance.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | # records for all token 7 | @dataclass 8 | class AfStakedTransferredBalanceHistDomain(FilterData): 9 | contract_address: str 10 | protocol_id: str 11 | wallet_address: str 12 | token_address: str 13 | block_transfer_value: int 14 | block_cumulative_value: int 15 | block_number: int 16 | block_timestamp: int 17 | 18 | 19 | @dataclass 20 | class AfStakedTransferredBalanceCurrentDomain(FilterData): 21 | contract_address: str 22 | protocol_id: str 23 | wallet_address: str 24 | token_address: str 25 | block_transfer_value: int 26 | block_cumulative_value: int 27 | block_number: int 28 | block_timestamp: int 29 | -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/domain/feature_staked_fbtc_detail.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class StakedFBTCDetail(FilterData): 8 | vault_address: str 9 | protocol_id: str 10 | wallet_address: str 11 | amount: int 12 | changed_amount: int 13 | block_number: int 14 | block_timestamp: int 15 | 16 | 17 | @dataclass 18 | class StakedFBTCCurrentStatus(FilterData): 19 | vault_address: str 20 | protocol_id: str 21 | wallet_address: str 22 | amount: int 23 | changed_amount: int 24 | block_number: int 25 | block_timestamp: int 26 | 27 | 28 | @dataclass 29 | class TransferredFBTCDetail(FilterData): 30 | vault_address: str 31 | protocol_id: str 32 | wallet_address: str 33 | amount: int 34 | changed_amount: int 35 | block_number: int 36 | block_timestamp: int 37 | 38 | 39 | @dataclass 40 | class TransferredFBTCCurrentStatus(FilterData): 41 | vault_address: str 42 | protocol_id: str 43 | wallet_address: str 44 | amount: int 45 | changed_amount: int 46 | block_number: int 47 | block_timestamp: int 48 | -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from flask_restx.namespace import Namespace 4 | 5 | staking_namespace = Namespace("Staking Explorer", path="/", description="Staking Feature Explorer API") 6 | -------------------------------------------------------------------------------- /indexer/modules/custom/staking_fbtc/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/staking_fbtc/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/stats/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/daily_addresses_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, DATE 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyAddressesStats(HemeraModel): 8 | 9 | __tablename__ = "af_stats_na_daily_addresses" 10 | 11 | block_date = Column(DATE, primary_key=True) 12 | active_address_cnt = Column(BIGINT) 13 | receiver_address_cnt = Column(BIGINT) 14 | sender_address_cnt = Column(BIGINT) 15 | total_address_cnt = Column(BIGINT) 16 | new_address_cnt = Column(BIGINT) 17 | -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/daily_blocks_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import NUMERIC, Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, DATE 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyBlocksStats(HemeraModel): 8 | 9 | __tablename__ = "af_stats_na_daily_blocks" 10 | 11 | block_date = Column(DATE, primary_key=True) 12 | cnt = Column(BIGINT) 13 | avg_size = Column(NUMERIC) 14 | avg_gas_limit = Column(NUMERIC) 15 | avg_gas_used = Column(NUMERIC) 16 | total_gas_used = Column(BIGINT) 17 | avg_gas_used_percentage = Column(NUMERIC) 18 | avg_txn_cnt = Column(NUMERIC) 19 | total_cnt = Column(BIGINT) 20 | block_interval = Column(NUMERIC) 21 | -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/daily_bridge_transactions_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, DATE 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyBridgeTransactionsAggregates(HemeraModel): 8 | 9 | __tablename__ = "af_stats_na_daily_bridge_transactions" 10 | 11 | block_date = Column(DATE, primary_key=True) 12 | deposit_cnt = Column(BIGINT) 13 | withdraw_cnt = Column(BIGINT) 14 | -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/daily_tokens_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, DATE, INTEGER 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyTokensStats(HemeraModel): 8 | 9 | __tablename__ = "af_stats_na_daily_tokens" 10 | 11 | block_date = Column(DATE, primary_key=True) 12 | erc20_active_address_cnt = Column(INTEGER) 13 | erc20_total_transfer_cnt = Column(BIGINT) 14 | erc721_active_address_cnt = Column(INTEGER) 15 | erc721_total_transfer_cnt = Column(BIGINT) 16 | erc1155_active_address_cnt = Column(INTEGER) 17 | erc1155_total_transfer_cnt = Column(BIGINT) 18 | -------------------------------------------------------------------------------- /indexer/modules/custom/stats/models/daily_transactions_stats.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column 2 | from sqlalchemy.dialects.postgresql import BIGINT, DATE, NUMERIC 3 | 4 | from common.models import HemeraModel 5 | 6 | 7 | class DailyTransactionsStats(HemeraModel): 8 | 9 | __tablename__ = "af_stats_na_daily_transactions" 10 | 11 | block_date = Column(DATE, primary_key=True) 12 | cnt = Column(BIGINT) 13 | total_cnt = Column(BIGINT) 14 | txn_error_cnt = Column(BIGINT) 15 | avg_transaction_fee = Column(NUMERIC) 16 | avg_gas_price = Column(NUMERIC) 17 | max_gas_price = Column(NUMERIC) 18 | min_gas_price = Column(NUMERIC) 19 | avg_receipt_l1_fee = Column(NUMERIC) 20 | max_receipt_l1_fee = Column(NUMERIC) 21 | min_receipt_l1_fee = Column(NUMERIC) 22 | avg_receipt_l1_gas_price = Column(NUMERIC) 23 | max_receipt_l1_gas_price = Column(NUMERIC) 24 | min_receipt_l1_gas_price = Column(NUMERIC) 25 | -------------------------------------------------------------------------------- /indexer/modules/custom/total_supply/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/total_supply/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/total_supply/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/total_supply/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/total_supply/domain/erc20_total_supply.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from indexer.domain import Domain, FilterData 5 | 6 | 7 | @dataclass 8 | class Erc20TotalSupply(FilterData): 9 | token_address: str 10 | total_supply: int 11 | block_number: int 12 | block_timestamp: int 13 | 14 | 15 | @dataclass 16 | class Erc20CurrentTotalSupply(FilterData): 17 | token_address: str 18 | total_supply: int 19 | block_number: int 20 | block_timestamp: int 21 | -------------------------------------------------------------------------------- /indexer/modules/custom/total_supply/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/total_supply/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v2/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v2/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/domain/feature_uniswap_v2.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from indexer.domain import FilterData 4 | 5 | 6 | @dataclass 7 | class UniswapV2Pool(FilterData): 8 | factory_address: str 9 | pool_address: str 10 | token0_address: str 11 | token1_address: str 12 | length: int 13 | block_number: int 14 | block_timestamp: int 15 | 16 | 17 | @dataclass 18 | class UniswapV2SwapEvent(FilterData): 19 | sender: str 20 | amount0_in: int 21 | amount1_in: int 22 | amount0_out: int 23 | amount1_out: int 24 | log_index: int 25 | to_address: str 26 | pool_address: str 27 | block_number: int 28 | block_timestamp: int 29 | transaction_hash: str 30 | 31 | 32 | @dataclass 33 | class UniswapV2Erc20TotalSupply(FilterData): 34 | token_address: str 35 | total_supply: int 36 | block_number: int 37 | block_timestamp: int 38 | 39 | 40 | @dataclass 41 | class UniswapV2Erc20CurrentTotalSupply(FilterData): 42 | token_address: str 43 | total_supply: int 44 | block_number: int 45 | block_timestamp: int 46 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v2/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/models/af_uniswap_v2_swap_event.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import INTEGER, Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.uniswap_v2.domain.feature_uniswap_v2 import UniswapV2SwapEvent 6 | 7 | 8 | class AfUniswapV2SwapEvent(HemeraModel): 9 | __tablename__ = "af_uniswap_v2_swap_event" 10 | transaction_hash = Column(BYTEA, primary_key=True) 11 | log_index = Column(INTEGER, primary_key=True) 12 | 13 | pool_address = Column(BYTEA) 14 | sender = Column(BYTEA) 15 | to_address = Column(BYTEA) 16 | 17 | amount0_in = Column(NUMERIC) 18 | amount1_in = Column(NUMERIC) 19 | amount0_out = Column(NUMERIC) 20 | amount1_out = Column(NUMERIC) 21 | 22 | block_number = Column(BIGINT) 23 | block_timestamp = Column(TIMESTAMP) 24 | 25 | create_time = Column(TIMESTAMP, server_default=func.now()) 26 | update_time = Column(TIMESTAMP, server_default=func.now()) 27 | 28 | __table_args__ = (PrimaryKeyConstraint("transaction_hash", "log_index"),) 29 | 30 | @staticmethod 31 | def model_domain_mapping(): 32 | return [ 33 | { 34 | "domain": UniswapV2SwapEvent, 35 | "conflict_do_update": True, 36 | "update_strategy": None, 37 | "converter": general_converter, 38 | } 39 | ] 40 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/models/feature_uniswap_v2_pools.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy import Column, PrimaryKeyConstraint, func 2 | from sqlalchemy.dialects.postgresql import BIGINT, BYTEA, NUMERIC, TIMESTAMP 3 | 4 | from common.models import HemeraModel, general_converter 5 | from indexer.modules.custom.uniswap_v2.domain.feature_uniswap_v2 import UniswapV2Pool 6 | 7 | 8 | class UniswapV2Pools(HemeraModel): 9 | __tablename__ = "af_uniswap_v2_pools" 10 | factory_address = Column(BYTEA, primary_key=True) 11 | pool_address = Column(BYTEA, primary_key=True) 12 | 13 | token0_address = Column(BYTEA) 14 | token1_address = Column(BYTEA) 15 | 16 | length = Column(NUMERIC(100)) 17 | 18 | block_number = Column(BIGINT) 19 | block_timestamp = Column(TIMESTAMP) 20 | 21 | create_time = Column(TIMESTAMP, server_default=func.now()) 22 | update_time = Column(TIMESTAMP, server_default=func.now()) 23 | 24 | __table_args__ = (PrimaryKeyConstraint("factory_address", "pool_address"),) 25 | 26 | @staticmethod 27 | def model_domain_mapping(): 28 | return [ 29 | { 30 | "domain": UniswapV2Pool, 31 | "conflict_do_update": True, 32 | "update_strategy": None, 33 | "converter": general_converter, 34 | } 35 | ] 36 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v2/uniswapv2_abi.py: -------------------------------------------------------------------------------- 1 | from common.utils.abi_code_utils import Event 2 | 3 | PAIR_CREATED_EVENT = Event( 4 | { 5 | "anonymous": False, 6 | "inputs": [ 7 | {"indexed": True, "internalType": "address", "name": "token0", "type": "address"}, 8 | {"indexed": True, "internalType": "address", "name": "token1", "type": "address"}, 9 | {"indexed": False, "internalType": "address", "name": "pair", "type": "address"}, 10 | {"indexed": False, "internalType": "uint256", "name": "", "type": "uint256"}, 11 | ], 12 | "name": "PairCreated", 13 | "type": "event", 14 | } 15 | ) 16 | 17 | SWAP_EVENT = Event( 18 | { 19 | "anonymous": False, 20 | "inputs": [ 21 | {"indexed": True, "internalType": "address", "name": "sender", "type": "address"}, 22 | {"indexed": False, "internalType": "uint256", "name": "amount0In", "type": "uint256"}, 23 | {"indexed": False, "internalType": "uint256", "name": "amount1In", "type": "uint256"}, 24 | {"indexed": False, "internalType": "uint256", "name": "amount0Out", "type": "uint256"}, 25 | {"indexed": False, "internalType": "uint256", "name": "amount1Out", "type": "uint256"}, 26 | {"indexed": True, "internalType": "address", "name": "to", "type": "address"}, 27 | ], 28 | "name": "Swap", 29 | "type": "event", 30 | } 31 | ) 32 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v3/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/constants.py: -------------------------------------------------------------------------------- 1 | ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" 2 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/domains/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v3/domains/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/endpoints/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | from flask_restx.namespace import Namespace 4 | 5 | uniswap_v3_namespace = Namespace("Uniswap V3 Explorer", path="/", description="Uniswap V3 Feature Explorer API") 6 | -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v3/models/__init__.py -------------------------------------------------------------------------------- /indexer/modules/custom/uniswap_v3/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/custom/uniswap_v3/tests/__init__.py -------------------------------------------------------------------------------- /indexer/modules/user_ops/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/user_ops/__init__.py -------------------------------------------------------------------------------- /indexer/modules/user_ops/domain/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/user_ops/domain/__init__.py -------------------------------------------------------------------------------- /indexer/modules/user_ops/domain/user_operations.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | from typing import Optional 3 | 4 | from indexer.domain import FilterData 5 | 6 | 7 | @dataclass 8 | class UserOperationsResult(FilterData): 9 | user_op_hash: str 10 | sender: Optional[str] 11 | paymaster: Optional[str] 12 | nonce: Optional[float] 13 | status: Optional[bool] 14 | actual_gas_cost: Optional[float] 15 | actual_gas_used: Optional[int] 16 | init_code: Optional[str] 17 | call_data: Optional[str] 18 | call_gas_limit: Optional[int] 19 | verification_gas_limit: Optional[int] 20 | pre_verification_gas: Optional[int] 21 | max_fee_per_gas: Optional[int] 22 | max_priority_fee_per_gas: Optional[int] 23 | paymaster_and_data: Optional[str] 24 | signature: Optional[str] 25 | transactions_hash: Optional[str] 26 | transactions_index: Optional[int] 27 | block_number: Optional[int] 28 | block_timestamp: Optional[str] # Consider using a datetime type if appropriate 29 | bundler: Optional[str] 30 | start_log_index: Optional[int] 31 | end_log_index: Optional[int] 32 | -------------------------------------------------------------------------------- /indexer/modules/user_ops/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/modules/user_ops/models/__init__.py -------------------------------------------------------------------------------- /indexer/tests/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | LINEA_PUBLIC_NODE_RPC_URL = os.environ.get("LINEA_PUBLIC_NODE_RPC_URL", "https://rpc.linea.build") 4 | LINEA_PUBLIC_NODE_DEBUG_RPC_URL = os.environ.get("LINEA_PUBLIC_NODE_DEBUG_RPC_URL", "https://rpc.linea.build") 5 | 6 | ETHEREUM_PUBLIC_NODE_RPC_URL = os.environ.get("ETHEREUM_PUBLIC_NODE_RPC_URL", "https://ethereum-rpc.publicnode.com") 7 | ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL = os.environ.get( 8 | "ETHEREUM_PUBLIC_NODE_DEBUG_RPC_URL", "https://ethereum-rpc.publicnode.com" 9 | ) 10 | 11 | DODO_TESTNET_PUBLIC_NODE_RPC_URL = os.environ.get( 12 | "DODO_TESTNET_PUBLIC_NODE_RPC_URL", "https://dodochain-testnet.alt.technology" 13 | ) 14 | 15 | MORPHL2_PUBLIC_NODE_RPC_URL = os.environ.get("MORPHL2_PUBLIC_NODE_RPC_URL", "https://rpc.morphl2.io") 16 | 17 | MANTLE_PUBLIC_NODE_RPC_URL = os.environ.get("MANTLE_PUBLIC_NODE_RPC_URL", "https://rpc.mantle.xyz") 18 | MANTLE_PUBLIC_NODE_DEBUG_RPC_URL = os.environ.get("MANTLE_PUBLIC_NODE_DEBUG_RPC_URL", "https://rpc.mantle.xyz") 19 | 20 | ARBITRUM_PUBLIC_NODE_RPC_URL = os.environ.get("ARBITRUM_PUBLIC_NODE_RPC_URL", "https://arbitrum-one-rpc.publicnode.com") 21 | ARBITRUM_TESTNET_PUBLIC_NODE_RPC_URL = os.environ.get( 22 | "ARBITRUM_TESTNET_PUBLIC_NODE_RPC_URL", 23 | "https://arbitrum-sepolia.blockpi.network/v1/rpc/public", 24 | ) 25 | 26 | 27 | CYBER_PUBLIC_NODE_RPC_URL = os.environ.get("CYBER_PUBLIC_NODE_RPC_URL", "https://cyber-mainnet-archive.alt.technology") 28 | -------------------------------------------------------------------------------- /indexer/tests/address_index/test_address_index_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 5 | from indexer.modules.custom.address_index.domain import * 6 | from indexer.tests import ETHEREUM_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_address_index 13 | @pytest.mark.serial 14 | def test_export_address_index_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[ 26 | TokenAddressNftInventory, 27 | AddressTransaction, 28 | AddressTokenTransfer, 29 | AddressNftTransfer, 30 | AddressTokenHolder, 31 | ], 32 | ) 33 | 34 | job_scheduler.run_jobs( 35 | start_block=20273057, 36 | end_block=20273058, 37 | ) 38 | 39 | job_scheduler.clear_data_buff() 40 | -------------------------------------------------------------------------------- /indexer/tests/bridge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/bridge/__init__.py -------------------------------------------------------------------------------- /indexer/tests/bridge/arbitrum/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/7/15 16:18 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/tests/bridge/arbitrum/arbitrum_bridge_parser_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/7/10 14:58 4 | # @Author will 5 | # @File arbitrum_bridge_parser_test.py 6 | # @Brief 7 | 8 | l2Rpc = "https://arbitrum-one-rpc.publicnode.com" 9 | l1Rpc = "https://ethereum-rpc.publicnode.com" 10 | contract_set = { 11 | "0x1c479675ad559dc151f6ec7ed3fbf8cee79582b6", 12 | "0x5ef0d09d1e6204141b4d37530808ed19f60fba35", 13 | "0x4dbd4fc535ac27206064b68ffcf827b0a60bab3f", 14 | "0x8315177ab297ba92a06054ce80a67ed4dbd7ed3a", 15 | "0x0000000000000000000000000000000000000064", 16 | } 17 | -------------------------------------------------------------------------------- /indexer/tests/bridge/arbitrum/jobs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Time 2024/7/15 16:18 4 | # @Author will 5 | # @File __init__.py.py 6 | # @Brief 7 | -------------------------------------------------------------------------------- /indexer/tests/bridge/bedrock/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/bridge/bedrock/__init__.py -------------------------------------------------------------------------------- /indexer/tests/bridge/bedrock/jobs/test_fetch_op_bedrock_bridge_on_data_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 5 | from indexer.modules.bridge.domain.op_bedrock import OpL1ToL2DepositedTransaction 6 | from indexer.tests import ETHEREUM_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_bridge 13 | @pytest.mark.indexer_bridge_optimism 14 | def test_fetch_op_bedrock_bridge_on_data(): 15 | 16 | job_scheduler = JobScheduler( 17 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), 18 | batch_web3_debug_provider=ThreadLocalProxy( 19 | lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True) 20 | ), 21 | item_exporters=[ConsoleItemExporter()], 22 | batch_size=100, 23 | debug_batch_size=1, 24 | max_workers=5, 25 | config={"optimism_portal_proxy": "0x9168765ee952de7c6f8fc6fad5ec209b960b7622"}, 26 | required_output_types=[OpL1ToL2DepositedTransaction], 27 | ) 28 | 29 | job_scheduler.run_jobs( 30 | start_block=20273057, 31 | end_block=20273060, 32 | ) 33 | 34 | data_buff = job_scheduler.get_data_buff() 35 | 36 | job_scheduler.clear_data_buff() 37 | -------------------------------------------------------------------------------- /indexer/tests/bridge/bedrock/parser/function_parser/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/bridge/bedrock/parser/function_parser/__init__.py -------------------------------------------------------------------------------- /indexer/tests/bridge/bedrock/parser/function_parser/test_finalize_bridge_eth.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.modules.bridge.bedrock.parser.function_parser import BedRockFunctionCallType 4 | 5 | 6 | @pytest.mark.indexer 7 | @pytest.mark.indexer_bridge 8 | def test_bedrock_finalize_bridge_eth_decoder(): 9 | from indexer.modules.bridge.bedrock.parser.function_parser.finalize_bridge_eth import decode_function 10 | 11 | bridge_info = decode_function( 12 | bytearray.fromhex( 13 | "1635f5fd000000000000000000000000550bf1c892b6a79118a1b20b65c923e8d1e6f715000000000000000000000000550bf1c892b6a79118a1b20b65c923e8d1e6f7150000000000000000000000000000000000000000000000000005789f30f4400000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000" 14 | ) 15 | ) 16 | 17 | assert bridge_info.bridge_from_address == "0x550bf1c892b6a79118a1b20b65c923e8d1e6f715" 18 | assert bridge_info.bridge_to_address == "0x550bf1c892b6a79118a1b20b65c923e8d1e6f715" 19 | assert bridge_info.amount == 1540000000000000 20 | assert bridge_info.remote_function_call_type == BedRockFunctionCallType.BRIDGE_ETH.value 21 | -------------------------------------------------------------------------------- /indexer/tests/day_mining/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/day_mining/__init__.py -------------------------------------------------------------------------------- /indexer/tests/day_mining/test_export_day_mining.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 5 | from indexer.modules.custom.all_features_value_record import AllFeatureValueRecordTraitsActiveness 6 | from indexer.tests import CYBER_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_jobs 13 | @pytest.mark.indexer_jobs_day_mining 14 | def test_export_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(CYBER_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(CYBER_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=10, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[AllFeatureValueRecordTraitsActiveness], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=1, 30 | end_block=100, 31 | ) 32 | assert len(job_scheduler.get_data_buff().get(AllFeatureValueRecordTraitsActiveness.type())) > 0 33 | job_scheduler.clear_data_buff() 34 | -------------------------------------------------------------------------------- /indexer/tests/jobs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/jobs/__init__.py -------------------------------------------------------------------------------- /indexer/tests/jobs/test_export_blocks_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.domain.block import Block 5 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 6 | from indexer.tests import ETHEREUM_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_exporter 13 | @pytest.mark.serial 14 | def test_export_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[Block], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=20273057, 30 | end_block=20273058, 31 | ) 32 | 33 | job_scheduler.clear_data_buff() 34 | -------------------------------------------------------------------------------- /indexer/tests/jobs/test_export_coin_balances_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.domain.coin_balance import CoinBalance 5 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 6 | from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_exporter 13 | @pytest.mark.serial 14 | def test_export_coin_balance_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[CoinBalance], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=7193582, 30 | end_block=7193583, 31 | ) 32 | 33 | data_buff = job_scheduler.get_data_buff() 34 | job_scheduler.clear_data_buff() 35 | -------------------------------------------------------------------------------- /indexer/tests/jobs/test_export_contracts_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.domain.contract import Contract 5 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 6 | from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_exporter 13 | @pytest.mark.serial 14 | def test_export_coin_balance_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[Contract], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=2786950, 30 | end_block=2786951, 31 | ) 32 | 33 | data_buff = job_scheduler.get_data_buff() 34 | job_scheduler.clear_data_buff() 35 | -------------------------------------------------------------------------------- /indexer/tests/jobs/test_export_trace_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.domain.contract_internal_transaction import ContractInternalTransaction 5 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 6 | from indexer.tests import LINEA_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_exporter 13 | @pytest.mark.serial 14 | def test_export_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(LINEA_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[ContractInternalTransaction], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=7193582, 30 | end_block=7193583, 31 | ) 32 | 33 | data_buff = job_scheduler.get_data_buff() 34 | assert len(data_buff[ContractInternalTransaction.type()]) == 24 35 | job_scheduler.clear_data_buff() 36 | -------------------------------------------------------------------------------- /indexer/tests/jobs/test_export_transactions_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.domain.log import Log 5 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 6 | from indexer.tests import ETHEREUM_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_exporter 13 | @pytest.mark.serial 14 | def test_export_transaction_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(ETHEREUM_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[Log], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=20273057, 30 | end_block=20273058, 31 | ) 32 | 33 | job_scheduler.clear_data_buff() 34 | -------------------------------------------------------------------------------- /indexer/tests/user_ops/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/user_ops/__init__.py -------------------------------------------------------------------------------- /indexer/tests/user_ops/test_export_user_ops_job.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from indexer.controller.scheduler.job_scheduler import JobScheduler 4 | from indexer.exporters.console_item_exporter import ConsoleItemExporter 5 | from indexer.modules.user_ops.domain.user_operations import UserOperationsResult 6 | from indexer.tests import CYBER_PUBLIC_NODE_RPC_URL 7 | from indexer.utils.provider import get_provider_from_uri 8 | from indexer.utils.thread_local_proxy import ThreadLocalProxy 9 | 10 | 11 | @pytest.mark.indexer 12 | @pytest.mark.indexer_jobs 13 | @pytest.mark.indexer_jobs_user_ops 14 | def test_export_job(): 15 | job_scheduler = JobScheduler( 16 | batch_web3_provider=ThreadLocalProxy(lambda: get_provider_from_uri(CYBER_PUBLIC_NODE_RPC_URL, batch=True)), 17 | batch_web3_debug_provider=ThreadLocalProxy( 18 | lambda: get_provider_from_uri(CYBER_PUBLIC_NODE_RPC_URL, batch=True) 19 | ), 20 | item_exporters=[ConsoleItemExporter()], 21 | batch_size=100, 22 | debug_batch_size=1, 23 | max_workers=5, 24 | config={}, 25 | required_output_types=[UserOperationsResult], 26 | ) 27 | 28 | job_scheduler.run_jobs( 29 | start_block=2423182, 30 | end_block=2424182, 31 | ) 32 | assert len(job_scheduler.get_data_buff().get(UserOperationsResult.type())) > 0 33 | job_scheduler.clear_data_buff() 34 | -------------------------------------------------------------------------------- /indexer/tests/utils.py: -------------------------------------------------------------------------------- 1 | # https://etherscan.io/tx/0xea2a5ee319f16233435360907440a96e22349ff191bc3ba0eff056cfb8fbbcf3#eventlog 2 | -------------------------------------------------------------------------------- /indexer/tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/tests/utils/__init__.py -------------------------------------------------------------------------------- /indexer/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/indexer/utils/__init__.py -------------------------------------------------------------------------------- /indexer/utils/logging_utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import signal 4 | import sys 5 | from logging.handlers import TimedRotatingFileHandler 6 | 7 | 8 | def logging_basic_config(log_level=logging.INFO, log_file=None): 9 | format = "%(asctime)s - %(name)s [%(levelname)s] - %(message)s" 10 | if log_file is not None: 11 | handler = TimedRotatingFileHandler(filename=log_file, when="h", interval=2, backupCount=12) 12 | logging.basicConfig(level=log_level, format=format, handlers=[handler]) 13 | else: 14 | logging.basicConfig(level=log_level, format=format) 15 | 16 | 17 | def configure_signals(): 18 | def sigterm_handler(_signo, _stack_frame): 19 | # Raises SystemExit(0): 20 | sys.exit(0) 21 | 22 | signal.signal(signal.SIGTERM, sigterm_handler) 23 | 24 | 25 | def configure_logging(log_level="INFO", log_file=None): 26 | if log_file: 27 | log_dir = os.path.dirname(log_file) 28 | 29 | if not os.path.exists(log_dir): 30 | os.makedirs(log_dir) 31 | 32 | for handler in logging.root.handlers[:]: 33 | logging.root.removeHandler(handler) 34 | 35 | level = logging.getLevelName(log_level) 36 | if level is str: 37 | raise ValueError("Unknown log level: %r" % log_level) 38 | logging_basic_config(log_level=level, log_file=log_file) 39 | -------------------------------------------------------------------------------- /indexer/utils/multicall_hemera/__init__.py: -------------------------------------------------------------------------------- 1 | from indexer.utils.multicall_hemera.call import Call 2 | from indexer.utils.multicall_hemera.multi_call import Multicall 3 | 4 | """ 5 | This package provides API for multicall3 smart contract 6 | """ 7 | -------------------------------------------------------------------------------- /indexer/utils/thread_local_proxy.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | 4 | class ThreadLocalProxy: 5 | def __init__(self, delegate_factory): 6 | self._thread_local = threading.local() 7 | self._delegate_factory = delegate_factory 8 | 9 | def __getattr__(self, name): 10 | return getattr(self._get_thread_local_delegate(), name) 11 | 12 | def _get_thread_local_delegate(self): 13 | if getattr(self._thread_local, "_delegate", None) is None: 14 | self._thread_local._delegate = self._delegate_factory() 15 | return self._thread_local._delegate 16 | -------------------------------------------------------------------------------- /licenses/LICENSE-ethereum-etl.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Evgeny Medvedev, evge.medvedev@gmail.com, https://twitter.com/EvgeMedvedev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /licenses/LICENSE-multicall.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 banteg 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /migrations/manual_versions/20240708_tokens_table_add_column_block_number.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 5e4608933f64 -> 8a915490914a 4 | 5 | ALTER TABLE tokens ADD COLUMN block_number BIGINT; 6 | 7 | UPDATE alembic_version SET version_num='8a915490914a' WHERE alembic_version.version_num = '5e4608933f64'; 8 | 9 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240726_modify_sync_record_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 9f2cf385645f -> 0b922153e040 4 | 5 | DROP TABLE sync_record; 6 | 7 | CREATE TABLE IF NOT EXISTS sync_record ( 8 | mission_sign VARCHAR NOT NULL, 9 | last_block_number BIGINT, 10 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 11 | PRIMARY KEY (mission_sign) 12 | ); 13 | 14 | UPDATE alembic_version SET version_num='0b922153e040' WHERE alembic_version.version_num = '9f2cf385645f'; 15 | 16 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240731_add_user_ops_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 3d5ce8939570 -> 9a1e927f02bb 4 | 5 | CREATE TABLE IF NOT EXISTS user_operations_results ( 6 | user_op_hash BYTEA NOT NULL, 7 | sender VARCHAR(42), 8 | paymaster VARCHAR(42), 9 | nonce NUMERIC, 10 | status BOOLEAN, 11 | actual_gas_cost NUMERIC, 12 | actual_gas_used NUMERIC, 13 | init_code BYTEA, 14 | call_data BYTEA, 15 | call_gas_limit NUMERIC, 16 | verification_gas_limit NUMERIC, 17 | pre_verification_gas NUMERIC, 18 | max_fee_per_gas NUMERIC, 19 | max_priority_fee_per_gas NUMERIC, 20 | paymaster_and_data BYTEA, 21 | signature BYTEA, 22 | transactions_hash BYTEA, 23 | transactions_index INTEGER, 24 | block_number BIGINT, 25 | block_timestamp TIMESTAMP WITHOUT TIME ZONE, 26 | bundler VARCHAR(42), 27 | start_log_index INTEGER, 28 | end_log_index INTEGER, 29 | PRIMARY KEY (user_op_hash) 30 | ); 31 | 32 | CREATE INDEX transactions_hash_index ON user_operations_results (transactions_hash); 33 | 34 | UPDATE alembic_version SET version_num='9a1e927f02bb' WHERE alembic_version.version_num = '3d5ce8939570'; 35 | 36 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240802_add_exception_recorder_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 9a1e927f02bb -> 040e5251f45d 4 | 5 | CREATE TABLE IF NOT EXISTS exception_records ( 6 | id BIGSERIAL NOT NULL, 7 | block_number BIGINT, 8 | dataclass VARCHAR, 9 | level VARCHAR, 10 | message_type VARCHAR, 11 | message VARCHAR, 12 | exception_env JSONB, 13 | record_time TIMESTAMP WITHOUT TIME ZONE, 14 | PRIMARY KEY (id) 15 | ); 16 | 17 | UPDATE alembic_version SET version_num='040e5251f45d' WHERE alembic_version.version_num = '9a1e927f02bb'; 18 | 19 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240802_add_uniswap_v2_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade e3a3e2114b9c -> aa99dd347ef1 4 | 5 | CREATE TABLE IF NOT EXISTS feature_uniswap_v2_pools ( 6 | factory_address BYTEA NOT NULL, 7 | pool_address BYTEA NOT NULL, 8 | token0_address BYTEA, 9 | token1_address BYTEA, 10 | length NUMERIC(100), 11 | called_block_number BIGINT, 12 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 13 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 14 | PRIMARY KEY (factory_address, pool_address) 15 | ); 16 | 17 | ALTER TABLE feature_uniswap_v3_pools ADD COLUMN called_block_number BIGINT; 18 | 19 | ALTER TABLE feature_uniswap_v3_pools DROP COLUMN mint_block_number; 20 | 21 | ALTER TABLE feature_uniswap_v3_tokens ADD COLUMN called_block_number BIGINT; 22 | 23 | ALTER TABLE feature_uniswap_v3_tokens DROP COLUMN mint_block_number; 24 | 25 | UPDATE alembic_version SET version_num='aa99dd347ef1' WHERE alembic_version.version_num = 'e3a3e2114b9c'; 26 | 27 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240805_add_column_to_contracts_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade aa99dd347ef1 -> 832fa52da346 4 | 5 | CREATE EXTENSION IF NOT EXISTS pgcrypto;; 6 | 7 | ALTER TABLE contracts ADD COLUMN deployed_code_hash VARCHAR GENERATED ALWAYS AS (encode(digest('0x'||encode(deployed_code, 'hex'), 'sha256'), 'hex')) STORED; 8 | 9 | ALTER TABLE contracts ADD COLUMN transaction_from_address BYTEA; 10 | 11 | UPDATE alembic_version SET version_num='832fa52da346' WHERE alembic_version.version_num = 'aa99dd347ef1'; 12 | 13 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240806_add_current_traits_activeness.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 832fa52da346 -> b86e241b5e18 4 | 5 | CREATE TABLE IF NOT EXISTS current_traits_activeness ( 6 | block_number BIGINT NOT NULL, 7 | address BYTEA NOT NULL, 8 | value JSONB, 9 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 10 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 11 | PRIMARY KEY (address) 12 | ); 13 | 14 | UPDATE alembic_version SET version_num='b86e241b5e18' WHERE alembic_version.version_num = '832fa52da346'; 15 | 16 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240808_add_blue_chip_holding.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade b86e241b5e18 -> 1b1c6a8b6c7b 4 | 5 | CREATE TABLE IF NOT EXISTS feature_blue_chip_holders ( 6 | wallet_address BYTEA NOT NULL, 7 | hold_detail JSONB, 8 | current_count BIGINT, 9 | called_block_number BIGINT, 10 | called_block_timestamp TIMESTAMP WITHOUT TIME ZONE, 11 | create_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 12 | update_time TIMESTAMP WITHOUT TIME ZONE DEFAULT now(), 13 | PRIMARY KEY (wallet_address) 14 | ); 15 | 16 | UPDATE alembic_version SET version_num='1b1c6a8b6c7b' WHERE alembic_version.version_num = 'b86e241b5e18'; 17 | 18 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20240827_add_token_price_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade bf51d23c852f -> 2359a28d63cb 4 | CREATE TABLE IF NOT EXISTS IF NOT EXISTS token_hourly_prices( 5 | symbol VARCHAR NOT NULL, 6 | timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, 7 | price NUMERIC, 8 | PRIMARY KEY (symbol, timestamp) 9 | ); 10 | 11 | CREATE TABLE IF NOT EXISTS IF NOT EXISTS token_prices( 12 | symbol VARCHAR NOT NULL, 13 | timestamp TIMESTAMP WITHOUT TIME ZONE NOT NULL, 14 | price NUMERIC, 15 | PRIMARY KEY (symbol, timestamp) 16 | ); 17 | 18 | UPDATE alembic_version SET version_num='2359a28d63cb' WHERE alembic_version.version_num = 'bf51d23c852f'; 19 | 20 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20241017_earlier_table_change.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 67015d9fa59b -> bc23aa19668e 4 | 5 | ALTER TABLE af_ens_node_current ADD COLUMN block_number BIGINT; 6 | 7 | UPDATE alembic_version SET version_num='bc23aa19668e' WHERE alembic_version.version_num = '67015d9fa59b'; 8 | 9 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20241121_add_failure_records_table.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade 3bd2e3099bae -> f846e3abeb18 4 | 5 | CREATE TABLE IF NOT EXISTS failure_records ( 6 | record_id BIGSERIAL NOT NULL, 7 | mission_sign VARCHAR, 8 | output_types VARCHAR, 9 | start_block_number BIGINT, 10 | end_block_number BIGINT, 11 | exception_stage VARCHAR, 12 | exception JSON, 13 | crash_time TIMESTAMP WITHOUT TIME ZONE, 14 | PRIMARY KEY (record_id) 15 | ); 16 | 17 | UPDATE alembic_version SET version_num='f846e3abeb18' WHERE alembic_version.version_num = '3bd2e3099bae'; 18 | 19 | COMMIT; -------------------------------------------------------------------------------- /migrations/manual_versions/20241128_update_table_for_0.6.0.sql: -------------------------------------------------------------------------------- 1 | BEGIN; 2 | 3 | -- Running upgrade f846e3abeb18 -> 3c7ea7b95dc5 4 | 5 | ALTER TABLE logs DROP CONSTRAINT logs_pkey; 6 | 7 | CREATE INDEX logs_pkey ON logs (transaction_hash, block_hash, log_index); 8 | 9 | ALTER TABLE af_holding_balance_uniswap_v3_period DROP CONSTRAINT af_holding_balance_uniswap_v3_period_pkey; 10 | 11 | ALTER TABLE af_holding_balance_uniswap_v3_period RENAME position_token_address TO pool_address; 12 | 13 | CREATE INDEX af_holding_balance_uniswap_v3_period_pkey ON af_holding_balance_uniswap_v3_period (period_date, protocol_id, pool_address, token_id); 14 | 15 | UPDATE alembic_version SET version_num='f846e3abeb18' WHERE alembic_version.version_num = '3c7ea7b95dc5'; 16 | 17 | COMMIT; -------------------------------------------------------------------------------- /migrations/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision | comma,n} 5 | Create Date: ${create_date} 6 | 7 | """ 8 | from typing import Sequence, Union 9 | 10 | from alembic import op 11 | import sqlalchemy as sa 12 | ${imports if imports else ""} 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = ${repr(up_revision)} 16 | down_revision: Union[str, None] = ${repr(down_revision)} 17 | branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} 18 | depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} 19 | 20 | 21 | def upgrade() -> None: 22 | ${upgrades if upgrades else "pass"} 23 | 24 | 25 | def downgrade() -> None: 26 | ${downgrades if downgrades else "pass"} 27 | -------------------------------------------------------------------------------- /migrations/versions/20240708_tokens_table_add_column_block_number.py: -------------------------------------------------------------------------------- 1 | """tokens table add column 'block_number' 2 | 3 | Revision ID: 8a915490914a 4 | Revises: 5e4608933f64 5 | Create Date: 2024-07-08 21:57:41.134980 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | 14 | # revision identifiers, used by Alembic. 15 | revision: str = "8a915490914a" 16 | down_revision: Union[str, None] = "5e4608933f64" 17 | branch_labels: Union[str, Sequence[str], None] = None 18 | depends_on: Union[str, Sequence[str], None] = None 19 | 20 | 21 | def upgrade() -> None: 22 | # ### commands auto generated by Alembic - please adjust! ### 23 | op.add_column("tokens", sa.Column("block_number", sa.BIGINT(), nullable=True)) 24 | # ### end Alembic commands ### 25 | 26 | 27 | def downgrade() -> None: 28 | # ### commands auto generated by Alembic - please adjust! ### 29 | op.drop_column("tokens", "block_number") 30 | # ### end Alembic commands ### 31 | -------------------------------------------------------------------------------- /migrations/versions/20240806_add_current_traits_activeness.py: -------------------------------------------------------------------------------- 1 | """add current_traits_activeness 2 | 3 | Revision ID: b86e241b5e18 4 | Revises: 832fa52da346 5 | Create Date: 2024-08-06 14:03:13.234591 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | from sqlalchemy.dialects import postgresql 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "b86e241b5e18" 17 | down_revision: Union[str, None] = "832fa52da346" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.create_table( 25 | "current_traits_activeness", 26 | sa.Column("block_number", sa.BIGINT(), nullable=False), 27 | sa.Column("address", postgresql.BYTEA(), nullable=False), 28 | sa.Column("value", postgresql.JSONB(astext_type=sa.Text()), nullable=True), 29 | sa.Column("create_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), 30 | sa.Column("update_time", postgresql.TIMESTAMP(), server_default=sa.text("now()"), nullable=True), 31 | sa.PrimaryKeyConstraint("address"), 32 | ) 33 | # ### end Alembic commands ### 34 | 35 | 36 | def downgrade() -> None: 37 | # ### commands auto generated by Alembic - please adjust! ### 38 | op.drop_table("current_traits_activeness") 39 | # ### end Alembic commands ### 40 | -------------------------------------------------------------------------------- /migrations/versions/20241017_earlier_table_change.py: -------------------------------------------------------------------------------- 1 | """earlier table change 2 | 3 | Revision ID: bc23aa19668e 4 | Revises: 67015d9fa59b 5 | Create Date: 2024-10-17 17:11:13.452322 6 | 7 | """ 8 | 9 | from typing import Sequence, Union 10 | 11 | import sqlalchemy as sa 12 | from alembic import op 13 | from sqlalchemy.dialects import postgresql 14 | 15 | # revision identifiers, used by Alembic. 16 | revision: str = "bc23aa19668e" 17 | down_revision: Union[str, None] = "67015d9fa59b" 18 | branch_labels: Union[str, Sequence[str], None] = None 19 | depends_on: Union[str, Sequence[str], None] = None 20 | 21 | 22 | def upgrade() -> None: 23 | # ### commands auto generated by Alembic - please adjust! ### 24 | op.add_column("af_ens_node_current", sa.Column("block_number", sa.BIGINT(), nullable=True)) 25 | # ### end Alembic commands ### 26 | 27 | 28 | def downgrade() -> None: 29 | # ### commands auto generated by Alembic - please adjust! ### 30 | op.drop_column("af_ens_node_current", "block_number") 31 | # ### end Alembic commands ### 32 | -------------------------------------------------------------------------------- /resource/hemera.ini.example: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location = migrations 3 | prepend_sys_path = . 4 | version_path_separator = os # Use os.pathsep. Default configuration used for new projects. 5 | sqlalchemy.url = postgresql+psycopg2://postgres:admin@localhost:5432/bt1 6 | 7 | [post_write_hooks] 8 | 9 | [loggers] 10 | keys = root,sqlalchemy,alembic 11 | 12 | [handlers] 13 | keys = console 14 | 15 | [formatters] 16 | keys = generic 17 | 18 | [logger_root] 19 | level = WARN 20 | handlers = console 21 | qualname = 22 | 23 | [logger_sqlalchemy] 24 | level = WARN 25 | handlers = 26 | qualname = sqlalchemy.engine 27 | 28 | [logger_alembic] 29 | level = INFO 30 | handlers = 31 | qualname = alembic 32 | 33 | [handler_console] 34 | class = StreamHandler 35 | args = (sys.stderr,) 36 | level = NOTSET 37 | formatter = generic 38 | 39 | [formatter_generic] 40 | format = %(levelname)-5.5s [%(name)s] %(message)s 41 | datefmt = %H:%M:%S 42 | 43 | -------------------------------------------------------------------------------- /scheduler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/scheduler/__init__.py -------------------------------------------------------------------------------- /scheduler/scheduler.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HemeraProtocol/hemera-indexer/bf522c5ceb9029d61930b31b7ef2daccbf59a009/scheduler/scheduler.py -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup() 4 | --------------------------------------------------------------------------------