mm-eth 0.2.5__py3-none-any.whl → 0.3.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.
@@ -1,180 +1,110 @@
1
1
  import sys
2
2
  import time
3
3
  from pathlib import Path
4
- from typing import Self
4
+ from typing import Annotated, Self
5
5
 
6
+ import mm_crypto_utils
6
7
  from loguru import logger
7
- from mm_std import BaseConfig, Err, Ok, str_to_list, utc_now
8
- from pydantic import Field, StrictStr, field_validator, model_validator
8
+ from mm_crypto_utils import AddressToPrivate, TxRoute
9
+ from mm_std import BaseConfig, Err, Ok, utc_now
10
+ from pydantic import AfterValidator, BeforeValidator, model_validator
9
11
 
10
12
  from mm_eth import rpc
11
- from mm_eth.account import create_private_keys_dict, private_to_address
12
- from mm_eth.cli import calcs, cli_utils, print_helpers, rpc_helpers, validators
13
+ from mm_eth.cli import cli_utils, print_helpers, rpc_helpers
14
+ from mm_eth.cli.calcs import calc_eth_expression
15
+ from mm_eth.cli.cli_utils import BaseConfigParams
16
+ from mm_eth.cli.validators import Validators as Validators
13
17
  from mm_eth.tx import sign_tx
14
18
  from mm_eth.utils import from_wei_str
15
19
 
16
20
 
21
+ # noinspection DuplicatedCode
17
22
  class Config(BaseConfig):
18
- class Tx(BaseConfig):
19
- from_address: str
20
- to_address: str
21
-
22
- nodes: list[StrictStr]
23
+ nodes: Annotated[list[str], BeforeValidator(Validators.nodes())]
23
24
  chain_id: int
24
- private_keys: dict[str, str] = Field(default_factory=dict)
25
- private_keys_file: str | None = None
26
- max_fee_per_gas: str
27
- max_fee_per_gas_limit: str | None = None
28
- max_priority_fee_per_gas: str
29
- value: str
30
- value_min_limit: str | None = None
31
- gas: str
32
- addresses_map: str | None = None
33
- addresses_from_file: str | None = None
34
- addresses_to_file: str | None = None
35
- delay: str | None = None # in seconds
25
+ routes: Annotated[list[TxRoute], BeforeValidator(Validators.eth_routes())]
26
+ private_keys: Annotated[AddressToPrivate, BeforeValidator(Validators.eth_private_keys())]
27
+ max_fee: Annotated[str, AfterValidator(Validators.valid_eth_expression("base_fee"))]
28
+ priority_fee: Annotated[str, AfterValidator(Validators.valid_eth_expression())]
29
+ max_fee_limit: Annotated[str | None, AfterValidator(Validators.valid_eth_expression())] = None
30
+ value: Annotated[str, AfterValidator(Validators.valid_eth_expression("balance"))]
31
+ value_min_limit: Annotated[str | None, AfterValidator(Validators.valid_eth_expression())] = None
32
+ gas: Annotated[str, AfterValidator(Validators.valid_eth_expression("estimate"))]
33
+ delay: Annotated[str | None, AfterValidator(Validators.valid_calc_decimal_value())] = None # in seconds
36
34
  round_ndigits: int = 5
37
- log_debug: str | None = None
38
- log_info: str | None = None
39
- txs: list[Tx] = Field(default_factory=list)
35
+ log_debug: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
36
+ log_info: Annotated[Path | None, BeforeValidator(Validators.log_file())] = None
40
37
 
41
38
  @property
42
39
  def from_addresses(self) -> list[str]:
43
- return [tx.from_address for tx in self.txs]
44
-
45
- @field_validator("log_debug", "log_info", mode="before")
46
- def log_validator(cls, v: str | None) -> str | None:
47
- return validators.log_validator(v)
48
-
49
- @field_validator("nodes", mode="before")
50
- def nodes_validator(cls, v: str | list[str] | None) -> list[str]:
51
- return validators.nodes_validator(v)
52
-
53
- @field_validator("private_keys", mode="before")
54
- def private_keys_validator(cls, v: str | list[str] | None) -> dict[str, str]:
55
- if v is None:
56
- return {}
57
- if isinstance(v, str):
58
- return create_private_keys_dict(str_to_list(v, unique=True, remove_comments=True))
59
- return create_private_keys_dict(v)
40
+ return [r.from_address for r in self.routes]
60
41
 
