avalanche-atomic-swap-daemon 0.2.0

Production-ready HTLC atomic swap daemon for Avalanche C-Chain and Subnet-EVM chains with bidirectional support and Prometheus metrics
use axum::{routing::get, Router};
use prometheus::{Counter, Encoder, Gauge, TextEncoder, register_counter, register_gauge};
use std::sync::OnceLock;

static INITIATED: OnceLock<Counter> = OnceLock::new();
static COMPLETED: OnceLock<Counter> = OnceLock::new();
static IN_FLIGHT: OnceLock<Gauge> = OnceLock::new();

pub fn init_metrics() {
    let _ = INITIATED.set(register_counter!("avalanche_atomic_swaps_initiated_total", "Total initiated swaps").unwrap());
    let _ = COMPLETED.set(register_counter!("avalanche_atomic_swaps_completed_total", "Total completed swaps").unwrap());
    let _ = IN_FLIGHT.set(register_gauge!("avalanche_atomic_swaps_in_flight", "Current in-flight swaps").unwrap());
}

pub fn inc_initiated() { if let Some(c) = INITIATED.get() { c.inc(); } }
pub fn inc_completed() { if let Some(c) = COMPLETED.get() { c.inc(); } }
pub fn set_in_flight(n: usize) { if let Some(g) = IN_FLIGHT.get() { g.set(n as f64); } }

pub async fn start_metrics_server(port: u16) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let app = Router::new().route("/metrics", get(metrics_handler));
    let listener = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", port)).await?;
    tracing::info!(" Prometheus metrics available at http://0.0.0.0:{}", port);
    axum::serve(listener, app).await?;
    Ok(())
}

async fn metrics_handler() -> String {
    let encoder = TextEncoder::new();
    let mut buffer = vec![];
    encoder.encode(&prometheus::gather(), &mut buffer).unwrap();
    String::from_utf8(buffer).unwrap()
}