mm-eth 0.5.11__py3-none-any.whl → 0.5.13__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mm_eth/async_rpc.py +5 -4
- mm_eth/ens.py +68 -13
- mm_eth/rpc_async.py +109 -0
- mm_eth/utils.py +2 -0
- {mm_eth-0.5.11.dist-info → mm_eth-0.5.13.dist-info}/METADATA +4 -3
- {mm_eth-0.5.11.dist-info → mm_eth-0.5.13.dist-info}/RECORD +8 -7
- {mm_eth-0.5.11.dist-info → mm_eth-0.5.13.dist-info}/WHEEL +0 -0
- {mm_eth-0.5.11.dist-info → mm_eth-0.5.13.dist-info}/entry_points.txt +0 -0
mm_eth/async_rpc.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import json
|
|
2
|
+
from collections.abc import Sequence
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
5
|
import websockets
|
|
@@ -12,9 +13,9 @@ async def rpc_call(
|
|
|
12
13
|
*,
|
|
13
14
|
nodes: Nodes,
|
|
14
15
|
method: str,
|
|
15
|
-
params:
|
|
16
|
+
params: Sequence[object],
|
|
16
17
|
id_: int = 1,
|
|
17
|
-
timeout:
|
|
18
|
+
timeout: float = 10,
|
|
18
19
|
proxies: Proxies = None,
|
|
19
20
|
attempts: int = 1,
|
|
20
21
|
) -> Result[Any]:
|
|
@@ -32,7 +33,7 @@ async def rpc_call(
|
|
|
32
33
|
return res
|
|
33
34
|
|
|
34
35
|
|
|
35
|
-
async def _http_call(node: str, data: dict[str, object], timeout:
|
|
36
|
+
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
|
|
36
37
|
res = await hra(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
|
|
37
38
|
if res.is_error():
|
|
38
39
|
return res.to_err_result()
|
|
@@ -47,7 +48,7 @@ async def _http_call(node: str, data: dict[str, object], timeout: int, proxy: st
|
|
|
47
48
|
return res.to_err_result(f"exception: {err}")
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
async def _ws_call(node: str, data: dict[str, object], timeout:
|
|
51
|
+
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result[Any]:
|
|
51
52
|
try:
|
|
52
53
|
async with websockets.connect(node, timeout=timeout) as ws:
|
|
53
54
|
await ws.send(json.dumps(data))
|
mm_eth/ens.py
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
+
from ens.utils import normal_name_to_hash
|
|
1
2
|
from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
|
|
2
3
|
from mm_std import Err, Ok, Result
|
|
4
|
+
from web3 import Web3
|
|
3
5
|
|
|
4
|
-
from mm_eth.
|
|
6
|
+
from mm_eth.async_rpc import rpc_call
|
|
7
|
+
from mm_eth.utils import get_w3
|
|
8
|
+
|
|
9
|
+
ENS_REGISTRY_ADDRESS: str = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
|
|
10
|
+
FUNC_SELECTOR_RESOLVER: str = "0x0178b8bf" # resolver(bytes32)
|
|
11
|
+
FUNC_SELECTOR_NAME: str = "0x691f3431" # name(bytes32)
|
|
5
12
|
|
|
6
13
|
|
|
7
14
|
def get_name_with_retries(
|
|
@@ -25,27 +32,75 @@ def get_name(rpc_url: str, address: str, timeout: float = 5, proxy: str | None =
|
|
|
25
32
|
error = e.__class__.__qualname__
|
|
26
33
|
return Err("exception: " + error)
|
|
27
34
|
|
|
35
|
+
# async def async_get_name(rpc_url: str, address: str, timeout: float = 5, proxy: str | None = None) -> Result[str | None]:
|
|
36
|
+
# w3 = await get_async_w3(rpc_url, timeout=timeout, proxy=proxy)
|
|
37
|
+
# try:
|
|
38
|
+
# res = await w3.ens.name(w3.to_checksum_address(address)) # type: ignore[union-attr]
|
|
39
|
+
# return Ok(res)
|
|
40
|
+
# except Exception as e:
|
|
41
|
+
# error = str(e)
|
|
42
|
+
# if not error:
|
|
43
|
+
# error = e.__class__.__qualname__
|
|
44
|
+
# return Err("exception: " + error)
|
|
45
|
+
# finally:
|
|
46
|
+
# await w3.provider.disconnect()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def get_name_async(rpc_url: str, address: str, timeout: float = 5, proxy: str | None = None) -> Result[str | None]:
|
|
50
|
+
checksum_addr: str = Web3.to_checksum_address(address)
|
|
51
|
+
reverse_name: str = checksum_addr.lower()[2:] + ".addr.reverse"
|
|
52
|
+
name_hash_hex: str = normal_name_to_hash(reverse_name).hex()
|
|
53
|
+
|
|
54
|
+
resolver_data: str = FUNC_SELECTOR_RESOLVER + name_hash_hex
|
|
55
|
+
|
|
56
|
+
resolver_params = [{"to": ENS_REGISTRY_ADDRESS, "data": resolver_data}, "latest"]
|
|
57
|
+
|
|
58
|
+
resolver_res: Result[str] = await rpc_call(
|
|
59
|
+
nodes=rpc_url,
|
|
60
|
+
method="eth_call",
|
|
61
|
+
params=resolver_params,
|
|
62
|
+
timeout=timeout,
|
|
63
|
+
proxies=proxy,
|
|
64
|
+
attempts=1,
|
|
65
|
+
)
|
|
66
|
+
if not isinstance(resolver_res, Ok) or len(resolver_res.ok) != 66:
|
|
67
|
+
return Ok(None)
|
|
68
|
+
|
|
69
|
+
resolver_address: str = Web3.to_checksum_address("0x" + resolver_res.ok[-40:])
|
|
70
|
+
|
|
71
|
+
name_data: str = FUNC_SELECTOR_NAME + name_hash_hex
|
|
72
|
+
name_params = [{"to": resolver_address, "data": name_data}, "latest"]
|
|
73
|
+
|
|
74
|
+
name_res: Result[str] = await rpc_call(
|
|
75
|
+
nodes=rpc_url,
|
|
76
|
+
method="eth_call",
|
|
77
|
+
params=name_params,
|
|
78
|
+
timeout=timeout,
|
|
79
|
+
proxies=proxy,
|
|
80
|
+
attempts=1,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if isinstance(name_res, Err):
|
|
84
|
+
return name_res
|
|
85
|
+
if name_res.ok == "0x":
|
|
86
|
+
return Ok(None)
|
|
28
87
|
|
|
29
|
-
async def async_get_name(rpc_url: str, address: str, timeout: float = 5, proxy: str | None = None) -> Result[str | None]:
|
|
30
|
-
w3 = await get_async_w3(rpc_url, timeout=timeout, proxy=proxy)
|
|
31
88
|
try:
|
|
32
|
-
|
|
33
|
-
|
|
89
|
+
hex_data: str = name_res.ok
|
|
90
|
+
length_hex: str = hex_data[66:130]
|
|
91
|
+
str_len: int = int(length_hex, 16) * 2
|
|
92
|
+
name_hex: str = hex_data[130 : 130 + str_len]
|
|
93
|
+
return Ok(bytes.fromhex(name_hex).decode("utf-8"))
|
|
34
94
|
except Exception as e:
|
|
35
|
-
|
|
36
|
-
if not error:
|
|
37
|
-
error = e.__class__.__qualname__
|
|
38
|
-
return Err("exception: " + error)
|
|
39
|
-
finally:
|
|
40
|
-
await w3.provider.disconnect()
|
|
95
|
+
return Err(e)
|
|
41
96
|
|
|
42
97
|
|
|
43
|
-
async def
|
|
98
|
+
async def get_name_with_retries_async(
|
|
44
99
|
rpc_urls: Nodes, address: str, retries: int, timeout: float = 5, proxies: Proxies = None
|
|
45
100
|
) -> Result[str | None]:
|
|
46
101
|
res: Result[str | None] = Err("not started yet")
|
|
47
102
|
for _ in range(retries):
|
|
48
|
-
res = await
|
|
103
|
+
res = await get_name_async(random_node(rpc_urls), address, timeout=timeout, proxy=random_proxy(proxies))
|
|
49
104
|
if res.is_ok():
|
|
50
105
|
return res
|
|
51
106
|
return res
|
mm_eth/rpc_async.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import string
|
|
3
|
+
from collections.abc import Sequence
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import eth_utils
|
|
7
|
+
import websockets
|
|
8
|
+
from mm_std import DataResult, http_request
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
async def rpc_call(
|
|
12
|
+
node: str,
|
|
13
|
+
method: str,
|
|
14
|
+
params: Sequence[object],
|
|
15
|
+
timeout: float,
|
|
16
|
+
proxy: str | None,
|
|
17
|
+
id_: int = 1,
|
|
18
|
+
) -> DataResult[Any]:
|
|
19
|
+
data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
|
|
20
|
+
if node.startswith("http"):
|
|
21
|
+
return await _http_call(node, data, timeout, proxy)
|
|
22
|
+
return await _ws_call(node, data, timeout)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> DataResult[Any]:
|
|
26
|
+
res = await http_request(node, method="POST", proxy=proxy, timeout=timeout, json=data)
|
|
27
|
+
if res.is_error():
|
|
28
|
+
return res.to_data_result_err()
|
|
29
|
+
try:
|
|
30
|
+
parsed_body = res.parse_json_body()
|
|
31
|
+
err = parsed_body.get("error", {}).get("message", "")
|
|
32
|
+
if err:
|
|
33
|
+
return res.to_data_result_err(f"service_error: {err}")
|
|
34
|
+
if "result" in parsed_body:
|
|
35
|
+
return res.to_data_result_ok(parsed_body["result"])
|
|
36
|
+
return res.to_data_result_err("unknown_response")
|
|
37
|
+
except Exception as err:
|
|
38
|
+
return res.to_data_result_err(f"exception: {err}")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
async def _ws_call(node: str, data: dict[str, object], timeout: float) -> DataResult[Any]:
|
|
42
|
+
try:
|
|
43
|
+
async with websockets.connect(node, timeout=timeout) as ws:
|
|
44
|
+
await ws.send(json.dumps(data))
|
|
45
|
+
response = json.loads(await ws.recv())
|
|
46
|
+
|
|
47
|
+
err = response.get("error", {}).get("message", "")
|
|
48
|
+
if err:
|
|
49
|
+
return DataResult(err=f"service_error: {err}", data=response)
|
|
50
|
+
if "result" in response:
|
|
51
|
+
return DataResult(ok=response["result"], data=response)
|
|
52
|
+
return DataResult(err="unknown_response", data=response)
|
|
53
|
+
except TimeoutError:
|
|
54
|
+
return DataResult(err="timeout")
|
|
55
|
+
except Exception as err:
|
|
56
|
+
return DataResult(err=f"exception: {err}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
async def eth_block_number(node: str, timeout: int = 10, proxy: str | None = None) -> DataResult[int]:
|
|
60
|
+
return (await rpc_call(node, "eth_blockNumber", [], timeout, proxy)).map(hex_str_to_int)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def eth_get_balance(node: str, address: str, timeout: int = 10, proxy: str | None = None) -> DataResult[int]:
|
|
64
|
+
return (await rpc_call(node, "eth_getBalance", [address, "latest"], timeout, proxy)).map(hex_str_to_int)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
async def erc20_balance(
|
|
68
|
+
node: str,
|
|
69
|
+
token_address: str,
|
|
70
|
+
user_address: str,
|
|
71
|
+
timeout: float = 7.0,
|
|
72
|
+
proxy: str | None = None,
|
|
73
|
+
) -> DataResult[int]:
|
|
74
|
+
data = "0x70a08231000000000000000000000000" + user_address[2:]
|
|
75
|
+
params = [{"to": token_address, "data": data}, "latest"]
|
|
76
|
+
return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(hex_str_to_int)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def erc20_name(node: str, token_address: str, timeout: float = 7.0, proxy: str | None = None) -> DataResult[str]:
|
|
80
|
+
params = [{"to": token_address, "data": "0x06fdde03"}, "latest"]
|
|
81
|
+
return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(_normalize_str)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def erc20_symbol(node: str, token_address: str, timeout: float = 7.0, proxy: str | None = None) -> DataResult[str]:
|
|
85
|
+
params = [{"to": token_address, "data": "0x95d89b41"}, "latest"]
|
|
86
|
+
return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(_normalize_str)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def erc20_decimals(node: str, token_address: str, timeout: float = 7.0, proxy: str | None = None) -> DataResult[int]:
|
|
90
|
+
params = [{"to": token_address, "data": "0x313ce567"}, "latest"]
|
|
91
|
+
res = await rpc_call(node, "eth_call", params, timeout, proxy)
|
|
92
|
+
if res.is_err():
|
|
93
|
+
return res
|
|
94
|
+
try:
|
|
95
|
+
if res.unwrap() == "0x":
|
|
96
|
+
return DataResult(err="no_decimals", data=res.data)
|
|
97
|
+
value = res.unwrap()
|
|
98
|
+
result = eth_utils.to_int(hexstr=value[0:66]) if len(value) > 66 else eth_utils.to_int(hexstr=value)
|
|
99
|
+
return DataResult(ok=result, data=res.data)
|
|
100
|
+
except Exception as e:
|
|
101
|
+
return DataResult(err=f"exception: {e}", data=res.data)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def hex_str_to_int(value: str) -> int:
|
|
105
|
+
return int(value, 16)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _normalize_str(value: str) -> str:
|
|
109
|
+
return "".join(filter(lambda x: x in string.printable, eth_utils.to_text(hexstr=value))).strip()
|
mm_eth/utils.py
CHANGED
|
@@ -196,6 +196,8 @@ def get_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None)
|
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
async def get_async_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None) -> AsyncWeb3:
|
|
199
|
+
# TODO: Don't use async w3. AsyncHTTPProvider uses threads
|
|
200
|
+
# check its constructor: self._request_session_manager = HTTPSessionManager()
|
|
199
201
|
request_kwargs: dict[str, object] = {"timeout": timeout}
|
|
200
202
|
if proxy and proxy.startswith("http"):
|
|
201
203
|
request_kwargs["proxy"] = proxy
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mm-eth
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.13
|
|
4
4
|
Requires-Python: >=3.12
|
|
5
5
|
Requires-Dist: aiohttp-socks~=0.10.1
|
|
6
|
-
Requires-Dist: mm-crypto-utils>=0.2.
|
|
6
|
+
Requires-Dist: mm-crypto-utils>=0.2.12
|
|
7
|
+
Requires-Dist: mm-std~=0.3.29
|
|
7
8
|
Requires-Dist: typer>=0.15.2
|
|
8
|
-
Requires-Dist: web3~=7.
|
|
9
|
+
Requires-Dist: web3~=7.10.0
|
|
9
10
|
Requires-Dist: websocket-client~=1.8.0
|
|
@@ -2,18 +2,19 @@ mm_eth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
2
2
|
mm_eth/abi.py,sha256=Qf-QOsR9QexyQM9XWKNeTMkRarIL3XQJbaDbJ8ifMrw,4856
|
|
3
3
|
mm_eth/account.py,sha256=k0MNMatBe0zo1iKZiB_Tq6zyEIo22IncD6ewNUJp3dY,2075
|
|
4
4
|
mm_eth/anvil.py,sha256=98RCfI7dEpxFBTV6UErYvubWVP3n0ctUFn1--4kZ84U,1603
|
|
5
|
-
mm_eth/async_rpc.py,sha256=
|
|
5
|
+
mm_eth/async_rpc.py,sha256=zjPcOoC-NTjMxbWu6cRQn-knHEQ75ZO5L-KZoaiwUJ0,2981
|
|
6
6
|
mm_eth/constants.py,sha256=Cy_G-IleBH4gAZ4ok8AGHHlqmdW0ZM7ZldyVpzAfWLs,54
|
|
7
7
|
mm_eth/deploy.py,sha256=SB3ruY808_5UnG8kHR34uVP66P3zOWZu0ImKD7UUv2s,691
|
|
8
|
-
mm_eth/ens.py,sha256=
|
|
8
|
+
mm_eth/ens.py,sha256=lH3TZABYFBiEEtWlyxCqrcYC-nZQsI_1wSCMzBzHPrM,3860
|
|
9
9
|
mm_eth/erc20.py,sha256=LZmTkjmXpN7sUhOtSLiTqb8Ubet9C9q32EQwTS78OqE,7378
|
|
10
10
|
mm_eth/ethernodes.py,sha256=V4VVbC6Nr9jhwT7blxtLugXC5KfXqE8n-kP0VvGHbqo,3070
|
|
11
11
|
mm_eth/json_encoder.py,sha256=S4oD-qfTVztMb4sRpY1puhBQwOBofTyQXWszmdXk4og,433
|
|
12
12
|
mm_eth/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
13
13
|
mm_eth/rpc.py,sha256=k0eHxo_Dp6G0fHQ_rD-QbwOJz5ngC6kxBjl5NEHnyw8,13832
|
|
14
|
+
mm_eth/rpc_async.py,sha256=3AzSSA9zQ_sPtGT4TUDUCorFGnDlmQC4WnmbwmUQETY,4260
|
|
14
15
|
mm_eth/solc.py,sha256=dYRvT8PjZlLDZhNsc_-0790Eug_ZwU2G-iBfIdGj6wQ,1071
|
|
15
16
|
mm_eth/tx.py,sha256=efSoMCoWkenbGdHo1_LX66_Edz1HvED5-J_i3wrHwMw,4051
|
|
16
|
-
mm_eth/utils.py,sha256=
|
|
17
|
+
mm_eth/utils.py,sha256=FytG3U6h80mnUaKP8W2mPZ77EuOp4U7pVbPuoKI3wW4,8215
|
|
17
18
|
mm_eth/vault.py,sha256=h8NyiOQh5YFskh1lZA3KyvnJUnxl9769ME2ChplG0CM,1477
|
|
18
19
|
mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
20
|
mm_eth/cli/calcs.py,sha256=cLFTYNAN-I53tUiSg-zFfVr2afjIZPftDDjHj16FBz0,1068
|
|
@@ -42,7 +43,7 @@ mm_eth/cli/cmd/wallet/private_key_cmd.py,sha256=Fv_2OLog1h32pIP7PJITwl_pHdy3BXva
|
|
|
42
43
|
mm_eth/cli/examples/balances.toml,sha256=i_ALpiEcf8-0TFiUg1cgJhxxfHYeBl9x0b3tnUWjswU,421
|
|
43
44
|
mm_eth/cli/examples/call_contract.toml,sha256=ZQWK-409V_vLIZ2bsRD5RCWPPzShPz2KJTTRQY4YaGw,248
|
|
44
45
|
mm_eth/cli/examples/transfer.toml,sha256=8mWuphDquoSDJw-hb9VJqtonjmv3kJ5Ip8jJ4t5YnfM,1810
|
|
45
|
-
mm_eth-0.5.
|
|
46
|
-
mm_eth-0.5.
|
|
47
|
-
mm_eth-0.5.
|
|
48
|
-
mm_eth-0.5.
|
|
46
|
+
mm_eth-0.5.13.dist-info/METADATA,sha256=AyFH0U7lyIP_seMH_MsCOwQHUsw-85j0jH2eloElwXA,277
|
|
47
|
+
mm_eth-0.5.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
48
|
+
mm_eth-0.5.13.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
|
|
49
|
+
mm_eth-0.5.13.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|