Skip to content

Instantly share code, notes, and snippets.

@mgild
Created April 3, 2025 16:09
Show Gist options
  • Save mgild/66bb7e536cc26e247c48258161feaf0c to your computer and use it in GitHub Desktop.
Save mgild/66bb7e536cc26e247c48258161feaf0c to your computer and use it in GitHub Desktop.
mod idl;
use anchor_lang::AnchorDeserialize;
use anyhow::Result;
use anyhow_ext::Context;
use idl::*;
use rust_decimal::Decimal;
use sanctum_lst_list::SanctumLstList;
use solana_client::nonblocking::rpc_client::RpcClient;
use solana_sdk::{program_pack::Pack, pubkey, pubkey::Pubkey};
use spl_token::state::Mint;
use crate::{calc_from_lst, to_lst};
const NSOL_POOL: Pubkey = pubkey!("AkbZvKxUAxMKz92FF7g5k2YLCJftg8SnYEPWdmZTt3mp");
const FRAGSOL_FUND: Pubkey = pubkey!("3TK9fNePM4qdKC4dwvDe8Bamv14prDqdVfuANxPeiryb");
pub async fn simulate_nsol_live_price(client: &RpcClient) -> Result<Decimal> {
let s = fetch_nsol_pool_account(client).await?;
let start_epoch = client.get_epoch_info().await?.epoch;
let SanctumLstList {
sanctum_lst_list: lsts,
} = SanctumLstList::load();
let mut total_weight = Decimal::ZERO;
let mint_account = client.get_account(&s.normalized_token_mint);
let mut futs = Vec::new();
for i in 0..s.supported_tokens.len() {
let lst_mint = &s.supported_tokens[i].mint;
futs.push(calc_from_lst(client, to_lst(&lsts, lst_mint)));
}
let prices = futures::future::try_join_all(futs).await?;
let mint_account = mint_account.await?;
for (i, lst) in s.supported_tokens.iter().enumerate() {
let price = prices[i];
total_weight += Decimal::from(lst.locked_amount) * price;
}
let nsol_mint = Mint::unpack_from_slice(&mint_account.data)?;
let end_epoch = client.get_epoch_info().await?.epoch;
if start_epoch != end_epoch {
return Err(anyhow::anyhow!("Epoch changed during fragsol calculation"));
}
Ok(total_weight / Decimal::from(nsol_mint.supply))
}
pub async fn get_latest_nsol_price(rpc: &RpcClient) -> Result<Decimal> {
let ntp = fetch_nsol_pool_account(rpc).await?;
let epoch_schedule = rpc.get_epoch_schedule().await?;
let current_epoch = rpc.get_epoch_info().await?.epoch;
let updated_epoch = epoch_schedule.get_epoch(ntp.normalized_token_value_updated_slot);
if current_epoch != updated_epoch {
return Err(anyhow::anyhow!("nsol token account not updated"));
}
Ok(Decimal::from_i128_with_scale(
ntp.one_normalized_token_as_sol.into(),
9,
))
}
pub async fn get_latest_fragsol_price(rpc: &RpcClient) -> Result<Decimal> {
let fund = fetch_fragsol_fund_account(rpc).await?;
let epoch_schedule = rpc
.get_epoch_schedule()
.await
.context("failed to fetch epoch schedule")?;
let current_epoch = rpc
.get_epoch_info()
.await
.context("failed to fetch epoch info")?
.epoch;
let updated_epoch = epoch_schedule.get_epoch(fund.receipt_token_value_updated_slot);
if current_epoch != updated_epoch {
return Err(anyhow::anyhow!("fragsol token account needs epoch refresh"));
}
Ok(Decimal::from_i128_with_scale(
fund.one_receipt_token_as_sol.into(),
9,
))
}
async fn fetch_nsol_pool_account(rpc: &RpcClient) -> Result<NormalizedTokenPoolAccount> {
let account_data = rpc.get_account_data(&NSOL_POOL).await?;
let account_data_slice = account_data[8..].to_vec();
let mut account_data_slice = account_data_slice.as_slice();
NormalizedTokenPoolAccount::deserialize(&mut account_data_slice)
.context("failed to deserialize nsol pool account")
}
async fn fetch_fragsol_fund_account(rpc: &RpcClient) -> Result<FundAccount> {
let account_data = rpc
.get_account_data(&FRAGSOL_FUND)
.await
.context("failed to fetch fragsol fund account")?;
let account_data_slice = &mut account_data.as_slice();
let account_data_slice_without_discriminator = &mut &account_data_slice[8..];
FundAccount::deserialize(account_data_slice_without_discriminator)
.context("failed to deserialize fragsol fund account")
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_simulate_fragsol_redemption() {
let client = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
println!("{:#?}", get_latest_fragsol_price(&client).await.unwrap());
}
#[tokio::test]
async fn test_simulate_nsol_price() {
let client = RpcClient::new("https://api.mainnet-beta.solana.com".to_string());
println!("{:#?}", simulate_nsol_live_price(&client).await.unwrap());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment