mm-eth 0.4.1__py3-none-any.whl → 0.5.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.
mm_eth/cli/cli.py CHANGED
@@ -17,7 +17,6 @@ from .cmd import (
17
17
  example_cmd,
18
18
  node_cmd,
19
19
  rpc_cmd,
20
- send_contract_cmd,
21
20
  solc_cmd,
22
21
  token_cmd,
23
22
  transfer_cmd,
@@ -27,7 +26,6 @@ from .cmd import (
27
26
  from .cmd.balances_cmd import BalancesCmdParams
28
27
  from .cmd.call_contract_cmd import CallContractCmdParams
29
28
  from .cmd.deploy_cmd import DeployCmdParams
30
- from .cmd.send_contract_cmd import SendContractCmdParams
31
29
  from .cmd.transfer_cmd import TransferCmdParams
32
30
  from .cmd.wallet import mnemonic_cmd, private_key_cmd
33
31
 
@@ -147,6 +145,7 @@ def tx_command(
147
145
  def transfer_command(
148
146
  config_path: Path,
149
147
  print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
148
+ print_transfers: bool = typer.Option(False, "--transfers", "-t", help="Print transfers (from, to, value) and exit"),
150
149
  print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
151
150
  emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
152
151
  skip_receipt: bool = typer.Option(False, "--skip-receipt", help="Don't wait for a tx receipt"),
@@ -156,6 +155,7 @@ def transfer_command(
156
155
  TransferCmdParams(
157
156
  config_path=config_path,
158
157
  print_balances=print_balances,
158
+ print_transfers=print_transfers,
159
159
  print_config=print_config,
160
160
  debug=debug,
161
161
  skip_receipt=skip_receipt,
@@ -164,25 +164,25 @@ def transfer_command(
164
164
  )
165
165
 
166
166
 
167
- @app.command(name="send-contract", help="Send transactions to a contract")
168
- def send_contract_command(
169
- config_path: Path,
170
- print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
171
- print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
172
- emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
173
- no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
174
- debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
175
- ) -> None:
176
- send_contract_cmd.run(
177
- SendContractCmdParams(
178
- config_path=config_path,
179
- print_balances=print_balances,
180
- print_config=print_config,
181
- debug=debug,
182
- no_receipt=no_receipt,
183
- emulate=emulate,
184
- )
185
- )
167
+ # @app.command(name="send-contract", help="Send transactions to a contract")
168
+ # def send_contract_command(
169
+ # config_path: Path,
170
+ # print_balances: bool = typer.Option(False, "--balances", "-b", help="Print balances and exit"),
171
+ # print_config: bool = typer.Option(False, "--config", "-c", help="Print config and exit"),
172
+ # emulate: bool = typer.Option(False, "--emulate", "-e", help="Emulate transaction posting"),
173
+ # no_receipt: bool = typer.Option(False, "--no-receipt", "-nr", help="Don't wait for a tx receipt"),
174
+ # debug: bool = typer.Option(False, "--debug", "-d", help="Print debug info"),
175
+ # ) -> None:
176
+ # send_contract_cmd.run(
177
+ # SendContractCmdParams(
178
+ # config_path=config_path,
179
+ # print_balances=print_balances,
180
+ # print_config=print_config,
181
+ # debug=debug,
182
+ # no_receipt=no_receipt,
183
+ # emulate=emulate,
184
+ # )
185
+ # )
186
186
 
187
187
 
188
188
  @app.command(name="balances", help="Print base and ERC20 token balances")
@@ -5,9 +5,10 @@ from typing import Annotated, Self
5
5
 
6
6
  import mm_crypto_utils
7
7
  from loguru import logger
8
- from mm_crypto_utils import AddressToPrivate, TxRoute
8
+ from mm_crypto_utils import AddressToPrivate, Transfer
9
9
  from mm_std import BaseConfig, Err, fatal, utc_now
10
10
  from pydantic import AfterValidator, BeforeValidator, Field, model_validator
11
+ from rich.console import Console
11
12
  from rich.live import Live
12
13
  from rich.table import Table
13
14
 
@@ -23,7 +24,7 @@ from mm_eth.utils import from_wei_str
23
24
  class Config(BaseConfig):
24
25
  nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
25
26
  chain_id: int
26
- routes: Annotated[list[TxRoute], BeforeValidator(Validators.eth_routes())]
27
+ transfers: Annotated[list[Transfer], BeforeValidator(Validators.eth_transfers())]
27
28
  private_keys: Annotated[AddressToPrivate, BeforeValidator(Validators.eth_private_keys())]
28
29
  token: Annotated[str | None, AfterValidator(Validators.eth_address())] = None # if None, then eth transfer
29
30
  token_decimals: int = -1
@@ -42,13 +43,20 @@ class Config(BaseConfig):
42
43
 
43
44
  @property
44
45
  def from_addresses(self) -> list[str]:
45
- return [r.from_address for r in self.routes]
46
+ return [r.from_address for r in self.transfers]
46
47
 
47
48
  @model_validator(mode="after")
48
49
  def final_validator(self) -> Self:
49
50
  if not self.private_keys.contains_all_addresses(self.from_addresses):
50
51
  raise ValueError("private keys are not set for all addresses")
51
52
 
53
+ for transfer in self.transfers: # If value is not set for a transfer, then set it to the global value of the config.
54
+ if not transfer.value:
55
+ transfer.value = self.value
56
+ for transfer in self.transfers: # Check all transfers have a value.
57
+ if not transfer.value:
58
+ raise ValueError(f"{transfer.log_prefix}: value is not set")
59
+
52
60
  if self.token:
53
61
  Validators.valid_token_expression("balance")(self.value)
54
62
  if self.value_min_limit:
@@ -69,6 +77,7 @@ class Config(BaseConfig):
69
77
 
70
78
  class TransferCmdParams(BaseConfigParams):
71
79
  print_balances: bool
80
+ print_transfers: bool
72
81
  debug: bool
73
82
  skip_receipt: bool
74
83
  emulate: bool
@@ -82,6 +91,10 @@ def run(cmd_params: TransferCmdParams) -> None:
82
91
 
83
92
  rpc_helpers.check_nodes_for_chain_id(config.nodes, config.chain_id)
84
93
 
94
+ if cmd_params.print_transfers:
95
+ _print_transfers(config)
96
+ sys.exit(0)
97
+
85
98
  if cmd_params.print_balances:
86
99
  _print_balances(config)
87
100
  sys.exit(0)
@@ -93,9 +106,9 @@ def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
93
106
  mm_crypto_utils.init_logger(cmd_params.debug, config.log_debug, config.log_info)
94
107
  logger.info(f"transfer {cmd_params.config_path}: started at {utc_now()} UTC")
95
108
  logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
96
- for i, route in enumerate(config.routes):
97
- _transfer(route, config, cmd_params)
98
- if config.delay is not None and i < len(config.routes) - 1:
109
+ for i, transfer in enumerate(config.transfers):
110
+ _transfer(transfer, config, cmd_params)
111
+ if config.delay is not None and i < len(config.transfers) - 1:
99
112
  delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
100
113
  logger.info(f"delay {delay_value} seconds")
101
114
  if not cmd_params.emulate:
@@ -103,34 +116,34 @@ def _run_transfers(config: Config, cmd_params: TransferCmdParams) -> None:
103
116
  logger.info(f"finished at {utc_now()} UTC")
104
117
 
105
118
 
106
- def _transfer(route: TxRoute, config: Config, cmd_params: TransferCmdParams) -> None:
107
- nonce = rpc_helpers.get_nonce(config.nodes, route.from_address, route.log_prefix)
119
+ def _transfer(t: Transfer, config: Config, cmd_params: TransferCmdParams) -> None:
120
+ nonce = rpc_helpers.get_nonce(config.nodes, t.from_address, t.log_prefix)
108
121
  if nonce is None:
109
122
  return
110
123
 
111
- max_fee = rpc_helpers.calc_max_fee(config.nodes, config.max_fee, route.log_prefix)
124
+ max_fee = rpc_helpers.calc_max_fee(config.nodes, config.max_fee, t.log_prefix)
112
125
  if max_fee is None:
113
126
  return
114
127
 
115
- if rpc_helpers.is_max_fee_limit_exceeded(max_fee, config.max_fee_limit, route.log_prefix):
128
+ if rpc_helpers.is_max_fee_limit_exceeded(max_fee, config.max_fee_limit, t.log_prefix):
116
129
  return
117
130
 
118
- gas = _calc_gas(route, config)
131
+ gas = _calc_gas(t, config)
119
132
  if gas is None:
120
133
  return
121
134
 
122
- value = _calc_value(route, max_fee=max_fee, gas=gas, config=config)
135
+ value = _calc_value(t, max_fee=max_fee, gas=gas, config=config)
123
136
  if value is None:
124
137
  return
125
138
 
126
- if not _check_value_min_limit(route, value, config):
139
+ if not _check_value_min_limit(t, value, config):
127
140
  return
128
141
 
129
142
  priority_fee = calc_eth_expression(config.priority_fee)
130
143
 
131
144
  # emulate?
132
145
  if cmd_params.emulate:
133
- msg = f"{route.log_prefix}: emulate,"
146
+ msg = f"{t.log_prefix}: emulate,"
134
147
  msg += f" value={_value_with_suffix(value, config)},"
135
148
  msg += f" max_fee={from_wei_str(max_fee, 'gwei', config.round_ndigits)},"
136
149
  msg += f" priority_fee={from_wei_str(priority_fee, 'gwei', config.round_ndigits)},"
@@ -138,39 +151,39 @@ def _transfer(route: TxRoute, config: Config, cmd_params: TransferCmdParams) ->
138
151
  logger.info(msg)
139
152
  return
140
153
 
141
- tx_hash = _send_tx(route=route, nonce=nonce, max_fee=max_fee, priority_fee=priority_fee, gas=gas, value=value, config=config)
154
+ tx_hash = _send_tx(transfer=t, nonce=nonce, max_fee=max_fee, priority_fee=priority_fee, gas=gas, value=value, config=config)
142
155
  if tx_hash is None:
143
156
  return
144
157
 
145
158
  status = "UNKNOWN"
146
159
  if not cmd_params.skip_receipt:
147
- logger.debug(f"{route.log_prefix}: waiting for receipt, tx_hash={tx_hash}")
160
+ logger.debug(f"{t.log_prefix}: waiting for receipt, tx_hash={tx_hash}")
148
161
  status = cli_utils.wait_tx_status(config.nodes, config.proxies, tx_hash, config.wait_tx_timeout)
149
162
 
150
- logger.info(f"{route.log_prefix}: tx_hash={tx_hash}, value={_value_with_suffix(value, config)}, status={status}")
163
+ logger.info(f"{t.log_prefix}: tx_hash={tx_hash}, value={_value_with_suffix(value, config)}, status={status}")
151
164
 
152
165
 
153
- def _calc_value(route: TxRoute, max_fee: int, gas: int, config: Config) -> int | None:
166
+ def _calc_value(transfer: Transfer, max_fee: int, gas: int, config: Config) -> int | None:
154
167
  if config.token:
155
168
  return rpc_helpers.calc_erc20_value_for_address(
156
169
  nodes=config.nodes,
157
- value_expression=config.value,
158
- wallet_address=route.from_address,
170
+ value_expression=transfer.value,
171
+ wallet_address=transfer.from_address,
159
172
  token_address=config.token,
160
173
  decimals=config.token_decimals,
161
- log_prefix=route.log_prefix,
174
+ log_prefix=transfer.log_prefix,
162
175
  )
163
176
  return rpc_helpers.calc_eth_value_for_address(
164
177
  nodes=config.nodes,
165
- value_expression=config.value,
166
- address=route.from_address,
178
+ value_expression=transfer.value,
179
+ address=transfer.from_address,
167
180
  gas=gas,
168
181
  max_fee=max_fee,
169
- log_prefix=route.log_prefix,
182
+ log_prefix=transfer.log_prefix,
170
183
  )
171
184
 
172
185
 
173
- def _check_value_min_limit(route: TxRoute, value: int, config: Config) -> bool:
186
+ def _check_value_min_limit(transfer: Transfer, value: int, config: Config) -> bool:
174
187
  """Returns False if the transfer should be skipped."""
175
188
  if config.value_min_limit:
176
189
  if config.token:
@@ -178,21 +191,23 @@ def _check_value_min_limit(route: TxRoute, value: int, config: Config) -> bool:
178
191
  else:
179
192
  value_min_limit = calcs.calc_eth_expression(config.value_min_limit)
180
193
  if value < value_min_limit:
181
- logger.info(f"{route.log_prefix}: value<value_min_limit, value={_value_with_suffix(value, config)}")
194
+ logger.info(f"{transfer.log_prefix}: value<value_min_limit, value={_value_with_suffix(value, config)}")
182
195
  return True
183
196
 
184
197
 
185
- def _send_tx(*, route: TxRoute, nonce: int, max_fee: int, priority_fee: int, gas: int, value: int, config: Config) -> str | None:
198
+ def _send_tx(
199
+ *, transfer: Transfer, nonce: int, max_fee: int, priority_fee: int, gas: int, value: int, config: Config
200
+ ) -> str | None:
186
201
  debug_tx_params = {
187
202
  "nonce": nonce,
188
203
  "max_fee": max_fee,
189
204
  "priority_fee": priority_fee,
190
205
  "gas": gas,
191
206
  "value": value,
192
- "to": route.to_address,
207
+ "to": transfer.to_address,
193
208
  "chain_id": config.chain_id,
194
209
  }
195
- logger.debug(f"{route.log_prefix}: tx_params={debug_tx_params}")
210
+ logger.debug(f"{transfer.log_prefix}: tx_params={debug_tx_params}")
196
211
 
197
212
  if config.token:
198
213
  signed_tx = erc20.sign_transfer_tx(
@@ -200,11 +215,11 @@ def _send_tx(*, route: TxRoute, nonce: int, max_fee: int, priority_fee: int, gas
200
215
  max_fee_per_gas=max_fee,
201
216
  max_priority_fee_per_gas=priority_fee,
202
217
  gas_limit=gas,
203
- private_key=config.private_keys[route.from_address],
218
+ private_key=config.private_keys[transfer.from_address],
204
219
  chain_id=config.chain_id,
205
220
  value=value,
206
221
  token_address=config.token,
207
- recipient_address=route.to_address,
222
+ recipient_address=transfer.to_address,
208
223
  )
209
224
  else:
210
225
  signed_tx = sign_tx(
@@ -212,38 +227,46 @@ def _send_tx(*, route: TxRoute, nonce: int, max_fee: int, priority_fee: int, gas
212
227
  max_fee_per_gas=max_fee,
213
228
  max_priority_fee_per_gas=priority_fee,
214
229
  gas=gas,
215
- private_key=config.private_keys[route.from_address],
230
+ private_key=config.private_keys[transfer.from_address],
216
231
  chain_id=config.chain_id,
217
232
  value=value,
218
- to=route.to_address,
233
+ to=transfer.to_address,
219
234
  )
220
235
  res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
221
236
  if isinstance(res, Err):
222
- logger.info(f"{route.log_prefix}: tx error {res.err}")
237
+ logger.info(f"{transfer.log_prefix}: tx error {res.err}")
223
238
  return None
224
239
  return res.ok
225
240
 
226
241
 
227
- def _calc_gas(route: TxRoute, config: Config) -> int | None:
242
+ def _calc_gas(transfer: Transfer, config: Config) -> int | None:
228
243
  if config.token:
229
244
  return rpc_helpers.calc_gas(
230
245
  nodes=config.nodes,
231
246
  gas_expression=config.gas,
232
- from_address=route.from_address,
247
+ from_address=transfer.from_address,
233
248
  to_address=config.token,
234
- data=erc20.encode_transfer_input_data(route.to_address, 1234),
235
- log_prefix=route.log_prefix,
249
+ data=erc20.encode_transfer_input_data(transfer.to_address, 1234),
250
+ log_prefix=transfer.log_prefix,
236
251
  )
237
252
  return rpc_helpers.calc_gas(
238
253
  nodes=config.nodes,
239
254
  gas_expression=config.gas,
240
- from_address=route.from_address,
241
- to_address=route.to_address,
255
+ from_address=transfer.from_address,
256
+ to_address=transfer.to_address,
242
257
  value=123,
243
- log_prefix=route.log_prefix,
258
+ log_prefix=transfer.log_prefix,
244
259
  )
245
260
 
246
261
 
262
+ def _print_transfers(config: Config) -> None:
263
+ table = Table("n", "from_address", "to_address", "value", title="transfers")
264
+ for count, transfer in enumerate(config.transfers, start=1):
265
+ table.add_row(str(count), transfer.from_address, transfer.to_address, transfer.value)
266
+ console = Console()
267
+ console.print(table)
268
+
269
+
247
270
  def _print_balances(config: Config) -> None:
248
271
  if config.token:
249
272
  headers = ["n", "from_address", "nonce", "eth", "t", "to_address", "nonce", "eth", "t"]
@@ -251,24 +274,24 @@ def _print_balances(config: Config) -> None:
251
274
  headers = ["n", "from_address", "nonce", "eth", "to_address", "nonce", "eth"]
252
275
  table = Table(*headers, title="balances")
253
276
  with Live(table, refresh_per_second=0.5):
254
- for count, route in enumerate(config.routes):
255
- from_nonce = _get_nonce_str(route.from_address, config)
256
- to_nonce = _get_nonce_str(route.to_address, config)
277
+ for count, transfer in enumerate(config.transfers):
278
+ from_nonce = _get_nonce_str(transfer.from_address, config)
279
+ to_nonce = _get_nonce_str(transfer.to_address, config)
257
280
 
258
- from_eth_balance = _get_eth_balance_str(route.from_address, config)
259
- to_eth_balance = _get_eth_balance_str(route.to_address, config)
281
+ from_eth_balance = _get_eth_balance_str(transfer.from_address, config)
282
+ to_eth_balance = _get_eth_balance_str(transfer.to_address, config)
260
283
 
261
- from_token_balance = _get_token_balance_str(route.from_address, config) if config.token else ""
262
- to_token_balance = _get_token_balance_str(route.to_address, config) if config.token else ""
284
+ from_token_balance = _get_token_balance_str(transfer.from_address, config) if config.token else ""
285
+ to_token_balance = _get_token_balance_str(transfer.to_address, config) if config.token else ""
263
286
 
264
287
  if config.token:
265
288
  table.add_row(
266
289
  str(count),
267
- route.from_address,
290
+ transfer.from_address,
268
291
  from_nonce,
269
292
  from_eth_balance,
270
293
  from_token_balance,
271
- route.to_address,
294
+ transfer.to_address,
272
295
  to_nonce,
273
296
  to_eth_balance,
274
297
  to_token_balance,
@@ -276,10 +299,10 @@ def _print_balances(config: Config) -> None:
276
299
  else:
277
300
  table.add_row(
278
301
  str(count),
279
- route.from_address,
302
+ transfer.from_address,
280
303
  from_nonce,
281
304
  from_eth_balance,
282
- route.to_address,
305
+ transfer.to_address,
283
306
  to_nonce,
284
307
  to_eth_balance,
285
308
  )
@@ -1,6 +1,9 @@
1
- routes = """
2
- 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b
3
- 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # can comment here
1
+ transfers = """
2
+ # from_address to_address value
3
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b 0.1t # comments are allowed here
4
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f 0.2balance-random(0.1t,0.5t)
5
+ 0x10ecB8d838746643E613f6B5218C8e342593225c 0xE19242B72a4833eD86F1b2015d4E59052A2b278b # if the value is not set, config.value will be used
6
+ file: /path/to/other_transfers.txt # transfers from this file will be added
4
7
  """
5
8
 
6
9
  private_keys = """
@@ -11,19 +14,19 @@ file: /path/to/other_private_keys.txt
11
14
 
12
15
  token = "0x60631C856303731BE4deb81C0303F80B652aA5b4" # If not specified, it ETH transfers
13
16
 
14
- max_fee = "1.2base_fee + 1gwei + random(1,200)" # supported var_name=base_fee
17
+ max_fee = "1.2base_fee+1gwei+random(1,200)" # supported var_name=base_fee
15
18
 
16
- max_fee_limit = "10.1gwei - random(1, 10)" # optional
19
+ max_fee_limit = "10.1gwei-random(1,10)" # optional
17
20
 
18
- priority_fee = "1gwei + random(1, 12)"
21
+ priority_fee = "1gwei+random(1,12)"
19
22
 
20
- gas = "estimate + random(100, 200) - 19" # supported var_name=estimate
23
+ gas = "estimate+random(100,200)-19" # supported var_name=estimate
21
24
 
22
- value = "0.5balance - random(1.5t, 3t) + 11t" # supported var_name=balance. For ERC20 token use 't' suffix.
25
+ value = "0.5balance-random(1.5t,3t)+11t" # supported var_name=balance. For ERC20 token use 't' suffix.
23
26
 
24
- value_min_limit = "0.5t + random(1, 2) - 7"
27
+ value_min_limit = "0.5t+random(1,2)-7"
25
28
 
26
- delay = "random(1.123, 10) + 1" # secs, optional
29
+ delay = "random(1.123,10)+1" # secs, optional
27
30
 
28
31
  log_debug = "/path/to/file_debug.log" # optional
29
32
 
mm_eth/cli/validators.py CHANGED
@@ -1,6 +1,6 @@
1
1
  from collections.abc import Callable
2
2
 
3
- from mm_crypto_utils import AddressToPrivate, ConfigValidators, TxRoute
3
+ from mm_crypto_utils import AddressToPrivate, ConfigValidators, Transfer
4
4
 
5
5
  from mm_eth.account import address_from_private, is_address
6
6
  from mm_eth.constants import SUFFIX_DECIMALS
@@ -22,8 +22,8 @@ class Validators(ConfigValidators):
22
22
  return ConfigValidators.valid_calc_int_expression(var_name, SUFFIX_DECIMALS | {"t": 6})
23
23
 
24
24
  @staticmethod
25
- def eth_routes() -> Callable[[str], list[TxRoute]]:
26
- return ConfigValidators.routes(is_address, to_lower=True)
25
+ def eth_transfers() -> Callable[[str], list[Transfer]]:
26
+ return ConfigValidators.transfers(is_address, to_lower=True)
27
27
 
28
28
  @staticmethod
29
29
  def eth_private_keys() -> Callable[[str], AddressToPrivate]:
@@ -1,8 +1,8 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mm-eth
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Requires-Python: >=3.12
5
- Requires-Dist: mm-crypto-utils>=0.1.5
5
+ Requires-Dist: mm-crypto-utils>=0.2.3
6
6
  Requires-Dist: typer>=0.15.1
7
7
  Requires-Dist: web3~=7.8.0
8
8
  Requires-Dist: websocket-client~=1.8.0
@@ -16,11 +16,11 @@ mm_eth/utils.py,sha256=sSxt9GZEntZlT0RU8ht9Qon875HPhpd-1JjgqUBEfVo,7405
16
16
  mm_eth/vault.py,sha256=h8NyiOQh5YFskh1lZA3KyvnJUnxl9769ME2ChplG0CM,1477
17
17
  mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
18
  mm_eth/cli/calcs.py,sha256=cLFTYNAN-I53tUiSg-zFfVr2afjIZPftDDjHj16FBz0,1068
19
- mm_eth/cli/cli.py,sha256=0UYDtNrCXvJqs836tPxsQHs7yTKjvAGL3qgWiXVhnDU,9208
19
+ mm_eth/cli/cli.py,sha256=nSOxvfjEu6HBXz6qFOCuiLFgw381pzeNB2tUGxdJIgA,9332
20
20
  mm_eth/cli/cli_utils.py,sha256=yBIS3dGid75zyxw8crPOQHA4p3Krk5BoA2g01turKmQ,1712
21
21
  mm_eth/cli/print_helpers.py,sha256=yOiOFjTKloumwf07AqNIHQswUo8t0yuT9bpeGBGl60Q,1470
22
22
  mm_eth/cli/rpc_helpers.py,sha256=FMV-QVNM3v9X8H_-DP0hjNRqmm7KOnfzkw9bP17Qbz0,4499
23
- mm_eth/cli/validators.py,sha256=whnuA4CFkKbcNcbl4e2EwktTSTDGFBTA798E9F_3EM8,1682
23
+ mm_eth/cli/validators.py,sha256=KIAQUohl4_KKDvynbeqIeywtNnMWhTKYlnTdaxcjn6U,1690
24
24
  mm_eth/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
25
  mm_eth/cli/cmd/balance_cmd.py,sha256=gkwUa8VGe1wXH0GDsit3-1NmRiijojaNLygi5zNcYSY,2110
26
26
  mm_eth/cli/cmd/balances_cmd.py,sha256=4UiWSNH9OCnkvcMGPTygEss8119do-rfG7QtsNMfWZs,4197
@@ -30,10 +30,9 @@ mm_eth/cli/cmd/encode_input_data_cmd.py,sha256=9UQ1MKPEFQJ8j_COsP3KGKhwOf9tT3feB
30
30
  mm_eth/cli/cmd/example_cmd.py,sha256=o4NTll3fjmspbKjZ0sHGDRHTZ1RcFNHZDi9Ka0VNoDQ,264
31
31
  mm_eth/cli/cmd/node_cmd.py,sha256=Ae5yPxxnNiHw3tZcojS7KwNLM4gEfLhsTfhZp_86rqU,1956
32
32
  mm_eth/cli/cmd/rpc_cmd.py,sha256=02q82YqgbPezfEBmV_QBCIeNReE7ktkPych8Xr9ann8,2186
33
- mm_eth/cli/cmd/send_contract_cmd.py,sha256=JX3L1Dz4CBySfa2Pf87E8grauPu0U6xIyyOus81285Y,7316
34
33
  mm_eth/cli/cmd/solc_cmd.py,sha256=tBpeMdPfGs2iQIMaIJAAhMh1a3KyXHwyninfXPVpsgs,677
35
34
  mm_eth/cli/cmd/token_cmd.py,sha256=4y6ZQpLOJ33_iNuKpm9tZXh4RntWhmPUcizgaNNBzaw,1102
36
- mm_eth/cli/cmd/transfer_cmd.py,sha256=nC1Jiqe6zDJQvK9xVhB8mCbkEB1ZgMZmA393tDnZ1Dw,12176
35
+ mm_eth/cli/cmd/transfer_cmd.py,sha256=r8mZK9WyqXdR-Ce7Xb4hhOOHEKaJhPfbmYigC_4HzqU,13151
37
36
  mm_eth/cli/cmd/tx_cmd.py,sha256=PIenXYTT60Z2fqsivpzybCLI2Z_tlcz-asm3B0JLHgI,517
38
37
  mm_eth/cli/cmd/vault_cmd.py,sha256=MOM1CILIaaqown1I-Fgo22ckqIMLtFt8t2D3fWNp798,606
39
38
  mm_eth/cli/cmd/wallet/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -41,8 +40,8 @@ mm_eth/cli/cmd/wallet/mnemonic_cmd.py,sha256=xE-5Ux9BdYsTZYBy0dMn9jupGhW4ced-AgY
41
40
  mm_eth/cli/cmd/wallet/private_key_cmd.py,sha256=Fv_2OLog1h32pIP7PJITwl_pHdy3BXvaDRcXZsxY1xo,241
42
41
  mm_eth/cli/examples/balances.toml,sha256=i_ALpiEcf8-0TFiUg1cgJhxxfHYeBl9x0b3tnUWjswU,421
43
42
  mm_eth/cli/examples/call_contract.toml,sha256=ZQWK-409V_vLIZ2bsRD5RCWPPzShPz2KJTTRQY4YaGw,248
44
- mm_eth/cli/examples/transfer.toml,sha256=HLVpkCNakZFE8qdFdDUoBPk7ZBdfEmk9TLZtooRsGAc,1198
45
- mm_eth-0.4.1.dist-info/METADATA,sha256=aMEeaP5awNBZ0ZhRIxmhTl1jennR_TAdETFamw4F9HU,207
46
- mm_eth-0.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
47
- mm_eth-0.4.1.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
48
- mm_eth-0.4.1.dist-info/RECORD,,
43
+ mm_eth/cli/examples/transfer.toml,sha256=OKpojNWV2DQ68hX8jQ84cAMevKmBt-l20ij3ROTO9G0,1463
44
+ mm_eth-0.5.0.dist-info/METADATA,sha256=5vJwdr_CVo2u2CDobOUKNK2pnQi1sDksc2qQd9XBNaE,207
45
+ mm_eth-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
46
+ mm_eth-0.5.0.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
47
+ mm_eth-0.5.0.dist-info/RECORD,,
@@ -1,197 +0,0 @@
1
- import json
2
- import sys
3
- import time
4
- from pathlib import Path
5
- from typing import Annotated, Self
6
-
7
- import mm_crypto_utils
8
- from loguru import logger
9
- from mm_crypto_utils import AddressToPrivate
10
- from mm_std import BaseConfig, Err, Ok, utc_now
11
- from pydantic import AfterValidator, BeforeValidator, StrictStr, model_validator
12
-
13
- from mm_eth import abi, rpc
14
- from mm_eth.cli import calcs, cli_utils, print_helpers, rpc_helpers, validators
15
- from mm_eth.cli.calcs import calc_eth_expression
16
- from mm_eth.cli.cli_utils import BaseConfigParams
17
- from mm_eth.cli.validators import Validators
18
- from mm_eth.tx import sign_tx
19
- from mm_eth.utils import from_wei_str
20
-
21
-
22
- class Config(BaseConfig):
23
- from_addresses: Annotated[list[str], BeforeValidator(Validators.eth_addresses(unique=True))]
24
- contract_address: Annotated[str, BeforeValidator(Validators.eth_address())]
25
- function_signature: str
26
- function_args: StrictStr = "[]"
27
- nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
28
- chain_id: int
29
- private_keys: Annotated[AddressToPrivate, BeforeValidator(Validators.eth_private_keys())]
30
- max_fee: Annotated[str, AfterValidator(Validators.valid_eth_expression("base_fee"))]
31
- priority_fee: Annotated[str, AfterValidator(Validators.valid_eth_expression())]
32
- max_fee_limit: Annotated[str | None, AfterValidator(Validators.valid_eth_expression())] = None
33
- value: Annotated[str, AfterValidator(Validators.valid_eth_expression("balance"))] # eth value
34
- gas: Annotated[str, AfterValidator(Validators.valid_eth_expression("estimate"))]
35
- delay: Annotated[str | None, AfterValidator(Validators.valid_calc_decimal_value())] = None # in seconds
36
- round_ndigits: int = 5
37
- log_debug: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
38
- log_info: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
39
-
40
- # noinspection DuplicatedCode
41
- @model_validator(mode="after")
42
- def final_validator(self) -> Self:
43
- # check all private keys exist
44
- if not self.private_keys.contains_all_addresses(self.from_addresses):
45
- raise ValueError("private keys are not set for all addresses")
46
-
47
- # check that from_addresses is not empty
48
- if not self.from_addresses:
49
- raise ValueError("from_addresses is empty")
50
-
51
- # function_args
52
- if not validators.is_valid_calc_function_args(self.function_args):
53
- raise ValueError(f"wrong function_args: {self.function_args}")
54
-
55
- return self
56
-
57
-
58
- class SendContractCmdParams(BaseConfigParams):
59
- print_balances: bool
60
- debug: bool
61
- no_receipt: bool
62
- emulate: bool
63
-
64
-
65
- # noinspection DuplicatedCode
66
- def run(cli_params: SendContractCmdParams) -> None:
67
- config = Config.read_toml_config_or_exit(cli_params.config_path)
68
- if cli_params.print_config:
69
- config.print_and_exit({"private_key"})
70
-
71
- mm_crypto_utils.init_logger(cli_params.debug, config.log_debug, config.log_info)
72
-
73
- rpc_helpers.check_nodes_for_chain_id(config.nodes, config.chain_id)
74
-
75
- if cli_params.print_balances:
76
- print_helpers.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits)
77
- sys.exit(0)
78
-
79
- _run_transfers(config, cli_params)
80
-
81
-
82
- # noinspection DuplicatedCode
83
- def _run_transfers(config: Config, cli_params: SendContractCmdParams) -> None:
84
- logger.info(f"started at {utc_now()} UTC")
85
- logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
86
- for i, from_address in enumerate(config.from_addresses):
87
- _transfer(from_address, config, cli_params)
88
- if not cli_params.emulate and config.delay is not None and i < len(config.from_addresses) - 1:
89
- delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
90
- logger.debug(f"delay {delay_value} seconds")
91
- time.sleep(float(delay_value))
92
- logger.info(f"finished at {utc_now()} UTC")
93
-
94
-
95
- # noinspection DuplicatedCode
96
- def _transfer(from_address: str, config: Config, cli_params: SendContractCmdParams) -> None:
97
- log_prefix = f"{from_address}"
98
- # get nonce
99
- nonce = rpc_helpers.get_nonce(config.nodes, from_address, log_prefix)
100
- if nonce is None:
101
- return
102
-
103
- # get max_fee
104
- max_fee = rpc_helpers.calc_max_fee(config.nodes, config.max_fee, log_prefix)
105
- if max_fee is None:
106
- return
107
-
108
- # check max_fee_limit
109
- if rpc_helpers.is_max_fee_limit_exceeded(max_fee, config.max_fee_limit, log_prefix):
110
- return
111
-
112
- priority_fee = calc_eth_expression(config.priority_fee)
113
-
114
- # data
115
- function_args = calcs.calc_function_args(config.function_args).replace("'", '"')
116
- data = abi.encode_function_input_by_signature(config.function_signature, json.loads(function_args))
117
-
118
- # get gas
119
- gas = rpc_helpers.calc_gas(
120
- nodes=config.nodes,
121
- gas_expression=config.gas,
122
- from_address=from_address,
123
- to_address=config.contract_address,
124
- value=None,
125
- data=data,
126
- log_prefix=log_prefix,
127
- )
128
- if gas is None:
129
- return
130
-
131
- # get value
132
- value = None
133
- if config.value is not None:
134
- value = rpc_helpers.calc_eth_value_for_address(
135
- nodes=config.nodes,
136
- value_expression=config.value,
137
- address=from_address,
138
- gas=gas,
139
- max_fee=max_fee,
140
- log_prefix=log_prefix,
141
- )
142
- if value is None:
143
- return
144
-
145
- # emulate?
146
- if cli_params.emulate:
147
- msg = f"{log_prefix}: emulate,"
148
- if value is not None:
149
- msg += f" value={from_wei_str(value, 'eth', config.round_ndigits)},"
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)},"
152
- msg += f" gas={gas}, "
153
- msg += f" data={data}"
154
- logger.info(msg)
155
- return
156
-
157
- debug_tx_params = {
158
- "nonce": nonce,
159
- "max_fee": max_fee,
160
- "priority_fee": priority_fee,
161
- "gas": gas,
162
- "value": value,
163
- "data": data,
164
- "to": config.contract_address,
165
- "chain_id": config.chain_id,
166
- }
167
- logger.debug(f"{log_prefix}: tx_params={debug_tx_params}")
168
- signed_tx = sign_tx(
169
- nonce=nonce,
170
- max_fee_per_gas=max_fee,
171
- max_priority_fee_per_gas=priority_fee,
172
- gas=gas,
173
- private_key=config.private_keys[from_address],
174
- chain_id=config.chain_id,
175
- value=value,
176
- data=data,
177
- to=config.contract_address,
178
- )
179
- res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
180
- if isinstance(res, Err):
181
- logger.info(f"{log_prefix}: send_error: {res.err}")
182
- return
183
- tx_hash = res.ok
184
-
185
- if cli_params.no_receipt:
186
- msg = f"{log_prefix}: tx_hash={tx_hash}"
187
- logger.info(msg)
188
- else:
189
- logger.debug(f"{log_prefix}: tx_hash={tx_hash}, wait receipt")
190
- while True: # TODO: infinite loop if receipt_res is err
191
- receipt_res = rpc.get_tx_status(config.nodes, tx_hash)
192
- if isinstance(receipt_res, Ok):
193
- status = "OK" if receipt_res.ok == 1 else "FAIL"
194
- msg = f"{log_prefix}: tx_hash={tx_hash}, status={status}"
195
- logger.info(msg)
196
- break
197
- time.sleep(1)
File without changes