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
@@ -1,24 +1,23 @@
1
+ import asyncio
1
2
  import sys
2
3
  import time
3
4
  from pathlib import Path
4
- from typing import Annotated, Self
5
+ from typing import Annotated, Literal, Self, cast
5
6
 
6
7
  import mm_crypto_utils
7
8
  from loguru import logger
8
- from mm_crypto_utils import AddressToPrivate, Transfer
9
- from mm_std import BaseConfig, Err, fatal, utc_now
9
+ from mm_crypto_utils import AddressToPrivate, Transfer, VarInt
10
+ from mm_std import BaseConfig, utc_now
10
11
  from pydantic import AfterValidator, BeforeValidator, Field, model_validator
11
12
  from rich.console import Console
12
13
  from rich.live import Live
13
14
  from rich.table import Table
14
15
 
15
- from mm_eth import erc20, rpc
16
- from mm_eth.cli import calcs, cli_utils, rpc_helpers
17
- from mm_eth.cli.calcs import calc_eth_expression
16
+ from mm_eth import converters, erc20, retry, tx
17
+ from mm_eth.cli import calcs, cli_utils
18
18
  from mm_eth.cli.cli_utils import BaseConfigParams
19
19
  from mm_eth.cli.validators import Validators
20
- from mm_eth.tx import sign_tx
21
- from mm_eth.utils import from_wei_str
20
+ from mm_eth.converters import from_wei
22
21
 
23
22
 
24
23
  class Config(BaseConfig):
@@ -45,8 +44,8 @@ class Config(BaseConfig):
45
44
  def from_addresses(self) -> list[str]:
46
45
  return [r.from_address for r in self.transfers]
47
46
 
48
- @model_validator(mode="after")
49
- def final_validator(self) -> Self:
47
+ @model_validator(mode="after") # type:ignore[misc]
48
+ async def final_validator(self) -> Self:
50
49
  if not self.private_keys.contains_all_addresses(self.from_addresses):
51
50
  raise ValueError("private keys are not set for all addresses")
52
51
 
@@ -69,10 +68,9 @@ class Config(BaseConfig):
69
68
  Validators.valid_eth_expression()(self.value_min_limit)
70
69
 
71
70
  if self.token:
72
- res = erc20.get_decimals(self.nodes, self.token, proxies=self.proxies, attempts=5)
73
- if isinstance(res, Err):
74
- fatal(f"can't get token decimals: {res.err}")
75
- self.token_decimals = res.ok
71
+ self.token_decimals = (await retry.erc20_decimals(5, self.nodes, self.proxies, token=self.token)).unwrap_or_exit(
72
+ "can't get token decimals"
73
+ )
76
74
 
77
75
  return self
78
76
 
@@ -85,119 +83,206 @@ class TransferCmdParams(BaseConfigParams):
85
83
  emulate: bool
86
84
 
87
85
 
88
- def run(cmd_params: TransferCmdParams) -> None:
89
- config = Config.read_toml_config_or_exit(cmd_params.config_path)
90
- if cmd_params.print_config:
91
- cli_utils.print_config(config, exclude={"private_keys"}, count=None if cmd_params.debug else {"proxies"})
92
- sys.exit(0)
93
-
94
- rpc_helpers.check_nodes_for_chain_id(config.nodes, config.chain_id)
86
+ async def run(params: TransferCmdParams) -> None:
87
+ config = await Config.read_toml_config_or_exit_async(params.config_path)
88
+ if params.print_config:
89
+ config.print_and_exit(exclude={"private_keys"})
95
90
 
96
- if cmd_params.print_transfers:
91
+ if params.print_transfers:
97
92
  _print_transfers(config)
98
93
  sys.exit(0)
99
94
 
100
- if cmd_params.print_balances:
101
- _print_balances(config)
95
+ await cli_utils.check_nodes_for_chain_id(config.nodes, config.chain_id)
96
+
97
+ if params.print_balances:
98
+ await _print_balances(config)
102
99
  sys.exit(0)
