alloy_zksync/network/transaction_request/
mod.rs

1use alloy::network::{
2    Network, TransactionBuilder, TransactionBuilderError, UnbuiltTransactionError,
3};
4use alloy::primitives::{B256, Bytes, TxKind, U256};
5
6use crate::contracts::l2::contract_deployer::CONTRACT_DEPLOYER_ADDRESS;
7use crate::network::{tx_type::TxType, unsigned_tx::eip712::TxEip712};
8
9use super::unsigned_tx::eip712::{BytecodeHashError, PaymasterParams, hash_bytecode};
10use super::{Zksync, unsigned_tx::eip712::Eip712Meta};
11
12/// Transaction request supporting ZKsync's EIP-712 transaction types.
13///
14/// Unlike `TransactionRequest` for Ethereum network, it would try to use ZKsync-native
15/// EIP712 transaction type by default, unless explicitly overridden. The reason for that
16/// is that EIP712 transactions have the same capabilities as type 0 and EIP1559
17/// transactions, while being cheaper to process.
18#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct TransactionRequest {
21    #[serde(flatten)]
22    base: alloy::rpc::types::transaction::TransactionRequest,
23    #[serde(skip_serializing_if = "Option::is_none")]
24    eip_712_meta: Option<Eip712Meta>,
25}
26
27impl Default for TransactionRequest {
28    fn default() -> Self {
29        Self {
30            base: alloy::rpc::types::transaction::TransactionRequest {
31                transaction_type: Some(TxType::Eip712 as u8),
32                ..Default::default()
33            },
34            eip_712_meta: Default::default(),
35        }
36    }
37}
38
39impl TransactionRequest {
40    /// Get the gas per pubdata for the transaction.
41    pub fn gas_per_pubdata(&self) -> Option<U256> {
42        self.eip_712_meta.as_ref().map(|meta| meta.gas_per_pubdata)
43    }
44
45    /// Set the gas per pubdata  for the transaction.
46    pub fn set_gas_per_pubdata(&mut self, gas_per_pubdata: U256) {
47        self.eip_712_meta
48            .get_or_insert_with(Eip712Meta::default)
49            .gas_per_pubdata = gas_per_pubdata;
50    }
51
52    /// Builder-pattern method for setting gas per pubdata.
53    pub fn with_gas_per_pubdata(mut self, gas_per_pubdata: U256) -> Self {
54        self.set_gas_per_pubdata(gas_per_pubdata);
55        self
56    }
57
58    /// Get the factory deps for the transaction.
59    pub fn factory_deps(&self) -> Option<&Vec<Bytes>> {
60        self.eip_712_meta
61            .as_ref()
62            .map(|meta| meta.factory_deps.as_ref())
63    }
64
65    /// Set the factory deps  for the transaction.
66    pub fn set_factory_deps(&mut self, factory_deps: Vec<Bytes>) {
67        self.eip_712_meta
68            .get_or_insert_with(Eip712Meta::default)
69            .factory_deps = factory_deps;
70    }
71
72    /// Builder-pattern method for setting factory deps.
73    pub fn with_factory_deps(mut self, factory_deps: Vec<Bytes>) -> Self {
74        self.set_factory_deps(factory_deps);
75        self
76    }
77
78    /// Get the custom signature for the transaction.
79    pub fn custom_signature(&self) -> Option<&Bytes> {
80        self.eip_712_meta
81            .as_ref()
82            .and_then(|meta| meta.custom_signature.as_ref())
83    }
84
85    /// Set the custom signature  for the transaction.
86    pub fn set_custom_signature(&mut self, custom_signature: Bytes) {
87        self.eip_712_meta
88            .get_or_insert_with(Eip712Meta::default)
89            .custom_signature = Some(custom_signature);
90    }
91
92    /// Builder-pattern method for setting custom signature.
93    pub fn with_custom_signature(mut self, custom_signature: Bytes) -> Self {
94        self.set_custom_signature(custom_signature);
95        self
96    }
97
98    /// Get the paymaster params for the transaction.
99    pub fn paymaster_params(&self) -> Option<&PaymasterParams> {
100        self.eip_712_meta
101            .as_ref()
102            .and_then(|meta| meta.paymaster_params.as_ref())
103    }
104
105    /// Set the paymaster params for the transaction.
106    pub fn set_paymaster_params(&mut self, paymaster_params: PaymasterParams) {
107        self.eip_712_meta
108            .get_or_insert_with(Eip712Meta::default)
109            .paymaster_params = Some(paymaster_params);
110    }
111
112    /// Builder-pattern method for setting paymaster params.
113    pub fn with_paymaster_params(mut self, paymaster_params: PaymasterParams) -> Self {
114        self.set_paymaster_params(paymaster_params);
115        self
116    }
117
118    /// Builder-pattern method for building a ZKsync EIP-712 create2 transaction.
119    pub fn with_create2_params(
120        self,
121        salt: B256,
122        code: Vec<u8>,
123        constructor_data: Vec<u8>,
124        factory_deps: Vec<Vec<u8>>,
125    ) -> Result<Self, BytecodeHashError> {
126        let bytecode_hash = hash_bytecode(&code)?;
127        let factory_deps = factory_deps
128            .into_iter()
129            .chain(vec![code])
130            .map(Into::into)
131            .collect();
132        let input = crate::contracts::l2::contract_deployer::encode_create2_calldata(
133            salt,
134            bytecode_hash.into(),
135            constructor_data.into(),
136        );
137        Ok(self
138            .with_to(CONTRACT_DEPLOYER_ADDRESS)
139            .with_input(input)
140            .with_factory_deps(factory_deps))
141    }
142
143    /// Builder-pattern method for building a ZKsync EIP-712 create transaction.
144    pub fn with_create_params(
145        self,
146        code: Vec<u8>,
147        constructor_data: Vec<u8>,
148        factory_deps: Vec<Vec<u8>>,
149    ) -> Result<Self, BytecodeHashError> {
150        let bytecode_hash = hash_bytecode(&code)?;
151        let factory_deps = factory_deps
152            .into_iter()
153            .chain(vec![code])
154            .map(Into::into)
155            .collect();
156        let input = crate::contracts::l2::contract_deployer::encode_create_calldata(
157            bytecode_hash.into(),
158            constructor_data.into(),
159        );
160        Ok(self
161            .with_to(CONTRACT_DEPLOYER_ADDRESS)
162            .with_input(input)
163            .with_factory_deps(factory_deps))
164    }
165}
166
167impl TransactionRequest {
168    #[deprecated(note = "use `set_paymaster_params` instead")]
169    pub fn set_paymaster(&mut self, paymaster_params: PaymasterParams) {
170        self.eip_712_meta
171            .get_or_insert_with(Eip712Meta::default)
172            .paymaster_params = Some(paymaster_params);
173    }
174
175    #[deprecated(note = "use `with_paymaster_params` instead")]
176    pub fn with_paymaster(mut self, paymaster_params: PaymasterParams) -> Self {
177        #[allow(deprecated)]
178        self.set_paymaster(paymaster_params);
179        self
180    }
181
182    #[deprecated(note = "use `with_create_params` instead")]
183    pub fn zksync_deploy(
184        self,
185        code: Vec<u8>,
186        constructor_data: Vec<u8>,
187        factory_deps: Vec<Vec<u8>>,
188    ) -> Result<Self, BytecodeHashError> {
189        #[allow(deprecated)]
190        self.zksync_deploy_inner(None, code, constructor_data, factory_deps)
191    }
192
193    #[deprecated(note = "use `with_create2_params` instead")]
194    pub fn zksync_deploy_with_salt(
195        self,
196        salt: B256,
197        code: Vec<u8>,
198        constructor_data: Vec<u8>,
199        factory_deps: Vec<Vec<u8>>,
200    ) -> Result<Self, BytecodeHashError> {
201        #[allow(deprecated)]
202        self.zksync_deploy_inner(Some(salt), code, constructor_data, factory_deps)
203    }
204
205    #[deprecated(note = "use `with_create_params` or `with_create2_params` instead")]
206    fn zksync_deploy_inner(
207        self,
208        salt: Option<B256>,
209        code: Vec<u8>,
210        constructor_data: Vec<u8>,
211        factory_deps: Vec<Vec<u8>>,
212    ) -> Result<Self, BytecodeHashError> {
213        let bytecode_hash = hash_bytecode(&code)?;
214        let factory_deps = factory_deps
215            .into_iter()
216            .chain(vec![code])
217            .map(Into::into)
218            .collect();
219        let input = match salt {
220            Some(salt) => crate::contracts::l2::contract_deployer::encode_create2_calldata(
221                salt,
222                bytecode_hash.into(),
223                constructor_data.into(),
224            ),
225            None => crate::contracts::l2::contract_deployer::encode_create_calldata(
226                bytecode_hash.into(),
227                constructor_data.into(),
228            ),
229        };
230        Ok(self
231            .with_to(CONTRACT_DEPLOYER_ADDRESS)
232            .with_input(input)
233            .with_factory_deps(factory_deps))
234    }
235}
236
237impl From<crate::network::unsigned_tx::TypedTransaction> for TransactionRequest {
238    fn from(value: crate::network::unsigned_tx::TypedTransaction) -> Self {
239        match value {
240            crate::network::unsigned_tx::TypedTransaction::Native(inner) => Self {
241                base: inner.into(),
242                eip_712_meta: None,
243            },
244            crate::network::unsigned_tx::TypedTransaction::Eip712(inner) => Self {
245                base: inner.clone().into(),
246                eip_712_meta: inner.eip712_meta,
247            },
248        }
249    }
250}
251
252impl From<crate::network::tx_envelope::TxEnvelope> for TransactionRequest {
253    fn from(value: crate::network::tx_envelope::TxEnvelope) -> Self {
254        match value {
255            crate::network::tx_envelope::TxEnvelope::Native(inner) => Self {
256                base: inner.into(),
257                eip_712_meta: None,
258            },
259            crate::network::tx_envelope::TxEnvelope::Eip712(signed) => Self {
260                base: signed.tx().clone().into(),
261                eip_712_meta: signed.tx().clone().eip712_meta,
262            },
263        }
264    }
265}
266
267// TODO: transactionbuilder for other networks
268
269impl TransactionBuilder<Zksync> for TransactionRequest {
270    fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
271        TransactionBuilder::chain_id(&self.base)
272    }
273
274    fn set_chain_id(&mut self, chain_id: alloy::primitives::ChainId) {
275        TransactionBuilder::set_chain_id(&mut self.base, chain_id)
276    }
277
278    fn nonce(&self) -> Option<u64> {
279        TransactionBuilder::nonce(&self.base)
280    }
281
282    fn set_nonce(&mut self, nonce: u64) {
283        TransactionBuilder::set_nonce(&mut self.base, nonce)
284    }
285
286    fn take_nonce(&mut self) -> Option<u64> {
287        TransactionBuilder::take_nonce(&mut self.base)
288    }
289
290    fn input(&self) -> Option<&alloy::primitives::Bytes> {
291        TransactionBuilder::input(&self.base)
292    }
293
294    fn set_input<T: Into<alloy::primitives::Bytes>>(&mut self, input: T) {
295        TransactionBuilder::set_input(&mut self.base, input.into())
296    }
297
298    fn from(&self) -> Option<alloy::primitives::Address> {
299        TransactionBuilder::from(&self.base)
300    }
301
302    fn set_from(&mut self, from: alloy::primitives::Address) {
303        TransactionBuilder::set_from(&mut self.base, from)
304    }
305
306    fn kind(&self) -> Option<alloy::primitives::TxKind> {
307        TransactionBuilder::kind(&self.base)
308    }
309
310    fn clear_kind(&mut self) {
311        TransactionBuilder::clear_kind(&mut self.base)
312    }
313
314    fn set_kind(&mut self, kind: alloy::primitives::TxKind) {
315        TransactionBuilder::set_kind(&mut self.base, kind)
316    }
317
318    fn value(&self) -> Option<alloy::primitives::U256> {
319        TransactionBuilder::value(&self.base)
320    }
321
322    fn set_value(&mut self, value: alloy::primitives::U256) {
323        TransactionBuilder::set_value(&mut self.base, value)
324    }
325
326    fn gas_price(&self) -> Option<u128> {
327        TransactionBuilder::gas_price(&self.base)
328    }
329
330    fn set_gas_price(&mut self, gas_price: u128) {
331        TransactionBuilder::set_gas_price(&mut self.base, gas_price)
332    }
333
334    fn max_fee_per_gas(&self) -> Option<u128> {
335        TransactionBuilder::max_fee_per_gas(&self.base)
336    }
337
338    fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
339        TransactionBuilder::set_max_fee_per_gas(&mut self.base, max_fee_per_gas)
340    }
341
342    fn max_priority_fee_per_gas(&self) -> Option<u128> {
343        TransactionBuilder::max_priority_fee_per_gas(&self.base)
344    }
345
346    fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
347        TransactionBuilder::set_max_priority_fee_per_gas(&mut self.base, max_priority_fee_per_gas)
348    }
349
350    fn gas_limit(&self) -> Option<u64> {
351        TransactionBuilder::gas_limit(&self.base)
352    }
353
354    fn set_gas_limit(&mut self, gas_limit: u64) {
355        TransactionBuilder::set_gas_limit(&mut self.base, gas_limit)
356    }
357
358    fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
359        TransactionBuilder::access_list(&self.base)
360    }
361
362    fn set_access_list(&mut self, access_list: alloy::rpc::types::AccessList) {
363        TransactionBuilder::set_access_list(&mut self.base, access_list)
364    }
365
366    fn complete_type(&self, ty: <Zksync as Network>::TxType) -> Result<(), Vec<&'static str>> {
367        // TODO: cover era-specific types.
368        match ty {
369            TxType::Eip712 => {
370                // TODO: Should check gas per pubdata?
371                TransactionBuilder::complete_type(&self.base, alloy::consensus::TxType::Eip1559)
372            }
373            _ if ty.as_eth_type().is_some() => {
374                TransactionBuilder::complete_type(&self.base, ty.as_eth_type().unwrap())
375            }
376            _ => Err(vec!["Unsupported transaction type"]),
377        }
378    }
379
380    fn can_submit(&self) -> bool {
381        TransactionBuilder::can_submit(&self.base)
382    }
383
384    fn can_build(&self) -> bool {
385        if self.eip_712_meta.is_some() {
386            let common = self.base.gas.is_some() && self.base.nonce.is_some();
387            let eip1559 =
388                self.base.max_fee_per_gas.is_some() && self.base.max_priority_fee_per_gas.is_some();
389            // TODO: Should check gas per pubdata?
390            return common && eip1559;
391        }
392
393        TransactionBuilder::can_build(&self.base)
394    }
395
396    fn output_tx_type(&self) -> <Zksync as Network>::TxType {
397        if self.eip_712_meta.is_some() {
398            return TxType::Eip712;
399        }
400
401        TransactionBuilder::output_tx_type(&self.base).into()
402    }
403
404    fn output_tx_type_checked(&self) -> Option<<Zksync as Network>::TxType> {
405        if self.eip_712_meta.is_some() {
406            if !self.can_build() {
407                return None;
408            }
409            return Some(TxType::Eip712);
410        }
411
412        TransactionBuilder::output_tx_type_checked(&self.base).map(Into::into)
413    }
414
415    fn prep_for_submission(&mut self) {
416        // This has to go first, as it overwrites the transaction type.
417        TransactionBuilder::prep_for_submission(&mut self.base);
418
419        if self.eip_712_meta.is_some() {
420            self.base.transaction_type = Some(TxType::Eip712 as u8);
421            self.base.gas_price = None;
422            self.base.blob_versioned_hashes = None;
423            self.base.sidecar = None;
424        }
425    }
426
427    fn build_unsigned(
428        self,
429    ) -> alloy::network::BuildResult<crate::network::unsigned_tx::TypedTransaction, Zksync> {
430        if self.eip_712_meta.is_some() {
431            let mut missing = Vec::new();
432            // TODO: Copy-paste, can be avoided?
433            if self.base.max_fee_per_gas.is_none() {
434                missing.push("max_fee_per_gas");
435            }
436            if self.base.max_priority_fee_per_gas.is_none() {
437                missing.push("max_priority_fee_per_gas");
438            }
439
440            if !missing.is_empty() {
441                return Err(TransactionBuilderError::InvalidTransactionRequest(
442                    TxType::Eip712,
443                    missing,
444                )
445                .into_unbuilt(self));
446            }
447
448            let TxKind::Call(to) = self.base.to.unwrap_or_default() else {
449                return Err(TransactionBuilderError::InvalidTransactionRequest(
450                    TxType::Eip712,
451                    vec!["to (recipient) must be specified for EIP-712 transactions"],
452                )
453                .into_unbuilt(self));
454            };
455
456            // TODO: Are unwraps safe?
457            let tx = TxEip712 {
458                chain_id: self.base.chain_id.unwrap(),
459                nonce: U256::from(self.base.nonce.unwrap()), // TODO: Deployment nonce?
460                gas: self.base.gas.unwrap(),
461                max_fee_per_gas: self.base.max_fee_per_gas.unwrap(),
462                max_priority_fee_per_gas: self.base.max_priority_fee_per_gas.unwrap(),
463                eip712_meta: self.eip_712_meta,
464                from: self.base.from.unwrap(),
465                to,
466                value: self.base.value.unwrap_or_default(),
467                input: self.base.input.into_input().unwrap_or_default(),
468            };
469            return Ok(crate::network::unsigned_tx::TypedTransaction::Eip712(tx));
470        }
471
472        use TransactionBuilderError::*;
473        let inner = self.base;
474
475        let result = TransactionBuilder::build_unsigned(inner);
476        match result {
477            Ok(tx) => Ok(crate::network::unsigned_tx::TypedTransaction::Native(tx)),
478            Err(err) => {
479                let UnbuiltTransactionError { request, error } = err;
480                let wrapped_request = Self {
481                    base: request,
482                    eip_712_meta: None,
483                };
484                let error = match error {
485                    InvalidTransactionRequest(tx, fields) => {
486                        InvalidTransactionRequest(tx.into(), fields)
487                    }
488                    UnsupportedSignatureType => UnsupportedSignatureType,
489                    Signer(s) => Signer(s),
490                    Custom(c) => Custom(c),
491                };
492
493                Err(UnbuiltTransactionError {
494                    request: wrapped_request,
495                    error,
496                })
497            }
498        }
499    }
500
501    async fn build<W: alloy::network::NetworkWallet<Zksync>>(
502        self,
503        wallet: &W,
504    ) -> Result<<Zksync as Network>::TxEnvelope, TransactionBuilderError<Zksync>> {
505        Ok(wallet.sign_request(self).await?)
506    }
507}
508
509impl From<alloy::rpc::types::transaction::TransactionRequest> for TransactionRequest {
510    fn from(value: alloy::rpc::types::transaction::TransactionRequest) -> Self {
511        Self {
512            base: value,
513            ..Default::default()
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521    use alloy::consensus::Transaction as _;
522    use alloy::primitives::U256;
523    use alloy::rpc::types::transaction::TransactionRequest as AlloyTransactionRequest;
524
525    #[test]
526    fn test_default_transaction_request() {
527        let tx_request = TransactionRequest::default();
528        assert_eq!(tx_request.base.transaction_type, Some(TxType::Eip712 as u8));
529        assert!(tx_request.eip_712_meta.is_none());
530    }
531
532    #[test]
533    fn test_set_gas_per_pubdata() {
534        let mut tx_request = TransactionRequest::default();
535        let gas_per_pubdata = U256::from(1000);
536        tx_request.set_gas_per_pubdata(gas_per_pubdata);
537        assert_eq!(tx_request.gas_per_pubdata(), Some(gas_per_pubdata));
538    }
539
540    #[test]
541    fn test_with_gas_per_pubdata() {
542        let gas_per_pubdata = U256::from(1000);
543        let tx_request = TransactionRequest::default().with_gas_per_pubdata(gas_per_pubdata);
544        assert_eq!(tx_request.gas_per_pubdata(), Some(gas_per_pubdata));
545    }
546
547    #[test]
548    fn test_set_factory_deps() {
549        let mut tx_request = TransactionRequest::default();
550        let factory_deps = vec![Bytes::from(vec![1, 2, 3])];
551        tx_request.set_factory_deps(factory_deps.clone());
552        assert_eq!(tx_request.factory_deps(), Some(&factory_deps));
553    }
554
555    #[test]
556    fn test_with_factory_deps() {
557        let factory_deps = vec![Bytes::from(vec![1, 2, 3])];
558        let tx_request = TransactionRequest::default().with_factory_deps(factory_deps.clone());
559        assert_eq!(tx_request.factory_deps(), Some(&factory_deps));
560    }
561
562    #[test]
563    fn test_set_custom_signature() {
564        let mut tx_request = TransactionRequest::default();
565        let custom_signature = Bytes::from(vec![1, 2, 3]);
566        tx_request.set_custom_signature(custom_signature.clone());
567        assert_eq!(tx_request.custom_signature(), Some(&custom_signature));
568    }
569
570    #[test]
571    fn test_with_custom_signature() {
572        let custom_signature = Bytes::from(vec![1, 2, 3]);
573        let tx_request =
574            TransactionRequest::default().with_custom_signature(custom_signature.clone());
575        assert_eq!(tx_request.custom_signature(), Some(&custom_signature));
576    }
577
578    #[test]
579    fn test_set_paymaster_params() {
580        let mut tx_request = TransactionRequest::default();
581        let paymaster_params = PaymasterParams::default();
582        tx_request.set_paymaster_params(paymaster_params.clone());
583        assert_eq!(tx_request.paymaster_params(), Some(&paymaster_params));
584    }
585
586    #[test]
587    fn test_with_paymaster_params() {
588        let paymaster_params = PaymasterParams::default();
589        let tx_request =
590            TransactionRequest::default().with_paymaster_params(paymaster_params.clone());
591        assert_eq!(tx_request.paymaster_params(), Some(&paymaster_params));
592    }
593
594    #[test]
595    fn test_from_alloy_transaction_request() {
596        let alloy_tx_request = AlloyTransactionRequest::default();
597        let tx_request: TransactionRequest = alloy_tx_request.clone().into();
598        assert_eq!(tx_request.base, alloy_tx_request);
599    }
600
601    #[test]
602    fn test_prep_for_submission_with_eip712_meta() {
603        let mut tx_request = TransactionRequest::default();
604        tx_request.set_gas_per_pubdata(U256::from(1000));
605        tx_request.prep_for_submission();
606        assert_eq!(tx_request.base.transaction_type, Some(TxType::Eip712 as u8));
607        assert!(tx_request.base.gas_price.is_none());
608        assert!(tx_request.base.blob_versioned_hashes.is_none());
609        assert!(tx_request.base.sidecar.is_none());
610    }
611
612    #[test]
613    fn test_can_build_with_eip712_meta() {
614        let mut tx_request = TransactionRequest::default();
615        tx_request.set_gas_per_pubdata(U256::from(1000));
616        tx_request.base.gas = Some(21000);
617        tx_request.base.nonce = Some(0);
618        tx_request.base.max_fee_per_gas = Some(100);
619        tx_request.base.max_priority_fee_per_gas = Some(1);
620        assert!(tx_request.can_build());
621    }
622
623    #[test]
624    fn test_cannot_build_without_gas() {
625        let mut tx_request = TransactionRequest::default();
626        tx_request.set_gas_per_pubdata(U256::from(1000));
627        tx_request.base.nonce = Some(0);
628        tx_request.base.max_fee_per_gas = Some(100);
629        tx_request.base.max_priority_fee_per_gas = Some(1);
630        assert!(!tx_request.can_build());
631    }
632
633    #[test]
634    fn test_cannot_build_without_nonce() {
635        let mut tx_request = TransactionRequest::default();
636        tx_request.set_gas_per_pubdata(U256::from(1000));
637        tx_request.base.gas = Some(21000);
638        tx_request.base.max_fee_per_gas = Some(100);
639        tx_request.base.max_priority_fee_per_gas = Some(1);
640        assert!(!tx_request.can_build());
641    }
642
643    #[test]
644    fn test_cannot_build_without_max_fee_per_gas() {
645        let mut tx_request = TransactionRequest::default();
646        tx_request.set_gas_per_pubdata(U256::from(1000));
647        tx_request.base.gas = Some(21000);
648        tx_request.base.nonce = Some(0);
649        tx_request.base.max_priority_fee_per_gas = Some(1);
650        assert!(!tx_request.can_build());
651    }
652
653    #[test]
654    fn test_cannot_build_without_max_priority_fee_per_gas() {
655        let mut tx_request = TransactionRequest::default();
656        tx_request.set_gas_per_pubdata(U256::from(1000));
657        tx_request.base.gas = Some(21000);
658        tx_request.base.nonce = Some(0);
659        tx_request.base.max_fee_per_gas = Some(100);
660        assert!(!tx_request.can_build());
661    }
662
663    #[test]
664    fn test_output_tx_type_checked_with_eip712_meta() {
665        let mut tx_request = TransactionRequest::default();
666        tx_request.set_gas_per_pubdata(U256::from(1000));
667        tx_request.base.gas = Some(21000);
668        tx_request.base.nonce = Some(0);
669        tx_request.base.max_fee_per_gas = Some(100);
670        tx_request.base.max_priority_fee_per_gas = Some(1);
671        assert_eq!(tx_request.output_tx_type_checked(), Some(TxType::Eip712));
672    }
673
674    #[test]
675    fn test_output_tx_type_checked_without_eip712_meta() {
676        let mut tx_request = TransactionRequest::default();
677        tx_request.base.gas = Some(21000);
678        tx_request.base.nonce = Some(0);
679        tx_request.base.max_fee_per_gas = Some(100);
680        tx_request.base.max_priority_fee_per_gas = Some(1);
681        assert_eq!(tx_request.output_tx_type_checked(), None);
682    }
683
684    #[test]
685    fn test_output_tx_type_checked_cannot_build() {
686        let mut tx_request = TransactionRequest::default();
687        tx_request.set_gas_per_pubdata(U256::from(1000));
688        tx_request.base.gas = Some(21000);
689        tx_request.base.nonce = Some(0);
690        assert_eq!(tx_request.output_tx_type_checked(), None);
691    }
692
693    #[test]
694    fn test_build_unsigned_with_eip712_meta() {
695        let mut tx_request = TransactionRequest::default();
696        tx_request.set_gas_per_pubdata(U256::from(1000));
697        tx_request.base.gas = Some(21000);
698        tx_request.base.nonce = Some(0);
699        tx_request.base.max_fee_per_gas = Some(100);
700        tx_request.base.max_priority_fee_per_gas = Some(1);
701        tx_request.base.to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS));
702        tx_request.base.chain_id = Some(1);
703        tx_request.base.from = Some(CONTRACT_DEPLOYER_ADDRESS);
704        let result = tx_request.build_unsigned();
705        assert!(result.is_ok());
706        if let Ok(crate::network::unsigned_tx::TypedTransaction::Eip712(tx)) = result {
707            assert_eq!(tx.chain_id, 1);
708            assert_eq!(tx.nonce, U256::from(0));
709            assert_eq!(tx.gas, 21000);
710            assert_eq!(tx.max_fee_per_gas, 100);
711            assert_eq!(tx.max_priority_fee_per_gas, 1);
712            assert_eq!(tx.from, CONTRACT_DEPLOYER_ADDRESS);
713        } else {
714            panic!("Expected Eip712 transaction");
715        }
716    }
717
718    #[test]
719    fn test_build_unsigned_without_eip712_meta() {
720        let mut tx_request = TransactionRequest::default();
721        tx_request.base.gas = Some(21000);
722        tx_request.base.nonce = Some(0);
723        tx_request.base.max_fee_per_gas = Some(100);
724        tx_request.base.max_priority_fee_per_gas = Some(1);
725        tx_request.base.to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS));
726        tx_request.base.chain_id = Some(1);
727        tx_request.base.from = Some(CONTRACT_DEPLOYER_ADDRESS);
728        let result = tx_request.build_unsigned();
729        assert!(result.is_ok());
730        if let Ok(crate::network::unsigned_tx::TypedTransaction::Native(tx)) = result {
731            assert_eq!(tx.chain_id(), Some(1));
732            assert_eq!(tx.nonce(), 0);
733            assert_eq!(tx.gas_limit(), 21000);
734            assert_eq!(tx.max_fee_per_gas(), 100);
735            assert_eq!(tx.max_priority_fee_per_gas(), Some(1));
736            assert_eq!(tx.to(), Some(CONTRACT_DEPLOYER_ADDRESS));
737        } else {
738            panic!("Expected Native transaction");
739        }
740    }
741
742    #[test]
743    fn test_build_unsigned_missing_fields() {
744        let tx_request = TransactionRequest::default();
745        let result = tx_request.build_unsigned();
746        assert!(result.is_err());
747    }
748}