kora_lib/transaction/
fees.rs

1use serde::{Deserialize, Serialize};
2use solana_client::nonblocking::rpc_client::RpcClient;
3use solana_sdk::{
4    native_token::LAMPORTS_PER_SOL, pubkey::Pubkey, rent::Rent, transaction::Transaction,
5};
6use spl_associated_token_account::get_associated_token_address;
7use std::time::Duration;
8use utoipa::ToSchema;
9
10use crate::{
11    error::KoraError,
12    oracle::{get_price_oracle, PriceSource, RetryingPriceOracle},
13    token::{TokenInterface, TokenProgram, TokenType},
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
17pub struct TokenPriceInfo {
18    pub price: f64,
19}
20
21pub async fn estimate_transaction_fee(
22    rpc_client: &RpcClient,
23    transaction: &Transaction,
24) -> Result<u64, KoraError> {
25    // Get base transaction fee
26    let base_fee = rpc_client
27        .get_fee_for_message(&transaction.message)
28        .await
29        .map_err(|e| KoraError::RpcError(e.to_string()))?;
30
31    // Get account creation fees (for ATA creation)
32    let account_creation_fee = get_associated_token_account_creation_fees(rpc_client, transaction)
33        .await
34        .map_err(|e| KoraError::RpcError(e.to_string()))?;
35
36    // Get priority fee from recent blocks
37    let priority_stats = rpc_client
38        .get_recent_prioritization_fees(&[])
39        .await
40        .map_err(|e| KoraError::RpcError(e.to_string()))?;
41    let priority_fee = priority_stats.iter().map(|fee| fee.prioritization_fee).max().unwrap_or(0);
42
43    Ok(base_fee + priority_fee + account_creation_fee)
44}
45
46async fn get_associated_token_account_creation_fees(
47    rpc_client: &RpcClient,
48    transaction: &Transaction,
49) -> Result<u64, KoraError> {
50    const ATA_ACCOUNT_SIZE: usize = 165; // Standard ATA size
51    let mut ata_count = 0u64;
52
53    // Check each instruction in the transaction for ATA creation
54    for instruction in &transaction.message.instructions {
55        let program_id = transaction.message.account_keys[instruction.program_id_index as usize];
56
57        // Skip if not an ATA program instruction
58        if program_id != spl_associated_token_account::id() {
59            continue;
60        }
61
62        let ata = transaction.message.account_keys[instruction.accounts[1] as usize];
63        let owner = transaction.message.account_keys[instruction.accounts[2] as usize];
64        let mint = transaction.message.account_keys[instruction.accounts[3] as usize];
65
66        let expected_ata = get_associated_token_address(&owner, &mint);
67
68        if ata == expected_ata && rpc_client.get_account(&ata).await.is_err() {
69            ata_count += 1;
70        }
71    }
72
73    // Get rent cost in lamports for ATA creation
74    let rent = Rent::default();
75    let exempt_min = rent.minimum_balance(ATA_ACCOUNT_SIZE);
76
77    Ok(exempt_min * ata_count)
78}
79
80pub async fn calculate_token_value_in_lamports(
81    amount: u64,
82    mint: &Pubkey,
83    price_source: PriceSource,
84    rpc_client: &RpcClient,
85) -> Result<u64, KoraError> {
86    // Fetch mint account data to determine token decimals
87    let mint_account =
88        rpc_client.get_account(mint).await.map_err(|e| KoraError::RpcError(e.to_string()))?;
89
90    let token_program = TokenProgram::new(TokenType::Spl);
91    let decimals = token_program.get_mint_decimals(&mint_account.data)?;
92
93    // Initialize price oracle with retries for reliability
94    let oracle =
95        RetryingPriceOracle::new(3, Duration::from_secs(1), get_price_oracle(price_source));
96
97    // Get token price in SOL directly
98    let token_price = oracle
99        .get_token_price(&mint.to_string())
100        .await
101        .map_err(|e| KoraError::RpcError(format!("Failed to fetch token price: {}", e)))?;
102
103    // Convert token amount to its real value based on decimals and multiply by SOL price
104    let token_amount = amount as f64 / 10f64.powi(decimals as i32);
105    let sol_amount = token_amount * token_price.price;
106
107    // Convert SOL to lamports and round down
108    let lamports = (sol_amount * LAMPORTS_PER_SOL as f64).floor() as u64;
109
110    Ok(lamports)
111}