mm-eth 0.5.17__py3-none-any.whl → 0.6.1__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.
Files changed (45) hide show
  1. mm_eth/abi.py +2 -4
  2. mm_eth/account.py +51 -18
  3. mm_eth/anvil.py +8 -8
  4. mm_eth/cli/calcs.py +1 -16
  5. mm_eth/cli/cli.py +55 -159
  6. mm_eth/cli/cli_utils.py +14 -27
  7. mm_eth/cli/cmd/balance_cmd.py +15 -16
  8. mm_eth/cli/cmd/balances_cmd.py +35 -36
  9. mm_eth/cli/cmd/deploy_cmd.py +9 -11
  10. mm_eth/cli/cmd/node_cmd.py +20 -15
  11. mm_eth/cli/cmd/solc_cmd.py +7 -6
  12. mm_eth/cli/cmd/transfer_cmd.py +210 -128
  13. mm_eth/cli/cmd/wallet/private_key_cmd.py +5 -4
  14. mm_eth/cli/rpc_helpers.py +32 -115
  15. mm_eth/cli/validators.py +13 -16
  16. mm_eth/converters.py +56 -0
  17. mm_eth/erc20.py +6 -224
  18. mm_eth/retry.py +153 -0
  19. mm_eth/rpc.py +230 -428
  20. mm_eth/solc.py +30 -17
  21. mm_eth/tx.py +8 -9
  22. mm_eth/utils.py +0 -224
  23. {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/METADATA +3 -2
  24. mm_eth-0.6.1.dist-info/RECORD +33 -0
  25. mm_eth/async_rpc.py +0 -94
  26. mm_eth/cli/cmd/call_contract_cmd.py +0 -44
  27. mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
  28. mm_eth/cli/cmd/example_cmd.py +0 -9
  29. mm_eth/cli/cmd/rpc_cmd.py +0 -78
  30. mm_eth/cli/cmd/token_cmd.py +0 -29
  31. mm_eth/cli/cmd/tx_cmd.py +0 -16
  32. mm_eth/cli/cmd/vault_cmd.py +0 -19
  33. mm_eth/cli/examples/balances.toml +0 -18
  34. mm_eth/cli/examples/call_contract.toml +0 -9
  35. mm_eth/cli/examples/transfer.toml +0 -46
  36. mm_eth/cli/print_helpers.py +0 -37
  37. mm_eth/constants.py +0 -1
  38. mm_eth/ens.py +0 -106
  39. mm_eth/ethernodes.py +0 -34
  40. mm_eth/json_encoder.py +0 -15
  41. mm_eth/rpc_async.py +0 -160
  42. mm_eth/vault.py +0 -38
  43. mm_eth-0.5.17.dist-info/RECORD +0 -49
  44. {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/WHEEL +0 -0
  45. {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/entry_points.txt +0 -0
mm_eth/rpc.py CHANGED
@@ -1,403 +1,104 @@
1
- from __future__ import annotations
2
-
1
+ import asyncio
3
2
  import json
4
- from dataclasses import dataclass
3
+ import string
4
+ from collections.abc import Sequence
5
5
  from typing import Any, Literal, cast
6
6
 
7
- import websocket
8
- from mm_crypto_utils import Nodes, Proxies, random_node, random_proxy
9
- from mm_std import Err, Ok, Result, hr, random_choice
10
- from pydantic import BaseModel
11
- from web3.types import BlockIdentifier
12
-
13
- from mm_eth.utils import hex_str_to_int
14
-
15
-
16
- @dataclass
17
- class TxReceipt:
18
- tx_hash: str
19
- tx_index: int
20
- block_number: int
21
- from_address: str
22
- to_address: str | None
23
- contract_address: str | None
24
- status: int | None
25
-
26
-
27
- @dataclass
28
- class Log:
29
- address: str
30
- block_hash: str
31
- block_number: int
32
- data: str
33
- log_index: int
34
- removed: bool
35
- topics: list[str]
36
- transaction_hash: str
37
- transaction_index: int
38
-
39
- @classmethod
40
- def from_json_rpc_dict(cls, data: dict[str, Any]) -> Result[Log]:
41
- try:
42
- return Ok(
43
- Log(
44
- address=data["address"],
45
- block_hash=data["blockHash"],
46
- block_number=int(data["blockNumber"], 16),
47
- data=data["data"],
48
- log_index=int(data["logIndex"], 16),
49
- removed=data["removed"],
50
- topics=data["topics"],
51
- transaction_hash=data["transactionHash"],
52
- transaction_index=int(data["transactionIndex"], 16),
53
- ),
54
- )
55
- except Exception as err:
56
- return Err(f"exception: {err}")
57
-
58
-
59
- class TxData(BaseModel):
60
- block_number: int | None # for pending tx it can be none
61
- from_: str
62
- to: str | None
63
- gas: int
64
- gas_price: int
65
- value: int
66
- hash: str
67
- input: str
68
- nonce: int
69
- v: int
70
- r: str
71
- s: str
72
-
73
-
74
- def rpc_call(
75
- *,
76
- nodes: Nodes,
7
+ import ens.utils
8
+ import eth_utils
9
+ import pydash
10
+ import websockets
11
+ from eth_typing import BlockIdentifier
12
+ from mm_std import Result, http_request
13
+ from web3.types import TxReceipt
14
+
15
+ TIMEOUT = 7.0
16
+
17
+
18
+ async def rpc_call(
19
+ node: str,
77
20
  method: str,
78
- params: list[object],
21
+ params: Sequence[object],
22
+ timeout: float,
23
+ proxy: str | None,
79
24
  id_: int = 1,
80
- timeout: int = 10,
81
- proxies: Proxies = None,
82
- attempts: int = 1,
83
25
  ) -> Result[Any]:
84
26
  data = {"jsonrpc": "2.0", "method": method, "params": params, "id": id_}
85
- res: Result[Any] = Err("not started yet")
86
- for _ in range(attempts):
87
- node = random_node(nodes)
88
- res = _http_call(node, data, timeout, random_proxy(proxies)) if node.startswith("http") else _ws_call(node, data, timeout)
89
- if isinstance(res, Ok):
90
- return res
91
- return res
27
+ if node.startswith("http"):
28
+ return await _http_call(node, data, timeout, proxy)
29
+ return await _ws_call(node, data, timeout)
92
30
 
93
31
 
94
- def _http_call(node: str, data: dict[str, object], timeout: int, proxy: str | None) -> Result[Any]:
95
- res = hr(node, method="POST", proxy=proxy, timeout=timeout, params=data, json_params=True)
32
+ async def _http_call(node: str, data: dict[str, object], timeout: float, proxy: str | None) -> Result[Any]:
33
+ res = await http_request(node, method="POST", proxy=proxy, timeout=timeout, json=data)
34
+ if res.is_err():
35
+ return res.to_err()
96
36
  try:
97
- if res.is_error():
98
- return res.to_err_result()
99
-
100
- err = res.json.get("error", {}).get("message", "")
37
+ parsed_body = res.parse_json_body()
38
+ err = parsed_body.get("error", {}).get("message", "")
101
39
  if err:
102
- return res.to_err_result(f"service_error: {err}")
103
- if "result" in res.json:
104
- return res.to_ok_result(res.json["result"])
40
+ return res.to_err(f"service_error: {err}")
41
+ if "result" in parsed_body:
42
+ return res.to_ok(parsed_body["result"])
43
+ return res.to_ok("unknown_response")
44
+ except Exception as e:
45
+ return res.to_err(e)
105
46
 
106
- return res.to_err_result("unknown_response")
107
- except Exception as err:
108
- return res.to_err_result(f"exception: {err}")
109
47
 
110
-
111
- def _ws_call(node: str, data: dict[str, object], timeout: int) -> Result[Any]:
48
+ async def _ws_call(node: str, data: dict[str, object], timeout: float) -> Result[Any]:
112
49
  try:
113
- ws = websocket.create_connection(node, timeout=timeout)
114
- ws.send(json.dumps(data))
115
- response = json.loads(ws.recv())
116
- ws.close()
117
- err = response.get("error", {}).get("message", "")
50
+ async with asyncio.timeout(timeout):
51
+ async with websockets.connect(node) as ws:
52
+ await ws.send(json.dumps(data))
53
+ response = json.loads(await ws.recv())
54
+
55
+ err = pydash.get(response, "error.message")
118
56
  if err:
119
- return Err(f"service_error: {err}")
57
+ return Result.err(f"service_error: {err}", {"response": response})
120
58
  if "result" in response:
121
- return Ok(response["result"])
122
- return Err(f"unknown_response: {response}")
59
+ return Result.ok(response["result"], {"response": response})
60
+ return Result.err("unknown_response", {"response": response})
123
61
  except TimeoutError:
124
- return Err("timeout")
125
- except Exception as err:
126
- return Err(f"exception: {err}")
127
-
128
-
129
- def eth_block_number(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
130
- return rpc_call(
131
- nodes=rpc_urls,
132
- method="eth_blockNumber",
133
- params=[],
134
- timeout=timeout,
135
- proxies=proxies,
136
- attempts=attempts,
137
- ).and_then(hex_str_to_int)
138
-
139
-
140
- def eth_chain_id(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
141
- return rpc_call(
142
- nodes=rpc_urls,
143
- method="eth_chainId",
144
- params=[],
145
- timeout=timeout,
146
- proxies=proxies,
147
- attempts=attempts,
148
- ).and_then(hex_str_to_int)
149
-
150
-
151
- def net_peer_count(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
152
- return rpc_call(
153
- nodes=rpc_urls,
154
- method="net_peerCount",
155
- params=[],
156
- timeout=timeout,
157
- proxies=proxies,
158
- attempts=attempts,
159
- ).and_then(hex_str_to_int)
160
-
161
-
162
- def web3_client_version(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
163
- return rpc_call(
164
- nodes=rpc_urls,
165
- method="web3_clientVersion",
166
- params=[],
167
- timeout=timeout,
168
- proxies=proxies,
169
- attempts=attempts,
170
- )
171
-
172
-
173
- def net_version(nodes: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
174
- return rpc_call(nodes=nodes, method="net_version", params=[], timeout=timeout, proxies=proxies, attempts=attempts)
175
-
176
-
177
- def eth_get_code(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
178
- return rpc_call(
179
- nodes=rpc_urls,
180
- method="eth_getCode",
181
- params=[address, "latest"],
182
- timeout=timeout,
183
- proxies=proxies,
184
- attempts=attempts,
185
- )
186
-
187
-
188
- def eth_send_raw_transaction(
189
- rpc_urls: Nodes,
190
- raw_tx: str,
191
- timeout: int = 10,
192
- proxies: Proxies = None,
193
- attempts: int = 1,
194
- ) -> Result[str]:
195
- return rpc_call(
196
- nodes=rpc_urls,
197
- method="eth_sendRawTransaction",
198
- params=[raw_tx],
199
- timeout=timeout,
200
- proxies=proxies,
201
- attempts=attempts,
202
- )
203
-
204
-
205
- def eth_get_balance(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
206
- return rpc_call(
207
- nodes=rpc_urls,
208
- method="eth_getBalance",
209
- params=[address, "latest"],
210
- timeout=timeout,
211
- proxies=proxies,
212
- attempts=attempts,
213
- ).and_then(hex_str_to_int)
214
-
215
-
216
- def eth_get_transaction_count(
217
- rpc_urls: Nodes,
218
- address: str,
219
- timeout: int = 10,
220
- proxies: Proxies = None,
221
- attempts: int = 1,
222
- ) -> Result[int]:
223
- return rpc_call(
224
- nodes=rpc_urls,
225
- method="eth_getTransactionCount",
226
- params=[address, "latest"],
227
- timeout=timeout,
228
- proxies=proxies,
229
- attempts=attempts,
230
- ).and_then(hex_str_to_int)
231
-
232
-
233
- def eth_get_block_by_number(
234
- rpc_urls: Nodes,
235
- block_number: BlockIdentifier,
236
- full_transaction: bool = False,
237
- timeout: int = 10,
238
- proxies: Proxies = None,
239
- attempts: int = 1,
62
+ return Result.err("timeout")
63
+ except Exception as e:
64
+ return Result.err(e)
65
+
66
+
67
+ # -- start eth rpc calls --
68
+
69
+
70
+ async def eth_block_number(node: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
71
+ return (await rpc_call(node, "eth_blockNumber", [], timeout, proxy)).map(_hex_str_to_int)
72
+
73
+
74
+ async def eth_get_balance(node: str, address: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
75
+ return (await rpc_call(node, "eth_getBalance", [address, "latest"], timeout, proxy)).map(_hex_str_to_int)
76
+
77
+
78
+ async def eth_chain_id(node: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
79
+ return (await rpc_call(node, "eth_chainId", [], timeout, proxy)).map(_hex_str_to_int)
80
+
81
+
82
+ async def eth_get_block_by_number(
83
+ node: str, block_number: BlockIdentifier, full_transaction: bool = False, timeout: float = TIMEOUT, proxy: str | None = None
240
84
  ) -> Result[dict[str, Any]]:
241
- return rpc_call(
242
- nodes=rpc_urls,
243
- method="eth_getBlockByNumber",
244
- params=[hex(block_number) if isinstance(block_number, int) else block_number, full_transaction],
245
- timeout=timeout,
246
- proxies=proxies,
247
- attempts=attempts,
248
- )
249
-
250
-
251
- def eth_get_logs(
252
- rpc_urls: Nodes,
253
- *,
254
- address: str | None = None,
255
- topics: list[str] | None = None,
256
- from_block: BlockIdentifier | None = None,
257
- to_block: BlockIdentifier | None = None,
258
- timeout: int = 10,
259
- proxies: Proxies = None,
260
- attempts: int = 1,
261
- ) -> Result[list[Log]]:
262
- params: dict[str, object] = {}
263
- if address:
264
- params["address"] = address
265
- if isinstance(from_block, int):
266
- params["fromBlock"] = hex(from_block)
267
- else:
268
- params["fromBlock"] = "earliest"
269
- if isinstance(to_block, int):
270
- params["toBlock"] = hex(to_block)
271
- if topics:
272
- params["topics"] = topics
273
-
274
- res = rpc_call(nodes=rpc_urls, method="eth_getLogs", params=[params], proxies=proxies, attempts=attempts, timeout=timeout)
275
- if isinstance(res, Err):
276
- return res
85
+ params = [hex(block_number) if isinstance(block_number, int) else block_number, full_transaction]
86
+ return await rpc_call(node, "eth_getBlockByNumber", params, timeout, proxy)
277
87
 
278
- result: list[Log] = []
279
- for log_data in res.ok:
280
- log_res = Log.from_json_rpc_dict(log_data)
281
- if isinstance(log_res, Err):
282
- return Err(log_res.err, data=res.data)
283
- result.append(log_res.ok)
284
- return Ok(result, data=res.data)
285
-
286
-
287
- def eth_get_transaction_receipt(
288
- rpc_urls: Nodes,
289
- tx_hash: str,
290
- timeout: int = 10,
291
- proxies: Proxies = None,
292
- attempts: int = 1,
293
- ) -> Result[TxReceipt]:
294
- res = rpc_call(
295
- nodes=rpc_urls,
296
- method="eth_getTransactionReceipt",
297
- params=[tx_hash],
298
- timeout=timeout,
299
- proxies=proxies,
300
- attempts=attempts,
301
- )
302
- if isinstance(res, Err):
303
- return res
304
88
 
305
- if res.ok is None:
306
- return Err("no_receipt", data=res.data)
89
+ async def eth_get_transaction_count(node: str, address: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
90
+ return (await rpc_call(node, "eth_getTransactionCount", [address, "latest"], timeout, proxy)).map(_hex_str_to_int)
307
91
 
308
- try:
309
- status = None
310
- receipt = cast(dict[str, Any], res.ok)
311
- if "status" in receipt:
312
- status = int(receipt["status"], 16)
313
- return Ok(
314
- TxReceipt(
315
- tx_hash=tx_hash,
316
- tx_index=int(receipt["transactionIndex"], 16),
317
- block_number=int(receipt["blockNumber"], 16),
318
- from_address=receipt["from"],
319
- to_address=receipt.get("to"),
320
- contract_address=receipt.get("contractAddress"),
321
- status=status,
322
- ),
323
- data=res.data,
324
- )
325
- except Exception as err:
326
- return Err(f"exception: {err}", data=res.data)
327
-
328
-
329
- def eth_get_transaction_by_hash(
330
- rpc_urls: Nodes,
331
- tx_hash: str,
332
- timeout: int = 10,
333
- proxies: Proxies = None,
334
- attempts: int = 1,
335
- ) -> Result[TxData]:
336
- res = rpc_call(
337
- nodes=rpc_urls,
338
- method="eth_getTransactionByHash",
339
- params=[tx_hash],
340
- timeout=timeout,
341
- proxies=proxies,
342
- attempts=attempts,
343
- )
344
- if isinstance(res, Err):
345
- return res
346
- if res.ok is None:
347
- return Err("not_found", data=res.data)
348
92
 
349
- try:
350
- tx = res.ok
351
- return Ok(
352
- TxData(
353
- block_number=int(tx["blockNumber"], 16) if tx["blockNumber"] is not None else None,
354
- from_=tx["from"],
355
- to=tx.get("to"),
356
- gas=int(tx["gas"], 16),
357
- gas_price=int(tx["gasPrice"], 16),
358
- value=int(tx["value"], 16),
359
- nonce=int(tx["nonce"], 16),
360
- input=tx["input"],
361
- hash=tx_hash,
362
- v=int(tx["v"], 16),
363
- r=tx.get("r"),
364
- s=tx.get("s"),
365
- ),
366
- data=res.data,
367
- )
368
-
369
- except Exception as err:
370
- return Err(f"exception: {err}", data=res.data)
371
-
372
-
373
- def eth_call(
374
- rpc_urls: Nodes,
375
- to: str,
376
- data: str,
377
- timeout: int = 10,
378
- proxies: Proxies = None,
379
- attempts: int = 1,
380
- ) -> Result[str]:
381
- return rpc_call(
382
- nodes=rpc_urls,
383
- method="eth_call",
384
- params=[{"to": to, "data": data}, "latest"],
385
- timeout=timeout,
386
- proxies=proxies,
387
- attempts=attempts,
388
- )
389
-
390
-
391
- def eth_estimate_gas(
392
- rpc_urls: Nodes,
93
+ async def eth_estimate_gas(
94
+ node: str,
393
95
  from_: str,
394
96
  to: str | None = None,
395
97
  value: int | None = 0,
396
98
  data: str | None = None,
397
99
  type_: Literal["0x0", "0x2"] | None = None,
398
- timeout: int = 10,
399
- proxies: Proxies = None,
400
- attempts: int = 1,
100
+ timeout: float = TIMEOUT,
101
+ proxy: str | None = None,
401
102
  ) -> Result[int]:
402
103
  params: dict[str, Any] = {"from": from_}
403
104
  if to:
@@ -408,68 +109,169 @@ def eth_estimate_gas(
408
109
  params["value"] = hex(value)
409
110
  if type_:
410
111
  params["type"] = type_
411
- return rpc_call(
412
- nodes=rpc_urls,
413
- method="eth_estimateGas",
414
- params=[params],
415
- timeout=timeout,
416
- proxies=proxies,
417
- attempts=attempts,
418
- ).and_then(hex_str_to_int)
419
-
420
-
421
- def eth_gas_price(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
422
- return rpc_call(
423
- nodes=rpc_urls,
424
- method="eth_gasPrice",
425
- params=[],
426
- timeout=timeout,
427
- proxies=proxies,
428
- attempts=attempts,
429
- ).and_then(hex_str_to_int)
430
-
431
-
432
- def eth_syncing(rpc_urls: Nodes, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[bool | dict[str, int]]:
433
- res = rpc_call(nodes=rpc_urls, method="eth_syncing", params=[], timeout=timeout, proxies=proxies, attempts=attempts)
434
- if isinstance(res, Err):
435
- return res
112
+ return (await rpc_call(node, "eth_estimateGas", [params], timeout, proxy)).map(_hex_str_to_int)
113
+
114
+
115
+ async def eth_send_raw_transaction(node: str, raw_tx: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[str]:
116
+ return await rpc_call(node, "eth_sendRawTransaction", [raw_tx], timeout, proxy)
117
+
436
118
 
437
- if isinstance(res.ok, dict):
438
- result = {}
439
- for k, v in res.ok.items():
440
- if v:
441
- result[k] = int(v, 16)
119
+ async def eth_get_transaction_receipt(
120
+ node: str, tx_hash: str, timeout: float = TIMEOUT, proxy: str | None = None
121
+ ) -> Result[TxReceipt]:
122
+ def convert_hex_str_ints(receipt: dict[str, Any]) -> TxReceipt:
123
+ int_fields = {
124
+ "blockNumber",
125
+ "cumulativeGasUsed",
126
+ "effectiveGasPrice",
127
+ "gasUsed",
128
+ "status",
129
+ "transactionIndex",
130
+ "type",
131
+ }
132
+
133
+ converted: dict[str, Any] = {}
134
+ for key, value in receipt.items():
135
+ if key in int_fields and isinstance(value, str) and value.startswith("0x"):
136
+ converted[key] = int(value, 16)
442
137
  else:
443
- result[k] = v
444
- if result.get("currentBlock") and result.get("highestBlock"):
445
- result["remaining"] = result["highestBlock"] - result["currentBlock"]
446
- return Ok(result, res.data)
138
+ converted[key] = value
139
+
140
+ return cast(TxReceipt, converted)
141
+
142
+ res = await rpc_call(node, "eth_getTransactionReceipt", [tx_hash], timeout, proxy)
143
+ if res.is_err():
144
+ return res
145
+
146
+ if res.unwrap() is None:
147
+ return Result.err("no_receipt", res.extra)
148
+
149
+ try:
150
+ return Result.ok(convert_hex_str_ints(res.unwrap()), res.extra)
151
+ except Exception as e:
152
+ return Result.err(e, res.extra)
153
+
154
+
155
+ # -- end eth rpc calls --
447
156
 
448
- return res
157
+ # -- start erc20 rpc calls --
449
158
 
450
159
 
451
- def get_tx_status(rpc_urls: Nodes, tx_hash: str, timeout: int = 5, proxies: Proxies = None, attempts: int = 5) -> Result[int]:
452
- res: Result[int] = Err("not started yet")
453
- for _ in range(attempts):
454
- node = cast(str, random_choice(rpc_urls))
455
- cast(str | None, random_choice(proxies))
456
- receipt_res = eth_get_transaction_receipt(node, tx_hash, timeout, proxies=proxies, attempts=1)
457
- if isinstance(receipt_res, Err) and receipt_res.err == "no_receipt":
458
- return receipt_res
459
- if isinstance(receipt_res, Ok) and receipt_res.ok.status is None:
460
- return Err("no_status", data=res.data)
160
+ async def erc20_balance(node: str, token: str, wallet: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
161
+ data = "0x70a08231000000000000000000000000" + wallet[2:]
162
+ params = [{"to": token, "data": data}, "latest"]
163
+ return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(_hex_str_to_int)
461
164
 
462
- if isinstance(receipt_res, Ok):
463
- return Ok(cast(int, receipt_res.ok.status), data=receipt_res.data)
464
- res = receipt_res
465
165
 
466
- return res
166
+ async def erc20_name(node: str, token: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[str]:
167
+ params = [{"to": token, "data": "0x06fdde03"}, "latest"]
168
+ return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(_normalize_str)
467
169
 
468
170
 
469
- def get_base_fee_per_gas(rpc_urls: Nodes, timeout: int = 5, proxies: Proxies = None, attempts: int = 5) -> Result[int]:
470
- res = eth_get_block_by_number(rpc_urls, "latest", False, timeout=timeout, proxies=proxies, attempts=attempts)
471
- if isinstance(res, Err):
171
+ async def erc20_symbol(node: str, token: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[str]:
172
+ params = [{"to": token, "data": "0x95d89b41"}, "latest"]
173
+ return (await rpc_call(node, "eth_call", params, timeout, proxy)).map(_normalize_str)
174
+
175
+
176
+ async def erc20_decimals(node: str, token: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
177
+ params = [{"to": token, "data": "0x313ce567"}, "latest"]
178
+ res = await rpc_call(node, "eth_call", params, timeout, proxy)
179
+ if res.is_err():
472
180
  return res
473
- if "baseFeePerGas" in res.ok:
474
- return Ok(int(res.ok["baseFeePerGas"], 16), data=res.data)
475
- return Err("no_base_fee_per_gas", data=res.data)
181
+ try:
182
+ if res.unwrap() == "0x":
183
+ return res.with_error("no_decimals")
184
+ value = res.unwrap()
185
+ result = eth_utils.to_int(hexstr=value[0:66]) if len(value) > 66 else eth_utils.to_int(hexstr=value)
186
+ return res.with_value(result)
187
+ except Exception as e:
188
+ return res.with_error(e)
189
+
190
+
191
+ # -- end erc20 rpc calls --
192
+
193
+ # -- start ens calls --
194
+
195
+
196
+ async def ens_name(node: str, address: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[str | None]:
197
+ ens_registry_address: str = "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"
198
+ func_selector_resolver: str = "0x0178b8bf" # resolver(bytes32)
199
+ func_selector_name: str = "0x691f3431" # name(bytes32)
200
+
201
+ checksum_addr = eth_utils.to_checksum_address(address)
202
+ reverse_name = checksum_addr.lower()[2:] + ".addr.reverse"
203
+ name_hash_hex = ens.utils.normal_name_to_hash(reverse_name).hex()
204
+
205
+ resolver_data = func_selector_resolver + name_hash_hex
206
+
207
+ resolver_params = [{"to": ens_registry_address, "data": resolver_data}, "latest"]
208
+
209
+ resolver_res = await rpc_call(node, method="eth_call", params=resolver_params, timeout=timeout, proxy=proxy)
210
+ if resolver_res.is_err():
211
+ return resolver_res
212
+
213
+ extra = {"resolver_response": resolver_res.to_dict()}
214
+
215
+ if resolver_res.is_ok() and len(resolver_res.unwrap()) != 66:
216
+ return Result.ok(None, extra)
217
+
218
+ resolver_address = eth_utils.to_checksum_address("0x" + resolver_res.unwrap()[-40:])
219
+
220
+ name_data: str = func_selector_name + name_hash_hex
221
+ name_params = [{"to": resolver_address, "data": name_data}, "latest"]
222
+
223
+ name_res = await rpc_call(node, "eth_call", name_params, timeout=timeout, proxy=proxy)
224
+
225
+ extra["name_response"] = name_res.to_dict()
226
+
227
+ if name_res.is_err():
228
+ return Result.err(name_res.unwrap_error(), extra)
229
+
230
+ if name_res.unwrap() == "0x":
231
+ return Result.ok(None, extra)
232
+
233
+ try:
234
+ hex_data = name_res.unwrap()
235
+ length_hex = hex_data[66:130]
236
+ str_len = int(length_hex, 16) * 2
237
+ name_hex = hex_data[130 : 130 + str_len]
238
+ return Result.ok(bytes.fromhex(name_hex).decode("utf-8"), extra)
239
+ except Exception as e:
240
+ return Result.err(e, extra)
241
+
242
+
243
+ # -- stop ens calls --
244
+
245
+ # -- start other --
246
+
247
+
248
+ async def get_base_fee_per_gas(node: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
249
+ res = await eth_get_block_by_number(node, "latest", False, timeout=timeout, proxy=proxy)
250
+ if res.is_err():
251
+ return Result.err(res.unwrap_error(), res.extra)
252
+ if "baseFeePerGas" in res.unwrap():
253
+ return res.with_value(int(res.unwrap()["baseFeePerGas"], 16))
254
+ return Result.err("no_base_fee_per_gas", res.extra)
255
+
256
+
257
+ async def get_tx_status(node: str, tx_hash: str, timeout: float = TIMEOUT, proxy: str | None = None) -> Result[int]:
258
+ res = await eth_get_transaction_receipt(node, tx_hash, timeout=timeout, proxy=proxy)
259
+ if res.is_err():
260
+ return Result.err(res.unwrap_error(), res.extra)
261
+ status = res.unwrap().get("status")
262
+ if status is None:
263
+ return Result.err("no_status", res.extra)
264
+ return Result.ok(status, res.extra)
265
+
266
+
267
+ # -- end other --
268
+
269
+ # -- utils --
270
+
271
+
272
+ def _hex_str_to_int(value: str) -> int:
273
+ return int(value, 16)
274
+
275
+
276
+ def _normalize_str(value: str) -> str:
277
+ return "".join(filter(lambda x: x in string.printable, eth_utils.to_text(hexstr=value))).strip()