',tox)
+
+### run tests wit tox
+tox: .tox
+
+tox-clean:
+ rm -rf .tox
+
+test-clean: tox-clean
+ rm -f .coverage
+
+test-sterile: test-clean
+ find logs -type f -not -name README.md -exec rm -f '{}' +
diff --git a/make/version.mk b/make/version.mk
new file mode 100644
index 0000000..31f9f00
--- /dev/null
+++ b/make/version.mk
@@ -0,0 +1,54 @@
+# version - automatic version management
+
+# - Prevent version changes with uncommited changes
+# - tag and commit version changes
+# - Use 'lightweight tags'
+
+define BUMPVERSION_CFG
+[bumpversion]
+current_version = $(version)
+commit = True
+tag = True
+[bumpversion:file:$(module)/version.py]
+search = __version__ = "{current_version}"
+replace = __version__ = "{new_version}"
+[bumpversion:file:VERSION]
+search = {current_version}
+replace = {new_version}
+
+endef
+
+export BUMPVERSION_CFG
+
+bumpversion = bumpversion --allow-dirty $(1) && git push
+
+bump: bump-patch
+
+### bump patch version
+bump-patch: version-update
+ $(call bumpversion,patch)
+
+### bump minor version, reset patch to zero
+bump-minor: version-update
+ $(call bumpversion,minor)
+
+### bump major version, reset minor and patch to zero
+bump-major: version-update
+ $(call bumpversion,major)
+
+# assert gitclean, rewrite requirements.txt, update timestamp, apply version update
+version-update:
+ $(call gitclean)
+ [ -f .bumpversion.cfg ] || { echo "$$BUMPVERSION_CFG" >.bumpversion.cfg; git add .bumpversion.cfg; }
+ $(MAKE) --no-print-directory requirements
+ git add requirements*.txt
+ sed -E -i $(module)/version.py -e "s/(.*__timestamp__.*=).*/\1 \"$$(date --rfc-3339=seconds)\"/"
+ git add $(module)/version.py VERSION
+ @echo "Updated version.py timestamp and requirements.txt"
+
+# clean up version tempfiles
+version-clean:
+ @:
+
+version-sterile:
+ rm -f .bumpversion.cfg
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..87cbdbe
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,59 @@
+[build-system]
+requires = ["flit_core >=3.2,<4"]
+build-backend = "flit_core.buildapi"
+requires_python = ">=3.10"
+
+[project]
+name = "rstms-etherscan-python"
+authors = [
+ {name = "Panagiotis-Christos Kotsias", email = "kotsias.pan@gmail.com"},
+ {name = "Matt Krueger", email = "mkrueger@rstms.net"}
+]
+readme = {file = "README.md", content-type = "text/markdown"}
+license = {file = "LICENSE"}
+keywords = ["etherscan", "api", "fork"]
+classifiers = [
+ "Intended Audience :: Developers",
+ "Natural Language :: English",
+ "Programming Language :: Python :: 3",
+ "Programming Language :: Python :: 3.10",
+ ]
+dynamic = ["version", "description"]
+
+dependencies = [
+ "requests"
+ ]
+
+[tool.flit.module]
+name = "rstms_etherscan_python"
+
+[project.optional-dependencies]
+dev = [
+ "black",
+ "bump2version",
+ "coverage",
+ "flake8",
+ "flake8-length",
+ "flit",
+ "isort",
+ "pdbpp",
+ "pyrate-limiter",
+ "pytest",
+ "pytest-datadir",
+ "tox"
+ ]
+docs = [
+ "m2r2",
+ "sphinx",
+ "sphinx-click",
+ "sphinx-rtd-theme"
+ ]
+
+[project.urls]
+ Home = "https://github.com/rstms/etherscan-python"
+
+[tool.black]
+line-length = 120
+
+[tool.isort]
+profile = "black"
diff --git a/pytest.ini b/pytest.ini
new file mode 100644
index 0000000..1c40189
--- /dev/null
+++ b/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+addopts = -x
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..98edf91
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,12 @@
+black
+bump2version
+coverage
+flake8
+flake8-length
+flit
+isort
+pdbpp
+pyrate-limiter
+pytest
+pytest-datadir
+tox
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..f229360
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1 @@
+requests
diff --git a/etherscan/__init__.py b/rstms_etherscan_python/__init__.py
similarity index 56%
rename from etherscan/__init__.py
rename to rstms_etherscan_python/__init__.py
index 7b527d3..45d933e 100644
--- a/etherscan/__init__.py
+++ b/rstms_etherscan_python/__init__.py
@@ -1,3 +1,12 @@
+"""
+etherscan-python
+
+A minimal, yet complete, python API for etherscan.io.
+
+forked from: https://github.com/pcko1/etherscan-python
+
+"""
+
from .etherscan import Etherscan
from .modules.accounts import Accounts as accounts
from .modules.blocks import Blocks as blocks
@@ -8,3 +17,18 @@
from .modules.stats import Stats as stats
from .modules.tokens import Tokens as tokens
from .modules.transactions import Transactions as transactions
+from .version import __version__
+
+__all__ = [
+ "Etherscan",
+ "__version__",
+ "accounts",
+ "blocks",
+ "contracts",
+ "gastracker",
+ "pro",
+ "proxy",
+ "stats",
+ "tokens",
+ "transactions",
+]
diff --git a/etherscan/configs/GOERLI-stable.json b/rstms_etherscan_python/configs/GOERLI-stable.json
similarity index 95%
rename from etherscan/configs/GOERLI-stable.json
rename to rstms_etherscan_python/configs/GOERLI-stable.json
index 4dbe408..03cde83 100644
--- a/etherscan/configs/GOERLI-stable.json
+++ b/rstms_etherscan_python/configs/GOERLI-stable.json
@@ -81,14 +81,14 @@
"gas": "0x5f5e0ff"
}
},
- "get_est_confirmation_time": {
+ "_get_est_confirmation_time": {
"module": "gastracker",
"kwargs": {
"gas_price": "2000000000"
}
},
- "get_gas_oracle": {
- "module": "gastracker",
+ "_get_gas_oracle": {
+ "module": "gastracker",
"kwargs": {}
},
"get_block_reward_by_block_number": {
@@ -97,10 +97,10 @@
"block_no": "2165403"
}
},
- "get_est_block_countdown_time_by_block_number": {
+ "_get_est_block_countdown_time_by_block_number": {
"module": "blocks",
"kwargs": {
- "block_no": "99999999"
+ "block_no": "16701588"
}
},
"get_block_number_by_timestamp": {
@@ -153,6 +153,12 @@
"address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"
}
},
+ "get_contract_creator_and_creation_tx_hash": {
+ "module": "contracts",
+ "kwargs": {
+ "addresses": ["0x7af963cF6D228E564e2A0aA0DdBF06210B38615D"]
+ }
+ },
"get_contract_execution_status": {
"module": "transactions",
"kwargs": {
@@ -307,4 +313,4 @@
"offset": 100
}
}
-}
\ No newline at end of file
+}
diff --git a/etherscan/configs/KOVAN-stable.json b/rstms_etherscan_python/configs/KOVAN-stable.json
similarity index 97%
rename from etherscan/configs/KOVAN-stable.json
rename to rstms_etherscan_python/configs/KOVAN-stable.json
index a8ac91f..3dac5b5 100644
--- a/etherscan/configs/KOVAN-stable.json
+++ b/rstms_etherscan_python/configs/KOVAN-stable.json
@@ -153,6 +153,12 @@
"address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"
}
},
+ "get_contract_creator_and_creation_tx_hash": {
+ "module": "contracts",
+ "kwargs": {
+ "addresses": ["0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"]
+ }
+ },
"get_contract_execution_status": {
"module": "transactions",
"kwargs": {
@@ -307,4 +313,4 @@
"offset": 100
}
}
-}
\ No newline at end of file
+}
diff --git a/etherscan/configs/MAIN-stable.json b/rstms_etherscan_python/configs/MAIN-stable.json
similarity index 97%
rename from etherscan/configs/MAIN-stable.json
rename to rstms_etherscan_python/configs/MAIN-stable.json
index 272ed0b..5e70803 100644
--- a/etherscan/configs/MAIN-stable.json
+++ b/rstms_etherscan_python/configs/MAIN-stable.json
@@ -94,13 +94,13 @@
"get_block_reward_by_block_number": {
"module": "blocks",
"kwargs": {
- "block_no": "2165403"
+ "block_no": "12697906"
}
},
- "get_est_block_countdown_time_by_block_number": {
+ "_get_est_block_countdown_time_by_block_number": {
"module": "blocks",
"kwargs": {
- "block_no": "99999999"
+ "block_no": "16701588"
}
},
"get_block_number_by_timestamp": {
@@ -153,6 +153,12 @@
"address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"
}
},
+ "get_contract_creator_and_creation_tx_hash": {
+ "module": "contracts",
+ "kwargs": {
+ "addresses": ["0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"]
+ }
+ },
"get_contract_execution_status": {
"module": "transactions",
"kwargs": {
@@ -463,4 +469,4 @@
"sort": "asc"
}
}
-}
\ No newline at end of file
+}
diff --git a/etherscan/configs/RINKEBY-stable.json b/rstms_etherscan_python/configs/RINKEBY-stable.json
similarity index 97%
rename from etherscan/configs/RINKEBY-stable.json
rename to rstms_etherscan_python/configs/RINKEBY-stable.json
index 3ca8296..2a21cf6 100644
--- a/etherscan/configs/RINKEBY-stable.json
+++ b/rstms_etherscan_python/configs/RINKEBY-stable.json
@@ -153,6 +153,12 @@
"address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"
}
},
+ "get_contract_creator_and_creation_tx_hash": {
+ "module": "contracts",
+ "kwargs": {
+ "addresses": ["0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"]
+ }
+ },
"get_contract_execution_status": {
"module": "transactions",
"kwargs": {
@@ -307,4 +313,4 @@
"offset": 100
}
}
-}
\ No newline at end of file
+}
diff --git a/etherscan/configs/ROPSTEN-stable.json b/rstms_etherscan_python/configs/ROPSTEN-stable.json
similarity index 97%
rename from etherscan/configs/ROPSTEN-stable.json
rename to rstms_etherscan_python/configs/ROPSTEN-stable.json
index 012856e..9f6798b 100644
--- a/etherscan/configs/ROPSTEN-stable.json
+++ b/rstms_etherscan_python/configs/ROPSTEN-stable.json
@@ -153,6 +153,12 @@
"address": "0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"
}
},
+ "get_contract_creator_and_creation_tx_hash": {
+ "module": "contracts",
+ "kwargs": {
+ "addresses": ["0xBB9bc244D798123fDe783fCc1C72d3Bb8C189413"]
+ }
+ },
"get_contract_execution_status": {
"module": "transactions",
"kwargs": {
@@ -307,4 +313,4 @@
"offset": 100
}
}
-}
\ No newline at end of file
+}
diff --git a/etherscan/configs/__init__.py b/rstms_etherscan_python/configs/__init__.py
similarity index 100%
rename from etherscan/configs/__init__.py
rename to rstms_etherscan_python/configs/__init__.py
diff --git a/etherscan/enums/__init__.py b/rstms_etherscan_python/enums/__init__.py
similarity index 100%
rename from etherscan/enums/__init__.py
rename to rstms_etherscan_python/enums/__init__.py
diff --git a/etherscan/enums/actions_enum.py b/rstms_etherscan_python/enums/actions_enum.py
similarity index 90%
rename from etherscan/enums/actions_enum.py
rename to rstms_etherscan_python/enums/actions_enum.py
index 20c81cb..7b13f89 100644
--- a/etherscan/enums/actions_enum.py
+++ b/rstms_etherscan_python/enums/actions_enum.py
@@ -28,14 +28,10 @@ class ActionsEnum:
ETH_ESTIMATE_GAS: str = "eth_estimateGas"
ETH_GAS_PRICE: str = "eth_gasPrice"
ETH_GET_BLOCK_BY_NUMBER: str = "eth_getBlockByNumber"
- ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER: str = (
- "eth_getBlockTransactionCountByNumber"
- )
+ ETH_GET_BLOCK_TRANSACTION_COUNT_BY_NUMBER: str = "eth_getBlockTransactionCountByNumber"
ETH_GET_CODE: str = "eth_getCode"
ETH_GET_STORAGE_AT: str = "eth_getStorageAt"
- ETH_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX: str = (
- "eth_getTransactionByBlockNumberAndIndex"
- )
+ ETH_GET_TRANSACTION_BY_BLOCK_NUMBER_AND_INDEX: str = "eth_getTransactionByBlockNumberAndIndex"
ETH_GET_TRANSACTION_BY_HASH: str = "eth_getTransactionByHash"
ETH_GET_TRANSACTION_COUNT: str = "eth_getTransactionCount"
ETH_GET_TRANSACTION_RECEIPT: str = "eth_getTransactionReceipt"
@@ -48,6 +44,7 @@ class ActionsEnum:
GET_BLOCK_COUNTDOWN: str = "getblockcountdown"
GET_BLOCK_NUMBER_BY_TIME: str = "getblocknobytime"
GET_BLOCK_REWARD: str = "getblockreward"
+ GET_CONTRACT_CREATION: str = "getcontractcreation"
GET_MINED_BLOCKS: str = "getminedblocks"
GET_SOURCE_CODE: str = "getsourcecode"
GET_STATUS: str = "getstatus"
diff --git a/etherscan/enums/fields_enum.py b/rstms_etherscan_python/enums/fields_enum.py
similarity index 95%
rename from etherscan/enums/fields_enum.py
rename to rstms_etherscan_python/enums/fields_enum.py
index b0bcc4d..d1d42ed 100644
--- a/etherscan/enums/fields_enum.py
+++ b/rstms_etherscan_python/enums/fields_enum.py
@@ -12,6 +12,7 @@ class FieldsEnum:
CLIENT_TYPE: str = "&clienttype="
CLOSEST: str = "&closest="
CONTRACT_ADDRESS: str = "&contractaddress="
+ CONTRACT_ADDRESSES: str = "&contractaddresses="
DATA: str = "&data="
END_BLOCK: str = "&endblock="
END_DATE: str = "&enddate="
diff --git a/etherscan/enums/modules_enum.py b/rstms_etherscan_python/enums/modules_enum.py
similarity index 99%
rename from etherscan/enums/modules_enum.py
rename to rstms_etherscan_python/enums/modules_enum.py
index e02e11c..881cffe 100644
--- a/etherscan/enums/modules_enum.py
+++ b/rstms_etherscan_python/enums/modules_enum.py
@@ -11,4 +11,3 @@ class ModulesEnum:
STATS: str = "stats"
TOKEN: str = "token"
TRANSACTION: str = "transaction"
-
diff --git a/etherscan/enums/tags_enum.py b/rstms_etherscan_python/enums/tags_enum.py
similarity index 100%
rename from etherscan/enums/tags_enum.py
rename to rstms_etherscan_python/enums/tags_enum.py
diff --git a/etherscan/etherscan.py b/rstms_etherscan_python/etherscan.py
similarity index 87%
rename from etherscan/etherscan.py
rename to rstms_etherscan_python/etherscan.py
index 5749aa8..06c45af 100644
--- a/etherscan/etherscan.py
+++ b/rstms_etherscan_python/etherscan.py
@@ -3,10 +3,9 @@
import requests
-import etherscan
-from etherscan import configs
-from etherscan.enums.fields_enum import FieldsEnum as fields
-from etherscan.utils.parsing import ResponseParser as parser
+from . import configs
+from .enums.fields_enum import FieldsEnum as fields
+from .utils.parsing import ResponseParser as parser
class Etherscan:
@@ -36,6 +35,8 @@ def wrapper(*args, **kwargs):
@classmethod
def from_config(cls, api_key: str, config_path: str, net: str):
+ import rstms_etherscan_python as etherscan
+
config = cls.__load_config(config_path)
for func, v in config.items():
if not func.startswith("_"): # disabled if _
diff --git a/rstms_etherscan_python/exceptions.py b/rstms_etherscan_python/exceptions.py
new file mode 100644
index 0000000..ed3c245
--- /dev/null
+++ b/rstms_etherscan_python/exceptions.py
@@ -0,0 +1,17 @@
+# Etherscan API Exceptions
+
+
+class EtherscanUnauthorizedEndpoint(Exception):
+ pass
+
+
+class EtherscanErrorResponse(Exception):
+ pass
+
+
+class EtherscanStatusFailure(Exception):
+ pass
+
+
+class EtherscanUnexpectedResponse(Exception):
+ pass
diff --git a/etherscan/modules/__init__.py b/rstms_etherscan_python/modules/__init__.py
similarity index 100%
rename from etherscan/modules/__init__.py
rename to rstms_etherscan_python/modules/__init__.py
diff --git a/etherscan/modules/accounts.py b/rstms_etherscan_python/modules/accounts.py
similarity index 89%
rename from etherscan/modules/accounts.py
rename to rstms_etherscan_python/modules/accounts.py
index 831b90d..b523f1a 100644
--- a/etherscan/modules/accounts.py
+++ b/rstms_etherscan_python/modules/accounts.py
@@ -1,10 +1,10 @@
from functools import reduce
from typing import List
-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
-from etherscan.enums.tags_enum import TagsEnum as tags
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
+from ..enums.tags_enum import TagsEnum as tags
class Accounts:
@@ -44,7 +44,10 @@ def get_eth_balance_multiple(addresses: List[str]) -> str:
@staticmethod
def get_normal_txs_by_address(
- address: str, startblock: int, endblock: int, sort: str,
+ address: str,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
# NOTE: Returns the last 10k events
url = (
@@ -65,7 +68,12 @@ def get_normal_txs_by_address(
@staticmethod
def get_normal_txs_by_address_paginated(
- address: str, page: int, offset: int, startblock: int, endblock: int, sort: str,
+ address: str,
+ page: int,
+ offset: int,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
url = (
f"{fields.MODULE}"
@@ -89,7 +97,10 @@ def get_normal_txs_by_address_paginated(
@staticmethod
def get_internal_txs_by_address(
- address: str, startblock: int, endblock: int, sort: str,
+ address: str,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
# NOTE: Returns the last 10k events
url = (
@@ -110,7 +121,12 @@ def get_internal_txs_by_address(
@staticmethod
def get_internal_txs_by_address_paginated(
- address: str, page: int, offset: int, startblock: int, endblock: int, sort: str,
+ address: str,
+ page: int,
+ offset: int,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
url = (
f"{fields.MODULE}"
@@ -147,7 +163,11 @@ def get_internal_txs_by_txhash(txhash: str) -> str:
@staticmethod
def get_internal_txs_by_block_range_paginated(
- startblock: int, endblock: int, page: int, offset: int, sort: str,
+ startblock: int,
+ endblock: int,
+ page: int,
+ offset: int,
+ sort: str,
) -> str:
# NOTE: Returns the last 10k events
url = (
@@ -170,7 +190,10 @@ def get_internal_txs_by_block_range_paginated(
@staticmethod
def get_erc20_token_transfer_events_by_address(
- address: str, startblock: int, endblock: int, sort: str,
+ address: str,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
# NOTE: Returns the last 10k events
url = (
@@ -193,7 +216,6 @@ def get_erc20_token_transfer_events_by_address(
def get_erc20_token_transfer_events_by_contract_address_paginated(
contract_address: str, page: int, offset: int, sort: str
) -> str:
-
url = (
f"{fields.MODULE}"
f"{modules.ACCOUNT}"
@@ -214,7 +236,6 @@ def get_erc20_token_transfer_events_by_contract_address_paginated(
def get_erc20_token_transfer_events_by_address_and_contract_paginated(
contract_address: str, address: str, page: int, offset: int, sort: str
) -> str:
-
url = (
f"{fields.MODULE}"
f"{modules.ACCOUNT}"
@@ -235,7 +256,10 @@ def get_erc20_token_transfer_events_by_address_and_contract_paginated(
@staticmethod
def get_erc721_token_transfer_events_by_address(
- address: str, startblock: int, endblock: int, sort: str,
+ address: str,
+ startblock: int,
+ endblock: int,
+ sort: str,
) -> str:
url = (
f"{fields.MODULE}"
@@ -310,9 +334,7 @@ def get_mined_blocks_by_address(address: str) -> str:
return url
@staticmethod
- def get_mined_blocks_by_address_paginated(
- address: str, page: int, offset: int
- ) -> str:
+ def get_mined_blocks_by_address_paginated(address: str, page: int, offset: int) -> str:
url = (
f"{fields.MODULE}"
f"{modules.ACCOUNT}"
diff --git a/etherscan/modules/blocks.py b/rstms_etherscan_python/modules/blocks.py
similarity index 85%
rename from etherscan/modules/blocks.py
rename to rstms_etherscan_python/modules/blocks.py
index c74b6e1..981e9c2 100644
--- a/etherscan/modules/blocks.py
+++ b/rstms_etherscan_python/modules/blocks.py
@@ -1,6 +1,6 @@
-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
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
class Blocks:
diff --git a/rstms_etherscan_python/modules/contracts.py b/rstms_etherscan_python/modules/contracts.py
new file mode 100644
index 0000000..766f65b
--- /dev/null
+++ b/rstms_etherscan_python/modules/contracts.py
@@ -0,0 +1,45 @@
+from functools import reduce
+from typing import List
+
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
+
+
+class Contracts:
+ @staticmethod
+ def get_contract_abi(address: str) -> str:
+ url = (
+ f"{fields.MODULE}"
+ f"{modules.CONTRACT}"
+ f"{fields.ACTION}"
+ f"{actions.GET_ABI}"
+ f"{fields.ADDRESS}"
+ f"{address}"
+ )
+ return url
+
+ @staticmethod
+ def get_contract_source_code(address: str) -> str:
+ url = (
+ f"{fields.MODULE}"
+ f"{modules.CONTRACT}"
+ f"{fields.ACTION}"
+ f"{actions.GET_SOURCE_CODE}"
+ f"{fields.ADDRESS}"
+ f"{address}"
+ )
+ return url
+
+ @staticmethod
+ def get_contract_creator_and_creation_tx_hash(addresses: List[str]) -> str:
+ address_list = reduce(lambda w1, w2: str(w1) + "," + str(w2), addresses)
+ url = (
+ f"{fields.MODULE}"
+ f"{modules.CONTRACT}"
+ f"{fields.ACTION}"
+ f"{actions.GET_CONTRACT_CREATION}"
+ f"{fields.CONTRACT_ADDRESSES}"
+ f"{address_list}"
+ )
+ return url
diff --git a/etherscan/modules/gastracker.py b/rstms_etherscan_python/modules/gastracker.py
similarity index 59%
rename from etherscan/modules/gastracker.py
rename to rstms_etherscan_python/modules/gastracker.py
index 191a8c3..77db5f3 100644
--- a/etherscan/modules/gastracker.py
+++ b/rstms_etherscan_python/modules/gastracker.py
@@ -1,6 +1,6 @@
-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
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
class GasTracker:
@@ -20,10 +20,5 @@ def get_est_confirmation_time(gas_price: int) -> str:
@staticmethod
def get_gas_oracle() -> str:
# NOTE: gas_price in wei, result in seconds
- url = (
- f"{fields.MODULE}"
- f"{modules.GASTRACKER}"
- f"{fields.ACTION}"
- f"{actions.GAS_ORACLE}"
- )
+ url = f"{fields.MODULE}" f"{modules.GASTRACKER}" f"{fields.ACTION}" f"{actions.GAS_ORACLE}"
return url
diff --git a/etherscan/modules/pro.py b/rstms_etherscan_python/modules/pro.py
similarity index 95%
rename from etherscan/modules/pro.py
rename to rstms_etherscan_python/modules/pro.py
index 7ac0876..56cbdfb 100644
--- a/etherscan/modules/pro.py
+++ b/rstms_etherscan_python/modules/pro.py
@@ -1,17 +1,11 @@
-from functools import reduce
-from typing import List
-
-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
-from etherscan.enums.tags_enum import TagsEnum as tags
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
class Pro:
@staticmethod
- def get_hist_eth_balance_for_address_by_block_no(
- address: str, block_no: int
- ) -> str:
+ def get_hist_eth_balance_for_address_by_block_no(address: str, block_no: int) -> str:
url = (
f"{fields.MODULE}"
f"{modules.ACCOUNT}"
@@ -125,9 +119,7 @@ def get_daily_uncle_block_count_and_rewards(
return url
@staticmethod
- def get_hist_erc20_token_total_supply_by_contract_address_and_block_no(
- contract_address: str, block_no: int
- ) -> str:
+ def get_hist_erc20_token_total_supply_by_contract_address_and_block_no(contract_address: str, block_no: int) -> str:
url = (
f"{fields.MODULE}"
f"{modules.STATS}"
diff --git a/etherscan/modules/proxy.py b/rstms_etherscan_python/modules/proxy.py
similarity index 86%
rename from etherscan/modules/proxy.py
rename to rstms_etherscan_python/modules/proxy.py
index 3c5ffac..bd7441c 100644
--- a/etherscan/modules/proxy.py
+++ b/rstms_etherscan_python/modules/proxy.py
@@ -1,18 +1,13 @@
-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
-from etherscan.enums.tags_enum import TagsEnum as tags
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
+from ..enums.tags_enum import TagsEnum as tags
class Proxy:
@staticmethod
def get_proxy_block_number() -> str:
- url = (
- f"{fields.MODULE}"
- f"{modules.PROXY}"
- f"{fields.ACTION}"
- f"{actions.ETH_BLOCK_NUMBER}"
- )
+ url = f"{fields.MODULE}" f"{modules.PROXY}" f"{fields.ACTION}" f"{actions.ETH_BLOCK_NUMBER}"
return url
@staticmethod
@@ -156,18 +151,11 @@ def get_proxy_storage_position_at(position: str, address: str) -> str:
@staticmethod
def get_proxy_gas_price() -> str:
# NOTE: Results are in WEI
- url = (
- f"{fields.MODULE}"
- f"{modules.PROXY}"
- f"{fields.ACTION}"
- f"{actions.ETH_GAS_PRICE}"
- )
+ url = f"{fields.MODULE}" f"{modules.PROXY}" f"{fields.ACTION}" f"{actions.ETH_GAS_PRICE}"
return url
@staticmethod
- def get_proxy_est_gas(
- to: str, data: str, value: str, gas_price: str, gas: str
- ) -> str:
+ def get_proxy_est_gas(to: str, data: str, value: str, gas_price: str, gas: str) -> str:
url = (
f"{fields.MODULE}"
f"{modules.PROXY}"
diff --git a/etherscan/modules/stats.py b/rstms_etherscan_python/modules/stats.py
similarity index 51%
rename from etherscan/modules/stats.py
rename to rstms_etherscan_python/modules/stats.py
index 816f46c..1d2d7bb 100644
--- a/etherscan/modules/stats.py
+++ b/rstms_etherscan_python/modules/stats.py
@@ -1,33 +1,21 @@
-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
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
class Stats:
@staticmethod
def get_total_eth_supply() -> str:
- url = (
- f"{fields.MODULE}"
- f"{modules.STATS}"
- f"{fields.ACTION}"
- f"{actions.ETH_SUPPLY}"
- )
+ url = f"{fields.MODULE}" f"{modules.STATS}" f"{fields.ACTION}" f"{actions.ETH_SUPPLY}"
return url
@staticmethod
def get_eth_last_price() -> str:
- url = (
- f"{fields.MODULE}"
- f"{modules.STATS}"
- f"{fields.ACTION}"
- f"{actions.ETH_PRICE}"
- )
+ url = f"{fields.MODULE}" f"{modules.STATS}" f"{fields.ACTION}" f"{actions.ETH_PRICE}"
return url
@staticmethod
- def get_eth_nodes_size(
- start_date: str, end_date: str, client_type: str, sync_mode: str, sort: str
- ) -> str:
+ def get_eth_nodes_size(start_date: str, end_date: str, client_type: str, sync_mode: str, sort: str) -> str:
url = (
f"{fields.MODULE}"
f"{modules.STATS}"
diff --git a/etherscan/modules/tokens.py b/rstms_etherscan_python/modules/tokens.py
similarity index 67%
rename from etherscan/modules/tokens.py
rename to rstms_etherscan_python/modules/tokens.py
index 1c90745..9a4e403 100644
--- a/etherscan/modules/tokens.py
+++ b/rstms_etherscan_python/modules/tokens.py
@@ -1,7 +1,7 @@
-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
-from etherscan.enums.tags_enum import TagsEnum as tags
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
+from ..enums.tags_enum import TagsEnum as tags
class Tokens:
@@ -18,9 +18,7 @@ def get_total_supply_by_contract_address(contract_address: str) -> str:
return url
@staticmethod
- def get_acc_balance_by_token_and_contract_address(
- contract_address: str, address: str
- ) -> str:
+ def get_acc_balance_by_token_and_contract_address(contract_address: str, address: str) -> str:
url = (
f"{fields.MODULE}"
f"{modules.ACCOUNT}"
diff --git a/etherscan/modules/transactions.py b/rstms_etherscan_python/modules/transactions.py
similarity index 77%
rename from etherscan/modules/transactions.py
rename to rstms_etherscan_python/modules/transactions.py
index 693089b..a5eb6f0 100644
--- a/etherscan/modules/transactions.py
+++ b/rstms_etherscan_python/modules/transactions.py
@@ -1,6 +1,6 @@
-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
+from ..enums.actions_enum import ActionsEnum as actions
+from ..enums.fields_enum import FieldsEnum as fields
+from ..enums.modules_enum import ModulesEnum as modules
class Transactions:
diff --git a/etherscan/utils/__init__.py b/rstms_etherscan_python/utils/__init__.py
similarity index 100%
rename from etherscan/utils/__init__.py
rename to rstms_etherscan_python/utils/__init__.py
diff --git a/etherscan/utils/conversions.py b/rstms_etherscan_python/utils/conversions.py
similarity index 100%
rename from etherscan/utils/conversions.py
rename to rstms_etherscan_python/utils/conversions.py
diff --git a/rstms_etherscan_python/utils/parsing.py b/rstms_etherscan_python/utils/parsing.py
new file mode 100644
index 0000000..e95c629
--- /dev/null
+++ b/rstms_etherscan_python/utils/parsing.py
@@ -0,0 +1,36 @@
+import requests
+
+from ..exceptions import (
+ EtherscanErrorResponse,
+ EtherscanStatusFailure,
+ EtherscanUnauthorizedEndpoint,
+ EtherscanUnexpectedResponse,
+)
+
+PRO_ENDPOINT_RESPONSE = (
+ "Sorry, it looks like you are trying to access an API Pro endpoint. Contact us to upgrade to API Pro."
+)
+
+
+class ResponseParser:
+ @staticmethod
+ def parse(response: requests.Response):
+ content = response.json()
+ if "result" in content.keys():
+ result = content["result"]
+ elif "error" in content.keys():
+ raise EtherscanErrorResponse(f"response={content}")
+ else:
+ raise EtherscanUnexpectedResponse(f"response={content}")
+ if "status" in content.keys():
+ status = bool(int(content["status"]))
+ if result == PRO_ENDPOINT_RESPONSE:
+ url, _, _ = response.url.partition("apikey=")
+ url += "apikey=..."
+ raise EtherscanUnauthorizedEndpoint(f"{result} {url=}")
+ if status is None:
+ raise EtherscanStatusFailure(f"response={content}")
+ else:
+ # GETH or Parity proxy msg format
+ result = dict(jsonrpc=content["jsonrpc"], cid=int(content["id"]), result=result)
+ return result
diff --git a/rstms_etherscan_python/version.py b/rstms_etherscan_python/version.py
new file mode 100644
index 0000000..dd0edd7
--- /dev/null
+++ b/rstms_etherscan_python/version.py
@@ -0,0 +1 @@
+__version__ = "2.1.11"
diff --git a/run_tests.sh b/run_tests.sh
index b1dcc2f..01e4f0a 100644
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -1,4 +1,4 @@
#!/bin/bash
clear
export API_KEY=$1
-coverage run -m unittest discover && coverage report -m
\ No newline at end of file
+coverage run -m unittest discover && coverage report -m
diff --git a/setup.py b/setup.py
deleted file mode 100644
index 9a4410a..0000000
--- a/setup.py
+++ /dev/null
@@ -1,21 +0,0 @@
-from setuptools import setup
-
-setup(
- name="etherscan-python",
- version="2.1.0",
- description="A minimal, yet complete, python API for etherscan.io.",
- url="https://github.com/pcko1/etherscan-python",
- author="Panagiotis-Christos Kotsias",
- author_email="kotsias.pan@gmail.com",
- license="MIT",
- packages=[
- "etherscan",
- "etherscan.configs",
- "etherscan.enums",
- "etherscan.modules",
- "etherscan.utils",
- ],
- install_requires=["requests"],
- include_package_data=True,
- zip_safe=False,
-)
diff --git a/test/__init__.py b/tests/__init__.py
similarity index 100%
rename from test/__init__.py
rename to tests/__init__.py
diff --git a/test/test_modules.py b/tests/test_modules.py
similarity index 62%
rename from test/test_modules.py
rename to tests/test_modules.py
index 8ad9f9b..35156ef 100644
--- a/test/test_modules.py
+++ b/tests/test_modules.py
@@ -1,15 +1,25 @@
import json
-from datetime import datetime
-import time
-
import os
+import time
+from datetime import datetime
from unittest import TestCase
-from etherscan.etherscan import Etherscan
+from rstms_etherscan_python import Etherscan
+from rstms_etherscan_python.exceptions import (
+ EtherscanErrorResponse,
+ EtherscanUnauthorizedEndpoint,
+)
-CONFIG_PATH = "etherscan/configs/{}-stable.json"
+CONFIG_PATH = "rstms_etherscan_python/configs/{}-stable.json"
API_KEY = os.environ["API_KEY"] # Encrypted env var by Travis
+TEST_API_PRO_ENDPOINTS = "TEST_API_PRO_ENDPOINTS" in os.environ
+
+
+def test_init():
+ api = Etherscan(API_KEY)
+ assert api
+
def load(fname):
with open(fname, "r") as f:
@@ -23,7 +33,8 @@ def dump(data, fname):
class Case(TestCase):
_MODULE = ""
- _NETS = ["MAIN", "KOVAN", "RINKEBY", "ROPSTEN"]
+ # _NETS = ["MAIN", "KOVAN", "RINKEBY", "ROPSTEN", "GOERLI"]
+ _NETS = ["MAIN", "GOERLI"]
def methods(self, net):
print(f"\nNET: {net}")
@@ -33,8 +44,20 @@ def methods(self, net):
for fun, v in config.items():
if not fun.startswith("_"): # disabled if _
if v["module"] == self._MODULE:
- res = getattr(etherscan, fun)(**v["kwargs"])
- print(f"METHOD: {fun}, RTYPE: {type(res)}")
+ try:
+ res = getattr(etherscan, fun)(**v["kwargs"])
+ rtype = type(res)
+ except EtherscanUnauthorizedEndpoint as exc:
+ if TEST_API_PRO_ENDPOINTS:
+ raise exc from exc
+ else:
+ rtype = type(exc)
+ res = repr(exc)
+ except EtherscanErrorResponse as exc:
+ rtype = type(exc)
+ res = repr(exc)
+
+ print(f"METHOD: {fun}, RTYPE: {rtype}")
# Create log files (will update existing ones)
fname = f"logs/standard/{net}-{fun}.json"
log = {
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..e0ecba7
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,30 @@
+[tox]
+envlist = flake8, py310
+isolated_build = True
+
+[testenv:flake8]
+skip_install = True
+basepython = python
+deps = flake8
+commands = flake8 rstms_etherscan_python tests
+package = skip
+
+[testenv:py310]
+setenv =
+passenv =
+ API_KEY
+ TEST_API_PRO_ENDPOINTS
+package = skip
+skip_install = True
+allowlist_externals = pip pytest
+commands =
+ pip install -f dist .[dev]
+ pytest {env:PYTEST_OPTIONS} --basetemp={envtmpdir}
+
+[flake8]
+max-line-length = 120
+show-source = False
+max-complexity = 10
+extend-ignore =
+ E501,
+ W505
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy