use alloy_primitives::TxHash;
use alloy_transport::TransportError;
use r402_protocol::error::{FacilitatorError, VerificationError};
use crate::chain::MetaTransactionSendError;
use crate::signature::StructuredSignatureFormatError;
#[derive(Debug, thiserror::Error)]
pub enum Eip155ExactError {
#[error(transparent)]
Transport(#[from] TransportError),
#[error(transparent)]
PendingTransaction(#[from] alloy_provider::PendingTransactionError),
#[error("Transaction {0} reverted")]
TransactionReverted(TxHash),
#[error("Transaction {0} missing matching Transfer event")]
TransferEventMismatch(TxHash),
#[error("receipt wait failed for {hash}")]
ReceiptWait {
hash: TxHash,
#[source]
source: alloy_provider::PendingTransactionError,
},
#[error("Contract call failed: {0}")]
ContractCall(String),
#[error(transparent)]
PaymentVerification(#[from] VerificationError),
#[error("erc20_approval_tx_failed: {0}")]
Erc20ApprovalTxFailed(TxHash),
#[error("erc20_approval_funding_failed")]
Erc20ApprovalFundingFailed,
#[error("chain RPC missing a required method")]
RpcMethodMissing,
}
impl From<Eip155ExactError> for FacilitatorError {
fn from(value: Eip155ExactError) -> Self {
match value {
Eip155ExactError::RpcMethodMissing => {
Self::transport(r402_protocol::error::FacilitatorTransportKind::RpcMethodMissing)
}
Eip155ExactError::Erc20ApprovalTxFailed(hash) => {
Self::Onchain(format!("erc20_approval_tx_failed: {hash}"))
}
Eip155ExactError::Erc20ApprovalFundingFailed
| Eip155ExactError::Transport(_)
| Eip155ExactError::PendingTransaction(_)
| Eip155ExactError::TransactionReverted(_)
| Eip155ExactError::TransferEventMismatch(_)
| Eip155ExactError::ReceiptWait { .. }
| Eip155ExactError::ContractCall(_) => Self::Onchain(value.to_string()),
Eip155ExactError::PaymentVerification(e) => Self::Verification(e),
}
}
}
impl From<StructuredSignatureFormatError> for Eip155ExactError {
fn from(e: StructuredSignatureFormatError) -> Self {
Self::PaymentVerification(VerificationError::InvalidSignature(e.to_string()))
}
}
impl From<MetaTransactionSendError> for Eip155ExactError {
fn from(e: MetaTransactionSendError) -> Self {
match e {
MetaTransactionSendError::Transport(e) => Self::Transport(e),
MetaTransactionSendError::PendingTransaction(e) => Self::PendingTransaction(e),
MetaTransactionSendError::ReceiptWait { hash, source } => {
Self::ReceiptWait { hash, source }
}
MetaTransactionSendError::Custom(e) => Self::ContractCall(e),
}
}
}
impl From<alloy_provider::MulticallError> for Eip155ExactError {
fn from(e: alloy_provider::MulticallError) -> Self {
match e {
alloy_provider::MulticallError::ValueTx
| alloy_provider::MulticallError::DecodeError(_)
| alloy_provider::MulticallError::NoReturnData
| alloy_provider::MulticallError::CallFailed(_) => {
Self::PaymentVerification(VerificationError::SimulationFailed(e.to_string()))
}
alloy_provider::MulticallError::TransportError(transport_error) => {
Self::Transport(transport_error)
}
}
}
}
impl From<alloy_contract::Error> for Eip155ExactError {
fn from(e: alloy_contract::Error) -> Self {
match e {
alloy_contract::Error::UnknownFunction(_)
| alloy_contract::Error::UnknownSelector(_)
| alloy_contract::Error::NotADeploymentTransaction
| alloy_contract::Error::ContractNotDeployed
| alloy_contract::Error::ZeroData(_, _)
| alloy_contract::Error::AbiError(_) => Self::ContractCall(e.to_string()),
alloy_contract::Error::TransportError(e) => Self::Transport(e),
alloy_contract::Error::PendingTransactionError(e) => Self::PendingTransaction(e),
}
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::TxHash;
use r402_protocol::error::{FacilitatorError, FacilitatorTransportKind};
use super::Eip155ExactError;
#[test]
fn rpc_method_missing_maps_to_transport_not_onchain() {
let err = FacilitatorError::from(Eip155ExactError::RpcMethodMissing);
assert!(err.is_transport());
assert!(
matches!(
err,
FacilitatorError::Transport {
kind: FacilitatorTransportKind::RpcMethodMissing
}
),
"got {err:?}"
);
assert!(
err.as_payment_problem().is_none(),
"RpcMethodMissing must not become a 402 payment problem"
);
}
#[test]
fn erc20_approval_tx_failed_maps_to_onchain_with_hash() {
let hash = TxHash::repeat_byte(0xAB);
let err = FacilitatorError::from(Eip155ExactError::Erc20ApprovalTxFailed(hash));
match err {
FacilitatorError::Onchain(message) => {
assert!(
message.contains("erc20_approval_tx_failed"),
"got {message}"
);
assert!(message.contains(&hash.to_string()), "got {message}");
}
other => panic!("expected Onchain, got {other:?}"),
}
}
}