df_hyperliquid_core/earn/lending/
state.rs1use crate::client::{ApiError, Transport, info, info_request, user_request};
7use crate::utils::{Address, Dec, Figure, Json};
8use serde::Deserialize;
9
10#[must_use]
12pub fn all_reserve_states_request() -> String {
13 info_request("allBorrowLendReserveStates", &[])
14}
15
16#[must_use]
19pub fn reserve_state_request(token: u32) -> String {
20 info_request("borrowLendReserveState", &[("token", Json::Number(token.to_string()))])
21}
22
23#[must_use]
25pub fn user_state_request(user: Address) -> String {
26 user_request("borrowLendUserState", user)
27}
28
29pub async fn all_reserve_states(transport: &impl Transport) -> Result<Vec<(u32, ReserveState)>, ApiError> {
31 info(transport, all_reserve_states_request()).await
32}
33
34pub async fn reserve_state(transport: &impl Transport, token: u32) -> Result<ReserveState, ApiError> {
36 info(transport, reserve_state_request(token)).await
37}
38
39pub async fn user_state(transport: &impl Transport, user: Address) -> Result<UserState, ApiError> {
40 info(transport, user_state_request(user)).await
41}
42
43#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
45#[serde(rename_all = "camelCase")]
46pub struct ReserveState {
47 pub borrow_yearly_rate: Dec,
49 pub supply_yearly_rate: Dec,
51 pub balance: Dec,
54 pub utilization: Dec,
56 pub oracle_px: Dec,
58 pub ltv: Dec,
60 pub total_supplied: Dec,
61 pub total_borrowed: Dec,
62}
63
64#[must_use]
66pub fn reserve(reserves: &[(u32, ReserveState)], token: u32) -> Option<&ReserveState> {
67 reserves.iter().find(|(index, _)| *index == token).map(|(_, reserve)| reserve)
68}
69
70#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct UserState {
75 #[serde(default)]
77 pub token_to_state: Vec<(u32, Position)>,
78 pub health: String,
81 #[serde(default)]
83 pub health_factor: Option<Figure>,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
88pub enum Risk {
89 Safe,
90 Near,
92 Liquidatable,
93}
94
95impl UserState {
96 #[must_use]
97 pub fn position(&self, token: u32) -> Option<&Position> {
98 self.token_to_state.iter().find(|(index, _)| *index == token).map(|(_, position)| position)
99 }
100
101 pub fn positions(&self) -> impl Iterator<Item = (u32, &Position)> {
103 self.token_to_state
104 .iter()
105 .filter(|(_, position)| !position.supply.value.is_zero() || !position.borrow.value.is_zero())
106 .map(|(token, position)| (*token, position))
107 }
108
109 #[must_use]
111 pub fn supplied(&self, token: u32) -> Dec {
112 self.position(token).map_or(Dec::ZERO, |position| position.supply.value)
113 }
114
115 #[must_use]
117 pub fn borrowed(&self, token: u32) -> Dec {
118 self.position(token).map_or(Dec::ZERO, |position| position.borrow.value)
119 }
120
121 #[must_use]
122 pub fn is_healthy(&self) -> bool {
123 self.health == "healthy"
124 }
125
126 #[must_use]
129 pub fn risk(&self) -> Risk {
130 match self.health.as_str() {
131 "healthy" => Risk::Safe,
132 "liquidatable" => Risk::Liquidatable,
133 _ => Risk::Near,
134 }
135 }
136
137 #[must_use]
139 pub fn factor(&self) -> Option<Dec> {
140 self.health_factor.as_ref().and_then(Figure::to_dec)
141 }
142}
143
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
145pub struct Position {
146 #[serde(default)]
147 pub borrow: Basis,
148 #[serde(default)]
149 pub supply: Basis,
150}
151
152#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
155pub struct Basis {
156 #[serde(default)]
157 pub basis: Dec,
158 pub value: Dec,
159}
160
161impl Basis {
162 #[must_use]
164 pub fn interest(&self) -> Option<Dec> {
165 self.value.checked_sub(self.basis)
166 }
167}
168
169#[cfg(test)]
170#[path = "tests/lend.rs"]
171mod tests;