1use std::future::Future;
4use std::time::{Duration, Instant};
5
6use alloy::primitives::{Address, U256};
7use serde::{Deserialize, Serialize};
8
9use crate::connection::{EvmConnection, TxResult};
10use crate::error::EvmError;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct ScanConfig {
16 pub from_block: u64,
20 pub max_block_range: u64,
24}
25
26impl Default for ScanConfig {
27 fn default() -> Self {
28 Self { from_block: 0, max_block_range: 50_000 }
29 }
30}
31
32impl ScanConfig {
33 pub fn from_block(from_block: u64) -> Self {
35 Self { from_block, ..Self::default() }
36 }
37}
38
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
41#[serde(rename_all = "camelCase")]
42pub struct LockReceipt {
43 pub approval: Option<TxResult>,
45 pub lock: TxResult,
47}
48
49pub(crate) async fn check_new_lock(
52 conn: &EvmConnection,
53 account: Address,
54 sender: Address,
55 recipient: Address,
56 amount: U256,
57 expiry: u64,
58) -> Result<(), EvmError> {
59 if sender != account {
60 return Err(EvmError::InvalidInput(format!(
61 "the swap's sender {sender} is not this client's account {account}"
62 )));
63 }
64 if amount.is_zero() {
65 return Err(EvmError::InvalidInput("amount is zero".to_owned()));
66 }
67 if recipient == Address::ZERO {
68 return Err(EvmError::InvalidInput("recipient is the zero address".to_owned()));
69 }
70 let now = conn.now().await?;
71 if expiry <= now {
72 return Err(EvmError::InvalidInput(format!("expiry {expiry} is not after the chain's time {now}")));
73 }
74 let max = conn.max_lock_seconds();
75 if expiry - now > max {
76 return Err(EvmError::InvalidInput(format!(
77 "expiry {expiry} is {} s after the chain's time {now}, more than the allowed {max} s (expiries are Unix \
78 seconds, not milliseconds)",
79 expiry - now
80 )));
81 }
82 Ok(())
83}
84
85pub(crate) async fn poll_until<T, F, Fut>(
88 interval: Duration,
89 timeout: Option<Duration>,
90 what: &str,
91 mut check: F,
92) -> Result<T, EvmError>
93where
94 F: FnMut() -> Fut,
95 Fut: Future<Output = Result<Option<T>, EvmError>>,
96{
97 let deadline = timeout.and_then(|t| Instant::now().checked_add(t));
99 loop {
100 let last_error = match check().await {
101 Ok(Some(value)) => return Ok(value),
102 Ok(None) => None,
103 Err(err) if err.is_transient() => Some(err),
104 Err(err) => return Err(err),
105 };
106 if let (Some(deadline), Some(timeout)) = (deadline, timeout)
107 && Instant::now().checked_add(interval).is_none_or(|next| next > deadline)
108 {
109 return Err(last_error.unwrap_or(EvmError::Timeout { what: what.to_owned(), timeout }));
110 }
111 tokio::time::sleep(interval).await;
112 }
113}