61
- # noinspection DuplicatedCode
62
42
  @model_validator(mode="after")
63
43
  def final_validator(self) -> Self:
64
- # load private keys from file
65
- if self.private_keys_file:
66
- file = Path(self.private_keys_file).expanduser()
67
- if not file.is_file():
68
- raise ValueError("can't read private_keys_file")
69
- for line in file.read_text().strip().split("\n"):
70
- line = line.strip() # noqa: PLW2901
71
- address = private_to_address(line)
72
- if address is None:
73
- raise ValueError("there is not a private key in private_keys_file")
74
- self.private_keys[address.lower()] = line
75
-
76
- # max_fee_per_gas
77
- if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas, "base"):
78
- raise ValueError(f"wrong max_fee_per_gas: {self.max_fee_per_gas}")
79
-
80
- # max_fee_per_gas_limit
81
- if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas_limit, "base"):
82
- raise ValueError(f"wrong max_fee_per_gas_limit: {self.max_fee_per_gas_limit}")
83
-
84
- # max_priority_fee_per_gas
85
- if not validators.is_valid_calc_var_wei_value(self.max_priority_fee_per_gas):
86
- raise ValueError(f"wrong max_priority_fee_per_gas: {self.max_priority_fee_per_gas}")
87
-
88
- # value
89
- if not validators.is_valid_calc_var_wei_value(self.value, "balance"):
90
- raise ValueError(f"wrong value: {self.value}")
91
-
92
- # value_min_limit
93
- if not validators.is_valid_calc_var_wei_value(self.value_min_limit):
94
- raise ValueError(f"wrong value_min_limit: {self.value_min_limit}")
95
-
96
- # gas
97
- if not validators.is_valid_calc_var_wei_value(self.gas, "estimate"):
98
- raise ValueError(f"wrong gas: {self.gas}")
99
-
100
- # delay
101
- if not validators.is_valid_calc_decimal_value(self.delay):
102
- raise ValueError(f"wrong delay: {self.delay}")
103
-
104
- # txs
105
- if self.addresses_map:
106
- for tx in cli_utils.load_tx_addresses_from_str(self.addresses_map):
107
- self.txs.append(Config.Tx(from_address=tx[0], to_address=tx[1]))
108
- if self.addresses_from_file and self.addresses_to_file:
109
- self.txs.extend(
110
- Config.Tx(from_address=tx[0], to_address=tx[1])
111
- for tx in cli_utils.load_tx_addresses_from_files(self.addresses_from_file, self.addresses_to_file)
112
- )
113
- if not self.txs:
114
- raise ValueError("txs is empty")
44
+ if not self.private_keys.contains_all_addresses(self.from_addresses):
45
+ raise ValueError("private keys are not set for all addresses")
115
46
 
116
47
  return self
117
48
 
118
49
 
119
- def run(
120
- config_path: str,
121
- *,
122
- print_balances: bool,
123
- print_config: bool,
124
- debug: bool,
125
- no_receipt: bool,
126
- emulate: bool,
127
- ) -> None:
128
- config = Config.read_config_or_exit(config_path)
129
- cli_utils.print_config_and_exit(print_config, config, {"private_key", "addresses_map"})
50
+ class TransferEthCmdParams(BaseConfigParams):
51
+ print_balances: bool
52
+ debug: bool
53
+ no_receipt: bool
54
+ emulate: bool
130
55
 
131
- cli_utils.init_logger(debug, config.log_debug, config.log_info)
56
+
57
+ def run(cli_params: TransferEthCmdParams) -> None:
58
+ config = Config.read_toml_config_or_exit(cli_params.config_path)
59
+ if cli_params.print_config_and_exit:
60
+ config.print_and_exit({"private_keys"})
61
+
62
+ mm_crypto_utils.init_logger(cli_params.debug, config.log_debug, config.log_info)
132
63
  rpc_helpers.check_nodes_for_chain_id(config.nodes, config.chain_id)
133
64
 
134
- if print_balances:
65
+ if cli_params.print_balances:
135
66
  print_helpers.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits)
136
67
  sys.exit(0)
137
68
 
138
- return _run_transfers(config, no_receipt=no_receipt, emulate=emulate)
69
+ return _run_transfers(config, cli_params)
139
70
 
