use crate::bitcoin::error::BitcoinResult;
use bitcoin::{Amount, Network, PublicKey, PrivateKey};
use lightning::{
ln::msgs::{ChannelAnnouncement, ChannelUpdate, NodeAnnouncement},
routing::router::Router,
util::logger::Logger,
};
use lightning_invoice::{Invoice, PaymentRequest};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
#[derive(Debug, thiserror::Error)]
pub enum LightningError {
#[error("Channel not found: {0}")]
ChannelNotFound(String),
#[error("Insufficient funds: {0}")]
InsufficientFunds(String),
#[error("Payment failed: {0}")]
PaymentFailed(String),
#[error("Invalid invoice: {0}")]
InvalidInvoice(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Channel closed: {0}")]
ChannelClosed(String),
}
pub type LightningResult<T> = Result<T, LightningError>;
pub struct LightningNetwork {
network: Network,
channel_manager: Arc<ChannelManager>,
router: Arc<Router>,
node_announcements: Arc<RwLock<HashMap<PublicKey, NodeAnnouncement>>>,
channel_announcements: Arc<RwLock<HashMap<String, ChannelAnnouncement>>>,
payment_requests: Arc<RwLock<HashMap<String, PaymentRequest>>>,
}
pub struct ChannelManager {
channels: HashMap<String, Channel>,
total_capacity: Amount,
available_balance: Amount,
}
#[derive(Debug, Clone)]
pub struct Channel {
pub channel_id: String,
pub remote_pubkey: PublicKey,
pub capacity: Amount,
pub local_balance: Amount,
pub remote_balance: Amount,
pub state: ChannelState,
pub flags: u16,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ChannelState {
Opening,
Open,
Closing,
Closed,
Disputed,
}
#[derive(Debug, Clone)]
pub struct PaymentResult {
pub payment_hash: String,
pub amount_msat: u64,
pub status: PaymentStatus,
pub fee_msat: u64,
pub route: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum PaymentStatus {
Pending,
InFlight,
Succeeded,
Failed,
}
impl LightningNetwork {
pub fn new(network: Network) -> Self {
Self {
network,
channel_manager: Arc::new(ChannelManager::new()),
router: Arc::new(Router::new()),
node_announcements: Arc::new(RwLock::new(HashMap::new())),
channel_announcements: Arc::new(RwLock::new(HashMap::new())),
payment_requests: Arc::new(RwLock::new(HashMap::new())),
}
}
pub async fn create_invoice(
&self,
amount_msat: u64,
description: &str,
expiry_seconds: Option<u64>,
) -> LightningResult<PaymentRequest> {
let invoice = Invoice::new(
self.network,
amount_msat,
description.to_string(),
expiry_seconds.unwrap_or(3600), )
.map_err(|e| LightningError::InvalidInvoice(e.to_string()))?;
let payment_request = PaymentRequest::from_invoice(invoice)
.map_err(|e| LightningError::InvalidInvoice(e.to_string()))?;
let payment_hash = payment_request.payment_hash().to_string();
self.payment_requests.write().await.insert(payment_hash.clone(), payment_request.clone());
Ok(payment_request)
}
pub async fn send_payment(
&self,
payment_request: &str,
max_fee_msat: Option<u64>,
) -> LightningResult<PaymentResult> {
let request = PaymentRequest::from_str(payment_request)
.map_err(|e| LightningError::InvalidInvoice(e.to_string()))?;
let amount_msat = request.amount_msat()
.ok_or_else(|| LightningError::InvalidInvoice("No amount specified".to_string()))?;
let available = self.channel_manager.get_available_balance().await;
if available < amount_msat {
return Err(LightningError::InsufficientFunds(
format!("Required: {} msat, Available: {} msat", amount_msat, available)
));
}
let route = self.find_route(&request).await?;
let fee_msat = self.calculate_fee(&route).await;
if let Some(max_fee) = max_fee_msat {
if fee_msat > max_fee {
return Err(LightningError::PaymentFailed(
format!("Fee {} msat exceeds maximum {} msat", fee_msat, max_fee)
));
}
}
let payment_hash = request.payment_hash().to_string();
let result = self.send_payment_through_channels(&route, amount_msat, &payment_hash).await?;
Ok(PaymentResult {
payment_hash,
amount_msat,
status: PaymentStatus::Succeeded,
fee_msat,
route: route.iter().map(|node| node.to_string()).collect(),
})
}
pub async fn get_balance(&self) -> LightningResult<u64> {
Ok(self.channel_manager.get_available_balance().await)
}
pub async fn open_channel(
&self,
remote_pubkey: &str,
capacity_sat: u64,
push_msat: Option<u64>,
) -> LightningResult<String> {
let pubkey = PublicKey::from_str(remote_pubkey)
.map_err(|e| LightningError::NetworkError(format!("Invalid public key: {}", e)))?;
let capacity = Amount::from_sat(capacity_sat);
let push_amount = push_msat.map(Amount::from_millisat);
let channel_id = self.channel_manager.create_channel(pubkey, capacity, push_amount).await?;
Ok(channel_id)
}
pub async fn close_channel(&self, channel_id: &str) -> LightningResult<()> {
self.channel_manager.close_channel(channel_id).await?;
Ok(())
}
pub async fn get_channel(&self, channel_id: &str) -> LightningResult<Channel> {
self.channel_manager.get_channel(channel_id).await
}
pub async fn list_channels(&self) -> LightningResult<Vec<Channel>> {
Ok(self.channel_manager.list_channels().await)
}
async fn find_route(&self, request: &PaymentRequest) -> LightningResult<Vec<PublicKey>> {
let destination = request.destination();
let announcements = self.node_announcements.read().await;
let mut route = Vec::new();
if let Some(intermediate) = announcements.keys().next() {
route.push(*intermediate);
}
route.push(destination);
Ok(route)
}
async fn calculate_fee(&self, route: &[PublicKey]) -> u64 {
let base_fee = 1000; let fee_rate = 1;
base_fee + (route.len() as u64 * fee_rate)
}
async fn send_payment_through_channels(
&self,
route: &[PublicKey],
amount_msat: u64,
payment_hash: &str,
) -> LightningResult<()> {
self.channel_manager.update_channel_balances(amount_msat).await;
Ok(())
}
}
impl ChannelManager {
pub fn new() -> Self {
Self {
channels: HashMap::new(),
total_capacity: Amount::from_sat(0),
available_balance: Amount::from_sat(0),
}
}
pub async fn get_available_balance(&self) -> u64 {
self.available_balance.to_sat()
}
pub async fn create_channel(
&mut self,
remote_pubkey: PublicKey,
capacity: Amount,
push_amount: Option<Amount>,
) -> LightningResult<String> {
let channel_id = format!("channel_{}", uuid::Uuid::new_v4());
let local_balance = capacity - push_amount.unwrap_or(Amount::from_sat(0));
let remote_balance = push_amount.unwrap_or(Amount::from_sat(0));
let channel = Channel {
channel_id: channel_id.clone(),
remote_pubkey,
capacity,
local_balance,
remote_balance,
state: ChannelState::Opening,
flags: 0,
};
self.channels.insert(channel_id.clone(), channel);
self.total_capacity += capacity;
self.available_balance += local_balance;
Ok(channel_id)
}
pub async fn close_channel(&mut self, channel_id: &str) -> LightningResult<()> {
if let Some(channel) = self.channels.get_mut(channel_id) {
channel.state = ChannelState::Closing;
Ok(())
} else {
Err(LightningError::ChannelNotFound(channel_id.to_string()))
}
}
pub async fn get_channel(&self, channel_id: &str) -> LightningResult<Channel> {
self.channels.get(channel_id)
.cloned()
.ok_or_else(|| LightningError::ChannelNotFound(channel_id.to_string()))
}
pub async fn list_channels(&self) -> Vec<Channel> {
self.channels.values().cloned().collect()
}
pub async fn update_channel_balances(&mut self, amount_msat: u64) {
let amount = Amount::from_millisat(amount_msat);
if self.available_balance >= amount {
self.available_balance -= amount;
}
}
}
impl std::str::FromStr for PublicKey {
type Err = lightning::ln::msgs::DecodeError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let bytes = hex::decode(s)
.map_err(|_| lightning::ln::msgs::DecodeError::InvalidValue)?;
PublicKey::from_slice(&bytes)
.map_err(|_| lightning::ln::msgs::DecodeError::InvalidValue)
}
}
impl std::fmt::Display for PublicKey {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(self.serialize()))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_create_invoice() {
let lightning = LightningNetwork::new(Network::Testnet);
let invoice = lightning.create_invoice(1000, "Test payment", None).await.unwrap();
assert!(invoice.amount_msat().is_some());
assert_eq!(invoice.amount_msat().unwrap(), 1000);
}
#[tokio::test]
async fn test_open_channel() {
let mut lightning = LightningNetwork::new(Network::Testnet);
let remote_pubkey = "02eec7245d6b7d2ccb30380bfbe2a3648cd7a942653f5aa340edcea1f283686619";
let channel_id = lightning.open_channel(remote_pubkey, 100000, None).await.unwrap();
assert!(!channel_id.is_empty());
}
#[tokio::test]
async fn test_get_balance() {
let lightning = LightningNetwork::new(Network::Testnet);
let balance = lightning.get_balance().await.unwrap();
assert_eq!(balance, 0); }
}