use alloy_contract::SolCallBuilder;
use alloy_primitives::{Address, B256, Bytes, Signature, TxHash, U256};
use alloy_provider::bindings::IMulticall3;
use alloy_provider::{MULTICALL3_ADDRESS, MulticallItem, Provider};
use alloy_sol_types::{Eip712Domain, SolCall};
use alloy_transport::TransportError;
#[cfg(feature = "telemetry")]
use tracing_core::Level;
use super::contract::{IEIP3009, IX402Permit2Proxy};
use super::error::Eip155ExactError;
use super::signature::{SignedMessage, StructuredSignature};
use super::{Eip3009Payment, Permit2Payment};
use crate::chain::{Eip155MetaTransactionProvider, MetaTransaction};
use crate::exact::X402_EXACT_PERMIT2_PROXY;
pub(super) struct TransferWithAuthorization0Call<P>(
pub TransferWithAuthorizationCall<P, IEIP3009::transferWithAuthorization_0Call, Bytes>,
);
impl<P> std::fmt::Debug for TransferWithAuthorization0Call<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransferWithAuthorization0Call")
.finish_non_exhaustive()
}
}
impl<'a, P: Provider> TransferWithAuthorization0Call<&'a P> {
pub(super) fn new(
contract: &'a IEIP3009::IEIP3009Instance<P>,
payment: &Eip3009Payment,
signature: Bytes,
) -> Self {
let from = payment.from;
let to = payment.to;
let value = payment.value;
let valid_after = U256::from(payment.valid_after.as_secs());
let valid_before = U256::from(payment.valid_before.as_secs());
let nonce = payment.nonce;
let tx = contract.transferWithAuthorization_0(
from,
to,
value,
valid_after,
valid_before,
nonce,
signature.clone(),
);
TransferWithAuthorization0Call(TransferWithAuthorizationCall {
tx,
from,
to,
value,
valid_after,
valid_before,
nonce,
signature,
contract_address: *contract.address(),
})
}
}
pub(super) struct TransferWithAuthorization1Call<P>(
pub TransferWithAuthorizationCall<P, IEIP3009::transferWithAuthorization_1Call, Signature>,
);
impl<P> std::fmt::Debug for TransferWithAuthorization1Call<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TransferWithAuthorization1Call")
.finish_non_exhaustive()
}
}
impl<'a, P: Provider> TransferWithAuthorization1Call<&'a P> {
pub(super) fn new(
contract: &'a IEIP3009::IEIP3009Instance<P>,
payment: &Eip3009Payment,
signature: Signature,
) -> Self {
let from = payment.from;
let to = payment.to;
let value = payment.value;
let valid_after = U256::from(payment.valid_after.as_secs());
let valid_before = U256::from(payment.valid_before.as_secs());
let nonce = payment.nonce;
let v = 27 + u8::from(signature.v());
let r = B256::from(signature.r());
let s = B256::from(signature.s());
let tx = contract.transferWithAuthorization_1(
from,
to,
value,
valid_after,
valid_before,
nonce,
v,
r,
s,
);
TransferWithAuthorization1Call(TransferWithAuthorizationCall {
tx,
from,
to,
value,
valid_after,
valid_before,
nonce,
signature,
contract_address: *contract.address(),
})
}
}
#[allow(
missing_debug_implementations,
reason = "generic type params may not impl Debug"
)]
pub(super) struct TransferWithAuthorizationCall<P, TCall, TSignature> {
pub tx: SolCallBuilder<P, TCall>,
pub from: Address,
pub to: Address,
pub value: U256,
pub valid_after: U256,
pub valid_before: U256,
pub nonce: B256,
pub signature: TSignature,
pub contract_address: Address,
}
#[allow(
clippy::cognitive_complexity,
reason = "settlement logic is inherently complex"
)]
pub(super) async fn settle_payment<P, E>(
provider: &P,
contract: &IEIP3009::IEIP3009Instance<&P::Inner>,
payment: &Eip3009Payment,
eip712_domain: &Eip712Domain,
) -> Result<TxHash, Eip155ExactError>
where
P: Eip155MetaTransactionProvider<Error = E> + Sync,
Eip155ExactError: From<E>,
{
let signed_message = SignedMessage::extract(payment, eip712_domain)?;
let payer = payment.from;
let receipt = match signed_message.signature {
StructuredSignature::EIP6492 {
factory,
factory_calldata,
inner,
original: _,
} => {
let is_deployed = is_contract_deployed(provider.inner(), &payer).await?;
let transfer_call = TransferWithAuthorization0Call::new(contract, payment, inner);
let transfer_call = transfer_call.0;
if is_deployed {
let tx_fut = Eip155MetaTransactionProvider::send_transaction(
provider,
MetaTransaction {
to: transfer_call.tx.target(),
calldata: transfer_call.tx.calldata().clone(),
confirmations: 1,
},
);
traced!(
tx_fut,
transfer_span!(
"call_transferWithAuthorization_0",
transfer_call,
sig_kind = "EIP6492.deployed"
)
)?
} else {
let deployment_call = IMulticall3::Call3 {
allowFailure: true,
target: factory,
callData: factory_calldata,
};
let transfer_with_authorization_call = IMulticall3::Call3 {
allowFailure: false,
target: transfer_call.tx.target(),
callData: transfer_call.tx.calldata().clone(),
};
let aggregate_call = IMulticall3::aggregate3Call {
calls: vec![deployment_call, transfer_with_authorization_call],
};
let tx_fut = Eip155MetaTransactionProvider::send_transaction(
provider,
MetaTransaction {
to: MULTICALL3_ADDRESS,
calldata: aggregate_call.abi_encode().into(),
confirmations: 1,
},
);
traced!(
tx_fut,
transfer_span!(
"call_transferWithAuthorization_0",
transfer_call,
sig_kind = "EIP6492.counterfactual"
)
)?
}
}
StructuredSignature::EIP1271(eip1271_signature) => {
let transfer_call =
TransferWithAuthorization0Call::new(contract, payment, eip1271_signature);
let transfer_call = transfer_call.0;
let tx_fut = Eip155MetaTransactionProvider::send_transaction(
provider,
MetaTransaction {
to: transfer_call.tx.target(),
calldata: transfer_call.tx.calldata().clone(),
confirmations: 1,
},
);
traced!(
tx_fut,
transfer_span!(
"call_transferWithAuthorization_0",
transfer_call,
sig_kind = "EIP1271"
)
)?
}
StructuredSignature::Eoa(signature) => {
let transfer_call = TransferWithAuthorization1Call::new(contract, payment, signature);
let transfer_call = transfer_call.0;
let tx_fut = Eip155MetaTransactionProvider::send_transaction(
provider,
MetaTransaction {
to: transfer_call.tx.target(),
calldata: transfer_call.tx.calldata().clone(),
confirmations: 1,
},
);
traced!(
tx_fut,
transfer_span!(
"call_transferWithAuthorization_1",
transfer_call,
sig_kind = "EOA"
)
)?
}
};
check_receipt(&receipt, "transferWithAuthorization")
}
#[allow(
clippy::cognitive_complexity,
reason = "settlement logic is inherently complex"
)]
pub(super) async fn settle_permit2_payment<P, E>(
provider: &P,
payment: &Permit2Payment,
) -> Result<TxHash, Eip155ExactError>
where
P: Eip155MetaTransactionProvider<Error = E> + Sync,
Eip155ExactError: From<E>,
{
let proxy = IX402Permit2Proxy::new(X402_EXACT_PERMIT2_PROXY, provider.inner());
let permit = IX402Permit2Proxy::Permit {
permitted: IX402Permit2Proxy::TokenPermissions {
token: payment.token,
amount: payment.amount,
},
nonce: payment.nonce,
deadline: payment.deadline,
};
let witness = IX402Permit2Proxy::Witness {
to: payment.to,
validAfter: payment.valid_after,
extra: payment.extra.clone(),
};
let settle_call = proxy.settle(permit, payment.from, witness, payment.signature.clone());
let calldata = settle_call.calldata().clone();
let tx_fut = Eip155MetaTransactionProvider::send_transaction(
provider,
MetaTransaction {
to: X402_EXACT_PERMIT2_PROXY,
calldata,
confirmations: 1,
},
);
let receipt = traced!(
tx_fut,
tracing::info_span!("settle_permit2",
from = %payment.from,
to = %payment.to,
token = %payment.token,
amount = %payment.amount,
otel.kind = "client",
)
)?;
check_receipt(&receipt, "Permit2 settle")
}
fn check_receipt(
receipt: &alloy_rpc_types_eth::TransactionReceipt,
label: &str,
) -> Result<TxHash, Eip155ExactError> {
if receipt.status() {
#[cfg(feature = "telemetry")]
tracing::event!(Level::INFO, status = "ok", tx = %receipt.transaction_hash, "{label} succeeded");
Ok(receipt.transaction_hash)
} else {
#[cfg(feature = "telemetry")]
tracing::event!(Level::WARN, status = "failed", tx = %receipt.transaction_hash, "{label} failed");
Err(Eip155ExactError::TransactionReverted(
receipt.transaction_hash,
))
}
}
async fn is_contract_deployed<P: Provider>(
provider: &P,
address: &Address,
) -> Result<bool, TransportError> {
let bytes_fut = provider.get_code_at(*address).into_future();
let bytes = traced!(
bytes_fut,
tracing::info_span!("get_code_at",
address = %address,
otel.kind = "client",
)
)?;
Ok(!bytes.is_empty())
}