140
71
 
141
72
  # noinspection DuplicatedCode
142
- def _run_transfers(config: Config, *, no_receipt: bool, emulate: bool) -> None:
73
+ def _run_transfers(config: Config, cli_params: TransferEthCmdParams) -> None:
143
74
  logger.info(f"started at {utc_now()} UTC")
144
- logger.debug(f"config={config.model_dump(exclude={'private_keys', 'addresses_map'}) | {'version': cli_utils.get_version()}}")
145
- cli_utils.check_private_keys(config.from_addresses, config.private_keys)
146
- for i, tx in enumerate(config.txs):
147
- _transfer(from_address=tx.from_address, to_address=tx.to_address, config=config, no_receipt=no_receipt, emulate=emulate)
148
- if not emulate and config.delay is not None and i < len(config.txs) - 1:
149
- delay_value = calcs.calc_decimal_value(config.delay)
75
+ logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version': cli_utils.get_version()}}")
76
+ for i, route in enumerate(config.routes):
77
+ _transfer(route, config, cli_params)
78
+ if not cli_params.emulate and config.delay is not None and i < len(config.routes) - 1:
79
+ delay_value = mm_crypto_utils.calc_decimal_value(config.delay)
150
80
  logger.debug(f"delay {delay_value} seconds")
151
81
  time.sleep(float(delay_value))
152
82
  logger.info(f"finished at {utc_now()} UTC")
153
83
 
154
84
 
155
85
  # noinspection DuplicatedCode
156
- def _transfer(*, from_address: str, to_address: str, config: Config, no_receipt: bool, emulate: bool) -> None:
157
- log_prefix = f"{from_address}->{to_address}"
86
+ def _transfer(route: TxRoute, config: Config, cli_params: TransferEthCmdParams) -> None:
87
+ log_prefix = f"{route.from_address}->{route.to_address}"
158
88
  # get nonce
159
- nonce = rpc_helpers.get_nonce(config.nodes, from_address, log_prefix)
89
+ nonce = rpc_helpers.get_nonce(config.nodes, route.from_address, log_prefix)
160
90
  if nonce is None:
161
91
  return
162
92
 
163
- # get max_fee_per_gas
164
- max_fee_per_gas = rpc_helpers.calc_max_fee_per_gas(config.nodes, config.max_fee_per_gas, log_prefix)
165
- if max_fee_per_gas is None:
93
+ # get max_fee
94
+ max_fee = rpc_helpers.calc_max_fee(config.nodes, config.max_fee, log_prefix)
95
+ if max_fee is None:
166
96
  return
167
97
 
168
- # check max_fee_per_gas_limit
169
- if rpc_helpers.is_max_fee_per_gas_limit_exceeded(max_fee_per_gas, config.max_fee_per_gas_limit, log_prefix):
98
+ # check max_fee_limit
99
+ if rpc_helpers.is_max_fee_limit_exceeded(max_fee, config.max_fee_limit, log_prefix):
170
100
  return
171
101
 
172
102
  # get gas
173
103
  gas = rpc_helpers.calc_gas(
174
104
  nodes=config.nodes,
175
- gas=config.gas,
176
- from_address=from_address,
177
- to_address=to_address,
105
+ gas_expression=config.gas,
106
+ from_address=route.from_address,
107
+ to_address=route.to_address,
178
108
  value=123,
179
109
  log_prefix=log_prefix,
180
110
  )
