Skip to content

Commit cc921f7

Browse files
committed
add a new feature to validate ethereum address
1 parent 2f300bc commit cc921f7

File tree

6 files changed

+167
-3
lines changed

6 files changed

+167
-3
lines changed

pdm.lock

Lines changed: 58 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ classifiers = [
3535
]
3636
requires-python = ">=3.8"
3737
dynamic = ["version"]
38-
dependencies = []
38+
dependencies = [
39+
"eth-hash[pycryptodome]>=0.7.0",
40+
]
3941

4042
[project.urls]
4143
Homepage = "https://python-validators.github.io/validators"

src/validators/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from .card import amex, card_number, diners, discover, jcb, mastercard, unionpay, visa
66
from .country import calling_code, country_code, currency
77
from .cron import cron
8-
from .crypto_addresses.btc_address import btc_address
8+
from .crypto_addresses import btc_address, eth_address
99
from .domain import domain
1010
from .email import email
1111
from .encoding import base58, base64
@@ -37,6 +37,7 @@
3737
# ...
3838
"between",
3939
"btc_address",
40+
"eth_address",
4041
# cards
4142
"amex",
4243
"card_number",
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
11
"""Crypto addresses."""
2+
3+
# local
4+
from .btc_address import btc_address
5+
from .eth_address import eth_address
6+
7+
__all__ = ("btc_address", "eth_address")
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""ETH Address."""
2+
3+
# standard
4+
import re
5+
6+
# external
7+
from eth_hash.auto import keccak
8+
9+
# local
10+
from validators.utils import validator
11+
12+
13+
def _validate_eth_checksum_address(addr: str):
14+
"""Validate ETH type checksum address."""
15+
addr = addr.replace("0x", "")
16+
addr_hash = keccak.new(addr.lower().encode("ascii")).digest().hex()
17+
18+
if len(addr) != 40:
19+
return False
20+
21+
for i in range(0, 40):
22+
if (int(addr_hash[i], 16) > 7 and addr[i].upper() != addr[i]) or (
23+
int(addr_hash[i], 16) <= 7 and addr[i].lower() != addr[i]
24+
):
25+
return False
26+
return True
27+
28+
29+
@validator
30+
def eth_address(value: str, /):
31+
"""Return whether or not given value is a valid ethereum address.
32+
33+
Full validation is implemented for ERC20 addresses.
34+
35+
Examples:
36+
>>> eth_address('0x9cc14ba4f9f68ca159ea4ebf2c292a808aaeb598')
37+
# Output: True
38+
>>> eth_address('0x8Ba1f109551bD432803012645Ac136ddd64DBa72')
39+
# Output: ValidationError(func=eth_address, args=...)
40+
41+
Args:
42+
value:
43+
Ethereum address string to validate.
44+
45+
Returns:
46+
(Literal[True]): If `value` is a valid ethereum address.
47+
(ValidationError): If `value` is an invalid ethereum address.
48+
"""
49+
if not value:
50+
return False
51+
52+
return re.compile(r"^0x[0-9a-f]{40}$|^0x[0-9A-F]{40}$").match(
53+
value
54+
) or _validate_eth_checksum_address(value)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
"""Test ETH address."""
2+
3+
# external
4+
import pytest
5+
6+
# local
7+
from validators import ValidationError, eth_address
8+
9+
10+
@pytest.mark.parametrize(
11+
"value",
12+
[
13+
"0x8ba1f109551bd432803012645ac136ddd64dba72",
14+
"0x9cc14ba4f9f68ca159ea4ebf2c292a808aaeb598",
15+
"0x5AEDA56215b167893e80B4fE645BA6d5Bab767DE",
16+
"0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
17+
"0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
18+
"0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
19+
"0x1234567890123456789012345678901234567890",
20+
"0x57Ab1ec28D129707052df4dF418D58a2D46d5f51",
21+
],
22+
)
23+
def test_returns_true_on_valid_eth_address(value: str):
24+
"""Test returns true on valid eth address."""
25+
assert eth_address(value)
26+
27+
28+
@pytest.mark.parametrize(
29+
"value",
30+
[
31+
"0x742d35Cc6634C0532925a3b844Bc454e4438f44g",
32+
"0x742d35Cc6634C0532925a3b844Bc454e4438f44",
33+
"0xAbcdefg1234567890Abcdefg1234567890Abcdefg",
34+
"0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c72",
35+
"0x80fBD7F8B3f81D0e1d6EACAb69AF104A6508AFB1",
36+
"0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c7g",
37+
"0x7c8EE9977c6f96b6b9774b3e8e4Cc9B93B12b2c",
38+
"0x7Fb21a171205f3B8d8E4d88A2d2f8A56E45DdB5c",
39+
"validators.eth",
40+
],
41+
)
42+
def test_returns_failed_validation_on_invalid_eth_address(value: str):
43+
"""Test returns failed validation on invalid eth address."""
44+
assert isinstance(eth_address(value), ValidationError)

0 commit comments

Comments
 (0)