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.
- mm_eth/account.py +11 -10
- mm_eth/cli/calcs.py +71 -90
- mm_eth/cli/cli.py +60 -37
- mm_eth/cli/cli_utils.py +6 -80
- mm_eth/cli/cmd/balance_cmd.py +1 -1
- mm_eth/cli/cmd/balances_cmd.py +23 -23
- mm_eth/cli/cmd/call_contract_cmd.py +9 -4
- mm_eth/cli/cmd/config_example_cmd.py +1 -1
- mm_eth/cli/cmd/deploy_cmd.py +10 -4
- mm_eth/cli/cmd/node_cmd.py +1 -1
- mm_eth/cli/cmd/send_contract_cmd.py +74 -122
- mm_eth/cli/cmd/transfer_erc20_cmd.py +86 -158
- mm_eth/cli/cmd/transfer_eth_cmd.py +85 -152
- mm_eth/cli/cmd/tx_cmd.py +16 -0
- mm_eth/cli/cmd/vault_cmd.py +6 -3
- mm_eth/cli/config_examples/{balances.yml → balances.toml} +7 -7
- mm_eth/cli/config_examples/call_contract.toml +5 -0
- mm_eth/cli/config_examples/transfer_erc20.toml +27 -0
- mm_eth/cli/config_examples/transfer_eth.toml +26 -0
- mm_eth/cli/rpc_helpers.py +43 -50
- mm_eth/cli/validators.py +23 -64
- mm_eth/constants.py +1 -0
- mm_eth/ens.py +2 -2
- mm_eth/erc20.py +1 -1
- mm_eth/json_encoder.py +15 -0
- mm_eth/rpc.py +2 -2
- mm_eth/utils.py +1 -15
- {mm_eth-0.2.5.dist-info → mm_eth-0.3.0.dist-info}/METADATA +2 -3
- mm_eth-0.3.0.dist-info/RECORD +49 -0
- mm_eth/cli/config_examples/call_contract.yml +0 -5
- mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
- mm_eth/cli/config_examples/transfer_eth.yml +0 -24
- mm_eth/types.py +0 -4
- mm_eth-0.2.5.dist-info/RECORD +0 -47
- {mm_eth-0.2.5.dist-info → mm_eth-0.3.0.dist-info}/WHEEL +0 -0
- {mm_eth-0.2.5.dist-info → mm_eth-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
8
|
-
from
|
|
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.
|
|
12
|
-
from mm_eth.cli import
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
value: str
|
|
30
|
-
value_min_limit: str | None = None
|
|
31
|
-
gas: str
|
|
32
|
-
|
|
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:
|
|
38
|
-
log_info:
|
|
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 [
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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,
|
|
69
|
+
return _run_transfers(config, cli_params)
|
|
139
70
|
|
|
140
71
|
|
|
141
72
|
# noinspection DuplicatedCode
|
|
142
|
-
def _run_transfers(config: Config,
|
|
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'
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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(
|
|
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
|
|
164
|
-
|
|
165
|
-
if
|
|
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
|
|
169
|
-
if rpc_helpers.
|
|
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
|
-
|
|
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.
|
|
115
|
+
value = rpc_helpers.calc_eth_value_for_address(
|
|
186
116
|
nodes=config.nodes,
|
|
187
|
-
|
|
188
|
-
address=from_address,
|
|
117
|
+
value_expression=config.value,
|
|
118
|
+
address=route.from_address,
|
|
189
119
|
gas=gas,
|
|
190
|
-
|
|
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
|
|
198
|
-
|
|
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
|
-
|
|
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"
|
|
215
|
-
msg += f"
|
|
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
|
-
|
|
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=
|
|
224
|
-
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"
|
mm_eth/cli/cmd/tx_cmd.py
ADDED
|
@@ -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)
|
mm_eth/cli/cmd/vault_cmd.py
CHANGED
|
@@ -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.
|
|
7
|
+
from mm_eth.account import is_private_key
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
def run(keys_url: str, vault_token: str, keys_file:
|
|
8
|
-
private_keys =
|
|
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
|
|
14
|
+
"""
|
|
15
|
+
round_ndigits = 3
|
|
@@ -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
|
|
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 =
|
|
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
|
|
40
|
-
if "
|
|
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
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
56
|
-
if
|
|
57
|
-
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
|
-
"{}
|
|
58
|
+
"{}max_fee_limit is exceeded, max_fee={}, max_fee_limit={}",
|
|
60
59
|
prefix,
|
|
61
|
-
from_wei_str(
|
|
62
|
-
from_wei_str(
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
if "estimate" in
|
|
80
|
-
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
|
-
|
|
87
|
-
return
|
|
85
|
+
var = VarInt("estimate", res.ok)
|
|
86
|
+
return calc_eth_expression(gas_expression, var)
|
|
88
87
|
|
|
89
88
|
|
|
90
|
-
def
|
|
89
|
+
def calc_eth_value_for_address(
|
|
91
90
|
*,
|
|
92
91
|
nodes: list[str],
|
|
93
|
-
|
|
92
|
+
value_expression: str,
|
|
94
93
|
address: str,
|
|
95
|
-
gas: int
|
|
96
|
-
|
|
94
|
+
gas: int,
|
|
95
|
+
max_fee: int,
|
|
97
96
|
log_prefix: str | None = None,
|
|
98
97
|
) -> int | None:
|
|
99
|
-
|
|
100
|
-
if "balance" in
|
|
101
|
-
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
114
|
+
def calc_erc20_value_for_address(
|
|
115
115
|
*,
|
|
116
116
|
nodes: list[str],
|
|
117
|
-
|
|
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
|
-
|
|
124
|
-
|
|
125
|
-
if "balance" in
|
|
126
|
-
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
|
-
|
|
133
|
-
return
|
|
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})
|