@@ -182,51 +112,54 @@ def _transfer(*, from_address: str, to_address: str, config: Config, no_receipt:
182
112
  return
183
113
 
184
114
  # get value
185
- value = rpc_helpers.calc_eth_value(
115
+ value = rpc_helpers.calc_eth_value_for_address(
186
116
  nodes=config.nodes,
187
- value_str=config.value,
188
- address=from_address,
117
+ value_expression=config.value,
118
+ address=route.from_address,
189
119
  gas=gas,
190
- max_fee_per_gas=max_fee_per_gas,
120
+ max_fee=max_fee,
191
121
  log_prefix=log_prefix,
192
122
  )
193
123
  if value is None:
194
124
  return
195
125
 
196
126
  # value_min_limit
197
- if calcs.is_value_less_min_limit(config.value_min_limit, value, "eth", log_prefix=log_prefix):
198
- return
127
+ if config.value_min_limit is not None:
128
+ value_min_limit = calc_eth_expression(config.value_min_limit)
129
+ if value < value_min_limit:
130
+ logger.info(f"{log_prefix}value<value_min_limit, value={from_wei_str(value, 'eth', config.round_ndigits)}")
131
+ return
199
132
 
200
- max_priority_fee_per_gas = calcs.calc_var_wei_value(config.max_priority_fee_per_gas)
201
- tx_params = {
202
- "nonce": nonce,
203
- "max_fee_per_gas": max_fee_per_gas,
204
- "max_priority_fee_per_gas": max_priority_fee_per_gas,
205
- "gas": gas,
206
- "value": value,
207
- "to": to_address,
208
- "chain_id": config.chain_id,
209
- }
133
+ priority_fee = calc_eth_expression(config.priority_fee)
210
134
 
211
135
  # emulate?
212
- if emulate:
136
+ if cli_params.emulate:
213
137
  msg = f"{log_prefix}: emulate, value={from_wei_str(value, 'eth', config.round_ndigits)},"
214
- msg += f" max_fee_per_gas={from_wei_str(max_fee_per_gas, 'gwei', config.round_ndigits)},"
215
- msg += f" max_priority_fee_per_gas={from_wei_str(max_priority_fee_per_gas, 'gwei', config.round_ndigits)},"
138
+ msg += f" max_fee={from_wei_str(max_fee, 'gwei', config.round_ndigits)},"
139
+ msg += f" priority_fee={from_wei_str(priority_fee, 'gwei', config.round_ndigits)},"
216
140
  msg += f" gas={gas}"
217
141
  logger.info(msg)
218
142
  return
219
143
 
220
- logger.debug(f"{log_prefix}: tx_params={tx_params}")
144
+ debug_tx_params = {
145
+ "nonce": nonce,
146
+ "max_fee": max_fee,
147
+ "priority_fee": priority_fee,
148
+ "gas": gas,
149
+ "value": value,
150
+ "to": route.to_address,
151
+ "chain_id": config.chain_id,
152
+ }
153
+ logger.debug(f"{log_prefix}: tx_params={debug_tx_params}")
221
154
  signed_tx = sign_tx(
222
155
  nonce=nonce,
223
- max_fee_per_gas=max_fee_per_gas,
224
- max_priority_fee_per_gas=max_priority_fee_per_gas,
156
+ max_fee_per_gas=max_fee,
157
+ max_priority_fee_per_gas=priority_fee,
225
158
  gas=gas,
226
- private_key=config.private_keys[from_address],
159
+ private_key=config.private_keys[route.from_address],
227
160
  chain_id=config.chain_id,
228
161
  value=value,
229
- to=to_address,
162
+ to=route.to_address,
230
163
  )
231
164
  res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
232
165
  if isinstance(res, Err):
@@ -234,12 +167,12 @@ def _transfer(*, from_address: str, to_address: str, config: Config, no_receipt:
234
167
  return
235
168
  tx_hash = res.ok
236
169
 
237
- if no_receipt:
170
+ if cli_params.no_receipt:
238
171
  msg = f"{log_prefix}: tx_hash={tx_hash}, value={from_wei_str(value, 'ether', round_ndigits=config.round_ndigits)}"
239
172
  logger.info(msg)
240
173
  else:
241
174
  logger.debug(f"{log_prefix}: tx_hash={tx_hash}, wait receipt")
242
- while True:
175
+ while True: # TODO: infinite loop if receipt_res is err
243
176
  receipt_res = rpc.get_tx_status(config.nodes, tx_hash)
244
177
  if isinstance(receipt_res, Ok):
245
178
  status = "OK" if receipt_res.ok == 1 else "FAIL"
@@ -0,0 +1,16 @@
1
+ from eth_typing import HexStr
2
+ from rich.pretty import pprint
3
+ from web3 import Web3
4
+
5
+ from mm_eth.cli.cli_utils import public_rpc_url
6
+
7
+
8
+ def run(rpc_url: str, tx_hash: str, get_receipt: bool) -> None:
9
+ rpc_url = public_rpc_url(rpc_url)
10
+ w3 = Web3(Web3.HTTPProvider(rpc_url))
11
+ transaction = w3.eth.get_transaction(HexStr(tx_hash))
12
+ pprint(dict(transaction), expand_all=True)
13
+
14
+ if get_receipt:
15
+ receipt = w3.eth.get_transaction_receipt(HexStr(tx_hash))
16
+ pprint(dict(receipt), expand_all=True)
@@ -1,11 +1,14 @@
1
+ from pathlib import Path
2
+
3
+ import mm_crypto_utils
1
4
  from mm_std import fatal, print_plain
2
5
 
3
6
  from mm_eth import vault
4
- from mm_eth.cli import cli_utils
7
+ from mm_eth.account import is_private_key
5
8
 
6
9
 
7
- def run(keys_url: str, vault_token: str, keys_file: str) -> None:
8
- private_keys = cli_utils.load_private_keys_from_file(keys_file)
10
+ def run(keys_url: str, vault_token: str, keys_file: Path) -> None:
11
+ private_keys = mm_crypto_utils.read_items_from_file(keys_file, is_private_key)
9
12
  if not private_keys:
10
13
  fatal("private keys not found")
11
14
 
@@ -1,15 +1,15 @@
1
- addresses: |
1
+ addresses = """
2
2
  0x10fd602Bff689e64D4720D1DCCCD3494f1f16623
3
3
  0x58487485c3858109f5A37e42546FE87473f79a4b
4
4
  0x97C77B548aE0d4925F5C201220fC6d8996424309
5
-
6
- tokens: |
5
+ """
6
+ tokens = """
7
7
  0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # USDT
8
8
  0x6a55fe4884DE7E1d904BdC47A3BA092240ae9B39 # USDC
9
-
10
- nodes: |
9
+ """
10
+ nodes = """
11
11
  https://arb1.arbitrum.io/rpc
12
12
  https://rpc.arb1.arbitrum.gateway.fm
13
13
  https://arbitrum-one.publicnode.com
14
-
15
- round_ndigits: 3
14
+ """
15
+ round_ndigits = 3
@@ -0,0 +1,5 @@
1
+ contract_address = "0xBa985cad26658EB00eA42aCc7516aed52e7a8AcC"
2
+ function_signature = "balanceOf(address)"
3
+ function_args = "['0x83aC43147BA5dAA5abc4ccEA84F2B8000bA82f26']"
4
+ outputs_types = "uint256" # optional
5
+ node = "https://rpc.eth.gateway.fm"
@@ -0,0 +1,27 @@
1
+ routes = """
2
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b
3
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # can comment here
4
+ """
5
+ private_keys = """
6
+ 0x7bb5b9c0ba991275f84b796b4d25fd3a8d7320911f50fade85410e7a2b000632
7
+ 0xb7e0b671e176b04ceb0897a698d34771bfe9acf29273dc52a141be6e97145a00
8
+ file: /path/to/other_private_keys.txt
9
+ """
10
+ token = "0x60631C856303731BE4deb81C0303F80B652aA5b4" # USDC
11
+ decimals = 6
12
+ max_fee = "1.2base_fee + 1gwei + random(1,200)" # supported var_name=base_fee
13
+ max_fee_limit = "10.1gwei - random(1, 10)" # optional
14
+ priority_fee = "1gwei + random(1, 12)"
15
+ gas = "estimate + random(100, 200) - 19" # supported var_name=estimate
16
+ value = "0.5balance - random(1.5t, 3t) + 11t" # supported var_name=balance
17
+ value_min_limit = "0.5t + random(1, 2) - 7"
18
+
19
+ delay = "random(1.123, 10) + 1" # secs, optional
20
+ log_debug = "/path/to/file_debug.log" # optional
21
+ log_info = "/path/to/file_info.log" # optional
22
+ round_ndigits = 6 # optional, default=5
23
+ chain_id = 421613
24
+ nodes = """
25
+ https://arbitrum-goerli.publicnode.com
26
+ https://rpc.goerli.arbitrum.gateway.fm
27
+ """
@@ -0,0 +1,26 @@
1
+ routes = """
2
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b
3
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # can comment here
4
+ file: /path/from_addresses.txt /path/to_addresses.txt
5
+ """
6
+ private_keys = """
7
+ # 0x7bb5b9c0ba991275f84b796b4d25fd3a8d7320911f50fade85410e7a2b000632
8
+ 0xb7e0b671e176b04ceb0897a698d34771bfe9acf29273dc52a141be6e97145a00
9
+ file: /path/to/private_keys.txt
10
+ """
11
+ max_fee = "1.2base + 1gwei + random(1, 200)" # supported var_name=base
12
+ max_fee_limit = "10.1gwei - random(1, 10)" # optional
13
+ priority_fee = "1gwei + random(1, 12)"
14
+ gas = "estimate + random(100, 200) - 19" # supported var_name=estimate
15
+ value = "balance - random(0.002eth, 0.0025eth) + 11gwei" # supported var_name=balance. If 'balance' is used, value=calc(value) - gas*max_fee_per_gas
16
+ value_min_limit = "0.001eth + random(1, 2) - 7gwei"
17
+
18
+ delay = "random(1.123,10) + 1" # secs
19
+ log_debug = "/path/to/file_debug.log" # optional
20
+ log_info = "/path/to/file_info.log" # optional
21
+ round_ndigits = 6
22
+ chain_id = 421613
23
+ nodes = """
24
+ https://arbitrum-goerli.publicnode.com
25
+ https://rpc.goerli.arbitrum.gateway.fm
26
+ """
mm_eth/cli/rpc_helpers.py CHANGED
@@ -1,10 +1,12 @@
1
+ import mm_crypto_utils
1
2
  from loguru import logger
3
+ from mm_crypto_utils import VarInt, get_log_prefix
2
4
  from mm_std import Err, fatal
3
5
 
4
6
  from mm_eth import erc20, rpc
5
7
  from mm_eth.utils import from_wei_str
6
8
 
7
- from . import calcs
9
+ from .calcs import calc_eth_expression
8
10
 
9
11
 
10
12
  def get_nonce(nodes: list[str] | str, address: str, log_prefix: str | None = None) -> int | None:
@@ -28,7 +30,7 @@ def check_nodes_for_chain_id(nodes: list[str], chain_id: int) -> None:
28
30
 
29
31
  def get_base_fee(nodes: list[str], log_prefix: str | None = None) -> int | None:
30
32
  res = rpc.get_base_fee_per_gas(nodes)
31
- prefix = _get_prefix(log_prefix)
33
+ prefix = get_log_prefix(log_prefix)
32
34
  logger.debug(f"{prefix}base_fee={res.ok_or_err()}")
33
35
  if isinstance(res, Err):
34
36
  logger.info(f"{prefix}base_fee error, {res.err}")
@@ -36,30 +38,27 @@ def get_base_fee(nodes: list[str], log_prefix: str | None = None) -> int | None:
36
38
  return res.ok
37
39
 
38
40
 
39
- def calc_max_fee_per_gas(nodes: list[str], max_fee_per_gas: str, log_prefix: str | None = None) -> int | None:
40
- if "base" in max_fee_per_gas.lower():
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():
41
43
  base_fee = get_base_fee(nodes, log_prefix)
42
44
  if base_fee is None:
43
45
  return None
44
- return calcs.calc_var_wei_value(max_fee_per_gas, var_name="base", var_value=base_fee)
45
- return calcs.calc_var_wei_value(max_fee_per_gas)
46
+ return calc_eth_expression(max_fee_expression, VarInt("base_fee", base_fee))
46
47
 
48
+ return calc_eth_expression(max_fee_expression)
47
49
 
48
- def is_max_fee_per_gas_limit_exceeded(
49
- max_fee_per_gas: int,
50
- max_fee_per_gas_limit: str | None,
51
- log_prefix: str | None = None,
52
- ) -> bool:
53
- if max_fee_per_gas_limit is None:
50
+
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:
54
53
  return False
55
- max_fee_per_gas_limit_value = calcs.calc_var_wei_value(max_fee_per_gas_limit)
56
- if max_fee_per_gas > max_fee_per_gas_limit_value:
57
- prefix = _get_prefix(log_prefix)
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)
58
57
  logger.info(
59
- "{}max_fee_per_gas_limit is exeeded, max_fee_per_gas={}, max_fee_per_gas_limit={}",
58
+ "{}max_fee_limit is exceeded, max_fee={}, max_fee_limit={}",
60
59
  prefix,
61
- from_wei_str(max_fee_per_gas, "gwei"),
62
- from_wei_str(max_fee_per_gas_limit_value, "gwei"),
60
+ from_wei_str(max_fee, "gwei"),
61
+ from_wei_str(max_limit_value, "gwei"),
63
62
  )
64
63
  return True
65
64
  return False
@@ -68,73 +67,67 @@ def is_max_fee_per_gas_limit_exceeded(
68
67
  def calc_gas(
69
68
  *,
70
69
  nodes: list[str],
71
- gas: str,
70
+ gas_expression: str,
72
71
  from_address: str,
73
72
  to_address: str,
74
73
  value: int | None = None,
75
74
  data: str | None = None,
76
75
  log_prefix: str | None = None,
77
76
  ) -> int | None:
78
- estimate_value = None
79
- if "estimate" in gas.lower():
80
- prefix = _get_prefix(log_prefix)
77
+ var = None
78
+ if "estimate" in gas_expression.lower():
79
+ prefix = get_log_prefix(log_prefix)
81
80
  res = rpc.eth_estimate_gas(nodes, from_address, to_address, data=data, value=value, attempts=5)
82
81
  logger.debug(f"{prefix}gas_estimate={res.ok_or_err()}")
83
82
  if isinstance(res, Err):
84
83
  logger.info(f"{prefix}estimate_gas error, {res.err}")
85
84
  return None
86
- estimate_value = res.ok
87
- return calcs.calc_var_wei_value(gas, var_name="estimate", var_value=estimate_value)
85
+ var = VarInt("estimate", res.ok)
86
+ return calc_eth_expression(gas_expression, var)
88
87
 
89
88
 
90
- def calc_eth_value(
89
+ def calc_eth_value_for_address(
91
90
  *,
92
91
  nodes: list[str],
93
- value_str: str,
92
+ value_expression: str,
94
93
  address: str,
95
- gas: int | None = None,
96
- max_fee_per_gas: int | None = None,
94
+ gas: int,
95
+ max_fee: int,
97
96
  log_prefix: str | None = None,
98
97
  ) -> int | None:
99
- balance_value = None
100
- if "balance" in value_str.lower():
101
- prefix = _get_prefix(log_prefix)
98
+ var = None
99
+ if "balance" in value_expression.lower():
100
+ prefix = get_log_prefix(log_prefix)
102
101
  res = rpc.eth_get_balance(nodes, address, attempts=5)
103
102
  logger.debug(f"{prefix}balance={res.ok_or_err()}")
104
103
  if isinstance(res, Err):
105
104
  logger.info(f"{prefix}balance error, {res.err}")
106
105
  return None
107
- balance_value = res.ok
108
- value = calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value)
109
- if "balance" in value_str.lower() and gas is not None and max_fee_per_gas is not None:
110
- value = value - gas * max_fee_per_gas
106
+ var = VarInt("balance", res.ok)
107
+
108
+ value = calc_eth_expression(value_expression, var)
109
+ if "balance" in value_expression.lower():
110
+ value = value - gas * max_fee
111
111
  return value
112
112
 
113
113
 
114
- def calc_erc20_value(
114
+ def calc_erc20_value_for_address(
115
115
  *,
116
116
  nodes: list[str],
117
- value_str: str,
117
+ value_expression: str,
118
118
  wallet_address: str,
119
119
  token_address: str,
120
120
  decimals: int,
121
121
  log_prefix: str | None = None,
122
122
  ) -> int | None:
123
- value_str = value_str.lower()
124
- balance_value = None
125
- if "balance" in value_str:
126
- prefix = _get_prefix(log_prefix)
123
+ value_expression = value_expression.lower()
124
+ var = None
125
+ if "balance" in value_expression:
126
+ prefix = get_log_prefix(log_prefix)
127
127
  res = erc20.get_balance(nodes, token_address, wallet_address, attempts=5)
128
128
  logger.debug(f"{prefix}balance={res.ok_or_err()}")
129
129
  if isinstance(res, Err):
130
130
  logger.info(f"{prefix}balance error, {res.err}")
131
131
  return None
132
- balance_value = res.ok
133
- return calcs.calc_var_wei_value(value_str, var_name="balance", var_value=balance_value, decimals=decimals)
134
-
135
-
136
- def _get_prefix(log_prefix: str | None) -> str:
137
- prefix = log_prefix or ""
138
- if prefix:
139
- prefix += ": "
140
- return prefix
132
+ var = VarInt("balance", res.ok)
133
+ return mm_crypto_utils.calc_int_expression(value_expression, var, suffix_decimals={"t": decimals})