use serde::{Deserialize, Serialize};
use crate::errors::AppError;
#[derive(Debug, Clone)]
pub struct SidecarClientConfig {
pub base_url: String,
pub timeout_ms: u64,
pub api_key: String,
}
#[derive(Debug, Serialize)]
pub struct DepositRequest {
pub user_private_key: String,
pub amount_lamports: u64,
}
#[derive(Debug, Deserialize)]
pub struct DepositResponse {
pub success: bool,
pub tx_signature: String,
pub user_pubkey: String,
}
#[derive(Debug, Serialize)]
pub struct WithdrawRequest {
pub user_private_key: String,
pub amount_lamports: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub target_currency: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct WithdrawResponse {
pub success: bool,
pub tx_signature: String,
pub fee_lamports: i64,
pub amount_lamports: i64,
pub is_partial: bool,
pub currency: Option<String>,
pub swap_tx_signature: Option<String>,
pub output_amount: Option<String>,
pub swap_failed: Option<bool>,
pub swap_error: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct SwapAndDepositRequest {
pub user_private_key: String,
pub input_mint: String,
pub amount: String,
}
#[derive(Debug, Deserialize)]
pub struct SwapAndDepositResponse {
pub success: bool,
pub swap_tx_signature: String,
pub deposit_tx_signature: String,
pub sol_amount_lamports: i64,
pub input_mint: String,
pub input_amount: String,
pub user_pubkey: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchSwapRequest {
pub private_key: String,
pub amount_lamports: u64,
pub output_currency: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchSwapResponse {
pub success: bool,
pub tx_signature: String,
pub input_lamports: u64,
pub output_amount: String,
pub output_currency: String,
pub error: Option<String>,
}
#[derive(Debug, Serialize)]
pub struct BalanceRequest {
pub user_private_key: String,
}
#[derive(Debug, Deserialize)]
pub struct BalanceResponse {
pub balance_lamports: u64,
pub balance_sol: f64,
pub user_pubkey: String,
}
#[derive(Debug, Serialize)]
pub struct TransferSolRequest {
pub user_private_key: String,
pub destination: String,
pub amount_lamports: u64,
}
#[derive(Debug, Serialize)]
pub struct TransferSplRequest {
pub user_private_key: String,
pub destination: String,
pub token_mint: String,
pub amount: String,
}
#[derive(Debug, Deserialize)]
pub struct TransferResponse {
pub success: bool,
pub tx_signature: String,
pub fee_lamports: i64,
}
impl SidecarSuccess for TransferResponse {
fn is_success(&self) -> bool {
self.success
}
}
#[derive(Debug, Serialize)]
pub struct WalletBalancesRequest {
pub wallet_address: String,
}
#[derive(Debug, Clone, Deserialize, serde::Serialize)]
pub struct TokenBalanceEntry {
pub mint: String,
pub amount: String,
pub decimals: u8,
}
#[derive(Debug, Deserialize, serde::Serialize)]
pub struct WalletBalancesResponse {
pub sol_lamports: u64,
pub tokens: Vec<TokenBalanceEntry>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifySolTransferRequest {
pub signature: String,
pub expected_source: String,
pub expected_destination: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub min_lamports: Option<u64>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VerifySolTransferResponse {
pub ok: bool,
pub signature: String,
pub observed_lamports: u64,
pub source: String,
pub destination: String,
}
#[derive(Debug, Deserialize)]
pub struct HealthResponse {
pub status: String,
pub timestamp: String,
pub network: String,
pub checks: HealthChecks,
}
#[derive(Debug, Deserialize)]
pub struct HealthChecks {
pub rpc_connected: bool,
pub sdk_loaded: bool,
}
#[derive(Debug, Deserialize)]
pub(crate) struct ErrorResponse {
pub error: String,
#[serde(default)]
pub details: Option<String>,
}
pub(crate) trait SidecarSuccess {
fn is_success(&self) -> bool;
fn failure_reason(&self) -> Option<&str> {
None
}
}
impl SidecarSuccess for DepositResponse {
fn is_success(&self) -> bool {
self.success
}
}
impl SidecarSuccess for WithdrawResponse {
fn is_success(&self) -> bool {
self.success
}
fn failure_reason(&self) -> Option<&str> {
self.swap_error.as_deref()
}
}
impl SidecarSuccess for SwapAndDepositResponse {
fn is_success(&self) -> bool {
self.success
}
}
impl SidecarSuccess for BatchSwapResponse {
fn is_success(&self) -> bool {
self.success
}
fn failure_reason(&self) -> Option<&str> {
self.error.as_deref()
}
}
pub(crate) fn ensure_sidecar_success<T: SidecarSuccess>(value: T) -> Result<T, AppError> {
if value.is_success() {
Ok(value)
} else if let Some(reason) = value.failure_reason() {
Err(AppError::Internal(anyhow::anyhow!(
"Sidecar reported failure: {}",
reason
)))
} else {
Err(AppError::Internal(anyhow::anyhow!(
"Sidecar reported failure (success=false)"
)))
}
}