mm-eth 0.1.5__py3-none-any.whl → 0.2.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 +3 -3
- mm_eth/account.py +1 -1
- mm_eth/anvil.py +2 -2
- mm_eth/erc20.py +8 -7
- mm_eth/rpc.py +8 -8
- mm_eth/solc.py +2 -3
- mm_eth/tx.py +3 -5
- mm_eth/utils.py +11 -16
- mm_eth/vault.py +5 -5
- mm_eth-0.2.1.dist-info/METADATA +7 -0
- mm_eth-0.2.1.dist-info/RECORD +18 -0
- {mm_eth-0.1.5.dist-info → mm_eth-0.2.1.dist-info}/WHEEL +1 -1
- mm_eth/abi/zksync.json +0 -2092
- mm_eth/cli/__init__.py +0 -0
- mm_eth/cli/calcs.py +0 -88
- mm_eth/cli/cli.py +0 -237
- mm_eth/cli/cli_helpers.py +0 -195
- mm_eth/cli/cli_utils.py +0 -148
- mm_eth/cli/cmd/__init__.py +0 -0
- mm_eth/cli/cmd/balance_cmd.py +0 -59
- mm_eth/cli/cmd/balances_cmd.py +0 -121
- mm_eth/cli/cmd/call_contract_cmd.py +0 -44
- mm_eth/cli/cmd/config_example_cmd.py +0 -9
- mm_eth/cli/cmd/deploy_cmd.py +0 -41
- mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
- mm_eth/cli/cmd/mnemonic_cmd.py +0 -27
- mm_eth/cli/cmd/node_cmd.py +0 -47
- mm_eth/cli/cmd/private_key_cmd.py +0 -10
- mm_eth/cli/cmd/rpc_cmd.py +0 -81
- mm_eth/cli/cmd/send_contract_cmd.py +0 -247
- mm_eth/cli/cmd/solc_cmd.py +0 -25
- mm_eth/cli/cmd/token_cmd.py +0 -29
- mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -275
- mm_eth/cli/cmd/transfer_eth_cmd.py +0 -252
- mm_eth/cli/cmd/vault_cmd.py +0 -16
- mm_eth/cli/config_examples/balances.yml +0 -15
- 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/cli/validators.py +0 -84
- mm_eth/services/__init__.py +0 -0
- mm_eth/zksync.py +0 -203
- mm_eth-0.1.5.dist-info/METADATA +0 -9
- mm_eth-0.1.5.dist-info/RECORD +0 -49
- mm_eth-0.1.5.dist-info/entry_points.txt +0 -2
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import time
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from typing import Self
|
|
5
|
-
|
|
6
|
-
from loguru import logger
|
|
7
|
-
from mm_std import Err, Ok, print_json, str_to_list, utc_now
|
|
8
|
-
from pydantic import Field, StrictStr, field_validator, model_validator
|
|
9
|
-
|
|
10
|
-
from mm_eth import abi, rpc
|
|
11
|
-
from mm_eth.account import create_private_keys_dict, private_to_address
|
|
12
|
-
from mm_eth.cli import calcs, cli_helpers, cli_utils, validators
|
|
13
|
-
from mm_eth.cli.cli_helpers import get_version
|
|
14
|
-
from mm_eth.tx import sign_tx
|
|
15
|
-
from mm_eth.utils import from_wei_str
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class Config(cli_utils.BaseConfig):
|
|
19
|
-
contract_address: str
|
|
20
|
-
function_signature: str
|
|
21
|
-
function_args: StrictStr = "[]"
|
|
22
|
-
nodes: list[StrictStr]
|
|
23
|
-
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 | None = None
|
|
30
|
-
gas: str
|
|
31
|
-
from_addresses: list[str]
|
|
32
|
-
delay: str | None = None # in seconds
|
|
33
|
-
round_ndigits: int = 5
|
|
34
|
-
log_debug: str | None = None
|
|
35
|
-
log_info: str | None = None
|
|
36
|
-
|
|
37
|
-
@field_validator("log_debug", "log_info", mode="before")
|
|
38
|
-
def log_validator(cls, v: str | None) -> str | None:
|
|
39
|
-
return validators.log_validator(v)
|
|
40
|
-
|
|
41
|
-
@field_validator("nodes", "from_addresses", mode="before")
|
|
42
|
-
def list_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
43
|
-
return validators.nodes_validator(v)
|
|
44
|
-
|
|
45
|
-
@field_validator("from_addresses", mode="before")
|
|
46
|
-
def from_addresses_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
47
|
-
return str_to_list(v, remove_comments=True, lower=True)
|
|
48
|
-
|
|
49
|
-
@field_validator("private_keys", mode="before")
|
|
50
|
-
def private_keys_validator(cls, v: str | list[str] | None) -> dict[str, str]:
|
|
51
|
-
if v is None:
|
|
52
|
-
return {}
|
|
53
|
-
if isinstance(v, str):
|
|
54
|
-
return create_private_keys_dict(str_to_list(v, unique=True, remove_comments=True))
|
|
55
|
-
return create_private_keys_dict(v)
|
|
56
|
-
|
|
57
|
-
# noinspection DuplicatedCode
|
|
58
|
-
@model_validator(mode="after")
|
|
59
|
-
def final_validator(self) -> Self:
|
|
60
|
-
# load private keys from file
|
|
61
|
-
if self.private_keys_file:
|
|
62
|
-
file = Path(self.private_keys_file).expanduser()
|
|
63
|
-
if not file.is_file():
|
|
64
|
-
raise ValueError("can't read private_keys_file")
|
|
65
|
-
for line in file.read_text().strip().split("\n"):
|
|
66
|
-
line = line.strip()
|
|
67
|
-
address = private_to_address(line)
|
|
68
|
-
if address is None:
|
|
69
|
-
raise ValueError("there is not a private key in private_keys_file")
|
|
70
|
-
self.private_keys[address.lower()] = line
|
|
71
|
-
|
|
72
|
-
# check that from_addresses is not empty
|
|
73
|
-
if not self.from_addresses:
|
|
74
|
-
raise ValueError("from_addresses is empty")
|
|
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 self.value is not None and not validators.is_valid_calc_var_wei_value(self.value, "balance"):
|
|
90
|
-
raise ValueError(f"wrong value: {self.value}")
|
|
91
|
-
|
|
92
|
-
# gas
|
|
93
|
-
if not validators.is_valid_calc_var_wei_value(self.gas, "estimate"):
|
|
94
|
-
raise ValueError(f"wrong gas: {self.gas}")
|
|
95
|
-
|
|
96
|
-
# delay
|
|
97
|
-
if not validators.is_valid_calc_decimal_value(self.delay):
|
|
98
|
-
raise ValueError(f"wrong delay: {self.delay}")
|
|
99
|
-
|
|
100
|
-
# function_args
|
|
101
|
-
if not validators.is_valid_calc_function_args(self.function_args):
|
|
102
|
-
raise ValueError(f"wrong function_args: {self.function_args}")
|
|
103
|
-
|
|
104
|
-
return self
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
# noinspection DuplicatedCode
|
|
108
|
-
def run(
|
|
109
|
-
config_path: str,
|
|
110
|
-
*,
|
|
111
|
-
print_balances: bool,
|
|
112
|
-
print_config: bool,
|
|
113
|
-
debug: bool,
|
|
114
|
-
no_receipt: bool,
|
|
115
|
-
emulate: bool,
|
|
116
|
-
) -> None:
|
|
117
|
-
config = cli_utils.read_config(Config, config_path)
|
|
118
|
-
if print_config:
|
|
119
|
-
print_json(config.model_dump(exclude={"private_keys"}))
|
|
120
|
-
exit(0)
|
|
121
|
-
|
|
122
|
-
cli_utils.init_logger(debug, config.log_debug, config.log_info)
|
|
123
|
-
cli_utils.check_nodes_for_chain_id(config.nodes, config.chain_id)
|
|
124
|
-
|
|
125
|
-
if print_balances:
|
|
126
|
-
cli_helpers.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits)
|
|
127
|
-
exit(0)
|
|
128
|
-
|
|
129
|
-
_run_transfers(config, no_receipt=no_receipt, emulate=emulate)
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
# noinspection DuplicatedCode
|
|
133
|
-
def _run_transfers(config: Config, *, no_receipt: bool, emulate: bool) -> None:
|
|
134
|
-
logger.info(f"started at {utc_now()} UTC")
|
|
135
|
-
logger.debug(f"config={config.model_dump(exclude={'private_keys'}) | {'version':get_version()}}")
|
|
136
|
-
cli_utils.check_private_keys(config.from_addresses, config.private_keys)
|
|
137
|
-
for i, from_address in enumerate(config.from_addresses):
|
|
138
|
-
_transfer(from_address=from_address, config=config, no_receipt=no_receipt, emulate=emulate)
|
|
139
|
-
if not emulate and config.delay is not None and i < len(config.from_addresses) - 1:
|
|
140
|
-
delay_value = calcs.calc_decimal_value(config.delay)
|
|
141
|
-
logger.debug(f"delay {delay_value} seconds")
|
|
142
|
-
time.sleep(float(delay_value))
|
|
143
|
-
logger.info(f"finished at {utc_now()} UTC")
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
# noinspection DuplicatedCode
|
|
147
|
-
def _transfer(*, from_address: str, config: Config, no_receipt: bool, emulate: bool) -> None:
|
|
148
|
-
log_prefix = f"{from_address}"
|
|
149
|
-
# get nonce
|
|
150
|
-
nonce = cli_helpers.get_nonce(config.nodes, from_address, log_prefix)
|
|
151
|
-
if nonce is None:
|
|
152
|
-
return
|
|
153
|
-
|
|
154
|
-
# get max_fee_per_gas
|
|
155
|
-
max_fee_per_gas = cli_helpers.calc_max_fee_per_gas(config.nodes, config.max_fee_per_gas, log_prefix)
|
|
156
|
-
if max_fee_per_gas is None:
|
|
157
|
-
return
|
|
158
|
-
|
|
159
|
-
# check max_fee_per_gas_limit
|
|
160
|
-
if cli_helpers.is_max_fee_per_gas_limit_exceeded(max_fee_per_gas, config.max_fee_per_gas_limit, log_prefix):
|
|
161
|
-
return
|
|
162
|
-
|
|
163
|
-
max_priority_fee_per_gas = calcs.calc_var_wei_value(config.max_priority_fee_per_gas)
|
|
164
|
-
|
|
165
|
-
# data
|
|
166
|
-
function_args = calcs.calc_function_args(config.function_args).replace("'", '"')
|
|
167
|
-
data = abi.encode_function_input_by_signature(config.function_signature, json.loads(function_args))
|
|
168
|
-
|
|
169
|
-
# get gas
|
|
170
|
-
gas = cli_helpers.calc_gas(
|
|
171
|
-
nodes=config.nodes,
|
|
172
|
-
gas=config.gas,
|
|
173
|
-
from_address=from_address,
|
|
174
|
-
to_address=config.contract_address,
|
|
175
|
-
value=None,
|
|
176
|
-
data=data,
|
|
177
|
-
log_prefix=log_prefix,
|
|
178
|
-
)
|
|
179
|
-
if gas is None:
|
|
180
|
-
return
|
|
181
|
-
|
|
182
|
-
# get value
|
|
183
|
-
value = None
|
|
184
|
-
if config.value is not None:
|
|
185
|
-
value = cli_helpers.calc_eth_value(
|
|
186
|
-
nodes=config.nodes,
|
|
187
|
-
value_str=config.value,
|
|
188
|
-
address=from_address,
|
|
189
|
-
gas=gas,
|
|
190
|
-
max_fee_per_gas=max_fee_per_gas,
|
|
191
|
-
log_prefix=log_prefix,
|
|
192
|
-
)
|
|
193
|
-
if value is None:
|
|
194
|
-
return
|
|
195
|
-
|
|
196
|
-
tx_params = {
|
|
197
|
-
"nonce": nonce,
|
|
198
|
-
"max_fee_per_gas": max_fee_per_gas,
|
|
199
|
-
"max_priority_fee_per_gas": max_priority_fee_per_gas,
|
|
200
|
-
"gas": gas,
|
|
201
|
-
"value": value,
|
|
202
|
-
"to": config.contract_address,
|
|
203
|
-
"chain_id": config.chain_id,
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
# emulate?
|
|
207
|
-
if emulate:
|
|
208
|
-
msg = f"{log_prefix}: emulate,"
|
|
209
|
-
if value is not None:
|
|
210
|
-
msg += f" value={from_wei_str(value, 'eth', config.round_ndigits)},"
|
|
211
|
-
msg += f" max_fee_per_gas={from_wei_str(max_fee_per_gas, 'gwei', config.round_ndigits)},"
|
|
212
|
-
msg += f" max_priority_fee_per_gas={from_wei_str(max_priority_fee_per_gas, 'gwei', config.round_ndigits)},"
|
|
213
|
-
msg += f" gas={gas}"
|
|
214
|
-
logger.info(msg)
|
|
215
|
-
return
|
|
216
|
-
|
|
217
|
-
logger.debug(f"{log_prefix}: tx_params={tx_params}")
|
|
218
|
-
signed_tx = sign_tx(
|
|
219
|
-
nonce=nonce,
|
|
220
|
-
max_fee_per_gas=max_fee_per_gas,
|
|
221
|
-
max_priority_fee_per_gas=max_priority_fee_per_gas,
|
|
222
|
-
gas=gas,
|
|
223
|
-
private_key=config.private_keys[from_address],
|
|
224
|
-
chain_id=config.chain_id,
|
|
225
|
-
value=value,
|
|
226
|
-
data=data,
|
|
227
|
-
to=config.contract_address,
|
|
228
|
-
)
|
|
229
|
-
res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
|
|
230
|
-
if isinstance(res, Err):
|
|
231
|
-
logger.info(f"{log_prefix}: send_error: {res.err}")
|
|
232
|
-
return
|
|
233
|
-
tx_hash = res.ok
|
|
234
|
-
|
|
235
|
-
if no_receipt:
|
|
236
|
-
msg = f"{log_prefix}: tx_hash={tx_hash}"
|
|
237
|
-
logger.info(msg)
|
|
238
|
-
else:
|
|
239
|
-
logger.debug(f"{log_prefix}: tx_hash={tx_hash}, wait receipt")
|
|
240
|
-
while True:
|
|
241
|
-
receipt_res = rpc.get_tx_status(config.nodes, tx_hash)
|
|
242
|
-
if isinstance(receipt_res, Ok):
|
|
243
|
-
status = "OK" if receipt_res.ok == 1 else "FAIL"
|
|
244
|
-
msg = f"{log_prefix}: tx_hash={tx_hash}, status={status}"
|
|
245
|
-
logger.info(msg)
|
|
246
|
-
break
|
|
247
|
-
time.sleep(1)
|
mm_eth/cli/cmd/solc_cmd.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
|
|
3
|
-
from mm_std import Err, PrintFormat, print_json, print_plain
|
|
4
|
-
from mm_std.fs import get_filename_without_extension
|
|
5
|
-
|
|
6
|
-
from mm_eth.cli import cli_helpers
|
|
7
|
-
from mm_eth.solc import solc
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
def run(contract_path: str, tmp_dir: str, print_format: PrintFormat) -> None:
|
|
11
|
-
contract_name = get_filename_without_extension(contract_path)
|
|
12
|
-
res = solc(contract_name, contract_path, tmp_dir)
|
|
13
|
-
if isinstance(res, Err):
|
|
14
|
-
cli_helpers.fatal(res.err)
|
|
15
|
-
|
|
16
|
-
bin_ = res.ok.bin
|
|
17
|
-
abi = res.ok.abi
|
|
18
|
-
|
|
19
|
-
if print_format == PrintFormat.JSON:
|
|
20
|
-
print_json({"bin": bin_, "abi": json.loads(abi)})
|
|
21
|
-
else:
|
|
22
|
-
print_plain("bin:")
|
|
23
|
-
print_plain(bin_)
|
|
24
|
-
print_plain("abi:")
|
|
25
|
-
print_plain(abi)
|
mm_eth/cli/cmd/token_cmd.py
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
from mm_std import Err, Ok, print_plain
|
|
2
|
-
|
|
3
|
-
from mm_eth import erc20, rpc
|
|
4
|
-
from mm_eth.cli.cli_utils import public_rpc_url
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def run(rpc_url: str, token_address: str) -> None:
|
|
8
|
-
rpc_url = public_rpc_url(rpc_url)
|
|
9
|
-
name = erc20.get_name(rpc_url, token_address).ok_or_err()
|
|
10
|
-
symbol = erc20.get_symbol(rpc_url, token_address).ok_or_err()
|
|
11
|
-
decimals = erc20.get_decimals(rpc_url, token_address).ok_or_err()
|
|
12
|
-
transfer_count = _calc_transfer_events(rpc_url, 100, token_address)
|
|
13
|
-
|
|
14
|
-
print_plain(f"name: {name}")
|
|
15
|
-
print_plain(f"symbol: {symbol}")
|
|
16
|
-
print_plain(f"decimals: {decimals}")
|
|
17
|
-
print_plain(f"transfer_count: {transfer_count}")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def _calc_transfer_events(rpc_url: str, last_block_limit: int, token_address: str) -> int | str:
|
|
21
|
-
current_block_res = rpc.eth_block_number(rpc_url)
|
|
22
|
-
if isinstance(current_block_res, Err):
|
|
23
|
-
return current_block_res.err
|
|
24
|
-
current_block = current_block_res.ok
|
|
25
|
-
|
|
26
|
-
res = erc20.get_transfer_event_logs(rpc_url, token_address, current_block - last_block_limit, current_block)
|
|
27
|
-
if isinstance(res, Ok):
|
|
28
|
-
return len(res.ok)
|
|
29
|
-
return res.err
|
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import time
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from typing import Self
|
|
4
|
-
|
|
5
|
-
from loguru import logger
|
|
6
|
-
from mm_std import Err, Ok, fatal, print_json, str_to_list, utc_now
|
|
7
|
-
from pydantic import Field, StrictStr, field_validator, model_validator
|
|
8
|
-
|
|
9
|
-
from mm_eth import erc20, rpc
|
|
10
|
-
from mm_eth.account import create_private_keys_dict, private_to_address
|
|
11
|
-
from mm_eth.cli import calcs, cli_helpers, cli_utils, validators
|
|
12
|
-
from mm_eth.cli.cli_helpers import get_version
|
|
13
|
-
from mm_eth.cli.cli_utils import BaseConfig
|
|
14
|
-
from mm_eth.utils import from_wei_str
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class Config(BaseConfig):
|
|
18
|
-
class Tx(BaseConfig):
|
|
19
|
-
from_address: str
|
|
20
|
-
to_address: str
|
|
21
|
-
|
|
22
|
-
token: str
|
|
23
|
-
decimals: int
|
|
24
|
-
nodes: list[StrictStr]
|
|
25
|
-
chain_id: int
|
|
26
|
-
private_keys: dict[str, str] = Field(default_factory=dict)
|
|
27
|
-
private_keys_file: str | None = None
|
|
28
|
-
max_fee_per_gas: str
|
|
29
|
-
max_fee_per_gas_limit: str | None = None
|
|
30
|
-
max_priority_fee_per_gas: str
|
|
31
|
-
value: str
|
|
32
|
-
value_min_limit: str | None = None
|
|
33
|
-
gas: str
|
|
34
|
-
addresses_map: str | None = None
|
|
35
|
-
addresses_from_file: str | None = None
|
|
36
|
-
addresses_to_file: str | None = None
|
|
37
|
-
delay: str | None = None # in seconds
|
|
38
|
-
round_ndigits: int = 5
|
|
39
|
-
log_debug: str | None = None
|
|
40
|
-
log_info: str | None = None
|
|
41
|
-
txs: list[Tx] = Field(default_factory=list)
|
|
42
|
-
|
|
43
|
-
@property
|
|
44
|
-
def from_addresses(self) -> list[str]:
|
|
45
|
-
return [tx.from_address for tx in self.txs]
|
|
46
|
-
|
|
47
|
-
@field_validator("log_debug", "log_info", mode="before")
|
|
48
|
-
def log_validator(cls, v: str | None) -> str | None:
|
|
49
|
-
return validators.log_validator(v)
|
|
50
|
-
|
|
51
|
-
@field_validator("nodes", mode="before")
|
|
52
|
-
def nodes_validator(cls, v: str | list[str] | None) -> list[str]:
|
|
53
|
-
return validators.nodes_validator(v)
|
|
54
|
-
|
|
55
|
-
@field_validator("private_keys", mode="before")
|
|
56
|
-
def private_keys_validator(cls, v: str | list[str] | None) -> dict[str, str]:
|
|
57
|
-
if v is None:
|
|
58
|
-
return {}
|
|
59
|
-
if isinstance(v, str):
|
|
60
|
-
return create_private_keys_dict(str_to_list(v, unique=True, remove_comments=True))
|
|
61
|
-
return create_private_keys_dict(v)
|
|
62
|
-
|
|
63
|
-
# noinspection DuplicatedCode
|
|
64
|
-
@model_validator(mode="after")
|
|
65
|
-
def final_validator(self) -> Self:
|
|
66
|
-
# load private keys from file
|
|
67
|
-
if self.private_keys_file:
|
|
68
|
-
file = Path(self.private_keys_file).expanduser()
|
|
69
|
-
if not file.is_file():
|
|
70
|
-
raise ValueError("can't read private_keys_file")
|
|
71
|
-
for line in file.read_text().strip().split("\n"):
|
|
72
|
-
line = line.strip()
|
|
73
|
-
address = private_to_address(line)
|
|
74
|
-
if address is None:
|
|
75
|
-
raise ValueError("there is not a private key in private_keys_file")
|
|
76
|
-
self.private_keys[address.lower()] = line
|
|
77
|
-
|
|
78
|
-
# max_fee_per_gas
|
|
79
|
-
if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas, "base"):
|
|
80
|
-
raise ValueError(f"wrong max_fee_per_gas: {self.max_fee_per_gas}")
|
|
81
|
-
|
|
82
|
-
# max_fee_per_gas_limit
|
|
83
|
-
if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas_limit, "base"):
|
|
84
|
-
raise ValueError(f"wrong max_fee_per_gas_limit: {self.max_fee_per_gas_limit}")
|
|
85
|
-
|
|
86
|
-
# max_priority_fee_per_gas
|
|
87
|
-
if not validators.is_valid_calc_var_wei_value(self.max_priority_fee_per_gas):
|
|
88
|
-
raise ValueError(f"wrong max_priority_fee_per_gas: {self.max_priority_fee_per_gas}")
|
|
89
|
-
|
|
90
|
-
# value
|
|
91
|
-
if not validators.is_valid_calc_var_wei_value(self.value, "balance", decimals=self.decimals):
|
|
92
|
-
raise ValueError(f"wrong value: {self.value}")
|
|
93
|
-
|
|
94
|
-
# value_min_limit
|
|
95
|
-
if not validators.is_valid_calc_var_wei_value(self.value_min_limit, decimals=self.decimals):
|
|
96
|
-
raise ValueError(f"wrong value_min_limit: {self.value_min_limit}")
|
|
97
|
-
|
|
98
|
-
# gas
|
|
99
|
-
if not validators.is_valid_calc_var_wei_value(self.gas, "estimate"):
|
|
100
|
-
raise ValueError(f"wrong gas: {self.gas}")
|
|
101
|
-
|
|
102
|
-
# delay
|
|
103
|
-
if not validators.is_valid_calc_decimal_value(self.delay):
|
|
104
|
-
raise ValueError(f"wrong delay: {self.delay}")
|
|
105
|
-
|
|
106
|
-
# txs
|
|
107
|
-
if self.addresses_map:
|
|
108
|
-
for tx in cli_utils.load_tx_addresses_from_str(self.addresses_map):
|
|
109
|
-
self.txs.append(Config.Tx(from_address=tx[0], to_address=tx[1]))
|
|
110
|
-
if self.addresses_from_file and self.addresses_to_file:
|
|
111
|
-
self.txs.extend(
|
|
112
|
-
Config.Tx(from_address=tx[0], to_address=tx[1])
|
|
113
|
-
for tx in cli_utils.load_tx_addresses_from_files(self.addresses_from_file, self.addresses_to_file)
|
|
114
|
-
)
|
|
115
|
-
if not self.txs:
|
|
116
|
-
raise ValueError("txs is empty")
|
|
117
|
-
|
|
118
|
-
return self
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# noinspection DuplicatedCode
|
|
122
|
-
def run(
|
|
123
|
-
config_path: str,
|
|
124
|
-
*,
|
|
125
|
-
print_balances: bool,
|
|
126
|
-
print_config: bool,
|
|
127
|
-
debug: bool,
|
|
128
|
-
no_receipt: bool,
|
|
129
|
-
emulate: bool,
|
|
130
|
-
) -> None:
|
|
131
|
-
config = cli_utils.read_config(Config, config_path)
|
|
132
|
-
if print_config:
|
|
133
|
-
print_json(config.model_dump(exclude={"private_keys", "addresses_map"}))
|
|
134
|
-
exit(0)
|
|
135
|
-
|
|
136
|
-
cli_utils.init_logger(debug, config.log_debug, config.log_info)
|
|
137
|
-
cli_utils.check_nodes_for_chain_id(config.nodes, config.chain_id)
|
|
138
|
-
|
|
139
|
-
# check decimals
|
|
140
|
-
res = erc20.get_decimals(config.nodes[0], config.token)
|
|
141
|
-
if isinstance(res, Err):
|
|
142
|
-
fatal(f"can't get token decimals: {res.err}")
|
|
143
|
-
if res.ok != config.decimals:
|
|
144
|
-
fatal(f"config.decimals is wrong: {config.decimals} != {res.ok}")
|
|
145
|
-
|
|
146
|
-
if print_balances:
|
|
147
|
-
cli_helpers.print_balances(
|
|
148
|
-
config.nodes,
|
|
149
|
-
config.from_addresses,
|
|
150
|
-
token_address=config.token,
|
|
151
|
-
token_decimals=config.decimals,
|
|
152
|
-
round_ndigits=config.round_ndigits,
|
|
153
|
-
)
|
|
154
|
-
exit(0)
|
|
155
|
-
|
|
156
|
-
return _run_transfers(config, no_receipt=no_receipt, emulate=emulate)
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
# noinspection DuplicatedCode
|
|
160
|
-
def _run_transfers(config: Config, *, no_receipt: bool, emulate: bool) -> None:
|
|
161
|
-
logger.info(f"started at {utc_now()} UTC")
|
|
162
|
-
logger.debug(f"config={config.model_dump(exclude={'private_keys', 'addresses_map'}) | {'version':get_version()}}")
|
|
163
|
-
cli_utils.check_private_keys(config.from_addresses, config.private_keys)
|
|
164
|
-
for i, tx in enumerate(config.txs):
|
|
165
|
-
_transfer(from_address=tx.from_address, to_address=tx.to_address, config=config, no_receipt=no_receipt, emulate=emulate)
|
|
166
|
-
if not emulate and config.delay is not None and i < len(config.txs) - 1:
|
|
167
|
-
delay_value = calcs.calc_decimal_value(config.delay)
|
|
168
|
-
logger.debug(f"delay {delay_value} seconds")
|
|
169
|
-
time.sleep(float(delay_value))
|
|
170
|
-
logger.info(f"finished at {utc_now()} UTC")
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
# noinspection DuplicatedCode
|
|
174
|
-
def _transfer(*, from_address: str, to_address: str, config: Config, no_receipt: bool, emulate: bool) -> None:
|
|
175
|
-
log_prefix = f"{from_address}->{to_address}"
|
|
176
|
-
# get nonce
|
|
177
|
-
nonce = cli_helpers.get_nonce(config.nodes, from_address, log_prefix)
|
|
178
|
-
if nonce is None:
|
|
179
|
-
return
|
|
180
|
-
|
|
181
|
-
# get max_fee_per_gas
|
|
182
|
-
max_fee_per_gas = cli_helpers.calc_max_fee_per_gas(config.nodes, config.max_fee_per_gas, log_prefix)
|
|
183
|
-
if max_fee_per_gas is None:
|
|
184
|
-
return
|
|
185
|
-
|
|
186
|
-
# check max_fee_per_gas_limit
|
|
187
|
-
if cli_helpers.is_max_fee_per_gas_limit_exceeded(max_fee_per_gas, config.max_fee_per_gas_limit, log_prefix):
|
|
188
|
-
return
|
|
189
|
-
|
|
190
|
-
# get gas
|
|
191
|
-
gas = cli_helpers.calc_gas(
|
|
192
|
-
nodes=config.nodes,
|
|
193
|
-
gas=config.gas,
|
|
194
|
-
from_address=from_address,
|
|
195
|
-
to_address=config.token,
|
|
196
|
-
data=erc20.encode_transfer_input_data(to_address, 1234),
|
|
197
|
-
log_prefix=log_prefix,
|
|
198
|
-
)
|
|
199
|
-
if gas is None:
|
|
200
|
-
return
|
|
201
|
-
|
|
202
|
-
# get value
|
|
203
|
-
value = cli_helpers.calc_erc20_value(
|
|
204
|
-
nodes=config.nodes,
|
|
205
|
-
value_str=config.value,
|
|
206
|
-
wallet_address=from_address,
|
|
207
|
-
token_address=config.token,
|
|
208
|
-
decimals=config.decimals,
|
|
209
|
-
log_prefix=log_prefix,
|
|
210
|
-
)
|
|
211
|
-
if value is None:
|
|
212
|
-
return
|
|
213
|
-
|
|
214
|
-
# value_min_limit
|
|
215
|
-
if cli_helpers.is_value_less_min_limit(
|
|
216
|
-
config.value_min_limit,
|
|
217
|
-
value,
|
|
218
|
-
"t",
|
|
219
|
-
decimals=config.decimals,
|
|
220
|
-
log_prefix=log_prefix,
|
|
221
|
-
):
|
|
222
|
-
return
|
|
223
|
-
|
|
224
|
-
max_priority_fee_per_gas = calcs.calc_var_wei_value(config.max_priority_fee_per_gas)
|
|
225
|
-
tx_params = {
|
|
226
|
-
"nonce": nonce,
|
|
227
|
-
"max_fee_per_gas": max_fee_per_gas,
|
|
228
|
-
"max_priority_fee_per_gas": max_priority_fee_per_gas,
|
|
229
|
-
"gas": gas,
|
|
230
|
-
"value": value,
|
|
231
|
-
"to": to_address,
|
|
232
|
-
"chain_id": config.chain_id,
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
# emulate?
|
|
236
|
-
if emulate:
|
|
237
|
-
msg = f"{log_prefix}: emulate,"
|
|
238
|
-
msg += f" value={from_wei_str(value, 't',decimals=config.decimals, round_ndigits=config.round_ndigits)},"
|
|
239
|
-
msg += f" max_fee_per_gas={from_wei_str(max_fee_per_gas, 'gwei', config.round_ndigits)},"
|
|
240
|
-
msg += f" max_priority_fee_per_gas={from_wei_str(max_priority_fee_per_gas, 'gwei', config.round_ndigits)},"
|
|
241
|
-
msg += f" gas={gas}"
|
|
242
|
-
logger.info(msg)
|
|
243
|
-
return
|
|
244
|
-
|
|
245
|
-
logger.debug(f"{log_prefix}: tx_params={tx_params}")
|
|
246
|
-
signed_tx = erc20.sign_transfer_tx(
|
|
247
|
-
nonce=nonce,
|
|
248
|
-
max_fee_per_gas=max_fee_per_gas,
|
|
249
|
-
max_priority_fee_per_gas=max_priority_fee_per_gas,
|
|
250
|
-
gas_limit=gas,
|
|
251
|
-
private_key=config.private_keys[from_address],
|
|
252
|
-
chain_id=config.chain_id,
|
|
253
|
-
value=value,
|
|
254
|
-
token_address=config.token,
|
|
255
|
-
recipient_address=to_address,
|
|
256
|
-
)
|
|
257
|
-
res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
|
|
258
|
-
if isinstance(res, Err):
|
|
259
|
-
logger.info(f"{log_prefix}: send_error: {res.err}")
|
|
260
|
-
return
|
|
261
|
-
tx_hash = res.ok
|
|
262
|
-
|
|
263
|
-
if no_receipt:
|
|
264
|
-
msg = f"{log_prefix}: tx_hash={tx_hash}, value={from_wei_str(value, 't',decimals=config.decimals, round_ndigits=config.round_ndigits)}" # noqa
|
|
265
|
-
logger.info(msg)
|
|
266
|
-
else:
|
|
267
|
-
logger.debug(f"{log_prefix}: tx_hash={tx_hash}, wait receipt")
|
|
268
|
-
while True:
|
|
269
|
-
receipt_res = rpc.get_tx_status(config.nodes, tx_hash)
|
|
270
|
-
if isinstance(receipt_res, Ok):
|
|
271
|
-
status = "OK" if receipt_res.ok == 1 else "FAIL"
|
|
272
|
-
msg = f"{log_prefix}: tx_hash={tx_hash}, value={from_wei_str(value, 't', decimals=config.decimals, round_ndigits=config.round_ndigits)}, status={status}" # noqa
|
|
273
|
-
logger.info(msg)
|
|
274
|
-
break
|
|
275
|
-
time.sleep(1)
|