use crate::Signed;
use alloc::{borrow::Cow, vec::Vec};
use alloy_eips::{eip2930::AccessList, eip4844::DATA_GAS_PER_BLOB, eip7702::SignedAuthorization};
use alloy_primitives::{keccak256, Address, Bytes, ChainId, Selector, TxHash, TxKind, B256, U256};
use auto_impl::auto_impl;
use core::{any, fmt};
mod eip1559;
pub use eip1559::TxEip1559;
mod eip2930;
pub use eip2930::TxEip2930;
mod eip7702;
pub use eip7702::TxEip7702;
mod envelope;
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub use envelope::serde_bincode_compat as envelope_serde_bincode_compat;
pub use envelope::{EthereumTxEnvelope, TxEnvelope, TxType};
pub mod eip4844;
pub use eip4844::{TxEip4844, TxEip4844Variant, TxEip4844WithSidecar};
mod eip4844_sidecar;
#[cfg(feature = "kzg")]
pub use eip4844_sidecar::BlobTransactionValidationError;
pub use eip4844_sidecar::TxEip4844Sidecar;
pub use alloy_eips::eip4844::{
builder::{SidecarBuilder, SidecarCoder, SimpleCoder},
utils as eip4844_utils, Blob, BlobTransactionSidecar, Bytes48,
};
pub mod pooled;
pub use pooled::PooledTransaction;
pub use either::Either;
mod legacy;
pub use legacy::{from_eip155_value, to_eip155_value, TxLegacy};
mod rlp;
#[doc(hidden)]
pub use rlp::{RlpEcdsaDecodableTx, RlpEcdsaEncodableTx, RlpEcdsaTx};
mod typed;
pub use typed::{EthereumTypedTransaction, TypedTransaction};
mod tx_type;
mod meta;
pub use meta::{TransactionInfo, TransactionMeta};
mod recovered;
pub use recovered::{Recovered, SignerRecoverable};
mod hashable;
pub use hashable::TxHashable;
#[cfg(feature = "serde")]
pub use legacy::{signed_legacy_serde, untagged_legacy_serde};
#[cfg(all(feature = "serde", feature = "serde-bincode-compat"))]
pub mod serde_bincode_compat {
pub use super::{
eip1559::serde_bincode_compat::*, eip2930::serde_bincode_compat::*,
eip7702::serde_bincode_compat::*, envelope::serde_bincode_compat::*,
legacy::serde_bincode_compat::*, typed::serde_bincode_compat::*,
};
}
use alloy_eips::Typed2718;
#[doc(alias = "Tx")]
#[auto_impl::auto_impl(&, Arc)]
pub trait Transaction: Typed2718 + fmt::Debug + any::Any + Send + Sync + 'static {
fn chain_id(&self) -> Option<ChainId>;
fn nonce(&self) -> u64;
fn gas_limit(&self) -> u64;
fn gas_price(&self) -> Option<u128>;
fn max_fee_per_gas(&self) -> u128;
fn max_priority_fee_per_gas(&self) -> Option<u128>;
fn max_fee_per_blob_gas(&self) -> Option<u128>;
fn priority_fee_or_price(&self) -> u128;
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128;
fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
let base_fee = base_fee as u128;
let max_fee_per_gas = self.max_fee_per_gas();
if max_fee_per_gas < base_fee {
return None;
}
let fee = max_fee_per_gas - base_fee;
self.max_priority_fee_per_gas()
.map_or(Some(fee), |priority_fee| Some(fee.min(priority_fee)))
}
fn is_dynamic_fee(&self) -> bool;
fn kind(&self) -> TxKind;
fn is_create(&self) -> bool;
fn to(&self) -> Option<Address> {
self.kind().into_to()
}
fn value(&self) -> U256;
fn input(&self) -> &Bytes;
fn function_selector(&self) -> Option<&Selector> {
if self.kind().is_call() {
self.input().get(..4).and_then(|s| TryFrom::try_from(s).ok())
} else {
None
}
}
fn access_list(&self) -> Option<&AccessList>;
fn blob_versioned_hashes(&self) -> Option<&[B256]>;
fn blob_count(&self) -> Option<u64> {
self.blob_versioned_hashes().map(|h| h.len() as u64)
}
#[inline]
fn blob_gas_used(&self) -> Option<u64> {
self.blob_count().map(|blobs| blobs * DATA_GAS_PER_BLOB)
}
fn authorization_list(&self) -> Option<&[SignedAuthorization]>;
fn authorization_count(&self) -> Option<u64> {
self.authorization_list().map(|auths| auths.len() as u64)
}
}
pub trait TransactionEnvelope: Transaction {
type TxType: Typed2718;
fn tx_type(&self) -> Self::TxType;
}
#[doc(alias = "SignableTx", alias = "TxSignable")]
pub trait SignableTransaction<Signature>: Transaction {
fn set_chain_id(&mut self, chain_id: ChainId);
fn set_chain_id_checked(&mut self, chain_id: ChainId) -> bool {
match self.chain_id() {
Some(tx_chain_id) => {
if tx_chain_id != chain_id {
return false;
}
}
None => {
self.set_chain_id(chain_id);
}
}
true
}
fn encode_for_signing(&self, out: &mut dyn alloy_rlp::BufMut);
fn payload_len_for_signature(&self) -> usize;
fn encoded_for_signing(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(self.payload_len_for_signature());
self.encode_for_signing(&mut buf);
buf
}
fn signature_hash(&self) -> B256 {
keccak256(self.encoded_for_signing())
}
fn into_signed(self, signature: Signature) -> Signed<Self, Signature>
where
Self: Sized,
{
Signed::new_unhashed(self, signature)
}
}
#[cfg(feature = "serde")]
impl<T: Transaction> Transaction for alloy_serde::WithOtherFields<T> {
#[inline]
fn chain_id(&self) -> Option<ChainId> {
self.inner.chain_id()
}
#[inline]
fn nonce(&self) -> u64 {
self.inner.nonce()
}
#[inline]
fn gas_limit(&self) -> u64 {
self.inner.gas_limit()
}
#[inline]
fn gas_price(&self) -> Option<u128> {
self.inner.gas_price()
}
#[inline]
fn max_fee_per_gas(&self) -> u128 {
self.inner.max_fee_per_gas()
}
#[inline]
fn max_priority_fee_per_gas(&self) -> Option<u128> {
self.inner.max_priority_fee_per_gas()
}
#[inline]
fn max_fee_per_blob_gas(&self) -> Option<u128> {
self.inner.max_fee_per_blob_gas()
}
#[inline]
fn priority_fee_or_price(&self) -> u128 {
self.inner.priority_fee_or_price()
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
self.inner.effective_gas_price(base_fee)
}
#[inline]
fn is_dynamic_fee(&self) -> bool {
self.inner.is_dynamic_fee()
}
#[inline]
fn kind(&self) -> TxKind {
self.inner.kind()
}
#[inline]
fn is_create(&self) -> bool {
self.inner.is_create()
}
#[inline]
fn value(&self) -> U256 {
self.inner.value()
}
#[inline]
fn input(&self) -> &Bytes {
self.inner.input()
}
#[inline]
fn access_list(&self) -> Option<&AccessList> {
self.inner.access_list()
}
#[inline]
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
self.inner.blob_versioned_hashes()
}
#[inline]
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
self.inner.authorization_list()
}
}
impl<L, R> Transaction for either::Either<L, R>
where
L: Transaction,
R: Transaction,
{
fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Left(tx) => tx.chain_id(),
Self::Right(tx) => tx.chain_id(),
}
}
fn nonce(&self) -> u64 {
match self {
Self::Left(tx) => tx.nonce(),
Self::Right(tx) => tx.nonce(),
}
}
fn gas_limit(&self) -> u64 {
match self {
Self::Left(tx) => tx.gas_limit(),
Self::Right(tx) => tx.gas_limit(),
}
}
fn gas_price(&self) -> Option<u128> {
match self {
Self::Left(tx) => tx.gas_price(),
Self::Right(tx) => tx.gas_price(),
}
}
fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Left(tx) => tx.max_fee_per_gas(),
Self::Right(tx) => tx.max_fee_per_gas(),
}
}
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Left(tx) => tx.max_priority_fee_per_gas(),
Self::Right(tx) => tx.max_priority_fee_per_gas(),
}
}
fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::Left(tx) => tx.max_fee_per_blob_gas(),
Self::Right(tx) => tx.max_fee_per_blob_gas(),
}
}
fn priority_fee_or_price(&self) -> u128 {
match self {
Self::Left(tx) => tx.priority_fee_or_price(),
Self::Right(tx) => tx.priority_fee_or_price(),
}
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match self {
Self::Left(tx) => tx.effective_gas_price(base_fee),
Self::Right(tx) => tx.effective_gas_price(base_fee),
}
}
fn effective_tip_per_gas(&self, base_fee: u64) -> Option<u128> {
match self {
Self::Left(tx) => tx.effective_tip_per_gas(base_fee),
Self::Right(tx) => tx.effective_tip_per_gas(base_fee),
}
}
fn is_dynamic_fee(&self) -> bool {
match self {
Self::Left(tx) => tx.is_dynamic_fee(),
Self::Right(tx) => tx.is_dynamic_fee(),
}
}
fn kind(&self) -> TxKind {
match self {
Self::Left(tx) => tx.kind(),
Self::Right(tx) => tx.kind(),
}
}
fn is_create(&self) -> bool {
match self {
Self::Left(tx) => tx.is_create(),
Self::Right(tx) => tx.is_create(),
}
}
fn to(&self) -> Option<Address> {
match self {
Self::Left(tx) => tx.to(),
Self::Right(tx) => tx.to(),
}
}
fn value(&self) -> U256 {
match self {
Self::Left(tx) => tx.value(),
Self::Right(tx) => tx.value(),
}
}
fn input(&self) -> &Bytes {
match self {
Self::Left(tx) => tx.input(),
Self::Right(tx) => tx.input(),
}
}
fn function_selector(&self) -> Option<&Selector> {
match self {
Self::Left(tx) => tx.function_selector(),
Self::Right(tx) => tx.function_selector(),
}
}
fn access_list(&self) -> Option<&AccessList> {
match self {
Self::Left(tx) => tx.access_list(),
Self::Right(tx) => tx.access_list(),
}
}
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
match self {
Self::Left(tx) => tx.blob_versioned_hashes(),
Self::Right(tx) => tx.blob_versioned_hashes(),
}
}
fn blob_count(&self) -> Option<u64> {
match self {
Self::Left(tx) => tx.blob_count(),
Self::Right(tx) => tx.blob_count(),
}
}
fn blob_gas_used(&self) -> Option<u64> {
match self {
Self::Left(tx) => tx.blob_gas_used(),
Self::Right(tx) => tx.blob_gas_used(),
}
}
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
match self {
Self::Left(tx) => tx.authorization_list(),
Self::Right(tx) => tx.authorization_list(),
}
}
fn authorization_count(&self) -> Option<u64> {
match self {
Self::Left(tx) => tx.authorization_count(),
Self::Right(tx) => tx.authorization_count(),
}
}
}
#[auto_impl(&, &mut, Box)]
pub trait TxHashRef {
fn tx_hash(&self) -> &TxHash;
}
impl<T: TxHashRef> TxHashRef for Recovered<T> {
fn tx_hash(&self) -> &TxHash {
self.inner().tx_hash()
}
}
impl<T: TxHashRef> TxHashRef for alloy_eips::eip2718::WithEncoded<T> {
fn tx_hash(&self) -> &TxHash {
self.value().tx_hash()
}
}
impl<T: TxHashRef + Clone> TxHashRef for Cow<'_, T> {
fn tx_hash(&self) -> &TxHash {
(**self).tx_hash()
}
}
#[cfg(all(test, feature = "serde"))]
mod tests {
use crate::{Signed, TransactionEnvelope, TxEip1559, TxEnvelope, TxType};
use alloy_primitives::Signature;
#[test]
fn test_custom_envelope() {
use serde::{Serialize, Serializer};
fn serialize_with<S: Serializer>(
tx: &Signed<TxEip1559>,
serializer: S,
) -> Result<S::Ok, S::Error> {
#[derive(Serialize)]
struct WithExtra<'a> {
#[serde(flatten)]
inner: &'a Signed<TxEip1559>,
extra: &'a str,
}
WithExtra { inner: tx, extra: "extra" }.serialize(serializer)
}
#[derive(Debug, Clone, TransactionEnvelope)]
#[envelope(alloy_consensus = crate, tx_type_name = MyTxType)]
enum MyEnvelope {
#[envelope(flatten)]
Ethereum(TxEnvelope),
#[envelope(ty = 10)]
MyTx(Signed<TxEip1559>),
#[envelope(ty = 11)]
#[serde(serialize_with = "serialize_with")]
AnotherMyTx(Signed<TxEip1559>),
}
assert_eq!(u8::from(MyTxType::Ethereum(TxType::Eip1559)), 2);
assert_eq!(u8::from(MyTxType::MyTx), 10);
assert_eq!(MyTxType::try_from(2u8).unwrap(), MyTxType::Ethereum(TxType::Eip1559));
assert_eq!(MyTxType::try_from(10u8).unwrap(), MyTxType::MyTx);
let tx = Signed::new_unhashed(
TxEip1559 {
chain_id: 1,
gas_limit: 21000,
max_fee_per_gas: 1000,
max_priority_fee_per_gas: 1000,
..Default::default()
},
Signature::new(Default::default(), Default::default(), Default::default()),
);
let my_tx = serde_json::to_string(&MyEnvelope::MyTx(tx.clone())).unwrap();
let another_my_tx = serde_json::to_string(&MyEnvelope::AnotherMyTx(tx)).unwrap();
assert_eq!(
my_tx,
r#"{"type":"0xa","chainId":"0x1","nonce":"0x0","gas":"0x5208","maxFeePerGas":"0x3e8","maxPriorityFeePerGas":"0x3e8","to":null,"value":"0x0","accessList":[],"input":"0x","r":"0x0","s":"0x0","yParity":"0x0","v":"0x0","hash":"0x2eaaca5609601faae806f5147abb8f51ae91cba12604bedc23a16f2776d5a97b"}"#
);
assert_eq!(
another_my_tx,
r#"{"type":"0xb","chainId":"0x1","nonce":"0x0","gas":"0x5208","maxFeePerGas":"0x3e8","maxPriorityFeePerGas":"0x3e8","to":null,"value":"0x0","accessList":[],"input":"0x","r":"0x0","s":"0x0","yParity":"0x0","v":"0x0","hash":"0x2eaaca5609601faae806f5147abb8f51ae91cba12604bedc23a16f2776d5a97b","extra":"extra"}"#
);
}
}