zksync_web3_rs/zks_wallet/
wallet.rs

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    /// Eth provider
67    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 connect_eth(&mut self, host: &str, port: u16) {
117    //     self.eth_provider = Provider::try_from(format!("http://{host}:{port}")).ok().map(|p| p.with_signer(self.wallet));
118    // }
119
120    // pub fn connect_era(&mut self, host: &str, port: u16) {
121    //     self.era_provider = Provider::try_from(format!("http://{host}:{port}")).ok().map(|p| p.with_signer(self.wallet));
122    // }
123
124    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            // TODO: Should we have a balance_on_block method?
164            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            // TODO: Should we have a balance_on_block method?
175            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        // TODO: Support multiple-token transfers.
184        _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        // TODO: add block as an override.
200        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        // TODO: Support multiple-token transfers.
215        _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 factory_deps = [];
252        let refund_recipient = self.l1_address();
253        // FIXME check base cost
254
255        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        // If the user has already provided max_fee_per_gas or gas_price, we will use
322        // it to calculate the base cost for the transaction
323        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        // ERC20 token, `msg.value` is used only for the fee.
342        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(&params.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, // FIXME
386            max_fee_per_gas: None,          // FIXME
387            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        // TODO: accept constructor parameters.
456        _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                // TODO: User could provide this instead of defaulting.
504                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        // TODO: Should we wait here for the transaction to be confirmed on-chain?
529
530        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.topics[0] == topic &&
606                log.address == messenger_contract_address
607            })
608            .collect();
609
610        // Get all the parameters needed to call the finalizeWithdrawal function on the main contract contract.
611        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}