use std::collections::HashMap;
use alloy_primitives::{Address, B256, Bytes, U256, address};
use alloy_provider::Provider;
use r402::chain::ChainProvider;
use r402::facilitator::{BoxFuture, Facilitator, FacilitatorError};
use r402::proto;
use r402::proto::UnixTimestamp;
use r402::proto::v2;
use r402::scheme::{SchemeBuilder, SchemeId};
use crate::chain::Eip155MetaTransactionProvider;
use crate::exact::types;
use crate::exact::{Eip155Exact, ExactPayload, ExactScheme};
macro_rules! traced {
($fut:expr, $span:expr) => {{
#[cfg(feature = "telemetry")]
{
use tracing::Instrument;
$fut.instrument($span).await
}
#[cfg(not(feature = "telemetry"))]
{
$fut.await
}
}};
}
macro_rules! transfer_span {
($name:expr, $call:expr $(, $key:ident = $val:expr)*) => {
tracing::info_span!($name,
from = %$call.from,
to = %$call.to,
value = %$call.value,
valid_after = %$call.valid_after,
valid_before = %$call.valid_before,
nonce = %$call.nonce,
signature = %$call.signature,
token_contract = %$call.contract_address,
$($key = $val,)*
otel.kind = "client",
)
};
}
mod contract;
mod error;
mod settle;
mod signature;
mod verify;
pub use error::Eip155ExactError;
use settle::{settle_payment, settle_permit2_payment};
pub use signature::StructuredSignatureFormatError;
use verify::{verify_payment, verify_permit2_payment};
pub const VALIDATOR_ADDRESS: Address = address!("0xdAcD51A54883eb67D95FAEb2BBfdC4a9a6BD2a3B");
#[derive(Debug)]
pub struct Eip3009Payment {
pub from: Address,
pub to: Address,
pub value: U256,
pub valid_after: UnixTimestamp,
pub valid_before: UnixTimestamp,
pub nonce: B256,
pub signature: Bytes,
}
#[derive(Debug)]
pub struct Permit2Payment {
pub from: Address,
pub to: Address,
pub token: Address,
pub amount: U256,
pub spender: Address,
pub nonce: U256,
pub deadline: U256,
pub valid_after: U256,
pub extra: Bytes,
pub signature: Bytes,
}
const DEFAULT_CLOCK_SKEW_TOLERANCE: u64 = 30;
pub struct Eip155ExactFacilitator<P> {
provider: P,
clock_skew_tolerance: u64,
}
impl<P> Eip155ExactFacilitator<P> {
pub const fn new(provider: P) -> Self {
Self {
provider,
clock_skew_tolerance: DEFAULT_CLOCK_SKEW_TOLERANCE,
}
}
#[must_use]
pub const fn with_clock_skew_tolerance(mut self, seconds: u64) -> Self {
self.clock_skew_tolerance = seconds;
self
}
}
impl<P> std::fmt::Debug for Eip155ExactFacilitator<P> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Eip155ExactFacilitator")
.finish_non_exhaustive()
}
}
impl<P> SchemeBuilder<P> for Eip155Exact
where
P: Eip155MetaTransactionProvider + ChainProvider + Send + Sync + 'static,
Eip155ExactError: From<P::Error>,
{
fn build(
&self,
provider: P,
_config: Option<serde_json::Value>,
) -> Result<Box<dyn Facilitator>, Box<dyn std::error::Error>> {
Ok(Box::new(Eip155ExactFacilitator::new(provider)))
}
}
impl<P> Facilitator for Eip155ExactFacilitator<P>
where
P: Eip155MetaTransactionProvider + ChainProvider + Send + Sync,
P::Inner: Provider,
Eip155ExactError: From<P::Error>,
{
fn verify(
&self,
request: proto::VerifyRequest,
) -> BoxFuture<'_, Result<proto::VerifyResponse, FacilitatorError>> {
Box::pin(async move {
let request = types::v2::VerifyRequest::from_proto(request)?;
let payload = &request.payment_payload;
let requirements = &request.payment_requirements;
match &payload.payload {
ExactPayload::Eip3009(eip3009) => {
let (contract, payment, eip712_domain) = verify::assert_valid_payment(
self.provider.inner(),
self.provider.chain(),
eip3009,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let payer =
verify_payment(self.provider.inner(), &contract, &payment, &eip712_domain)
.await?;
Ok(proto::VerifyResponse::valid(payer.to_string()))
}
ExactPayload::Permit2(permit2) => {
let (_erc20, payment, eip712_domain) = verify::assert_valid_permit2_payment(
self.provider.inner(),
self.provider.chain(),
permit2,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let payer =
verify_permit2_payment(self.provider.inner(), &payment, &eip712_domain)
.await?;
Ok(proto::VerifyResponse::valid(payer.to_string()))
}
}
})
}
fn settle(
&self,
request: proto::SettleRequest,
) -> BoxFuture<'_, Result<proto::SettleResponse, FacilitatorError>> {
Box::pin(async move {
let request = types::v2::SettleRequest::from_settle(request)?;
let payload = &request.payment_payload;
let requirements = &request.payment_requirements;
match &payload.payload {
ExactPayload::Eip3009(eip3009) => {
let (contract, payment, eip712_domain) = verify::assert_valid_payment(
self.provider.inner(),
self.provider.chain(),
eip3009,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let tx_hash =
settle_payment(&self.provider, &contract, &payment, &eip712_domain).await?;
Ok(proto::SettleResponse::Success {
payer: payment.from.to_string(),
transaction: tx_hash.to_string(),
network: payload.accepted.network.to_string(),
extensions: None,
})
}
ExactPayload::Permit2(permit2) => {
let (_erc20, payment, _eip712_domain) = verify::assert_valid_permit2_payment(
self.provider.inner(),
self.provider.chain(),
permit2,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let tx_hash = settle_permit2_payment(&self.provider, &payment).await?;
Ok(proto::SettleResponse::Success {
payer: payment.from.to_string(),
transaction: tx_hash.to_string(),
network: payload.accepted.network.to_string(),
extensions: None,
})
}
}
})
}
fn supported(&self) -> BoxFuture<'_, Result<proto::SupportedResponse, FacilitatorError>> {
Box::pin(async move {
let chain_id = self.provider.chain_id();
let kinds = vec![proto::SupportedPaymentKind {
x402_version: v2::V2.into(),
scheme: ExactScheme.to_string(),
network: chain_id.into(),
extra: None,
}];
let signers = {
let mut signers = HashMap::with_capacity(1);
signers.insert(Eip155Exact.caip_family(), self.provider.signer_addresses());
signers
};
Ok(proto::SupportedResponse {
kinds,
extensions: Vec::new(),
signers,
})
})
}
}