1use super::ZKSWalletError;
2use super::{
3 requests::transfer_request::TransferRequest, DeployRequest, DepositRequest, WithdrawRequest,
4};
5use crate::zks_utils::{
6 DEFAULT_ERC20_DEPOSIT_GAS_LIMIT, DEPOSIT_GAS_PER_PUBDATA_LIMIT, ERA_MAINNET_CHAIN_ID,
7};
8use crate::{
9 abi,
10 contracts::main_contract::{MainContract, MainContractInstance},
11 eip712::Eip712Transaction,
12 eip712::{hash_bytecode, Eip712Meta, Eip712TransactionRequest},
13 types::TransactionReceipt,
14 zks_provider::ZKSProvider,
15 zks_utils::{self, CONTRACT_DEPLOYER_ADDR, EIP712_TX_TYPE, ETHER_L1_ADDRESS, ETH_CHAIN_ID},
16};
17use ethers::{
18 abi::{decode, Abi, ParamType, Tokenizable},
19 prelude::{
20 encode_function_data,
21 k256::{
22 ecdsa::{RecoveryId, Signature as RecoverableSignature},
23 schnorr::signature::hazmat::PrehashSigner,
24 },
25 MiddlewareBuilder, SignerMiddleware,
26 },
27 providers::Middleware,
28 signers::{Signer, Wallet},
29 types::{
30 transaction::eip2718::TypedTransaction, Address, Bytes, Eip1559TransactionRequest, Log,
31 Signature, H160, H256, U256,
32 },
33};
34use lazy_static::lazy_static;
35use serde_json::{Map, Value};
36use std::collections::HashMap;
37use std::{fs::File, io::BufReader, path::PathBuf, str::FromStr, sync::Arc};
38use zksync_web3_rs::core::abi::Tokenize;
39
40const RAW_ERC20_DEPOSIT_GAS_LIMIT: &str = include_str!("DepositERC20GasLimit.json");
41
42lazy_static! {
43 static ref ERC20_DEPOSIT_GAS_LIMITS: HashMap<String, u64> = {
44 #![allow(clippy::expect_used)]
45 let mut m = HashMap::new();
46 let raw: Map<String, Value> = serde_json::from_str(RAW_ERC20_DEPOSIT_GAS_LIMIT)
47 .expect("Failed to parse DepositERC20GasLimit.json");
48 for (address, value) in raw.iter() {
49 m.insert(
50 address.to_owned(),
51 value
52 .as_u64()
53 .expect("Failed to ERC20 deposit gas limit for address {address:?}"),
54 );
55 }
56 m
57 };
58}
59
60#[derive(Clone, Debug)]
61pub struct ZKSWallet<M, D>
62where
63 M: Middleware + Clone,
64 D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Clone,
65{
66 pub eth_provider: Option<Arc<SignerMiddleware<M, Wallet<D>>>>,
68 pub era_provider: Option<Arc<SignerMiddleware<M, Wallet<D>>>>,
69 pub l2_wallet: Wallet<D>,
70 pub l1_wallet: Wallet<D>,
71}
72
73impl<M, D> ZKSWallet<M, D>
74where
75 M: Middleware + 'static + Clone,
76 D: PrehashSigner<(RecoverableSignature, RecoveryId)> + Sync + Send + Clone,
77{
78 pub fn new(
79 l2_wallet: Wallet<D>,
80 l1_wallet: Option<Wallet<D>>,
81 era_provider: Option<M>,
82 eth_provider: Option<M>,
83 ) -> Result<Self, ZKSWalletError<M, D>> {
84 let l1_wallet = match l1_wallet {
85 Some(wallet) => wallet,
86 None => l2_wallet.clone().with_chain_id(ETH_CHAIN_ID),
87 };
88 Ok(Self {
89 l2_wallet: l2_wallet.clone(),
90 l1_wallet: l1_wallet.clone(),
91 era_provider: era_provider.map(|p| p.with_signer(l2_wallet).into()),
92 eth_provider: eth_provider.map(|p| p.with_signer(l1_wallet).into()),
93 })
94 }
95
96 pub fn connect_eth_provider(mut self, eth_provider: M) -> Self {
97 self.eth_provider = Some(eth_provider.with_signer(self.l1_wallet.clone()).into());
98 self
99 }
100
101 pub fn connect_era_provider(mut self, era_provider: M) -> Self {
102 self.era_provider = Some(era_provider.with_signer(self.l2_wallet.clone()).into());
103 self
104 }
105
106 pub fn connect_eth_signer(mut self, eth_signer: SignerMiddleware<M, Wallet<D>>) -> Self {
107 self.eth_provider = Some(eth_signer.into());
108 self
109 }
110
111 pub fn connect_era_signer(mut self, era_signer: SignerMiddleware<M, Wallet<D>>) -> Self {
112 self.era_provider = Some(era_signer.into());
113 self
114 }
115
116 pub fn l2_address(&self) -> Address {
125 self.l2_wallet.address()
126 }
127
128 pub fn l1_address(&self) -> Address {
129 self.l1_wallet.address()
130 }
131
132 pub fn l2_chain_id(&self) -> u64 {
133 self.l2_wallet.chain_id()
134 }
135
136 pub fn l1_chain_id(&self) -> u64 {
137 self.l1_wallet.chain_id()
138 }
139
140 pub fn get_eth_provider(
141 &self,
142 ) -> Result<Arc<SignerMiddleware<M, Wallet<D>>>, ZKSWalletError<M, D>> {
143 match &self.eth_provider {
144 Some(eth_provider) => Ok(Arc::clone(eth_provider)),
145 None => Err(ZKSWalletError::NoL1ProviderError()),
146 }
147 }
148
149 pub fn get_era_provider(
150 &self,
151 ) -> Result<Arc<SignerMiddleware<M, Wallet<D>>>, ZKSWalletError<M, D>> {
152 match &self.era_provider {
153 Some(era_provider) => Ok(Arc::clone(era_provider)),
154 None => Err(ZKSWalletError::NoL2ProviderError()),
155 }
156 }
157
158 pub async fn eth_balance(&self) -> Result<U256, ZKSWalletError<M, D>>
159 where
160 M: ZKSProvider,
161 {
162 match &self.eth_provider {
163 Some(eth_provider) => Ok(eth_provider.get_balance(self.l1_address(), None).await?),
165 None => Err(ZKSWalletError::CustomError("no eth provider".to_owned())),
166 }
167 }
168
169 pub async fn era_balance(&self) -> Result<U256, ZKSWalletError<M, D>>
170 where
171 M: ZKSProvider,
172 {
173 match &self.era_provider {
174 Some(era_provider) => Ok(era_provider.get_balance(self.l2_address(), None).await?),
176 None => Err(ZKSWalletError::CustomError("no era provider".to_owned())),
177 }
178 }
179
180 pub async fn transfer(
181 &self,
182 request: &TransferRequest,
183 _token: Option<Address>,
185 ) -> Result<H256, ZKSWalletError<M, D>>
186 where
187 M: ZKSProvider,
188 {
189 let era_provider = self.get_era_provider()?;
190
191 let mut transfer_request: Eip1559TransactionRequest = request.clone().into();
192
193 let fee = era_provider.estimate_fee(transfer_request.clone()).await?;
194 transfer_request = transfer_request.max_priority_fee_per_gas(fee.max_priority_fee_per_gas);
195 transfer_request = transfer_request.max_fee_per_gas(fee.max_fee_per_gas);
196
197 let transaction: TypedTransaction = transfer_request.into();
198
199 let transaction_receipt = era_provider
201 .send_transaction(transaction, None)
202 .await?
203 .await?
204 .ok_or(ZKSWalletError::CustomError(
205 "No transaction receipt".to_owned(),
206 ))?;
207
208 Ok(transaction_receipt.transaction_hash)
209 }
210
211 pub async fn transfer_eip712(
212 &self,
213 request: &TransferRequest,
214 _token: Option<Address>,
216 ) -> Result<H256, ZKSWalletError<M, D>>
217 where
218 M: ZKSProvider,
219 {
220 let era_provider = self.get_era_provider()?;
221
222 let transaction_receipt = era_provider
223 .send_transaction_eip712(&self.l2_wallet, request.clone())
224 .await?
225 .await?
226 .ok_or(ZKSWalletError::CustomError(
227 "No transaction receipt".to_owned(),
228 ))?;
229
230 Ok(transaction_receipt.transaction_hash)
231 }
232
233 pub async fn deposit(&self, request: &DepositRequest) -> Result<H256, ZKSWalletError<M, D>>
234 where
235 M: ZKSProvider,
236 {
237 let to = request.to.unwrap_or(self.l2_address());
238 let call_data = Bytes::default();
239 let l2_gas_limit: U256 = request.l2_gas_limit;
240 let l2_value = request.amount;
241 let gas_per_pubdata_byte: U256 = request.gas_per_pubdata_byte;
242 let gas_price = request
243 .gas_price
244 .unwrap_or(self.get_eth_provider()?.get_gas_price().await?);
245 let gas_limit: U256 = request.gas_limit;
246 let operator_tip: U256 = request.operator_tip;
247 let base_cost = self
248 .get_base_cost(gas_limit, gas_per_pubdata_byte, gas_price)
249 .await?;
250 let l1_value = base_cost + operator_tip + request.amount;
251 let refund_recipient = self.l1_address();
253 let receipt = if request.token == ETHER_L1_ADDRESS {
256 let main_contract_address = self.get_era_provider()?.get_main_contract().await?;
257 let main_contract =
258 MainContractInstance::new(main_contract_address, self.get_eth_provider()?);
259
260 main_contract
261 .request_l2_transaction(
262 to,
263 l2_value,
264 call_data,
265 l2_gas_limit,
266 gas_per_pubdata_byte,
267 Default::default(),
268 refund_recipient,
269 gas_price,
270 gas_limit,
271 l1_value,
272 )
273 .await?
274 } else {
275 self.deposit_erc20_token(
276 request.token,
277 request.amount().to_owned(),
278 to,
279 operator_tip,
280 request.bridge_address,
281 None,
282 Some(gas_price),
283 )
284 .await?
285 };
286
287 Ok(receipt.transaction_hash)
288 }
289
290 async fn deposit_erc20_token(
291 &self,
292 l1_token_address: Address,
293 amount: U256,
294 to: Address,
295 operator_tip: U256,
296 bridge_address: Option<Address>,
297 max_fee_per_gas: Option<U256>,
298 gas_price: Option<U256>,
299 ) -> Result<TransactionReceipt, ZKSWalletError<M, D>>
300 where
301 M: ZKSProvider,
302 {
303 let eth_provider = self.get_eth_provider()?;
304 let era_provider = self.get_era_provider()?;
305
306 let gas_limit: U256 = {
307 let address_str = format!("{l1_token_address:?}");
308 let is_mainnet =
309 self.get_era_provider()?.get_chainid().await? == ERA_MAINNET_CHAIN_ID.into();
310 if is_mainnet {
311 (*ERC20_DEPOSIT_GAS_LIMITS)
312 .get(&address_str)
313 .unwrap_or(&DEFAULT_ERC20_DEPOSIT_GAS_LIMIT)
314 .to_owned()
315 } else {
316 DEFAULT_ERC20_DEPOSIT_GAS_LIMIT
317 }
318 }
319 .into();
320
321 let gas_price = if let Some(max_fee_per_gas) = max_fee_per_gas {
324 max_fee_per_gas
325 } else if let Some(gas_price) = gas_price {
326 gas_price
327 } else {
328 era_provider.get_gas_price().await?
329 };
330
331 let l2_gas_limit = U256::from(3_000_000_u32);
332
333 let base_cost: U256 = self
334 .get_base_cost(
335 l2_gas_limit,
336 DEPOSIT_GAS_PER_PUBDATA_LIMIT.into(),
337 gas_price,
338 )
339 .await?;
340
341 let value = base_cost + operator_tip;
343
344 let data: Bytes = {
345 let bridge_contract = abi::l1_bridge_contract();
346
347 #[allow(clippy::expect_used)]
348 let contract_function = bridge_contract
349 .function("deposit")
350 .expect("failed to get deposit function parameters");
351
352 let params = (
353 to,
354 l1_token_address,
355 amount,
356 l2_gas_limit,
357 U256::from(DEPOSIT_GAS_PER_PUBDATA_LIMIT),
358 );
359
360 #[allow(clippy::expect_used)]
361 contract_function
362 .encode_input(¶ms.into_tokens())
363 .expect("failed to encode deposit function parameters")
364 .into()
365 };
366
367 let chain_id = eth_provider.get_chainid().await?.as_u64();
368
369 let bridge_address: Address = match bridge_address {
370 Some(address) => address,
371 None => {
372 let bridge_contracts = era_provider.get_bridge_contracts().await?;
373 bridge_contracts.l1_erc20_default_bridge
374 }
375 };
376
377 let deposit_transaction = Eip1559TransactionRequest {
378 from: Some(self.get_eth_provider()?.address()),
379 to: Some(bridge_address.into()),
380 gas: Some(gas_limit),
381 value: Some(value),
382 data: Some(data),
383 nonce: None,
384 access_list: Default::default(),
385 max_priority_fee_per_gas: None, max_fee_per_gas: None, chain_id: Some(chain_id.into()),
388 };
389
390 let _approve_tx_receipt = self
391 .approve_erc20(bridge_address, amount, l1_token_address)
392 .await?;
393 let pending_transaction = eth_provider
394 .send_transaction(deposit_transaction, None)
395 .await?;
396
397 pending_transaction
398 .await?
399 .ok_or(ZKSWalletError::CustomError(
400 "no transaction receipt".to_owned(),
401 ))
402 }
403
404 async fn approve_erc20(
405 &self,
406 bridge: Address,
407 amount: U256,
408 token: Address,
409 ) -> Result<TransactionReceipt, ZKSWalletError<M, D>>
410 where
411 M: ZKSProvider,
412 {
413 let provider = self.get_eth_provider()?;
414 let function_signature =
415 "function approve(address spender,uint256 amount) public virtual returns (bool)";
416 let parameters = [format!("{bridge:?}"), format!("{amount:?}")];
417 let response = provider
418 .send(
419 &self.l1_wallet,
420 token,
421 function_signature,
422 Some(parameters.into()),
423 None,
424 )
425 .await?;
426
427 response.await?.ok_or(ZKSWalletError::CustomError(
428 "No transaction receipt for erc20 approval".to_owned(),
429 ))
430 }
431
432 async fn get_base_cost(
433 &self,
434 gas_limit: U256,
435 gas_per_pubdata_byte: U256,
436 gas_price: U256,
437 ) -> Result<U256, ZKSWalletError<M, D>>
438 where
439 M: ZKSProvider,
440 {
441 let main_contract_address = self.get_era_provider()?.get_main_contract().await?;
442 let main_contract = MainContract::new(main_contract_address, self.get_eth_provider()?);
443 let base_cost: U256 = main_contract
444 .l_2_transaction_base_cost(gas_price, gas_limit, gas_per_pubdata_byte)
445 .call()
446 .await?;
447
448 Ok(base_cost)
449 }
450
451 pub async fn deploy_from_bytecode<T>(
452 &self,
453 contract_bytecode: &[u8],
454 contract_dependencies: Option<Vec<Vec<u8>>>,
455 _constructor_parameters: Option<T>,
457 ) -> Result<H160, ZKSWalletError<M, D>>
458 where
459 M: ZKSProvider,
460 T: Tokenizable,
461 {
462 let era_provider = self.get_era_provider()?;
463
464 let custom_data = Eip712Meta::new().factory_deps({
465 let mut factory_deps = Vec::new();
466 if let Some(contract_dependencies) = contract_dependencies {
467 factory_deps.extend(contract_dependencies);
468 }
469 factory_deps.push(contract_bytecode.to_vec());
470 factory_deps
471 });
472
473 let mut contract_deployer_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
474 contract_deployer_path.push("src/abi/ContractDeployer.json");
475 let mut deploy_request = Eip712TransactionRequest::new()
476 .r#type(EIP712_TX_TYPE)
477 .from(self.l2_address())
478 .to(Address::from_str(CONTRACT_DEPLOYER_ADDR).map_err(|e| {
479 ZKSWalletError::CustomError(format!("invalid contract deployer address: {e}"))
480 })?)
481 .chain_id(self.l2_chain_id())
482 .nonce(
483 era_provider
484 .get_transaction_count(self.l2_address(), None)
485 .await?,
486 )
487 .gas_price(era_provider.get_gas_price().await?)
488 .max_fee_per_gas(era_provider.get_gas_price().await?)
489 .data({
490 let contract_deployer = Abi::load(BufReader::new(
491 File::open(contract_deployer_path).map_err(|e| {
492 ZKSWalletError::CustomError(format!(
493 "failed to open ContractDeployer abi: {e}"
494 ))
495 })?,
496 ))
497 .map_err(|e| {
498 ZKSWalletError::CustomError(format!("failed to load ContractDeployer abi: {e}"))
499 })?;
500 let create = contract_deployer.function("create").map_err(|e| {
501 ZKSWalletError::CustomError(format!("failed to get create function: {e}"))
502 })?;
503 let salt = [0_u8; 32];
505 let bytecode_hash = hash_bytecode(contract_bytecode)?;
506 let call_data = Bytes::default();
507
508 encode_function_data(create, (salt, bytecode_hash, call_data))?
509 })
510 .custom_data(custom_data.clone());
511
512 let fee = era_provider.estimate_fee(deploy_request.clone()).await?;
513 deploy_request = deploy_request
514 .max_priority_fee_per_gas(fee.max_priority_fee_per_gas)
515 .max_fee_per_gas(fee.max_fee_per_gas)
516 .gas_limit(fee.gas_limit);
517
518 let signable_data: Eip712Transaction = deploy_request.clone().try_into()?;
519 let signature: Signature = self.l2_wallet.sign_typed_data(&signable_data).await?;
520 deploy_request =
521 deploy_request.custom_data(custom_data.custom_signature(signature.to_vec()));
522
523 let encoded_rlp = &*deploy_request.rlp_signed(signature)?;
524 let pending_transaction = era_provider
525 .send_raw_transaction([&[EIP712_TX_TYPE], encoded_rlp].concat().into())
526 .await?;
527
528 let transaction_receipt = pending_transaction
531 .await?
532 .ok_or(ZKSWalletError::CustomError(
533 "no transaction receipt".to_owned(),
534 ))?;
535
536 let contract_address =
537 transaction_receipt
538 .contract_address
539 .ok_or(ZKSWalletError::CustomError(
540 "no contract address".to_owned(),
541 ))?;
542
543 Ok(contract_address)
544 }
545
546 pub async fn deploy(&self, request: &DeployRequest) -> Result<H160, ZKSWalletError<M, D>>
547 where
548 M: ZKSProvider,
549 {
550 let era_provider = self.get_era_provider()?;
551
552 let eip712_request: Eip712TransactionRequest = request.clone().try_into()?;
553
554 let transaction_receipt = era_provider
555 .send_transaction_eip712(&self.l2_wallet, eip712_request)
556 .await?
557 .await?
558 .ok_or(ZKSWalletError::CustomError(
559 "No transaction receipt".to_owned(),
560 ))?;
561
562 transaction_receipt
563 .contract_address
564 .ok_or(ZKSWalletError::CustomError(
565 "No contract address".to_owned(),
566 ))
567 }
568
569 pub async fn withdraw(&self, request: &WithdrawRequest) -> Result<H256, ZKSWalletError<M, D>>
570 where
571 M: ZKSProvider,
572 {
573 let era_provider = self.get_era_provider()?;
574 let transaction_receipt = era_provider
575 .send_transaction_eip712(&self.l2_wallet, request.clone())
576 .await?
577 .await?
578 .ok_or(ZKSWalletError::CustomError(
579 "No transaction receipt".to_owned(),
580 ))?;
581
582 Ok(transaction_receipt.transaction_hash)
583 }
584
585 pub async fn finalize_withdraw(&self, tx_hash: H256) -> Result<H256, ZKSWalletError<M, D>>
586 where
587 M: ZKSProvider,
588 {
589 let era_provider = self.get_era_provider()?;
590 let eth_provider = self.get_eth_provider()?;
591
592 let withdrawal_receipt = era_provider.get_transaction_receipt(tx_hash).await?.ok_or(
593 ZKSWalletError::CustomError("Error getting transaction receipt of withdraw".to_owned()),
594 )?;
595
596 let messenger_contract_address = Address::from_str(zks_utils::CONTRACTS_L1_MESSENGER_ADDR)
597 .map_err(|error| {
598 ZKSWalletError::CustomError(format!("failed to parse contract address: {error}"))
599 })?;
600
601 let logs: Vec<Log> = withdrawal_receipt
602 .logs
603 .into_iter()
604 .filter(|log| {
605 log.address == messenger_contract_address
607 })
608 .collect();
609
610 let (_, l2_to_l1_log_index) = serde_json::from_value::<Vec<Value>>(
612 withdrawal_receipt
613 .other
614 .get("l2ToL1Logs")
615 .ok_or(ZKSWalletError::CustomError(
616 "Field not present in receipt".to_owned(),
617 ))?
618 .clone(),
619 )
620 .map_err(|err| {
621 ZKSWalletError::CustomError(format!("Error getting logs in receipt: {err:?}"))
622 })?
623 .iter()
624 .zip(0_u64..)
625 .find(|(log, _)| {
626 if let Some(sender) = log.get("sender") {
627 sender == zks_utils::CONTRACTS_L1_MESSENGER_ADDR
628 } else {
629 false
630 }
631 })
632 .ok_or(ZKSWalletError::CustomError(
633 "Error getting log index parameter".to_owned(),
634 ))?;
635
636 let filtered_log = logs
637 .first()
638 .ok_or(ZKSWalletError::CustomError(
639 "Error getting log in receipt".to_owned(),
640 ))?
641 .clone();
642 let proof = era_provider
643 .get_l2_to_l1_log_proof(tx_hash, Some(l2_to_l1_log_index))
644 .await?
645 .ok_or(ZKSWalletError::CustomError(
646 "Error getting proof parameter".to_owned(),
647 ))?;
648 let main_contract = era_provider.get_main_contract().await?;
649 let merkle_proof: Vec<H256> = proof.merkle_proof;
650 let l1_batch_number = era_provider.get_l1_batch_number().await?;
651 let l2_message_index = U256::from(proof.id);
652
653 let l2_tx_number_in_block: String = serde_json::from_value::<String>(
654 withdrawal_receipt
655 .other
656 .get("l1BatchTxIndex")
657 .ok_or(ZKSWalletError::CustomError(
658 "Field not present in receipt".to_owned(),
659 ))?
660 .clone(),
661 )
662 .map_err(|err| ZKSWalletError::CustomError(format!("Failed to deserialize field {err}")))?;
663
664 let message: Bytes = decode(&[ParamType::Bytes], &filtered_log.data)
665 .map_err(|e| ZKSWalletError::CustomError(format!("failed to decode log data: {e}")))?
666 .first()
667 .ok_or(ZKSWalletError::CustomError(
668 "Message not found in decoded data".to_owned(),
669 ))?
670 .clone()
671 .into_bytes()
672 .ok_or(ZKSWalletError::CustomError(
673 "Could not convert message to bytes".to_owned(),
674 ))?
675 .into();
676
677 let parameters = [
678 format!("{l1_batch_number:?}"),
679 format!("{l2_message_index:?}"),
680 l2_tx_number_in_block,
681 hex::encode(&message),
682 format!("{merkle_proof:?}")
683 .replace('"', "")
684 .replace(' ', ""),
685 ];
686
687 let function_signature = "function finalizeEthWithdrawal(uint256 _l2BlockNumber,uint256 _l2MessageIndex,uint16 _l2TxNumberInBlock,bytes calldata _message,bytes32[] calldata _merkleProof) external";
688 let transaction_receipt = eth_provider
689 .send(
690 &self.l1_wallet,
691 main_contract,
692 function_signature,
693 Some(parameters.into()),
694 None,
695 )
696 .await?
697 .await?
698 .ok_or(ZKSWalletError::CustomError(
699 "No transaction receipt".to_owned(),
700 ))?;
701
702 Ok(transaction_receipt.transaction_hash)
703 }
704}