mm-eth 0.5.16__py3-none-any.whl → 0.6.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 (46) 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 +143 -0
  19. mm_eth/rpc.py +226 -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.16.dist-info → mm_eth-0.6.0.dist-info}/METADATA +3 -3
  24. mm_eth-0.6.0.dist-info/RECORD +32 -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/py.typed +0 -0
  42. mm_eth/rpc_async.py +0 -170
  43. mm_eth/vault.py +0 -38
  44. mm_eth-0.5.16.dist-info/RECORD +0 -49
  45. {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/WHEEL +0 -0
  46. {mm_eth-0.5.16.dist-info → mm_eth-0.6.0.dist-info}/entry_points.txt +0 -0
mm_eth/cli/validators.py CHANGED
@@ -1,11 +1,18 @@
1
1
  from collections.abc import Callable
2
2
 
3
+ import eth_utils
3
4
  from mm_crypto_utils import AddressToPrivate, ConfigValidators, Transfer
4
5
 
5
- from mm_eth.account import address_from_private, is_address
6
- from mm_eth.constants import SUFFIX_DECIMALS
6
+ from mm_eth import account
7
7
 
8
- from . import calcs
8
+ SUFFIX_DECIMALS = {"eth": 18, "gwei": 9, "ether": 18}
9
+
10
+
11
+ def address_from_private(private_key: str) -> str:
12
+ res = account.private_to_address(private_key)
13
+ if res.is_err():
14
+ raise ValueError("invalid private key")
15
+ return res.unwrap().lower()
9
16
 
10
17
 
11
18
  class Validators(ConfigValidators):
@@ -23,7 +30,7 @@ class Validators(ConfigValidators):
23
30
 
24
31
  @staticmethod
25
32
  def eth_transfers() -> Callable[[str], list[Transfer]]:
26
- return ConfigValidators.transfers(is_address, to_lower=True)
33
+ return ConfigValidators.transfers(is_address=eth_utils.is_address, to_lower=True)
27
34
 
28
35
  @staticmethod
29
36
  def eth_private_keys() -> Callable[[str], AddressToPrivate]:
@@ -31,18 +38,8 @@ class Validators(ConfigValidators):
31
38
 
32
39
  @staticmethod
33
40
  def eth_address() -> Callable[[str], str]:
34
- return ConfigValidators.address(is_address, to_lower=True)
41
+ return ConfigValidators.address(eth_utils.is_address, to_lower=True)
35
42
 
36
43
  @staticmethod
37
44
  def eth_addresses(unique: bool) -> Callable[[str], list[str]]:
38
- return ConfigValidators.addresses(unique, to_lower=True, is_address=is_address)
39
-
40
-
41
- def is_valid_calc_function_args(value: str | None) -> bool:
42
- if value is None:
43
- return True
44
- try:
45
- calcs.calc_function_args(value)
46
- return True # noqa: TRY300
47
- except ValueError:
48
- return False
45
+ return ConfigValidators.addresses(unique, to_lower=True, is_address=eth_utils.is_address)
mm_eth/converters.py ADDED
@@ -0,0 +1,56 @@
1
+ from decimal import Decimal, localcontext
2
+ from typing import cast
3
+
4
+ import eth_utils
5
+ from web3.types import Wei
6
+
7
+
8
+ def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
9
+ if isinstance(value, int):
10
+ return Wei(value)
11
+ if isinstance(value, Decimal):
12
+ if value != value.to_integral_value():
13
+ raise ValueError(f"value must be integral number: {value}")
14
+ return Wei(int(value))
15
+ if isinstance(value, str):
16
+ value = value.lower().replace(" ", "").strip()
17
+ if value.endswith("gwei"):
18
+ value = value.replace("gwei", "")
19
+ return Wei(int(Decimal(value) * 1000000000))
20
+ if value.endswith("ether"):
21
+ value = value.replace("ether", "")
22
+ return Wei(int(Decimal(value) * 1000000000000000000))
23
+ if value.endswith("eth"):
24
+ value = value.replace("eth", "")
25
+ return Wei(int(Decimal(value) * 1000000000000000000))
26
+ if value.endswith("t"):
27
+ if decimals is None:
28
+ raise ValueError("t without decimals")
29
+ value = value.removesuffix("t")
30
+ return Wei(int(Decimal(value) * 10**decimals))
31
+ if value.isdigit():
32
+ return Wei(int(value))
33
+ raise ValueError("wrong value " + value)
34
+
35
+ raise ValueError(f"value has a wrong type: {type(value)}")
36
+
37
+
38
+ def from_wei(value: int, unit: str, round_ndigits: int | None = None, decimals: int | None = None) -> Decimal:
39
+ if value == 0:
40
+ return Decimal(0)
41
+
42
+ is_negative = value < 0
43
+ if unit.lower() == "eth":
44
+ unit = "ether"
45
+
46
+ if unit.lower() == "t":
47
+ if decimals is None:
48
+ raise ValueError("t without decimals")
49
+ with localcontext() as ctx:
50
+ ctx.prec = 999
51
+ res = Decimal(value=abs(value), context=ctx) / Decimal(10**decimals)
52
+ else:
53
+ res = cast(Decimal, eth_utils.from_wei(abs(value), unit))
54
+ if round_ndigits is not None:
55
+ res = round(res, ndigits=round_ndigits)
56
+ return -1 * res if is_negative else res
mm_eth/erc20.py CHANGED
@@ -1,203 +1,18 @@
1
- from __future__ import annotations
2
-
3
- import string
4
- from collections.abc import Sequence
5
- from dataclasses import dataclass
6
-
7
1
  import eth_abi
8
2
  import eth_utils
9
3
  from eth_typing import HexStr
10
- from eth_utils import to_checksum_address, to_hex
11
- from mm_crypto_utils import Nodes, Proxies
12
- from mm_std import Err, Ok, Result
13
4
 
14
- from mm_eth import async_rpc, rpc
15
- from mm_eth.rpc import Log
16
- from mm_eth.tx import SignedTx, sign_legacy_tx, sign_tx
17
- from mm_eth.utils import hex_str_to_int, hex_to_bytes, log_topic_to_address
5
+ from mm_eth import tx
6
+ from mm_eth.tx import SignedTx
18
7
 
19
8
  TRANSFER_METHOD = "0xa9059cbb"
20
9
  TRANSFER_TOPIC = HexStr("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef")
21
10
 
22
11
 
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
- 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
-
94
- def get_name(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
95
- return rpc.rpc_call(
96
- nodes=rpc_urls,
97
- method="eth_call",
98
- params=[{"to": address, "data": "0x06fdde03"}, "latest"],
99
- timeout=timeout,
100
- proxies=proxies,
101
- attempts=attempts,
102
- ).and_then(_normalize_str)
103
-
104
-
105
- def get_symbol(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[str]:
106
- return rpc.rpc_call(
107
- nodes=rpc_urls,
108
- method="eth_call",
109
- params=[{"to": address, "data": "0x95d89b41"}, "latest"],
110
- timeout=timeout,
111
- proxies=proxies,
112
- attempts=attempts,
113
- ).and_then(_normalize_str)
114
-
115
-
116
- def get_decimals(rpc_urls: Nodes, address: str, timeout: int = 10, proxies: Proxies = None, attempts: int = 1) -> Result[int]:
117
- res = rpc.rpc_call(
118
- nodes=rpc_urls,
119
- method="eth_call",
120
- params=[{"to": address, "data": "0x313ce567"}, "latest"],
121
- timeout=timeout,
122
- proxies=proxies,
123
- attempts=attempts,
124
- )
125
- if isinstance(res, Err):
126
- return res
127
-
128
- try:
129
- if res.ok == "0x":
130
- return Err("no_decimals", data=res.data)
131
-
132
- result = eth_utils.to_int(hexstr=res.ok[0:66]) if len(res.ok) > 66 else eth_utils.to_int(hexstr=res.ok)
133
- return Ok(result, data=res.data)
134
-
135
- except Exception as e:
136
- return Err(f"exception: {e}", data=res.data)
137
-
138
-
139
12
  def encode_transfer_input_data(recipient: str, value: int) -> str:
140
- recipient = to_checksum_address(recipient)
141
- input_data = hex_to_bytes(TRANSFER_METHOD) + eth_abi.encode(["address", "uint256"], [recipient, value])
142
- return to_hex(input_data)
143
-
144
-
145
- def transfer_token_legacy(
146
- *,
147
- rpc_urls: str | Sequence[str],
148
- token_address: str,
149
- recipient_address: str,
150
- value: int,
151
- nonce: int,
152
- gas_price: int,
153
- gas_limit: int,
154
- private_key: str,
155
- chain_id: int,
156
- timeout: int = 10,
157
- proxies: Proxies = None,
158
- attempts: int = 1,
159
- ) -> Result[str]:
160
- input_data = encode_transfer_input_data(recipient_address, value)
161
- signed_tx = sign_legacy_tx(
162
- nonce=nonce,
163
- gas_price=gas_price,
164
- gas=gas_limit,
165
- private_key=private_key,
166
- chain_id=chain_id,
167
- data=input_data,
168
- to=token_address,
169
- )
170
- return rpc.eth_send_raw_transaction(rpc_urls, signed_tx.raw_tx, timeout=timeout, proxies=proxies, attempts=attempts)
171
-
172
-
173
- def transfer_token(
174
- *,
175
- rpc_urls: Nodes,
176
- token_address: str,
177
- recipient_address: str,
178
- value: int,
179
- nonce: int,
180
- max_fee_per_gas: int,
181
- max_priority_fee_per_gas: int,
182
- gas_limit: int,
183
- private_key: str,
184
- chain_id: int,
185
- timeout: int = 10,
186
- proxies: Proxies = None,
187
- attempts: int = 1,
188
- ) -> Result[str]:
189
- input_data = encode_transfer_input_data(recipient_address, value)
190
- signed_tx = sign_tx(
191
- nonce=nonce,
192
- max_fee_per_gas=max_fee_per_gas,
193
- max_priority_fee_per_gas=max_priority_fee_per_gas,
194
- gas=gas_limit,
195
- private_key=private_key,
196
- chain_id=chain_id,
197
- data=input_data,
198
- to=token_address,
199
- )
200
- return rpc.eth_send_raw_transaction(rpc_urls, signed_tx.raw_tx, timeout=timeout, proxies=proxies, attempts=attempts)
13
+ recipient = eth_utils.to_checksum_address(recipient)
14
+ input_data = eth_utils.to_bytes(hexstr=HexStr(TRANSFER_METHOD)) + eth_abi.encode(["address", "uint256"], [recipient, value])
15
+ return eth_utils.to_hex(input_data)
201
16
 
202
17
 
203
18
  def sign_transfer_tx(
@@ -213,7 +28,7 @@ def sign_transfer_tx(
213
28
  chain_id: int,
214
29
  ) -> SignedTx:
215
30
  input_data = encode_transfer_input_data(recipient_address, value)
216
- return sign_tx(
31
+ return tx.sign_tx(
217
32
  nonce=nonce,
218
33
  max_fee_per_gas=max_fee_per_gas,
219
34
  max_priority_fee_per_gas=max_priority_fee_per_gas,
@@ -223,36 +38,3 @@ def sign_transfer_tx(
223
38
  data=input_data,
224
39
  to=token_address,
225
40
  )
226
-
227
-
228
- def get_transfer_event_logs(
229
- rpc_urls: Nodes,
230
- token_address: str | None,
231
- from_block: int,
232
- to_block: int,
233
- timeout: int = 10,
234
- proxies: Proxies = None,
235
- attempts: int = 1,
236
- ) -> Result[list[TransferEventLog]]:
237
- res = rpc.eth_get_logs(
238
- rpc_urls,
239
- address=token_address,
240
- topics=[TRANSFER_TOPIC],
241
- from_block=from_block,
242
- to_block=to_block,
243
- timeout=timeout,
244
- proxies=proxies,
245
- attempts=attempts,
246
- )
247
- if isinstance(res, Err):
248
- return res
249
- result: list[TransferEventLog] = []
250
- for log in res.ok:
251
- event_log_res = TransferEventLog.from_log(log)
252
- if isinstance(event_log_res, Ok):
253
- result.append(event_log_res.ok)
254
- return Ok(result, data=res.data)
255
-
256
-
257
- def _normalize_str(value: str) -> Ok[str]:
258
- return Ok("".join(filter(lambda x: x in string.printable, eth_utils.to_text(hexstr=value))).strip())
mm_eth/retry.py ADDED
@@ -0,0 +1,143 @@
1
+ from typing import Any, Literal
2
+
3
+ from eth_typing import BlockIdentifier
4
+ from mm_crypto_utils import Nodes, Proxies, retry_with_node_and_proxy
5
+ from mm_std import Result
6
+ from web3.types import TxReceipt
7
+
8
+ from mm_eth import rpc
9
+
10
+ TIMEOUT = 5.0
11
+
12
+
13
+ # -- start eth rpc calls --
14
+
15
+
16
+ async def eth_block_number(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
17
+ return await retry_with_node_and_proxy(
18
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_block_number(node, timeout, proxy)
19
+ )
20
+
21
+
22
+ async def eth_get_balance(retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = TIMEOUT) -> Result[int]:
23
+ return await retry_with_node_and_proxy(
24
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_get_balance(node, address, timeout, proxy)
25
+ )
26
+
27
+
28
+ async def eth_chain_id(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
29
+ return await retry_with_node_and_proxy(retries, nodes, proxies, lambda node, proxy: rpc.eth_chain_id(node, timeout, proxy))
30
+
31
+
32
+ async def eth_get_block_by_number(
33
+ retries: int,
34
+ nodes: Nodes,
35
+ proxies: Proxies,
36
+ *,
37
+ block_number: BlockIdentifier,
38
+ full_transaction: bool = False,
39
+ timeout: float = TIMEOUT,
40
+ ) -> Result[dict[str, Any]]:
41
+ return await retry_with_node_and_proxy(
42
+ retries,
43
+ nodes,
44
+ proxies,
45
+ lambda node, proxy: rpc.eth_get_block_by_number(node, block_number, full_transaction, timeout, proxy),
46
+ )
47
+
48
+
49
+ async def eth_get_transaction_count(
50
+ retries: int, nodes: Nodes, proxies: Proxies, *, address: str, timeout: float = TIMEOUT
51
+ ) -> Result[int]:
52
+ return await retry_with_node_and_proxy(
53
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_get_transaction_count(node, address, timeout, proxy)
54
+ )
55
+
56
+
57
+ async def eth_estimate_gas(
58
+ retries: int,
59
+ nodes: Nodes,
60
+ proxies: Proxies,
61
+ *,
62
+ from_: str,
63
+ to: str | None = None,
64
+ value: int | None = 0,
65
+ data: str | None = None,
66
+ type_: Literal["0x0", "0x2"] | None = None,
67
+ timeout: float = TIMEOUT,
68
+ ) -> Result[int]:
69
+ return await retry_with_node_and_proxy(
70
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_estimate_gas(node, from_, to, value, data, type_, timeout, proxy)
71
+ )
72
+
73
+
74
+ async def eth_send_raw_transaction(
75
+ retries: int, nodes: Nodes, proxies: Proxies, *, raw_tx: str, timeout: float = TIMEOUT
76
+ ) -> Result[str]:
77
+ return await retry_with_node_and_proxy(
78
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_send_raw_transaction(node, raw_tx, timeout, proxy)
79
+ )
80
+
81
+
82
+ async def eth_get_transaction_receipt(
83
+ retries: int, nodes: Nodes, proxies: Proxies, *, tx_hash: str, timeout: float = TIMEOUT
84
+ ) -> Result[TxReceipt]:
85
+ return await retry_with_node_and_proxy(
86
+ retries, nodes, proxies, lambda node, proxy: rpc.eth_get_transaction_receipt(node, tx_hash, timeout, proxy)
87
+ )
88
+
89
+
90
+ # -- end eth rpc calls --
91
+
92
+
93
+ # -- start erc20 rpc calls --
94
+
95
+
96
+ async def erc20_balance(
97
+ retries: int, nodes: Nodes, proxies: Proxies, *, token: str, wallet: str, timeout: float = TIMEOUT
98
+ ) -> Result[int]:
99
+ return await retry_with_node_and_proxy(
100
+ retries,
101
+ nodes,
102
+ proxies,
103
+ lambda node, proxy: rpc.erc20_balance(node, token=token, wallet=wallet, proxy=proxy, timeout=timeout),
104
+ )
105
+
106
+
107
+ async def erc20_name(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[str]:
108
+ return await retry_with_node_and_proxy(
109
+ retries, nodes, proxies, lambda node, proxy: rpc.erc20_name(node, token, timeout, proxy)
110
+ )
111
+
112
+
113
+ async def erc20_symbol(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[str]:
114
+ return await retry_with_node_and_proxy(
115
+ retries, nodes, proxies, lambda node, proxy: rpc.erc20_symbol(node, token, timeout, proxy)
116
+ )
117
+
118
+
119
+ async def erc20_decimals(retries: int, nodes: Nodes, proxies: Proxies, *, token: str, timeout: float = TIMEOUT) -> Result[int]:
120
+ return await retry_with_node_and_proxy(
121
+ retries, nodes, proxies, lambda node, proxy: rpc.erc20_decimals(node, token, timeout, proxy)
122
+ )
123
+
124
+
125
+ # -- end erc20 rpc calls --
126
+
127
+
128
+ # -- start other --
129
+
130
+
131
+ async def get_base_fee_per_gas(retries: int, nodes: Nodes, proxies: Proxies, *, timeout: float = TIMEOUT) -> Result[int]:
132
+ return await retry_with_node_and_proxy(
133
+ retries, nodes, proxies, lambda node, proxy: rpc.get_base_fee_per_gas(node, timeout, proxy)
134
+ )
135
+
136
+
137
+ async def get_tx_status(retries: int, nodes: Nodes, proxies: Proxies, *, tx_hash: str, timeout: float = TIMEOUT) -> Result[int]:
138
+ return await retry_with_node_and_proxy(
139
+ retries, nodes, proxies, lambda node, proxy: rpc.get_tx_status(node, tx_hash, timeout, proxy)
140
+ )
141
+
142
+
143
+ # -- end other --