alloy_zksync/network/
transaction_response.rs

1use serde::{Deserialize, Serialize};
2
3/// ZKsync transaction response.
4#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
5#[serde(
6    into = "serde_from::TransactionEither",
7    from = "serde_from::TransactionEither"
8)]
9pub struct TransactionResponse {
10    #[serde(flatten)]
11    inner: alloy::rpc::types::transaction::Transaction<crate::network::tx_envelope::TxEnvelope>,
12}
13
14impl alloy::consensus::Transaction for TransactionResponse {
15    fn chain_id(&self) -> Option<alloy::primitives::ChainId> {
16        self.inner.chain_id()
17    }
18
19    fn nonce(&self) -> u64 {
20        self.inner.nonce()
21    }
22
23    fn gas_limit(&self) -> u64 {
24        self.inner.gas_limit()
25    }
26
27    fn gas_price(&self) -> Option<u128> {
28        self.inner.gas_price()
29    }
30
31    fn max_fee_per_gas(&self) -> u128 {
32        self.inner.max_fee_per_gas()
33    }
34
35    fn max_priority_fee_per_gas(&self) -> Option<u128> {
36        self.inner.max_priority_fee_per_gas()
37    }
38
39    fn max_fee_per_blob_gas(&self) -> Option<u128> {
40        self.inner.max_fee_per_blob_gas()
41    }
42
43    fn priority_fee_or_price(&self) -> u128 {
44        self.inner.priority_fee_or_price()
45    }
46
47    fn to(&self) -> Option<alloy::primitives::Address> {
48        self.inner.to()
49    }
50
51    fn is_create(&self) -> bool {
52        self.inner.is_create()
53    }
54
55    fn value(&self) -> alloy::primitives::U256 {
56        self.inner.value()
57    }
58
59    fn input(&self) -> &alloy::primitives::Bytes {
60        self.inner.input()
61    }
62
63    fn access_list(&self) -> Option<&alloy::rpc::types::AccessList> {
64        self.inner.access_list()
65    }
66
67    fn blob_versioned_hashes(&self) -> Option<&[alloy::primitives::B256]> {
68        self.inner.blob_versioned_hashes()
69    }
70
71    fn authorization_list(&self) -> Option<&[alloy::eips::eip7702::SignedAuthorization]> {
72        self.inner.authorization_list()
73    }
74
75    fn kind(&self) -> alloy::primitives::TxKind {
76        self.inner.kind()
77    }
78
79    fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
80        self.inner.effective_gas_price(base_fee)
81    }
82
83    fn is_dynamic_fee(&self) -> bool {
84        self.inner.is_dynamic_fee()
85    }
86}
87
88impl alloy::consensus::Typed2718 for TransactionResponse {
89    fn ty(&self) -> u8 {
90        self.inner.ty()
91    }
92}
93
94impl alloy::network::TransactionResponse for TransactionResponse {
95    fn tx_hash(&self) -> alloy::primitives::TxHash {
96        self.inner.tx_hash()
97    }
98
99    fn from(&self) -> alloy::primitives::Address {
100        self.inner.from()
101    }
102
103    fn block_hash(&self) -> Option<alloy::primitives::BlockHash> {
104        self.inner.block_hash()
105    }
106
107    fn block_number(&self) -> Option<u64> {
108        self.inner.block_number()
109    }
110
111    fn transaction_index(&self) -> Option<u64> {
112        self.inner.transaction_index()
113    }
114}
115
116impl AsRef<crate::network::tx_envelope::TxEnvelope> for TransactionResponse {
117    fn as_ref(&self) -> &crate::network::tx_envelope::TxEnvelope {
118        &self.inner.inner
119    }
120}
121
122mod serde_from {
123    //! NB: Why do we need this?
124    //!
125    //! Helper module for serializing and deserializing ZKsync [`TransactionResponse`].
126    //!
127    //! This is needed because we might need to deserialize the `from` field into both
128    //! [`field@alloy::rpc::types::transaction::Transaction::from`] and [`field@TxEip712::from`].
129    use crate::network::transaction_response::TransactionResponse;
130    use crate::network::tx_envelope::TxEnvelope;
131    use crate::network::unsigned_tx::eip712::TxEip712;
132    use alloy::consensus::{Signed, transaction::Recovered};
133    use alloy::primitives::BlockHash;
134    use serde::{Deserialize, Serialize};
135
136    /// Exactly the same thing as [`alloy::rpc::types::transaction::Transaction`] but without the
137    /// `from` field. We need it because [`TxEnvelope::Eip712`] can consume `from` first thus
138    /// failing the entire deserialization process.
139    #[derive(Serialize, Deserialize)]
140    pub struct TransactionWithoutFrom {
141        #[serde(flatten)]
142        pub inner: Signed<TxEip712>,
143        pub block_hash: Option<BlockHash>,
144        pub block_number: Option<u64>,
145        pub transaction_index: Option<u64>,
146        pub effective_gas_price: Option<u128>,
147    }
148
149    /// (De)serializes both regular [`alloy::rpc::types::transaction::Transaction`] and [`TransactionWithoutFrom`].
150    #[derive(Serialize, Deserialize)]
151    #[serde(untagged)]
152    pub enum TransactionEither {
153        Regular(alloy::rpc::types::transaction::Transaction<TxEnvelope>),
154        WithoutFrom(TransactionWithoutFrom),
155    }
156
157    impl From<TransactionEither> for TransactionResponse {
158        fn from(value: TransactionEither) -> Self {
159            match value {
160                TransactionEither::Regular(tx) => TransactionResponse { inner: tx },
161                TransactionEither::WithoutFrom(value) => {
162                    let from = value.inner.tx().from;
163                    TransactionResponse {
164                        inner: alloy::rpc::types::transaction::Transaction {
165                            inner: Recovered::new_unchecked(TxEnvelope::Eip712(value.inner), from),
166                            block_hash: value.block_hash,
167                            block_number: value.block_number,
168                            transaction_index: value.transaction_index,
169                            effective_gas_price: value.effective_gas_price,
170                        },
171                    }
172                }
173            }
174        }
175    }
176
177    impl From<TransactionResponse> for TransactionEither {
178        fn from(value: TransactionResponse) -> Self {
179            match value.inner.inner.as_ref() {
180                TxEnvelope::Native(_) => TransactionEither::Regular(value.inner),
181                TxEnvelope::Eip712(signed) => {
182                    TransactionEither::WithoutFrom(TransactionWithoutFrom {
183                        inner: signed.clone(),
184                        block_hash: value.inner.block_hash,
185                        block_number: value.inner.block_number,
186                        transaction_index: value.inner.transaction_index,
187                        effective_gas_price: value.inner.effective_gas_price,
188                    })
189                }
190            }
191        }
192    }
193}