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.
Files changed (45) hide show
  1. mm_eth/abi.py +3 -3
  2. mm_eth/account.py +1 -1
  3. mm_eth/anvil.py +2 -2
  4. mm_eth/erc20.py +8 -7
  5. mm_eth/rpc.py +8 -8
  6. mm_eth/solc.py +2 -3
  7. mm_eth/tx.py +3 -5
  8. mm_eth/utils.py +11 -16
  9. mm_eth/vault.py +5 -5
  10. mm_eth-0.2.1.dist-info/METADATA +7 -0
  11. mm_eth-0.2.1.dist-info/RECORD +18 -0
  12. {mm_eth-0.1.5.dist-info → mm_eth-0.2.1.dist-info}/WHEEL +1 -1
  13. mm_eth/abi/zksync.json +0 -2092
  14. mm_eth/cli/__init__.py +0 -0
  15. mm_eth/cli/calcs.py +0 -88
  16. mm_eth/cli/cli.py +0 -237
  17. mm_eth/cli/cli_helpers.py +0 -195
  18. mm_eth/cli/cli_utils.py +0 -148
  19. mm_eth/cli/cmd/__init__.py +0 -0
  20. mm_eth/cli/cmd/balance_cmd.py +0 -59
  21. mm_eth/cli/cmd/balances_cmd.py +0 -121
  22. mm_eth/cli/cmd/call_contract_cmd.py +0 -44
  23. mm_eth/cli/cmd/config_example_cmd.py +0 -9
  24. mm_eth/cli/cmd/deploy_cmd.py +0 -41
  25. mm_eth/cli/cmd/encode_input_data_cmd.py +0 -10
  26. mm_eth/cli/cmd/mnemonic_cmd.py +0 -27
  27. mm_eth/cli/cmd/node_cmd.py +0 -47
  28. mm_eth/cli/cmd/private_key_cmd.py +0 -10
  29. mm_eth/cli/cmd/rpc_cmd.py +0 -81
  30. mm_eth/cli/cmd/send_contract_cmd.py +0 -247
  31. mm_eth/cli/cmd/solc_cmd.py +0 -25
  32. mm_eth/cli/cmd/token_cmd.py +0 -29
  33. mm_eth/cli/cmd/transfer_erc20_cmd.py +0 -275
  34. mm_eth/cli/cmd/transfer_eth_cmd.py +0 -252
  35. mm_eth/cli/cmd/vault_cmd.py +0 -16
  36. mm_eth/cli/config_examples/balances.yml +0 -15
  37. mm_eth/cli/config_examples/call_contract.yml +0 -5
  38. mm_eth/cli/config_examples/transfer_erc20.yml +0 -26
  39. mm_eth/cli/config_examples/transfer_eth.yml +0 -24
  40. mm_eth/cli/validators.py +0 -84
  41. mm_eth/services/__init__.py +0 -0
  42. mm_eth/zksync.py +0 -203
  43. mm_eth-0.1.5.dist-info/METADATA +0 -9
  44. mm_eth-0.1.5.dist-info/RECORD +0 -49
  45. 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)
@@ -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)
@@ -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)