mm-eth 0.1.0__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 (50) hide show
  1. mm_eth/__init__.py +0 -0
  2. mm_eth/abi/zksync.json +2092 -0
  3. mm_eth/abi.py +130 -0
  4. mm_eth/account.py +70 -0
  5. mm_eth/anvil.py +56 -0
  6. mm_eth/cli/__init__.py +0 -0
  7. mm_eth/cli/calcs.py +88 -0
  8. mm_eth/cli/cli.py +233 -0
  9. mm_eth/cli/cli_helpers.py +195 -0
  10. mm_eth/cli/cli_utils.py +150 -0
  11. mm_eth/cli/cmd/__init__.py +0 -0
  12. mm_eth/cli/cmd/balance_cmd.py +59 -0
  13. mm_eth/cli/cmd/balances_cmd.py +121 -0
  14. mm_eth/cli/cmd/call_contract_cmd.py +44 -0
  15. mm_eth/cli/cmd/config_example_cmd.py +9 -0
  16. mm_eth/cli/cmd/deploy_cmd.py +41 -0
  17. mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
  18. mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
  19. mm_eth/cli/cmd/node_cmd.py +47 -0
  20. mm_eth/cli/cmd/private_key_cmd.py +10 -0
  21. mm_eth/cli/cmd/rpc_cmd.py +81 -0
  22. mm_eth/cli/cmd/send_contract_cmd.py +247 -0
  23. mm_eth/cli/cmd/solc_cmd.py +25 -0
  24. mm_eth/cli/cmd/token_cmd.py +29 -0
  25. mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
  26. mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
  27. mm_eth/cli/cmd/vault_cmd.py +16 -0
  28. mm_eth/cli/config_examples/balances.yml +15 -0
  29. mm_eth/cli/config_examples/call_contract.yml +5 -0
  30. mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
  31. mm_eth/cli/config_examples/transfer_eth.yml +24 -0
  32. mm_eth/cli/validators.py +84 -0
  33. mm_eth/deploy.py +20 -0
  34. mm_eth/ens.py +16 -0
  35. mm_eth/erc20.py +240 -0
  36. mm_eth/ethernodes.py +34 -0
  37. mm_eth/py.typed +0 -0
  38. mm_eth/rpc.py +478 -0
  39. mm_eth/services/__init__.py +0 -0
  40. mm_eth/solc.py +34 -0
  41. mm_eth/tx.py +164 -0
  42. mm_eth/types.py +5 -0
  43. mm_eth/utils.py +245 -0
  44. mm_eth/vault.py +38 -0
  45. mm_eth/zksync.py +203 -0
  46. mm_eth-0.1.0.dist-info/METADATA +24 -0
  47. mm_eth-0.1.0.dist-info/RECORD +50 -0
  48. mm_eth-0.1.0.dist-info/WHEEL +5 -0
  49. mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
  50. mm_eth-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,84 @@
