mm-eth 0.1.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.
Files changed (50) hide show
  1. mm_eth/__init__.py +0 -0
  2. mm_eth/abi/zksync.json +2092 -0
  3. mm_eth/abi.py +130 -0
  4. mm_eth/account.py +70 -0
  5. mm_eth/anvil.py +56 -0
  6. mm_eth/cli/__init__.py +0 -0
  7. mm_eth/cli/calcs.py +88 -0
  8. mm_eth/cli/cli.py +233 -0
  9. mm_eth/cli/cli_helpers.py +195 -0
  10. mm_eth/cli/cli_utils.py +150 -0
  11. mm_eth/cli/cmd/__init__.py +0 -0
  12. mm_eth/cli/cmd/balance_cmd.py +59 -0
  13. mm_eth/cli/cmd/balances_cmd.py +121 -0
  14. mm_eth/cli/cmd/call_contract_cmd.py +44 -0
  15. mm_eth/cli/cmd/config_example_cmd.py +9 -0
  16. mm_eth/cli/cmd/deploy_cmd.py +41 -0
  17. mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
  18. mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
  19. mm_eth/cli/cmd/node_cmd.py +47 -0
  20. mm_eth/cli/cmd/private_key_cmd.py +10 -0
  21. mm_eth/cli/cmd/rpc_cmd.py +81 -0
  22. mm_eth/cli/cmd/send_contract_cmd.py +247 -0
  23. mm_eth/cli/cmd/solc_cmd.py +25 -0
  24. mm_eth/cli/cmd/token_cmd.py +29 -0
  25. mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
  26. mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
  27. mm_eth/cli/cmd/vault_cmd.py +16 -0
  28. mm_eth/cli/config_examples/balances.yml +15 -0
  29. mm_eth/cli/config_examples/call_contract.yml +5 -0
  30. mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
  31. mm_eth/cli/config_examples/transfer_eth.yml +24 -0
  32. mm_eth/cli/validators.py +84 -0
  33. mm_eth/deploy.py +20 -0
  34. mm_eth/ens.py +16 -0
  35. mm_eth/erc20.py +240 -0
  36. mm_eth/ethernodes.py +34 -0
  37. mm_eth/py.typed +0 -0
  38. mm_eth/rpc.py +478 -0
  39. mm_eth/services/__init__.py +0 -0
  40. mm_eth/solc.py +34 -0
  41. mm_eth/tx.py +164 -0
  42. mm_eth/types.py +5 -0
  43. mm_eth/utils.py +245 -0
  44. mm_eth/vault.py +38 -0
  45. mm_eth/zksync.py +203 -0
  46. mm_eth-0.1.0.dist-info/METADATA +24 -0
  47. mm_eth-0.1.0.dist-info/RECORD +50 -0
  48. mm_eth-0.1.0.dist-info/WHEEL +5 -0
  49. mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
  50. mm_eth-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,275 @@
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)
@@ -0,0 +1,252 @@
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, print_json, str_to_list, utc_now
7
+ from pydantic import Field, StrictStr, field_validator, model_validator
8
+
9
+ from mm_eth import 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.tx import sign_tx
15
+ from mm_eth.utils import from_wei_str
16
+
17
+
18
+ class Config(BaseConfig):
19
+ class Tx(BaseConfig):
20
+ from_address: str
21
+ to_address: str
22
+
23
+ nodes: list[StrictStr]
24
+ chain_id: int
25
+ private_keys: dict[str, str] = Field(default_factory=dict)
26
+ private_keys_file: str | None = None
27
+ max_fee_per_gas: str
28
+ max_fee_per_gas_limit: str | None = None
29
+ max_priority_fee_per_gas: str
30
+ value: str
31
+ value_min_limit: str | None = None
32
+ gas: str
33
+ addresses_map: str | None = None
34
+ addresses_from_file: str | None = None
35
+ addresses_to_file: str | None = None
36
+ delay: str | None = None # in seconds
37
+ round_ndigits: int = 5
38
+ log_debug: str | None = None
39
+ log_info: str | None = None
40
+ txs: list[Tx] = Field(default_factory=list)
41
+
42
+ @property
43
+ def from_addresses(self) -> list[str]:
44
+ return [tx.from_address for tx in self.txs]
45
+
46
+ @field_validator("log_debug", "log_info", mode="before")
47
+ def log_validator(cls, v: str | None) -> str | None:
48
+ return validators.log_validator(v)
49
+
50
+ @field_validator("nodes", mode="before")
51
+ def nodes_validator(cls, v: str | list[str] | None) -> list[str]:
52
+ return validators.nodes_validator(v)
53
+
54
+ @field_validator("private_keys", mode="before")
55
+ def private_keys_validator(cls, v: str | list[str] | None) -> dict[str, str]:
56
+ if v is None:
57
+ return {}
58
+ if isinstance(v, str):
59
+ return create_private_keys_dict(str_to_list(v, unique=True, remove_comments=True))
60
+ return create_private_keys_dict(v)
61
+
62
+ # noinspection DuplicatedCode
63
+ @model_validator(mode="after")
64
+ def final_validator(self) -> Self:
65
+ # load private keys from file
66
+ if self.private_keys_file:
67
+ file = Path(self.private_keys_file).expanduser()
68
+ if not file.is_file():
69
+ raise ValueError("can't read private_keys_file")
70
+ for line in file.read_text().strip().split("\n"):
71
+ line = line.strip()
72
+ address = private_to_address(line)
73
+ if address is None:
74
+ raise ValueError("there is not a private key in private_keys_file")
75
+ self.private_keys[address.lower()] = line
76
+
77
+ # max_fee_per_gas
78
+ if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas, "base"):
79
+ raise ValueError(f"wrong max_fee_per_gas: {self.max_fee_per_gas}")
80
+
81
+ # max_fee_per_gas_limit
82
+ if not validators.is_valid_calc_var_wei_value(self.max_fee_per_gas_limit, "base"):
83
+ raise ValueError(f"wrong max_fee_per_gas_limit: {self.max_fee_per_gas_limit}")
84
+
85
+ # max_priority_fee_per_gas
86
+ if not validators.is_valid_calc_var_wei_value(self.max_priority_fee_per_gas):
87
+ raise ValueError(f"wrong max_priority_fee_per_gas: {self.max_priority_fee_per_gas}")
88
+
89
+ # value
90
+ if not validators.is_valid_calc_var_wei_value(self.value, "balance"):
91
+ raise ValueError(f"wrong value: {self.value}")
92
+
93
+ # value_min_limit
94
+ if not validators.is_valid_calc_var_wei_value(self.value_min_limit):
95
+ raise ValueError(f"wrong value_min_limit: {self.value_min_limit}")
96
+
97
+ # gas
98
+ if not validators.is_valid_calc_var_wei_value(self.gas, "estimate"):
99
+ raise ValueError(f"wrong gas: {self.gas}")
100
+
101
+ # delay
102
+ if not validators.is_valid_calc_decimal_value(self.delay):
103
+ raise ValueError(f"wrong delay: {self.delay}")
104
+
105
+ # txs
106
+ if self.addresses_map:
107
+ for tx in cli_utils.load_tx_addresses_from_str(self.addresses_map):
108
+ self.txs.append(Config.Tx(from_address=tx[0], to_address=tx[1]))
109
+ if self.addresses_from_file and self.addresses_to_file:
110
+ self.txs.extend(
111
+ Config.Tx(from_address=tx[0], to_address=tx[1])
112
+ for tx in cli_utils.load_tx_addresses_from_files(self.addresses_from_file, self.addresses_to_file)
113
+ )
114
+ if not self.txs:
115
+ raise ValueError("txs is empty")
116
+
117
+ return self
118
+
119
+
120
+ def run(
121
+ config_path: str,
122
+ *,
123
+ print_balances: bool,
124
+ print_config: bool,
125
+ debug: bool,
126
+ no_receipt: bool,
127
+ emulate: bool,
128
+ ) -> None:
129
+ config = cli_utils.read_config(Config, config_path)
130
+ if print_config:
131
+ print_json(config.model_dump(exclude={"private_keys", "addresses_map"}))
132
+ exit(0)
133
+
134
+ cli_utils.init_logger(debug, config.log_debug, config.log_info)
135
+ cli_utils.check_nodes_for_chain_id(config.nodes, config.chain_id)
136
+
137
+ if print_balances:
138
+ cli_helpers.print_balances(config.nodes, config.from_addresses, round_ndigits=config.round_ndigits)
139
+ exit(0)
140
+
141
+ return _run_transfers(config, no_receipt=no_receipt, emulate=emulate)
142
+
143
+
144
+ # noinspection DuplicatedCode
145
+ def _run_transfers(config: Config, *, no_receipt: bool, emulate: bool) -> None:
146
+ logger.info(f"started at {utc_now()} UTC")
147
+ logger.debug(f"config={config.model_dump(exclude={'private_keys', 'addresses_map'}) | {'version': get_version()} }")
148
+ cli_utils.check_private_keys(config.from_addresses, config.private_keys)
149
+ for i, tx in enumerate(config.txs):
150
+ _transfer(from_address=tx.from_address, to_address=tx.to_address, config=config, no_receipt=no_receipt, emulate=emulate)
151
+ if not emulate and config.delay is not None and i < len(config.txs) - 1:
152
+ delay_value = calcs.calc_decimal_value(config.delay)
153
+ logger.debug(f"delay {delay_value} seconds")
154
+ time.sleep(float(delay_value))
155
+ logger.info(f"finished at {utc_now()} UTC")
156
+
157
+
158
+ # noinspection DuplicatedCode
159
+ def _transfer(*, from_address: str, to_address: str, config: Config, no_receipt: bool, emulate: bool) -> None:
160
+ log_prefix = f"{from_address}->{to_address}"
161
+ # get nonce
162
+ nonce = cli_helpers.get_nonce(config.nodes, from_address, log_prefix)
163
+ if nonce is None:
164
+ return
165
+
166
+ # get max_fee_per_gas
167
+ max_fee_per_gas = cli_helpers.calc_max_fee_per_gas(config.nodes, config.max_fee_per_gas, log_prefix)
168
+ if max_fee_per_gas is None:
169
+ return
170
+
171
+ # check max_fee_per_gas_limit
172
+ if cli_helpers.is_max_fee_per_gas_limit_exceeded(max_fee_per_gas, config.max_fee_per_gas_limit, log_prefix):
173
+ return
174
+
175
+ # get gas
176
+ gas = cli_helpers.calc_gas(
177
+ nodes=config.nodes,
178
+ gas=config.gas,
179
+ from_address=from_address,
180
+ to_address=to_address,
181
+ value=123,
182
+ log_prefix=log_prefix,
183
+ )
184
+ if gas is None:
185
+ return
186
+
187
+ # get value
188
+ value = cli_helpers.calc_eth_value(
189
+ nodes=config.nodes,
190
+ value_str=config.value,
191
+ address=from_address,
192
+ gas=gas,
193
+ max_fee_per_gas=max_fee_per_gas,
194
+ log_prefix=log_prefix,
195
+ )
196
+ if value is None:
197
+ return
198
+
199
+ # value_min_limit
200
+ if cli_helpers.is_value_less_min_limit(config.value_min_limit, value, "eth", log_prefix=log_prefix):
201
+ return
202
+
203
+ max_priority_fee_per_gas = calcs.calc_var_wei_value(config.max_priority_fee_per_gas)
204
+ tx_params = {
205
+ "nonce": nonce,
206
+ "max_fee_per_gas": max_fee_per_gas,
207
+ "max_priority_fee_per_gas": max_priority_fee_per_gas,
208
+ "gas": gas,
209
+ "value": value,
210
+ "to": to_address,
211
+ "chain_id": config.chain_id,
212
+ }
213
+
214
+ # emulate?
215
+ if emulate:
216
+ msg = f"{log_prefix}: emulate, value={from_wei_str(value, 'eth', config.round_ndigits)},"
217
+ msg += f" max_fee_per_gas={from_wei_str(max_fee_per_gas, 'gwei', config.round_ndigits)},"
218
+ msg += f" max_priority_fee_per_gas={from_wei_str(max_priority_fee_per_gas, 'gwei', config.round_ndigits)},"
219
+ msg += f" gas={gas}"
220
+ logger.info(msg)
221
+ return
222
+
223
+ logger.debug(f"{log_prefix}: tx_params={tx_params}")
224
+ signed_tx = sign_tx(
225
+ nonce=nonce,
226
+ max_fee_per_gas=max_fee_per_gas,
227
+ max_priority_fee_per_gas=max_priority_fee_per_gas,
228
+ gas=gas,
229
+ private_key=config.private_keys[from_address],
230
+ chain_id=config.chain_id,
231
+ value=value,
232
+ to=to_address,
233
+ )
234
+ res = rpc.eth_send_raw_transaction(config.nodes, signed_tx.raw_tx, attempts=5)
235
+ if isinstance(res, Err):
236
+ logger.info(f"{log_prefix}: send_error: {res.err}")
237
+ return
238
+ tx_hash = res.ok
239
+
240
+ if no_receipt:
241
+ msg = f"{log_prefix}: tx_hash={tx_hash}, value={from_wei_str(value, 'ether', round_ndigits=config.round_ndigits)}"
242
+ logger.info(msg)
243
+ else:
244
+ logger.debug(f"{log_prefix}: tx_hash={tx_hash}, wait receipt")
245
+ while True:
246
+ receipt_res = rpc.get_tx_status(config.nodes, tx_hash)
247
+ if isinstance(receipt_res, Ok):
248
+ status = "OK" if receipt_res.ok == 1 else "FAIL"
249
+ msg = f"{log_prefix}: tx_hash={tx_hash}, value={from_wei_str(value, 'ether', round_ndigits=config.round_ndigits)}, status={status}" # noqa
250
+ logger.info(msg)
251
+ break
252
+ time.sleep(1)
@@ -0,0 +1,16 @@
1
+ from mm_std import fatal, print_plain
2
+
3
+ from mm_eth import vault
4
+ from mm_eth.cli import cli_utils
5
+
6
+
7
+ def run(keys_url: str, vault_token: str, keys_file: str) -> None:
8
+ private_keys = cli_utils.load_private_keys_from_file(keys_file)
9
+ if not private_keys:
10
+ fatal("private keys not found")
11
+
12
+ res = vault.set_keys_from_vault(keys_url, vault_token, private_keys)
13
+ if res.is_ok() and res.ok is True:
14
+ print_plain(f"saved {len(private_keys)} private keys to the vault")
15
+ else:
16
+ fatal(f"error: {res.err}")
@@ -0,0 +1,15 @@
1
+ addresses: |
2
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623
3
+ 0x58487485c3858109f5A37e42546FE87473f79a4b
4
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309
5
+
6
+ tokens: |
7
+ 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # USDT
8
+ 0x6a55fe4884DE7E1d904BdC47A3BA092240ae9B39 # USDC
9
+
10
+ nodes: |
11
+ https://arb1.arbitrum.io/rpc
12
+ https://rpc.arb1.arbitrum.gateway.fm
13
+ https://arbitrum-one.publicnode.com
14
+
15
+ round_ndigits: 3
@@ -0,0 +1,5 @@
1
+ contract_address: "0xBa985cad26658EB00eA42aCc7516aed52e7a8AcC"
2
+ function_signature: balanceOf(address)
3
+ function_args: "['0x83aC43147BA5dAA5abc4ccEA84F2B8000bA82f26']"
4
+ outputs_types: uint256 # optional
5
+ node: https://rpc.eth.gateway.fm
@@ -0,0 +1,26 @@
1
+ token: "0x60631C856303731BE4deb81C0303F80B652aA5b4" # USDC
2
+ decimals: 6
3
+ max_fee_per_gas: 1.2base + 1gwei + random(1,200) # supported var_name=base
4
+ max_fee_per_gas_limit: 10.1gwei - random(1,10) # optional
5
+ max_priority_fee_per_gas: 1gwei + random(1,12)
6
+ gas: estimate + random(100,200) - 19 # supported var_name=estimate
7
+ value: 0.5balance - random(1.5t,3t) + 11gwei # supported var_name=balance
8
+ value_min_limit: 0.5t + random(1,2) - 7
9
+ addresses_map: |
10
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b
11
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # can comment here
12
+ # and here
13
+ addresses_from_file: ~/path/from.txt
14
+ addresses_to_file: ~/path/to.txt
15
+ delay: random(1.123,10) + 1 # secs, optional
16
+ private_keys: | # optional, private_keys or private_keys_file must be used
17
+ 0x7bb5b9c0ba991275f84b796b4d25fd3a8d7320911f50fade85410e7a2b000632
18
+ 0xb7e0b671e176b04ceb0897a698d34771bfe9acf29273dc52a141be6e97145a00
19
+ private_keys_file: ~/path/private_keys.txt # optional, private_keys or private_keys_file must be used
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
@@ -0,0 +1,24 @@
1
+ max_fee_per_gas: 1.2base + 1gwei + random(1,200) # supported var_name=base
2
+ max_fee_per_gas_limit: 10.1gwei - random(1,10) # optional
3
+ max_priority_fee_per_gas: 1gwei + random(1,12)
4
+ gas: estimate + random(100,200) - 19 # supported var_name=estimate
5
+ value: balance - random(0.002eth,0.0025eth) + 11gwei # supported var_name=balance. If 'balance' is used, value=calc(value) - gas*max_fee_per_gas
6
+ value_min_limit: 0.001eth + random(1,2) - 7
7
+ addresses_map: |
8
+ 0x10fd602Bff689e64D4720D1DCCCD3494f1f16623 0x58487485c3858109f5A37e42546FE87473f79a4b
9
+ 0x97C77B548aE0d4925F5C201220fC6d8996424309 0x7EdF3b8579c21A8820b4C0B8352541c1CE04045f # can comment here
10
+ # and here
11
+ #addresses_from_file: ~/path/from.txt
12
+ #addresses_to_file: ~/path/to.txt
13
+ delay: random(1.123,10) + 1 # secs
14
+ #private_keys: |
15
+ # 0x7bb5b9c0ba991275f84b796b4d25fd3a8d7320911f50fade85410e7a2b000632
16
+ # 0xb7e0b671e176b04ceb0897a698d34771bfe9acf29273dc52a141be6e97145a00
17
+ private_keys_file: ~/path/private_keys.txt
18
+ log_debug: /path/to/file_debug.log # optional
19
+ log_info: /path/to/file_info.log # optional
20
+ round_ndigits: 6
21
+ chain_id: 421613
22
+ nodes: |
23
+ https://arbitrum-goerli.publicnode.com
24
+ https://rpc.goerli.arbitrum.gateway.fm