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