mm-eth 0.1.5__py3-none-any.whl → 0.2.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 +3 -3
  2. mm_eth/account.py +1 -1
  3. mm_eth/anvil.py +2 -2
  4. mm_eth/erc20.py +8 -7
  5. mm_eth/rpc.py +8 -8
  6. mm_eth/solc.py +2 -3
  7. mm_eth/tx.py +3 -5
  8. mm_eth/utils.py +11 -16
  9. mm_eth/vault.py +5 -5
  10. mm_eth-0.2.1.dist-info/METADATA +7 -0
  11. mm_eth-0.2.1.dist-info/RECORD +18 -0
  12. {mm_eth-0.1.5.dist-info → mm_eth-0.2.1.dist-info}/WHEEL +1 -1
  13. mm_eth/abi/zksync.json +0 -2092
  14. mm_eth/cli/__init__.py +0 -0
  15. mm_eth/cli/calcs.py +0 -88
  16. mm_eth/cli/cli.py +0 -237
  17. mm_eth/cli/cli_helpers.py +0 -195
  18. mm_eth/cli/cli_utils.py +0 -148
  19. mm_eth/cli/cmd/__init__.py +0 -0
  20. mm_eth/cli/cmd/balance_cmd.py +0 -59
  21. mm_eth/cli/cmd/balances_cmd.py +0 -121
  22. mm_eth/cli/cmd/call_contract_cmd.py +0 -44
  23. mm_eth/cli/cmd/config_example_cmd.py +0 -9
  24. mm_eth/cli/cmd/deploy_cmd.py +0 -41
  25. mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
  26. mm_eth/cli/cmd/mnemonic_cmd.py +0 -27
  27. mm_eth/cli/cmd/node_cmd.py +0 -47
  28. mm_eth/cli/cmd/private_key_cmd.py +0 -10
  29. mm_eth/cli/cmd/rpc_cmd.py +0 -81
  30. mm_eth/cli/cmd/send_contract_cmd.py +0 -247
  31. mm_eth/cli/cmd/solc_cmd.py +0 -25
  32. mm_eth/cli/cmd/token_cmd.py +0 -29
  33. mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -275
  34. mm_eth/cli/cmd/transfer_eth_cmd.py +0 -252
  35. mm_eth/cli/cmd/vault_cmd.py +0 -16
  36. mm_eth/cli/config_examples/balances.yml +0 -15
  37. mm_eth/cli/config_examples/call_contract.yml +0 -5
  38. mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
  39. mm_eth/cli/config_examples/transfer_eth.yml +0 -24
  40. mm_eth/cli/validators.py +0 -84
  41. mm_eth/services/__init__.py +0 -0
  42. mm_eth/zksync.py +0 -203
  43. mm_eth-0.1.5.dist-info/METADATA +0 -9
  44. mm_eth-0.1.5.dist-info/RECORD +0 -49
  45. mm_eth-0.1.5.dist-info/entry_points.txt +0 -2
