mm-eth 0.5.6__py3-none-any.whl → 0.5.8__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 ADDED
@@ -0,0 +1,93 @@
1
+ import json
2
+ from typing import Any
3
+
4
+ import websockets
5
+ from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
6
+ from mm_std import Err, Ok, Result, ahr
7
+
8
+ from mm_eth.utils import hex_str_to_int
9
+
10
+
11
+ async def rpc_call(
12
+ *,
13
+ nodes: Nodes,
14
+ method: str,
15
+ params: list[object],
16
+ id_: int = 1,
17
+ timeout: int = 10,
18
+ proxies: Proxies = None,
19
+ attempts: int = 1,
20
+ ) -> Result[Any]:
21
+ data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
22
+ res: Result[Any] = Err("not started yet")
23
+ for _ in range(attempts):
24
+ node = random_node(nodes)
25
+ res = (
26
+ await _http_call(node, data, timeout, random_proxy(proxies))
27
+ if node.startswith("http")
28
+ else await _ws_call(node, data, timeout)
29
+ )
30
+ if isinstance(res, Ok):
31
+ return res
32
+ return res
33
+
34
+
35
+ async def _http_call(node: str, data: dict[str, object], timeout: int, proxy: str | None) -> Result[Any]:
36
+ res = await ahr(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
37
+ if res.is_error():
38
+ return res.to_err_result()
39
+ try:
40
+ err = res.json.get("error", {}).get("message", "")
41
+ if err:
42
+ return res.to_err_result(f"service_error: {err}")
43
+ if "result" in res.json:
44
+ return res.to_ok_result(res.json["result"])
45
+ return res.to_err_result("unknown_response")
46
+ except Exception as err:
47
+ return res.to_err_result(f"exception: {err}")
48
+
49
+
50
+ async def _ws_call(node: str, data: dict[str, object], timeout: int) -> Result[Any]:
51
+ try:
52
+ async with websockets.connect(node, timeout=timeout) as ws:
53
+ await ws.send(json.dumps(data))
54
+ response = json.loads(await ws.recv())
55
+
56
+ err = response.get("error", {}).get("message", "")
57
+ if err:
58
+ return Err(f"service_error: {err}")
59
+ if "result" in response:
60
+ return Ok(response["result"], response)
61
+ return Err(f"unknown_response: {response}")
62
+ except TimeoutError:
63
+ return Err("timeout")
64
+ except Exception as err:
65
+ return Err(f"exception: {err}")
66
+
67
+
68
+ async def eth_block_number(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
69
+ return (
70
+ await rpc_call(
71
+ nodes=rpc_urls,
72
+ method="eth_blockNumber",
73
+ params=[],
74
+ timeout=timeout,
75
+ proxies=proxies,
76
+ attempts=attempts,
77
+ )
78
+ ).and_then(hex_str_to_int)
79
+
80
+
81
+ async def eth_get_balance(
82
+ rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1
83
+ ) -> Result[int]:
84
+ return (
85
+ await rpc_call(
86
+ nodes=rpc_urls,
87
+ method="eth_getBalance",
88
+ params=[address, "latest"],
89
+ timeout=timeout,
90
+ proxies=proxies,
91
+ attempts=attempts,
92
+ )
93
+ ).and_then(hex_str_to_int)
mm_eth/ens.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
2
2
  from mm_std import Err, Ok, Result
3
3
 
4
- from mm_eth.utils import get_w3
4
+ from mm_eth.utils import get_async_w3, get_w3
5
5
 
6
6
 
7
7
  def get_name_with_retries(
@@ -21,3 +21,24 @@ def get_name(rpc_url: str, address: str, timeout: float = 5, proxy: str | None =
21
21
  return Ok(w3.ens.name(w3.to_checksum_address(address))) # type: ignore[union-attr]
22
22
  except Exception as e:
23
23
  return Err(e)
24
+
25
+
26
+ async def async_get_name(rpc_url: str, address: str, timeout: float = 5, proxy: str | None = None) -> Result[str | None]:
27
+ try:
28
+ w3 = await get_async_w3(rpc_url, timeout=timeout, proxy=proxy)
29
+ res = await w3.ens.name(w3.to_checksum_address(address)) # type: ignore[union-attr]
30
+ await w3.provider.disconnect()
31
+ return Ok(res)
32
+ except Exception as e:
33
+ return Err(e)
34
+
35
+
36
+ async def async_get_name_with_retries(
37
+ rpc_urls: Nodes, address: str, retries: int, timeout: float = 5, proxies: Proxies = None
38
+ ) -> Result[str | None]:
39
+ res: Result[str | None] = Err("not started yet")
40
+ for _ in range(retries):
41
+ res = await async_get_name(random_node(rpc_urls), address, timeout=timeout, proxy=random_proxy(proxies))
42
+ if res.is_ok():
43
+ return res
44
+ return res
mm_eth/erc20.py CHANGED
@@ -11,7 +11,7 @@ from eth_utils import to_checksum_address, to_hex
11
11
  from mm_crypto_utils import Nodes, Proxies
12
12
  from mm_std import Err, Ok, Result
13
13
 
14
- from mm_eth import rpc
14
+ from mm_eth import async_rpc, rpc
15
15
  from mm_eth.rpc import Log
16
16
  from mm_eth.tx import SignedTx, sign_legacy_tx, sign_tx
17
17
  from mm_eth.utils import hex_str_to_int, hex_to_bytes, log_topic_to_address
@@ -70,6 +70,27 @@ def get_balance(
70
70
  ).and_then(hex_str_to_int)
71
71
 
72
72
 
73
+ async def async_get_balance(
74
+ rpc_urls: Nodes,
75
+ token_address: str,
76
+ user_address: str,
77
+ timeout: int = 10,
78
+ proxies: Proxies = None,
79
+ attempts: int = 1,
80
+ ) -> Result[int]:
81
+ data = "0x70a08231000000000000000000000000" + user_address[2:]
82
+ return (
83
+ await async_rpc.rpc_call(
84
+ nodes=rpc_urls,
85
+ method="eth_call",
86
+ params=[{"to": token_address, "data": data}, "latest"],
87
+ timeout=timeout,
88
+ proxies=proxies,
89
+ attempts=attempts,
90
+ )
91
+ ).and_then(hex_str_to_int)
92
+
93
+
73
94
  def get_name(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
74
95
  return rpc.rpc_call(
75
96
  nodes=rpc_urls,
mm_eth/ethernodes.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from datetime import datetime
2
2
 
3
- from mm_std import CHROME_USER_AGENT, Result, hr
3
+ from mm_std import CHROME_USER_AGENT, Ok, Result, hr
4
4
  from pydantic import BaseModel, Field
5
5
 
6
6
 
@@ -29,6 +29,6 @@ def search_nodes(offset: int = 0, proxy: str | None = None) -> Result[SearchResu
29
29
  if res.is_error():
30
30
  return res.to_err_result()
31
31
  try:
32
- return res.to_ok_result(SearchResult(**res.json))
32
+ return Ok(SearchResult(**res.json))
33
33
  except Exception as e:
34
34
  return res.to_err_result(f"exception: {e}")
mm_eth/utils.py CHANGED
@@ -2,13 +2,15 @@ import re
2
2
  from decimal import Decimal, localcontext
3
3
  from typing import Any, cast
4
4
 
5
+ import aiohttp
5
6
  import eth_utils
6
7
  import pydash
8
+ from aiohttp_socks import ProxyConnector
7
9
  from eth_typing import HexStr
8
10
  from hexbytes import HexBytes
9
11
  from mm_std import Err, Ok, Result, number_with_separator
10
12
  from pydantic import BaseModel
11
- from web3 import Web3
13
+ from web3 import AsyncWeb3, Web3
12
14
  from web3.types import Wei
13
15
 
14
16
 
@@ -193,6 +195,20 @@ def get_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None)
193
195
  return Web3(Web3.HTTPProvider(rpc_url, request_kwargs=request_kwargs))
194
196
 
195
197
 
198
+ async def get_async_w3(rpc_url: str, timeout: float | None = None, proxy: str | None = None) -> AsyncWeb3:
199
+ request_kwargs: dict[str, object] = {"timeout": timeout}
200
+ if proxy and proxy.startswith("http"):
201
+ request_kwargs["proxy"] = proxy
202
+ provider = AsyncWeb3.AsyncHTTPProvider(rpc_url, request_kwargs=request_kwargs, exception_retry_configuration=None)
203
+ w3 = AsyncWeb3(provider)
204
+
205
+ if proxy and proxy.startswith("socks"):
206
+ session = aiohttp.ClientSession(connector=ProxyConnector.from_url(proxy))
207
+ await provider.cache_async_session(session)
208
+
209
+ return w3
210
+
211
+
196
212
  def name_network(chain_id: int) -> str:
197
213
  match chain_id:
198
214
  case 1:
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mm-eth
3
- Version: 0.5.6
3
+ Version: 0.5.8
4
4
  Requires-Python: >=3.12
5
- Requires-Dist: mm-crypto-utils>=0.2.6
6
- Requires-Dist: requests[socks]>=2.32.0
5
+ Requires-Dist: aiohttp-socks~=0.10.1
6
+ Requires-Dist: mm-crypto-utils>=0.2.9
7
7
  Requires-Dist: typer>=0.15.2
8
- Requires-Dist: web3~=7.8.0
8
+ Requires-Dist: web3~=7.9.0
9
9
  Requires-Dist: websocket-client~=1.8.0
@@ -2,17 +2,18 @@ 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=8rMtg3Hr6Pn7NaFROxxWsXQSM8wJ92EUYlOOoc6_TOk,2934
5
6
  mm_eth/constants.py,sha256=Cy_G-IleBH4gAZ4ok8AGHHlqmdW0ZM7ZldyVpzAfWLs,54
6
7
  mm_eth/deploy.py,sha256=SB3ruY808_5UnG8kHR34uVP66P3zOWZu0ImKD7UUv2s,691
7
- mm_eth/ens.py,sha256=4sEglXdgemKm9fYLCXEvsEeUpdUCqec1GXPklyp19eQ,851
8
- mm_eth/erc20.py,sha256=V__yPDG4ABXWfkSdIqffE5qQDjjZl93287YunllN52A,6804
9
- mm_eth/ethernodes.py,sha256=9y_poTmFUj6cnWaT9mtfc6S9lAfVXTwLGRqxMQ8hT0Y,3080
8
+ mm_eth/ens.py,sha256=amMs4xbBia1hVHKglKwqeheL_8NpukKNAZIsg65wpDA,1689
9
+ mm_eth/erc20.py,sha256=LZmTkjmXpN7sUhOtSLiTqb8Ubet9C9q32EQwTS78OqE,7378
10
+ mm_eth/ethernodes.py,sha256=V4VVbC6Nr9jhwT7blxtLugXC5KfXqE8n-kP0VvGHbqo,3070
10
11
  mm_eth/json_encoder.py,sha256=S4oD-qfTVztMb4sRpY1puhBQwOBofTyQXWszmdXk4og,433
11
12
  mm_eth/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
13
  mm_eth/rpc.py,sha256=k0eHxo_Dp6G0fHQ_rD-QbwOJz5ngC6kxBjl5NEHnyw8,13832
13
14
  mm_eth/solc.py,sha256=dYRvT8PjZlLDZhNsc_-0790Eug_ZwU2G-iBfIdGj6wQ,1071
14
15
  mm_eth/tx.py,sha256=efSoMCoWkenbGdHo1_LX66_Edz1HvED5-J_i3wrHwMw,4051
15
- mm_eth/utils.py,sha256=gLWT5I7CxFvfHgTQrZSSHUk3YyaDzdmtHwLHFjwuV3I,7407
16
+ mm_eth/utils.py,sha256=bkh4EBaxTYEnrR1s7zQ2hzZCwXw1r-2HmfN-dpTXwRc,8069
16
17
  mm_eth/vault.py,sha256=h8NyiOQh5YFskh1lZA3KyvnJUnxl9769ME2ChplG0CM,1477
17
18
  mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
19
  mm_eth/cli/calcs.py,sha256=cLFTYNAN-I53tUiSg-zFfVr2afjIZPftDDjHj16FBz0,1068
@@ -41,7 +42,7 @@ mm_eth/cli/cmd/wallet/private_key_cmd.py,sha256=Fv_2OLog1h32pIP7PJITwl_pHdy3BXva
41
42
  mm_eth/cli/examples/balances.toml,sha256=i_ALpiEcf8-0TFiUg1cgJhxxfHYeBl9x0b3tnUWjswU,421
42
43
  mm_eth/cli/examples/call_contract.toml,sha256=ZQWK-409V_vLIZ2bsRD5RCWPPzShPz2KJTTRQY4YaGw,248
43
44
  mm_eth/cli/examples/transfer.toml,sha256=8mWuphDquoSDJw-hb9VJqtonjmv3kJ5Ip8jJ4t5YnfM,1810
44
- mm_eth-0.5.6.dist-info/METADATA,sha256=R49ouAdttwFUlqby9en4HmcJsu1oxjWqp21vKKn9l_s,246
45
- mm_eth-0.5.6.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
46
- mm_eth-0.5.6.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
47
- mm_eth-0.5.6.dist-info/RECORD,,
45
+ mm_eth-0.5.8.dist-info/METADATA,sha256=8YvcBQc-8hK5tLsYKGyxmUtfLl9o8WeEXtn4verNNFY,244
46
+ mm_eth-0.5.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
+ mm_eth-0.5.8.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
48
+ mm_eth-0.5.8.dist-info/RECORD,,
File without changes