mm-eth 0.1.4__py3-none-any.whl → 0.2.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/types.py +2 -3
- mm_eth-0.2.0.dist-info/METADATA +7 -0
- mm_eth-0.2.0.dist-info/RECORD +18 -0
- {mm_eth-0.1.4.dist-info → mm_eth-0.2.0.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 -150
- 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.4.dist-info/METADATA +0 -9
- mm_eth-0.1.4.dist-info/RECORD +0 -49
- mm_eth-0.1.4.dist-info/entry_points.txt +0 -2
|
@@ -1,252 +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, 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)
|
mm_eth/cli/cmd/vault_cmd.py
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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}")
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
mm_eth/cli/validators.py
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from decimal import Decimal, InvalidOperation
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
from mm_std import str_to_list
|
|
6
|
-
|
|
7
|
-
from mm_eth.cli import calcs
|
|
8
|
-
from mm_eth.utils import to_wei
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def wei_validator(v: str | None) -> int | None:
|
|
12
|
-
if v is None:
|
|
13
|
-
return None
|
|
14
|
-
return to_wei(v)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def log_validator(v: str | None) -> str | None:
|
|
18
|
-
if v is None:
|
|
19
|
-
return None
|
|
20
|
-
log_file = Path(v).expanduser()
|
|
21
|
-
log_file.touch(exist_ok=True)
|
|
22
|
-
if not (log_file.is_file() and os.access(log_file, os.W_OK)):
|
|
23
|
-
raise ValueError(f"wrong log path: {v}")
|
|
24
|
-
return v
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def nodes_validator(v: str | list[str] | None) -> list[str]:
|
|
28
|
-
if v is None:
|
|
29
|
-
return []
|
|
30
|
-
if isinstance(v, str):
|
|
31
|
-
return str_to_list(v, unique=True, remove_comments=True, split_line=True)
|
|
32
|
-
return v
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def addresses_validator(v: str | list[str] | None) -> list[str]:
|
|
36
|
-
if v is None:
|
|
37
|
-
return []
|
|
38
|
-
if isinstance(v, str):
|
|
39
|
-
return str_to_list(v, unique=True, remove_comments=True, split_line=True, lower=True)
|
|
40
|
-
return v
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def delay_validator(v: str | Decimal) -> Decimal | tuple[Decimal, Decimal]:
|
|
44
|
-
if isinstance(v, int | float):
|
|
45
|
-
return Decimal(str(v))
|
|
46
|
-
elif isinstance(v, str):
|
|
47
|
-
arr = [a.strip() for a in v.split("-")]
|
|
48
|
-
if len(arr) != 2:
|
|
49
|
-
raise ValueError("wrong delay value")
|
|
50
|
-
try:
|
|
51
|
-
return Decimal(arr[0]), Decimal(arr[1])
|
|
52
|
-
except InvalidOperation:
|
|
53
|
-
raise ValueError("wrong delay value") from None
|
|
54
|
-
raise ValueError("wrong delay value")
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def is_valid_calc_var_wei_value(value: str | None, base_name: str = "var", decimals: int | None = None) -> bool:
|
|
58
|
-
if value is None:
|
|
59
|
-
return True # check for None on BaseModel.field type level
|
|
60
|
-
try:
|
|
61
|
-
calcs.calc_var_wei_value(value, var_value=123, var_name=base_name, decimals=decimals)
|
|
62
|
-
return True
|
|
63
|
-
except ValueError:
|
|
64
|
-
return False
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def is_valid_calc_decimal_value(value: str | None) -> bool:
|
|
68
|
-
if value is None:
|
|
69
|
-
return True # check for None on BaseModel.field type level
|
|
70
|
-
try:
|
|
71
|
-
calcs.calc_decimal_value(value)
|
|
72
|
-
return True
|
|
73
|
-
except ValueError:
|
|
74
|
-
return False
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def is_valid_calc_function_args(value: str | None) -> bool:
|
|
78
|
-
if value is None:
|
|
79
|
-
return True
|
|
80
|
-
try:
|
|
81
|
-
calcs.calc_function_args(value)
|
|
82
|
-
return True
|
|
83
|
-
except ValueError:
|
|
84
|
-
return False
|
mm_eth/services/__init__.py
DELETED
|
File without changes
|
mm_eth/zksync.py
DELETED
|
@@ -1,203 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import pkgutil
|
|
3
|
-
from typing import cast
|
|
4
|
-
|
|
5
|
-
from eth_typing import ABI, ChecksumAddress, HexStr
|
|
6
|
-
from hexbytes import HexBytes
|
|
7
|
-
from mm_std import Err, Ok, Result
|
|
8
|
-
from pydantic import BaseModel, Field
|
|
9
|
-
from web3 import Web3
|
|
10
|
-
from web3.types import Nonce, TxParams, Wei
|
|
11
|
-
|
|
12
|
-
from mm_eth import abi, rpc
|
|
13
|
-
from mm_eth.rpc import rpc_call
|
|
14
|
-
from mm_eth.types import Nodes, Proxies
|
|
15
|
-
from mm_eth.utils import hex_str_to_int
|
|
16
|
-
|
|
17
|
-
L2_ETH_TOKEN_ADDRESS = Web3.to_checksum_address("0x000000000000000000000000000000000000800a")
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class BridgeContracts(BaseModel):
|
|
21
|
-
l1_erc20_Default_bridge: str = Field(..., alias="l1Erc20DefaultBridge")
|
|
22
|
-
l2_erc20_Default_bridge: str = Field(..., alias="l2Erc20DefaultBridge")
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
def zks_get_bridge_contracts(
|
|
26
|
-
rpc_urls: Nodes,
|
|
27
|
-
timeout: int = 10,
|
|
28
|
-
proxies: Proxies = None,
|
|
29
|
-
attempts: int = 1,
|
|
30
|
-
) -> Result[BridgeContracts]:
|
|
31
|
-
res = rpc_call(
|
|
32
|
-
nodes=rpc_urls,
|
|
33
|
-
method="zks_getBridgeContracts",
|
|
34
|
-
params=[],
|
|
35
|
-
timeout=timeout,
|
|
36
|
-
proxies=proxies,
|
|
37
|
-
attempts=attempts,
|
|
38
|
-
)
|
|
39
|
-
if isinstance(res, Err):
|
|
40
|
-
return res
|
|
41
|
-
|
|
42
|
-
try:
|
|
43
|
-
return Ok(BridgeContracts(**res.ok), data=res.data)
|
|
44
|
-
except Exception as e:
|
|
45
|
-
return Err(f"exception: {e}", data=res.data)
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
def zks_get_main_contract(
|
|
49
|
-
rpc_urls: Nodes,
|
|
50
|
-
timeout: int = 10,
|
|
51
|
-
proxies: Proxies = None,
|
|
52
|
-
attempts: int = 1,
|
|
53
|
-
) -> Result[str]:
|
|
54
|
-
return rpc_call(
|
|
55
|
-
nodes=rpc_urls,
|
|
56
|
-
method="zks_getMainContract",
|
|
57
|
-
params=[],
|
|
58
|
-
timeout=timeout,
|
|
59
|
-
proxies=proxies,
|
|
60
|
-
attempts=attempts,
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
def zks_estimate_gas_l1_to_l2(
|
|
65
|
-
rpc_urls: Nodes,
|
|
66
|
-
timeout: int = 10,
|
|
67
|
-
proxies: Proxies = None,
|
|
68
|
-
attempts: int = 1,
|
|
69
|
-
) -> Result[int]:
|
|
70
|
-
return rpc_call(
|
|
71
|
-
nodes=rpc_urls,
|
|
72
|
-
method="zks_estimateGasL1ToL2",
|
|
73
|
-
params=[
|
|
74
|
-
[
|
|
75
|
-
{
|
|
76
|
-
"from": "0x1111111111111111111111111111111111111111",
|
|
77
|
-
"to": "0x2222222222222222222222222222222222222222",
|
|
78
|
-
"data": "0xffffffff",
|
|
79
|
-
},
|
|
80
|
-
],
|
|
81
|
-
],
|
|
82
|
-
timeout=timeout,
|
|
83
|
-
proxies=proxies,
|
|
84
|
-
attempts=attempts,
|
|
85
|
-
).and_then(hex_str_to_int)
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def zksync_contract_abi() -> ABI:
|
|
89
|
-
data = pkgutil.get_data(__name__, "abi/zksync.json")
|
|
90
|
-
if data is None:
|
|
91
|
-
raise RuntimeError("can't read abi/zksync.json")
|
|
92
|
-
return cast(ABI, json.loads(data.decode()))
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def get_l2_transactio_base_cost(
|
|
96
|
-
eth_rpc: str,
|
|
97
|
-
contract_address: ChecksumAddress,
|
|
98
|
-
gas_price: Wei,
|
|
99
|
-
l2_gas_limit: int = 10000000,
|
|
100
|
-
l2_gas_per_pubdata_byte_limit: int = 800,
|
|
101
|
-
) -> Result[Wei]:
|
|
102
|
-
w3 = Web3(Web3.HTTPProvider(eth_rpc))
|
|
103
|
-
try:
|
|
104
|
-
contract = w3.eth.contract(address=contract_address, abi=zksync_contract_abi())
|
|
105
|
-
res = contract.functions.l2TransactionBaseCost(gas_price, l2_gas_limit, l2_gas_per_pubdata_byte_limit).call()
|
|
106
|
-
return Ok(Wei(res))
|
|
107
|
-
except Exception as e:
|
|
108
|
-
return Err(str(e))
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
def deposit(
|
|
112
|
-
*,
|
|
113
|
-
eth_rpc: str,
|
|
114
|
-
contract_address: ChecksumAddress,
|
|
115
|
-
wallet_address: ChecksumAddress,
|
|
116
|
-
private_key: str,
|
|
117
|
-
gas_price: Wei,
|
|
118
|
-
value: Wei,
|
|
119
|
-
l2_gas_limit: int = 1_300_000,
|
|
120
|
-
l2_gas_per_pubdata_byte_limit: int = 800,
|
|
121
|
-
gas: int = 150096,
|
|
122
|
-
nonce: Nonce | None = None,
|
|
123
|
-
) -> Result[str]:
|
|
124
|
-
w3 = Web3(Web3.HTTPProvider(eth_rpc))
|
|
125
|
-
try:
|
|
126
|
-
# get nonce
|
|
127
|
-
if nonce is None:
|
|
128
|
-
res_nonce = rpc.eth_get_transaction_count(eth_rpc, wallet_address)
|
|
129
|
-
if isinstance(res_nonce, Err):
|
|
130
|
-
return res_nonce
|
|
131
|
-
|
|
132
|
-
nonce = Nonce(res_nonce.ok)
|
|
133
|
-
|
|
134
|
-
# get base_cost
|
|
135
|
-
res = get_l2_transactio_base_cost(eth_rpc, contract_address, gas_price, l2_gas_limit, l2_gas_per_pubdata_byte_limit)
|
|
136
|
-
if isinstance(res, Err):
|
|
137
|
-
return res
|
|
138
|
-
base_cost = Wei(res.ok)
|
|
139
|
-
|
|
140
|
-
contract_address_l2 = wallet_address
|
|
141
|
-
l2_value = value
|
|
142
|
-
calldata = b""
|
|
143
|
-
factory_deps = [] # type: ignore[var-annotated]
|
|
144
|
-
refund_recipient = wallet_address
|
|
145
|
-
|
|
146
|
-
contract = w3.eth.contract(address=contract_address, abi=zksync_contract_abi())
|
|
147
|
-
function_call = contract.functions.requestL2Transaction(
|
|
148
|
-
contract_address_l2,
|
|
149
|
-
l2_value,
|
|
150
|
-
calldata,
|
|
151
|
-
l2_gas_limit,
|
|
152
|
-
l2_gas_per_pubdata_byte_limit,
|
|
153
|
-
factory_deps,
|
|
154
|
-
refund_recipient,
|
|
155
|
-
)
|
|
156
|
-
|
|
157
|
-
transaction_data = function_call.build_transaction(
|
|
158
|
-
{"from": wallet_address, "gas": gas, "gasPrice": gas_price, "nonce": nonce, "value": Wei(value + base_cost)},
|
|
159
|
-
)
|
|
160
|
-
signed_transaction = w3.eth.account.sign_transaction(transaction_data, private_key)
|
|
161
|
-
txn = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)
|
|
162
|
-
return Ok(str(txn))
|
|
163
|
-
except Exception as e:
|
|
164
|
-
return Err(f"exception: {e}")
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
def withdraw(
|
|
168
|
-
*,
|
|
169
|
-
zksync_rpc: str,
|
|
170
|
-
wallet_address: ChecksumAddress,
|
|
171
|
-
private_key: str,
|
|
172
|
-
value: Wei,
|
|
173
|
-
chain_id: int,
|
|
174
|
-
) -> Result[HexBytes]:
|
|
175
|
-
try:
|
|
176
|
-
w3 = Web3(Web3.HTTPProvider(zksync_rpc))
|
|
177
|
-
|
|
178
|
-
# get nonce
|
|
179
|
-
nonce = w3.eth.get_transaction_count(wallet_address)
|
|
180
|
-
|
|
181
|
-
input_data = abi.encode_function_signature("withdraw(address)") + abi.encode_data(
|
|
182
|
-
["address"],
|
|
183
|
-
[wallet_address],
|
|
184
|
-
).removeprefix("0x")
|
|
185
|
-
|
|
186
|
-
transaction_data: TxParams = {
|
|
187
|
-
"from": wallet_address,
|
|
188
|
-
"to": L2_ETH_TOKEN_ADDRESS,
|
|
189
|
-
"nonce": nonce,
|
|
190
|
-
"data": HexStr(input_data),
|
|
191
|
-
"value": value,
|
|
192
|
-
"chainId": chain_id,
|
|
193
|
-
}
|
|
194
|
-
gas = w3.eth.estimate_gas(transaction_data)
|
|
195
|
-
transaction_data["gas"] = gas
|
|
196
|
-
gas_price = w3.eth.gas_price
|
|
197
|
-
transaction_data["gasPrice"] = gas_price
|
|
198
|
-
signed_transaction = w3.eth.account.sign_transaction(transaction_data, private_key)
|
|
199
|
-
txn = w3.eth.send_raw_transaction(signed_transaction.rawTransaction)
|
|
200
|
-
return Ok(txn)
|
|
201
|
-
|
|
202
|
-
except Exception as e:
|
|
203
|
-
return Err(f"exception: {e}")
|
mm_eth-0.1.4.dist-info/METADATA
DELETED