use super::signer::NetworkWallet;
use crate::Network;
pub use alloy_network_primitives::{TransactionBuilder4844, TransactionBuilder7702};
use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256};
use alloy_rpc_types_eth::{AccessList, TransactionInputKind};
use alloy_sol_types::SolCall;
use futures_utils_wasm::impl_future;
pub type BuildResult<T, N> = Result<T, UnbuiltTransactionError<N>>;
#[derive(Debug, thiserror::Error)]
#[error("Failed to build transaction: {error}")]
pub struct UnbuiltTransactionError<N: Network> {
pub request: N::TransactionRequest,
#[source]
pub error: TransactionBuilderError<N>,
}
#[derive(Debug, thiserror::Error)]
pub enum TransactionBuilderError<N: Network> {
#[error("{0} transaction can't be built due to missing keys: {1:?}")]
InvalidTransactionRequest(N::TxType, Vec<&'static str>),
#[error("Signer cannot produce signature type required for transaction")]
UnsupportedSignatureType,
#[error(transparent)]
Signer(#[from] alloy_signer::Error),
#[error("{0}")]
Custom(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
}
impl<N: Network> TransactionBuilderError<N> {
pub fn custom<E>(e: E) -> Self
where
E: std::error::Error + Send + Sync + 'static,
{
Self::Custom(Box::new(e))
}
pub const fn into_unbuilt(self, request: N::TransactionRequest) -> UnbuiltTransactionError<N> {
UnbuiltTransactionError { request, error: self }
}
}
#[doc(alias = "TxBuilder")]
pub trait TransactionBuilder: Default + Sized + Send + Sync + 'static {
fn chain_id(&self) -> Option<ChainId>;
fn set_chain_id(&mut self, chain_id: ChainId);
fn with_chain_id(mut self, chain_id: ChainId) -> Self {
self.set_chain_id(chain_id);
self
}
fn nonce(&self) -> Option<u64>;
fn set_nonce(&mut self, nonce: u64);
fn take_nonce(&mut self) -> Option<u64>;
fn with_nonce(mut self, nonce: u64) -> Self {
self.set_nonce(nonce);
self
}
fn without_nonce(mut self) -> Self {
self.take_nonce();
self
}
fn input(&self) -> Option<&Bytes>;
fn set_input<T: Into<Bytes>>(&mut self, input: T);
fn with_input<T: Into<Bytes>>(mut self, input: T) -> Self {
self.set_input(input);
self
}
fn set_input_kind<T: Into<Bytes>>(&mut self, input: T, _: TransactionInputKind) {
self.set_input(input);
}
fn with_input_kind<T: Into<Bytes>>(mut self, input: T, kind: TransactionInputKind) -> Self {
self.set_input_kind(input, kind);
self
}
fn from(&self) -> Option<Address>;
fn set_from(&mut self, from: Address);
fn with_from(mut self, from: Address) -> Self {
self.set_from(from);
self
}
fn kind(&self) -> Option<TxKind>;
fn clear_kind(&mut self);
fn set_kind(&mut self, kind: TxKind);
fn with_kind(mut self, kind: TxKind) -> Self {
self.set_kind(kind);
self
}
fn to(&self) -> Option<Address> {
if let Some(TxKind::Call(addr)) = self.kind() {
return Some(addr);
}
None
}
fn set_to(&mut self, to: Address) {
self.set_kind(to.into());
}
fn with_to(mut self, to: Address) -> Self {
self.set_to(to);
self
}
fn set_create(&mut self) {
self.set_kind(TxKind::Create);
}
fn into_create(mut self) -> Self {
self.set_create();
self
}
fn set_deploy_code<T: Into<Bytes>>(&mut self, code: T) {
self.set_input(code.into());
self.set_create()
}
fn with_deploy_code<T: Into<Bytes>>(mut self, code: T) -> Self {
self.set_deploy_code(code);
self
}
fn set_call<T: SolCall>(&mut self, t: &T) {
self.set_input(t.abi_encode());
if matches!(self.kind(), Some(TxKind::Create)) {
self.clear_kind();
}
}
fn with_call<T: SolCall>(mut self, t: &T) -> Self {
self.set_call(t);
self
}
fn calculate_create_address(&self) -> Option<Address> {
if !self.kind().is_some_and(|to| to.is_create()) {
return None;
}
let from = self.from()?;
let nonce = self.nonce()?;
Some(from.create(nonce))
}
fn value(&self) -> Option<U256>;
fn set_value(&mut self, value: U256);
fn with_value(mut self, value: U256) -> Self {
self.set_value(value);
self
}
fn gas_price(&self) -> Option<u128>;
fn set_gas_price(&mut self, gas_price: u128);
fn with_gas_price(mut self, gas_price: u128) -> Self {
self.set_gas_price(gas_price);
self
}
fn max_fee_per_gas(&self) -> Option<u128>;
fn set_max_fee_per_gas(&mut self, max_fee_per_gas: u128);
fn with_max_fee_per_gas(mut self, max_fee_per_gas: u128) -> Self {
self.set_max_fee_per_gas(max_fee_per_gas);
self
}
fn max_priority_fee_per_gas(&self) -> Option<u128>;
fn set_max_priority_fee_per_gas(&mut self, max_priority_fee_per_gas: u128);
fn with_max_priority_fee_per_gas(mut self, max_priority_fee_per_gas: u128) -> Self {
self.set_max_priority_fee_per_gas(max_priority_fee_per_gas);
self
}
fn gas_limit(&self) -> Option<u64>;
fn set_gas_limit(&mut self, gas_limit: u64);
fn with_gas_limit(mut self, gas_limit: u64) -> Self {
self.set_gas_limit(gas_limit);
self
}
fn access_list(&self) -> Option<&AccessList>;
fn set_access_list(&mut self, access_list: AccessList);
fn with_access_list(mut self, access_list: AccessList) -> Self {
self.set_access_list(access_list);
self
}
fn apply<F>(self, f: F) -> Self
where
F: FnOnce(Self) -> Self,
{
f(self)
}
fn try_apply<F, E>(self, f: F) -> Result<Self, E>
where
F: FnOnce(Self) -> Result<Self, E>,
{
f(self)
}
}
#[doc(alias = "NetworkTxBuilder")]
pub trait NetworkTransactionBuilder<N: Network>: TransactionBuilder {
fn can_submit(&self) -> bool;
fn can_build(&self) -> bool;
fn complete_type(&self, ty: N::TxType) -> Result<(), Vec<&'static str>>;
fn complete_preferred(&self) -> Result<(), Vec<&'static str>> {
self.complete_type(self.output_tx_type())
}
fn assert_preferred(&self, ty: N::TxType) {
debug_assert_eq!(self.output_tx_type(), ty);
}
fn assert_preferred_chained(self, ty: N::TxType) -> Self {
self.assert_preferred(ty);
self
}
#[doc(alias = "output_transaction_type")]
fn output_tx_type(&self) -> N::TxType;
#[doc(alias = "output_transaction_type_checked")]
fn output_tx_type_checked(&self) -> Option<N::TxType>;
fn prep_for_submission(&mut self);
fn build_unsigned(self) -> BuildResult<N::UnsignedTx, N>;
fn build<W: NetworkWallet<N>>(
self,
wallet: &W,
) -> impl_future!(<Output = Result<N::TxEnvelope, TransactionBuilderError<N>>>);
}