alloy_consensus/transaction/
mod.rs1use crate::Signed;
4use alloc::vec::Vec;
5use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization};
6use alloy_primitives::{keccak256, Address, Bytes, ChainId, Selector, TxKind, B256, U256};
7use core::{any, fmt};
8
9mod eip1559;
10pub use eip1559::TxEip1559;
11
12mod eip2930;
13pub use eip2930::TxEip2930;
14
15mod eip7702;
16pub use eip7702::TxEip7702;
17
18mod envelope;
19#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
20pub use envelope::serde_bincode_compat as envelope_serde_bincode_compat;
21pub use envelope::{EthereumTxEnvelope, TxEnvelope};
22
23pub mod eip4844;
25pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
26
27mod eip4844_sidecar;
28#[cfg(feature = "kzg")]
29pub use eip4844_sidecar::BlobTransactionValidationError;
30pub use eip4844_sidecar::TxEip4844Sidecar;
31
32pub use alloy_eips::eip4844::{
34 builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
35 utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
36};
37
38pub mod pooled;
39pub use pooled::PooledTransaction;
40
41pub use either::Either;
43
44mod legacy;
45pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
46
47mod rlp;
48#[doc(hidden)]
49pub use rlp::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, RlpEcdsaTx};
50
51mod typed;
52pub use typed::{EthereumTypedTransaction, TypedTransaction};
53
54mod tx_type;
55pub use tx_type::TxType;
56
57mod meta;
58pub use meta::{TransactionInfo, TransactionMeta};
59
60mod recovered;
61pub use recovered::{Recovered, SignerRecoverable};
62
63#[cfg(feature = "serde")]
64pub use legacy::signed_legacy_serde;
65
66#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
68pub mod serde_bincode_compat {
69 pub use super::{
70 eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
71 eip7702::serde_bincode_compat::*, envelope::serde_bincode_compat::*,
72 legacy::serde_bincode_compat::*, typed::serde_bincode_compat::*,
73 };
74}
75
76use alloy_eips::Typed2718;
77
78#[doc(alias = "Tx")]
83#[auto_impl::auto_impl(&, Arc)]
84pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
85 fn chain_id(&self) -> Option<ChainId>;
87
88 fn nonce(&self) -> u64;
90
91 fn gas_limit(&self) -> u64;
93
94 fn gas_price(&self) -> Option<u128>;
96
97 fn max_fee_per_gas(&self) -> u128;
103
104 fn max_priority_fee_per_gas(&self) -> Option<u128>;
109
110 fn max_fee_per_blob_gas(&self) -> Option<u128>;
116
117 fn priority_fee_or_price(&self) -> u128;
125
126 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
130
131 fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
136 let base_fee = base_fee as u128;
137
138 let max_fee_per_gas = self.max_fee_per_gas();
139
140 if max_fee_per_gas < base_fee {
142 return None;
143 }
144
145 let fee = max_fee_per_gas - base_fee;
147
148 self.max_priority_fee_per_gas()
150 .map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
151 }
152
153 fn is_dynamic_fee(&self) -> bool;
155
156 fn kind(&self) -> TxKind;
158
159 fn is_create(&self) -> bool;
163
164 fn to(&self) -> Option<Address> {
169 self.kind().to().copied()
170 }
171
172 fn value(&self) -> U256;
174
175 fn input(&self) -> &Bytes;
177
178 fn function_selector(&self) -> Option<&Selector> {
182 if self.kind().is_call() {
183 self.input().get(..4).and_then(|s| TryFrom::try_from(s).ok())
184 } else {
185 None
186 }
187 }
188
189 fn access_list(&self) -> Option<&AccessList>;
192
193 fn blob_versioned_hashes(&self) -> Option<&[B256]>;
196
197 fn blob_count(&self) -> Option<u64> {
203 self.blob_versioned_hashes().map(|h| h.len() as u64)
204 }
205
206 #[inline]
210 fn blob_gas_used(&self) -> Option<u64> {
211 self.blob_count().map(|blobs| blobs * DATA_GAS_PER_BLOB)
213 }
214
215 fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
219
220 fn authorization_count(&self) -> Option<u64> {
226 self.authorization_list().map(|auths| auths.len() as u64)
227 }
228}
229
230#[doc(alias = "SignableTx", alias = "TxSignable")]
237pub trait SignableTransaction<Signature>: Transaction {
238 fn set_chain_id(&mut self, chain_id: ChainId);
242
243 fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
246 match self.chain_id() {
247 Some(tx_chain_id) => {
248 if tx_chain_id != chain_id {
249 return false;
250 }
251 self.set_chain_id(chain_id);
252 }
253 None => {
254 self.set_chain_id(chain_id);
255 }
256 }
257 true
258 }
259
260 fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
262
263 fn payload_len_for_signature(&self) -> usize;
265
266 fn encoded_for_signing(&self) -> Vec<u8> {
270 let mut buf = Vec::with_capacity(self.payload_len_for_signature());
271 self.encode_for_signing(&mut buf);
272 buf
273 }
274
275 fn signature_hash(&self) -> B256 {
277 keccak256(self.encoded_for_signing())
278 }
279
280 fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
282 where
283 Self: Sized,
284 {
285 Signed::new_unhashed(self, signature)
286 }
287}
288
289#[doc(hidden)]
291impl<S: 'static> dyn SignableTransaction<S> {
292 pub fn __downcast_ref<T: any::Any>(&self) -> Option<&T> {
293 if any::Any::type_id(self) == any::TypeId::of::<T>() {
294 unsafe { Some(&*(self as *const _ as *const T)) }
295 } else {
296 None
297 }
298 }
299}
300
301#[cfg(feature = "serde")]
302impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
303 #[inline]
304 fn chain_id(&self) -> Option<ChainId> {
305 self.inner.chain_id()
306 }
307
308 #[inline]
309 fn nonce(&self) -> u64 {
310 self.inner.nonce()
311 }
312
313 #[inline]
314 fn gas_limit(&self) -> u64 {
315 self.inner.gas_limit()
316 }
317
318 #[inline]
319 fn gas_price(&self) -> Option<u128> {
320 self.inner.gas_price()
321 }
322
323 #[inline]
324 fn max_fee_per_gas(&self) -> u128 {
325 self.inner.max_fee_per_gas()
326 }
327
328 #[inline]
329 fn max_priority_fee_per_gas(&self) -> Option<u128> {
330 self.inner.max_priority_fee_per_gas()
331 }
332
333 #[inline]
334 fn max_fee_per_blob_gas(&self) -> Option<u128> {
335 self.inner.max_fee_per_blob_gas()
336 }
337
338 #[inline]
339 fn priority_fee_or_price(&self) -> u128 {
340 self.inner.priority_fee_or_price()
341 }
342
343 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
344 self.inner.effective_gas_price(base_fee)
345 }
346
347 #[inline]
348 fn is_dynamic_fee(&self) -> bool {
349 self.inner.is_dynamic_fee()
350 }
351
352 #[inline]
353 fn kind(&self) -> TxKind {
354 self.inner.kind()
355 }
356
357 #[inline]
358 fn is_create(&self) -> bool {
359 self.inner.is_create()
360 }
361
362 #[inline]
363 fn value(&self) -> U256 {
364 self.inner.value()
365 }
366
367 #[inline]
368 fn input(&self) -> &Bytes {
369 self.inner.input()
370 }
371
372 #[inline]
373 fn access_list(&self) -> Option<&AccessList> {
374 self.inner.access_list()
375 }
376
377 #[inline]
378 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
379 self.inner.blob_versioned_hashes()
380 }
381
382 #[inline]
383 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
384 self.inner.authorization_list()
385 }
386}
387
388impl<L, R> Transaction for either::Either<L, R>
389where
390 L: Transaction,
391 R: Transaction,
392{
393 fn chain_id(&self) -> Option<ChainId> {
394 match self {
395 Self::Left(tx) => tx.chain_id(),
396 Self::Right(tx) => tx.chain_id(),
397 }
398 }
399
400 fn nonce(&self) -> u64 {
401 match self {
402 Self::Left(tx) => tx.nonce(),
403 Self::Right(tx) => tx.nonce(),
404 }
405 }
406
407 fn gas_limit(&self) -> u64 {
408 match self {
409 Self::Left(tx) => tx.gas_limit(),
410 Self::Right(tx) => tx.gas_limit(),
411 }
412 }
413
414 fn gas_price(&self) -> Option<u128> {
415 match self {
416 Self::Left(tx) => tx.gas_price(),
417 Self::Right(tx) => tx.gas_price(),
418 }
419 }
420
421 fn max_fee_per_gas(&self) -> u128 {
422 match self {
423 Self::Left(tx) => tx.max_fee_per_gas(),
424 Self::Right(tx) => tx.max_fee_per_gas(),
425 }
426 }
427
428 fn max_priority_fee_per_gas(&self) -> Option<u128> {
429 match self {
430 Self::Left(tx) => tx.max_priority_fee_per_gas(),
431 Self::Right(tx) => tx.max_priority_fee_per_gas(),
432 }
433 }
434
435 fn max_fee_per_blob_gas(&self) -> Option<u128> {
436 match self {
437 Self::Left(tx) => tx.max_fee_per_blob_gas(),
438 Self::Right(tx) => tx.max_fee_per_blob_gas(),
439 }
440 }
441
442 fn priority_fee_or_price(&self) -> u128 {
443 match self {
444 Self::Left(tx) => tx.priority_fee_or_price(),
445 Self::Right(tx) => tx.priority_fee_or_price(),
446 }
447 }
448
449 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
450 match self {
451 Self::Left(tx) => tx.effective_gas_price(base_fee),
452 Self::Right(tx) => tx.effective_gas_price(base_fee),
453 }
454 }
455
456 fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
457 match self {
458 Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
459 Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
460 }
461 }
462
463 fn is_dynamic_fee(&self) -> bool {
464 match self {
465 Self::Left(tx) => tx.is_dynamic_fee(),
466 Self::Right(tx) => tx.is_dynamic_fee(),
467 }
468 }
469
470 fn kind(&self) -> TxKind {
471 match self {
472 Self::Left(tx) => tx.kind(),
473 Self::Right(tx) => tx.kind(),
474 }
475 }
476
477 fn is_create(&self) -> bool {
478 match self {
479 Self::Left(tx) => tx.is_create(),
480 Self::Right(tx) => tx.is_create(),
481 }
482 }
483
484 fn to(&self) -> Option<Address> {
485 match self {
486 Self::Left(tx) => tx.to(),
487 Self::Right(tx) => tx.to(),
488 }
489 }
490
491 fn value(&self) -> U256 {
492 match self {
493 Self::Left(tx) => tx.value(),
494 Self::Right(tx) => tx.value(),
495 }
496 }
497
498 fn input(&self) -> &Bytes {
499 match self {
500 Self::Left(tx) => tx.input(),
501 Self::Right(tx) => tx.input(),
502 }
503 }
504
505 fn function_selector(&self) -> Option<&Selector> {
506 match self {
507 Self::Left(tx) => tx.function_selector(),
508 Self::Right(tx) => tx.function_selector(),
509 }
510 }
511
512 fn access_list(&self) -> Option<&AccessList> {
513 match self {
514 Self::Left(tx) => tx.access_list(),
515 Self::Right(tx) => tx.access_list(),
516 }
517 }
518
519 fn blob_versioned_hashes(&self) -> Option<&[B256]> {
520 match self {
521 Self::Left(tx) => tx.blob_versioned_hashes(),
522 Self::Right(tx) => tx.blob_versioned_hashes(),
523 }
524 }
525
526 fn blob_count(&self) -> Option<u64> {
527 match self {
528 Self::Left(tx) => tx.blob_count(),
529 Self::Right(tx) => tx.blob_count(),
530 }
531 }
532
533 fn blob_gas_used(&self) -> Option<u64> {
534 match self {
535 Self::Left(tx) => tx.blob_gas_used(),
536 Self::Right(tx) => tx.blob_gas_used(),
537 }
538 }
539
540 fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
541 match self {
542 Self::Left(tx) => tx.authorization_list(),
543 Self::Right(tx) => tx.authorization_list(),
544 }
545 }
546
547 fn authorization_count(&self) -> Option<u64> {
548 match self {
549 Self::Left(tx) => tx.authorization_count(),
550 Self::Right(tx) => tx.authorization_count(),
551 }
552 }
553}