Skip to content

Instantly share code, notes, and snippets.

@mgild
Created November 16, 2025 13:00
Show Gist options
  • Select an option

  • Save mgild/049abe83a2d0369aca9550e12bdc3365 to your computer and use it in GitHub Desktop.

Select an option

Save mgild/049abe83a2d0369aca9550e12bdc3365 to your computer and use it in GitHub Desktop.
use std::{
collections::HashMap,
str::FromStr,
sync::{atomic::AtomicU64, Arc},
};
use anyhow::Context;
use anyhow_ext::{anyhow, Result};
use async_recursion::async_recursion;
use borsh::de::BorshDeserialize;
use flat_fee_interface::ProgramState;
use lazy_static::lazy_static;
use rust_decimal::Decimal;
use s_controller_interface::PoolState;
use s_sol_val_calc_prog_aggregate::{
KnownLstSolValCalc, LidoLstSolValCalc, LstSolValCalc, MarinadeLstSolValCalc,
MutableLstSolValCalc, SanctumSplLstSolValCalc, SanctumSplMultiLstSolValCalc, SplLstSolValCalc,
SplLstSolValCalcInitKeys, WsolLstSolValCalc,
};
use sanctum_lst_list::{
PoolInfo::{SPool, SanctumSpl, SanctumSplMulti, Spl},
SanctumLst, SanctumLstList, SplPoolAccounts,
};
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_program::pubkey::Pubkey as ProgramPubkey;
use solana_readonly_account::keyed;
use solana_sdk::{
account::{Account, AccountSharedData},
native_token::LAMPORTS_PER_SOL,
program_pack::Pack,
pubkey::Pubkey,
sysvar,
sysvar::clock::Clock,
};
use spl_token::state::Mint;
use tracing::info;
use crate::{
oracle_job::SanctumLstPriceTask, TaskInterface, TaskInterfaceAsync, TaskOutput, TaskResult,
TaskRunnerContext,
};
lazy_static! {
static ref INF_MINT: Pubkey =
Pubkey::from_str("5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm").unwrap();
static ref SOL_MINT: Pubkey =
Pubkey::from_str("So11111111111111111111111111111111111111112").unwrap();
}
pub async fn get_epoch(client: &RpcClient) -> Result<Arc<AtomicU64>> {
let account_data = client.get_account(&sysvar::clock::ID).await;
let account_data = account_data?;
let clock = bincode::deserialize::<Clock>(&account_data.data)?;
let epoch = Arc::new(AtomicU64::new(clock.epoch));
Ok(epoch)
}
fn overwrite_shared_current_epoch(calc: &mut KnownLstSolValCalc) -> Result<()> {
match calc {
KnownLstSolValCalc::Lido(LidoLstSolValCalc {
calc: Some(calc),
shared_current_epoch,
}) => *shared_current_epoch = Arc::new(AtomicU64::new(calc.computed_in_epoch)),
KnownLstSolValCalc::Spl(SplLstSolValCalc {
calc: Some(calc),
shared_current_epoch,
..
})
| KnownLstSolValCalc::SanctumSpl(SanctumSplLstSolValCalc(SplLstSolValCalc {
calc: Some(calc),
shared_current_epoch,
..
}))
| KnownLstSolValCalc::SanctumSplMulti(SanctumSplMultiLstSolValCalc(SplLstSolValCalc {
calc: Some(calc),
shared_current_epoch,
..
})) => *shared_current_epoch = Arc::new(AtomicU64::new(calc.last_update_epoch)),
KnownLstSolValCalc::Marinade(_) | KnownLstSolValCalc::Wsol(_) => {}
_ => return Err(anyhow!("Failed to overwrite shared current epoch")),
};
Ok(())
}
#[allow(dead_code)]
async fn get_accounts(client: &RpcClient, keys: Vec<Pubkey>) -> Result<HashMap<Pubkey, Account>> {
let mut account_datas = Vec::new();
for key_chunk in keys.chunks(100) {
let chunk: Vec<Option<Account>> = client.get_multiple_accounts(key_chunk).await.unwrap();
account_datas.extend(chunk);
}
let mut data_map = HashMap::new();
for i in 0..keys.len() {
let key = &keys[i];
let account = &account_datas[i];
if let Some(account) = account {
data_map.insert(*key, account.clone());
} else {
info!("Failed to get SPool required account {key}");
}
}
Ok(data_map)
}
pub async fn calc_from_calculator(
client: &RpcClient,
mut calc: KnownLstSolValCalc,
skip_epoch_check: bool,
) -> Result<Decimal> {
let accounts = calc.get_accounts_to_update();
let account_datas: Vec<Account> = client
.get_multiple_accounts(&accounts.clone())
.await
.map_err(|_| anyhow!("Failed to get accounts"))?
.into_iter()
.flatten()
.collect::<Vec<_>>();
if accounts.len() != account_datas.len() {
return Err(anyhow!(
"Account data mismatch, failed to fetch all needed calc accounts"
));
}
let mut data_map = HashMap::new();
for i in 0..accounts.len() {
let key = &accounts[i];
let account_data = AccountSharedData::from(account_datas[i].clone());
let keyed_account = keyed::Keyed {
pubkey: ProgramPubkey::new_from_array(key.to_bytes()),
account: account_data,
};
data_map.insert(*key, keyed_account);
}
calc.update(&data_map)
.map_err(|_| anyhow!("Failed to update calc"))?;
if skip_epoch_check {
// Overwrite `shared_current_epoch` to bypass validation
overwrite_shared_current_epoch(&mut calc)?;
}
let range = calc
.lst_to_sol(LAMPORTS_PER_SOL)
.map_err(|_| anyhow!("Failed to calculate lst_to_sol"))?;
// Allow up to 1 for rounding
if range.get_max() - range.get_min() > 1 {
return Err(anyhow!("SanctumCalcError: Unexpected range"));
}
Ok(Decimal::from(range.get_max()) / Decimal::from(LAMPORTS_PER_SOL))
}
pub async fn calc_from_lst(
client: &RpcClient,
lst: Option<&SanctumLst>,
skip_epoch_check: bool,
) -> Result<Decimal> {
let lst = lst.ok_or(anyhow!("SanctumLstNotFound"))?;
let calc;
let epoch = get_epoch(client).await?;
if let SanctumSplMulti(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::SanctumSplMulti(SanctumSplMultiLstSolValCalc::from_keys(
SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
},
epoch,
));
} else if let SanctumSpl(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::SanctumSpl(SanctumSplLstSolValCalc::from_keys(
SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
},
epoch,
));
} else if let Spl(SplPoolAccounts { pool, .. }) = lst.pool {
calc = KnownLstSolValCalc::Spl(SplLstSolValCalc::from_keys(
SplLstSolValCalcInitKeys {
lst_mint: lst.mint,
stake_pool_addr: pool,
},
epoch,
));
} else if let sanctum_lst_list::PoolInfo::Marinade = lst.pool {
calc = MarinadeLstSolValCalc::default().into();
} else if let sanctum_lst_list::PoolInfo::Lido = lst.pool {
calc = LidoLstSolValCalc {
shared_current_epoch: epoch,
calc: None,
}
.into();
} else if let sanctum_lst_list::PoolInfo::ReservePool = lst.pool {
calc = WsolLstSolValCalc {}.into();
} else if let SPool(_) = lst.pool {
if lst.mint != *INF_MINT {
return Err(anyhow!("InvalidSpool"));
}
return inf_quote(client).await;
} else {
return Err(anyhow!("SanctumLstNotSpl"));
}
calc_from_calculator(client, calc, skip_epoch_check).await
}
pub async fn inf_quote(client: &RpcClient) -> Result<Decimal> {
let sanctum_state_key =
Pubkey::from_str("DpWzqkAVNjgdiLCDVy1M3XWGBPjJ11h177kNX8NywYNE").unwrap();
let inf_pool_key = Pubkey::from_str("AYhux5gJzCoeoc1PoJ1VxwPDe22RwcvpHviLDD1oCGvW").unwrap();
let sanctum_state_data = client
.get_account_data(&sanctum_state_key)
.await
.context("Failed to get sanctum state data")?;
let inf_pool_data = client
.get_account_data(&inf_pool_key)
.await
.context("Failed to get inf pool data")?;
let pool_state = PoolState::deserialize(&mut inf_pool_data.as_slice())
.context("Failed to deserialize pool state")?;
let sanctum_state = ProgramState::deserialize(&mut sanctum_state_data.as_slice())
.context("Failed to deserialize sanctum state")?;
let inf_mint_key = pool_state.lp_token_mint;
let inf_mint_data = client
.get_account_data(&inf_mint_key)
.await
.context("Failed to get inf mint data")?;
let inf_mint = Mint::unpack(inf_mint_data.as_slice()).context("Failed to unpack inf mint")?;
let ten_thousand = Decimal::from(10_000);
let _lp_withdrawal_fee = ten_thousand
.checked_sub(Decimal::from(sanctum_state.lp_withdrawal_fee_bps))
.context("Failed to calculate lp_withdrawal_fee")?
.checked_div(ten_thousand)
.context("Failed to calculate lp_withdrawal_fee")?;
let total_sol_value = Decimal::from(pool_state.total_sol_value);
let lp_total_supply = Decimal::from(inf_mint.supply);
let ratio = total_sol_value
.checked_div(lp_total_supply)
.context("Failed to calculate ratio")?;
// let rate_with_fee = ratio.checked_mul(lp_withdrawal_fee)
// .context("Failed to calculate rate with fee")?;
// Ok(rate_with_fee)
Ok(ratio)
}
pub fn to_lst<'a>(lsts: &'a [SanctumLst], key: &'a Pubkey) -> Option<&'a SanctumLst> {
lsts.iter().find(|x| x.mint == *key)
}
#[async_recursion]
pub async fn sanctum_task(ctx: &TaskRunnerContext, task: &SanctumLstPriceTask) -> TaskResult {
let lst_mint = Pubkey::from_str(&task.lst_mint.clone().unwrap_or_default())
.map_err(|_| anyhow!("SanctumPriceTask:InvalidMint"))?;
let skip_epoch_check = task.skip_epoch_check.unwrap_or(false);
let SanctumLstList {
sanctum_lst_list: lsts,
} = SanctumLstList::load();
let client = ctx.mainnet_rpc();
let price = calc_from_lst(&client, to_lst(&lsts, &lst_mint), skip_epoch_check).await?;
Ok(TaskOutput::Num(price))
}
impl TaskInterface for SanctumLstPriceTask {
fn children(&self) -> Vec<crate::protos::OracleJob> {
Vec::new()
}
fn uses_input(&self) -> bool {
false
}
}
#[async_trait::async_trait]
impl TaskInterfaceAsync for SanctumLstPriceTask {
async fn execute<'a>(&'a self, ctx: &'a mut TaskRunnerContext) -> TaskResult {
sanctum_task(ctx, self).await
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use sanctum_lst_list::SanctumLstList;
use super::*;
use crate::{oracle_job::SanctumLstPriceTask, test_utils};
#[tokio::test]
async fn sanctum_inf_base() {
let ctx = test_utils::get_test_task_runner_context(true);
let task = SanctumLstPriceTask {
lst_mint: Some("5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm".to_string()),
skip_epoch_check: Some(false),
};
let output = sanctum_task(&ctx, &task).await.unwrap();
println!("output: {:#?}", output);
}
#[tokio::test]
async fn sanctum_test_base() {
let ctx = test_utils::get_test_task_runner_context(true);
let task = SanctumLstPriceTask {
lst_mint: Some("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn".to_string()),
skip_epoch_check: Some(false),
};
let output = sanctum_task(&ctx, &task).await.unwrap();
println!("output: {:#?}", output);
}
#[tokio::test]
async fn sanctum_test_1() -> Result<()> {
let client = test_utils::get_test_rpc_client(true);
let jup_sol_mint = Pubkey::from_str("jupSoLaHXQiZZTSfEWMTRRgpnyFm8f6sZdosWBjx93v").unwrap();
let _path_mint = Pubkey::from_str("pathdXw4He1Xk3eX84pDdDZnGKEme3GivBamGCVPZ5a").unwrap();
let jito_sol_mint =
Pubkey::from_str("J1toso1uCk3RLmjorhTtrVwY9HJ7X8V9yYac6Y7kGCPn").unwrap();
let inf_mint = Pubkey::from_str("5oVNBeEEQvYi1cX3ir8Dx5n1P7pdxydbGF2X4TxVusJm").unwrap();
let _lido_sol_mint = LidoLstSolValCalc {
calc: None,
shared_current_epoch: get_epoch(&client).await?,
}
.lst_mint();
let _msol_mint = MarinadeLstSolValCalc::default().lst_mint();
let wsol_mint = WsolLstSolValCalc {}.lst_mint();
let SanctumLstList {
sanctum_lst_list: lsts,
} = SanctumLstList::load();
let _skip_epoch_check = true;
println!(
"price: {:#?}",
calc_from_lst(&client, to_lst(&lsts, &jup_sol_mint), false).await?
);
println!(
"price: {:#?}",
calc_from_lst(&client, to_lst(&lsts, &wsol_mint), false).await?
);
println!(
"price: {:#?}",
calc_from_lst(&client, to_lst(&lsts, &jito_sol_mint), false).await?
);
println!(
"price: {:#?}",
calc_from_lst(&client, to_lst(&lsts, &inf_mint), false).await?
);
Ok(())
}
#[tokio::test]
async fn sanctum_test_mango() {
let ctx = test_utils::get_test_task_runner_context(true);
let task = SanctumLstPriceTask {
lst_mint: Some("StPsoHokZryePePFV8N7iXvfEmgUoJ87rivABX7gaW6".to_string()),
skip_epoch_check: Some(false),
};
let output = sanctum_task(&ctx, &task).await.unwrap();
println!("output: {:#?}", output);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment