1use crate::AnyaError;
6use crate::AnyaResult;
7use secp256k1::SecretKey as Secp256k1SecretKey;
8use std::collections::HashMap;
9use std::fmt;
10use std::str::FromStr;
11use std::sync::{Arc, Mutex};
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use crate::bitcoin::config::BitcoinConfig;
16
17#[derive(Clone)]
19pub struct LightningPublicKey {
20 pub bytes: [u8; 33],
21}
22
23impl fmt::Debug for LightningPublicKey {
24 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25 write!(f, "LightningPublicKey({})", hex::encode(self.bytes))
26 }
27}
28
29impl std::str::FromStr for LightningPublicKey {
30 type Err = String;
31
32 fn from_str(s: &str) -> Result<Self, Self::Err> {
33 if s.len() != 66 {
34 return Err("Invalid public key length".to_string());
35 }
36
37 let hex_str = s.strip_prefix("0x").unwrap_or(s);
39
40 let mut bytes = [0u8; 33];
42 hex::decode_to_slice(hex_str, &mut bytes)
43 .map_err(|e| format!("Invalid hex format: {e}"))?;
44
45 Ok(LightningPublicKey { bytes })
46 }
47}
48
49impl LightningPublicKey {
50 pub fn from_secret_key(
51 secp: &secp256k1::Secp256k1<secp256k1::All>,
52 secret_key: &Secp256k1SecretKey,
53 ) -> Self {
54 let public_key = secp256k1::PublicKey::from_secret_key(secp, secret_key);
55 let mut bytes = [0u8; 33];
56 bytes.copy_from_slice(&public_key.serialize());
57 LightningPublicKey { bytes }
58 }
59}
60
61impl fmt::Display for LightningPublicKey {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 write!(f, "{}", hex::encode(self.bytes))
64 }
65}
66
67#[derive(Debug, Clone, PartialEq, Eq)]
69pub struct LightningTxid([u8; 32]);
70
71impl LightningTxid {
72 pub fn from_slice(slice: &[u8]) -> Result<Self, String> {
73 if slice.len() != 32 {
74 return Err("Invalid txid length".to_string());
75 }
76 let mut bytes = [0u8; 32];
77 bytes.copy_from_slice(slice);
78 Ok(LightningTxid(bytes))
79 }
80}
81
82impl fmt::Display for LightningTxid {
83 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
84 write!(f, "{}", hex::encode(self.0))
85 }
86}
87
88#[derive(Debug, Clone)]
90pub struct NodeInfo {
91 pub pubkey: String,
92 pub addresses: Vec<String>,
93 pub alias: Option<String>,
94 pub color: Option<String>,
95 pub features: Vec<String>,
96}
97
98pub struct LightningNode {
100 config: BitcoinConfig,
102
103 state: Mutex<LightningState>,
105
106 #[allow(dead_code)]
108 secp: LightningSecp256k1<All>,
110
111 pub node_id: LightningPublicKey,
113}
114
115struct LightningState {
117 channels: HashMap<String, Channel>,
119
120 peers: HashMap<String, PeerInfo>,
122
123 invoices: HashMap<String, Invoice>,
125
126 payments: HashMap<String, Payment>,
128
129 last_updated: u64,
131}
132
133#[derive(Debug, Clone)]
135pub struct Channel {
136 pub channel_id: String,
138
139 pub funding_txid: LightningTxid,
141
142 pub funding_output_idx: u32,
144
145 pub capacity: u64,
147
148 pub local_balance: u64,
150
151 pub remote_balance: u64,
153
154 pub remote_pubkey: LightningPublicKey,
156
157 pub is_active: bool,
159
160 pub is_public: bool,
162
163 pub short_channel_id: Option<String>,
165}
166
167#[derive(Debug, Clone)]
169pub struct PeerInfo {
170 pub pubkey: LightningPublicKey,
172
173 pub addresses: Vec<String>,
175
176 pub alias: Option<String>,
178
179 pub color: Option<String>,
181
182 pub is_connected: bool,
184
185 pub connected_since: Option<u64>,
187}
188
189#[derive(Debug, Clone)]
191pub struct Invoice {
192 pub bolt11: String,
194
195 pub payment_hash: String,
197
198 pub description: String,
200
201 pub amount_msat: Option<u64>,
203
204 pub expiry: u32,
206
207 pub timestamp: u64,
209
210 pub is_paid: bool,
212
213 pub paid_at: Option<u64>,
215}
216
217#[derive(Debug, Clone)]
219pub struct Payment {
220 pub payment_id: String,
222
223 pub payment_hash: String,
225
226 pub preimage: Option<String>,
228
229 pub amount_msat: u64,
231
232 pub fee_msat: u64,
234
235 pub status: PaymentStatus,
237
238 pub created_at: u64,
240
241 pub resolved_at: Option<u64>,
243
244 pub description: Option<String>,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq, Eq)]
250pub enum PaymentStatus {
251 Pending,
253
254 Succeeded,
256
257 Failed,
259}
260
261pub struct BitcoinLightningBridge {
263 lightning_node: Arc<LightningNode>,
265
266 channel_transactions: Mutex<HashMap<String, ChannelTransaction>>,
268
269 funding_addresses: Mutex<HashMap<String, FundingAddress>>,
271
272 last_scanned_height: Mutex<u32>,
274}
275
276#[derive(Debug, Clone)]
278pub struct ChannelTransaction {
279 pub channel_id: String,
281
282 pub funding_txid: LightningTxid,
284
285 pub funding_output_idx: u32,
287
288 pub funding_amount: u64,
290
291 pub status: ChannelTransactionStatus,
293
294 pub confirmation_height: Option<u32>,
296
297 pub closing_txid: Option<LightningTxid>,
299
300 pub created_at: u64,
302
303 pub updated_at: u64,
305}
306
307#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum ChannelTransactionStatus {
310 Pending,
312
313 Confirmed,
315
316 Closed,
318}
319
320#[derive(Debug, Clone)]
322pub struct FundingAddress {
323 pub address: String,
325
326 pub required_amount: u64,
328
329 pub channel_params: ChannelParameters,
331
332 pub created_at: u64,
334}
335
336#[derive(Debug, Clone)]
338pub struct ChannelParameters {
339 pub peer_pubkey: LightningPublicKey,
341
342 pub push_msat: Option<u64>,
344
345 pub is_private: bool,
347}
348
349impl LightningNode {
350 pub fn new(config: &BitcoinConfig) -> AnyaResult<Self> {
352 let secp = LightningSecp256k1::new();
353
354 let node_secret = Secp256k1SecretKey::from_slice(&[0x42; 32])
356 .map_err(|e| AnyaError::Bitcoin(format!("Failed to create Lightning node key: {e}")))?;
357 let node_id = LightningPublicKey::from_secret_key(&secp, &node_secret);
358
359 let state = LightningState {
361 channels: HashMap::new(),
362 peers: HashMap::new(),
363 invoices: HashMap::new(),
364 payments: HashMap::new(),
365 last_updated: current_time(),
366 };
367
368 Ok(Self {
369 config: config.clone(),
370 state: Mutex::new(state),
371 secp,
372 node_id,
373 })
374 }
375
376 pub fn get_node_info(&self) -> AnyaResult<NodeInfo> {
378 Ok(NodeInfo {
379 pubkey: self.node_id.to_string(),
380 addresses: vec![format!("127.0.0.1:9735")], alias: Some("Anya Lightning Node".to_string()),
382 color: Some("#3399FF".to_string()),
383 features: vec![
384 "option_static_remotekey".to_string(),
385 "option_anchor_outputs".to_string(),
386 "option_route_blinding".to_string(),
387 ],
388 })
389 }
390
391 pub fn connect_peer(&self, node_pubkey: &str, host: &str, port: u16) -> AnyaResult<()> {
393 let pubkey = LightningPublicKey::from_str(node_pubkey)
394 .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
395
396 let mut state = self
397 .state
398 .lock()
399 .map_err(|e| format!("Mutex lock error: {e}"))?;
400
401 if state.peers.contains_key(node_pubkey) {
403 return Err(AnyaError::Bitcoin(format!(
404 "Already connected to {node_pubkey}"
405 )));
406 }
407
408 let peer_info = PeerInfo {
410 pubkey,
411 addresses: vec![format!("{}:{}", host, port)],
412 alias: None,
413 color: None,
414 is_connected: true,
415 connected_since: Some(current_time()),
416 };
417
418 state.peers.insert(node_pubkey.to_string(), peer_info);
419 state.last_updated = current_time();
420
421 Ok(())
422 }
423
424 pub fn list_peers(&self) -> AnyaResult<Vec<PeerInfo>> {
426 let state = self
427 .state
428 .lock()
429 .map_err(|e| format!("Mutex lock error: {e}"))?;
430 Ok(state.peers.values().cloned().collect())
431 }
432
433 pub fn open_channel(
435 &self,
436 node_pubkey: &str,
437 capacity: u64,
438 push_msat: Option<u64>,
439 is_private: bool,
440 ) -> AnyaResult<Channel> {
441 let pubkey = LightningPublicKey::from_str(node_pubkey)
442 .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
443
444 let mut state = self
445 .state
446 .lock()
447 .map_err(|e| format!("Mutex lock error: {e}"))?;
448
449 if !state.peers.contains_key(node_pubkey) {
451 return Err(AnyaError::Bitcoin(format!(
452 "Not connected to peer {node_pubkey}"
453 )));
454 }
455
456 let channel_id = format!("channel_{:x}", rand::random::<u64>());
458
459 let funding_txid = LightningTxid::from_slice(&[0x42; 32])
461 .map_err(|e| AnyaError::Bitcoin(format!("Failed to create txid: {e}")))?;
462
463 let push_amount = push_msat.unwrap_or(0) / 1000; let local_balance = capacity - push_amount;
466 let remote_balance = push_amount;
467
468 let channel = Channel {
470 channel_id: channel_id.clone(),
471 funding_txid,
472 funding_output_idx: 0,
473 capacity,
474 local_balance,
475 remote_balance,
476 remote_pubkey: pubkey,
477 is_active: true,
478 is_public: !is_private,
479 short_channel_id: None,
480 };
481
482 state.channels.insert(channel_id, channel.clone());
483 state.last_updated = current_time();
484
485 Ok(channel)
486 }
487
488 pub fn list_channels(&self) -> AnyaResult<Vec<Channel>> {
490 let state = self
491 .state
492 .lock()
493 .map_err(|e| format!("Mutex lock error: {e}"))?;
494 Ok(state.channels.values().cloned().collect())
495 }
496
497 pub fn create_invoice(
499 &self,
500 amount_msat: Option<u64>,
501 description: &str,
502 expiry: Option<u32>,
503 ) -> AnyaResult<Invoice> {
504 let mut state = self
505 .state
506 .lock()
507 .map_err(|e| format!("Mutex lock error: {e}"))?;
508 let now = current_time();
509
510 let payment_hash = format!("hash_{:x}", rand::random::<u64>());
512
513 let network_prefix = match self.config.network.as_str() {
515 "bitcoin" => "lnbc",
516 "testnet" => "lntb",
517 "regtest" => "lnbcrt",
518 "signet" => "lnsb",
519 _ => "lnbc", };
521
522 let amount_part = match amount_msat {
523 Some(amt) => format!("{}", amt / 1000), None => "any".to_string(),
525 };
526
527 let bolt11 = format!(
528 "{}{}{}{}",
529 network_prefix,
530 amount_part,
531 description.chars().take(10).collect::<String>(),
532 now % 1000000
533 );
534
535 let invoice = Invoice {
537 bolt11,
538 payment_hash: payment_hash.clone(),
539 description: description.to_string(),
540 amount_msat,
541 expiry: expiry.unwrap_or(3600), timestamp: now,
543 is_paid: false,
544 paid_at: None,
545 };
546
547 state.invoices.insert(payment_hash, invoice.clone());
548 state.last_updated = now;
549
550 Ok(invoice)
551 }
552
553 pub fn pay_invoice(&self, bolt11: &str, amount_msat: Option<u64>) -> AnyaResult<Payment> {
555 let mut state = self
556 .state
557 .lock()
558 .map_err(|e| format!("Mutex lock error: {e}"))?;
559 let now = current_time();
560
561 let payment_hash = format!("hash_{:x}", rand::random::<u64>());
563 let payment_id = format!("pay_{:x}", rand::random::<u64>());
564
565 let invoice_amount = amount_msat.unwrap_or(10_000); let preimage = format!("preimage_{:x}", rand::random::<u64>());
570
571 let payment = Payment {
573 payment_id: payment_id.clone(),
574 payment_hash: payment_hash.clone(),
575 preimage: Some(preimage),
576 amount_msat: invoice_amount,
577 fee_msat: invoice_amount / 100, status: PaymentStatus::Succeeded, created_at: now,
580 resolved_at: Some(now),
581 description: Some(format!("Payment for invoice {bolt11}")),
582 };
583
584 state.payments.insert(payment_id, payment.clone());
585 state.last_updated = now;
586
587 Ok(payment)
588 }
589
590 pub fn decode_invoice(&self, bolt11: &str) -> AnyaResult<Invoice> {
592 let payment_hash = format!("hash_{:x}", rand::random::<u64>());
595
596 Ok(Invoice {
597 bolt11: bolt11.to_string(),
598 payment_hash,
599 description: "Decoded invoice".to_string(),
600 amount_msat: Some(50_000), expiry: 3600,
602 timestamp: current_time(),
603 is_paid: false,
604 paid_at: None,
605 })
606 }
607
608 pub fn get_payment(&self, payment_hash: &str) -> AnyaResult<Option<Payment>> {
610 let state = self
611 .state
612 .lock()
613 .map_err(|e| format!("Mutex lock error: {e}"))?;
614
615 let payment = state
617 .payments
618 .values()
619 .find(|p| p.payment_hash == payment_hash)
620 .cloned();
621
622 Ok(payment)
623 }
624
625 pub fn list_payments(&self) -> AnyaResult<Vec<Payment>> {
627 let state = self
628 .state
629 .lock()
630 .map_err(|e| format!("Mutex lock error: {e}"))?;
631 Ok(state.payments.values().cloned().collect())
632 }
633}
634
635impl BitcoinLightningBridge {
636 pub fn new(lightning_node: Arc<LightningNode>) -> AnyaResult<Self> {
638 Ok(Self {
639 lightning_node,
640 channel_transactions: Mutex::new(HashMap::new()),
641 funding_addresses: Mutex::new(HashMap::new()),
642 last_scanned_height: Mutex::new(0),
643 })
644 }
645
646 pub fn init(&self, current_height: u32) -> AnyaResult<()> {
648 let mut last_height = self
649 .last_scanned_height
650 .lock()
651 .map_err(|e| format!("Mutex lock error: {e}"))?;
652 *last_height = current_height;
653 Ok(())
654 }
655
656 pub fn create_funding_address(
658 &self,
659 peer_pubkey: &str,
660 amount_sat: u64,
661 push_msat: Option<u64>,
662 is_private: bool,
663 ) -> AnyaResult<String> {
664 let peers = self.lightning_node.list_peers()?;
666 let pubkey = LightningPublicKey::from_str(peer_pubkey)
667 .map_err(|e| AnyaError::Bitcoin(format!("Invalid node pubkey: {e}")))?;
668
669 let is_connected = peers.iter().any(|p| p.pubkey.to_string() == peer_pubkey);
670
671 if !is_connected {
672 return Err(AnyaError::Bitcoin(format!(
673 "Not connected to peer {peer_pubkey}"
674 )));
675 }
676
677 let address = format!("bc1q{:x}", rand::random::<u64>());
679
680 let channel_params = ChannelParameters {
682 peer_pubkey: pubkey,
683 push_msat,
684 is_private,
685 };
686
687 let funding_address = FundingAddress {
689 address: address.clone(),
690 required_amount: amount_sat,
691 channel_params,
692 created_at: current_time(),
693 };
694
695 let mut funding_addresses = self
696 .funding_addresses
697 .lock()
698 .map_err(|e| format!("Mutex lock error: {e}"))?;
699 funding_addresses.insert(address.clone(), funding_address);
700
701 Ok(address)
702 }
703
704 pub fn register_channel_transaction(
706 &self,
707 channel_id: &str,
708 funding_txid: &str,
709 funding_output_idx: u32,
710 funding_amount: u64,
711 ) -> AnyaResult<()> {
712 let txid = LightningTxid::from_slice(&hex::decode(funding_txid).unwrap_or_default())
713 .map_err(|e| AnyaError::Bitcoin(format!("Invalid txid: {e}")))?;
714
715 let channel_transaction = ChannelTransaction {
716 channel_id: channel_id.to_string(),
717 funding_txid: txid,
718 funding_output_idx,
719 funding_amount,
720 status: ChannelTransactionStatus::Pending,
721 confirmation_height: None,
722 closing_txid: None,
723 created_at: current_time(),
724 updated_at: current_time(),
725 };
726
727 let mut transactions = self
728 .channel_transactions
729 .lock()
730 .map_err(|e| format!("Mutex lock error: {e}"))?;
731 transactions.insert(channel_id.to_string(), channel_transaction);
732
733 Ok(())
734 }
735
736 pub fn update_channel_transaction(
738 &self,
739 channel_id: &str,
740 status: ChannelTransactionStatus,
741 confirmation_height: Option<u32>,
742 ) -> AnyaResult<()> {
743 let mut transactions = self
744 .channel_transactions
745 .lock()
746 .map_err(|e| format!("Mutex lock error: {e}"))?;
747
748 if let Some(transaction) = transactions.get_mut(channel_id) {
749 transaction.status = status;
750 transaction.confirmation_height = confirmation_height;
751 transaction.updated_at = current_time();
752 }
753
754 Ok(())
755 }
756
757 pub fn get_channel_transaction(
759 &self,
760 channel_id: &str,
761 ) -> AnyaResult<Option<ChannelTransaction>> {
762 let transactions = self
763 .channel_transactions
764 .lock()
765 .map_err(|e| format!("Mutex lock error: {e}"))?;
766
767 Ok(transactions.get(channel_id).cloned())
768 }
769
770 pub fn list_channel_transactions(&self) -> AnyaResult<Vec<ChannelTransaction>> {
772 let transactions = self
773 .channel_transactions
774 .lock()
775 .map_err(|e| format!("Mutex lock error: {e}"))?;
776
777 Ok(transactions.values().cloned().collect())
778 }
779}
780
781fn current_time() -> u64 {
782 SystemTime::now()
783 .duration_since(UNIX_EPOCH)
784 .unwrap_or_default()
785 .as_secs()
786}
787
788type LightningSecp256k1<T> = secp256k1::Secp256k1<T>;
790type All = secp256k1::All;
791
792#[cfg(test)]
793mod tests {
794 use super::*;
795
796 #[test]
797 fn test_lightning_public_key_from_str() {
798 let valid_key = "02".to_string() + &"a".repeat(64);
799 let pubkey = LightningPublicKey::from_str(&valid_key);
800 assert!(pubkey.is_ok());
801 }
802
803 #[test]
804 fn test_lightning_public_key_invalid_length() {
805 let invalid_key = "02".to_string() + &"a".repeat(32); let pubkey = LightningPublicKey::from_str(&invalid_key);
807 assert!(pubkey.is_err());
808 }
809
810 #[test]
811 fn test_lightning_txid_from_slice() {
812 let valid_txid = [0x42u8; 32];
813 let txid = LightningTxid::from_slice(&valid_txid);
814 assert!(txid.is_ok());
815 }
816
817 #[test]
818 fn test_lightning_txid_invalid_length() {
819 let invalid_txid = [0x42u8; 16]; let txid = LightningTxid::from_slice(&invalid_txid);
821 assert!(txid.is_err());
822 }
823}