use super::RpcHandler; use anyhow::{Context, Result}; use serde::{Deserialize, Serialize}; #[derive(Debug, Serialize)] struct LndInfo { alias: String, num_active_channels: u32, num_peers: u32, synced_to_chain: bool, block_height: u64, balance_sats: i64, channel_balance_sats: i64, pending_open_balance: i64, } #[derive(Debug, Deserialize)] struct LndGetInfoResponse { alias: Option, num_active_channels: Option, num_peers: Option, synced_to_chain: Option, block_height: Option, } #[derive(Debug, Deserialize)] struct LndChannelBalanceResponse { local_balance: Option, pending_open_local_balance: Option, } #[derive(Debug, Deserialize)] struct LndBalanceResponse { total_balance: Option, #[allow(dead_code)] confirmed_balance: Option, } #[derive(Debug, Deserialize)] struct LndAmount { sat: Option, } impl RpcHandler { pub(super) async fn handle_lnd_getinfo(&self) -> Result { let macaroon_path = "/var/lib/archipelago/lnd/data/chain/bitcoin/mainnet/admin.macaroon"; let macaroon_bytes = tokio::fs::read(macaroon_path) .await .context("Failed to read LND admin macaroon — is LND installed?")?; let macaroon_hex = hex::encode(&macaroon_bytes); let client = reqwest::Client::builder() .timeout(std::time::Duration::from_secs(10)) .danger_accept_invalid_certs(true) .build() .context("Failed to create HTTP client")?; let get_info: LndGetInfoResponse = client .get("https://127.0.0.1:8080/v1/getinfo") .header("Grpc-Metadata-macaroon", &macaroon_hex) .send() .await .context("LND REST connection failed")? .json() .await .context("Failed to parse LND getinfo response")?; let channel_balance: LndChannelBalanceResponse = match client .get("https://127.0.0.1:8080/v1/balance/channels") .header("Grpc-Metadata-macaroon", &macaroon_hex) .send() .await { Ok(resp) => resp.json().await.unwrap_or(LndChannelBalanceResponse { local_balance: None, pending_open_local_balance: None, }), Err(_) => LndChannelBalanceResponse { local_balance: None, pending_open_local_balance: None, }, }; let wallet_balance: LndBalanceResponse = match client .get("https://127.0.0.1:8080/v1/balance/blockchain") .header("Grpc-Metadata-macaroon", &macaroon_hex) .send() .await { Ok(resp) => resp.json().await.unwrap_or(LndBalanceResponse { total_balance: None, confirmed_balance: None, }), Err(_) => LndBalanceResponse { total_balance: None, confirmed_balance: None, }, }; let info = LndInfo { alias: get_info.alias.unwrap_or_default(), num_active_channels: get_info.num_active_channels.unwrap_or(0), num_peers: get_info.num_peers.unwrap_or(0), synced_to_chain: get_info.synced_to_chain.unwrap_or(false), block_height: get_info.block_height.unwrap_or(0), balance_sats: wallet_balance .total_balance .and_then(|s| s.parse().ok()) .unwrap_or(0), channel_balance_sats: channel_balance .local_balance .and_then(|a| a.sat.and_then(|s| s.parse().ok())) .unwrap_or(0), pending_open_balance: channel_balance .pending_open_local_balance .and_then(|a| a.sat.and_then(|s| s.parse().ok())) .unwrap_or(0), }; Ok(serde_json::to_value(info)?) } }