use clap::Subcommand;
use tail_fin_common::TailFinError;
use crate::session::{browser_session, print_json, Ctx};
#[derive(Subcommand)]
pub enum NansenAction {
#[command(hide = true)]
Discover {
#[arg(default_value = "/tokens")]
page: String,
},
Search {
query: String,
#[arg(long, default_value = "any")]
result_type: String,
#[arg(long, default_value_t = 10)]
limit: usize,
},
TokenInfo {
address: String,
#[arg(long, default_value = "ethereum")]
chain: String,
},
TokenScreener {
#[arg(long, default_value = "ethereum,solana")]
chains: String,
#[arg(long, default_value = "netflow")]
sort: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
TokenFlows {
address: String,
#[arg(long, default_value = "ethereum")]
chain: String,
#[arg(long, default_value = "7d")]
period: String,
},
TokenHolders {
address: String,
#[arg(long, default_value = "ethereum")]
chain: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
TokenTrades {
address: String,
#[arg(long, default_value = "ethereum")]
chain: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
TokenTransfers {
address: String,
#[arg(long, default_value = "ethereum")]
chain: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
SmartMoneyNetflow {
#[arg(long, default_value = "ethereum,solana")]
chains: String,
},
SmartMoneyTrades {
#[arg(long, default_value = "ethereum,solana")]
chains: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
SmartMoneyHoldings {
#[arg(long, default_value = "all")]
chains: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
Wallet { address: String },
WalletTxns {
address: String,
#[arg(long, default_value = "evm")]
chain: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
WalletCounterparties {
address: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
HlLeaderboard {
#[arg(long, default_value = "totalPnl")]
sort: String,
#[arg(long, default_value_t = 1)]
page: u32,
},
ChainGrowth {
#[arg(long, default_value = "7d")]
period: String,
},
}
pub async fn run(action: NansenAction, ctx: &Ctx) -> Result<(), TailFinError> {
let session = if let Some(ref host) = ctx.connect {
browser_session(host, ctx.headed).await?
} else {
eprintln!("Launching stealth browser for Nansen (Cloudflare bypass)...");
let s = night_fury_core::BrowserSession::builder()
.headed(ctx.headed)
.cloudflare_timeout(std::time::Duration::from_secs(30))
.launch_stealth("https://app.nansen.ai")
.await?;
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
s
};
let client = tail_fin_nansen::NansenClient::new(session);
if let Some(ref cookies_flag) = ctx.cookies {
let path = if cookies_flag == "auto" {
crate::session::default_cookies_path("nansen")
} else {
std::path::PathBuf::from(cookies_flag)
};
eprintln!("Injecting auth cookies from {}...", path.display());
client.inject_cookies(&path).await?;
}
match action {
NansenAction::Discover { page } => {
print_json(&client.discover_api_routes(&page).await?)?;
}
NansenAction::Search {
query,
result_type,
limit,
} => {
print_json(&client.search(&query, &result_type, limit).await?)?;
}
NansenAction::TokenInfo { address, chain } => {
print_json(&client.token_info(&address, &chain).await?)?;
}
NansenAction::TokenScreener { chains, sort, page } => {
let c: Vec<&str> = chains.split(',').collect();
print_json(&client.token_screener(&c, &sort, page).await?)?;
}
NansenAction::TokenFlows {
address,
chain,
period,
} => {
print_json(&client.token_flows(&address, &chain, &period).await?)?;
}
NansenAction::TokenHolders {
address,
chain,
page,
} => {
print_json(&client.token_holders(&address, &chain, page).await?)?;
}
NansenAction::TokenTrades {
address,
chain,
page,
} => {
print_json(&client.token_trades(&address, &chain, page).await?)?;
}
NansenAction::TokenTransfers {
address,
chain,
page,
} => {
print_json(&client.token_transfers(&address, &chain, page).await?)?;
}
NansenAction::SmartMoneyNetflow { chains } => {
let c: Vec<&str> = chains.split(',').collect();
print_json(&client.smart_money_netflow(&c).await?)?;
}
NansenAction::SmartMoneyTrades { chains, page } => {
let c: Vec<&str> = chains.split(',').collect();
print_json(&client.smart_money_trades(&c, page).await?)?;
}
NansenAction::SmartMoneyHoldings { chains, page } => {
let c: Vec<&str> = chains.split(',').collect();
print_json(&client.smart_money_holdings(&c, page).await?)?;
}
NansenAction::Wallet { address } => {
print_json(&client.wallet(&address).await?)?;
}
NansenAction::WalletTxns {
address,
chain,
page,
} => {
print_json(&client.wallet_txns(&address, &chain, page).await?)?;
}
NansenAction::WalletCounterparties { address, page } => {
print_json(&client.wallet_counterparties(&address, page).await?)?;
}
NansenAction::HlLeaderboard { sort, page } => {
print_json(&client.hl_leaderboard(&sort, page).await?)?;
}
NansenAction::ChainGrowth { period } => {
print_json(&client.chain_growth(&period).await?)?;
}
}
Ok(())
}
pub struct Adapter;
impl crate::adapter::CliAdapter for Adapter {
fn name(&self) -> &'static str {
"nansen"
}
fn about(&self) -> &'static str {
"Nansen blockchain analytics (stealth browser + cookies)"
}
fn command(&self) -> clap::Command {
<NansenAction as clap::Subcommand>::augment_subcommands(
clap::Command::new("nansen")
.about("Nansen blockchain analytics (stealth browser + cookies)"),
)
}
fn dispatch<'a>(
&'a self,
matches: &'a clap::ArgMatches,
ctx: &'a crate::session::Ctx,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), TailFinError>> + Send + 'a>>
{
Box::pin(async move {
let action = <NansenAction as clap::FromArgMatches>::from_arg_matches(matches)
.map_err(|e| TailFinError::Api(e.to_string()))?;
run(action, ctx).await
})
}
}