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 input(&self) -> Option<&alloy::primitives::Bytes> {
287        TransactionBuilder::input(&self.base)
288    }
289
290    fn set_input<T: Into<alloy::primitives::Bytes>>(&mut self, input: T) {
291        TransactionBuilder::set_input(&mut self.base, input.into())
292    }
293
294    fn from(&self) -> Option<alloy::primitives::Address> {
295        TransactionBuilder::from(&self.base)
296    }
297
298    fn set_from(&mut self, from: alloy::primitives::Address) {
299        TransactionBuilder::set_from(&mut self.base, from)
300    }
301
302    fn kind(&self) -> Option<alloy::primitives::TxKind> {
303        TransactionBuilder::kind(&self.base)
304    }
305
306    fn clear_kind(&mut self) {
307        TransactionBuilder::clear_kind(&mut self.base)
308    }
309
310    fn set_kind(&mut self, kind: alloy::primitives::TxKind) {
311        TransactionBuilder::set_kind(&mut self.base, kind)
312    }
313
314    fn value(&self) -> Option<alloy::primitives::U256> {
315        TransactionBuilder::value(&self.base)
316    }
317
318    fn set_value(&mut self, value: alloy::primitives::U256) {
319        TransactionBuilder::set_value(&mut self.base, value)
320    }
321
322    fn gas_price(&self) -> Option<u128> {
323        TransactionBuilder::gas_price(&self.base)
324    }
325
326    fn set_gas_price(&mut self, gas_price: u128) {
327        TransactionBuilder::set_gas_price(&mut self.base, gas_price)
328    }
329
330    fn max_fee_per_gas(&self) -> Option<u128> {
331        TransactionBuilder::max_fee_per_gas(&self.base)
332    }
333
334    fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128) {
335        TransactionBuilder::set_max_fee_per_gas(&mut self.base, max_fee_per_gas)
336    }
337
338    fn max_priority_fee_per_gas(&self) -> Option<u128> {
339        TransactionBuilder::max_priority_fee_per_gas(&self.base)
340    }
341
342    fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128) {
343        TransactionBuilder::set_max_priority_fee_per_gas(&mut self.base, max_priority_fee_per_gas)
344    }
345
346    fn gas_limit(&self) -> Option<u64> {
347        TransactionBuilder::gas_limit(&self.base)
348    }
349
350    fn set_gas_limit(&mut self, gas_limit: u64) {
351        TransactionBuilder::set_gas_limit(&mut self.base, gas_limit)
352    }
353
354    fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
355        TransactionBuilder::access_list(&self.base)
356    }
357
358    fn set_access_list(&mut self, access_list: alloy::rpc::types::AccessList) {
359        TransactionBuilder::set_access_list(&mut self.base, access_list)
360    }
361
362    fn complete_type(&self, ty: <Zksync as Network>::TxType) -> Result<(), Vec<&'static str>> {
363        // TODO: cover era-specific types.
364        match ty {
365            TxType::Eip712 => {
366                // TODO: Should check gas per pubdata?
367                TransactionBuilder::complete_type(&self.base, alloy::consensus::TxType::Eip1559)
368            }
369            _ if ty.as_eth_type().is_some() => {
370                TransactionBuilder::complete_type(&self.base, ty.as_eth_type().unwrap())
371            }
372            _ => Err(vec!["Unsupported transaction type"]),
373        }
374    }
375
376    fn can_submit(&self) -> bool {
377        TransactionBuilder::can_submit(&self.base)
378    }
379
380    fn can_build(&self) -> bool {
381        if self.eip_712_meta.is_some() {
382            let common = self.base.gas.is_some() && self.base.nonce.is_some();
383            let eip1559 =
384                self.base.max_fee_per_gas.is_some() && self.base.max_priority_fee_per_gas.is_some();
385            // TODO: Should check gas per pubdata?
386            return common && eip1559;
387        }
388
389        TransactionBuilder::can_build(&self.base)
390    }
391
392    fn output_tx_type(&self) -> <Zksync as Network>::TxType {
393        if self.eip_712_meta.is_some() {
394            return TxType::Eip712;
395        }
396
397        TransactionBuilder::output_tx_type(&self.base).into()
398    }
399
400    fn output_tx_type_checked(&self) -> Option<<Zksync as Network>::TxType> {
401        if self.eip_712_meta.is_some() {
402            if !self.can_build() {
403                return None;
404            }
405            return Some(TxType::Eip712);
406        }
407
408        TransactionBuilder::output_tx_type_checked(&self.base).map(Into::into)
409    }
410
411    fn prep_for_submission(&mut self) {
412        // This has to go first, as it overwrites the transaction type.
413        TransactionBuilder::prep_for_submission(&mut self.base);
414
415        if self.eip_712_meta.is_some() {
416            self.base.transaction_type = Some(TxType::Eip712 as u8);
417            self.base.gas_price = None;
418            self.base.blob_versioned_hashes = None;
419            self.base.sidecar = None;
420        }
421    }
422
423    fn build_unsigned(
424        self,
425    ) -> alloy::network::BuildResult<crate::network::unsigned_tx::TypedTransaction, Zksync> {
426        if self.eip_712_meta.is_some() {
427            let mut missing = Vec::new();
428            // TODO: Copy-paste, can be avoided?
429            if self.base.max_fee_per_gas.is_none() {
430                missing.push("max_fee_per_gas");
431            }
432            if self.base.max_priority_fee_per_gas.is_none() {
433                missing.push("max_priority_fee_per_gas");
434            }
435
436            if !missing.is_empty() {
437                return Err(TransactionBuilderError::InvalidTransactionRequest(
438                    TxType::Eip712,
439                    missing,
440                )
441                .into_unbuilt(self));
442            }
443
444            let TxKind::Call(to) = self.base.to.unwrap_or_default() else {
445                return Err(TransactionBuilderError::InvalidTransactionRequest(
446                    TxType::Eip712,
447                    vec!["to (recipient) must be specified for EIP-712 transactions"],
448                )
449                .into_unbuilt(self));
450            };
451
452            // TODO: Are unwraps safe?
453            let tx = TxEip712 {
454                chain_id: self.base.chain_id.unwrap(),
455                nonce: U256::from(self.base.nonce.unwrap()), // TODO: Deployment nonce?
456                gas: self.base.gas.unwrap(),
457                max_fee_per_gas: self.base.max_fee_per_gas.unwrap(),
458                max_priority_fee_per_gas: self.base.max_priority_fee_per_gas.unwrap(),
459                eip712_meta: self.eip_712_meta,
460                from: self.base.from.unwrap(),
461                to,
462                value: self.base.value.unwrap_or_default(),
463                input: self.base.input.into_input().unwrap_or_default(),
464            };
465            return Ok(crate::network::unsigned_tx::TypedTransaction::Eip712(tx));
466        }
467
468        use TransactionBuilderError::*;
469        let inner = self.base;
470
471        let result = TransactionBuilder::build_unsigned(inner);
472        match result {
473            Ok(tx) => Ok(crate::network::unsigned_tx::TypedTransaction::Native(tx)),
474            Err(err) => {
475                let UnbuiltTransactionError { request, error } = err;
476                let wrapped_request = Self {
477                    base: request,
478                    eip_712_meta: None,
479                };
480                let error = match error {
481                    InvalidTransactionRequest(tx, fields) => {
482                        InvalidTransactionRequest(tx.into(), fields)
483                    }
484                    UnsupportedSignatureType => UnsupportedSignatureType,
485                    Signer(s) => Signer(s),
486                    Custom(c) => Custom(c),
487                };
488
489                Err(UnbuiltTransactionError {
490                    request: wrapped_request,
491                    error,
492                })
493            }
494        }
495    }
496
497    async fn build<W: alloy::network::NetworkWallet<Zksync>>(
498        self,
499        wallet: &W,
500    ) -> Result<<Zksync as Network>::TxEnvelope, TransactionBuilderError<Zksync>> {
501        Ok(wallet.sign_request(self).await?)
502    }
503}
504
505impl From<alloy::rpc::types::transaction::TransactionRequest> for TransactionRequest {
506    fn from(value: alloy::rpc::types::transaction::TransactionRequest) -> Self {
507        Self {
508            base: value,
509            ..Default::default()
510        }
511    }
512}
513
514#[cfg(test)]
515mod tests {
516    use super::*;
517    use alloy::consensus::Transaction as _;
518    use alloy::primitives::U256;
519    use alloy::rpc::types::transaction::TransactionRequest as AlloyTransactionRequest;
520
521    #[test]
522    fn test_default_transaction_request() {
523        let tx_request = TransactionRequest::default();
524        assert_eq!(tx_request.base.transaction_type, Some(TxType::Eip712 as u8));
525        assert!(tx_request.eip_712_meta.is_none());
526    }
527
528    #[test]
529    fn test_set_gas_per_pubdata() {
530        let mut tx_request = TransactionRequest::default();
531        let gas_per_pubdata = U256::from(1000);
532        tx_request.set_gas_per_pubdata(gas_per_pubdata);
533        assert_eq!(tx_request.gas_per_pubdata(), Some(gas_per_pubdata));
534    }
535
536    #[test]
537    fn test_with_gas_per_pubdata() {
538        let gas_per_pubdata = U256::from(1000);
539        let tx_request = TransactionRequest::default().with_gas_per_pubdata(gas_per_pubdata);
540        assert_eq!(tx_request.gas_per_pubdata(), Some(gas_per_pubdata));
541    }
542
543    #[test]
544    fn test_set_factory_deps() {
545        let mut tx_request = TransactionRequest::default();
546        let factory_deps = vec![Bytes::from(vec![1, 2, 3])];
547        tx_request.set_factory_deps(factory_deps.clone());
548        assert_eq!(tx_request.factory_deps(), Some(&factory_deps));
549    }
550
551    #[test]
552    fn test_with_factory_deps() {
553        let factory_deps = vec![Bytes::from(vec![1, 2, 3])];
554        let tx_request = TransactionRequest::default().with_factory_deps(factory_deps.clone());
555        assert_eq!(tx_request.factory_deps(), Some(&factory_deps));
556    }
557
558    #[test]
559    fn test_set_custom_signature() {
560        let mut tx_request = TransactionRequest::default();
561        let custom_signature = Bytes::from(vec![1, 2, 3]);
562        tx_request.set_custom_signature(custom_signature.clone());
563        assert_eq!(tx_request.custom_signature(), Some(&custom_signature));
564    }
565
566    #[test]
567    fn test_with_custom_signature() {
568        let custom_signature = Bytes::from(vec![1, 2, 3]);
569        let tx_request =
570            TransactionRequest::default().with_custom_signature(custom_signature.clone());
571        assert_eq!(tx_request.custom_signature(), Some(&custom_signature));
572    }
573
574    #[test]
575    fn test_set_paymaster_params() {
576        let mut tx_request = TransactionRequest::default();
577        let paymaster_params = PaymasterParams::default();
578        tx_request.set_paymaster_params(paymaster_params.clone());
579        assert_eq!(tx_request.paymaster_params(), Some(&paymaster_params));
580    }
581
582    #[test]
583    fn test_with_paymaster_params() {
584        let paymaster_params = PaymasterParams::default();
585        let tx_request =
586            TransactionRequest::default().with_paymaster_params(paymaster_params.clone());
587        assert_eq!(tx_request.paymaster_params(), Some(&paymaster_params));
588    }
589
590    #[test]
591    fn test_from_alloy_transaction_request() {
592        let alloy_tx_request = AlloyTransactionRequest::default();
593        let tx_request: TransactionRequest = alloy_tx_request.clone().into();
594        assert_eq!(tx_request.base, alloy_tx_request);
595    }
596
597    #[test]
598    fn test_prep_for_submission_with_eip712_meta() {
599        let mut tx_request = TransactionRequest::default();
600        tx_request.set_gas_per_pubdata(U256::from(1000));
601        tx_request.prep_for_submission();
602        assert_eq!(tx_request.base.transaction_type, Some(TxType::Eip712 as u8));
603        assert!(tx_request.base.gas_price.is_none());
604        assert!(tx_request.base.blob_versioned_hashes.is_none());
605        assert!(tx_request.base.sidecar.is_none());
606    }
607
608    #[test]
609    fn test_can_build_with_eip712_meta() {
610        let mut tx_request = TransactionRequest::default();
611        tx_request.set_gas_per_pubdata(U256::from(1000));
612        tx_request.base.gas = Some(21000);
613        tx_request.base.nonce = Some(0);
614        tx_request.base.max_fee_per_gas = Some(100);
615        tx_request.base.max_priority_fee_per_gas = Some(1);
616        assert!(tx_request.can_build());
617    }
618
619    #[test]
620    fn test_cannot_build_without_gas() {
621        let mut tx_request = TransactionRequest::default();
622        tx_request.set_gas_per_pubdata(U256::from(1000));
623        tx_request.base.nonce = Some(0);
624        tx_request.base.max_fee_per_gas = Some(100);
625        tx_request.base.max_priority_fee_per_gas = Some(1);
626        assert!(!tx_request.can_build());
627    }
628
629    #[test]
630    fn test_cannot_build_without_nonce() {
631        let mut tx_request = TransactionRequest::default();
632        tx_request.set_gas_per_pubdata(U256::from(1000));
633        tx_request.base.gas = Some(21000);
634        tx_request.base.max_fee_per_gas = Some(100);
635        tx_request.base.max_priority_fee_per_gas = Some(1);
636        assert!(!tx_request.can_build());
637    }
638
639    #[test]
640    fn test_cannot_build_without_max_fee_per_gas() {
641        let mut tx_request = TransactionRequest::default();
642        tx_request.set_gas_per_pubdata(U256::from(1000));
643        tx_request.base.gas = Some(21000);
644        tx_request.base.nonce = Some(0);
645        tx_request.base.max_priority_fee_per_gas = Some(1);
646        assert!(!tx_request.can_build());
647    }
648
649    #[test]
650    fn test_cannot_build_without_max_priority_fee_per_gas() {
651        let mut tx_request = TransactionRequest::default();
652        tx_request.set_gas_per_pubdata(U256::from(1000));
653        tx_request.base.gas = Some(21000);
654        tx_request.base.nonce = Some(0);
655        tx_request.base.max_fee_per_gas = Some(100);
656        assert!(!tx_request.can_build());
657    }
658
659    #[test]
660    fn test_output_tx_type_checked_with_eip712_meta() {
661        let mut tx_request = TransactionRequest::default();
662        tx_request.set_gas_per_pubdata(U256::from(1000));
663        tx_request.base.gas = Some(21000);
664        tx_request.base.nonce = Some(0);
665        tx_request.base.max_fee_per_gas = Some(100);
666        tx_request.base.max_priority_fee_per_gas = Some(1);
667        assert_eq!(tx_request.output_tx_type_checked(), Some(TxType::Eip712));
668    }
669
670    #[test]
671    fn test_output_tx_type_checked_without_eip712_meta() {
672        let mut tx_request = TransactionRequest::default();
673        tx_request.base.gas = Some(21000);
674        tx_request.base.nonce = Some(0);
675        tx_request.base.max_fee_per_gas = Some(100);
676        tx_request.base.max_priority_fee_per_gas = Some(1);
677        assert_eq!(tx_request.output_tx_type_checked(), None);
678    }
679
680    #[test]
681    fn test_output_tx_type_checked_cannot_build() {
682        let mut tx_request = TransactionRequest::default();
683        tx_request.set_gas_per_pubdata(U256::from(1000));
684        tx_request.base.gas = Some(21000);
685        tx_request.base.nonce = Some(0);
686        assert_eq!(tx_request.output_tx_type_checked(), None);
687    }
688
689    #[test]
690    fn test_build_unsigned_with_eip712_meta() {
691        let mut tx_request = TransactionRequest::default();
692        tx_request.set_gas_per_pubdata(U256::from(1000));
693        tx_request.base.gas = Some(21000);
694        tx_request.base.nonce = Some(0);
695        tx_request.base.max_fee_per_gas = Some(100);
696        tx_request.base.max_priority_fee_per_gas = Some(1);
697        tx_request.base.to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS));
698        tx_request.base.chain_id = Some(1);
699        tx_request.base.from = Some(CONTRACT_DEPLOYER_ADDRESS);
700        let result = tx_request.build_unsigned();
701        assert!(result.is_ok());
702        if let Ok(crate::network::unsigned_tx::TypedTransaction::Eip712(tx)) = result {
703            assert_eq!(tx.chain_id, 1);
704            assert_eq!(tx.nonce, U256::from(0));
705            assert_eq!(tx.gas, 21000);
706            assert_eq!(tx.max_fee_per_gas, 100);
707            assert_eq!(tx.max_priority_fee_per_gas, 1);
708            assert_eq!(tx.from, CONTRACT_DEPLOYER_ADDRESS);
709        } else {
710            panic!("Expected Eip712 transaction");
711        }
712    }
713
714    #[test]
715    fn test_build_unsigned_without_eip712_meta() {
716        let mut tx_request = TransactionRequest::default();
717        tx_request.base.gas = Some(21000);
718        tx_request.base.nonce = Some(0);
719        tx_request.base.max_fee_per_gas = Some(100);
720        tx_request.base.max_priority_fee_per_gas = Some(1);
721        tx_request.base.to = Some(TxKind::Call(CONTRACT_DEPLOYER_ADDRESS));
722        tx_request.base.chain_id = Some(1);
723        tx_request.base.from = Some(CONTRACT_DEPLOYER_ADDRESS);
724        let result = tx_request.build_unsigned();
725        assert!(result.is_ok());
726        if let Ok(crate::network::unsigned_tx::TypedTransaction::Native(tx)) = result {
727            assert_eq!(tx.chain_id(), Some(1));
728            assert_eq!(tx.nonce(), 0);
729            assert_eq!(tx.gas_limit(), 21000);
730            assert_eq!(tx.max_fee_per_gas(), 100);
731            assert_eq!(tx.max_priority_fee_per_gas(), Some(1));
732            assert_eq!(tx.to(), Some(CONTRACT_DEPLOYER_ADDRESS));
733        } else {
734            panic!("Expected Native transaction");
735        }
736    }
737
738    #[test]
739    fn test_build_unsigned_missing_fields() {
740        let tx_request = TransactionRequest::default();
741        let result = tx_request.build_unsigned();
742        assert!(result.is_err());
743    }
744}