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.
- mm_eth/__init__.py +0 -0
- mm_eth/abi/zksync.json +2092 -0
- mm_eth/abi.py +130 -0
- mm_eth/account.py +70 -0
- mm_eth/anvil.py +56 -0
- mm_eth/cli/__init__.py +0 -0
- mm_eth/cli/calcs.py +88 -0
- mm_eth/cli/cli.py +233 -0
- mm_eth/cli/cli_helpers.py +195 -0
- mm_eth/cli/cli_utils.py +150 -0
- mm_eth/cli/cmd/__init__.py +0 -0
- mm_eth/cli/cmd/balance_cmd.py +59 -0
- mm_eth/cli/cmd/balances_cmd.py +121 -0
- mm_eth/cli/cmd/call_contract_cmd.py +44 -0
- mm_eth/cli/cmd/config_example_cmd.py +9 -0
- mm_eth/cli/cmd/deploy_cmd.py +41 -0
- mm_eth/cli/cmd/encode_input_data_cmd.py +10 -0
- mm_eth/cli/cmd/mnemonic_cmd.py +27 -0
- mm_eth/cli/cmd/node_cmd.py +47 -0
- mm_eth/cli/cmd/private_key_cmd.py +10 -0
- mm_eth/cli/cmd/rpc_cmd.py +81 -0
- mm_eth/cli/cmd/send_contract_cmd.py +247 -0
- mm_eth/cli/cmd/solc_cmd.py +25 -0
- mm_eth/cli/cmd/token_cmd.py +29 -0
- mm_eth/cli/cmd/transfer_erc20_cmd.py +275 -0
- mm_eth/cli/cmd/transfer_eth_cmd.py +252 -0
- mm_eth/cli/cmd/vault_cmd.py +16 -0
- mm_eth/cli/config_examples/balances.yml +15 -0
- mm_eth/cli/config_examples/call_contract.yml +5 -0
- mm_eth/cli/config_examples/transfer_erc20.yml +26 -0
- mm_eth/cli/config_examples/transfer_eth.yml +24 -0
- mm_eth/cli/validators.py +84 -0
- mm_eth/deploy.py +20 -0
- mm_eth/ens.py +16 -0
- mm_eth/erc20.py +240 -0
- mm_eth/ethernodes.py +34 -0
- mm_eth/py.typed +0 -0
- mm_eth/rpc.py +478 -0
- mm_eth/services/__init__.py +0 -0
- mm_eth/solc.py +34 -0
- mm_eth/tx.py +164 -0
- mm_eth/types.py +5 -0
- mm_eth/utils.py +245 -0
- mm_eth/vault.py +38 -0
- mm_eth/zksync.py +203 -0
- mm_eth-0.1.0.dist-info/METADATA +24 -0
- mm_eth-0.1.0.dist-info/RECORD +50 -0
- mm_eth-0.1.0.dist-info/WHEEL +5 -0
- mm_eth-0.1.0.dist-info/entry_points.txt +2 -0
- mm_eth-0.1.0.dist-info/top_level.txt +1 -0
mm_eth/utils.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from decimal import Decimal, localcontext
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
|
|
5
|
+
import eth_utils
|
|
6
|
+
import pydash
|
|
7
|
+
from eth_typing import HexStr
|
|
8
|
+
from hexbytes import HexBytes
|
|
9
|
+
from mm_std import Err, Ok, Result, number_with_separator, random_choice
|
|
10
|
+
from mm_std.random_ import random_str_choice
|
|
11
|
+
from pydantic import BaseModel
|
|
12
|
+
from web3 import Web3
|
|
13
|
+
from web3.types import Wei
|
|
14
|
+
|
|
15
|
+
from mm_eth.types import Nodes, Proxies
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def parse_addresses(data: str) -> list[str]:
|
|
19
|
+
data = data.lower()
|
|
20
|
+
result = []
|
|
21
|
+
for word in data.split():
|
|
22
|
+
if len(word) == 42 and re.match("0x[a-f0-9]{40}", word):
|
|
23
|
+
result.append(word)
|
|
24
|
+
return pydash.uniq(result)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def to_token_wei(value: str | int, decimals: int) -> int:
|
|
28
|
+
if isinstance(value, int):
|
|
29
|
+
return value
|
|
30
|
+
elif isinstance(value, str):
|
|
31
|
+
value = value.lower().replace(" ", "").strip()
|
|
32
|
+
if value.endswith("t"):
|
|
33
|
+
value = value.replace("t", "")
|
|
34
|
+
return int(Decimal(value) * 10**decimals)
|
|
35
|
+
elif value.isdigit():
|
|
36
|
+
return int(value)
|
|
37
|
+
|
|
38
|
+
raise ValueError("wrong value" + value)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def to_wei(value: str | int | Decimal, decimals: int | None = None) -> Wei:
|
|
42
|
+
if isinstance(value, int):
|
|
43
|
+
return Wei(value)
|
|
44
|
+
elif isinstance(value, Decimal):
|
|
45
|
+
if value != value.to_integral_value():
|
|
46
|
+
raise ValueError(f"value must be integral number: {value}")
|
|
47
|
+
return Wei(int(value))
|
|
48
|
+
elif isinstance(value, str):
|
|
49
|
+
value = value.lower().replace(" ", "").strip()
|
|
50
|
+
if value.endswith("navax"): # https://snowtrace.io/unitconverter
|
|
51
|
+
value = value.replace("navax", "")
|
|
52
|
+
return Wei(int(Decimal(value) * 10**9))
|
|
53
|
+
if value.endswith("gwei"):
|
|
54
|
+
value = value.replace("gwei", "")
|
|
55
|
+
return Wei(int(Decimal(value) * 1000000000))
|
|
56
|
+
if value.endswith("ether"):
|
|
57
|
+
value = value.replace("ether", "")
|
|
58
|
+
return Wei(int(Decimal(value) * 1000000000000000000))
|
|
59
|
+
if value.endswith("eth"):
|
|
60
|
+
value = value.replace("eth", "")
|
|
61
|
+
return Wei(int(Decimal(value) * 1000000000000000000))
|
|
62
|
+
if value.endswith("t"):
|
|
63
|
+
if decimals is None:
|
|
64
|
+
raise ValueError("t without decimals")
|
|
65
|
+
value = value.removesuffix("t")
|
|
66
|
+
return Wei(int(Decimal(value) * 10**decimals))
|
|
67
|
+
if value.isdigit():
|
|
68
|
+
return Wei(int(value))
|
|
69
|
+
raise ValueError("wrong value " + value)
|
|
70
|
+
|
|
71
|
+
else:
|
|
72
|
+
raise ValueError(f"value has a wrong type: {type(value)}")
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def from_wei(
|
|
76
|
+
value: int,
|
|
77
|
+
unit: str,
|
|
78
|
+
round_ndigits: int | None = None,
|
|
79
|
+
decimals: int | None = None,
|
|
80
|
+
) -> Decimal:
|
|
81
|
+
if value == 0:
|
|
82
|
+
return Decimal(0)
|
|
83
|
+
|
|
84
|
+
is_negative = value < 0
|
|
85
|
+
if unit.lower() == "eth":
|
|
86
|
+
unit = "ether"
|
|
87
|
+
|
|
88
|
+
if unit.lower() == "t":
|
|
89
|
+
if decimals is None:
|
|
90
|
+
raise ValueError("t without decimals")
|
|
91
|
+
with localcontext() as ctx:
|
|
92
|
+
ctx.prec = 999
|
|
93
|
+
res = Decimal(value=abs(value), context=ctx) / Decimal(10**decimals)
|
|
94
|
+
else:
|
|
95
|
+
res = cast(Decimal, eth_utils.from_wei(abs(value), unit))
|
|
96
|
+
if round_ndigits is not None:
|
|
97
|
+
res = round(res, ndigits=round_ndigits)
|
|
98
|
+
return -1 * res if is_negative else res
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def from_wei_str(
|
|
102
|
+
value: int,
|
|
103
|
+
unit: str,
|
|
104
|
+
round_ndigits: int | None = None,
|
|
105
|
+
print_unit_name: bool = True,
|
|
106
|
+
decimals: int | None = None,
|
|
107
|
+
) -> str:
|
|
108
|
+
res = format(from_wei(value, unit, round_ndigits, decimals=decimals), "f")
|
|
109
|
+
if unit == "ether":
|
|
110
|
+
unit = "eth"
|
|
111
|
+
if print_unit_name:
|
|
112
|
+
res += unit
|
|
113
|
+
return res
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def from_token_wei_str(value: int, decimals: int, symbol: str = "", round_ndigits: int | None = None) -> str:
|
|
117
|
+
res = value / 10**decimals
|
|
118
|
+
if round_ndigits is not None:
|
|
119
|
+
res = round(res, ndigits=round_ndigits)
|
|
120
|
+
if symbol:
|
|
121
|
+
res = f"{res} {symbol}"
|
|
122
|
+
return str(res)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def to_wei_token(value: str | int | Decimal, symbol: str, decimals: int) -> int:
|
|
126
|
+
if isinstance(value, int):
|
|
127
|
+
return value
|
|
128
|
+
elif isinstance(value, Decimal):
|
|
129
|
+
if value != value.to_integral_value():
|
|
130
|
+
raise ValueError(f"value must be integral number: {value}")
|
|
131
|
+
return int(value)
|
|
132
|
+
elif isinstance(value, str):
|
|
133
|
+
value = value.lower().replace(" ", "").strip()
|
|
134
|
+
if value.isdigit():
|
|
135
|
+
return int(value)
|
|
136
|
+
try:
|
|
137
|
+
return int(Decimal(value.replace(symbol.lower(), "").strip()) * (10**decimals))
|
|
138
|
+
except Exception as e:
|
|
139
|
+
raise ValueError from e
|
|
140
|
+
else:
|
|
141
|
+
raise ValueError(f"value has a wrong type: {type(value)}")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def to_checksum_address(address: str) -> str:
|
|
145
|
+
return Web3.to_checksum_address(address)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def hex_to_bytes(data: str) -> bytes:
|
|
149
|
+
return eth_utils.to_bytes(hexstr=HexStr(data))
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def get_chain_name(chain_id: int | str) -> str:
|
|
153
|
+
chain_id = str(chain_id)
|
|
154
|
+
if chain_id == "1":
|
|
155
|
+
return "mainnet"
|
|
156
|
+
if chain_id == "3":
|
|
157
|
+
return "ropsten"
|
|
158
|
+
if chain_id == "5":
|
|
159
|
+
return "goerli"
|
|
160
|
+
return chain_id
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def to_human_readable_tx(tx: dict[str, Any] | BaseModel) -> dict[str, object]:
|
|
164
|
+
if isinstance(tx, BaseModel):
|
|
165
|
+
tx = tx.model_dump()
|
|
166
|
+
tx["human_readable"] = {}
|
|
167
|
+
tx["human_readable"]["gas_price"] = str(tx["gas_price"] / 10**9) + " gwei"
|
|
168
|
+
tx["human_readable"]["value"] = str(tx["value"] / 10**18) + " ether"
|
|
169
|
+
tx["human_readable"]["gas"] = number_with_separator(tx["gas"])
|
|
170
|
+
if tx.get("chain_id") is not None:
|
|
171
|
+
tx["human_readable"]["chain_id"] = get_chain_name(tx["chain_id"])
|
|
172
|
+
|
|
173
|
+
return tx
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def truncate_hex_str(hex_str: str, digits: int = 4, replace_str: str = "...") -> str:
|
|
177
|
+
if not hex_str.startswith("0x") and not hex_str.startswith("0X"):
|
|
178
|
+
raise ValueError("truncate_hex_str: hex_str must start with 0x")
|
|
179
|
+
if digits <= 0:
|
|
180
|
+
raise ValueError("truncate_hex_str: digits must be more than zero")
|
|
181
|
+
hex_str = hex_str.removeprefix("0x").removeprefix("0X")
|
|
182
|
+
if digits * 2 >= len(hex_str):
|
|
183
|
+
raise ValueError("truncate_hex_str: digits is too large")
|
|
184
|
+
return "0x" + hex_str[:digits] + replace_str + hex_str[-1 * digits :]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def log_topic_to_address(topic: HexBytes | str) -> str:
|
|
188
|
+
if isinstance(topic, HexBytes):
|
|
189
|
+
result = topic.hex()[-40:]
|
|
190
|
+
else:
|
|
191
|
+
result = topic[-40:]
|
|
192
|
+
if not result.startswith("0x"):
|
|
193
|
+
result = f"0x{result}"
|
|
194
|
+
return result
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def get_w3(rpc_url: str, timeout: int | None = None, proxy: str | None = None) -> Web3:
|
|
198
|
+
request_kwargs: dict[str, object] = {"timeout": timeout}
|
|
199
|
+
if proxy:
|
|
200
|
+
request_kwargs["proxies"] = {"http": proxy, "https": proxy}
|
|
201
|
+
return Web3(Web3.HTTPProvider(rpc_url, request_kwargs=request_kwargs))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def name_network(chain_id: int) -> str:
|
|
205
|
+
match chain_id:
|
|
206
|
+
case 1:
|
|
207
|
+
return "Ethereum Mainnet"
|
|
208
|
+
case 5:
|
|
209
|
+
return "Goerli"
|
|
210
|
+
case 10:
|
|
211
|
+
return "OP Mainnet"
|
|
212
|
+
case 280:
|
|
213
|
+
return "zkSync Era Testnet"
|
|
214
|
+
case 324:
|
|
215
|
+
return "zkSync Era Mainnet"
|
|
216
|
+
case 420:
|
|
217
|
+
return "Optimism Goerli Testnet"
|
|
218
|
+
case 42161:
|
|
219
|
+
return "Arbitrum One"
|
|
220
|
+
case 43113:
|
|
221
|
+
return "Avalanche Fuji Testnet"
|
|
222
|
+
case 43114:
|
|
223
|
+
return "Avalanche C-Chain"
|
|
224
|
+
case 421613:
|
|
225
|
+
return "Arbitrum Goerli"
|
|
226
|
+
case _:
|
|
227
|
+
return ""
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def hex_str_to_int(value: str) -> Result[int]:
|
|
231
|
+
try:
|
|
232
|
+
return Ok(int(value, 16))
|
|
233
|
+
except Exception:
|
|
234
|
+
return Err(f"can't convert to int: {value}")
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def random_node(nodes: Nodes, remove_slash: bool = True) -> str:
|
|
238
|
+
node = cast(str, random_choice(nodes))
|
|
239
|
+
if remove_slash and node.endswith("/"):
|
|
240
|
+
node = node.removesuffix("/")
|
|
241
|
+
return node
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def random_proxy(proxies: Proxies) -> str | None:
|
|
245
|
+
return random_str_choice(proxies)
|
mm_eth/vault.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
from mm_std import Err, Ok, Result, hr
|
|
2
|
+
|
|
3
|
+
from mm_eth.account import private_to_address
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def read_keys_from_vault(keys_url: str, token: str) -> Result[dict[str, str]]:
|
|
7
|
+
data = None
|
|
8
|
+
try:
|
|
9
|
+
# keys_url example, https://vault.site.com:8200/v1/kv/keys1
|
|
10
|
+
res = hr(keys_url, headers={"X-Vault-Token": token})
|
|
11
|
+
data = res.json
|
|
12
|
+
return Ok(res.json["data"], data=data)
|
|
13
|
+
except Exception as e:
|
|
14
|
+
return Err(f"exception: {e}", data=data)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def set_keys_from_vault(keys_url: str, token: str, private_keys: list[str], verify_tls: bool = True) -> Result[bool]:
|
|
18
|
+
"""It works with KV version=1 only!!!""" # TODO: check that keys_url is kv1 version and error if it's kv2
|
|
19
|
+
data = None
|
|
20
|
+
try:
|
|
21
|
+
# keys_url example, https://vault.site.com:8200/v1/kv/keys1
|
|
22
|
+
keys: dict[str, str] = {}
|
|
23
|
+
for private_key in private_keys:
|
|
24
|
+
address = private_to_address(private_key)
|
|
25
|
+
if address is None:
|
|
26
|
+
raise ValueError("there is a wrong private key")
|
|
27
|
+
keys[address] = private_key
|
|
28
|
+
|
|
29
|
+
res = hr(keys_url, method="post", headers={"X-Vault-Token": token}, params=keys, verify=verify_tls)
|
|
30
|
+
data = res.json
|
|
31
|
+
if res.code == 204:
|
|
32
|
+
return Ok(res.code == 204, data=data)
|
|
33
|
+
elif res.code == 403:
|
|
34
|
+
return Err("permission denied", data=data)
|
|
35
|
+
else:
|
|
36
|
+
return Err(res.error or "error", data=data)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
return Err(f"exception: {e}", data=data)
|
mm_eth/zksync.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import pkgutil
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
5
|
+
from eth_typing import 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 ABI, 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}")
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mm-eth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Requires-Python: >=3.12
|
|
5
|
+
Requires-Dist: mm-std ~=0.1.0
|
|
6
|
+
Requires-Dist: websocket-client ~=1.8.0
|
|
7
|
+
Requires-Dist: web3 ~=6.20.0
|
|
8
|
+
Requires-Dist: typer ~=0.12.0
|
|
9
|
+
Requires-Dist: loguru ~=0.7.2
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: build ~=1.2.1 ; extra == 'dev'
|
|
12
|
+
Requires-Dist: twine ~=5.1.0 ; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest ~=8.3.2 ; extra == 'dev'
|
|
14
|
+
Requires-Dist: pytest-xdist ~=3.6.1 ; extra == 'dev'
|
|
15
|
+
Requires-Dist: pytest-httpserver ~=1.0.8 ; extra == 'dev'
|
|
16
|
+
Requires-Dist: coverage ~=7.6.0 ; extra == 'dev'
|
|
17
|
+
Requires-Dist: ruff ~=0.5.2 ; extra == 'dev'
|
|
18
|
+
Requires-Dist: pip-audit ~=2.7.0 ; extra == 'dev'
|
|
19
|
+
Requires-Dist: bandit ~=1.7.7 ; extra == 'dev'
|
|
20
|
+
Requires-Dist: mypy ~=1.11.0 ; extra == 'dev'
|
|
21
|
+
Requires-Dist: types-python-dateutil ~=2.9.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: types-requests ~=2.32.0.20240523 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: types-PyYAML ~=6.0.12.12 ; extra == 'dev'
|
|
24
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
mm_eth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
mm_eth/abi.py,sha256=VpZETb7jh8Ovghxit-UI13cxAWP7siv5UqIzC70GIB0,4736
|
|
3
|
+
mm_eth/account.py,sha256=ZpmcYRmJVa6oJMtSlLSkpjgbA8M-g6alaHQaeQT1I9Y,1982
|
|
4
|
+
mm_eth/anvil.py,sha256=9CWXWyK7tVrvs6NeOy7QnVwMTrUke-nBMFwZeJ4VULA,1582
|
|
5
|
+
mm_eth/deploy.py,sha256=SB3ruY808_5UnG8kHR34uVP66P3zOWZu0ImKD7UUv2s,691
|
|
6
|
+
mm_eth/ens.py,sha256=WMxqC1v3zwDDuLH_oWekm22qrNYxCNcvZumQMT7SYds,623
|
|
7
|
+
mm_eth/erc20.py,sha256=m62j00zizSZzXgLUEih2Q1iVFehU9qrqOMfc3gyix7k,6844
|
|
8
|
+
mm_eth/ethernodes.py,sha256=9y_poTmFUj6cnWaT9mtfc6S9lAfVXTwLGRqxMQ8hT0Y,3080
|
|
9
|
+
mm_eth/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
mm_eth/rpc.py,sha256=kQnYllp5ZHGuIUUX2Vw-ejLwL3zXQgka3ukwDndv4AM,13881
|
|
11
|
+
mm_eth/solc.py,sha256=JcBCel-LO-lBvz3_1f7s3pVhECsUittavBvXqxYIP2c,1066
|
|
12
|
+
mm_eth/tx.py,sha256=XkyafbEepG5VtJwVlM2NNPuFGit8pldKdOPh_R_1iJE,4032
|
|
13
|
+
mm_eth/types.py,sha256=w_B1o_XggFz2cHXa36tI-wjfE4r-BFhvKIF9nb2YI5g,154
|
|
14
|
+
mm_eth/utils.py,sha256=TMvQMtP7YyuBnv-bsjbcIzRfBUyVFBI8IjeolCXXlVg,7785
|
|
15
|
+
mm_eth/vault.py,sha256=1ahABcgyYAMUlE4ELMGdlG_uV4irmwzrfzUZTpo3rdY,1500
|
|
16
|
+
mm_eth/zksync.py,sha256=XLop9r5_gVEi9tm1MUfuSl0wUmbE27m7x0pir7T3hZA,5997
|
|
17
|
+
mm_eth/abi/zksync.json,sha256=aHp6-Y5W6wRz0hrC0aPTjAHb-Q2ipDikcAB5FtlxWWA,45503
|
|
18
|
+
mm_eth/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
mm_eth/cli/calcs.py,sha256=nHfn_Q9T_E8_hP7ExMyWlUGvAMzxQs6KgzdNg1CUnvU,3651
|
|
20
|
+
mm_eth/cli/cli.py,sha256=cxkSistUsdalp3xuOxgMYBp5u9jxnNitgv8Oc-AyVww,8768
|
|
21
|
+
mm_eth/cli/cli_helpers.py,sha256=0NapwmrRVQ1PWDP6iqwaX1fnzrIdD_TwvcZXIcq5QzM,6572
|
|
22
|
+
mm_eth/cli/cli_utils.py,sha256=4I12cJV0deKfarRpd2wNhW42L_jQab4oxa9DX-WxicM,5041
|
|
23
|
+
mm_eth/cli/validators.py,sha256=bE4G-OL6W6S30P4FK5pVn_LiY2G2w8aeA3R1kQaX0gY,2379
|
|
24
|
+
mm_eth/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
mm_eth/cli/cmd/balance_cmd.py,sha256=kDV_KAPrUY_fsGiVLcGGHspuU2zqpqsVnTe2btjtCxs,2254
|
|
26
|
+
mm_eth/cli/cmd/balances_cmd.py,sha256=P3zyaJrghLXAYE7mnT3ReKnPYHiAqA7kZbkvLPioVUE,4326
|
|
27
|
+
mm_eth/cli/cmd/call_contract_cmd.py,sha256=pKuWXZ6i4Taml_AnEJlffSsgjd_ktRoLDqmyAzwuDXk,1245
|
|
28
|
+
mm_eth/cli/cmd/config_example_cmd.py,sha256=hMfrFxjIP0xZllJRRxi99qhF8tzJI3lrp-XudFEWF1g,270
|
|
29
|
+
mm_eth/cli/cmd/deploy_cmd.py,sha256=MoPxZXNB_YlPh9qprA4EZ96Su-A2n_jeVJkhWVpsz1c,1258
|
|
30
|
+
mm_eth/cli/cmd/encode_input_data_cmd.py,sha256=9UQ1MKPEFQJ8j_COsP3KGKhwOf9tT3feBezI8vvxTlw,267
|
|
31
|
+
mm_eth/cli/cmd/mnemonic_cmd.py,sha256=BfcNSflEr5R8eINc1ATDc6w2pErfKfzL-Zloqbk3oio,1006
|
|
32
|
+
mm_eth/cli/cmd/node_cmd.py,sha256=mUqixPGNHzuKCJvk1Fd5VaUGumpbR2AE7raGmXZscg4,1943
|
|
33
|
+
mm_eth/cli/cmd/private_key_cmd.py,sha256=Fv_2OLog1h32pIP7PJITwl_pHdy3BXvaDRcXZsxY1xo,241
|
|
34
|
+
mm_eth/cli/cmd/rpc_cmd.py,sha256=dCEbAF9bmJQq1pKf8JGcaIB-iorPz-tT4Tc7CweaEmY,2217
|
|
35
|
+
mm_eth/cli/cmd/send_contract_cmd.py,sha256=xT1RVgIUvJIvEtvPxrqnCK0St5HoL022CXStxLEdriM,9047
|
|
36
|
+
mm_eth/cli/cmd/solc_cmd.py,sha256=Xu6rdn15zQeHEEFpGxbD7ofojT_o6SVqg1dTpbock-Q,717
|
|
37
|
+
mm_eth/cli/cmd/token_cmd.py,sha256=Sm4PiAABwnfgchBgj1VnmYrzF7oll8js9gKh6YNdwnE,1107
|
|
38
|
+
mm_eth/cli/cmd/transfer_erc20_cmd.py,sha256=DA4Az9rINKDx7Pjrr4v9v-QNYpOc03TgnpaNEVGDVr4,10245
|
|
39
|
+
mm_eth/cli/cmd/transfer_eth_cmd.py,sha256=dbXt2Ngb--RyA5HdvISwSlr129cd674GvGYLLuPaG3U,9429
|
|
40
|
+
mm_eth/cli/cmd/vault_cmd.py,sha256=rRTclz0U6N_87KFsssdvZhi3_f6tmkHiYxMsIoVeBGc,532
|
|
41
|
+
mm_eth/cli/config_examples/balances.yml,sha256=fpXJfoOSqOrkoWpqO7-HrTTb5OBs9PM-PpZ1ulqWUnY,399
|
|
42
|
+
mm_eth/cli/config_examples/call_contract.yml,sha256=E0XuWuBnbhyTYfxNqaoxH6Cy7UCYqMLDwoEu_4OSV3M,233
|
|
43
|
+
mm_eth/cli/config_examples/transfer_erc20.yml,sha256=mCUpUfqzZjTvst8kqd3EGzJY0S7cr48ySqS6yZhzLdI,1324
|
|
44
|
+
mm_eth/cli/config_examples/transfer_eth.yml,sha256=i4hh3-LTcpl7JsrSuuM6gDi23B5ZnCeEnaYazQO3bzQ,1181
|
|
45
|
+
mm_eth/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
+
mm_eth-0.1.0.dist-info/METADATA,sha256=QdbiQBGZqJ9j6C3lB4X4zX49ytobR1CkR-ThN6YFUMw,925
|
|
47
|
+
mm_eth-0.1.0.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
|
|
48
|
+
mm_eth-0.1.0.dist-info/entry_points.txt,sha256=aGhpsozl8NIrkuUcX5fSgURCcDhr3ShUdeTSIrJq4oc,46
|
|
49
|
+
mm_eth-0.1.0.dist-info/top_level.txt,sha256=hNXi_YDQ5FI_Qn4MC4AX0KeWf8KMAcTIk7jsGhoRRMA,7
|
|
50
|
+
mm_eth-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mm_eth
|