alloy_consensus/transaction/
typed.rs1pub use crate::transaction::envelope::EthereumTypedTransaction;
2use crate::{
3 error::ValueError,
4 transaction::{
5 eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar},
6 RlpEcdsaEncodableTx,
7 },
8 EthereumTxEnvelope, SignableTransaction, Signed, TxEip1559, TxEip2930, TxEip7702, TxLegacy,
9 TxType,
10};
11use alloy_eips::Typed2718;
12use alloy_primitives::{bytes::BufMut, ChainId, Signature, TxHash};
13
14pub type TypedTransaction = EthereumTypedTransaction<TxEip4844Variant>;
16
17impl<Eip4844> From<TxLegacy> for EthereumTypedTransaction<Eip4844> {
18 fn from(tx: TxLegacy) -> Self {
19 Self::Legacy(tx)
20 }
21}
22
23impl<Eip4844> From<TxEip2930> for EthereumTypedTransaction<Eip4844> {
24 fn from(tx: TxEip2930) -> Self {
25 Self::Eip2930(tx)
26 }
27}
28
29impl<Eip4844> From<TxEip1559> for EthereumTypedTransaction<Eip4844> {
30 fn from(tx: TxEip1559) -> Self {
31 Self::Eip1559(tx)
32 }
33}
34
35impl<Eip4844: From<TxEip4844>> From<TxEip4844> for EthereumTypedTransaction<Eip4844> {
36 fn from(tx: TxEip4844) -> Self {
37 Self::Eip4844(tx.into())
38 }
39}
40
41impl<T, Eip4844: From<TxEip4844WithSidecar<T>>> From<TxEip4844WithSidecar<T>>
42 for EthereumTypedTransaction<Eip4844>
43{
44 fn from(tx: TxEip4844WithSidecar<T>) -> Self {
45 Self::Eip4844(tx.into())
46 }
47}
48
49impl<Sidecar, Eip4844: From<TxEip4844Variant<Sidecar>>> From<TxEip4844Variant<Sidecar>>
50 for EthereumTypedTransaction<Eip4844>
51{
52 fn from(tx: TxEip4844Variant<Sidecar>) -> Self {
53 Self::Eip4844(tx.into())
54 }
55}
56
57impl<Eip4844> From<TxEip7702> for EthereumTypedTransaction<Eip4844> {
58 fn from(tx: TxEip7702) -> Self {
59 Self::Eip7702(tx)
60 }
61}
62
63impl<Eip4844> From<EthereumTxEnvelope<Eip4844>> for EthereumTypedTransaction<Eip4844> {
64 fn from(envelope: EthereumTxEnvelope<Eip4844>) -> Self {
65 match envelope {
66 EthereumTxEnvelope::Legacy(tx) => Self::Legacy(tx.strip_signature()),
67 EthereumTxEnvelope::Eip2930(tx) => Self::Eip2930(tx.strip_signature()),
68 EthereumTxEnvelope::Eip1559(tx) => Self::Eip1559(tx.strip_signature()),
69 EthereumTxEnvelope::Eip4844(tx) => Self::Eip4844(tx.strip_signature()),
70 EthereumTxEnvelope::Eip7702(tx) => Self::Eip7702(tx.strip_signature()),
71 }
72 }
73}
74
75impl<T> From<EthereumTypedTransaction<TxEip4844WithSidecar<T>>>
76 for EthereumTypedTransaction<TxEip4844>
77{
78 fn from(value: EthereumTypedTransaction<TxEip4844WithSidecar<T>>) -> Self {
79 value.map_eip4844(|eip4844| eip4844.into())
80 }
81}
82
83impl<T> From<EthereumTypedTransaction<TxEip4844Variant<T>>>
84 for EthereumTypedTransaction<TxEip4844>
85{
86 fn from(value: EthereumTypedTransaction<TxEip4844Variant<T>>) -> Self {
87 value.map_eip4844(|eip4844| eip4844.into())
88 }
89}
90
91impl<T> From<EthereumTypedTransaction<TxEip4844>>
92 for EthereumTypedTransaction<TxEip4844Variant<T>>
93{
94 fn from(value: EthereumTypedTransaction<TxEip4844>) -> Self {
95 value.map_eip4844(|eip4844| eip4844.into())
96 }
97}
98
99impl<Eip4844> EthereumTypedTransaction<Eip4844> {
100 pub fn map_eip4844<U>(self, mut f: impl FnMut(Eip4844) -> U) -> EthereumTypedTransaction<U> {
105 match self {
106 Self::Legacy(tx) => EthereumTypedTransaction::Legacy(tx),
107 Self::Eip2930(tx) => EthereumTypedTransaction::Eip2930(tx),
108 Self::Eip1559(tx) => EthereumTypedTransaction::Eip1559(tx),
109 Self::Eip4844(tx) => EthereumTypedTransaction::Eip4844(f(tx)),
110 Self::Eip7702(tx) => EthereumTypedTransaction::Eip7702(tx),
111 }
112 }
113
114 pub fn into_envelope(self, signature: Signature) -> EthereumTxEnvelope<Eip4844> {
116 match self {
117 Self::Legacy(tx) => EthereumTxEnvelope::Legacy(tx.into_signed(signature)),
118 Self::Eip2930(tx) => EthereumTxEnvelope::Eip2930(tx.into_signed(signature)),
119 Self::Eip1559(tx) => EthereumTxEnvelope::Eip1559(tx.into_signed(signature)),
120 Self::Eip4844(tx) => EthereumTxEnvelope::Eip4844(Signed::new_unhashed(tx, signature)),
121 Self::Eip7702(tx) => EthereumTxEnvelope::Eip7702(tx.into_signed(signature)),
122 }
123 }
124}
125
126impl<Eip4844: RlpEcdsaEncodableTx> EthereumTypedTransaction<Eip4844> {
127 #[doc(alias = "transaction_type")]
129 pub const fn tx_type(&self) -> TxType {
130 match self {
131 Self::Legacy(_) => TxType::Legacy,
132 Self::Eip2930(_) => TxType::Eip2930,
133 Self::Eip1559(_) => TxType::Eip1559,
134 Self::Eip4844(_) => TxType::Eip4844,
135 Self::Eip7702(_) => TxType::Eip7702,
136 }
137 }
138
139 pub const fn legacy(&self) -> Option<&TxLegacy> {
141 match self {
142 Self::Legacy(tx) => Some(tx),
143 _ => None,
144 }
145 }
146
147 pub const fn eip2930(&self) -> Option<&TxEip2930> {
149 match self {
150 Self::Eip2930(tx) => Some(tx),
151 _ => None,
152 }
153 }
154
155 pub const fn eip1559(&self) -> Option<&TxEip1559> {
157 match self {
158 Self::Eip1559(tx) => Some(tx),
159 _ => None,
160 }
161 }
162
163 pub const fn eip7702(&self) -> Option<&TxEip7702> {
165 match self {
166 Self::Eip7702(tx) => Some(tx),
167 _ => None,
168 }
169 }
170
171 pub fn try_into_legacy(self) -> Result<TxLegacy, ValueError<Self>> {
173 match self {
174 Self::Legacy(tx) => Ok(tx),
175 _ => Err(ValueError::new(self, "Expected legacy transaction")),
176 }
177 }
178
179 pub fn try_into_eip2930(self) -> Result<TxEip2930, ValueError<Self>> {
181 match self {
182 Self::Eip2930(tx) => Ok(tx),
183 _ => Err(ValueError::new(self, "Expected EIP-2930 transaction")),
184 }
185 }
186
187 pub fn try_into_eip4844(self) -> Result<Eip4844, ValueError<Self>> {
189 match self {
190 Self::Eip4844(tx) => Ok(tx),
191 _ => Err(ValueError::new(self, "Expected EIP-4844 transaction")),
192 }
193 }
194
195 pub fn try_into_eip7702(self) -> Result<TxEip7702, ValueError<Self>> {
197 match self {
198 Self::Eip7702(tx) => Ok(tx),
199 _ => Err(ValueError::new(self, "Expected EIP-7702 transaction")),
200 }
201 }
202
203 pub fn tx_hash(&self, signature: &Signature) -> TxHash {
205 match self {
206 Self::Legacy(tx) => tx.tx_hash(signature),
207 Self::Eip2930(tx) => tx.tx_hash(signature),
208 Self::Eip1559(tx) => tx.tx_hash(signature),
209 Self::Eip4844(tx) => tx.tx_hash(signature),
210 Self::Eip7702(tx) => tx.tx_hash(signature),
211 }
212 }
213}
214
215impl<Eip4844: RlpEcdsaEncodableTx + Typed2718> RlpEcdsaEncodableTx
216 for EthereumTypedTransaction<Eip4844>
217{
218 fn rlp_encoded_fields_length(&self) -> usize {
219 match self {
220 Self::Legacy(tx) => tx.rlp_encoded_fields_length(),
221 Self::Eip2930(tx) => tx.rlp_encoded_fields_length(),
222 Self::Eip1559(tx) => tx.rlp_encoded_fields_length(),
223 Self::Eip4844(tx) => tx.rlp_encoded_fields_length(),
224 Self::Eip7702(tx) => tx.rlp_encoded_fields_length(),
225 }
226 }
227
228 fn rlp_encode_fields(&self, out: &mut dyn alloy_rlp::BufMut) {
229 match self {
230 Self::Legacy(tx) => tx.rlp_encode_fields(out),
231 Self::Eip2930(tx) => tx.rlp_encode_fields(out),
232 Self::Eip1559(tx) => tx.rlp_encode_fields(out),
233 Self::Eip4844(tx) => tx.rlp_encode_fields(out),
234 Self::Eip7702(tx) => tx.rlp_encode_fields(out),
235 }
236 }
237
238 fn eip2718_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
239 match self {
240 Self::Legacy(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
241 Self::Eip2930(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
242 Self::Eip1559(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
243 Self::Eip4844(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
244 Self::Eip7702(tx) => tx.eip2718_encode_with_type(signature, tx.ty(), out),
245 }
246 }
247
248 fn eip2718_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
249 match self {
250 Self::Legacy(tx) => tx.eip2718_encode(signature, out),
251 Self::Eip2930(tx) => tx.eip2718_encode(signature, out),
252 Self::Eip1559(tx) => tx.eip2718_encode(signature, out),
253 Self::Eip4844(tx) => tx.eip2718_encode(signature, out),
254 Self::Eip7702(tx) => tx.eip2718_encode(signature, out),
255 }
256 }
257
258 fn network_encode_with_type(&self, signature: &Signature, _ty: u8, out: &mut dyn BufMut) {
259 match self {
260 Self::Legacy(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
261 Self::Eip2930(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
262 Self::Eip1559(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
263 Self::Eip4844(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
264 Self::Eip7702(tx) => tx.network_encode_with_type(signature, tx.ty(), out),
265 }
266 }
267
268 fn network_encode(&self, signature: &Signature, out: &mut dyn BufMut) {
269 match self {
270 Self::Legacy(tx) => tx.network_encode(signature, out),
271 Self::Eip2930(tx) => tx.network_encode(signature, out),
272 Self::Eip1559(tx) => tx.network_encode(signature, out),
273 Self::Eip4844(tx) => tx.network_encode(signature, out),
274 Self::Eip7702(tx) => tx.network_encode(signature, out),
275 }
276 }
277
278 fn tx_hash_with_type(&self, signature: &Signature, _ty: u8) -> TxHash {
279 match self {
280 Self::Legacy(tx) => tx.tx_hash_with_type(signature, tx.ty()),
281 Self::Eip2930(tx) => tx.tx_hash_with_type(signature, tx.ty()),
282 Self::Eip1559(tx) => tx.tx_hash_with_type(signature, tx.ty()),
283 Self::Eip4844(tx) => tx.tx_hash_with_type(signature, tx.ty()),
284 Self::Eip7702(tx) => tx.tx_hash_with_type(signature, tx.ty()),
285 }
286 }
287
288 fn tx_hash(&self, signature: &Signature) -> TxHash {
289 match self {
290 Self::Legacy(tx) => tx.tx_hash(signature),
291 Self::Eip2930(tx) => tx.tx_hash(signature),
292 Self::Eip1559(tx) => tx.tx_hash(signature),
293 Self::Eip4844(tx) => tx.tx_hash(signature),
294 Self::Eip7702(tx) => tx.tx_hash(signature),
295 }
296 }
297}
298
299impl<Eip4844: SignableTransaction<Signature>> SignableTransaction<Signature>
300 for EthereumTypedTransaction<Eip4844>
301{
302 fn set_chain_id(&mut self, chain_id: ChainId) {
303 match self {
304 Self::Legacy(tx) => tx.set_chain_id(chain_id),
305 Self::Eip2930(tx) => tx.set_chain_id(chain_id),
306 Self::Eip1559(tx) => tx.set_chain_id(chain_id),
307 Self::Eip4844(tx) => tx.set_chain_id(chain_id),
308 Self::Eip7702(tx) => tx.set_chain_id(chain_id),
309 }
310 }
311
312 fn encode_for_signing(&self, out: &mut dyn BufMut) {
313 match self {
314 Self::Legacy(tx) => tx.encode_for_signing(out),
315 Self::Eip2930(tx) => tx.encode_for_signing(out),
316 Self::Eip1559(tx) => tx.encode_for_signing(out),
317 Self::Eip4844(tx) => tx.encode_for_signing(out),
318 Self::Eip7702(tx) => tx.encode_for_signing(out),
319 }
320 }
321
322 fn payload_len_for_signature(&self) -> usize {
323 match self {
324 Self::Legacy(tx) => tx.payload_len_for_signature(),
325 Self::Eip2930(tx) => tx.payload_len_for_signature(),
326 Self::Eip1559(tx) => tx.payload_len_for_signature(),
327 Self::Eip4844(tx) => tx.payload_len_for_signature(),
328 Self::Eip7702(tx) => tx.payload_len_for_signature(),
329 }
330 }
331}
332
333#[cfg(feature = "serde")]
334impl<Eip4844, T: From<EthereumTypedTransaction<Eip4844>>> From<EthereumTypedTransaction<Eip4844>>
335 for alloy_serde::WithOtherFields<T>
336{
337 fn from(value: EthereumTypedTransaction<Eip4844>) -> Self {
338 Self::new(value.into())
339 }
340}
341
342#[cfg(feature = "serde")]
343impl<Eip4844, T> From<EthereumTxEnvelope<Eip4844>> for alloy_serde::WithOtherFields<T>
344where
345 T: From<EthereumTxEnvelope<Eip4844>>,
346{
347 fn from(value: EthereumTxEnvelope<Eip4844>) -> Self {
348 Self::new(value.into())
349 }
350}
351
352#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
354pub(crate) mod serde_bincode_compat {
355 use alloc::borrow::Cow;
356 use serde::{Deserialize, Deserializer, Serialize, Serializer};
357 use serde_with::{DeserializeAs, SerializeAs};
358
359 #[derive(Debug, Serialize, Deserialize)]
375 pub enum EthereumTypedTransaction<'a, Eip4844: Clone = crate::transaction::TxEip4844> {
376 Legacy(crate::serde_bincode_compat::transaction::TxLegacy<'a>),
378 Eip2930(crate::serde_bincode_compat::transaction::TxEip2930<'a>),
380 Eip1559(crate::serde_bincode_compat::transaction::TxEip1559<'a>),
382 Eip4844(Cow<'a, Eip4844>),
386 Eip7702(crate::serde_bincode_compat::transaction::TxEip7702<'a>),
388 }
389
390 impl<'a, T: Clone> From<&'a super::EthereumTypedTransaction<T>>
391 for EthereumTypedTransaction<'a, T>
392 {
393 fn from(value: &'a super::EthereumTypedTransaction<T>) -> Self {
394 match value {
395 super::EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
396 super::EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
397 super::EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
398 super::EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)),
399 super::EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
400 }
401 }
402 }
403
404 impl<'a, T: Clone> From<EthereumTypedTransaction<'a, T>> for super::EthereumTypedTransaction<T> {
405 fn from(value: EthereumTypedTransaction<'a, T>) -> Self {
406 match value {
407 EthereumTypedTransaction::Legacy(tx) => Self::Legacy(tx.into()),
408 EthereumTypedTransaction::Eip2930(tx) => Self::Eip2930(tx.into()),
409 EthereumTypedTransaction::Eip1559(tx) => Self::Eip1559(tx.into()),
410 EthereumTypedTransaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()),
411 EthereumTypedTransaction::Eip7702(tx) => Self::Eip7702(tx.into()),
412 }
413 }
414 }
415
416 impl<T: Serialize + Clone> SerializeAs<super::EthereumTypedTransaction<T>>
417 for EthereumTypedTransaction<'_, T>
418 {
419 fn serialize_as<S>(
420 source: &super::EthereumTypedTransaction<T>,
421 serializer: S,
422 ) -> Result<S::Ok, S::Error>
423 where
424 S: Serializer,
425 {
426 EthereumTypedTransaction::<'_, T>::from(source).serialize(serializer)
427 }
428 }
429
430 impl<'de, T: Deserialize<'de> + Clone> DeserializeAs<'de, super::EthereumTypedTransaction<T>>
431 for EthereumTypedTransaction<'de, T>
432 {
433 fn deserialize_as<D>(
434 deserializer: D,
435 ) -> Result<super::EthereumTypedTransaction<T>, D::Error>
436 where
437 D: Deserializer<'de>,
438 {
439 EthereumTypedTransaction::<'_, T>::deserialize(deserializer).map(Into::into)
440 }
441 }
442
443 #[cfg(test)]
444 mod tests {
445 use super::super::{serde_bincode_compat, EthereumTypedTransaction};
446 use crate::TxEip4844;
447 use arbitrary::Arbitrary;
448 use bincode::config;
449 use rand::Rng;
450 use serde::{Deserialize, Serialize};
451 use serde_with::serde_as;
452
453 #[test]
454 fn test_typed_tx_bincode_roundtrip() {
455 #[serde_as]
456 #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
457 struct Data {
458 #[serde_as(as = "serde_bincode_compat::EthereumTypedTransaction<'_>")]
459 transaction: EthereumTypedTransaction<TxEip4844>,
460 }
461
462 let mut bytes = [0u8; 1024];
463 rand::thread_rng().fill(bytes.as_mut_slice());
464 let data = Data {
465 transaction: EthereumTypedTransaction::arbitrary(
466 &mut arbitrary::Unstructured::new(&bytes),
467 )
468 .unwrap(),
469 };
470
471 let encoded = bincode::serde::encode_to_vec(&data, config::legacy()).unwrap();
472 let (decoded, _) =
473 bincode::serde::decode_from_slice::<Data, _>(&encoded, config::legacy()).unwrap();
474 assert_eq!(decoded, data);
475 }
476 }
477}