df_hyperliquid_core/earn/staking/
account.rs1use crate::client::{ApiError, Network, Transport};
6use crate::earn::staking::reads::{
7 Delegation, DelegatorSummary, Reward, StakeDelta, StakeEvent, delegations, delegator_history, delegator_rewards,
8 delegator_summary,
9};
10use crate::utils::{Address, Dec};
11
12pub const REWARDS_WINDOW_MS: u64 = 30 * 86_400_000;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17pub struct PendingWithdrawal {
18 pub amount: Dec,
19 pub initiated: u64,
21 pub ready_at: u64,
23}
24
25#[must_use]
28pub fn pending_withdrawals(network: Network, history: &[StakeEvent], now_ms: u64) -> Vec<PendingWithdrawal> {
29 history
30 .iter()
31 .filter(|event| event.time <= now_ms)
32 .filter_map(|event| {
33 let StakeDelta::Withdrawal { amount, .. } = &event.delta else { return None };
34 let ready_at = event.unstaked_at(network)?;
35 (ready_at > now_ms).then_some(PendingWithdrawal { amount: *amount, initiated: event.time, ready_at })
36 })
37 .collect()
38}
39
40#[must_use]
43pub fn rewards_since(rewards: &[Reward], since_ms: u64) -> Option<Dec> {
44 Dec::checked_sum(rewards.iter().filter(|reward| reward.time >= since_ms).map(|reward| reward.total_amount))
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
49pub struct Staking {
50 pub summary: DelegatorSummary,
51 pub delegations: Vec<Delegation>,
52 pub pending: Vec<PendingWithdrawal>,
54 pub rewards: Dec,
56}
57
58impl Staking {
59 #[must_use]
60 pub fn new(
61 network: Network,
62 summary: DelegatorSummary,
63 delegations: Vec<Delegation>,
64 rewards: &[Reward],
65 history: &[StakeEvent],
66 now_ms: u64,
67 ) -> Staking {
68 let count = usize::try_from(summary.n_pending_withdrawals).unwrap_or(usize::MAX);
69 let mut pending = pending_withdrawals(network, history, now_ms);
70 pending.truncate(count);
71 let rewards = rewards_since(rewards, now_ms.saturating_sub(REWARDS_WINDOW_MS)).unwrap_or(Dec::ZERO);
72 Staking { summary, delegations, pending, rewards }
73 }
74
75 #[must_use]
78 pub fn total(&self) -> Option<Dec> {
79 let summary = &self.summary;
80 summary.delegated.checked_add(summary.undelegated)?.checked_add(summary.total_pending_withdrawal)
81 }
82
83 #[must_use]
85 pub fn delegation(&self, validator: Address) -> Option<&Delegation> {
86 self.delegations.iter().find(|delegation| delegation.validator == validator)
87 }
88}
89
90pub async fn staking(
92 transport: &impl Transport,
93 network: Network,
94 user: Address,
95 now_ms: u64,
96) -> Result<Staking, ApiError> {
97 let summary = delegator_summary(transport, user).await?;
98 let delegations = delegations(transport, user).await?;
99 let rewards = delegator_rewards(transport, user).await?;
100 let history = delegator_history(transport, user).await?;
101 Ok(Staking::new(network, summary, delegations, &rewards, &history, now_ms))
102}
103
104#[cfg(test)]
105#[path = "tests/staking.rs"]
106mod tests;