Skip to content

Commit e49df5b

Browse files
committed
feat: enhance logging and error handling in transaction fetching
1 parent 4d1ccab commit e49df5b

File tree

7 files changed

+188
-25
lines changed

7 files changed

+188
-25
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22

33
.env*
44

5-
*.txt
5+
*.txt
6+
7+
logs/

Cargo.lock

Lines changed: 58 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ solana-transaction-status = "2.0.9"
2121
thiserror = "1.0.63"
2222
tokio = { version = "1.40.0", features = ["full"] }
2323
tracing = "0.1.40"
24-
tracing-subscriber = "0.3.18"
24+
tracing-appender = "0.2.3"
25+
tracing-subscriber = { version = "0.3.18", features = ["env-filter", "json"] }
2526

2627
[patch.crates-io]
2728
# curve25519-dalek 3.x pin zeroize to <1.4

src/core.rs

Lines changed: 95 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,14 @@ use shyft_rs_sdk::{
1616
models::parsed_transaction_details::{self, ParsedTransactionDetails},
1717
ShyftApi,
1818
};
19-
use solana_client::{nonblocking::rpc_client::RpcClient, rpc_config::RpcTransactionConfig};
20-
use solana_sdk::{commitment_config::CommitmentConfig, signature::Signature};
19+
use solana_client::{
20+
nonblocking::rpc_client::RpcClient, rpc_client::GetConfirmedSignaturesForAddress2Config,
21+
rpc_config::RpcTransactionConfig,
22+
};
23+
use solana_sdk::{commitment_config::CommitmentConfig, pubkey::Pubkey, signature::Signature};
2124
use solana_transaction_status::UiTransactionEncoding;
2225

23-
use crate::{get_spinner, PrevBuy, RepeatingWallet};
26+
use crate::{error::PrevBuysFetchError, get_spinner, PrevBuy, RepeatingWallet};
2427

2528
/// Runs the main logic of the solana-copy-trade-detect application.
2629
///
@@ -45,6 +48,8 @@ pub async fn run(args: &crate::Args) -> Result<Vec<RepeatingWallet>, crate::Erro
4548
let fresh_swaps = fetch_fresh_swaps(args).await?;
4649
spinner.finish();
4750

