use crate::client::{ApiError, Network, Transport};
use crate::earn::staking::reads::{
Delegation, DelegatorSummary, Reward, StakeDelta, StakeEvent, delegations, delegator_history, delegator_rewards,
delegator_summary,
};
use crate::utils::{Address, Dec};
pub const REWARDS_WINDOW_MS: u64 = 30 * 86_400_000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PendingWithdrawal {
pub amount: Dec,
pub initiated: u64,
pub ready_at: u64,
}
#[must_use]
pub fn pending_withdrawals(network: Network, history: &[StakeEvent], now_ms: u64) -> Vec<PendingWithdrawal> {
history
.iter()
.filter(|event| event.time <= now_ms)
.filter_map(|event| {
let StakeDelta::Withdrawal { amount, .. } = &event.delta else { return None };
let ready_at = event.unstaked_at(network)?;
(ready_at > now_ms).then_some(PendingWithdrawal { amount: *amount, initiated: event.time, ready_at })
})
.collect()
}
#[must_use]
pub fn rewards_since(rewards: &[Reward], since_ms: u64) -> Option<Dec> {
Dec::checked_sum(rewards.iter().filter(|reward| reward.time >= since_ms).map(|reward| reward.total_amount))
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Staking {
pub summary: DelegatorSummary,
pub delegations: Vec<Delegation>,
pub pending: Vec<PendingWithdrawal>,
pub rewards: Dec,
}
impl Staking {
#[must_use]
pub fn new(
network: Network,
summary: DelegatorSummary,
delegations: Vec<Delegation>,
rewards: &[Reward],
history: &[StakeEvent],
now_ms: u64,
) -> Staking {
let count = usize::try_from(summary.n_pending_withdrawals).unwrap_or(usize::MAX);
let mut pending = pending_withdrawals(network, history, now_ms);
pending.truncate(count);
let rewards = rewards_since(rewards, now_ms.saturating_sub(REWARDS_WINDOW_MS)).unwrap_or(Dec::ZERO);
Staking { summary, delegations, pending, rewards }
}
#[must_use]
pub fn total(&self) -> Option<Dec> {
let summary = &self.summary;
summary.delegated.checked_add(summary.undelegated)?.checked_add(summary.total_pending_withdrawal)
}
#[must_use]
pub fn delegation(&self, validator: Address) -> Option<&Delegation> {
self.delegations.iter().find(|delegation| delegation.validator == validator)
}
}
pub async fn staking(
transport: &impl Transport,
network: Network,
user: Address,
now_ms: u64,
) -> Result<Staking, ApiError> {
let summary = delegator_summary(transport, user).await?;
let delegations = delegations(transport, user).await?;
let rewards = delegator_rewards(transport, user).await?;
let history = delegator_history(transport, user).await?;
Ok(Staking::new(network, summary, delegations, &rewards, &history, now_ms))
}
#[cfg(test)]
#[path = "tests/staking.rs"]
mod tests;