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 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: list[object],
16
+ params: Sequence[object],
16
17
  id_: int = 1,
17
- timeout: int = 10,
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: int, proxy: str | None) -> Result[Any]:
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: int) -> Result[Any]:
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.utils import get_async_w3, get_w3
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
- res = await w3.ens.name(w3.to_checksum_address(address)) # type: ignore[union-attr]
33
- return Ok(res)
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
- error = str(e)
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 async_get_name_with_retries(
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 async_get_name(random_node(rpc_urls), address, timeout=timeout, proxy=random_proxy(proxies))
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.11
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.10
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.0
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=bQUzb6lgadkxLyc5041qaxe12fvXmUKTJM77dKx1wJE,2934
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=x36ttvVtpwbi6iZ2AuPiOSRxz2WVoVVGLbMun_AmZgg,1918
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=bkh4EBaxTYEnrR1s7zQ2hzZCwXw1r-2HmfN-dpTXwRc,8069
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.11.dist-info/METADATA,sha256=w72FUYGmTMgY2Y-6fNSllbRUkoXScJI8UhK097KmBgk,246
46
- mm_eth-0.5.11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
- mm_eth-0.5.11.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
48
- mm_eth-0.5.11.dist-info/RECORD,,
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,,