51+
tracing::info!("Fetched {} fresh swaps", fresh_swaps.len());
52+
4853
if fresh_swaps.is_empty() {
4954
eprintln!(
5055
"\n{}",
@@ -87,7 +92,10 @@ pub async fn run(args: &crate::Args) -> Result<Vec<RepeatingWallet>, crate::Erro
8792

8893
for item in fresh_swaps.iter() {
8994
if let models::feed::Item::Swap(swap) = item {
90-
let prev_buys = fetch_prev_buys(args, &shyft_api, swap).await?;
95+
let prev_buys =
96+
fetch_prev_buys(args, &rpc_client, &shyft_api, swap, args.delay_ms).await?;
97+
98+
tracing::info!("Fetched {} previous buys", prev_buys.len());
9199

92100
for buy in prev_buys.iter() {
93101
let block_diff = get_block_diff(&rpc_client, swap, buy, args.delay_ms).await?;
@@ -156,29 +164,99 @@ async fn fetch_fresh_swaps(
156164
/// # Arguments
157165
///
158166
/// * `args` - A reference to the arguments containing the API key, wallet address, and other parameters.
167+
/// * `rpc_client` - A reference to the Solana RPC client.
159168
/// * `shyft_api` - A reference to the Shyft API client.
160169
/// * `swap` - A reference to the swap transaction details.
170+
/// * `delay_ms` - The delay in milliseconds between requests.
161171
///
162172
/// # Errors
163173
///
164-
/// This function will return an error if the Shyft API request fails.
174+
/// This function will return an error if the Shyft API request fails or if the transaction parsing fails.
165175
async fn fetch_prev_buys(
166176
args: &crate::Args,
177+
rpc_client: &RpcClient,
167178
shyft_api: &ShyftApi,
168179
swap: &models::feed::Swap,
169-
) -> Result<Vec<ParsedTransactionDetails>, shyft_rs_sdk::Error> {
170-
Ok(filter_buys(
171-
shyft_api
172-
.get_transaction_history(
173-
&swap.token1_address,
174-
Some(args.scan_tx_count),
175-
Some(&swap.tx_hash),
176-
None,
177-
Some(true),
178-
None,
180+
delay_ms: u64,
181+
) -> Result<Vec<ParsedTransactionDetails>, PrevBuysFetchError> {
182+
let successful_signatures =
183+
fetch_successful_signatures(rpc_client, swap, args.scan_tx_count as usize, delay_ms)
184+
.await?;
185+
tracing::info!(
186+
"Fetched {} successful signatures",
187+
successful_signatures.len()
188+
);
189+
190+
if successful_signatures.is_empty() {
191+
tracing::warn!("No successful signatures found");
192+
return Ok(Vec::new());
193+
}
194+
195+
let parsed_txs = shyft_api
196+
.get_transaction_parse_selected(
197+
&successful_signatures
198+
[..std::cmp::min(successful_signatures.len(), args.scan_tx_count as usize)],
199+
Some(true),
200+
None,
201+
)
202+
.await?;
203+
204+
Ok(filter_buys(parsed_txs))
205+
}
206+
207+
/// Fetches successful transaction signatures for a given swap.
208+
///
209+
/// This function retrieves the transaction signatures for the specified token address
210+
/// and filters the signatures to include only those that are successful.
211+
///
212+
/// # Arguments
213+
///
214+
/// * `rpc_client` - A reference to the Solana RPC client.
215+
/// * `swap` - A reference to the swap transaction details.
216+
/// * `scan_tx_count` - The number of transaction signatures to scan.
217+
/// * `delay_ms` - The delay in milliseconds between requests.
218+
///
219+
/// # Errors
220+
///
221+
/// This function will return an error if the Solana RPC request fails.
222+
async fn fetch_successful_signatures(
223+
rpc_client: &RpcClient,
224+
swap: &models::feed::Swap,
225+
scan_tx_count: usize,
226+
delay_ms: u64,
227+
) -> Result<Vec<String>, solana_client::client_error::ClientError> {
228+
let mut successful_signatures = Vec::new();
229+
230+
let mut before_tx = Signature::from_str(&swap.tx_hash).unwrap();
231+
while successful_signatures.len() < scan_tx_count {
232+
let tx_signatures = rpc_client
233+
.get_signatures_for_address_with_config(
234+
&Pubkey::from_str(&swap.token1_address).unwrap(),
235+
GetConfirmedSignaturesForAddress2Config {
236+
before: Some(before_tx),
237+
until: None,
238+
limit: None,
239+
commitment: Some(CommitmentConfig::confirmed()),
240+
},
179241
)
180-
.await?,
181-
))
242+
.await?;
243+
244+
tracing::debug!("Fetched {} signatures", tx_signatures.len());
245+
246+
if tx_signatures.is_empty() {
247+
break;
248+
}
249+
before_tx = Signature::from_str(&tx_signatures.last().unwrap().signature).unwrap();
250+
251+
for signature in tx_signatures.iter() {
252+
if signature.err.is_none() {
253+
successful_signatures.push(signature.signature.to_string());
254+
}
255+
}
256+
tokio::time::sleep(tokio::time::Duration::from_millis(delay_ms)).await;
257+
}
258+
259+
Ok(successful_signatures)
182260
}
183261

184262
/// Filters transactions to include only those that involve a swap where SOL is the input token.

src/error.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,20 @@ pub enum Error {
1818
/// Error that occurs when there is an issue with the Solana RPC client.
1919
#[error("Solana RPC client error: {0}")]
2020
RpcClient(#[from] solana_client::client_error::ClientError),
21+
22+
/// Error that occurs when fetching previous buy transactions fails.
23+
#[error("Failed to fetch previous buy transactions: {0}")]
24+
PrevBuysFetch(#[from] PrevBuysFetchError),
25+
}
26+
27+
/// Represents the errors that can occur while fetching previous buy transactions.
28+
#[derive(thiserror::Error, Debug)]
29+
pub enum PrevBuysFetchError {
30+
/// Error that occurs when there is an issue with the Shyft API.
31+
#[error("Shyft API error: {0}")]
32+
ShyftApi(#[from] shyft_rs_sdk::Error),
33+
34+
/// Error that occurs when there is an issue with the Solana RPC client.
35+
#[error("Solana RPC client error: {0}")]
36+
RpcClient(#[from] solana_client::client_error::ClientError),
2137
}

src/main.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,23 @@ use std::{
88
use clap::Parser;
99
use indicatif::{ProgressBar, ProgressStyle};
1010
use solana_copy_trade_detect::{get_spinner, Args, RepeatingWallet};
11+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer};
1112

1213
#[tokio::main]
1314
async fn main() {
1415
dotenvy::dotenv().ok();
1516

17+
// Setup logging
18+
// Create a file layer with debug level filtering
19+
let file_appender = tracing_appender::rolling::daily("logs", ".log");
20+
let (file_writer, _file_writer_guard) = tracing_appender::non_blocking(file_appender);
21+
let file_layer = tracing_subscriber::fmt::layer()
22+
.json()
23+
.with_writer(file_writer)
24+
.with_filter(EnvFilter::new("solana_copy_trade_detect::core=debug"));
25+
26+
tracing_subscriber::registry().with(file_layer).init();
27+
1628
let args = Args::parse();
1729
let result = solana_copy_trade_detect::run(&args).await;
1830

tests/basic.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fn test_basic_flow() -> Result<(), Box<dyn std::error::Error>> {
99
let mut cmd = Command::cargo_bin("solana-copy-trade-detect")?;
1010
cmd.args([
1111
"-w",
12-
"E5nrNt3zGXwin6KvKsDqesPoou1GAbxZ2WdPHTF4q9if",
12+
"C8WtJP4YveQbza5k1otS7BNFQ6My4pjVwecApCEQCNQi",
1313
"--swap-num",
1414
"5",
1515
"--scan-tx-count",
@@ -20,7 +20,7 @@ fn test_basic_flow() -> Result<(), Box<dyn std::error::Error>> {
2020

2121
// Check if the output is valid JSON
2222
let output = String::from_utf8(output.stdout)?;
23+
println!("Output: {}", output);
2324
serde_json::from_str::<serde_json::Value>(&output).expect("Failed to parse json");
24-
println!("{}", output);
2525
Ok(())
2626
}

0 commit comments

Comments
 (0)