use crate::{UnknownTxEnvelope, UnknownTypedTransaction};
use alloy_consensus::{
error::ValueError, transaction::Either, SignableTransaction, Signed,
Transaction as TransactionTrait, TxEip1559, TxEip2930, TxEip4844Variant, TxEip7702, TxEnvelope,
TxLegacy, Typed2718, TypedTransaction,
};
use alloy_eips::{
eip2718::{Decodable2718, Encodable2718},
eip7702::SignedAuthorization,
};
use alloy_primitives::{bytes::BufMut, Bytes, ChainId, Signature, B256, U256};
use alloy_rpc_types_eth::{AccessList, TransactionRequest};
use alloy_serde::WithOtherFields;
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[doc(alias = "AnyTypedTx")]
pub enum AnyTypedTransaction {
Ethereum(TypedTransaction),
Unknown(UnknownTypedTransaction),
}
#[cold]
#[track_caller]
fn panic_unknown_transaction_signing(ty: u8) -> ! {
panic!("unknown transaction type 0x{ty:02x} cannot be signed via AnyTypedTransaction")
}
#[cold]
#[track_caller]
fn panic_unknown_transaction_encoding(ty: u8) -> ! {
panic!(
"Attempted to encode unknown transaction type 0x{ty:02x}. This is not a bug in alloy. To encode or decode unknown transaction types, use a custom Transaction type and a custom Network implementation. See https://docs.rs/alloy-network/latest/alloy_network/ for network documentation."
)
}
impl From<UnknownTypedTransaction> for AnyTypedTransaction {
fn from(value: UnknownTypedTransaction) -> Self {
Self::Unknown(value)
}
}
impl From<TypedTransaction> for AnyTypedTransaction {
fn from(value: TypedTransaction) -> Self {
Self::Ethereum(value)
}
}
impl From<AnyTxEnvelope> for AnyTypedTransaction {
fn from(value: AnyTxEnvelope) -> Self {
match value {
AnyTxEnvelope::Ethereum(tx) => Self::Ethereum(tx.into()),
AnyTxEnvelope::Unknown(UnknownTxEnvelope { inner, .. }) => inner.into(),
}
}
}
impl From<AnyTypedTransaction> for WithOtherFields<TransactionRequest> {
fn from(value: AnyTypedTransaction) -> Self {
match value {
AnyTypedTransaction::Ethereum(tx) => Self::new(tx.into()),
AnyTypedTransaction::Unknown(UnknownTypedTransaction { ty, mut fields, .. }) => {
fields.insert("type".to_string(), serde_json::Value::Number(ty.0.into()));
Self { inner: Default::default(), other: fields }
}
}
}
}
impl From<AnyTxEnvelope> for WithOtherFields<TransactionRequest> {
fn from(value: AnyTxEnvelope) -> Self {
AnyTypedTransaction::from(value).into()
}
}
impl TransactionTrait for AnyTypedTransaction {
#[inline]
fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Ethereum(inner) => inner.chain_id(),
Self::Unknown(inner) => inner.chain_id(),
}
}
#[inline]
fn nonce(&self) -> u64 {
match self {
Self::Ethereum(inner) => inner.nonce(),
Self::Unknown(inner) => inner.nonce(),
}
}
#[inline]
fn gas_limit(&self) -> u64 {
match self {
Self::Ethereum(inner) => inner.gas_limit(),
Self::Unknown(inner) => inner.gas_limit(),
}
}
#[inline]
fn gas_price(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.gas_price(),
Self::Unknown(inner) => inner.gas_price(),
}
}
#[inline]
fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Ethereum(inner) => inner.max_fee_per_gas(),
Self::Unknown(inner) => inner.max_fee_per_gas(),
}
}
#[inline]
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
}
}
#[inline]
fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
}
}
#[inline]
fn priority_fee_or_price(&self) -> u128 {
self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match self {
Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
Self::Unknown(inner) => inner.effective_gas_price(base_fee),
}
}
#[inline]
fn is_dynamic_fee(&self) -> bool {
match self {
Self::Ethereum(inner) => inner.is_dynamic_fee(),
Self::Unknown(inner) => inner.is_dynamic_fee(),
}
}
fn kind(&self) -> alloy_primitives::TxKind {
match self {
Self::Ethereum(inner) => inner.kind(),
Self::Unknown(inner) => inner.kind(),
}
}
#[inline]
fn is_create(&self) -> bool {
match self {
Self::Ethereum(inner) => inner.is_create(),
Self::Unknown(inner) => inner.is_create(),
}
}
#[inline]
fn value(&self) -> U256 {
match self {
Self::Ethereum(inner) => inner.value(),
Self::Unknown(inner) => inner.value(),
}
}
#[inline]
fn input(&self) -> &Bytes {
match self {
Self::Ethereum(inner) => inner.input(),
Self::Unknown(inner) => inner.input(),
}
}
#[inline]
fn access_list(&self) -> Option<&AccessList> {
match self {
Self::Ethereum(inner) => inner.access_list(),
Self::Unknown(inner) => inner.access_list(),
}
}
#[inline]
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
match self {
Self::Ethereum(inner) => inner.blob_versioned_hashes(),
Self::Unknown(inner) => inner.blob_versioned_hashes(),
}
}
#[inline]
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
match self {
Self::Ethereum(inner) => inner.authorization_list(),
Self::Unknown(inner) => inner.authorization_list(),
}
}
}
impl Typed2718 for AnyTypedTransaction {
fn ty(&self) -> u8 {
match self {
Self::Ethereum(inner) => inner.ty(),
Self::Unknown(inner) => inner.ty(),
}
}
}
impl SignableTransaction<Signature> for AnyTypedTransaction {
fn set_chain_id(&mut self, chain_id: ChainId) {
match self {
Self::Ethereum(typed_tx) => typed_tx.set_chain_id(chain_id),
Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
}
}
fn encode_for_signing(&self, out: &mut dyn BufMut) {
match self {
Self::Ethereum(typed_tx) => typed_tx.encode_for_signing(out),
Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
}
}
fn payload_len_for_signature(&self) -> usize {
match self {
Self::Ethereum(typed_tx) => typed_tx.payload_len_for_signature(),
Self::Unknown(unknown_tx) => panic_unknown_transaction_signing(unknown_tx.ty()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
#[doc(alias = "AnyTransactionEnvelope")]
pub enum AnyTxEnvelope {
Ethereum(TxEnvelope),
Unknown(UnknownTxEnvelope),
}
impl From<Signed<AnyTypedTransaction>> for AnyTxEnvelope {
fn from(value: Signed<AnyTypedTransaction>) -> Self {
let sig = *value.signature();
let tx = value.strip_signature();
match tx {
AnyTypedTransaction::Ethereum(typed_tx) => Self::Ethereum(match typed_tx {
TypedTransaction::Legacy(tx) => TxEnvelope::Legacy(tx.into_signed(sig)),
TypedTransaction::Eip2930(tx) => TxEnvelope::Eip2930(tx.into_signed(sig)),
TypedTransaction::Eip1559(tx) => TxEnvelope::Eip1559(tx.into_signed(sig)),
TypedTransaction::Eip4844(tx) => TxEnvelope::Eip4844(tx.into_signed(sig)),
TypedTransaction::Eip7702(tx) => TxEnvelope::Eip7702(tx.into_signed(sig)),
}),
AnyTypedTransaction::Unknown(unknown_tx) => {
panic_unknown_transaction_signing(unknown_tx.ty())
}
}
}
}
impl AnyTxEnvelope {
pub const fn is_ethereum(&self) -> bool {
matches!(self, Self::Ethereum(_))
}
pub const fn is_unknown(&self) -> bool {
matches!(self, Self::Unknown(_))
}
pub const fn as_envelope(&self) -> Option<&TxEnvelope> {
match self {
Self::Ethereum(inner) => Some(inner),
Self::Unknown(_) => None,
}
}
pub const fn as_unknown(&self) -> Option<&UnknownTxEnvelope> {
match self {
Self::Unknown(inner) => Some(inner),
Self::Ethereum(_) => None,
}
}
pub fn try_into_envelope(self) -> Result<TxEnvelope, ValueError<Self>> {
match self {
Self::Ethereum(inner) => Ok(inner),
this => Err(ValueError::new_static(this, "unknown transaction envelope")),
}
}
pub fn try_into_unknown(self) -> Result<UnknownTxEnvelope, Self> {
match self {
Self::Unknown(inner) => Ok(inner),
this => Err(this),
}
}
pub fn try_into_either<T>(self) -> Result<Either<TxEnvelope, T>, T::Error>
where
T: TryFrom<UnknownTxEnvelope>,
{
self.try_map_unknown(|inner| inner.try_into())
}
pub fn try_map_unknown<T, E>(
self,
f: impl FnOnce(UnknownTxEnvelope) -> Result<T, E>,
) -> Result<Either<TxEnvelope, T>, E> {
match self {
Self::Ethereum(tx) => Ok(Either::Left(tx)),
Self::Unknown(tx) => Ok(Either::Right(f(tx)?)),
}
}
pub const fn as_legacy(&self) -> Option<&Signed<TxLegacy>> {
match self.as_envelope() {
Some(TxEnvelope::Legacy(tx)) => Some(tx),
_ => None,
}
}
pub const fn as_eip2930(&self) -> Option<&Signed<TxEip2930>> {
match self.as_envelope() {
Some(TxEnvelope::Eip2930(tx)) => Some(tx),
_ => None,
}
}
pub const fn as_eip1559(&self) -> Option<&Signed<TxEip1559>> {
match self.as_envelope() {
Some(TxEnvelope::Eip1559(tx)) => Some(tx),
_ => None,
}
}
pub const fn as_eip4844(&self) -> Option<&Signed<TxEip4844Variant>> {
match self.as_envelope() {
Some(TxEnvelope::Eip4844(tx)) => Some(tx),
_ => None,
}
}
pub const fn as_eip7702(&self) -> Option<&Signed<TxEip7702>> {
match self.as_envelope() {
Some(TxEnvelope::Eip7702(tx)) => Some(tx),
_ => None,
}
}
#[inline]
pub const fn is_legacy(&self) -> bool {
matches!(self.as_envelope(), Some(TxEnvelope::Legacy(_)))
}
#[inline]
pub const fn is_eip2930(&self) -> bool {
matches!(self.as_envelope(), Some(TxEnvelope::Eip2930(_)))
}
#[inline]
pub const fn is_eip1559(&self) -> bool {
matches!(self.as_envelope(), Some(TxEnvelope::Eip1559(_)))
}
#[inline]
pub const fn is_eip4844(&self) -> bool {
matches!(self.as_envelope(), Some(TxEnvelope::Eip4844(_)))
}
#[inline]
pub const fn is_eip7702(&self) -> bool {
matches!(self.as_envelope(), Some(TxEnvelope::Eip7702(_)))
}
}
impl Typed2718 for AnyTxEnvelope {
fn ty(&self) -> u8 {
match self {
Self::Ethereum(inner) => inner.ty(),
Self::Unknown(inner) => inner.ty(),
}
}
}
impl Encodable2718 for AnyTxEnvelope {
fn encode_2718_len(&self) -> usize {
match self {
Self::Ethereum(t) => t.encode_2718_len(),
Self::Unknown(inner) => panic_unknown_transaction_encoding(inner.ty()),
}
}
#[track_caller]
fn encode_2718(&self, out: &mut dyn alloy_primitives::bytes::BufMut) {
match self {
Self::Ethereum(t) => t.encode_2718(out),
Self::Unknown(inner) => panic_unknown_transaction_encoding(inner.ty()),
}
}
fn trie_hash(&self) -> B256 {
match self {
Self::Ethereum(tx) => tx.trie_hash(),
Self::Unknown(inner) => inner.hash,
}
}
}
impl Decodable2718 for AnyTxEnvelope {
fn typed_decode(ty: u8, buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
TxEnvelope::typed_decode(ty, buf).map(Self::Ethereum)
}
fn fallback_decode(buf: &mut &[u8]) -> alloy_eips::eip2718::Eip2718Result<Self> {
TxEnvelope::fallback_decode(buf).map(Self::Ethereum)
}
}
impl TransactionTrait for AnyTxEnvelope {
#[inline]
fn chain_id(&self) -> Option<ChainId> {
match self {
Self::Ethereum(inner) => inner.chain_id(),
Self::Unknown(inner) => inner.chain_id(),
}
}
#[inline]
fn nonce(&self) -> u64 {
match self {
Self::Ethereum(inner) => inner.nonce(),
Self::Unknown(inner) => inner.nonce(),
}
}
#[inline]
fn gas_limit(&self) -> u64 {
match self {
Self::Ethereum(inner) => inner.gas_limit(),
Self::Unknown(inner) => inner.gas_limit(),
}
}
#[inline]
fn gas_price(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.gas_price(),
Self::Unknown(inner) => inner.gas_price(),
}
}
#[inline]
fn max_fee_per_gas(&self) -> u128 {
match self {
Self::Ethereum(inner) => inner.max_fee_per_gas(),
Self::Unknown(inner) => inner.max_fee_per_gas(),
}
}
#[inline]
fn max_priority_fee_per_gas(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.max_priority_fee_per_gas(),
Self::Unknown(inner) => inner.max_priority_fee_per_gas(),
}
}
#[inline]
fn max_fee_per_blob_gas(&self) -> Option<u128> {
match self {
Self::Ethereum(inner) => inner.max_fee_per_blob_gas(),
Self::Unknown(inner) => inner.max_fee_per_blob_gas(),
}
}
#[inline]
fn priority_fee_or_price(&self) -> u128 {
self.max_priority_fee_per_gas().or_else(|| self.gas_price()).unwrap_or_default()
}
fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
match self {
Self::Ethereum(inner) => inner.effective_gas_price(base_fee),
Self::Unknown(inner) => inner.effective_gas_price(base_fee),
}
}
#[inline]
fn is_dynamic_fee(&self) -> bool {
match self {
Self::Ethereum(inner) => inner.is_dynamic_fee(),
Self::Unknown(inner) => inner.is_dynamic_fee(),
}
}
fn kind(&self) -> alloy_primitives::TxKind {
match self {
Self::Ethereum(inner) => inner.kind(),
Self::Unknown(inner) => inner.kind(),
}
}
#[inline]
fn is_create(&self) -> bool {
match self {
Self::Ethereum(inner) => inner.is_create(),
Self::Unknown(inner) => inner.is_create(),
}
}
#[inline]
fn value(&self) -> U256 {
match self {
Self::Ethereum(inner) => inner.value(),
Self::Unknown(inner) => inner.value(),
}
}
#[inline]
fn input(&self) -> &Bytes {
match self {
Self::Ethereum(inner) => inner.input(),
Self::Unknown(inner) => inner.input(),
}
}
#[inline]
fn access_list(&self) -> Option<&AccessList> {
match self {
Self::Ethereum(inner) => inner.access_list(),
Self::Unknown(inner) => inner.access_list(),
}
}
#[inline]
fn blob_versioned_hashes(&self) -> Option<&[B256]> {
match self {
Self::Ethereum(inner) => inner.blob_versioned_hashes(),
Self::Unknown(inner) => inner.blob_versioned_hashes(),
}
}
#[inline]
fn authorization_list(&self) -> Option<&[SignedAuthorization]> {
match self {
Self::Ethereum(inner) => inner.authorization_list(),
Self::Unknown(inner) => inner.authorization_list(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::AnyTxType;
use std::str::FromStr;
fn unknown_inner_tx() -> UnknownTypedTransaction {
UnknownTypedTransaction {
ty: AnyTxType(0x7e),
fields: Default::default(),
memo: Default::default(),
}
}
fn unknown_tx() -> AnyTypedTransaction {
AnyTypedTransaction::Unknown(unknown_inner_tx())
}
fn unknown_envelope() -> AnyTxEnvelope {
AnyTxEnvelope::Unknown(UnknownTxEnvelope { hash: B256::ZERO, inner: unknown_inner_tx() })
}
#[test]
#[should_panic(expected = "cannot be signed via AnyTypedTransaction")]
fn unknown_transaction_signature_hash_panics() {
let tx = unknown_tx();
let _ = tx.signature_hash();
}
#[test]
#[should_panic(expected = "cannot be signed via AnyTypedTransaction")]
fn signed_unknown_transaction_cannot_convert_to_envelope() {
let tx = unknown_tx();
let sig = Signature::from_str(
"48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353efffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c8041b",
)
.unwrap();
let _: AnyTxEnvelope = tx.into_signed(sig).into();
}
#[test]
#[should_panic(expected = "Attempted to encode unknown transaction type")]
fn unknown_transaction_encode_2718_len_panics() {
let envelope = unknown_envelope();
let _ = envelope.encode_2718_len();
}
#[test]
#[should_panic(expected = "Attempted to encode unknown transaction type")]
fn unknown_transaction_encoded_2718_panics() {
let envelope = unknown_envelope();
let _ = envelope.encoded_2718();
}
}