diff --git a/etherscan/__init__.py b/etherscan/__init__.py index 7b527d3..8436041 100644 --- a/etherscan/__init__.py +++ b/etherscan/__init__.py @@ -4,6 +4,7 @@ from .modules.contracts import Contracts as contracts from .modules.gastracker import GasTracker as gastracker from .modules.pro import Pro as pro +from .modules.logs import Logs as logs from .modules.proxy import Proxy as proxy from .modules.stats import Stats as stats from .modules.tokens import Tokens as tokens diff --git a/etherscan/configs/MAIN-stable.json b/etherscan/configs/MAIN-stable.json index 272ed0b..7241f80 100644 --- a/etherscan/configs/MAIN-stable.json +++ b/etherscan/configs/MAIN-stable.json @@ -1,4 +1,15 @@ { + "get_logs": { + "module": "logs", + "kwargs": { + "from_block": 4993830, + "to_block": 4993832, + "address": "0xe561479bebee0e606c19bb1973fc4761613e3c42", + "topic_0": "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "topic_0_1_opr": "and", + "topic_1": "0x000000000000000000000000730e2065b9daee84c3003c05bf6d2b3a08e55667" + } + }, "get_proxy_block_number": { "module": "proxy", "kwargs": {} @@ -463,4 +474,4 @@ "sort": "asc" } } -} \ No newline at end of file +} diff --git a/etherscan/enums/actions_enum.py b/etherscan/enums/actions_enum.py index 20c81cb..c732e4f 100644 --- a/etherscan/enums/actions_enum.py +++ b/etherscan/enums/actions_enum.py @@ -48,6 +48,7 @@ class ActionsEnum: GET_BLOCK_COUNTDOWN: str = "getblockcountdown" GET_BLOCK_NUMBER_BY_TIME: str = "getblocknobytime" GET_BLOCK_REWARD: str = "getblockreward" + GET_LOGS: str = "getLogs" GET_MINED_BLOCKS: str = "getminedblocks" GET_SOURCE_CODE: str = "getsourcecode" GET_STATUS: str = "getstatus" diff --git a/etherscan/enums/fields_enum.py b/etherscan/enums/fields_enum.py index b0bcc4d..6795248 100644 --- a/etherscan/enums/fields_enum.py +++ b/etherscan/enums/fields_enum.py @@ -8,6 +8,7 @@ class FieldsEnum: API_KEY: str = "&apikey=" BLOCK_TYPE: str = "&blocktype=" BLOCKNO: str = "&blockno=" + BLOCKS: str = "blocks" # BOOLEAN: str = "&boolean=" CLIENT_TYPE: str = "&clienttype=" CLOSEST: str = "&closest=" @@ -15,6 +16,8 @@ class FieldsEnum: DATA: str = "&data=" END_BLOCK: str = "&endblock=" END_DATE: str = "&enddate=" + FROM_BLOCK: str = "&fromBlock=" + FROM: str = "&from=" GAS_PRICE: str = "&gasPrice=" GAS: str = "&gas=" HEX: str = "&hex=" @@ -23,13 +26,24 @@ class FieldsEnum: OFFSET: str = "&offset=" PAGE: str = "&page=" POSITION: str = "&position=" - PREFIX: str = "https://api-{}.etherscan.io/api?" + PREFIX: str = "https://api.etherscan.io/api?" SORT: str = "&sort=" START_BLOCK: str = "&startblock=" START_DATE: str = "&startdate=" SYNC_MODE: str = "&syncmode=" TAG: str = "&tag=" TIMESTAMP: str = "×tamp=" + TO_BLOCK: str = "&toBlock=" TO: str = "&to=" + TOPIC_0_1_OPR: str = "&topic0_1_opr=" + TOPIC_0_2_OPR: str = "&topic0_2_opr=" + TOPIC_0_3_OPR: str = "&topic0_3_opr=" + TOPIC_0: str = "&topic0=" + TOPIC_1_2_OPR: str = "&topic1_2_opr=" + TOPIC_1_3_OPR: str = "&topic1_3_opr=" + TOPIC_1: str = "&topic1=" + TOPIC_2_3_OPR: str = "&topic2_3_opr=" + TOPIC_2: str = "&topic2=" + TOPIC_3: str = "&topic3=" TXHASH: str = "&txhash=" VALUE: str = "&value=" diff --git a/etherscan/enums/modules_enum.py b/etherscan/enums/modules_enum.py index e02e11c..883be4a 100644 --- a/etherscan/enums/modules_enum.py +++ b/etherscan/enums/modules_enum.py @@ -8,6 +8,7 @@ class ModulesEnum: CONTRACT: str = "contract" GASTRACKER: str = "gastracker" PROXY: str = "proxy" + LOGS: str = "logs" STATS: str = "stats" TOKEN: str = "token" TRANSACTION: str = "transaction" diff --git a/etherscan/modules/logs.py b/etherscan/modules/logs.py new file mode 100644 index 0000000..623d2b5 --- /dev/null +++ b/etherscan/modules/logs.py @@ -0,0 +1,118 @@ +from etherscan.enums.actions_enum import ActionsEnum as actions +from etherscan.enums.fields_enum import FieldsEnum as fields +from etherscan.enums.modules_enum import ModulesEnum as modules + + +class Logs: + @staticmethod + def get_logs( + from_block: int, + to_block: int, + address: str, + topic_0: str = "", + topic_1: str = "", + topic_2: str = "", + topic_3: str = "", + topic_0_1_opr: str = "", + topic_1_2_opr: str = "", + topic_2_3_opr: str = "", + topic_0_2_opr: str = "", + topic_0_3_opr: str = "", + topic_1_3_opr: str = "", + ): + """This is an alternative to the native eth_getLogs. An address and/or + topic_x parameters are required. When multiple topic_x parameters are + used, the topic_x_y_opr ("and"/"or" operator) is also required. + **NOTE: Only the first 1000 results are returned.** + Args: + from_block (int): Start block of the query. + to_block (int): End block of the query. + address (str): Address of the logs. + topic_0 (str, optional): Topic 0 in the logs. Defaults to "". + topic_1 (str, optional): Topic 1 in the logs. Defaults to "". + topic_2 (str, optional): Topic 2 in the logs. Defaults to "". + topic_3 (str, optional): Topic 3 in the logs. Defaults to "". + topic_0_1_opr (str, optional): Logical operator between topic 0 and 1. Defaults to "". + topic_1_2_opr (str, optional): Logical operator between topic 1 and 2. Defaults to "". + topic_2_3_opr (str, optional): Logical operator between topic 2 and 3. Defaults to "". + topic_0_2_opr (str, optional): Logical operator between topic 0 and 2. Defaults to "". + topic_0_3_opr (str, optional): Logical operator between topic 0 and 3. Defaults to "". + topic_1_3_opr (str, optional): Logical operator between topic 1 and 3. Defaults to "". + Returns: + dict: The event logs in a dictionary, including topics and data fields. + Example:: + from etherscan import Etherscan + async with Etherscan(YOUR_API_KEY) as client: + print( + await client.get_logs( + from_block=4993830, + to_block=4993832, + address="0xe561479bebee0e606c19bb1973fc4761613e3c42", + topic_0="0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + topic_0_1_opr="and", + topic_1="0x000000000000000000000000730e2065b9daee84c3003c05bf6d2b3a08e55667" + ) + ) + Results:: + [ + { + "address": "0xe561479bebee0e606c19bb1973fc4761613e3c42", + "topics": [ + "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", + "0x000000000000000000000000730e2065b9daee84c3003c05bf6d2b3a08e55667", + "0x000000000000000000000000d7d19938eae260d7f0e0a4c36e665ff4cf4b7acc" + ], + "data": "0x000000000000000000000000000000000000000000000000076cd96f53f24b0a", + "blockNumber": "0x4c3326", + "timeStamp": "0x602e9ef1", + "gasPrice": "0x2540be400", + "gasUsed": "0x1b0f2", + "logIndex": "0xf7", + "transactionHash": "0x73844fcfc6beab2e973a897c9573f4d79811b12213ce263045a203e0d3cea90e", + "transactionIndex": "0xb9" + } + ] + """ + return ( + f"{fields.MODULE}" + f"{modules.LOGS}" + f"{fields.ACTION}" + f"{actions.GET_LOGS}" + f"{fields.FROM_BLOCK}" + f"{from_block}" + f"{fields.TO_BLOCK}" + f"{to_block}" + f"{fields.ADDRESS}" + f"{address}" + # topic 0 + f"{fields.TOPIC_0*bool(topic_0)}" + f"{topic_0}" + # + # Everything below is optional. If not provided by user, then + # they remain empty and do not affect the tail of the url. + # + # topic 0_x operators + f"{fields.TOPIC_0_1_OPR*bool(topic_0_1_opr)}" + f"{topic_0_1_opr}" + f"{fields.TOPIC_0_2_OPR*bool(topic_0_2_opr)}" + f"{topic_0_2_opr}" + f"{fields.TOPIC_0_3_OPR*bool(topic_0_3_opr)}" + f"{topic_0_3_opr}" + # topic 1 + f"{fields.TOPIC_1*bool(topic_1)}" + f"{topic_1}" + # topic 1_x operators + f"{fields.TOPIC_1_2_OPR*bool(topic_1_2_opr)}" + f"{topic_1_2_opr}" + f"{fields.TOPIC_1_3_OPR*bool(topic_1_3_opr)}" + f"{topic_1_3_opr}" + # topic 2 + f"{fields.TOPIC_2*bool(topic_2)}" + f"{topic_2}" + # topic 2_x operators + f"{fields.TOPIC_2_3_OPR*bool(topic_2_3_opr)}" + f"{topic_2_3_opr}" + # topic 3 + f"{fields.TOPIC_3*bool(topic_3)}" + f"{topic_3}" + )