mm_eth/cli/__init__.py DELETED
File without changes
mm_eth/cli/calcs.py DELETED
@@ -1,88 +0,0 @@
1
- import random
2
- from decimal import Decimal
3
-
4
- from mm_std.random_ import random_decimal
5
- from mm_std.str import split_on_plus_minus_tokens
6
-
7
- from mm_eth.utils import to_wei
8
-
9
-
10
- def calc_var_wei_value(value: str, *, var_name: str = "var", var_value: int | None = None, decimals: int | None = None) -> int:
11
- if not isinstance(value, str):
12
- raise ValueError(f"value is not str: {value}")
13
- try:
14
- var_name = var_name.lower()
15
- result = 0
16
- for item in split_on_plus_minus_tokens(value.lower()):
17
- operator = item[0]
18
- item = item[1:]
19
- if item.isdigit():
20
- item_value = int(item)
21
- elif item.endswith("eth"):
22
- item = item.removesuffix("eth")
23
- item_value = int(Decimal(item) * 10**18)
24
- elif item.endswith("ether"):
25
- item = item.removesuffix("ether")
26
- item_value = int(Decimal(item) * 10**18)
27
- elif item.endswith("gwei"):
28
- item = item.removesuffix("gwei")
29
- item_value = int(Decimal(item) * 10**9)
30
- elif item.endswith("t"):
31
- if decimals is None:
32
- raise ValueError("t without decimals")
33
- item = item.removesuffix("t")
34
- item_value = int(Decimal(item) * 10**decimals)
35
- elif item.endswith(var_name):
36
- if var_value is None:
37
- raise ValueError("base value is not set")
38
- item = item.removesuffix(var_name)
39
- k = Decimal(item) if item else Decimal(1)
40
- item_value = int(k * var_value)
41
- elif item.startswith("random(") and item.endswith(")"):
42
- item = item.lstrip("random(").rstrip(")")
43
- arr = item.split(",")
44
- if len(arr) != 2:
45
- raise ValueError(f"wrong value, random part: {value}")
46
- from_value = to_wei(arr[0], decimals=decimals)
47
- to_value = to_wei(arr[1], decimals=decimals)
48
- if from_value > to_value:
49
- raise ValueError(f"wrong value, random part: {value}")
50
- item_value = random.randint(from_value, to_value)
51
- else:
52
- raise ValueError(f"wrong value: {value}")
53
-
54
- if operator == "+":
55
- result += item_value
56
- if operator == "-":
57
- result -= item_value
58
-
59
- return result
60
- except Exception as err:
61
- raise ValueError(f"wrong value: {value}, error={err}") from err
62
-
63
-
64
- def calc_decimal_value(value: str) -> Decimal:
65
- value = value.lower().strip()
66
- if value.startswith("random(") and value.endswith(")"):
67
- arr = value.lstrip("random(").rstrip(")").split(",")
68
- if len(arr) != 2:
69
- raise ValueError(f"wrong value, random part: {value}")
70
- from_value = Decimal(arr[0])
71
- to_value = Decimal(arr[1])
72
- if from_value > to_value:
73
- raise ValueError(f"wrong value, random part: {value}")
74
- return random_decimal(from_value, to_value)
75
- return Decimal(value)
76
-
77
-
78
- def calc_function_args(value: str) -> str:
79
- while True:
80
- if "random(" not in value:
81
- return value
82
- start_index = value.index("random(")
83
- stop_index = value.index(")", start_index)
84
- random_range = [int(v.strip()) for v in value[start_index + 7 : stop_index].split(",")]
85
- if len(random_range) != 2:
86
- raise ValueError("wrong random(from,to) template")
87
- rand_value = str(random.randint(random_range[0], random_range[1]))
88
- value = value[0:start_index] + rand_value + value[stop_index + 1 :]
mm_eth/cli/cli.py DELETED
@@ -1,237 +0,0 @@
1
- from enum import Enum
2
- from typing import Annotated, Union
3
-
4
- import typer
5
- from mm_std import PrintFormat, print_plain
6
-
7
- from mm_eth.cli import cli_helpers
8
- from mm_eth.cli.cmd import (
9
- balance_cmd,
10
- balances_cmd,
11
- call_contract_cmd,
12
- config_example_cmd,
13
- deploy_cmd,
14
- encode_input_data_cmd,
15
- mnemonic_cmd,
16
- node_cmd,
17
- private_key_cmd,
18
- rpc_cmd,
19
- send_contract_cmd,
20
- solc_cmd,
21
- token_cmd,
22
- transfer_erc20_cmd,
23
- transfer_eth_cmd,
24
- vault_cmd,
25
- )
26
-
27
- app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
28
-
29
- wallet_app = typer.Typer(no_args_is_help=True, help="Wallet commands: generate mnemonic, private to address")
30
- app.add_typer(wallet_app, name="wallet")
31
- app.add_typer(wallet_app, name="w", hidden=True)
32
-
33
-
34
- class ConfigExample(str, Enum):
35
- TRANSFER_ETH = "transfer-eth"
36
- TRANSFER_ERC20 = "transfer-erc20"
37
- BALANCES = "balances"
38
- CALL_CONTRACT = "call-contract"
39
-
40
-
41
- @app.command(name="balance", help="Gen account balance")
42
- def balance_command(
43
- wallet_address: Annotated[str, typer.Argument()],
44
- token_address: Annotated[Union[str | None], typer.Option("--token", "-t")] = None, # noqa UP007
45
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "", # nosec
46
- wei: bool = typer.Option(False, "--wei", "-w", help="Print balances in wei units"),
47
- print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
48
- ) -> None:
49
- balance_cmd.run(rpc_url, wallet_address, token_address, wei, print_format)
50
-
51
-
52
- @app.command(name="token", help="Get token info")
53
- def token_command(
54
- token_address: Annotated[str, typer.Argument()],
55
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL")] = "",
56
- ) -> None:
57
- token_cmd.run(rpc_url, token_address)
58
-
59
-
60
- @app.command(name="node", help="Check RPC url")
61
- def node_command(
62
- urls: Annotated[list[str], typer.Argument()],
63
- print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.TABLE,
64
- proxy: Annotated[str | None, typer.Option("--proxy", "-p", help="Proxy")] = None,
65
- ) -> None:
66
- node_cmd.run(urls, print_format, proxy)
67
-
68
-
69
- @wallet_app.command(name="mnemonic", help="Generate eth accounts based on a mnemonic")
70
- def mnemonic_command( # nosec
71
- mnemonic: Annotated[str, typer.Option("--mnemonic", "-m")] = "",
72
- passphrase: Annotated[str, typer.Option("--passphrase", "-pass")] = "",
73
- print_path: bool = typer.Option(False, "--print_path"),
74
- path_prefix: Annotated[str, typer.Option("--path")] = "m/44'/60'/0'/0",
75
- limit: int = typer.Option(10, "--limit", "-l"),
76
- save_file: str = typer.Option("", "--save", "-s", help="Save private keys to a file"),
77
- ) -> None:
78
- mnemonic_cmd.run(
79
- mnemonic,
80
- passphrase=passphrase,
81
- print_path=print_path,
82
- limit=limit,
83
- path_prefix=path_prefix,
84
- save_file=save_file,
85
- )
86
-
87
-
88
- @wallet_app.command(name="private-key", help="Print an address for a private key")
89
- def private_key_command(private_key: str) -> None:
90
- private_key_cmd.run(private_key)
91
-
92
-
93
- @app.command(name="solc", help="Compile a solidity file")
94
- def solc_command(
95
- contract_path: str,
96
- tmp_dir: str = "/tmp", # nosec
97
- print_format: Annotated[PrintFormat, typer.Option("--format", "-f", help="Print format")] = PrintFormat.PLAIN,
98
- ) -> None:
99
- solc_cmd.run(contract_path, tmp_dir, print_format)
100
-
101
-
102
- @app.command(name="vault", help="Save private keys to vault")
103
- def vault_command(
104
- keys_url: Annotated[
105
- str,
106
- typer.Option(..., "--url", "-u", help="Url to keys, for example https://vault.site.com:8200/v1/kv/keys1"),
107
- ],
108
- vault_token: Annotated[
109
- str,
110
- typer.Option(..., "--token", "-t", prompt=True, hide_input=True, prompt_required=False, help="A vault token"),
111
- ],
112
- keys_file: Annotated[str, typer.Option(..., "--file", "-f", help="Path to a file with private keys")],
113
- ) -> None:
114
- vault_cmd.run(keys_url, vault_token, keys_file)
115
-
116
-
117
- @app.command(name="rpc", help="Call a JSON-RPC method")
118
- def rpc_command(
119
- method: Annotated[str, typer.Argument()] = "",
120
- params: Annotated[str, typer.Argument()] = "[]",
121
- rpc_url: Annotated[str, typer.Option("--url", "-u", envvar="ETH_RPC_URL", help="RPC node url")] = "",
122
- hex2dec: Annotated[bool, typer.Option("--hex2dec", "-d", help="Print result in decimal value")] = False,
123
- ) -> None:
124
- rpc_cmd.run(rpc_url, method, params, hex2dec)
125
-
126
-
127
- @app.command(name="transfer-eth", help="Transfer eth / base token from one or many accounts")
128
- def transfer_eth_command(
129
- config_path: str,
130
- print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
131
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
132
- emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
133
- no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
134
- debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
135
- ) -> None:
136
- transfer_eth_cmd.run(
137
- config_path,
138
- print_balances=print_balances,
139
- print_config=print_config,
140
- debug=debug,
141
- no_receipt=no_receipt,
142
- emulate=emulate,
143
- )
144
-
145
-
146
- @app.command(name="transfer-erc20", help="Transfer ERC20 token from one or many accounts")
147
- def transfer_erc20_command(
148
- config_path: str,
149
- print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
150
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
151
- emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
152
- no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
153
- debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
154
- ) -> None:
155
- transfer_erc20_cmd.run(
156
- config_path,
157
- print_balances=print_balances,
158
- print_config=print_config,
159
- debug=debug,
160
- no_receipt=no_receipt,
161
- emulate=emulate,
162
- )
163
-
164
-
165
- @app.command(name="send-contract", help="Send transactions to a contract")
166
- def send_contract_command(
167
- config_path: str,
168
- print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
169
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
170
- emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
171
- no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
172
- debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
173
- ) -> None:
174
- send_contract_cmd.run(
175
- config_path,
176
- print_balances=print_balances,
177
- print_config=print_config,
178
- debug=debug,
179
- no_receipt=no_receipt,
180
- emulate=emulate,
181
- )
182
-
183
-
184
- @app.command(name="balances", help="Print base and ERC20 token balances")
185
- def balances_command(
186
- config_path: str,
187
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
188
- nonce: bool = typer.Option(False, "--nonce", "-n", help="Print nonce also"),
189
- wei: bool = typer.Option(False, "--wei", "-w", help="Show balances in WEI"),
190
- ) -> None:
191
- balances_cmd.run(config_path, print_config, wei, nonce)
192
-
193
-
194
- @app.command(name="call-contract", help="Call a method on a contract")
195
- def call_contract_command(
196
- config_path: str,
197
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
198
- ) -> None:
199
- call_contract_cmd.run(config_path, print_config)
200
-
201
-
202
- @app.command(name="deploy", help="Deploy a smart contract onchain")
203
- def deploy_command(
204
- config_path: str,
205
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config only and exit"),
206
- ) -> None:
207
- deploy_cmd.run(config_path, print_config=print_config)
208
-
209
-
210
- @app.command(name="config-example", help="Print an example of config for a command")
211
- def config_example_command(command: Annotated[ConfigExample, typer.Argument()]) -> None:
212
- config_example_cmd.run(command)
213
-
214
-
215
- @app.command(name="encode-input-data", help="Encode input data by a function signature")
216
- def encode_input_data(
217
- funtion_signature: str = typer.Argument(help="Function signature, for example: transfer(address, uint256)"),
218
- args_str: str = typer.Argument(
219
- help="""Function arguments, as an array string. For example: '["0xA659FB44eB5d4bFaC1074Cb426b1b11D58D28308", 123]' """,
220
- ),
221
- ) -> None:
222
- encode_input_data_cmd.run(funtion_signature, args_str)
223
-
224
-
225
- def version_callback(value: bool) -> None:
226
- if value:
227
- print_plain(f"mm-eth version: {cli_helpers.get_version()}")
228
- raise typer.Exit()
229
-
230
-
231
- @app.callback()
232
- def main(_version: bool = typer.Option(None, "--version", callback=version_callback, is_eager=True)) -> None:
233
- pass
234
-
235
-
236
- if __name__ == "__main_":
237
- app()
mm_eth/cli/cli_helpers.py DELETED
@@ -1,195 +0,0 @@
1
- import importlib.metadata
2
- import sys
3
- from typing import NoReturn
4
-
5
- from loguru import logger
6
- from mm_std import Err
7
- from rich.live import Live
8
- from rich.table import Table
9
-
10
- from mm_eth import erc20, rpc
11
- from mm_eth.cli import calcs
12
- from mm_eth.utils import from_wei_str
13
-
14
-
15
- def get_nonce(nodes: list[str] | str, address: str, log_prefix: str | None = None) -> int | None:
16
- res = rpc.eth_get_transaction_count(nodes, address, attempts=5)
17
- prefix = log_prefix or address
18
- logger.debug(f"{prefix}: nonce={res.ok_or_err()}")
19
- if isinstance(res, Err):
20
- logger.info(f"{prefix}: nonce error, {res.err}")
21
- return None
22
- return res.ok
23
-
24
-
25
- def get_base_fee(nodes: list[str], log_prefix: str | None = None) -> int | None:
26
- res = rpc.get_base_fee_per_gas(nodes)
27
- prefix = _get_prefix(log_prefix)
28
- logger.debug(f"{prefix}base_fee={res.ok_or_err()}")
29
- if isinstance(res, Err):
30
- logger.info(f"{prefix}base_fee error, {res.err}")
31
- return None
32
- return res.ok
33
-
34
-
35
- def calc_max_fee_per_gas(nodes: list[str], max_fee_per_gas: str, log_prefix: str | None = None) -> int | None:
36
- if "base" in max_fee_per_gas.lower():
37
- base_fee = get_base_fee(nodes, log_prefix)
38
- if base_fee is None:
39
- return None
40
- return calcs.calc_var_wei_value(max_fee_per_gas, var_name="base", var_value=base_fee)
41
- return calcs.calc_var_wei_value(max_fee_per_gas)
42
-
43
-
44
- def is_max_fee_per_gas_limit_exceeded(
45
- max_fee_per_gas: int,
46
- max_fee_per_gas_limit: str | None,
47
- log_prefix: str | None = None,
48
- ) -> bool:
49
- if max_fee_per_gas_limit is None:
50
- return False
51
- max_fee_per_gas_limit_value = calcs.calc_var_wei_value(max_fee_per_gas_limit)
52
- if max_fee_per_gas > max_fee_per_gas_limit_value:
53
- prefix = _get_prefix(log_prefix)
54
- logger.info(
55
- "{}max_fee_per_gas_limit is exeeded, max_fee_per_gas={}, max_fee_per_gas_limit={}",
56
- prefix,
57
- from_wei_str(max_fee_per_gas, "gwei"),
58
- from_wei_str(max_fee_per_gas_limit_value, "gwei"),
59
- )
60
- return True
61
- return False
62
-
63
-
64
- def is_value_less_min_limit(
65
- value_min_limit: str | None,
66
- value: int,
67
- value_unit: str,
68
- decimals: int | None = None,
69
- log_prefix: str | None = None,
70
- ) -> bool:
71
- if value_min_limit is None:
72
- return False
73
- if value < calcs.calc_var_wei_value(value_min_limit, decimals=decimals):
74
- prefix = _get_prefix(log_prefix)
75
- logger.info("{}value is less min limit, value={}", prefix, from_wei_str(value, value_unit, decimals=decimals))
76
- return True
77
- return False
78
-
79
-
80
- def calc_gas(
81
- *,
82
- nodes: list[str],
83
- gas: str,
84
- from_address: str,
85
- to_address: str,
86
- value: int | None = None,
87
- data: str | None = None,
88
- log_prefix: str | None = None,
89
- ) -> int | None:
90
- estimate_value = None
91
- if "estimate" in gas.lower():
92
- prefix = _get_prefix(log_prefix)
93
- res = rpc.eth_estimate_gas(nodes, from_address, to_address, data=data, value=value, attempts=5)
94
- logger.debug(f"{prefix}gas_estimate={res.ok_or_err()}")
95
- if isinstance(res, Err):
96
- logger.info(f"{prefix}estimate_gas error, {res.err}")
97
- return None
98
- estimate_value = res.ok
99
- return calcs.calc_var_wei_value(gas, var_name="estimate", var_value=estimate_value)
100
-
101
-
102
- def calc_eth_value(
103
- *,
104
- nodes: list[str],
105
- value_str: str,
106
- address: str,
107
- gas: int | None = None,
108
- max_fee_per_gas: int | None = None,
109
- log_prefix: str | None = None,
110
- ) -> int | None:
111
- balance_value = None
112
- if "balance" in value_str.lower():
113
- prefix = _get_prefix(log_prefix)
114
- res = rpc.eth_get_balance(nodes, address, attempts=5)
115
- logger.debug(f"{prefix}balance={res.ok_or_err()}")
116
- if isinstance(res, Err):
117
- logger.info(f"{prefix}balance error, {res.err}")
118
- return None
119
- balance_value = res.ok
120
- value = calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value)
121
- if "balance" in value_str.lower() and gas is not None and max_fee_per_gas is not None:
122
- value = value - gas * max_fee_per_gas
123
- return value
124
-
125
-
126
- def calc_erc20_value(
127
- *,
128
- nodes: list[str],
129
- value_str: str,
130
- wallet_address: str,
131
- token_address: str,
132
- decimals: int,
133
- log_prefix: str | None = None,
134
- ) -> int | None:
135
- value_str = value_str.lower()
136
- balance_value = None
137
- if "balance" in value_str:
138
- prefix = _get_prefix(log_prefix)
139
- res = erc20.get_balance(nodes, token_address, wallet_address, attempts=5)
140
- logger.debug(f"{prefix}balance={res.ok_or_err()}")
141
- if isinstance(res, Err):
142
- logger.info(f"{prefix}balance error, {res.err}")
143
- return None
144
- balance_value = res.ok
145
- return calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value, decimals=decimals)
146
-
147
-
148
- def print_balances(
149
- rpc_nodes: list[str],
150
- addresses: list[str],
151
- *,
152
- token_address: str | None = None,
153
- token_decimals: int | None = None,
154
- round_ndigits: int = 5,
155
- ) -> None:
156
- table = Table(title="balances")
157
- table.add_column("n")
158
- table.add_column("address")
159
- table.add_column("nonce")
160
- table.add_column("balance, eth")
161
- if token_address is not None and token_decimals is not None:
162
- table.add_column("token, t")
163
- with Live(table, refresh_per_second=0.5):
164
- count = 0
165
- for address in addresses:
166
- count += 1
167
- nonce = str(rpc.eth_get_transaction_count(rpc_nodes, address, attempts=5).ok_or_err())
168
- balance = rpc.eth_get_balance(rpc_nodes, address, attempts=5).map_or_else(
169
- lambda err: err,
170
- lambda ok: from_wei_str(ok, "eth", round_ndigits),
171
- )
172
- row: list[str] = [str(count), address, nonce, balance]
173
- if token_address is not None and token_decimals is not None:
174
- erc20_balance = erc20.get_balance(rpc_nodes, token_address, address, attempts=5).map_or_else(
175
- lambda err: err,
176
- lambda ok: from_wei_str(ok, "t", decimals=token_decimals, round_ndigits=round_ndigits),
177
- )
178
- row.append(erc20_balance)
179
- table.add_row(*row)
180
-
181
-
182
- def fatal(message: str) -> NoReturn:
183
- print(f"error: {message}", file=sys.stderr) # noqa: T201
184
- sys.exit(1)
185
-
186
-
187
- def get_version() -> str:
188
- return importlib.metadata.version("mm-eth")
189
-
190
-
191
- def _get_prefix(log_prefix: str | None) -> str:
192
- prefix = log_prefix or ""
193
- if prefix:
194
- prefix += ": "
195
- return prefix
mm_eth/cli/cli_utils.py DELETED
@@ -1,148 +0,0 @@
1
- import sys
2
- import time
3
- from pathlib import Path
4
-
5
- import eth_utils
6
- import yaml
7
- from loguru import logger
8
- from mm_std import Err, fatal, str_to_list, utc_now
9
- from pydantic import BaseModel, ConfigDict, ValidationError
10
- from rich.console import Console
11
- from rich.table import Table
12
-
13
- from mm_eth import account, rpc
14
- from mm_eth.account import is_private_key
15
- from mm_eth.cli import calcs
16
-
17
- # from typing import TypeVar
18
-
19
-
20
- class BaseConfig(BaseModel):
21
- model_config = ConfigDict(extra="forbid")
22
-
23
-
24
- def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
25
- for node in nodes:
26
- res = rpc.eth_chain_id(node, timeout=7)
27
- if isinstance(res, Err):
28
- fatal(f"can't get chain_id for {node}, error={res.err}")
29
- if res.ok != chain_id:
30
- fatal(f"node {node} has a wrong chain_id: {res.ok}")
31
-
32
-
33
- def check_private_keys(addresses: list[str], private_keys: dict[str, str]) -> None:
34
- for address in addresses:
35
- address = address.lower()
36
- if address not in private_keys:
37
- fatal(f"no private key for {address}")
38
- if account.private_to_address(private_keys[address]) != address:
39
- fatal(f"no private key for {address}")
40
-
41
-
42
- def delay(value: str | None) -> None:
43
- if value is None:
44
- return
45
- time.sleep(float(calcs.calc_decimal_value(value)))
46
-
47
-
48
- def read_config[T](config_cls: type[T], config_path: str) -> T:
49
- try:
50
- with open(config_path) as f:
51
- config = config_cls(**yaml.full_load(f))
52
- return config
53
- except ValidationError as err:
54
- table = Table(title="config validation errors")
55
- table.add_column("field")
56
- table.add_column("message")
57
- for e in err.errors():
58
- loc = e["loc"]
59
- field = str(loc[0]) if len(loc) > 0 else ""
60
- table.add_row(field, e["msg"])
61
- console = Console()
62
- console.print(table)
63
- exit(1)
64
-
65
-
66
- def log(log_path: str | None, *messages: object) -> None:
67
- if log_path is None:
68
- return
69
- message = ", ".join([str(m) for m in messages])
70
- message = f"{utc_now()}, {message}\n"
71
- with open(Path(log_path).expanduser(), "a") as f:
72
- f.write(message)
73
-
74
-
75
- def load_tx_addresses_from_str(v: str | None) -> list[tuple[str, str]]:
76
- result: list[tuple[str, str]] = []
77
- if v is None:
78
- return result
79
- for line in str_to_list(v, remove_comments=True):
80
- arr = line.split()
81
- if len(arr) == 2 and eth_utils.is_address(arr[0]) and eth_utils.is_address(arr[1]):
82
- result.append((arr[0].lower(), arr[1].lower()))
83
- return result
84
-
85
-
86
- def load_tx_addresses_from_files(addresses_from_file: str, addresses_to_file: str) -> list[tuple[str, str]]:
87
- from_file = Path(addresses_from_file).expanduser()
88
- to_file = Path(addresses_to_file).expanduser()
89
- if not from_file.is_file():
90
- raise ValueError(f"can't read addresses from 'addresses_from_file={addresses_from_file}")
91
- if not to_file.is_file():
92
- raise ValueError(f"can't read addresses from 'addresses_to_file={addresses_to_file}")
93
-
94
- # get addresses_from
95
- addresses_from = []
96
- for line in from_file.read_text().strip().split("\n"):
97
- if not eth_utils.is_address(line):
98
- raise ValueError(f"illigal address in addresses_from_file: {line}")
99
- addresses_from.append(line.lower())
100
-
101
- # get addresses_to
102
- addresses_to = []
103
- for line in to_file.read_text().strip().split("\n"):
104
- if not eth_utils.is_address(line):
105
- raise ValueError(f"illigal address in addresses_to_file: {line}")
106
- addresses_to.append(line.lower())
107
-
108
- if len(addresses_from) != len(addresses_to):
109
- raise ValueError("len(addresses_from) != len(addresses_to)")
110
-
111
- return list(zip(addresses_from, addresses_to, strict=True))
112
-
113
-
114
- def load_private_keys_from_file(private_keys_file: str) -> list[str]:
115
- result: list[str] = []
116
- for item in Path(private_keys_file).expanduser().read_text().split():
117
- if is_private_key(item):
118
- result.append(item)
119
- return result
120
-
121
-
122
- def init_logger(debug: bool, log_debug_file: str | None, log_info_file: str | None) -> None:
123
- if debug:
124
- level = "DEBUG"
125
- format_ = "<green>{time:YYYY-MM-DD HH:mm:ss}</green> <level>{level}</level> {message}"
126
- else:
127
- level = "INFO"
128
- format_ = "{message}"
129
-
130
- logger.remove()
131
- logger.add(sys.stderr, format=format_, colorize=True, level=level)
132
- if log_debug_file:
133
- logger.add(Path(log_debug_file).expanduser(), format="{time:YYYY-MM-DD HH:mm:ss} {level} {message}")
134
- if log_info_file:
135
- logger.add(Path(log_info_file).expanduser(), format="{message}", level="INFO")
136
-
137
-
138
- def public_rpc_url(url: str | None) -> str:
139
- if not url or url == "1":
140
- return "https://ethereum.publicnode.com"
141
- if url.startswith(("http://", "https://", "ws://", "wss://")):
142
- return url
143
-
144
- match url.lower():
145
- case "opbnb" | "204":
146
- return "https://opbnb-mainnet-rpc.bnbchain.org"
147
- case _:
148
- return url
File without changes