use alloy_chains::Chain;
use alloy_network::AnyTransactionReceipt;
use alloy_primitives::{utils::format_units, TxHash, U256};
use alloy_provider::{PendingTransactionBuilder, PendingTransactionError, Provider, WatchTxError};
use eyre::Result;
use foundry_common::{provider::RetryProvider, shell};
use std::time::Duration;
pub enum TxStatus {
Dropped,
Success(AnyTransactionReceipt),
Revert(AnyTransactionReceipt),
}
impl From<AnyTransactionReceipt> for TxStatus {
fn from(receipt: AnyTransactionReceipt) -> Self {
if !receipt.inner.inner.inner.receipt.status.coerce_status() {
Self::Revert(receipt)
} else {
Self::Success(receipt)
}
}
}
pub async fn check_tx_status(
provider: &RetryProvider,
hash: TxHash,
timeout: u64,
) -> (TxHash, Result<TxStatus, eyre::Report>) {
let result = async move {
let receipt_opt = provider.get_transaction_receipt(hash).await?;
if let Some(receipt) = receipt_opt {
return Ok(receipt.into());
}
loop {
match PendingTransactionBuilder::new(provider.clone(), hash)
.with_timeout(Some(Duration::from_secs(timeout)))
.get_receipt()
.await
{
Ok(receipt) => return Ok(receipt.into()),
Err(PendingTransactionError::TxWatcher(WatchTxError::Timeout)) => {}
Err(e) => return Err(e.into()),
}
if provider.get_transaction_by_hash(hash).await?.is_some() {
trace!("tx is still known to the node, waiting for receipt");
} else {
trace!("eth_getTransactionByHash returned null, assuming dropped");
break
}
}
Ok(TxStatus::Dropped)
}
.await;
(hash, result)
}
pub fn format_receipt(chain: Chain, receipt: &AnyTransactionReceipt) -> String {
let gas_used = receipt.gas_used;
let gas_price = receipt.effective_gas_price;
let block_number = receipt.block_number.unwrap_or_default();
let success = receipt.inner.inner.inner.receipt.status.coerce_status();
if shell::is_json() {
let _ = sh_println!(
"{}",
serde_json::json!({
"chain": chain,
"status": if success {
"success"
} else {
"failed"
},
"tx_hash": receipt.transaction_hash,
"contract_address": receipt.contract_address.map(|addr| addr.to_string()),
"block_number": block_number,
"gas_used": gas_used,
"gas_price": gas_price,
})
);
String::new()
} else {
format!(
"\n##### {chain}\n{status} Hash: {tx_hash:?}{contract_address}\nBlock: {block_number}\n{gas}\n\n",
status = if success { "✅ [Success]" } else { "❌ [Failed]" },
tx_hash = receipt.transaction_hash,
contract_address = if let Some(addr) = &receipt.contract_address {
format!("\nContract Address: {}", addr.to_checksum(None))
} else {
String::new()
},
gas = if gas_price == 0 {
format!("Gas Used: {gas_used}")
} else {
let paid = format_units(gas_used.saturating_mul(gas_price), 18)
.unwrap_or_else(|_| "N/A".into());
let gas_price =
format_units(U256::from(gas_price), 9).unwrap_or_else(|_| "N/A".into());
format!(
"Paid: {} ETH ({gas_used} gas * {} gwei)",
paid.trim_end_matches('0'),
gas_price.trim_end_matches('0').trim_end_matches('.')
)
},
)
}
}