use std::future::Future;
use std::time::{Duration, Instant};
use alloy::primitives::{Address, U256};
use serde::{Deserialize, Serialize};
use crate::connection::{EvmConnection, TxResult};
use crate::error::EvmError;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ScanConfig {
pub from_block: u64,
pub max_block_range: u64,
}
impl Default for ScanConfig {
fn default() -> Self {
Self { from_block: 0, max_block_range: 50_000 }
}
}
impl ScanConfig {
pub fn from_block(from_block: u64) -> Self {
Self { from_block, ..Self::default() }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LockReceipt {
pub approval: Option<TxResult>,
pub lock: TxResult,
}
pub(crate) async fn check_new_lock(
conn: &EvmConnection,
account: Address,
sender: Address,
recipient: Address,
amount: U256,
expiry: u64,
) -> Result<(), EvmError> {
if sender != account {
return Err(EvmError::InvalidInput(format!(
"the swap's sender {sender} is not this client's account {account}"
)));
}
if amount.is_zero() {
return Err(EvmError::InvalidInput("amount is zero".to_owned()));
}
if recipient == Address::ZERO {
return Err(EvmError::InvalidInput("recipient is the zero address".to_owned()));
}
let now = conn.now().await?;
if expiry <= now {
return Err(EvmError::InvalidInput(format!("expiry {expiry} is not after the chain's time {now}")));
}
let max = conn.max_lock_seconds();
if expiry - now > max {
return Err(EvmError::InvalidInput(format!(
"expiry {expiry} is {} s after the chain's time {now}, more than the allowed {max} s (expiries are Unix \
seconds, not milliseconds)",
expiry - now
)));
}
Ok(())
}
pub(crate) async fn poll_until<T, F, Fut>(
interval: Duration,
timeout: Option<Duration>,
what: &str,
mut check: F,
) -> Result<T, EvmError>
where
F: FnMut() -> Fut,
Fut: Future<Output = Result<Option<T>, EvmError>>,
{
let deadline = timeout.and_then(|t| Instant::now().checked_add(t));
loop {
let last_error = match check().await {
Ok(Some(value)) => return Ok(value),
Ok(None) => None,
Err(err) if err.is_transient() => Some(err),
Err(err) => return Err(err),
};
if let (Some(deadline), Some(timeout)) = (deadline, timeout)
&& Instant::now().checked_add(interval).is_none_or(|next| next > deadline)
{
return Err(last_error.unwrap_or(EvmError::Timeout { what: what.to_owned(), timeout }));
}
tokio::time::sleep(interval).await;
}
}