103
100
 
104
- _run_transfers(config, cmd_params)
101
+ await _run_transfers(config, params)
105
102
 
106
103
 
107
- def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
104
+ async def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
108
105
  mm_crypto_utils.init_logger(cmd_params.debug, config.log_debug, config.log_info)
109
106
  logger.info(f"transfer {cmd_params.config_path}: started at {utc_now()} UTC")
110
107
  logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
111
108
  for i, transfer in enumerate(config.transfers):
112
- _transfer(transfer, config, cmd_params)
109
+ await _transfer(transfer, config, cmd_params)
113
110
  if config.delay is not None and i < len(config.transfers) - 1:
114
111
  delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
115
112
  logger.info(f"delay {delay_value} seconds")
116
113
  if not cmd_params.emulate:
117
- time.sleep(float(delay_value))
114
+ await asyncio.sleep(float(delay_value))
118
115
  logger.info(f"finished at {utc_now()} UTC")
119
116
 
120
117
 
121
- def _transfer(t: Transfer, config: Config, cmd_params: TransferCmdParams) -> None:
122
- nonce = rpc_helpers.get_nonce(config.nodes, t.from_address, t.log_prefix)
118
+ async def _get_nonce(t: Transfer, config: Config) -> int | None:
119
+ res = await retry.eth_get_transaction_count(5, config.nodes, config.proxies, address=t.from_address)
120
+ if res.is_err():
121
+ logger.error(f"{t.log_prefix}: nonce error: {res.unwrap_error()}")
122
+ return None
123
+ logger.debug(f"{t.log_prefix}: nonce={res.unwrap()}")
124
+ return res.unwrap()
125
+
126
+
127
+ async def _calc_max_fee(t: Transfer, config: Config) -> int | None:
128
+ if "base_fee" in config.max_fee.lower():
129
+ base_fee_res = await retry.get_base_fee_per_gas(5, config.nodes, config.proxies)
130
+ if base_fee_res.is_err():
131
+ logger.error(f"{t.log_prefix}: base_fee error: {base_fee_res.unwrap_error()}")
132
+ return None
133
+ logger.debug(f"{t.log_prefix}: base_fee={base_fee_res.unwrap()}")
134
+ return calcs.calc_eth_expression(config.max_fee, VarInt("base_fee", base_fee_res.unwrap()))
135
+ return calcs.calc_eth_expression(config.max_fee)
136
+
137
+
138
+ def check_max_fee_limit(t: Transfer, config: Config, max_fee: int) -> bool:
139
+ if config.max_fee_limit:
140
+ max_fee_limit = calcs.calc_eth_expression(config.max_fee_limit)
141
+ if max_fee > max_fee_limit:
142
+ msg = f"{t.log_prefix}: max_fee limit exceeded"
143
+ msg += f", max_fee={from_wei(max_fee, 'gwei')}gwei, max_fee_limit={from_wei(max_fee_limit, 'gwei')}gwei"
144
+ logger.error(msg)
145
+ return False
146
+ return True
147
+
148
+
149
+ async def _calc_gas(t: Transfer, config: Config) -> int | None:
150
+ var = None
151
+ if "estimate" in config.gas.lower():
152
+ if config.token:
153
+ res = await retry.eth_estimate_gas(
154
+ 5,
155
+ config.nodes,
156
+ config.proxies,
157
+ from_=t.from_address,
158
+ to=config.token,
159
+ data=erc20.encode_transfer_input_data(t.to_address, 12345),
160
+ )
161
+ else:
162
+ res = await retry.eth_estimate_gas(
163
+ 5, config.nodes, config.proxies, from_=t.from_address, to=t.to_address, value=12345
164
+ )
165
+ if res.is_err():
166
+ logger.error(f"{t.log_prefix}: gas estimate error: {res.unwrap_error()}")
167
+ return None
168
+ logger.debug(f"{t.log_prefix}: gas estimate={res.unwrap()}")
169
+ var = VarInt("estimate", res.unwrap())
170
+ return calcs.calc_eth_expression(config.gas, var)
171
+
172
+
173
+ async def _calc_eth_value(t: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
174
+ value_expression = t.value.lower()
175
+ var = None
176
+ if "balance" in value_expression:
177
+ res = await retry.eth_get_balance(5, config.nodes, config.proxies, address=t.from_address)
178
+ if res.is_err():
179
+ logger.error(f"{t.log_prefix}: balance error: {res.unwrap_error()}")
180
+ return None
181
+ logger.debug(f"{t.log_prefix}: balance={res.unwrap()}")
182
+ var = VarInt("balance", res.unwrap())
183
+
184
+ value = calcs.calc_eth_expression(value_expression, var)
185
+ if "balance" in value_expression.lower():
186
+ value = value - gas * max_fee
187
+ return value
188
+
189
+
190
+ async def _calc_token_value(t: Transfer, config: Config) -> int | None:
191
+ value_expression = t.value.lower()
192
+ var = None
193
+ if "balance" in value_expression:
194
+ res = await retry.erc20_balance(5, config.nodes, config.proxies, token=cast(str, config.token), wallet=t.from_address)
195
+ if res.is_err():
196
+ logger.error(f"{t.log_prefix}: balance error: {res.unwrap_error()}")
197
+ return None
198
+ logger.debug(f"{t.log_prefix}: balance={res.unwrap()}")
199
+ var = VarInt("balance", res.unwrap())
200
+ return calcs.calc_token_expression(value_expression, config.token_decimals, var)
201
+
202
+
203
+ async def _calc_value(t: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
204
+ if config.token:
205
+ return await _calc_token_value(t, config)
206
+ return await _calc_eth_value(t, max_fee, gas, config)
207
+
208
+
209
+ def _check_value_min_limit(t: Transfer, value: int, config: Config) -> bool:
210
+ """Returns False if the transfer should be skipped."""
211
+ if config.value_min_limit:
212
+ if config.token:
213
+ value_min_limit = calcs.calc_token_expression(config.value_min_limit, config.token_decimals)
214
+ else:
215
+ value_min_limit = calcs.calc_eth_expression(config.value_min_limit)
216
+ if value < value_min_limit:
217
+ logger.info(f"{t.log_prefix}: value<value_min_limit, value={_value_with_suffix(value, config)}")
218
+ return True
219
+
220
+
221
+ async def _transfer(t: Transfer, config: Config, cmd_params: TransferCmdParams) -> None:
222
+ nonce = await _get_nonce(t, config)
123
223
  if nonce is None:
124
224
  return
125
225
 
126
- max_fee = rpc_helpers.calc_max_fee(config.nodes, config.max_fee, t.log_prefix)
226
+ max_fee = await _calc_max_fee(t, config)
127
227
  if max_fee is None:
128
228
  return
129
229
 
130
- if rpc_helpers.is_max_fee_limit_exceeded(max_fee, config.max_fee_limit, t.log_prefix):
230
+ if not check_max_fee_limit(t, config, max_fee):
131
231
  return
132
232
 
133
- gas = _calc_gas(t, config)
233
+ gas = await _calc_gas(t, config)
134
234
  if gas is None:
135
235
  return
136
236
 
137
- value = _calc_value(t, max_fee=max_fee, gas=gas, config=config)
237
+ value = await _calc_value(t, max_fee, gas, config)
138
238
  if value is None:
139
239
  return
140
240
 
141
241
  if not _check_value_min_limit(t, value, config):
142
242
  return
143
243
 
144
- priority_fee = calc_eth_expression(config.priority_fee)
244
+ priority_fee = calcs.calc_eth_expression(config.priority_fee)
145
245
 
146
246
  # emulate?
147
247
  if cmd_params.emulate:
148
248
  msg = f"{t.log_prefix}: emulate,"
149
249
  msg += f" value={_value_with_suffix(value, config)},"
150
- msg += f" max_fee={from_wei_str(max_fee, 'gwei', config.round_ndigits)},"
151
- msg += f" priority_fee={from_wei_str(priority_fee, 'gwei', config.round_ndigits)},"
250
+ msg += f" max_fee={from_wei(max_fee, 'gwei', config.round_ndigits)}gwei,"
251
+ msg += f" priority_fee={from_wei(priority_fee, 'gwei', config.round_ndigits)}gwei,"
152
252
  msg += f" gas={gas}"
153
253
  logger.info(msg)
154
254
  return
155
255
 
156
- tx_hash = _send_tx(transfer=t, nonce=nonce, max_fee=max_fee, priority_fee=priority_fee, gas=gas, value=value, config=config)
256
+ tx_hash = await _send_tx(
257
+ transfer=t, nonce=nonce, max_fee=max_fee, priority_fee=priority_fee, gas=gas, value=value, config=config
258
+ )
157
259
  if tx_hash is None:
158
260
  return
159
261
 
160
262
  status = "UNKNOWN"
161
263
  if not cmd_params.skip_receipt:
162
- logger.debug(f"{t.log_prefix}: waiting for receipt, tx_hash={tx_hash}")
163
- status = cli_utils.wait_tx_status(config.nodes, config.proxies, tx_hash, config.wait_tx_timeout)
264
+ status = await wait_tx_status(t, tx_hash, config)
164
265
 
165
266
  logger.info(f"{t.log_prefix}: tx_hash={tx_hash}, value={_value_with_suffix(value, config)}, status={status}")
166
267
 
167
268
 
168
- def _calc_value(transfer: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
169
- if config.token:
170
- return rpc_helpers.calc_erc20_value_for_address(
171
- nodes=config.nodes,
172
- value_expression=transfer.value,
173
- wallet_address=transfer.from_address,
174
- token_address=config.token,
175
- decimals=config.token_decimals,
176
- log_prefix=transfer.log_prefix,
177
- )
178
- return rpc_helpers.calc_eth_value_for_address(
179
- nodes=config.nodes,
180
- value_expression=transfer.value,
181
- address=transfer.from_address,
182
- gas=gas,
183
- max_fee=max_fee,
184
- log_prefix=transfer.log_prefix,
185
- )
269
+ async def wait_tx_status(t: Transfer, tx_hash: str, config: Config) -> Literal["OK", "FAIL", "TIMEOUT"]:
270
+ logger.debug(f"{t.log_prefix}: waiting for receipt, tx_hash={tx_hash}")
271
+ started_at = time.perf_counter()
272
+ count = 0
273
+ while True:
274
+ res = await retry.get_tx_status(5, config.nodes, config.proxies, tx_hash=tx_hash)
275
+ logger.debug(f"{t.log_prefix}: status={res.value_or_error()}")
276
+ if res.is_ok():
277
+ return "OK" if res.unwrap() == 1 else "FAIL"
186
278
 
187
-
188
- def _check_value_min_limit(transfer: Transfer, value: int, config: Config) -> bool:
189
- """Returns False if the transfer should be skipped."""
190
- if config.value_min_limit:
191
- if config.token:
192
- value_min_limit = calcs.calc_token_expression(config.value_min_limit, config.token_decimals)
193
- else:
194
- value_min_limit = calcs.calc_eth_expression(config.value_min_limit)
195
- if value < value_min_limit:
196
- logger.info(f"{transfer.log_prefix}: value<value_min_limit, value={_value_with_suffix(value, config)}")
197
- return True
279
+ await asyncio.sleep(1)
280
+ count += 1
281
+ if time.perf_counter() - started_at > config.wait_tx_timeout:
282
+ return "TIMEOUT"
198
283
 
199
284
 
200
- def _send_tx(
285
+ async def _send_tx(
201
286
  *, transfer: Transfer, nonce: int, max_fee: int, priority_fee: int, gas: int, value: int, config: Config
202
287
  ) -> str | None:
203
288
  debug_tx_params = {
@@ -224,7 +309,7 @@ def _send_tx(
224
309
  recipient_address=transfer.to_address,
225
310
  )
226
311
  else:
227
- signed_tx = sign_tx(
312
+ signed_tx = tx.sign_tx(
228
313
  nonce=nonce,
229
314
  max_fee_per_gas=max_fee,
230
315
  max_priority_fee_per_gas=priority_fee,
@@ -234,31 +319,13 @@ def _send_tx(
234
319
  value=value,
235
320
  to=transfer.to_address,
236
321
  )
237
- res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
238
- if isinstance(res, Err):
239
- logger.info(f"{transfer.log_prefix}: tx error {res.err}")
322
+ res = await retry.eth_send_raw_transaction(5, config.nodes, config.proxies, raw_tx=signed_tx.raw_tx)
323
+ if res.is_err():
324
+ logger.error(f"{transfer.log_prefix}: send tx error={res.unwrap_error()}")
240
325
  return None
241
- return res.ok
242
-
326
+ logger.debug(f"{transfer.log_prefix}: tx_hash={res.unwrap()}")
243
327
 
244
- def _calc_gas(transfer: Transfer, config: Config) -> int | None:
245
- if config.token:
246
- return rpc_helpers.calc_gas(
247
- nodes=config.nodes,
248
- gas_expression=config.gas,
249
- from_address=transfer.from_address,
250
- to_address=config.token,
251
- data=erc20.encode_transfer_input_data(transfer.to_address, 1234),
252
- log_prefix=transfer.log_prefix,
253
- )
254
- return rpc_helpers.calc_gas(
255
- nodes=config.nodes,
256
- gas_expression=config.gas,
257
- from_address=transfer.from_address,
258
- to_address=transfer.to_address,
259
- value=123,
260
- log_prefix=transfer.log_prefix,
261
- )
328
+ return res.unwrap()
262
329
 
263
330
 
264
331
  def _print_transfers(config: Config) -> None:
@@ -269,7 +336,7 @@ def _print_transfers(config: Config) -> None:
269
336
  console.print(table)
270
337
 
271
338
 
272
- def _print_balances(config: Config) -> None:
339
+ async def _print_balances(config: Config) -> None:
273
340
  if config.token:
274
341
  headers = ["n", "from_address", "nonce", "eth", "t", "to_address", "nonce", "eth", "t"]
275
342
  else:
@@ -277,18 +344,52 @@ def _print_balances(config: Config) -> None:
277
344
  table = Table(*headers, title="balances")
278
345
  with Live(table, refresh_per_second=0.5):
279
346
  for count, transfer in enumerate(config.transfers):
280
- from_nonce = _get_nonce_str(transfer.from_address, config)
281
- to_nonce = _get_nonce_str(transfer.to_address, config)
347
+ from_nonce = (
348
+ await retry.eth_get_transaction_count(5, config.nodes, config.proxies, address=transfer.from_address)
349
+ ).value_or_error()
350
+ to_nonce = (
351
+ await retry.eth_get_transaction_count(5, config.nodes, config.proxies, address=transfer.to_address)
352
+ ).value_or_error()
353
+
354
+ from_eth_balance = (
355
+ (await retry.eth_get_balance(5, config.nodes, config.proxies, address=transfer.from_address))
356
+ .map(lambda value: converters.from_wei(value, "ether", config.round_ndigits))
357
+ .value_or_error()
358
+ )
359
+
360
+ to_eth_balance = (
361
+ (await retry.eth_get_balance(5, config.nodes, config.proxies, address=transfer.to_address))
362
+ .map(lambda value: converters.from_wei(value, "ether", config.round_ndigits))
363
+ .value_or_error()
364
+ )
282
365
 
283
- from_eth_balance = _get_eth_balance_str(transfer.from_address, config)
284
- to_eth_balance = _get_eth_balance_str(transfer.to_address, config)
285
-
286
- from_token_balance = _get_token_balance_str(transfer.from_address, config) if config.token else ""
287
- to_token_balance = _get_token_balance_str(transfer.to_address, config) if config.token else ""
366
+ if config.token:
367
+ from_token_balance = (
368
+ (await retry.erc20_balance(5, config.nodes, config.proxies, token=config.token, wallet=transfer.from_address))
369
+ .map(
370
+ lambda value: converters.from_wei(
371
+ value, "t", decimals=config.token_decimals, round_ndigits=config.round_ndigits
372
+ )
373
+ )
374
+ .value_or_error()
375
+ )
376
+ to_token_balance = (
377
+ (await retry.erc20_balance(5, config.nodes, config.proxies, token=config.token, wallet=transfer.to_address))
378
+ .map(
379
+ lambda value: converters.from_wei(
380
+ value, "t", decimals=config.token_decimals, round_ndigits=config.round_ndigits
381
+ )
382
+ )
383
+ .value_or_error()
384
+ )
385
+ else:
386
+ from_token_balance = "" # nosec
387
+ to_token_balance = "" # nosec
288
388
 
289
389
  if config.token:
290
- table.add_row(
291
- str(count),
390
+ cli_utils.add_table_raw(
391
+ table,
392
+ count,
292
393
  transfer.from_address,
293
394
  from_nonce,
294
395
  from_eth_balance,
@@ -299,8 +400,9 @@ def _print_balances(config: Config) -> None:
299
400
  to_token_balance,
300
401
  )
301
402
  else:
302
- table.add_row(
303
- str(count),
403
+ cli_utils.add_table_raw(
404
+ table,
405
+ count,
304
406
  transfer.from_address,
305
407
  from_nonce,
306
408
  from_eth_balance,
@@ -310,27 +412,7 @@ def _print_balances(config: Config) -> None:
310
412
  )
311
413
 
312
414
 
313
- def _get_nonce_str(address: str, config: Config) -> str:
314
- return str(rpc.eth_get_transaction_count(config.nodes, address, proxies=config.proxies, attempts=5).ok_or_err())
315
-
316
-
317
- def _get_eth_balance_str(address: str, config: Config) -> str:
318
- return rpc.eth_get_balance(config.nodes, address, proxies=config.proxies, attempts=5).map_or_else(
319
- lambda err: err,
320
- lambda ok: from_wei_str(ok, "eth", config.round_ndigits),
321
- )
322
-
323
-
324
- def _get_token_balance_str(address: str, config: Config) -> str:
325
- if not config.token:
326
- raise ValueError("token is not set")
327
- return erc20.get_balance(config.nodes, config.token, address, proxies=config.proxies, attempts=5).map_or_else(
328
- lambda err: err,
329
- lambda ok: from_wei_str(ok, "t", decimals=config.token_decimals, round_ndigits=config.round_ndigits),
330
- )
331
-
332
-
333
415
  def _value_with_suffix(value: int, config: Config) -> str:
334
416
  if config.token:
335
- return from_wei_str(value, "t", config.round_ndigits, decimals=config.token_decimals)
336
- return from_wei_str(value, "eth", config.round_ndigits)
417
+ return f"{from_wei(value, 't', config.round_ndigits, decimals=config.token_decimals)}t"
418
+ return f"{from_wei(value, 'eth', config.round_ndigits)}eth"
@@ -4,7 +4,8 @@ from mm_eth import account
4
4
 
5
5
 
6
6
  def run(private_key: str) -> None:
7
- try:
8
- print_plain(account.private_to_address(private_key))
9
- except Exception as e:
10
- fatal(f"wrong private key: {e}")
7
+ res = account.private_to_address(private_key)
8
+ if res.is_ok():
9
+ print_plain(res.unwrap())
10
+ else:
11
+ fatal(f"invalid private key: '{private_key}'")
mm_eth/cli/rpc_helpers.py CHANGED
@@ -1,133 +1,50 @@
1
- import mm_crypto_utils
2
- from loguru import logger
3
- from mm_crypto_utils import VarInt, get_log_prefix
4
- from mm_std import Err, fatal
1
+ import logging
5
2
 
6
- from mm_eth import erc20, rpc
7
- from mm_eth.utils import from_wei_str
3
+ from mm_crypto_utils import Nodes, Proxies, VarInt
8
4
 
9
- from .calcs import calc_eth_expression
5
+ from mm_eth import retry
6
+ from mm_eth.cli import calcs
10
7
 
8
+ logger = logging.getLogger(__name__)
11
9
 
12
- def get_nonce(nodes: list[str] | str, address: str, log_prefix: str | None = None) -> int | None:
13
- res = rpc.eth_get_transaction_count(nodes, address, attempts=5)
10
+
11
+ async def get_nonce_with_logging(
12
+ log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies, *, address: str
13
+ ) -> int | None:
14
+ res = await retry.eth_get_transaction_count(retries, nodes, proxies, address=address)
14
15
  prefix = log_prefix or address
15
- logger.debug(f"{prefix}: nonce={res.ok_or_err()}")
16
- if isinstance(res, Err):
17
- logger.info(f"{prefix}: nonce error, {res.err}")
16
+ if res.is_err():
17
+ logger.error(f"{prefix}: nonce error: {res.unwrap_error()}")
18
18
  return None
19
- return res.ok
20
-
19
+ logger.debug(f"{prefix}: nonce={res.unwrap()}")
20
+ return res.unwrap()
21
21
 
22
- def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
23
- for node in nodes:
24
- res = rpc.eth_chain_id(node, timeout=7)
25
- if isinstance(res, Err):
26
- fatal(f"can't get chain_id for {node}, error={res.err}")
27
- if res.ok != chain_id:
28
- fatal(f"node {node} has a wrong chain_id: {res.ok}")
29
22
 
30
-
31
- def get_base_fee(nodes: list[str], log_prefix: str | None = None) -> int | None:
32
- res = rpc.get_base_fee_per_gas(nodes)
23
+ async def get_base_fee_with_logging(log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies) -> int | None:
33
24
  prefix = get_log_prefix(log_prefix)
34
- logger.debug(f"{prefix}base_fee={res.ok_or_err()}")
35
- if isinstance(res, Err):
36
- logger.info(f"{prefix}base_fee error, {res.err}")
25
+ res = await retry.get_base_fee_per_gas(retries, nodes, proxies)
26
+ if res.is_err():
27
+ logger.error(f"{prefix}base_fee error, {res.unwrap_error()}")
37
28
  return None
38
- return res.ok
39
-
40
-
41
- def calc_max_fee(nodes: list[str], max_fee_expression: str, log_prefix: str | None = None) -> int | None:
42
- if "base_fee" in max_fee_expression.lower():
43
- base_fee = get_base_fee(nodes, log_prefix)
44
- if base_fee is None:
45
- return None
46
- return calc_eth_expression(max_fee_expression, VarInt("base_fee", base_fee))
47
-
48
- return calc_eth_expression(max_fee_expression)
49
29
 
30
+ logger.debug(f"{prefix}base_fee={res.unwrap()}")
31
+ return res.unwrap()
50
32
 
51
- def is_max_fee_limit_exceeded(max_fee: int, max_fee_limit_expression: str | None, log_prefix: str | None = None) -> bool:
52
- if max_fee_limit_expression is None:
53
- return False
54
- max_limit_value = calc_eth_expression(max_fee_limit_expression)
55
- if max_fee > max_limit_value:
56
- prefix = get_log_prefix(log_prefix)
57
- logger.info(
58
- "{}max_fee_limit is exceeded, max_fee={}, max_fee_limit={}",
59
- prefix,
60
- from_wei_str(max_fee, "gwei"),
61
- from_wei_str(max_limit_value, "gwei"),
62
- )
63
- return True
64
- return False
65
33
 
66
-
67
- def calc_gas(
68
- *,
69
- nodes: list[str],
70
- gas_expression: str,
71
- from_address: str,
72
- to_address: str,
73
- value: int | None = None,
74
- data: str | None = None,
75
- log_prefix: str | None = None,
76
- ) -> int | None:
77
- var = None
78
- if "estimate" in gas_expression.lower():
79
- prefix = get_log_prefix(log_prefix)
80
- res = rpc.eth_estimate_gas(nodes, from_address, to_address, data=data, value=value, attempts=5)
81
- logger.debug(f"{prefix}gas_estimate={res.ok_or_err()}")
82
- if isinstance(res, Err):
83
- logger.info(f"{prefix}estimate_gas error, {res.err}")
84
- return None
85
- var = VarInt("estimate", res.ok)
86
- return calc_eth_expression(gas_expression, var)
87
-
88
-
89
- def calc_eth_value_for_address(
90
- *,
91
- nodes: list[str],
92
- value_expression: str,
93
- address: str,
94
- gas: int,
95
- max_fee: int,
96
- log_prefix: str | None = None,
34
+ async def calc_max_fee_with_logging(
35
+ log_prefix: str | None, retries: int, nodes: Nodes, proxies: Proxies, *, max_fee_expression: str
97
36
  ) -> int | None:
98
- var = None
99
- if "balance" in value_expression.lower():
100
- prefix = get_log_prefix(log_prefix)
101
- res = rpc.eth_get_balance(nodes, address, attempts=5)
102
- logger.debug(f"{prefix}balance={res.ok_or_err()}")
103
- if isinstance(res, Err):
104
- logger.info(f"{prefix}balance error, {res.err}")
37
+ if "base_fee" in max_fee_expression.lower():
38
+ base_fee = await get_base_fee_with_logging(log_prefix, retries, nodes, proxies)
39
+ if base_fee is None:
105
40
  return None
106
- var = VarInt("balance", res.ok)
41
+ return calcs.calc_eth_expression(max_fee_expression, VarInt("base_fee", base_fee))
107
42
 
108
- value = calc_eth_expression(value_expression, var)
109
- if "balance" in value_expression.lower():
110
- value = value - gas * max_fee
111
- return value
43
+ return calcs.calc_eth_expression(max_fee_expression)
112
44
 
113
45
 
114
- def calc_erc20_value_for_address(
115
- *,
116
- nodes: list[str],
117
- value_expression: str,
118
- wallet_address: str,
119
- token_address: str,
120
- decimals: int,
121
- log_prefix: str | None = None,
122
- ) -> int | None:
123
- value_expression = value_expression.lower()
124
- var = None
125
- if "balance" in value_expression:
126
- prefix = get_log_prefix(log_prefix)
127
- res = erc20.get_balance(nodes, token_address, wallet_address, attempts=5)
128
- logger.debug(f"{prefix}balance={res.ok_or_err()}")
129
- if isinstance(res, Err):
130
- logger.info(f"{prefix}balance error, {res.err}")
131
- return None
132
- var = VarInt("balance", res.ok)
133
- return mm_crypto_utils.calc_int_expression(value_expression, var, suffix_decimals={"t": decimals})
46
+ def get_log_prefix(log_prefix: str | None) -> str:
47
+ prefix = log_prefix or ""
48
+ if prefix:
49
+ prefix += ": "
50
+ return prefix