Created
April 3, 2025 16:09
-
-
Save mgild/66bb7e536cc26e247c48258161feaf0c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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