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