jup-lend-sdk 0.2.10

SDK for Jupiter lending protocol
Documentation
use anchor_client::{
    solana_sdk::{borsh1::try_from_slice_unchecked, commitment_config::CommitmentConfig},
    Client, Cluster,
};
use anchor_lang::{prelude::Pubkey, AccountDeserialize};
use anyhow::Ok;
use pyth_solana_receiver_sdk::price_update::PriceUpdateV2;
use std::sync::Arc;

use crate::{
    math::{casting::Cast, safe_math::SafeMath, u256::safe_multiply_divide},
    oracle::{marinade, OraclePriceLiquidate, ORACLE_PROGRAM_ID},
    programs::oracle,
    ReadKeypair,
};
use spl_stake_pool::state::StakePool;

const RATE_OUTPUT_DECIMALS: u32 = 15;
const CONFIDENCE_SCALE_FACTOR_LIQUIDATE: u64 = 25; // Rejects if confidence < 1/25 = 4% of price

// const CONFIDENCE_SCALE_FACTOR_OPERATE: u64 = 50; // Rejects if confidence < 1/50 = 2% of price

pub async fn get_oracle_price_liquidate(
    oracle: Pubkey,
    cluster: Cluster,
) -> anyhow::Result<OraclePriceLiquidate> {
    let payer = ReadKeypair::new();
    let provider = Client::new_with_options(
        cluster.clone(),
        Arc::new(payer.clone()),
        CommitmentConfig::confirmed(),
    );
    let program = provider.program(ORACLE_PROGRAM_ID)?;
    let oracle_data: oracle::accounts::Oracle = program.account(oracle).await?;

    if oracle_data.sources.len() == 0 {
        return Err(anyhow::anyhow!("No sources found"));
    }

    let mut rate: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);
    let futures = oracle_data.sources.iter().map(|source| {
        let program = &program;
        let source = source.clone();
        async move {
            let price = match source.source_type {
                oracle::types::SourceType::Pyth => {
                    let rpc = program.rpc();

                    let account = rpc.get_account(&source.source).await?;

                    let mut price_update_data: &[u8] = account.data.iter().as_slice();

                    let price_update =
                        PriceUpdateV2::try_deserialize(&mut price_update_data).unwrap();

                    if price_update.price_message.price <= 0
                        || price_update.price_message.exponent > 0
                    {
                        return Err(anyhow::anyhow!("Price is not valid"));
                    }

                    if price_update
                        .price_message
                        .conf
                        .safe_mul(CONFIDENCE_SCALE_FACTOR_LIQUIDATE)?
                        > price_update.price_message.price.abs() as u64
                    {
                        return Err(anyhow::anyhow!("Price confidence not sufficient"));
                    }

                    let multiplier: u32 = price_update.price_message.exponent.abs().cast()?;
                    let delta = 10u128.pow(RATE_OUTPUT_DECIMALS.safe_sub(multiplier)?);
                    let mut rate = price_update.price_message.price.cast::<u128>()?;

                    rate = rate.safe_mul(delta)?.saturating_div(source.divisor);

                    if rate > 0 && source.invert {
                        rate = 10u128.pow(RATE_OUTPUT_DECIMALS * 2).saturating_div(rate);
                    }

                    rate
                }
                oracle::types::SourceType::StakePool => {
                    const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);

                    let rpc = program.rpc();
                    let data = rpc.get_account_data(&source.source).await?;
                    let stake_pool: StakePool = try_from_slice_unchecked(&data)?;

                    let scaled_pool_lamports: u128 =
                        FACTOR.safe_mul(stake_pool.total_lamports.cast()?)?;
                    let total_supply: u128 = stake_pool.pool_token_supply.cast()?;

                    scaled_pool_lamports.safe_div(total_supply)?
                }

                oracle::types::SourceType::MsolPool => {
                    const FACTOR: u128 = 10u128.pow(RATE_OUTPUT_DECIMALS);

                    let rpc = program.rpc();
                    let data = rpc.get_account_data(&source.source).await?;
                    #[allow(deprecated)]
                    let msol_pool: marinade::State =
                        anchor_lang::solana_program::borsh0_10::try_from_slice_unchecked(
                            &data[8..],
                        )?;

                    let pending_unstake_lamports = msol_pool
                        .stake_system
                        .delayed_unstake_cooling_down
                        .safe_add(msol_pool.emergency_cooling_down)?
                        .cast()?;

                    let total_controlled_lamports = msol_pool
                        .validator_system
                        .total_active_balance
                        .safe_add(pending_unstake_lamports)?
                        .safe_add(msol_pool.available_reserve_balance)?;

                    let effective_staked_lamports = total_controlled_lamports
                        .saturating_sub(msol_pool.circulating_ticket_balance)
                        .cast::<u128>()?;

                    let scaled_pool_lamports = effective_staked_lamports.safe_mul(FACTOR)?;
                    let total_supply: u128 = msol_pool.msol_supply.cast()?;

                    scaled_pool_lamports.safe_div(total_supply)?
                }
                _ => {
                    return Err(anyhow::anyhow!("Unsupported source type"));
                }
            };

            Ok(price)
        }
    });

    let prices = futures::future::try_join_all(futures).await?;

    for price in prices {
        rate = safe_multiply_divide(price, rate, 10u128.pow(RATE_OUTPUT_DECIMALS))?;
    }

    Ok(OraclePriceLiquidate {
        price: rate,
        sources: oracle_data.sources,
    })
}