1use bytes::Bytes;
2use calldata::encode_calldata;
3use ethereum_types::{H160, H256, U256};
4use ethrex_common::types::EIP7702Transaction;
5use ethrex_common::types::FeeTokenTransaction;
6use ethrex_common::types::Fork;
7use ethrex_common::utils::keccak;
8use ethrex_common::{
9 Address,
10 types::{
11 AccessListEntry, BlobsBundle, EIP1559Transaction, GenericTransaction, TxKind, TxType,
12 WrappedEIP4844Transaction,
13 },
14};
15use ethrex_l2_common::{calldata::Value, messages::L1MessageProof};
16use ethrex_l2_rpc::{
17 clients::get_l1_message_proof,
18 signer::{LocalSigner, Signable, Signer},
19};
20use ethrex_rlp::encode::RLPEncode;
21use ethrex_rpc::clients::eth::{EthClient, Overrides, errors::EthClientError};
22use ethrex_rpc::types::block_identifier::{BlockIdentifier, BlockTag};
23use ethrex_rpc::types::receipt::RpcReceipt;
24use secp256k1::SecretKey;
25use serde::{Deserialize, Deserializer, Serialize, Serializer};
26use std::ops::{Add, Div};
27use std::str::FromStr;
28use std::{fs::read_to_string, path::Path};
29use tracing::{error, warn};
30
31pub mod calldata;
32pub mod l1_to_l2_tx_data;
33pub mod privileged_data;
34
35pub use l1_to_l2_tx_data::{L1ToL2TransactionData, send_l1_to_l2_tx};
36
37#[doc(inline)]
39pub use ethrex_sdk_contract_utils::*;
40
41use calldata::from_hex_string_to_h256_array;
42
43use crate::privileged_data::PrivilegedTransactionData;
44
45pub const DEFAULT_BRIDGE_ADDRESS: Address = H160([
47 0x79, 0x7b, 0x34, 0x7d, 0x22, 0x09, 0xf7, 0xdc, 0xb8, 0xf9, 0xbb, 0x68, 0xfe, 0x42, 0x96, 0xa0,
48 0x5d, 0x5a, 0x2c, 0x2b,
49]);
50
51pub const COMMON_BRIDGE_L2_ADDRESS: Address = H160([
53 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
54 0x00, 0x00, 0xff, 0xff,
55]);
56
57pub const L2_TO_L1_MESSENGER_ADDRESS: Address = H160([
59 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
60 0x00, 0x00, 0xff, 0xfe,
61]);
62
63pub const FEE_TOKEN_REGISTRY_ADDRESS: Address = H160([
65 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66 0x00, 0x00, 0xff, 0xfc,
67]);
68
69pub const FEE_TOKEN_PRICER_ADDRESS: Address = H160([
71 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
72 0x00, 0x00, 0xff, 0xfb,
73]);
74
75pub const ADDRESS_ALIASING: Address = H160([
77 0xee, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
78 0x00, 0x00, 0x11, 0xff,
79]);
80
81pub const L2_WITHDRAW_SIGNATURE: &str = "withdraw(address)";
82
83pub const SP1_VERIFIER_ID: u8 = 1;
85pub const RISC0_VERIFIER_ID: u8 = 2;
87
88pub const REGISTER_FEE_TOKEN_SIGNATURE: &str = "registerNewFeeToken(address)";
89pub const SET_FEE_TOKEN_RATIO_SIGNATURE: &str = "setFeeTokenRatio(address,uint256)";
90
91const ERC1967_PROXY_BYTECODE: &[u8] = include_bytes!(concat!(
94 env!("OUT_DIR"),
95 "/contracts/solc_out/ERC1967Proxy.bytecode"
96));
97
98#[derive(Debug, thiserror::Error)]
99pub enum SdkError {
100 #[error("Failed to parse address from hex")]
101 FailedToParseAddressFromHex,
102}
103
104pub fn bridge_address() -> Result<Address, SdkError> {
106 std::env::var("ETHREX_WATCHER_BRIDGE_ADDRESS")
107 .unwrap_or(format!("{DEFAULT_BRIDGE_ADDRESS:#x}"))
108 .parse()
109 .map_err(|_| SdkError::FailedToParseAddressFromHex)
110}
111
112pub async fn wait_for_transaction_receipt(
113 tx_hash: H256,
114 client: &EthClient,
115 max_retries: u64,
116) -> Result<RpcReceipt, EthClientError> {
117 let mut r#try = 1;
118 loop {
119 match client.get_transaction_receipt(tx_hash).await {
120 Ok(Some(receipt)) => return Ok(receipt),
121 Ok(None) => {
122 }
124 Err(e) => {
125 let error_msg = e.to_string();
129 if !error_msg.contains("transaction indexing is in progress") {
130 return Err(e);
131 }
132 }
134 }
135
136 println!("[{try}/{max_retries}] Retrying to get transaction receipt for {tx_hash:#x}");
137
138 if max_retries == r#try {
139 return Err(EthClientError::Custom(format!(
140 "Transaction receipt for {tx_hash:#x} not found after {max_retries} retries"
141 )));
142 }
143 r#try += 1;
144
145 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
146 }
147}
148
149pub async fn transfer(
150 amount: U256,
151 from: Address,
152 to: Address,
153 private_key: &SecretKey,
154 client: &EthClient,
155) -> Result<H256, EthClientError> {
156 println!("Transferring {amount} from {from:#x} to {to:#x}");
157 let gas_price = client
158 .get_gas_price_with_extra(20)
159 .await?
160 .try_into()
161 .map_err(|_| {
162 EthClientError::InternalError("Failed to convert gas_price to a u64".to_owned())
163 })?;
164
165 let tx = build_generic_tx(
166 client,
167 TxType::EIP1559,
168 to,
169 from,
170 Default::default(),
171 Overrides {
172 value: Some(amount),
173 max_fee_per_gas: Some(gas_price),
174 max_priority_fee_per_gas: Some(gas_price),
175 ..Default::default()
176 },
177 )
178 .await?;
179
180 let signer = LocalSigner::new(*private_key).into();
181 send_generic_transaction(client, tx, &signer).await
182}
183
184pub async fn deposit_through_transfer(
185 amount: U256,
186 from: Address,
187 from_pk: &SecretKey,
188 eth_client: &EthClient,
189) -> Result<H256, EthClientError> {
190 println!("Depositing {amount} from {from:#x} to bridge");
191 transfer(
192 amount,
193 from,
194 bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
195 from_pk,
196 eth_client,
197 )
198 .await
199}
200
201pub async fn withdraw(
202 amount: U256,
203 from: Address,
204 from_pk: SecretKey,
205 proposer_client: &EthClient,
206 nonce: Option<u64>,
207 gas_limit: Option<u64>,
208) -> Result<H256, EthClientError> {
209 let withdraw_transaction = build_generic_tx(
210 proposer_client,
211 TxType::EIP1559,
212 COMMON_BRIDGE_L2_ADDRESS,
213 from,
214 Bytes::from(encode_calldata(
215 L2_WITHDRAW_SIGNATURE,
216 &[Value::Address(from)],
217 )?),
218 Overrides {
219 value: Some(amount),
220 nonce,
221 gas_limit,
222 ..Default::default()
223 },
224 )
225 .await?;
226
227 let signer = LocalSigner::new(from_pk).into();
228
229 send_generic_transaction(proposer_client, withdraw_transaction, &signer).await
230}
231
232pub async fn claim_withdraw(
233 amount: U256,
234 from: Address,
235 from_pk: SecretKey,
236 eth_client: &EthClient,
237 message_proof: &L1MessageProof,
238) -> Result<H256, EthClientError> {
239 println!("Claiming {amount} from bridge to {from:#x}");
240
241 const CLAIM_WITHDRAWAL_SIGNATURE: &str = "claimWithdrawal(uint256,uint256,uint256,bytes32[])";
242
243 let calldata_values = vec![
244 Value::Uint(amount),
245 Value::Uint(message_proof.batch_number.into()),
246 Value::Uint(message_proof.message_id),
247 Value::Array(
248 message_proof
249 .merkle_proof
250 .iter()
251 .map(|hash| Value::FixedBytes(hash.as_fixed_bytes().to_vec().into()))
252 .collect(),
253 ),
254 ];
255
256 let claim_withdrawal_data = encode_calldata(CLAIM_WITHDRAWAL_SIGNATURE, &calldata_values)?;
257
258 println!(
259 "Claiming withdrawal with calldata: {}",
260 hex::encode(&claim_withdrawal_data)
261 );
262
263 let claim_tx = build_generic_tx(
264 eth_client,
265 TxType::EIP1559,
266 bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
267 from,
268 claim_withdrawal_data.into(),
269 Overrides {
270 from: Some(from),
271 ..Default::default()
272 },
273 )
274 .await?;
275
276 let signer = LocalSigner::new(from_pk).into();
277
278 send_generic_transaction(eth_client, claim_tx, &signer).await
279}
280
281pub async fn claim_erc20withdraw(
282 token_l1: Address,
283 token_l2: Address,
284 amount: U256,
285 from_signer: &Signer,
286 eth_client: &EthClient,
287 message_proof: &L1MessageProof,
288) -> Result<H256, EthClientError> {
289 let from = from_signer.address();
290 const CLAIM_WITHDRAWAL_ERC20_SIGNATURE: &str =
291 "claimWithdrawalERC20(address,address,uint256,uint256,uint256,bytes32[])";
292
293 let calldata_values = vec![
294 Value::Address(token_l1),
295 Value::Address(token_l2),
296 Value::Uint(amount),
297 Value::Uint(U256::from(message_proof.batch_number)),
298 Value::Uint(message_proof.message_id),
299 Value::Array(
300 message_proof
301 .merkle_proof
302 .iter()
303 .map(|v| Value::Uint(U256::from_big_endian(v.as_bytes())))
304 .collect(),
305 ),
306 ];
307
308 let claim_withdrawal_data =
309 encode_calldata(CLAIM_WITHDRAWAL_ERC20_SIGNATURE, &calldata_values)?;
310
311 println!(
312 "Claiming withdrawal with calldata: {}",
313 hex::encode(&claim_withdrawal_data)
314 );
315
316 let claim_tx = build_generic_tx(
317 eth_client,
318 TxType::EIP1559,
319 bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
320 from,
321 claim_withdrawal_data.into(),
322 Overrides {
323 from: Some(from),
324 ..Default::default()
325 },
326 )
327 .await?;
328
329 send_generic_transaction(eth_client, claim_tx, from_signer).await
330}
331
332pub async fn deposit_erc20(
333 token_l1: Address,
334 token_l2: Address,
335 amount: U256,
336 from: Address,
337 from_signer: &Signer,
338 eth_client: &EthClient,
339) -> Result<H256, EthClientError> {
340 println!("Claiming {amount} from bridge to {from:#x}");
341
342 const DEPOSIT_ERC20_SIGNATURE: &str = "depositERC20(address,address,address,uint256)";
343
344 let calldata_values = vec![
345 Value::Address(token_l1),
346 Value::Address(token_l2),
347 Value::Address(from),
348 Value::Uint(amount),
349 ];
350
351 let deposit_data = encode_calldata(DEPOSIT_ERC20_SIGNATURE, &calldata_values)?;
352
353 let mut deposit_tx = build_generic_tx(
354 eth_client,
355 TxType::EIP1559,
356 bridge_address().map_err(|err| EthClientError::Custom(err.to_string()))?,
357 from,
358 deposit_data.into(),
359 Overrides {
360 from: Some(from),
361 ..Default::default()
362 },
363 )
364 .await?;
365
366 deposit_tx.gas = deposit_tx.gas.map(|gas| gas * 2); send_generic_transaction(eth_client, deposit_tx, from_signer).await
369}
370
371pub fn secret_key_deserializer<'de, D>(deserializer: D) -> Result<SecretKey, D::Error>
372where
373 D: Deserializer<'de>,
374{
375 let hex = H256::deserialize(deserializer)?;
376 SecretKey::from_slice(hex.as_bytes()).map_err(serde::de::Error::custom)
377}
378
379pub fn secret_key_serializer<S>(secret_key: &SecretKey, serializer: S) -> Result<S::Ok, S::Error>
380where
381 S: Serializer,
382{
383 let hex = H256::from_slice(&secret_key.secret_bytes());
384 hex.serialize(serializer)
385}
386pub const DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS: Address = H160([
389 0x4e, 0x59, 0xb4, 0x48, 0x47, 0xb3, 0x79, 0x57, 0x85, 0x88, 0x92, 0x0c, 0xa7, 0x8f, 0xbf, 0x26,
390 0xc0, 0xb4, 0x95, 0x6c,
391]);
392
393pub const SAFE_SINGLETON_FACTORY_ADDRESS: Address = H160([
396 0x91, 0x4d, 0x7F, 0xec, 0x6a, 0xac, 0x8c, 0xd5, 0x42, 0xe7, 0x2b, 0xca, 0x78, 0xb3, 0x06, 0x50,
397 0xd4, 0x56, 0x43, 0xd7,
398]);
399
400pub const CREATE2DEPLOYER_ADDRESS: Address = H160([
403 0x13, 0xb0, 0xd8, 0x5c, 0xcb, 0x8b, 0xf8, 0x60, 0xb6, 0xb7, 0x9a, 0xf3, 0x02, 0x9f, 0xca, 0x08,
404 0x1a, 0xe9, 0xbe, 0xf2,
405]);
406
407#[derive(Default, Clone)]
408pub struct ProxyDeployment {
409 pub proxy_address: Address,
410 pub proxy_tx_hash: H256,
411 pub implementation_address: Address,
412 pub implementation_tx_hash: H256,
413}
414
415#[derive(Debug, thiserror::Error)]
416pub enum DeployError {
417 #[error("Failed to decode init code: {0}")]
418 FailedToReadInitCode(#[from] std::io::Error),
419 #[error("Failed to decode init code: {0}")]
420 FailedToDecodeBytecode(#[from] hex::FromHexError),
421 #[error("Failed to deploy contract: {0}")]
422 FailedToDeploy(#[from] EthClientError),
423 #[error("Proxy bytecode not found. Make sure to compile the sdk with `COMPILE_CONTRACTS` set.")]
424 ProxyBytecodeNotFound,
425}
426
427pub async fn create_deploy(
428 client: &EthClient,
429 deployer: &Signer,
430 init_code: Bytes,
431 overrides: Overrides,
432) -> Result<(H256, Address), EthClientError> {
433 let mut deploy_overrides = overrides;
434 deploy_overrides.to = Some(TxKind::Create);
435
436 let deploy_tx = build_generic_tx(
437 client,
438 TxType::EIP1559,
439 Address::zero(),
440 deployer.address(),
441 init_code,
442 deploy_overrides,
443 )
444 .await?;
445 let deploy_tx_hash = send_generic_transaction(client, deploy_tx, deployer).await?;
446
447 let receipt = wait_for_transaction_receipt(deploy_tx_hash, client, 1000).await?;
448
449 let deployed_address = receipt.tx_info.contract_address.ok_or_else(|| {
450 EthClientError::Custom("Deploy transaction did not create a contract".to_owned())
451 })?;
452
453 Ok((deploy_tx_hash, deployed_address))
454}
455
456pub async fn create2_deploy_from_path(
457 constructor_args: &[u8],
458 contract_path: &Path,
459 deployer: &Signer,
460 salt: &[u8],
461 eth_client: &EthClient,
462) -> Result<(H256, Address), DeployError> {
463 let bytecode_hex = read_to_string(contract_path)?;
464 let bytecode = hex::decode(bytecode_hex.trim_start_matches("0x").trim())?;
465 create2_deploy_from_bytecode(constructor_args, &bytecode, deployer, salt, eth_client).await
466}
467
468pub async fn create2_deploy_from_bytecode(
469 constructor_args: &[u8],
470 bytecode: &[u8],
471 deployer: &Signer,
472 salt: &[u8],
473 eth_client: &EthClient,
474) -> Result<(H256, Address), DeployError> {
475 let (deploy_tx_hash, contract_address) = create2_deploy_from_bytecode_no_wait(
476 constructor_args,
477 bytecode,
478 deployer,
479 salt,
480 eth_client,
481 Overrides::default(),
482 )
483 .await?;
484 wait_for_transaction_receipt(deploy_tx_hash, eth_client, 10).await?;
485 Ok((deploy_tx_hash, contract_address))
486}
487
488pub async fn create2_deploy_from_bytecode_no_wait(
489 constructor_args: &[u8],
490 bytecode: &[u8],
491 deployer: &Signer,
492 salt: &[u8],
493 eth_client: &EthClient,
494 overrides: Overrides,
495) -> Result<(H256, Address), DeployError> {
496 let init_code = [bytecode, constructor_args].concat();
497
498 let (deploy_tx_hash, contract_address) =
499 create2_deploy_no_wait(salt, &init_code, deployer, eth_client, overrides).await?;
500
501 Ok((deploy_tx_hash, contract_address))
502}
503
504fn build_proxy_init_code(implementation_address: Address) -> Result<Bytes, DeployError> {
506 #[allow(clippy::const_is_empty)]
507 if ERC1967_PROXY_BYTECODE.is_empty() {
508 return Err(DeployError::ProxyBytecodeNotFound);
509 }
510
511 let mut init_code = ERC1967_PROXY_BYTECODE.to_vec();
512
513 init_code.extend(H256::from(implementation_address).0);
514 init_code.extend(H256::from_low_u64_be(0x40).0);
515 init_code.extend(H256::zero().0);
516
517 Ok(Bytes::from(init_code))
518}
519
520async fn deploy_proxy(
525 deployer: &Signer,
526 eth_client: &EthClient,
527 implementation_address: Address,
528 salt: &[u8],
529) -> Result<(H256, Address), DeployError> {
530 let (tx_hash, address) = deploy_proxy_no_wait(
531 deployer,
532 eth_client,
533 implementation_address,
534 salt,
535 Overrides::default(),
536 )
537 .await?;
538 wait_for_transaction_receipt(tx_hash, eth_client, 10).await?;
539 Ok((tx_hash, address))
540}
541
542async fn deploy_proxy_no_wait(
544 deployer: &Signer,
545 eth_client: &EthClient,
546 implementation_address: Address,
547 salt: &[u8],
548 overrides: Overrides,
549) -> Result<(H256, Address), DeployError> {
550 let init_code = build_proxy_init_code(implementation_address)?;
551
552 create2_deploy_no_wait(salt, &init_code, deployer, eth_client, overrides)
553 .await
554 .map_err(DeployError::from)
555}
556
557pub async fn deploy_with_proxy(
559 deployer: &Signer,
560 eth_client: &EthClient,
561 contract_path: &Path,
562 salt: &[u8],
563) -> Result<ProxyDeployment, DeployError> {
564 let (implementation_tx_hash, implementation_address) =
565 create2_deploy_from_path(&[], contract_path, deployer, salt, eth_client).await?;
566
567 let (proxy_tx_hash, proxy_address) =
568 deploy_proxy(deployer, eth_client, implementation_address, salt).await?;
569
570 Ok(ProxyDeployment {
571 proxy_address,
572 proxy_tx_hash,
573 implementation_address,
574 implementation_tx_hash,
575 })
576}
577
578pub async fn deploy_with_proxy_no_wait(
580 deployer: &Signer,
581 eth_client: &EthClient,
582 contract_path: &Path,
583 salt: &[u8],
584 overrides: Overrides,
585) -> Result<ProxyDeployment, DeployError> {
586 let bytecode_hex = read_to_string(contract_path)?;
587 let bytecode = hex::decode(bytecode_hex.trim_start_matches("0x").trim())?;
588 let (implementation_tx_hash, implementation_address) = create2_deploy_from_bytecode_no_wait(
589 &[],
590 &bytecode,
591 deployer,
592 salt,
593 eth_client,
594 overrides.clone(),
595 )
596 .await?;
597
598 let (proxy_tx_hash, proxy_address) = deploy_proxy_no_wait(
599 deployer,
600 eth_client,
601 implementation_address,
602 salt,
603 Overrides {
604 nonce: overrides.nonce.map(|nonce| nonce + 1),
605 ..overrides
606 },
607 )
608 .await?;
609
610 Ok(ProxyDeployment {
611 proxy_address,
612 proxy_tx_hash,
613 implementation_address,
614 implementation_tx_hash,
615 })
616}
617
618pub async fn deploy_with_proxy_from_bytecode(
620 deployer: &Signer,
621 eth_client: &EthClient,
622 bytecode: &[u8],
623 salt: &[u8],
624) -> Result<ProxyDeployment, DeployError> {
625 let (implementation_tx_hash, implementation_address) =
626 create2_deploy_from_bytecode(&[], bytecode, deployer, salt, eth_client).await?;
627
628 let (proxy_tx_hash, proxy_address) =
629 deploy_proxy(deployer, eth_client, implementation_address, salt).await?;
630
631 Ok(ProxyDeployment {
632 proxy_address,
633 proxy_tx_hash,
634 implementation_address,
635 implementation_tx_hash,
636 })
637}
638
639pub async fn deploy_with_proxy_from_bytecode_no_wait(
641 deployer: &Signer,
642 eth_client: &EthClient,
643 bytecode: &[u8],
644 salt: &[u8],
645 overrides: Overrides,
646) -> Result<ProxyDeployment, DeployError> {
647 let (implementation_tx_hash, implementation_address) = create2_deploy_from_bytecode_no_wait(
648 &[],
649 bytecode,
650 deployer,
651 salt,
652 eth_client,
653 overrides.clone(),
654 )
655 .await?;
656
657 let (proxy_tx_hash, proxy_address) = deploy_proxy_no_wait(
658 deployer,
659 eth_client,
660 implementation_address,
661 salt,
662 Overrides {
663 nonce: overrides.nonce.map(|nonce| nonce + 1),
664 ..overrides
665 },
666 )
667 .await?;
668
669 Ok(ProxyDeployment {
670 proxy_address,
671 proxy_tx_hash,
672 implementation_address,
673 implementation_tx_hash,
674 })
675}
676
677async fn build_create2_deploy_tx(
678 salt: &[u8],
679 init_code: &[u8],
680 deployer: &Signer,
681 eth_client: &EthClient,
682 overrides: Overrides,
683) -> Result<GenericTransaction, EthClientError> {
684 let calldata = [salt, init_code].concat();
685 let gas_price = eth_client
686 .get_gas_price_with_extra(20)
687 .await?
688 .try_into()
689 .map_err(|_| {
690 EthClientError::InternalError("Failed to convert gas_price to a u64".to_owned())
691 })?;
692
693 build_generic_tx(
694 eth_client,
695 TxType::EIP1559,
696 DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS,
697 deployer.address(),
698 calldata.into(),
699 Overrides {
700 max_fee_per_gas: Some(gas_price),
701 max_priority_fee_per_gas: Some(gas_price),
702 ..overrides
703 },
704 )
705 .await
706}
707
708async fn create2_deploy_no_wait(
709 salt: &[u8],
710 init_code: &[u8],
711 deployer: &Signer,
712 eth_client: &EthClient,
713 overrides: Overrides,
714) -> Result<(H256, Address), EthClientError> {
715 let deploy_tx =
716 build_create2_deploy_tx(salt, init_code, deployer, eth_client, overrides).await?;
717
718 let deploy_tx_hash = send_generic_transaction(eth_client, deploy_tx, deployer).await?;
719
720 let deployed_address = create2_address(salt, keccak(init_code));
721 Ok((deploy_tx_hash, deployed_address))
722}
723
724#[allow(clippy::indexing_slicing)]
725fn create2_address(salt: &[u8], init_code_hash: H256) -> Address {
726 Address::from_slice(
727 &keccak(
728 [
729 &[0xff],
730 DETERMINISTIC_DEPLOYMENT_PROXY_ADDRESS.as_bytes(),
731 salt,
732 init_code_hash.as_bytes(),
733 ]
734 .concat(),
735 )
736 .as_bytes()[12..],
737 )
738}
739
740pub async fn initialize_contract_no_wait(
741 contract_address: Address,
742 initialize_calldata: Vec<u8>,
743 initializer: &Signer,
744 eth_client: &EthClient,
745 overrides: Overrides,
746) -> Result<H256, EthClientError> {
747 let initialize_tx = build_generic_tx(
748 eth_client,
749 TxType::EIP1559,
750 contract_address,
751 initializer.address(),
752 initialize_calldata.into(),
753 overrides,
754 )
755 .await?;
756
757 let initialize_tx_hash =
758 send_generic_transaction(eth_client, initialize_tx, initializer).await?;
759
760 Ok(initialize_tx_hash)
761}
762
763pub async fn call_contract(
764 client: &EthClient,
765 signer: &Signer,
766 to: Address,
767 signature: &str,
768 parameters: Vec<Value>,
769) -> Result<H256, EthClientError> {
770 let calldata = encode_calldata(signature, ¶meters)?.into();
771 let from = signer.address();
772 let tx = build_generic_tx(
773 client,
774 TxType::EIP1559,
775 to,
776 from,
777 calldata,
778 Default::default(),
779 )
780 .await?;
781
782 let tx_hash = send_generic_transaction(client, tx, signer).await?;
783
784 wait_for_transaction_receipt(tx_hash, client, 100).await?;
785 Ok(tx_hash)
786}
787
788pub fn address_to_word(address: Address) -> U256 {
789 let mut word = [0u8; 32];
790 for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) {
791 *word_byte = *address_byte;
792 }
793 U256::from_big_endian(&word)
794}
795
796pub fn get_erc1967_slot(name: &str) -> U256 {
797 U256::from_big_endian(&keccak(name).0) - U256::one()
798}
799
800pub fn get_address_alias(address: Address) -> Address {
801 let address = U256::from_big_endian(&address.to_fixed_bytes());
802 let alias = address.add(U256::from_big_endian(&ADDRESS_ALIASING.to_fixed_bytes()));
803 H160::from_slice(&alias.to_big_endian()[12..32])
804}
805
806const WAIT_TIME_FOR_RECEIPT_SECONDS: u64 = 2;
807
808pub async fn send_generic_transaction(
809 client: &EthClient,
810 generic_tx: GenericTransaction,
811 signer: &Signer,
812) -> Result<H256, EthClientError> {
813 let mut encoded_tx = vec![generic_tx.r#type.into()];
814 match generic_tx.r#type {
815 TxType::EIP1559 => {
816 let tx: EIP1559Transaction = generic_tx.try_into()?;
817 let signed_tx = tx
818 .sign(signer)
819 .await
820 .map_err(|err| EthClientError::Custom(err.to_string()))?;
821
822 signed_tx.encode(&mut encoded_tx);
823 }
824 TxType::EIP4844 => {
825 let mut tx: WrappedEIP4844Transaction = generic_tx.try_into()?;
826 tx.tx
827 .sign_inplace(signer)
828 .await
829 .map_err(|err| EthClientError::Custom(err.to_string()))?;
830
831 tx.encode(&mut encoded_tx);
832 }
833 TxType::EIP7702 => {
834 let tx: EIP7702Transaction = generic_tx.try_into()?;
835 let signed_tx = tx
836 .sign(signer)
837 .await
838 .map_err(|err| EthClientError::Custom(err.to_string()))?;
839 signed_tx.encode(&mut encoded_tx);
840 }
841 TxType::FeeToken => {
842 let tx: FeeTokenTransaction = generic_tx.try_into()?;
843 let signed_tx = tx
844 .sign(signer)
845 .await
846 .map_err(|err| EthClientError::Custom(err.to_string()))?;
847
848 signed_tx.encode(&mut encoded_tx);
849 }
850 _ => {
851 return Err(EthClientError::Custom(format!(
852 "Unsupported transaction type: {:?}",
853 generic_tx.r#type
854 )));
855 }
856 };
857
858 client.send_raw_transaction(encoded_tx.as_slice()).await
859}
860
861pub async fn send_tx_bump_gas_exponential_backoff(
862 client: &EthClient,
863 mut tx: GenericTransaction,
864 signer: &Signer,
865) -> Result<H256, EthClientError> {
866 let mut number_of_retries = 0;
867
868 'outer: while number_of_retries < client.max_number_of_retries {
869 if let Some(max_fee_per_gas) = client.maximum_allowed_max_fee_per_gas {
870 let (Some(tx_max_fee), Some(tx_max_priority_fee)) =
871 (&mut tx.max_fee_per_gas, &mut tx.max_priority_fee_per_gas)
872 else {
873 return Err(EthClientError::Custom(
874 "Invalid transaction: max_fee_per_gas or max_priority_fee_per_gas is missing"
875 .to_string(),
876 ));
877 };
878
879 if *tx_max_fee > max_fee_per_gas {
880 *tx_max_fee = max_fee_per_gas;
881
882 if *tx_max_priority_fee > *tx_max_fee {
884 *tx_max_priority_fee = *tx_max_fee;
885 }
886
887 warn!(
888 "max_fee_per_gas exceeds the allowed limit, adjusting it to {max_fee_per_gas}"
889 );
890 }
891 }
892
893 if let Some(tx_max_fee_per_blob_gas) = &mut tx.max_fee_per_blob_gas
895 && let Some(max_fee_per_blob_gas) = client.maximum_allowed_max_fee_per_blob_gas
896 && *tx_max_fee_per_blob_gas > U256::from(max_fee_per_blob_gas)
897 {
898 *tx_max_fee_per_blob_gas = U256::from(max_fee_per_blob_gas);
899 warn!(
900 "max_fee_per_blob_gas exceeds the allowed limit, adjusting it to {max_fee_per_blob_gas}"
901 );
902 }
903 let Ok(tx_hash) = send_generic_transaction(client, tx.clone(), signer)
904 .await
905 .inspect_err(|e| {
906 error!(
907 "Error sending generic transaction {e} attempts [{number_of_retries}/{}]",
908 client.max_number_of_retries
909 );
910 })
911 else {
912 bump_gas_generic_tx(&mut tx, 30);
913 number_of_retries += 1;
914 continue;
915 };
916
917 if number_of_retries > 0 {
918 warn!(
919 "Resending Transaction after bumping gas, attempts [{number_of_retries}/{}]\nTxHash: {tx_hash:#x}",
920 client.max_number_of_retries
921 );
922 }
923
924 let mut receipt = client.get_transaction_receipt(tx_hash).await?;
925
926 let mut attempt = 1;
927
928 #[allow(clippy::as_conversions)]
929 let attempts_to_wait_in_seconds = client
930 .backoff_factor
931 .pow(number_of_retries as u32)
932 .clamp(client.min_retry_delay, client.max_retry_delay);
933 while receipt.is_none() {
934 if attempt >= (attempts_to_wait_in_seconds / WAIT_TIME_FOR_RECEIPT_SECONDS) {
935 bump_gas_generic_tx(&mut tx, 30);
938
939 number_of_retries += 1;
940 continue 'outer;
941 }
942
943 attempt += 1;
944
945 tokio::time::sleep(std::time::Duration::from_secs(
946 WAIT_TIME_FOR_RECEIPT_SECONDS,
947 ))
948 .await;
949
950 receipt = client.get_transaction_receipt(tx_hash).await?;
951 }
952
953 return Ok(tx_hash);
954 }
955
956 Err(EthClientError::TimeoutError)
957}
958
959fn bump_gas_generic_tx(tx: &mut GenericTransaction, bump_percentage: u64) {
960 if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) =
961 (&mut tx.max_fee_per_gas, &mut tx.max_priority_fee_per_gas)
962 {
963 *max_fee_per_gas = (*max_fee_per_gas * (100 + bump_percentage)) / 100;
964 *max_priority_fee_per_gas = (*max_priority_fee_per_gas * (100 + bump_percentage)) / 100;
965 }
966 if let Some(max_fee_per_blob_gas) = &mut tx.max_fee_per_blob_gas {
967 let factor = 1 + (bump_percentage / 100) * 10;
968 *max_fee_per_blob_gas = max_fee_per_blob_gas
969 .saturating_mul(U256::from(factor))
970 .div(10);
971 }
972}
973
974pub async fn build_generic_tx(
980 client: &EthClient,
981 r#type: TxType,
982 to: Address,
983 from: Address,
984 calldata: Bytes,
985 overrides: Overrides,
986) -> Result<GenericTransaction, EthClientError> {
987 match r#type {
988 TxType::EIP1559
989 | TxType::EIP4844
990 | TxType::EIP7702
991 | TxType::Privileged
992 | TxType::FeeToken => {}
993 TxType::EIP2930 | TxType::Legacy => {
994 return Err(EthClientError::Custom(
995 "Unsupported tx type in build_generic_tx".to_owned(),
996 ));
997 }
998 }
999 if overrides.fee_token.is_none() && r#type == TxType::FeeToken {
1000 return Err(EthClientError::Custom(
1001 "fee_token must be set for FeeToken tx type".to_owned(),
1002 ));
1003 };
1004 let mut tx = GenericTransaction {
1005 r#type,
1006 to: overrides.to.clone().unwrap_or(TxKind::Call(to)),
1007 chain_id: Some(if let Some(chain_id) = overrides.chain_id {
1008 chain_id
1009 } else {
1010 client.get_chain_id().await?.try_into().map_err(|_| {
1011 EthClientError::Custom("Failed at get_chain_id().try_into()".to_owned())
1012 })?
1013 }),
1014 nonce: Some(get_nonce_from_overrides_or_rpc(client, &overrides, from).await?),
1015 max_fee_per_gas: Some(
1016 get_fee_from_override_or_get_gas_price(client, overrides.max_fee_per_gas).await?,
1017 ),
1018 max_priority_fee_per_gas: Some(
1019 priority_fee_from_override_or_rpc(client, overrides.max_priority_fee_per_gas).await?,
1020 ),
1021 max_fee_per_blob_gas: overrides.gas_price_per_blob,
1022 value: overrides.value.unwrap_or_default(),
1023 input: calldata,
1024 access_list: overrides
1025 .access_list
1026 .iter()
1027 .map(AccessListEntry::from)
1028 .collect(),
1029 fee_token: overrides.fee_token,
1030 from,
1031 wrapper_version: overrides.wrapper_version,
1032 authorization_list: overrides.authorization_list,
1033 ..Default::default()
1034 };
1035 tx.gas_price = U256::from(tx.max_fee_per_gas.unwrap_or_default());
1036 if let Some(blobs_bundle) = &overrides.blobs_bundle {
1037 tx.blob_versioned_hashes = blobs_bundle.generate_versioned_hashes();
1038 add_blobs_to_generic_tx(&mut tx, blobs_bundle);
1039 }
1040 tx.gas = Some(match overrides.gas_limit {
1041 Some(gas) => gas,
1042 None => client.estimate_gas(tx.clone()).await?,
1043 });
1044
1045 Ok(tx)
1046}
1047
1048pub fn add_blobs_to_generic_tx(tx: &mut GenericTransaction, bundle: &BlobsBundle) {
1049 tx.blobs = bundle
1050 .blobs
1051 .iter()
1052 .map(|blob| Bytes::copy_from_slice(blob))
1053 .collect()
1054}
1055
1056async fn get_nonce_from_overrides_or_rpc(
1057 client: &EthClient,
1058 overrides: &Overrides,
1059 address: Address,
1060) -> Result<u64, EthClientError> {
1061 if let Some(nonce) = overrides.nonce {
1062 return Ok(nonce);
1063 }
1064 client
1065 .get_nonce(address, BlockIdentifier::Tag(BlockTag::Latest))
1066 .await
1067}
1068
1069async fn get_fee_from_override_or_get_gas_price(
1070 client: &EthClient,
1071 maybe_gas_fee: Option<u64>,
1072) -> Result<u64, EthClientError> {
1073 if let Some(gas_fee) = maybe_gas_fee {
1074 return Ok(gas_fee);
1075 }
1076 client
1077 .get_gas_price()
1078 .await?
1079 .try_into()
1080 .map_err(|_| EthClientError::Custom("Failed to get gas for fee".to_owned()))
1081}
1082
1083async fn priority_fee_from_override_or_rpc(
1084 client: &EthClient,
1085 maybe_priority_fee: Option<u64>,
1086) -> Result<u64, EthClientError> {
1087 if let Some(priority_fee) = maybe_priority_fee {
1088 return Ok(priority_fee);
1089 }
1090
1091 if let Ok(priority_fee) = client.get_max_priority_fee().await
1092 && let Ok(priority_fee_u64) = priority_fee.try_into()
1093 {
1094 return Ok(priority_fee_u64);
1095 }
1096
1097 get_fee_from_override_or_get_gas_price(client, None).await
1098}
1099
1100pub async fn wait_for_l1_message_proof(
1101 client: &EthClient,
1102 transaction_hash: H256,
1103 max_retries: u64,
1104) -> Result<Vec<L1MessageProof>, EthClientError> {
1105 let mut message_proof = get_l1_message_proof(client, transaction_hash).await?;
1106 let mut r#try = 1;
1107 while message_proof.is_none() {
1108 println!(
1109 "[{try}/{max_retries}] Retrying to get message proof for tx {transaction_hash:#x}"
1110 );
1111
1112 if max_retries == r#try {
1113 return Err(EthClientError::Custom(format!(
1114 "L1Message proof for tx {transaction_hash:#x} not found after {max_retries} retries"
1115 )));
1116 }
1117 r#try += 1;
1118
1119 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
1120
1121 message_proof = get_l1_message_proof(client, transaction_hash).await?;
1122 }
1123 message_proof.ok_or(EthClientError::Custom("L1Message proof is None".to_owned()))
1124}
1125
1126pub async fn get_last_committed_batch(
1127 client: &EthClient,
1128 on_chain_proposer_address: Address,
1129) -> Result<u64, EthClientError> {
1130 _call_u64_variable(client, b"lastCommittedBatch()", on_chain_proposer_address).await
1131}
1132
1133pub async fn get_last_verified_batch(
1134 client: &EthClient,
1135 on_chain_proposer_address: Address,
1136) -> Result<u64, EthClientError> {
1137 _call_u64_variable(client, b"lastVerifiedBatch()", on_chain_proposer_address).await
1138}
1139
1140pub async fn get_sp1_vk_for_batch(
1143 client: &EthClient,
1144 on_chain_proposer_address: Address,
1145 batch_number: u64,
1146) -> Result<[u8; 32], EthClientError> {
1147 get_verification_key_for_batch(
1148 client,
1149 on_chain_proposer_address,
1150 batch_number,
1151 SP1_VERIFIER_ID,
1152 )
1153 .await
1154}
1155
1156pub async fn get_risc0_vk_for_batch(
1159 client: &EthClient,
1160 on_chain_proposer_address: Address,
1161 batch_number: u64,
1162) -> Result<[u8; 32], EthClientError> {
1163 get_verification_key_for_batch(
1164 client,
1165 on_chain_proposer_address,
1166 batch_number,
1167 RISC0_VERIFIER_ID,
1168 )
1169 .await
1170}
1171
1172async fn get_verification_key_for_batch(
1175 client: &EthClient,
1176 on_chain_proposer_address: Address,
1177 batch_number: u64,
1178 verifier_id: u8,
1179) -> Result<[u8; 32], EthClientError> {
1180 let commit_hash =
1181 get_batch_commit_hash(client, on_chain_proposer_address, batch_number).await?;
1182 get_verification_key(client, on_chain_proposer_address, commit_hash, verifier_id).await
1183}
1184
1185pub async fn get_batch_commit_hash(
1187 client: &EthClient,
1188 on_chain_proposer_address: Address,
1189 batch_number: u64,
1190) -> Result<[u8; 32], EthClientError> {
1191 let values = vec![Value::Uint(U256::from(batch_number))];
1196 let calldata = encode_calldata("batchCommitments(uint256)", &values)?;
1197
1198 let response = client
1199 .call(
1200 on_chain_proposer_address,
1201 calldata.into(),
1202 Default::default(),
1203 )
1204 .await?;
1205
1206 let hex = response.strip_prefix("0x").ok_or(EthClientError::Custom(
1209 "Couldn't strip '0x' prefix from response".to_owned(),
1210 ))?;
1211
1212 let commit_hash_hex = hex.get(384..448).ok_or(EthClientError::Custom(
1214 "Response too short to contain commitHash".to_owned(),
1215 ))?;
1216
1217 let bytes = hex::decode(commit_hash_hex)
1218 .map_err(|e| EthClientError::Custom(format!("Failed to decode commit hash hex: {e}")))?;
1219
1220 let arr: [u8; 32] = bytes.try_into().map_err(|_| {
1221 EthClientError::Custom("Failed to convert commit hash bytes to [u8; 32]".to_owned())
1222 })?;
1223
1224 Ok(arr)
1225}
1226
1227pub async fn get_verification_key(
1229 client: &EthClient,
1230 on_chain_proposer_address: Address,
1231 commit_hash: [u8; 32],
1232 verifier_id: u8,
1233) -> Result<[u8; 32], EthClientError> {
1234 let values = vec![
1235 Value::FixedBytes(commit_hash.to_vec().into()),
1236 Value::Uint(U256::from(verifier_id)),
1237 ];
1238 let calldata = encode_calldata("verificationKeys(bytes32,uint8)", &values)?;
1239
1240 let response = client
1241 .call(
1242 on_chain_proposer_address,
1243 calldata.into(),
1244 Default::default(),
1245 )
1246 .await?;
1247
1248 let hex = response.strip_prefix("0x").ok_or(EthClientError::Custom(
1249 "Couldn't strip '0x' prefix from response".to_owned(),
1250 ))?;
1251
1252 let bytes = hex::decode(hex)
1253 .map_err(|e| EthClientError::Custom(format!("Failed to decode VK hex: {e}")))?;
1254
1255 let arr: [u8; 32] = bytes
1256 .try_into()
1257 .map_err(|_| EthClientError::Custom("Failed to convert VK bytes to [u8; 32]".to_owned()))?;
1258
1259 Ok(arr)
1260}
1261
1262pub async fn get_last_fetched_l1_block(
1263 client: &EthClient,
1264 common_bridge_address: Address,
1265) -> Result<u64, EthClientError> {
1266 _call_u64_variable(client, b"lastFetchedL1Block()", common_bridge_address).await
1267}
1268
1269pub async fn get_l2_gas_limit(
1270 client: &EthClient,
1271 common_bridge_address: Address,
1272) -> Result<u64, EthClientError> {
1273 _call_u64_variable(client, b"l2GasLimit()", common_bridge_address).await
1274}
1275
1276pub async fn get_pending_l1_messages(
1277 client: &EthClient,
1278 common_bridge_address: Address,
1279) -> Result<Vec<H256>, EthClientError> {
1280 let response = _generic_call(
1281 client,
1282 b"getPendingTransactionHashes()",
1283 common_bridge_address,
1284 )
1285 .await?;
1286 from_hex_string_to_h256_array(&response)
1287}
1288
1289pub async fn get_pending_l2_messages(
1290 client: &EthClient,
1291 common_bridge_address: Address,
1292 chain_id: u64,
1293) -> Result<Vec<H256>, EthClientError> {
1294 let selector = keccak(b"getPendingL2MessagesHashes(uint256)")
1295 .as_bytes()
1296 .get(..4)
1297 .ok_or(EthClientError::Custom("Failed to get selector.".to_owned()))?
1298 .to_vec();
1299
1300 let mut calldata = Vec::new();
1301 calldata.extend_from_slice(&selector);
1302 calldata.extend_from_slice(&U256::from(chain_id).to_big_endian());
1303
1304 let response = client
1305 .call(common_bridge_address, calldata.into(), Overrides::default())
1306 .await?;
1307
1308 from_hex_string_to_h256_array(&response)
1309}
1310
1311pub async fn get_l1_active_fork(
1313 client: &EthClient,
1314 activation_time: Option<u64>,
1315) -> Result<Fork, EthClientError> {
1316 let Some(osaka_activation_time) = activation_time else {
1317 return Ok(Fork::Osaka);
1318 };
1319 let current_timestamp = client
1320 .get_block_by_number(BlockIdentifier::Tag(BlockTag::Latest), false)
1321 .await?
1322 .header
1323 .timestamp;
1324 if current_timestamp < osaka_activation_time {
1325 Ok(Fork::Prague)
1326 } else {
1327 Ok(Fork::Osaka)
1328 }
1329}
1330
1331async fn _generic_call(
1332 client: &EthClient,
1333 selector: &[u8],
1334 contract_address: Address,
1335) -> Result<String, EthClientError> {
1336 let selector = keccak(selector)
1337 .as_bytes()
1338 .get(..4)
1339 .ok_or(EthClientError::Custom("Failed to get selector.".to_owned()))?
1340 .to_vec();
1341 let hex_string = client
1342 .call(contract_address, selector.into(), Overrides::default())
1343 .await?;
1344
1345 Ok(hex_string)
1346}
1347
1348async fn _call_u64_variable(
1349 client: &EthClient,
1350 selector: &[u8],
1351 contract_address: Address,
1352) -> Result<u64, EthClientError> {
1353 let hex_string = _generic_call(client, selector, contract_address).await?;
1354
1355 let value = U256::from_str_radix(hex_string.trim_start_matches("0x"), 16)?
1356 .try_into()
1357 .map_err(|_| {
1358 EthClientError::Custom("Failed to convert from_hex_string_to_u256()".to_owned())
1359 })?;
1360
1361 Ok(value)
1362}
1363
1364async fn _call_address_variable(
1365 eth_client: &EthClient,
1366 selector: &[u8],
1367 on_chain_proposer_address: Address,
1368) -> Result<Address, EthClientError> {
1369 let hex_string = _generic_call(eth_client, selector, on_chain_proposer_address).await?;
1370
1371 let hex_str = &hex_string.strip_prefix("0x").ok_or(EthClientError::Custom(
1372 "Couldn't strip prefix from request.".to_owned(),
1373 ))?[24..]; let value = Address::from_str(hex_str)
1376 .map_err(|_| EthClientError::Custom("Failed to convert from_str()".to_owned()))?;
1377 Ok(value)
1378}
1379
1380async fn _call_bytes32_variable(
1381 client: &EthClient,
1382 selector: &[u8],
1383 contract_address: Address,
1384) -> Result<[u8; 32], EthClientError> {
1385 let hex_string = _generic_call(client, selector, contract_address).await?;
1386
1387 let hex = hex_string.strip_prefix("0x").ok_or(EthClientError::Custom(
1388 "Couldn't strip '0x' prefix from hex string".to_owned(),
1389 ))?;
1390
1391 let bytes = hex::decode(hex)
1392 .map_err(|e| EthClientError::Custom(format!("Failed to decode hex string: {e}")))?;
1393
1394 let arr: [u8; 32] = bytes
1395 .try_into()
1396 .map_err(|_| EthClientError::Custom("Failed to convert bytes to [u8; 32]".to_owned()))?;
1397
1398 Ok(arr)
1399}
1400
1401pub async fn get_fee_token_ratio(
1402 fee_token: &Address,
1403 client: &EthClient,
1404) -> Result<u64, EthClientError> {
1405 let values = vec![Value::Address(*fee_token)];
1406 let calldata = encode_calldata("getFeeTokenRatio(address)", &values)?;
1407
1408 let ratio = client
1409 .call(
1410 FEE_TOKEN_PRICER_ADDRESS,
1411 calldata.into(),
1412 Default::default(),
1413 )
1414 .await?;
1415
1416 let ratio: u64 = ratio
1417 .trim_start_matches("0x")
1418 .parse::<u64>()
1419 .map_err(|e| EthClientError::Custom(format!("Failed to parse ratio to u64: {}", e)))?;
1420
1421 Ok(ratio)
1422}
1423pub async fn register_fee_token_no_wait(
1424 client: &EthClient,
1425 bridge_address: Address,
1426 fee_token: Address,
1427 signer: &Signer,
1428 overrides: Overrides,
1429) -> Result<H256, EthClientError> {
1430 let calldata = encode_calldata(REGISTER_FEE_TOKEN_SIGNATURE, &[Value::Address(fee_token)])?;
1431
1432 let tx_register = build_generic_tx(
1433 client,
1434 TxType::EIP1559,
1435 bridge_address,
1436 signer.address(),
1437 calldata.into(),
1438 overrides,
1439 )
1440 .await?;
1441
1442 send_generic_transaction(client, tx_register, signer).await
1443}
1444
1445pub async fn wait_for_l2_deposit_receipt(
1446 l1_rpc_receipt: &RpcReceipt,
1447 l1_client: &EthClient,
1448 l2_client: &EthClient,
1449) -> Result<RpcReceipt, EthClientError> {
1450 let data = l1_rpc_receipt
1451 .logs
1452 .iter()
1453 .find_map(|log| PrivilegedTransactionData::from_log(log.log.clone()).ok())
1454 .ok_or_else(|| {
1455 EthClientError::Custom(format!(
1456 "RpcReceipt for transaction {:?} contains no valid logs",
1457 l1_rpc_receipt.tx_info.transaction_hash
1458 ))
1459 })?;
1460
1461 let l2_deposit_tx_hash = data
1462 .into_tx(
1463 l1_client,
1464 l2_client
1465 .get_chain_id()
1466 .await?
1467 .try_into()
1468 .map_err(|e| EthClientError::Custom(format!("Invalid chain id: {e}")))?,
1469 0,
1470 )
1471 .await?
1472 .get_privileged_hash()
1473 .ok_or_else(|| EthClientError::Custom("Empty transaction hash".to_owned()))?;
1474
1475 wait_for_transaction_receipt(l2_deposit_tx_hash, l2_client, 10000).await
1476}