indodax-cli 0.1.49

A command-line interface for the Indodax cryptocurrency exchange
Documentation
use rmcp::model::{CallToolResult, Tool};

use super::IndodaxMcp;
use crate::commands::alert;

pub fn alert_tools() -> Vec<Tool> {
    vec![
        IndodaxMcp::tool_def(
            "alert_add",
            "Create a new price alert for a specific trading pair. Alerts can be triggered by absolute price thresholds or percentage changes.",
            serde_json::json!({
                "pair": IndodaxMcp::str_param("The trading pair to monitor (e.g., 'btc_idr').", true, None),
                "above": IndodaxMcp::num_param("Trigger the alert when the price rises ABOVE this absolute value.", false),
                "below": IndodaxMcp::num_param("Trigger the alert when the price falls BELOW this absolute value.", false),
                "percent_up": IndodaxMcp::num_param("Trigger the alert when the price increases by this percentage from the current price.", false),
                "percent_down": IndodaxMcp::num_param("Trigger the alert when the price decreases by this percentage from the current price.", false),
                "note": IndodaxMcp::str_param("Optional: A custom note or label to include with the alert notification.", false, None),
            }),
            vec!["pair"],
        ),
        IndodaxMcp::tool_def(
            "alert_list",
            "Retrieve a list of all configured price alerts. By default, only active alerts are shown.",
            serde_json::json!({
                "history": IndodaxMcp::bool_param("Set to true to also include triggered and cancelled alerts in the output."),
            }),
            vec![],
        ),
        IndodaxMcp::tool_def(
            "alert_cancel",
            "Cancel one or more existing price alerts. Use this to stop monitoring specific price conditions.",
            serde_json::json!({
                "id": IndodaxMcp::num_param("The unique ID of the specific alert to cancel.", false),
                "all": IndodaxMcp::bool_param("Set to true to cancel ALL currently active alerts."),
            }),
            vec![],
        ),
        IndodaxMcp::tool_def(
            "alert_check",
            "Manually trigger a check of active alerts against current market prices. Any alerts whose conditions are met will be marked as triggered.",
            serde_json::json!({
                "id": IndodaxMcp::num_param("Optional: Only check a specific alert by its ID.", false),
                "pair": IndodaxMcp::str_param("Optional: Only check alerts for a specific trading pair.", false, None),
            }),
            vec![],
        ),
    ]
}

impl IndodaxMcp {
    pub async fn handle_alert_add(
        &self,
        pair: &str,
        above: Option<f64>,
        below: Option<f64>,
        percent_up: Option<f64>,
        percent_down: Option<f64>,
        note: Option<String>,
    ) -> CallToolResult {
        if pair.is_empty() {
            return Self::validation_error_result("Missing required parameter: pair".into());
        }
        if above.is_none() && below.is_none() && percent_up.is_none() && percent_down.is_none() {
            return Self::validation_error_result(
                "Must provide at least one trigger: above, below, percent_up, or percent_down".into()
            );
        }
        let pair = crate::commands::helpers::normalize_pair(pair);
        match alert::alert_add(&pair, above, below, percent_up, percent_down, note, &self.client).await {
            Ok(output) => Self::json_result(output.data),
            Err(e) => Self::error_result(e.to_string()),
        }
    }

    pub async fn handle_alert_list(&self, history: bool) -> CallToolResult {
        match alert::alert_list(history) {
            Ok(output) => Self::json_result(output.data),
            Err(e) => Self::error_result(e.to_string()),
        }
    }

    pub async fn handle_alert_cancel(&self, id: Option<f64>, cancel_all: bool) -> CallToolResult {
        if id.is_none() && !cancel_all {
            return Self::validation_error_result("Must provide either `id` or `all`".into());
        }
        if let Some(v) = id {
            if v.fract() != 0.0 {
                return Self::validation_error_result(format!("alert ID must be a whole number, got {}", v));
            }
        }
        let id = id.map(|v| v as u64);
        match alert::alert_cancel(id, cancel_all) {
            Ok(output) => Self::json_result(output.data),
            Err(e) => Self::error_result(e.to_string()),
        }
    }

    pub async fn handle_alert_check(
        &self,
        id: Option<f64>,
        pair: Option<String>,
    ) -> CallToolResult {
        if let Some(v) = id {
            if v.fract() != 0.0 {
                return Self::validation_error_result(format!("alert ID must be a whole number, got {}", v));
            }
        }
        let id = id.map(|v| v as u64);
        let pair = pair.as_deref().map(|p| crate::commands::helpers::normalize_pair(p));
        match alert::alert_check(&self.client, id, pair.as_deref()).await {
            Ok(output) => Self::json_result(output.data),
            Err(e) => Self::error_result(e.to_string()),
        }
    }
}