1
+ import os
2
+ from decimal import Decimal, InvalidOperation
3
+ from pathlib import Path
4
+
5
+ from mm_std import str_to_list
6
+
7
+ from mm_eth.cli import calcs
8
+ from mm_eth.utils import to_wei
9
+
10
+
11
+ def wei_validator(v: str | None) -> int | None:
12
+ if v is None:
13
+ return None
14
+ return to_wei(v)
15
+
16
+
17
+ def log_validator(v: str | None) -> str | None:
18
+ if v is None:
19
+ return None
20
+ log_file = Path(v).expanduser()
21
+ log_file.touch(exist_ok=True)
22
+ if not (log_file.is_file() and os.access(log_file, os.W_OK)):
23
+ raise ValueError(f"wrong log path: {v}")
24
+ return v
25
+
26
+
27
+ def nodes_validator(v: str | list[str] | None) -> list[str]:
28
+ if v is None:
29
+ return []
30
+ if isinstance(v, str):
31
+ return str_to_list(v, unique=True, remove_comments=True, split_line=True)
32
+ return v
33
+
34
+
35
+ def addresses_validator(v: str | list[str] | None) -> list[str]:
36
+ if v is None:
37
+ return []
38
+ if isinstance(v, str):
39
+ return str_to_list(v, unique=True, remove_comments=True, split_line=True, lower=True)
40
+ return v
41
+
42
+
43
+ def delay_validator(v: str | Decimal) -> Decimal | tuple[Decimal, Decimal]:
44
+ if isinstance(v, int | float):
45
+ return Decimal(str(v))
46
+ elif isinstance(v, str):
47
+ arr = [a.strip() for a in v.split("-")]
48
+ if len(arr) != 2:
49
+ raise ValueError("wrong delay value")
50
+ try:
51
+ return Decimal(arr[0]), Decimal(arr[1])
52
+ except InvalidOperation:
53
+ raise ValueError("wrong delay value") from None
54
+ raise ValueError("wrong delay value")
55
+
56
+
57
+ def is_valid_calc_var_wei_value(value: str | None, base_name: str = "var", decimals: int | None = None) -> bool:
58
+ if value is None:
59
+ return True # check for None on BaseModel.field type level
60
+ try:
61
+ calcs.calc_var_wei_value(value, var_value=123, var_name=base_name, decimals=decimals)
62
+ return True
63
+ except ValueError:
64
+ return False
65
+
66
+
67
+ def is_valid_calc_decimal_value(value: str | None) -> bool:
68
+ if value is None:
69
+ return True # check for None on BaseModel.field type level
70
+ try:
71
+ calcs.calc_decimal_value(value)
72
+ return True
73
+ except ValueError:
74
+ return False
75
+
76
+
77
+ def is_valid_calc_function_args(value: str | None) -> bool:
78
+ if value is None:
79
+ return True
80
+ try:
81
+ calcs.calc_function_args(value)
82
+ return True
83
+ except ValueError:
84
+ return False
mm_eth/deploy.py ADDED
@@ -0,0 +1,20 @@
1
+ import eth_utils
2
+ import rlp
3
+ from eth_utils import keccak
4
+
5
+ from mm_eth import abi
6
+
7
+
8
+ def get_deploy_contract_data(contract_bin: str, constructor_types: list[str], constructor_values: list[object]) -> str:
9
+ constructor_data = ""
10
+ if constructor_types and constructor_values:
11
+ constructor_data = abi.encode_data(constructor_types, constructor_values)[2:]
12
+ return contract_bin + constructor_data
13
+
14
+
15
+ def get_contract_address(sender_address: str, nonce: int) -> str:
16
+ sender_bytes = eth_utils.to_bytes(hexstr=sender_address)
17
+ raw = rlp.encode([sender_bytes, nonce])
18
+ h = keccak(raw)
19
+ address_bytes = h[12:]
20
+ return eth_utils.to_checksum_address(address_bytes).lower()
mm_eth/ens.py ADDED
@@ -0,0 +1,16 @@
1
+ from mm_std import Err, Ok, Result
2
+
3
+ from mm_eth.types import Nodes, Proxies
4
+ from mm_eth.utils import get_w3, random_node, random_proxy
5
+
6
+
7
+ def get_name(nodes: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 3) -> Result[str]:
8
+ result = Err("not_started")
9
+ for _ in range(attempts):
10
+ try:
11
+ w3 = get_w3(random_node(nodes), timeout=timeout, proxy=random_proxy(proxies))
12
+ res = w3.ens.name(w3.to_checksum_address(address)) # type: ignore[union-attr]
13
+ return Ok(res or "")
14
+ except Exception as e:
15
+ result = Err(e)
16
+ return result
mm_eth/erc20.py ADDED
@@ -0,0 +1,240 @@
1
+ from __future__ import annotations
2
+
3
+ import string
4
+ from collections.abc import Sequence
5
+ from dataclasses import dataclass
6
+
7
+ import eth_abi
8
+ import eth_utils
9
+ from eth_typing import HexStr
10
+ from eth_utils import to_checksum_address, to_hex
11
+ from mm_std import Err, Ok, Result
12
+
13
+ from mm_eth import rpc
14
+ from mm_eth.rpc import Log
15
+ from mm_eth.tx import SignedTx, sign_legacy_tx, sign_tx
16
+ from mm_eth.types import Nodes, Proxies
17
+ from mm_eth.utils import hex_str_to_int, hex_to_bytes, log_topic_to_address
18
+
19
+ TRANSFER_METHOD = "0xa9059cbb"
20
+ TRANSFER_TOPIC = HexStr("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
21
+
22
+
23
+ USDT_MAINNET_ADDRESS = "0xdac17f958d2ee523a2206206994597c13d831ec7"
24
+
25
+
26
+ @dataclass
27
+ class TransferEventLog:
28
+ token_address: str
29
+ from_address: str
30
+ to_address: str
31
+ value: int
32
+ tx_hash: str
33
+ block_number: int
34
+ log_index: int
35
+
36
+ @staticmethod
37
+ def from_log(log: Log) -> Result[TransferEventLog]:
38
+ try:
39
+ return Ok(
40
+ TransferEventLog(
41
+ token_address=log.address,
42
+ from_address=log_topic_to_address(log.topics[1]),
43
+ to_address=log_topic_to_address(log.topics[2]),
44
+ value=int(log.data, 16),
45
+ tx_hash=log.transaction_hash,
46
+ block_number=log.block_number,
47
+ log_index=log.log_index,
48
+ ),
49
+ )
50
+ except Exception as err:
51
+ return Err(err)
52
+
53
+
54
+ def get_balance(
55
+ rpc_urls: Nodes,
56
+ token_address: str,
57
+ user_address: str,
58
+ timeout: int = 10,
59
+ proxies: Proxies = None,
60
+ attempts: int = 1,
61
+ ) -> Result[int]:
62
+ data = "0x70a08231000000000000000000000000" + user_address[2:]
63
+ return rpc.rpc_call(
64
+ nodes=rpc_urls,
65
+ method="eth_call",
66
+ params=[{"to": token_address, "data": data}, "latest"],
67
+ timeout=timeout,
68
+ proxies=proxies,
69
+ attempts=attempts,
70
+ ).and_then(hex_str_to_int)
71
+
72
+
73
+ def get_name(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
74
+ return rpc.rpc_call(
75
+ nodes=rpc_urls,
76
+ method="eth_call",
77
+ params=[{"to": address, "data": "0x06fdde03"}, "latest"],
78
+ timeout=timeout,
79
+ proxies=proxies,
80
+ attempts=attempts,
81
+ ).and_then(_normalize_str)
82
+
83
+
84
+ def get_symbol(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
85
+ return rpc.rpc_call(
86
+ nodes=rpc_urls,
87
+ method="eth_call",
88
+ params=[{"to": address, "data": "0x95d89b41"}, "latest"],
89
+ timeout=timeout,
90
+ proxies=proxies,
91
+ attempts=attempts,
92
+ ).and_then(_normalize_str)
93
+
94
+
95
+ def get_decimals(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
96
+ res = rpc.rpc_call(
97
+ nodes=rpc_urls,
98
+ method="eth_call",
99
+ params=[{"to": address, "data": "0x313ce567"}, "latest"],
100
+ timeout=timeout,
101
+ proxies=proxies,
102
+ attempts=attempts,
103
+ )
104
+ if isinstance(res, Err):
105
+ return res
106
+
107
+ try:
108
+ if res.ok == "0x":
109
+ return Err("no_decimals", data=res.data)
110
+
111
+ if len(res.ok) > 66:
112
+ result = eth_utils.to_int(hexstr=res.ok[0:66])
113
+ else:
114
+ result = eth_utils.to_int(hexstr=res.ok)
115
+ return Ok(result, data=res.data)
116
+
117
+ except Exception as e:
118
+ return Err(f"exception: {e}", data=res.data)
119
+
120
+
121
+ def encode_transfer_input_data(recipient: str, value: int) -> str:
122
+ recipient = to_checksum_address(recipient)
123
+ input_data = hex_to_bytes(TRANSFER_METHOD) + eth_abi.encode(["address", "uint256"], [recipient, value])
124
+ return to_hex(input_data)
125
+
126
+
127
+ def transfer_token_legacy(
128
+ *,
129
+ rpc_urls: str | Sequence[str],
130
+ token_address: str,
131
+ recipient_address: str,
132
+ value: int,
133
+ nonce: int,
134
+ gas_price: int,
135
+ gas_limit: int,
136
+ private_key: str,
137
+ chain_id: int,
138
+ timeout: int = 10,
139
+ proxies: Proxies = None,
140
+ attempts: int = 1,
141
+ ) -> Result[str]:
142
+ input_data = encode_transfer_input_data(recipient_address, value)
143
+ signed_tx = sign_legacy_tx(
144
+ nonce=nonce,
145
+ gas_price=gas_price,
146
+ gas=gas_limit,
147
+ private_key=private_key,
148
+ chain_id=chain_id,
149
+ data=input_data,
150
+ to=token_address,
151
+ )
152
+ return rpc.eth_send_raw_transaction(rpc_urls, signed_tx.raw_tx, timeout=timeout, proxies=proxies, attempts=attempts)
153
+
154
+
155
+ def transfer_token(
156
+ *,
157
+ rpc_urls: Nodes,
158
+ token_address: str,
159
+ recipient_address: str,
160
+ value: int,
161
+ nonce: int,
162
+ max_fee_per_gas: int,
163
+ max_priority_fee_per_gas: int,
164
+ gas_limit: int,
165
+ private_key: str,
166
+ chain_id: int,
167
+ timeout: int = 10,
168
+ proxies: Proxies = None,
169
+ attempts: int = 1,
170
+ ) -> Result[str]:
171
+ input_data = encode_transfer_input_data(recipient_address, value)
172
+ signed_tx = sign_tx(
173
+ nonce=nonce,
174
+ max_fee_per_gas=max_fee_per_gas,
175
+ max_priority_fee_per_gas=max_priority_fee_per_gas,
176
+ gas=gas_limit,
177
+ private_key=private_key,
178
+ chain_id=chain_id,
179
+ data=input_data,
180
+ to=token_address,
181
+ )
182
+ return rpc.eth_send_raw_transaction(rpc_urls, signed_tx.raw_tx, timeout=timeout, proxies=proxies, attempts=attempts)
183
+
184
+
185
+ def sign_transfer_tx(
186
+ *,
187
+ token_address: str,
188
+ recipient_address: str,
189
+ value: int,
190
+ nonce: int,
191
+ max_fee_per_gas: int,
192
+ max_priority_fee_per_gas: int,
193
+ gas_limit: int,
194
+ private_key: str,
195
+ chain_id: int,
196
+ ) -> SignedTx:
197
+ input_data = encode_transfer_input_data(recipient_address, value)
198
+ return sign_tx(
199
+ nonce=nonce,
200
+ max_fee_per_gas=max_fee_per_gas,
201
+ max_priority_fee_per_gas=max_priority_fee_per_gas,
202
+ gas=gas_limit,
203
+ private_key=private_key,
204
+ chain_id=chain_id,
205
+ data=input_data,
206
+ to=token_address,
207
+ )
208
+
209
+
210
+ def get_transfer_event_logs(
211
+ rpc_urls: Nodes,
212
+ token_address: str | None,
213
+ from_block: int,
214
+ to_block: int,
215
+ timeout: int = 10,
216
+ proxies: Proxies = None,
217
+ attempts: int = 1,
218
+ ) -> Result[list[TransferEventLog]]:
219
+ res = rpc.eth_get_logs(
220
+ rpc_urls,
221
+ address=token_address,
222
+ topics=[TRANSFER_TOPIC],
223
+ from_block=from_block,
224
+ to_block=to_block,
225
+ timeout=timeout,
226
+ proxies=proxies,
227
+ attempts=attempts,
228
+ )
229
+ if isinstance(res, Err):
230
+ return res
231
+ result: list[TransferEventLog] = []
232
+ for log in res.ok:
233
+ event_log_res = TransferEventLog.from_log(log)
234
+ if isinstance(event_log_res, Ok):
235
+ result.append(event_log_res.ok)
236
+ return Ok(result, data=res.data)
237
+
238
+
239
+ def _normalize_str(value: str) -> Ok[str]:
240
+ return Ok("".join(filter(lambda x: x in string.printable, eth_utils.to_text(hexstr=value))).strip())
mm_eth/ethernodes.py ADDED
@@ -0,0 +1,34 @@
1
+ from datetime import datetime
2
+
3
+ from mm_std import CHROME_USER_AGENT, Result, hr
4
+ from pydantic import BaseModel, Field
5
+
6
+
7
+ class SearchResult(BaseModel):
8
+ class Node(BaseModel):
9
+ id: str
10
+ host: str
11
+ port: int
12
+ client: str
13
+ client_version: str = Field(..., alias="clientVersion")
14
+ os: str
15
+ last_update: datetime = Field(..., alias="lastUpdate")
16
+ country: str
17
+ in_sync: int | None = Field(None, alias="inSync")
18
+ isp: str
19
+
20
+ draw: int
21
+ records_total: int = Field(..., alias="recordsTotal")
22
+ records_filtered: int = Field(..., alias="recordsFiltered")
23
+ data: list[Node]
24
+
25
+
26
+ def search_nodes(offset: int = 0, proxy: str | None = None) -> Result[SearchResult]:
27
+ url = f"https://www.ethernodes.org/data?draw=1&columns%5B0%5D%5Bdata%5D=id&columns%5B0%5D%5Bname%5D=&columns%5B0%5D%5Bsearchable%5D=true&columns%5B0%5D%5Borderable%5D=true&columns%5B0%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B0%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B1%5D%5Bdata%5D=host&columns%5B1%5D%5Bname%5D=&columns%5B1%5D%5Bsearchable%5D=true&columns%5B1%5D%5Borderable%5D=true&columns%5B1%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B1%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B2%5D%5Bdata%5D=isp&columns%5B2%5D%5Bname%5D=&columns%5B2%5D%5Bsearchable%5D=true&columns%5B2%5D%5Borderable%5D=true&columns%5B2%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B2%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B3%5D%5Bdata%5D=country&columns%5B3%5D%5Bname%5D=&columns%5B3%5D%5Bsearchable%5D=true&columns%5B3%5D%5Borderable%5D=true&columns%5B3%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B3%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B4%5D%5Bdata%5D=client&columns%5B4%5D%5Bname%5D=&columns%5B4%5D%5Bsearchable%5D=true&columns%5B4%5D%5Borderable%5D=true&columns%5B4%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B4%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B5%5D%5Bdata%5D=clientVersion&columns%5B5%5D%5Bname%5D=&columns%5B5%5D%5Bsearchable%5D=true&columns%5B5%5D%5Borderable%5D=true&columns%5B5%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B5%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B6%5D%5Bdata%5D=os&columns%5B6%5D%5Bname%5D=&columns%5B6%5D%5Bsearchable%5D=true&columns%5B6%5D%5Borderable%5D=true&columns%5B6%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B6%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B7%5D%5Bdata%5D=lastUpdate&columns%5B7%5D%5Bname%5D=&columns%5B7%5D%5Bsearchable%5D=true&columns%5B7%5D%5Borderable%5D=true&columns%5B7%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B7%5D%5Bsearch%5D%5Bregex%5D=false&columns%5B8%5D%5Bdata%5D=inSync&columns%5B8%5D%5Bname%5D=&columns%5B8%5D%5Bsearchable%5D=true&columns%5B8%5D%5Borderable%5D=true&columns%5B8%5D%5Bsearch%5D%5Bvalue%5D=&columns%5B8%5D%5Bsearch%5D%5Bregex%5D=false&order%5B0%5D%5Bcolumn%5D=0&order%5B0%5D%5Bdir%5D=asc&search%5Bvalue%5D=&search%5Bregex%5D=false&length=100&start={offset}"
28
+ res = hr(url, proxy=proxy, user_agent=CHROME_USER_AGENT)
29
+ if res.is_error():
30
+ return res.to_err_result()
31
+ try:
32
+ return res.to_ok_result(SearchResult(**res.json))
33
+ except Exception as e:
34
+ return res.to_err_result(f"exception: {e}")
mm_eth/py.typed ADDED
File without changes