Skip to content

Instantly share code, notes, and snippets.

@benjiqq
Created September 21, 2025 03:42
Show Gist options
  • Select an option

  • Save benjiqq/d8af43ae3d4a18e6f507baac557d7f91 to your computer and use it in GitHub Desktop.

Select an option

Save benjiqq/d8af43ae3d4a18e6f507baac557d7f91 to your computer and use it in GitHub Desktop.
txconfirm
use anyhow::{anyhow, Result};
use log::{error, info, warn};
use serde::{Deserialize, Serialize};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{
commitment_config::CommitmentConfig, signature::Signature, transaction::TransactionError,
};
use std::time::Duration;
use tokio::time::sleep;
// Define commitment level constant
#[allow(dead_code)]
pub const COMMITMENT_LEVEL: CommitmentConfig = CommitmentConfig::confirmed();
// Error codes and texts
#[allow(dead_code)]
pub const ERR_1001: &str = "Unknown instruction error";
#[allow(dead_code)]
pub const ERR_1002: &str = "Provided owner is not allowed";
#[allow(dead_code)]
pub const ERR_1003: &str = "custom program error: insufficient funds";
#[allow(dead_code)]
pub const ERR_1011: &str = "Not known Error";
pub const ERR_2006: &str = "custom program error: 2006 | A seeds constraint was violated";
pub const ERR_3007: &str =
"custom program error: 3007 | The given account is owned by a different program than expected";
pub const ERR_3012: &str =
"custom program error: 3012 | The program expected this account to be already initialized";
pub const ERR_6002: &str = "slippage: Too much SOL required to buy the given amount of tokens.";
pub const ERR_6003: &str = "slippage: Too little SOL received to sell the given amount of tokens.";
pub const ERR_6005: &str = "custom program error: 6005 | The bonding curve has completed and liquidity migrated to raydium";
//Program Error: "Insufficient Funds For Rent: A2WBBtwKEQduapSSmdeu4oYB1GxHewXHNs5YNNDkPhjB"
// Helper function for sleeping
async fn sleep_ms(ms: u64) {
sleep(Duration::from_millis(ms)).await;
}
// Transaction confirmation result
#[derive(Debug)]
pub struct TransactionConfirmation {
pub success: bool,
#[allow(dead_code)]
pub txsig: String,
#[allow(dead_code)]
pub confirmation_time: Option<chrono::DateTime<chrono::Utc>>,
pub error: Option<String>,
pub error_code: Option<u32>,
}
// Status poller for transaction confirmation
pub async fn get_status_txn_retry(
client: &RpcClient,
txsig: &str,
max_retries: u32,
retry_sleep: u64,
) -> Result<TransactionConfirmation> {
info!("try get_status_txn {txsig} (max {max_retries}, every {retry_sleep} ms)");
for retry in 0..max_retries {
match client.get_signature_statuses(&[txsig.parse()?]) {
Ok(signature_statuses) => {
if let Some(status) = signature_statuses.value.first() {
if let Some(status) = status {
if status.err.is_none() {
info!("Transaction confirmed {txsig}");
// Get confirmation time from block time
let mut confirmation_time = None;
let slot = status.slot;
if slot > 0 {
match client.get_block_time(slot) {
Ok(block_time) => {
confirmation_time = Some(
chrono::DateTime::from_timestamp(block_time, 0)
.unwrap_or_default(),
);
info!("Block time for slot {slot}: {confirmation_time:?}");
}
Err(e) => {
warn!("Could not fetch block time for slot {slot}: {e}");
}
}
}
return Ok(TransactionConfirmation {
success: true,
txsig: txsig.to_string(),
confirmation_time,
error: None,
error_code: None,
});
}
// Decode InstructionError
if let Some(err) = &status.err {
match err {
TransactionError::InstructionError(instruction_index, instruction_error) => {
match instruction_error {
solana_sdk::instruction::InstructionError::Custom(custom_code) => {
let code_u32: u32 = *custom_code;
let (error_msg, error_code): (String, u32) = match code_u32 {
1u32 => (ERR_1003.to_string(), 1003u32),
2006u32 => (ERR_2006.to_string(), 2006u32),
3007u32 => (ERR_3007.to_string(), 3007u32),
3012u32 => (ERR_3012.to_string(), 3012u32),
6002u32 => (ERR_6002.to_string(), 6002u32),
6003u32 => (ERR_6003.to_string(), 6003u32),
6005u32 => (ERR_6005.to_string(), 6005u32),
_ => (format!(
"Instruction #{} failed with custom program error: {}",
instruction_index, code_u32
), code_u32),
};
error!("Custom error result: {error_msg} (code: {error_code})");
return Ok(TransactionConfirmation {
success: false,
txsig: txsig.to_string(),
confirmation_time: None,
error: Some(error_msg),
error_code: Some(error_code),
});
}
solana_sdk::instruction::InstructionError::InvalidAccountOwner => {
return Ok(TransactionConfirmation {
success: false,
txsig: txsig.to_string(),
confirmation_time: None,
error: Some(ERR_1002.to_string()),
error_code: Some(1002),
});
}
other => {
let full_msg = format!(
"Instruction #{} failed: {:?}",
instruction_index, other
);
error!("{full_msg}");
return Ok(TransactionConfirmation {
success: false,
txsig: txsig.to_string(),
confirmation_time: None,
error: Some(full_msg),
error_code: Some(1011),
});
}
}
}
_ => {
error!("Unexpected tx error: {err:?}");
return Ok(TransactionConfirmation {
success: false,
txsig: txsig.to_string(),
confirmation_time: None,
error: Some(format!("Unexpected tx error: {err:?}")),
error_code: Some(1001),
});
}
}
}
}
}
}
Err(e) => {
error!("confirmation attempt {retry}: {e}");
}
}
sleep_ms(retry_sleep).await;
}
error!("Max retries reached. Tx confirmation failed {txsig}");
Ok(TransactionConfirmation {
success: false,
txsig: txsig.to_string(),
confirmation_time: None,
error: Some("could not confirm in time".to_string()),
error_code: Some(1003),
})
}
// Get transaction via RPC
#[allow(dead_code)]
pub async fn get_tx(url: &str, txsig: &str) -> Result<Option<serde_json::Value>> {
let body = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "getTransaction",
"params": [
txsig,
{
"commitment": "confirmed",
"maxSupportedTransactionVersion": 0,
"encoding": "json",
},
],
});
let client = reqwest::Client::new();
let response =
client.post(url).header("Content-Type", "application/json").json(&body).send().await?;
if !response.status().is_success() {
return Err(anyhow!("HTTP error! status: {}", response.status()));
}
let data: serde_json::Value = response.json().await?;
Ok(data.get("result").cloned())
}
// Transaction result structure
#[derive(Debug, Serialize, Deserialize)]
pub struct TransactionResult {
pub signature: String,
pub token_amount: Option<f64>,
pub token_sol_price: Option<f64>,
pub token_usd_price: Option<f64>,
pub transaction_fee: Option<f64>,
pub net_buy_sol_amount: Option<f64>,
pub execution_price: Option<f64>,
pub error: Option<String>,
}
// Parse transaction details (simplified version)
pub async fn parse_transaction(
signature: &Signature,
_token_address: &str,
_wallet_public_key: &str,
_client: &RpcClient,
) -> Result<TransactionResult> {
// This is a placeholder implementation
// The actual transaction parsing would need to be implemented based on the specific requirements
Ok(TransactionResult {
signature: signature.to_string(),
token_amount: None,
token_sol_price: None,
token_usd_price: None,
transaction_fee: None,
net_buy_sol_amount: None,
execution_price: None,
error: Some("Transaction parsing not yet implemented".to_string()),
})
}
// Test function to demonstrate the transaction confirmation functionality
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_transaction_confirmation_structure() {
let confirmation = TransactionConfirmation {
success: true,
txsig: "test_signature".to_string(),
confirmation_time: None,
error: None,
error_code: None,
};
assert!(confirmation.success);
assert_eq!(confirmation.txsig, "test_signature");
assert!(confirmation.error.is_none());
assert!(confirmation.error_code.is_none());
}
#[tokio::test]
async fn test_transaction_result_structure() {
let result = TransactionResult {
signature: "test_signature".to_string(),
token_amount: Some(100.0),
token_sol_price: Some(0.1),
token_usd_price: Some(10.0),
transaction_fee: Some(0.000005),
net_buy_sol_amount: Some(0.1),
execution_price: Some(0.1),
error: None,
};
assert_eq!(result.signature, "test_signature");
assert_eq!(result.token_amount, Some(100.0));
assert_eq!(result.token_sol_price, Some(0.1));
assert_eq!(result.token_usd_price, Some(10.0));
assert_eq!(result.transaction_fee, Some(0.000005));
assert_eq!(result.net_buy_sol_amount, Some(0.1));
assert_eq!(result.execution_price, Some(0.1));
assert!(result.error.is_none());
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment