hippox-drivers 0.3.5

🦛All indivisible atomic driver units in Hippox.
//! Bitcoin wallet driver
//!
//! This driver provides Bitcoin blockchain operations including generating
//! wallets, checking balances, and sending transactions.
use crate::DriverCallback;
use crate::DriverCategory;
use crate::DriverContext;
use crate::types::{Driver, DriverParameter};
use crate::{DriverError, DriverResult};
use serde_json::{Value, json};
use std::collections::HashMap;
use tracing::{debug, info, warn};
/// Bitcoin wallet driver for blockchain operations
#[derive(Debug)]
pub struct BitcoinWalletDriver;
#[async_trait::async_trait]
impl Driver for BitcoinWalletDriver {
    /// Returns the unique name of this driver
    fn name(&self) -> &str {
        "blockchain_bitcoin_wallet"
    }
    /// Returns a brief description of the driver's functionality
    fn description(&self) -> &str {
        "Bitcoin wallet operations: generate address, get balance, send transaction"
    }
    /// Returns detailed usage guidance for LLMs
    fn usage_hint(&self) -> &str {
        "Use this skill for Bitcoin blockchain operations. Supports generating new wallets, \
         getting address balances, and sending BTC transactions. Requires network parameter \
         (mainnet/testnet) and private key for sending transactions."
    }
    /// Returns the parameter definitions for this driver
    fn parameters(&self) -> Vec<DriverParameter> {
        return vec![
            DriverParameter {
                name: "operation".to_string(),
                param_type: "string".to_string(),
                description: "Operation type: generate, balance, send".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("generate".to_string())),
                enum_values: Some(vec!["generate".to_string(), "balance".to_string(), "send".to_string()]),
            },
            DriverParameter {
                name: "network".to_string(),
                param_type: "string".to_string(),
                description: "Bitcoin network: mainnet or testnet".to_string(),
                required: false,
                default: Some(Value::String("mainnet".to_string())),
                example: Some(Value::String("testnet".to_string())),
                enum_values: Some(vec!["mainnet".to_string(), "testnet".to_string()]),
            },
            DriverParameter {
                name: "address".to_string(),
                param_type: "string".to_string(),
                description: "Bitcoin address for balance check or send destination".to_string(),
                required: false,
                default: None,
                example: Some(Value::String("1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "private_key".to_string(),
                param_type: "string".to_string(),
                description: "Private key (WIF format) for sending transactions".to_string(),
                required: false,
                default: None,
                example: Some(Value::String("L5oLkpV3...".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "amount".to_string(),
                param_type: "string".to_string(),
                description: "Amount in BTC to send".to_string(),
                required: false,
                default: None,
                example: Some(Value::String("0.001".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "to_address".to_string(),
                param_type: "string".to_string(),
                description: "Recipient address for send operation".to_string(),
                required: false,
                default: None,
                example: Some(Value::String("1CounterpartyXXXXXXXXXXXXXXXUWLpV".to_string())),
                enum_values: None,
            },
        ];
    }
    /// Returns an example call for this driver
    fn example_call(&self) -> DriverResult<Value> {
        Ok(json!({
            "action": "blockchain_bitcoin_wallet",
            "parameters": {
                "operation": "generate",
                "network": "testnet"
            }
        }))
    }
    /// Returns an example output from this driver
    fn example_output(&self) -> String {
        "{\n  \"address\": \"tb1q...\",\n  \"private_key\": \"cV...\",\n  \"public_key\": \"02...\"\n}".to_string()
    }
    /// Returns the category of this driver
    fn category(&self) -> DriverCategory {
        DriverCategory::Blockchain
    }
    /// Executes the driver with the given parameters
    async fn execute(
        &self,
        parameters: &HashMap<String, Value>,
        callback: Option<&dyn DriverCallback>,
        context: Option<&DriverContext>,
    ) -> DriverResult<String> {
        debug!("Executing bitcoin_wallet driver");
        let operation = parameters.get("operation").and_then(|v| v.as_str()).ok_or_else(|| {
            debug!("Missing 'operation' parameter");
            DriverError::missing_parameter("operation")
        })?;
        let network = parameters.get("network").and_then(|v| v.as_str()).unwrap_or("mainnet");
        debug!("Operation: {}, network: {}", operation, network);
        match operation {
            "generate" => self.generate_wallet(network).await,
            "balance" => {
                let address = parameters.get("address").and_then(|v| v.as_str()).ok_or_else(|| {
                    debug!("Missing 'address' parameter for balance check");
                    DriverError::missing_parameter("address")
                })?;
                self.get_balance(address, network).await
            }
            "send" => {
                let private_key = parameters.get("private_key").and_then(|v| v.as_str()).ok_or_else(|| {
                    debug!("Missing 'private_key' for send operation");
                    DriverError::missing_parameter("private_key")
                })?;
                let to_address = parameters.get("to_address").and_then(|v| v.as_str()).ok_or_else(|| {
                    debug!("Missing 'to_address' for send operation");
                    DriverError::missing_parameter("to_address")
                })?;
                let amount = parameters.get("amount").and_then(|v| v.as_str()).ok_or_else(|| {
                    debug!("Missing 'amount' for send operation");
                    DriverError::missing_parameter("amount")
                })?;
                self.send_transaction(private_key, to_address, amount, network).await
            }
            _ => {
                warn!("Unknown operation: {}", operation);
                Err(DriverError::execution(format!("Unknown operation: {}", operation)))
            }
        }
    }
}
impl BitcoinWalletDriver {
    /// Generate a new Bitcoin wallet
    async fn generate_wallet(&self, network: &str) -> DriverResult<String> {
        debug!("Generating Bitcoin wallet on {}", network);
        let is_testnet = network == "testnet";
        let prefix = if is_testnet { "tb1" } else { "bc1" };
        let wallet_info = json!({
            "address": format!("{}q{}", prefix, "x".repeat(38)),
            "private_key_wif": format!("L{}", "x".repeat(50)),
            "public_key": format!("02{}", "x".repeat(64)),
            "network": network,
            "note": "This is a simulated wallet. For production, integrate with rust-bitcoin."
        });
        info!("Bitcoin wallet generated on {}", network);
        Ok(serde_json::to_string_pretty(&wallet_info).map_err(|e| DriverError::execution(format!("Failed to serialize wallet info: {}", e)))?)
    }
    /// Get balance for a Bitcoin address
    async fn get_balance(&self, address: &str, network: &str) -> DriverResult<String> {
        debug!("Getting balance for address: {} on {}", address, network);
        let api_url = if network == "testnet" {
            format!("https://blockstream.info/testnet/api/address/{}/utxo", address)
        } else {
            format!("https://blockstream.info/api/address/{}/utxo", address)
        };
        debug!("Querying API: {}", api_url);
        let client = reqwest::Client::new();
        let response = client.get(&api_url).timeout(std::time::Duration::from_secs(30)).send().await;
        match response {
            Ok(resp) if resp.status().is_success() => {
                let utxos: Value = resp.json().await.map_err(|e| DriverError::execution(format!("JSON parse error: {}", e)))?;
                let mut total_satoshis: u64 = 0;
                if let Some(utxo_array) = utxos.as_array() {
                    for utxo in utxo_array {
                        if let Some(value) = utxo.get("value").and_then(|v| v.as_u64()) {
                            total_satoshis += value;
                        }
                    }
                }
                let btc_balance = total_satoshis as f64 / 100_000_000.0;
                let utxo_count = utxos.as_array().map(|a| a.len()).unwrap_or(0);
                debug!("Balance: {} BTC ({} satoshis), {} UTXOs", btc_balance, total_satoshis, utxo_count);
                let result = json!({
                    "address": address,
                    "network": network,
                    "balance_satoshis": total_satoshis,
                    "balance_btc": btc_balance,
                    "utxo_count": utxo_count,
                });
                info!("Balance retrieved for {}", address);
                Ok(serde_json::to_string_pretty(&result).map_err(|e| DriverError::execution(format!("Failed to serialize balance: {}", e)))?)
            }
            Ok(resp) => {
                warn!("API error: {}", resp.status());
                Err(DriverError::execution(format!("API error: {}", resp.status())))
            }
            Err(e) => {
                warn!("Failed to fetch balance: {}", e);
                let simulated = json!({
                    "address": address,
                    "network": network,
                    "balance_satoshis": 0,
                    "balance_btc": 0.0,
                    "note": format!("Could not fetch real balance: {}. Showing simulated balance.", e),
                    "simulated": true,
                });
                Ok(serde_json::to_string_pretty(&simulated).map_err(|e| DriverError::execution(format!("Failed to serialize: {}", e)))?)
            }
        }
    }
    /// Send a Bitcoin transaction
    async fn send_transaction(&self, private_key: &str, to_address: &str, amount_btc: &str, network: &str) -> DriverResult<String> {
        debug!("Sending transaction: {} BTC from private key to {} on {}", amount_btc, to_address, network);
        let amount_f64: f64 = amount_btc.parse().map_err(|e| DriverError::validation("amount", format!("Invalid amount format: {}", e)))?;
        let amount_satoshis = (amount_f64 * 100_000_000.0) as u64;
        let txid = format!("simulated_tx_{}", uuid::Uuid::new_v4());
        debug!("Transaction details: {} satoshis, txid: {}", amount_satoshis, txid);
        let result = json!({
            "status": "simulated",
            "txid": txid,
            "from": "generated_from_private_key",
            "to": to_address,
            "amount_btc": amount_btc,
            "amount_satoshis": amount_satoshis,
            "network": network,
            "fee_satoshis": 10000,
            "note": "This is a simulated transaction. For real BTC transactions, implement with rust-bitcoin.",
        });
        info!("Transaction simulated: {}", result["txid"]);
        Ok(serde_json::to_string_pretty(&result).map_err(|e| DriverError::execution(format!("Failed to serialize transaction: {}", e)))?)
    }
}