kora_lib/transaction/
fees.rs1use 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 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 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 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; let mut ata_count = 0u64;
52
53 for instruction in &transaction.message.instructions {
55 let program_id = transaction.message.account_keys[instruction.program_id_index as usize];
56
57 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 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 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 let oracle =
95 RetryingPriceOracle::new(3, Duration::from_secs(1), get_price_oracle(price_source));
96
97 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 let token_amount = amount as f64 / 10f64.powi(decimals as i32);
105 let sol_amount = token_amount * token_price.price;
106
107 let lamports = (sol_amount * LAMPORTS_PER_SOL as f64).floor() as u64;
109
110 Ok(lamports)
111}