use std::collections::HashMap;
use std::future::Future;
use alloy_primitives::{Address as EvmAddress, B256, Bytes, U256};
use r402_core::cache::{Duplicate, SettlementCache};
use r402_core::chain::ChainProvider;
use r402_core::error::VerificationError;
use r402_core::facilitator::{DynFacilitator, Facilitator, FacilitatorError};
use r402_core::scheme::{SchemeBuilder, SchemeId};
use r402_core::wire;
use r402_core::wire::UnixTimestamp;
use crate::chain::TronChainProvider;
use crate::exact::types;
use crate::exact::{ExactPayload, ExactScheme, TronExact, TronExactError};
mod settle;
mod signature;
mod verify;
use settle::{settle_payment, settle_permit2_payment};
pub use signature::TronSignatureError;
use verify::{verify_payment, verify_permit2_payment};
const DEFAULT_CLOCK_SKEW_TOLERANCE: u64 = crate::TRON_DEFAULT_CLOCK_SKEW_TOLERANCE_SECS;
#[derive(Debug)]
pub struct Eip3009Payment {
pub from: EvmAddress,
pub to: EvmAddress,
pub value: U256,
pub valid_after: UnixTimestamp,
pub valid_before: UnixTimestamp,
pub nonce: B256,
pub signature: Bytes,
}
#[derive(Debug)]
pub struct Permit2Payment {
pub from: EvmAddress,
pub to: EvmAddress,
pub token: EvmAddress,
pub amount: U256,
pub spender: EvmAddress,
pub nonce: U256,
pub deadline: U256,
pub valid_after: U256,
pub signature: Bytes,
}
pub struct TronExactFacilitator {
provider: TronChainProvider,
clock_skew_tolerance: u64,
settlement_cache: SettlementCache,
}
impl TronExactFacilitator {
#[must_use]
pub fn new(provider: TronChainProvider) -> Self {
Self::with_settlement_cache(provider, SettlementCache::new())
}
#[must_use]
pub const fn with_settlement_cache(
provider: TronChainProvider,
settlement_cache: SettlementCache,
) -> Self {
Self {
provider,
clock_skew_tolerance: DEFAULT_CLOCK_SKEW_TOLERANCE,
settlement_cache,
}
}
#[must_use]
pub const fn with_clock_skew_tolerance(mut self, seconds: u64) -> Self {
self.clock_skew_tolerance = seconds;
self
}
fn eip3009_cache_key(&self, nonce: B256) -> String {
format!("{}:{nonce}", self.provider.chain_id())
}
fn permit2_cache_key(&self, nonce: &U256) -> String {
format!("{}:permit2:{nonce:#x}", self.provider.chain_id())
}
}
impl std::fmt::Debug for TronExactFacilitator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TronExactFacilitator")
.finish_non_exhaustive()
}
}
impl SchemeBuilder<TronChainProvider> for TronExact {
fn build(
&self,
provider: TronChainProvider,
_config: Option<serde_json::Value>,
) -> Result<Box<dyn DynFacilitator>, Box<dyn std::error::Error + Send + Sync>> {
Ok(Box::new(TronExactFacilitator::new(provider)))
}
}
impl From<TronExactError> for FacilitatorError {
fn from(e: TronExactError) -> Self {
match e {
TronExactError::SignatureRecovery(_)
| TronExactError::SignerMismatch
| TronExactError::InsufficientBalance
| TronExactError::NotYetValid
| TronExactError::Expired
| TronExactError::ValueMismatch
| TronExactError::RecipientMismatch
| TronExactError::AssetMismatch
| TronExactError::InvalidPermit2Spender(_)
| TronExactError::NonceAlreadyUsed
| TronExactError::ChainMismatch
| TronExactError::MissingTip712Domain
| TronExactError::UnsupportedTransferMethod => {
Self::Verification(VerificationError::from(e))
}
TronExactError::TronGrid(_)
| TronExactError::TransactionFailed(_)
| TronExactError::ConfirmationTimeout => Self::Onchain(e.to_string()),
}
}
}
impl Facilitator for TronExactFacilitator {
async fn verify(
&self,
request: wire::VerifyRequest,
) -> Result<wire::VerifyResponse, FacilitatorError> {
let request = types::v2::VerifyRequest::from_verify(request)?;
let payload = &request.payment_payload;
let requirements = &request.payment_requirements;
let chain = self.provider.chain_reference();
match &payload.payload {
ExactPayload::Eip3009(eip3009) => {
let (payment, eip712_domain) = verify::assert_valid_payment(
&self.provider,
&chain,
eip3009,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let payer = verify_payment(&self.provider, &payment, &eip712_domain).await?;
Ok(wire::VerifyResponse::valid(
crate::chain::Address::from_evm(payer).to_string(),
))
}
ExactPayload::Permit2(permit2) => {
let (payment, eip712_domain) = verify::assert_valid_permit2_payment(
&self.provider,
&chain,
permit2,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let payer =
verify_permit2_payment(&self.provider, &chain, &payment, &eip712_domain)
.await?;
Ok(wire::VerifyResponse::valid(
crate::chain::Address::from_evm(payer).to_string(),
))
}
}
}
async fn settle(
&self,
request: wire::SettleRequest,
) -> Result<wire::SettleResponse, FacilitatorError> {
let request = types::v2::SettleRequest::from_settle(request)?;
let payload = &request.payment_payload;
let requirements = &request.payment_requirements;
let chain = self.provider.chain_reference();
let cache_key = match &payload.payload {
ExactPayload::Eip3009(eip3009) => self.eip3009_cache_key(eip3009.authorization.nonce),
ExactPayload::Permit2(permit2) => {
self.permit2_cache_key(&permit2.permit2_authorization.nonce.into())
}
};
if self.settlement_cache.reserve(cache_key) == Duplicate::Yes {
return Err(VerificationError::DuplicateSettlement.into());
}
match &payload.payload {
ExactPayload::Eip3009(eip3009) => {
let (payment, eip712_domain) = verify::assert_valid_payment(
&self.provider,
&chain,
eip3009,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let tx_hash = settle_payment(&self.provider, &payment, &eip712_domain).await?;
Ok(wire::SettleResponse::Success {
payer: crate::chain::Address::from_evm(payment.from)
.to_string()
.into(),
transaction: tx_hash.into(),
network: payload.accepted.network.to_string().into(),
amount: Some(requirements.amount.0.to_string().into()),
extensions: wire::Extensions::new(),
})
}
ExactPayload::Permit2(permit2) => {
let (payment, _eip712_domain) = verify::assert_valid_permit2_payment(
&self.provider,
&chain,
permit2,
payload,
requirements,
self.clock_skew_tolerance,
)
.await?;
let tx_hash = settle_permit2_payment(&self.provider, &chain, &payment).await?;
Ok(wire::SettleResponse::Success {
payer: crate::chain::Address::from_evm(payment.from)
.to_string()
.into(),
transaction: tx_hash.into(),
network: payload.accepted.network.to_string().into(),
amount: Some(requirements.amount.0.to_string().into()),
extensions: wire::Extensions::new(),
})
}
}
}
fn supported(
&self,
) -> impl Future<Output = Result<wire::SupportedResponse, FacilitatorError>> + Send {
use compact_str::CompactString;
let chain_id = self.provider.chain_id();
let kinds = vec![wire::SupportedPaymentKind::new(
wire::V2.into(),
ExactScheme.to_string(),
chain_id.to_string(),
)];
let mut signers: HashMap<CompactString, Vec<CompactString>> = HashMap::with_capacity(1);
let _ = signers.insert(
TronExact.caip_family().into(),
self.provider
.signer_addresses()
.into_iter()
.map(CompactString::from)
.collect(),
);
std::future::ready(Ok(wire::SupportedResponse::new()
.with_kinds(kinds)
.with_signers(signers)))
}
}