Skip to main content

klend_interface/helpers/
refresh.rs

1use solana_instruction::Instruction;
2use solana_pubkey::Pubkey;
3
4use super::{
5    common::{build_refresh_obligation, build_refresh_reserve},
6    info::{ObligationInfo, ReserveInfo},
7};
8use crate::{util::writable, KLEND_PROGRAM_ID};
9
10/// Build a single `refresh_reserve` instruction.
11///
12/// Returns: `refresh_reserve`
13pub fn refresh_reserve(reserve: &ReserveInfo) -> Instruction {
14    build_refresh_reserve(reserve)
15}
16
17/// Build a single `refresh_obligation` instruction with all required
18/// remaining accounts (deposit reserves, borrow reserves, referrer token
19/// states) derived from the obligation info.
20///
21/// Returns: `refresh_obligation`
22pub fn refresh_obligation(lending_market: &Pubkey, obligation: &ObligationInfo) -> Instruction {
23    build_refresh_obligation(lending_market, obligation)
24}
25
26/// Build a `refresh_reserves_batch` instruction from a slice of reserve infos.
27///
28/// The remaining_accounts list is built automatically: for each reserve, the
29/// reserve account (writable) is followed by its lending market (readonly) and
30/// oracle accounts (readonly).
31///
32/// Returns: `refresh_reserves_batch`
33pub fn refresh_reserves_batch(reserves: &[ReserveInfo], skip_price_updates: bool) -> Instruction {
34    let mut remaining = Vec::with_capacity(reserves.len() * 6);
35    for r in reserves {
36        remaining.push(writable(r.address));
37        remaining.push(crate::util::readonly(r.lending_market));
38        remaining.push(crate::util::optional_account(
39            &KLEND_PROGRAM_ID,
40            r.pyth_oracle,
41            false,
42        ));
43        remaining.push(crate::util::optional_account(
44            &KLEND_PROGRAM_ID,
45            r.switchboard_price_oracle,
46            false,
47        ));
48        remaining.push(crate::util::optional_account(
49            &KLEND_PROGRAM_ID,
50            r.switchboard_twap_oracle,
51            false,
52        ));
53        remaining.push(crate::util::optional_account(
54            &KLEND_PROGRAM_ID,
55            r.scope_prices,
56            false,
57        ));
58    }
59    crate::instructions::refresh::refresh_reserves_batch(skip_price_updates, remaining)
60}
61
62/// Build all refresh instructions needed for an obligation: refresh each
63/// unique reserve (deposits + borrows), then refresh the obligation itself.
64///
65/// This is useful for bots that need to bring an entire obligation up-to-date
66/// before scanning health or executing liquidations.
67///
68/// `reserve_infos` is a lookup function that returns `ReserveInfo` for a given
69/// reserve address. All deposit and borrow reserves on the obligation must be
70/// resolvable through this function.
71///
72/// Returns: `Ok([refresh_reserve * N, refresh_obligation])`
73///
74/// # Errors
75///
76/// Returns an error if any deposit or borrow reserve on the obligation cannot
77/// be resolved through `reserve_infos`.
78pub fn refresh_all_for_obligation(
79    lending_market: &Pubkey,
80    obligation: &ObligationInfo,
81    reserve_infos: &dyn Fn(&Pubkey) -> Option<ReserveInfo>,
82) -> Result<Vec<Instruction>, RefreshError> {
83    // Collect unique reserves (deposits + borrows may overlap)
84    let mut seen =
85        Vec::with_capacity(obligation.deposit_reserves.len() + obligation.borrow_reserves.len());
86    let mut ixs = Vec::new();
87
88    for r in obligation
89        .deposit_reserves
90        .iter()
91        .chain(obligation.borrow_reserves.iter())
92    {
93        if !seen.contains(r) {
94            seen.push(*r);
95            let info = reserve_infos(r).ok_or(RefreshError::ReserveNotFound(*r))?;
96            ixs.push(build_refresh_reserve(&info));
97        }
98    }
99
100    ixs.push(build_refresh_obligation(lending_market, obligation));
101    Ok(ixs)
102}
103
104/// Error type for refresh operations.
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub enum RefreshError {
107    /// A reserve referenced by the obligation could not be resolved.
108    ReserveNotFound(Pubkey),
109}
110
111impl std::fmt::Display for RefreshError {
112    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113        match self {
114            RefreshError::ReserveNotFound(key) => {
115                write!(f, "reserve not found: {key}")
116            }
117        }
118    }
119}
120
121impl std::error::Error for RefreshError {}