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.
- mm_eth/abi.py +2 -4
- mm_eth/account.py +51 -18
- mm_eth/anvil.py +8 -8
- mm_eth/cli/calcs.py +1 -16
- mm_eth/cli/cli.py +55 -159
- mm_eth/cli/cli_utils.py +14 -27
- mm_eth/cli/cmd/balance_cmd.py +15 -16
- mm_eth/cli/cmd/balances_cmd.py +35 -36
- mm_eth/cli/cmd/deploy_cmd.py +9 -11
- mm_eth/cli/cmd/node_cmd.py +20 -15
- mm_eth/cli/cmd/solc_cmd.py +7 -6
- mm_eth/cli/cmd/transfer_cmd.py +210 -128
- mm_eth/cli/cmd/wallet/private_key_cmd.py +5 -4
- mm_eth/cli/rpc_helpers.py +32 -115
- mm_eth/cli/validators.py +13 -16
- mm_eth/converters.py +56 -0
- mm_eth/erc20.py +6 -224
- mm_eth/retry.py +153 -0
- mm_eth/rpc.py +230 -428
- mm_eth/solc.py +30 -17
- mm_eth/tx.py +8 -9
- mm_eth/utils.py +0 -224
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/METADATA +3 -2
- mm_eth-0.6.1.dist-info/RECORD +33 -0
- mm_eth/async_rpc.py +0 -94
- mm_eth/cli/cmd/call_contract_cmd.py +0 -44
- mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
- mm_eth/cli/cmd/example_cmd.py +0 -9
- mm_eth/cli/cmd/rpc_cmd.py +0 -78
- mm_eth/cli/cmd/token_cmd.py +0 -29
- mm_eth/cli/cmd/tx_cmd.py +0 -16
- mm_eth/cli/cmd/vault_cmd.py +0 -19
- mm_eth/cli/examples/balances.toml +0 -18
- mm_eth/cli/examples/call_contract.toml +0 -9
- mm_eth/cli/examples/transfer.toml +0 -46
- mm_eth/cli/print_helpers.py +0 -37
- mm_eth/constants.py +0 -1
- mm_eth/ens.py +0 -106
- mm_eth/ethernodes.py +0 -34
- mm_eth/json_encoder.py +0 -15
- mm_eth/rpc_async.py +0 -160
- mm_eth/vault.py +0 -38
- mm_eth-0.5.17.dist-info/RECORD +0 -49
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/WHEEL +0 -0
- {mm_eth-0.5.17.dist-info → mm_eth-0.6.1.dist-info}/entry_points.txt +0 -0
mm_eth/cli/cmd/transfer_cmd.py
CHANGED
|
@@ -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,
|
|
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,
|
|
16
|
-
from mm_eth.cli import calcs, cli_utils
|
|
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.
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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(
|
|
89
|
-
config = Config.
|
|
90
|
-
if
|
|
91
|
-
|
|
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
|
|
91
|
+
if params.print_transfers:
|
|
97
92
|
_print_transfers(config)
|
|
98
93
|
sys.exit(0)
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
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,
|
|
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
|
-
|
|
114
|
+
await asyncio.sleep(float(delay_value))
|
|
118
115
|
logger.info(f"finished at {utc_now()} UTC")
|
|
119
116
|
|
|
120
117
|
|
|
121
|
-
def
|
|
122
|
-
|
|
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 =
|
|
226
|
+
max_fee = await _calc_max_fee(t, config)
|
|
127
227
|
if max_fee is None:
|
|
128
228
|
return
|
|
129
229
|
|
|
130
|
-
if
|
|
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
|
|
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={
|
|
151
|
-
msg += f" priority_fee={
|
|
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(
|
|
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
|
-
|
|
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
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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 =
|
|
238
|
-
if
|
|
239
|
-
logger.
|
|
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
|
-
|
|
242
|
-
|
|
326
|
+
logger.debug(f"{transfer.log_prefix}: tx_hash={res.unwrap()}")
|
|
243
327
|
|
|
244
|
-
|
|
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 =
|
|
281
|
-
|
|
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
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
|
336
|
-
return
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
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
|
|
7
|
-
from mm_eth.utils import from_wei_str
|
|
3
|
+
from mm_crypto_utils import Nodes, Proxies, VarInt
|
|
8
4
|
|
|
9
|
-
from
|
|
5
|
+
from mm_eth import retry
|
|
6
|
+
from mm_eth.cli import calcs
|
|
10
7
|
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
16
|
-
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
if
|
|
36
|
-
logger.
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
41
|
+
return calcs.calc_eth_expression(max_fee_expression, VarInt("base_fee", base_fee))
|
|
107
42
|
|
|
108
|
-
|
|
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
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|