1use crate::{credentials::Credentials, signer::Handle, util::exec, Error};
2use std::sync::atomic::{AtomicBool, Ordering};
3use gl_client::credentials::NodeIdProvider;
4use gl_client::node::{Client as GlClient, ClnClient, Node as ClientNode};
5use gl_client::pb::{self as glpb, cln as clnpb};
6use lightning_invoice::Bolt11Invoice;
7use std::sync::{Arc, Mutex};
8use tokio::sync::OnceCell;
9
10#[derive(uniffi::Object)]
13#[allow(unused)]
14pub struct Node {
15 inner: ClientNode,
16 cln_client: OnceCell<ClnClient>,
17 gl_client: OnceCell<GlClient>,
18 stored_credentials: Option<Credentials>,
19 signer_handle: Option<Handle>,
20 disconnected: AtomicBool,
21}
22
23#[uniffi::export]
24impl Node {
25 #[uniffi::constructor()]
26 pub fn new(credentials: &Credentials) -> Result<Self, Error> {
27 let node_id = credentials
28 .inner
29 .node_id()
30 .map_err(|_e| Error::UnparseableCreds())?;
31 let inner = ClientNode::new(node_id, credentials.inner.clone())
32 .expect("infallible client instantiation");
33
34 let cln_client = OnceCell::const_new();
35 let gl_client = OnceCell::const_new();
36 Ok(Node {
37 inner,
38 cln_client,
39 gl_client,
40 stored_credentials: Some(credentials.clone()),
41 signer_handle: None,
42 disconnected: AtomicBool::new(false),
43 })
44 }
45
46 pub fn stop(&self) -> Result<(), Error> {
48 self.check_connected()?;
49 let mut cln_client = exec(self.get_cln_client())?.clone();
50
51 let req = clnpb::StopRequest {};
52
53 let _ = exec(cln_client.stop(req));
57 Ok(())
58 }
59
60 pub fn credentials(&self) -> Result<Vec<u8>, Error> {
63 match &self.stored_credentials {
64 Some(creds) => creds.save(),
65 None => Err(Error::Other(
66 "No credentials stored. Use register/recover/connect to create a Node with credentials.".to_string(),
67 )),
68 }
69 }
70
71 pub fn disconnect(&self) -> Result<(), Error> {
75 self.disconnected.store(true, Ordering::Relaxed);
76 if let Some(ref handle) = self.signer_handle {
77 handle.try_stop();
78 }
79 Ok(())
80 }
81
82 pub fn receive(
93 &self,
94 label: String,
95 description: String,
96 amount_msat: Option<u64>,
97 ) -> Result<ReceiveResponse, Error> {
98 self.check_connected()?;
99 let mut gl_client = exec(self.get_gl_client())?.clone();
100
101 let req = gl_client::pb::LspInvoiceRequest {
102 amount_msat: amount_msat.unwrap_or_default(),
103 description: description,
104 label: label,
105 lsp_id: "".to_owned(),
106 token: "".to_owned(),
107 };
108 let res = exec(gl_client.lsp_invoice(req))
109 .map_err(|s| Error::Rpc(s.to_string()))?
110 .into_inner();
111 Ok(ReceiveResponse {
112 bolt11: res.bolt11,
113 opening_fee_msat: res.opening_fee_msat,
114 })
115 }
116
117 pub fn send(&self, invoice: String, amount_msat: Option<u64>) -> Result<SendResponse, Error> {
118 self.check_connected()?;
119 let mut cln_client = exec(self.get_cln_client())?.clone();
120 let req = clnpb::PayRequest {
121 amount_msat: match amount_msat {
122 Some(a) => Some(clnpb::Amount { msat: a }),
123 None => None,
124 },
125
126 bolt11: invoice,
127 description: None,
128 exclude: vec![],
129 exemptfee: None,
130 label: None,
131 localinvreqid: None,
132 maxdelay: None,
133 maxfee: None,
134 maxfeepercent: None,
135 partial_msat: None,
136 retry_for: None,
137 riskfactor: None,
138 };
139 exec(cln_client.pay(req))
140 .map_err(|e| Error::Rpc(e.to_string()))
141 .map(|r| r.into_inner().into())
142 }
143
144 pub fn onchain_send(
156 &self,
157 destination: String,
158 amount_or_all: String,
159 ) -> Result<OnchainSendResponse, Error> {
160 self.check_connected()?;
161 let mut cln_client = exec(self.get_cln_client())?.clone();
162
163 let (num, suffix): (String, String) = amount_or_all.chars().partition(|c| c.is_digit(10));
166
167 let num = if num.len() > 0 {
168 num.parse::<u64>().unwrap()
169 } else {
170 0
171 };
172 let satoshi = match (num, suffix.as_ref()) {
173 (n, "") | (n, "sat") => clnpb::AmountOrAll {
174 value: Some(clnpb::amount_or_all::Value::Amount(clnpb::Amount {
178 msat: n * 1000,
179 })),
180 },
181 (n, "msat") => clnpb::AmountOrAll {
182 value: Some(clnpb::amount_or_all::Value::Amount(clnpb::Amount {
183 msat: n,
184 })),
185 },
186 (0, "all") => clnpb::AmountOrAll {
187 value: Some(clnpb::amount_or_all::Value::All(true)),
188 },
189 (_, _) => return Err(Error::Argument("amount_or_all".to_owned(), amount_or_all)),
190 };
191
192 let req = clnpb::WithdrawRequest {
193 destination: destination,
194 minconf: None,
195 feerate: None,
196 satoshi: Some(satoshi),
197 utxos: vec![],
198 };
199
200 exec(cln_client.withdraw(req))
201 .map_err(|e| Error::Rpc(e.to_string()))
202 .map(|r| r.into_inner().into())
203 }
204
205 pub fn onchain_receive(&self) -> Result<OnchainReceiveResponse, Error> {
211 self.check_connected()?;
212 let mut cln_client = exec(self.get_cln_client())?.clone();
213
214 let req = clnpb::NewaddrRequest {
215 addresstype: Some(clnpb::newaddr_request::NewaddrAddresstype::All.into()),
216 };
217
218 let res = exec(cln_client.new_addr(req))
219 .map_err(|e| Error::Rpc(e.to_string()))?
220 .into_inner();
221 Ok(res.into())
222 }
223
224 pub fn get_info(&self) -> Result<GetInfoResponse, Error> {
229 self.check_connected()?;
230 let mut cln_client = exec(self.get_cln_client())?.clone();
231
232 let req = clnpb::GetinfoRequest {};
233
234 let res = exec(cln_client.getinfo(req))
235 .map_err(|e| Error::Rpc(e.to_string()))?
236 .into_inner();
237 Ok(res.into())
238 }
239
240 pub fn list_peers(&self) -> Result<ListPeersResponse, Error> {
245 self.check_connected()?;
246 let mut cln_client = exec(self.get_cln_client())?.clone();
247
248 let req = clnpb::ListpeersRequest {
249 id: None,
250 level: None,
251 };
252
253 let res = exec(cln_client.list_peers(req))
254 .map_err(|e| Error::Rpc(e.to_string()))?
255 .into_inner();
256 Ok(res.into())
257 }
258
259 pub fn list_peer_channels(&self) -> Result<ListPeerChannelsResponse, Error> {
264 self.check_connected()?;
265 let mut cln_client = exec(self.get_cln_client())?.clone();
266
267 let req = clnpb::ListpeerchannelsRequest { id: None };
268
269 let res = exec(cln_client.list_peer_channels(req))
270 .map_err(|e| Error::Rpc(e.to_string()))?
271 .into_inner();
272 Ok(res.into())
273 }
274
275 pub fn list_funds(&self) -> Result<ListFundsResponse, Error> {
280 self.check_connected()?;
281 let mut cln_client = exec(self.get_cln_client())?.clone();
282
283 let req = clnpb::ListfundsRequest { spent: None };
284
285 let res = exec(cln_client.list_funds(req))
286 .map_err(|e| Error::Rpc(e.to_string()))?
287 .into_inner();
288 Ok(res.into())
289 }
290
291 pub fn list_invoices(
295 &self,
296 label: Option<String>,
297 invstring: Option<String>,
298 payment_hash: Option<Vec<u8>>,
299 offer_id: Option<String>,
300 index: Option<ListIndex>,
301 start: Option<u64>,
302 limit: Option<u32>,
303 ) -> Result<ListInvoicesResponse, Error> {
304 self.check_connected()?;
305 let mut cln_client = exec(self.get_cln_client())?.clone();
306
307 let req = clnpb::ListinvoicesRequest {
308 label,
309 invstring,
310 payment_hash,
311 offer_id,
312 index: index.map(|i| i.to_i32()),
313 start,
314 limit,
315 };
316
317 let res = exec(cln_client.list_invoices(req))
318 .map_err(|e| Error::Rpc(e.to_string()))?
319 .into_inner();
320 Ok(res.into())
321 }
322
323 pub fn list_pays(
326 &self,
327 bolt11: Option<String>,
328 payment_hash: Option<Vec<u8>>,
329 status: Option<PayStatus>,
330 index: Option<ListIndex>,
331 start: Option<u64>,
332 limit: Option<u32>,
333 ) -> Result<ListPaysResponse, Error> {
334 self.check_connected()?;
335 let mut cln_client = exec(self.get_cln_client())?.clone();
336
337 let cln_status = status.map(|s| match s {
339 PayStatus::PENDING => 0,
340 PayStatus::COMPLETE => 1,
341 PayStatus::FAILED => 2,
342 });
343
344 let req = clnpb::ListpaysRequest {
345 bolt11,
346 payment_hash,
347 status: cln_status,
348 index: index.map(|i| i.to_i32()),
349 start,
350 limit,
351 };
352
353 let res = exec(cln_client.list_pays(req))
354 .map_err(|e| Error::Rpc(e.to_string()))?
355 .into_inner();
356 Ok(res.into())
357 }
358
359 pub fn list_payments(&self, req: ListPaymentsRequest) -> Result<Vec<Payment>, Error> {
366 self.check_connected()?;
367 let mut cln_client = exec(self.get_cln_client())?.clone();
368
369 let invoices = exec(cln_client.list_invoices(clnpb::ListinvoicesRequest::default()))
370 .map_err(|e| Error::Rpc(e.to_string()))?
371 .into_inner();
372
373 let mut cln_client = exec(self.get_cln_client())?.clone();
374 let pays = exec(cln_client.list_pays(clnpb::ListpaysRequest::default()))
375 .map_err(|e| Error::Rpc(e.to_string()))?
376 .into_inner();
377
378 let mut payments: Vec<Payment> = Vec::new();
379
380 let include_received = req
382 .filters
383 .as_ref()
384 .map(|f| f.is_empty() || f.iter().any(|t| matches!(t, PaymentTypeFilter::Received)))
385 .unwrap_or(true);
386
387 let include_sent = req
389 .filters
390 .as_ref()
391 .map(|f| f.is_empty() || f.iter().any(|t| matches!(t, PaymentTypeFilter::Sent)))
392 .unwrap_or(true);
393
394 if include_received {
395 payments.extend(invoices.invoices.into_iter().map(|i| -> Payment { i.into() }));
396 }
397 if include_sent {
398 payments.extend(pays.pays.into_iter().map(|p| -> Payment { p.into() }));
399 }
400
401 let include_failures = req.include_failures.unwrap_or(false);
402
403 payments.retain(|p| {
404 if !include_failures && matches!(p.status, PaymentStatus::Failed) {
405 return false;
406 }
407 if let Some(from) = req.from_timestamp {
408 if p.payment_time < from {
409 return false;
410 }
411 }
412 if let Some(to) = req.to_timestamp {
413 if p.payment_time > to {
414 return false;
415 }
416 }
417 true
418 });
419
420 payments.sort_by(|a, b| b.payment_time.cmp(&a.payment_time));
422
423 let offset = req.offset.unwrap_or(0) as usize;
425 let limit = req.limit.unwrap_or(u32::MAX) as usize;
426 let payments = payments.into_iter().skip(offset).take(limit).collect();
427
428 Ok(payments)
429 }
430
431 pub fn stream_node_events(&self) -> Result<Arc<NodeEventStream>, Error> {
441 self.check_connected()?;
442 let mut gl_client = exec(self.get_gl_client())?.clone();
443 let req = glpb::NodeEventsRequest {};
444 let stream = exec(gl_client.stream_node_events(req))
445 .map_err(|e| Error::Rpc(e.to_string()))?
446 .into_inner();
447 Ok(Arc::new(NodeEventStream {
448 inner: Mutex::new(stream),
449 }))
450 }
451}
452
453impl Node {
455 fn check_connected(&self) -> Result<(), Error> {
456 if self.disconnected.load(Ordering::Relaxed) {
457 return Err(Error::Other("Node is disconnected".to_string()));
458 }
459 Ok(())
460 }
461
462 pub(crate) fn with_signer(
465 credentials: Credentials,
466 handle: Handle,
467 ) -> Result<Self, Error> {
468 let node_id = credentials
469 .inner
470 .node_id()
471 .map_err(|_e| Error::UnparseableCreds())?;
472 let inner = ClientNode::new(node_id, credentials.inner.clone())
473 .expect("infallible client instantiation");
474
475 let cln_client = OnceCell::const_new();
476 let gl_client = OnceCell::const_new();
477 Ok(Node {
478 inner,
479 cln_client,
480 gl_client,
481 stored_credentials: Some(credentials),
482 signer_handle: Some(handle),
483 disconnected: AtomicBool::new(false),
484 })
485 }
486
487 async fn get_gl_client<'a>(&'a self) -> Result<&'a GlClient, Error> {
488 let inner = self.inner.clone();
489 self.gl_client
490 .get_or_try_init(|| async { inner.schedule::<GlClient>().await })
491 .await
492 .map_err(|e| Error::Rpc(e.to_string()))
493 }
494
495 async fn get_cln_client<'a>(&'a self) -> Result<&'a ClnClient, Error> {
496 let inner = self.inner.clone();
497
498 self.cln_client
499 .get_or_try_init(|| async { inner.schedule::<ClnClient>().await })
500 .await
501 .map_err(|e| Error::Rpc(e.to_string()))
502 }
503}
504
505#[derive(uniffi::Record)]
507pub struct OnchainSendResponse {
508 pub tx: Vec<u8>,
510 pub txid: Vec<u8>,
512 pub psbt: String,
514}
515
516impl From<clnpb::WithdrawResponse> for OnchainSendResponse {
517 fn from(other: clnpb::WithdrawResponse) -> Self {
518 Self {
519 tx: other.tx,
520 txid: other.txid,
521 psbt: other.psbt,
522 }
523 }
524}
525
526#[derive(uniffi::Record)]
528pub struct OnchainReceiveResponse {
529 pub bech32: String,
531 pub p2tr: String,
533}
534
535impl From<clnpb::NewaddrResponse> for OnchainReceiveResponse {
536 fn from(other: clnpb::NewaddrResponse) -> Self {
537 OnchainReceiveResponse {
538 bech32: other.bech32.unwrap_or_default(),
539 p2tr: other.p2tr.unwrap_or_default(),
540 }
541 }
542}
543
544#[derive(uniffi::Record)]
545pub struct SendResponse {
546 pub status: PayStatus,
547 pub preimage: Vec<u8>,
548 pub payment_hash: Vec<u8>,
549 pub destination_pubkey: Option<Vec<u8>>,
550 pub amount_msat: u64,
551 pub amount_sent_msat: u64,
552 pub parts: u32,
553}
554
555impl From<clnpb::PayResponse> for SendResponse {
556 fn from(other: clnpb::PayResponse) -> Self {
557 Self {
558 status: other.status.into(),
559 preimage: other.payment_preimage,
560 payment_hash: other.payment_hash,
561 destination_pubkey: other.destination,
562 amount_msat: other.amount_msat.unwrap().msat,
563 amount_sent_msat: other.amount_sent_msat.unwrap().msat,
564 parts: other.parts,
565 }
566 }
567}
568
569#[derive(uniffi::Record)]
570pub struct ReceiveResponse {
571 pub bolt11: String,
572 pub opening_fee_msat: u64,
575}
576
577#[derive(uniffi::Enum, Clone)]
578pub enum PayStatus {
579 COMPLETE = 0,
580 PENDING = 1,
581 FAILED = 2,
582}
583
584impl From<clnpb::pay_response::PayStatus> for PayStatus {
585 fn from(other: clnpb::pay_response::PayStatus) -> Self {
586 match other {
587 clnpb::pay_response::PayStatus::Complete => PayStatus::COMPLETE,
588 clnpb::pay_response::PayStatus::Failed => PayStatus::FAILED,
589 clnpb::pay_response::PayStatus::Pending => PayStatus::PENDING,
590 }
591 }
592}
593
594impl From<i32> for PayStatus {
595 fn from(i: i32) -> Self {
596 match i {
597 0 => PayStatus::COMPLETE,
598 1 => PayStatus::PENDING,
599 2 => PayStatus::FAILED,
600 o => panic!("Unknown pay_status {}", o),
601 }
602 }
603}
604
605#[allow(unused)]
610#[derive(Clone, uniffi::Record)]
611pub struct GetInfoResponse {
612 pub id: Vec<u8>,
613 pub alias: Option<String>,
614 pub color: Vec<u8>,
615 pub num_peers: u32,
616 pub num_pending_channels: u32,
617 pub num_active_channels: u32,
618 pub num_inactive_channels: u32,
619 pub version: String,
620 pub lightning_dir: String,
621 pub blockheight: u32,
622 pub network: String,
623 pub fees_collected_msat: u64,
624}
625
626impl From<clnpb::GetinfoResponse> for GetInfoResponse {
627 fn from(other: clnpb::GetinfoResponse) -> Self {
628 Self {
629 id: other.id,
630 alias: other.alias,
631 color: other.color,
632 num_peers: other.num_peers,
633 num_pending_channels: other.num_pending_channels,
634 num_active_channels: other.num_active_channels,
635 num_inactive_channels: other.num_inactive_channels,
636 version: other.version,
637 lightning_dir: other.lightning_dir,
638 blockheight: other.blockheight,
639 network: other.network,
640 fees_collected_msat: other.fees_collected_msat.map(|a| a.msat).unwrap_or(0),
641 }
642 }
643}
644
645#[allow(unused)]
650#[derive(Clone, uniffi::Record)]
651pub struct ListPeersResponse {
652 pub peers: Vec<Peer>,
653}
654
655#[allow(unused)]
656#[derive(Clone, uniffi::Record)]
657pub struct Peer {
658 pub id: Vec<u8>,
659 pub connected: bool,
660 pub num_channels: Option<u32>,
661 pub netaddr: Vec<String>,
662 pub remote_addr: Option<String>,
663 pub features: Option<Vec<u8>>,
664}
665
666impl From<clnpb::ListpeersResponse> for ListPeersResponse {
667 fn from(other: clnpb::ListpeersResponse) -> Self {
668 Self {
669 peers: other.peers.into_iter().map(|p| p.into()).collect(),
670 }
671 }
672}
673
674impl From<clnpb::ListpeersPeers> for Peer {
675 fn from(other: clnpb::ListpeersPeers) -> Self {
676 Self {
677 id: other.id,
678 connected: other.connected,
679 num_channels: other.num_channels,
680 netaddr: other.netaddr,
681 remote_addr: other.remote_addr,
682 features: other.features,
683 }
684 }
685}
686
687#[allow(unused)]
692#[derive(Clone, uniffi::Record)]
693pub struct ListPeerChannelsResponse {
694 pub channels: Vec<PeerChannel>,
695}
696
697#[allow(unused)]
698#[derive(Clone, uniffi::Record)]
699pub struct PeerChannel {
700 pub peer_id: Vec<u8>,
701 pub peer_connected: bool,
702 pub state: ChannelState,
703 pub short_channel_id: Option<String>,
704 pub channel_id: Option<Vec<u8>>,
705 pub funding_txid: Option<Vec<u8>>,
706 pub funding_outnum: Option<u32>,
707 pub to_us_msat: Option<u64>,
708 pub total_msat: Option<u64>,
709 pub spendable_msat: Option<u64>,
710 pub receivable_msat: Option<u64>,
711}
712
713#[derive(Clone, uniffi::Enum)]
714pub enum ChannelState {
715 Openingd,
716 ChanneldAwaitingLockin,
717 ChanneldNormal,
718 ChanneldShuttingDown,
719 ClosingdSigexchange,
720 ClosingdComplete,
721 AwaitingUnilateral,
722 FundingSpendSeen,
723 Onchain,
724 DualopendOpenInit,
725 DualopendAwaitingLockin,
726 DualopendOpenCommitted,
727 DualopendOpenCommitReady,
728}
729
730impl ChannelState {
731 fn from_i32(value: i32) -> Self {
732 match value {
733 0 => ChannelState::Openingd,
734 1 => ChannelState::ChanneldAwaitingLockin,
735 2 => ChannelState::ChanneldNormal,
736 3 => ChannelState::ChanneldShuttingDown,
737 4 => ChannelState::ClosingdSigexchange,
738 5 => ChannelState::ClosingdComplete,
739 6 => ChannelState::AwaitingUnilateral,
740 7 => ChannelState::FundingSpendSeen,
741 8 => ChannelState::Onchain,
742 9 => ChannelState::DualopendOpenInit,
743 10 => ChannelState::DualopendAwaitingLockin,
744 11 => ChannelState::DualopendOpenCommitted,
745 12 => ChannelState::DualopendOpenCommitReady,
746 _ => ChannelState::Onchain, }
748 }
749}
750
751impl From<clnpb::ListpeerchannelsResponse> for ListPeerChannelsResponse {
752 fn from(other: clnpb::ListpeerchannelsResponse) -> Self {
753 Self {
754 channels: other.channels.into_iter().map(|c| c.into()).collect(),
755 }
756 }
757}
758
759impl From<clnpb::ListpeerchannelsChannels> for PeerChannel {
760 fn from(other: clnpb::ListpeerchannelsChannels) -> Self {
761 let state = ChannelState::from_i32(other.state);
762 Self {
763 peer_id: other.peer_id,
764 peer_connected: other.peer_connected,
765 state,
766 short_channel_id: other.short_channel_id,
767 channel_id: other.channel_id,
768 funding_txid: other.funding_txid,
769 funding_outnum: other.funding_outnum,
770 to_us_msat: other.to_us_msat.map(|a| a.msat),
771 total_msat: other.total_msat.map(|a| a.msat),
772 spendable_msat: other.spendable_msat.map(|a| a.msat),
773 receivable_msat: other.receivable_msat.map(|a| a.msat),
774 }
775 }
776}
777
778#[allow(unused)]
783#[derive(Clone, uniffi::Record)]
784pub struct ListFundsResponse {
785 pub outputs: Vec<FundOutput>,
786 pub channels: Vec<FundChannel>,
787}
788
789#[allow(unused)]
790#[derive(Clone, uniffi::Record)]
791pub struct FundOutput {
792 pub txid: Vec<u8>,
793 pub output: u32,
794 pub amount_msat: u64,
795 pub status: OutputStatus,
796 pub address: Option<String>,
797 pub blockheight: Option<u32>,
798}
799
800#[derive(Clone, uniffi::Enum)]
801pub enum OutputStatus {
802 Unconfirmed,
803 Confirmed,
804 Spent,
805 Immature,
806}
807
808impl OutputStatus {
809 fn from_i32(value: i32) -> Self {
810 match value {
811 0 => OutputStatus::Unconfirmed,
812 1 => OutputStatus::Confirmed,
813 2 => OutputStatus::Spent,
814 3 => OutputStatus::Immature,
815 _ => OutputStatus::Unconfirmed, }
817 }
818}
819
820#[allow(unused)]
821#[derive(Clone, uniffi::Record)]
822pub struct FundChannel {
823 pub peer_id: Vec<u8>,
824 pub our_amount_msat: u64,
825 pub amount_msat: u64,
826 pub funding_txid: Vec<u8>,
827 pub funding_output: u32,
828 pub connected: bool,
829 pub state: ChannelState,
830 pub short_channel_id: Option<String>,
831 pub channel_id: Option<Vec<u8>>,
832}
833
834impl From<clnpb::ListfundsResponse> for ListFundsResponse {
835 fn from(other: clnpb::ListfundsResponse) -> Self {
836 Self {
837 outputs: other.outputs.into_iter().map(|o| o.into()).collect(),
838 channels: other.channels.into_iter().map(|c| c.into()).collect(),
839 }
840 }
841}
842
843impl From<clnpb::ListfundsOutputs> for FundOutput {
844 fn from(other: clnpb::ListfundsOutputs) -> Self {
845 let status = OutputStatus::from_i32(other.status);
846 Self {
847 txid: other.txid,
848 output: other.output,
849 amount_msat: other.amount_msat.map(|a| a.msat).unwrap_or(0),
850 status,
851 address: other.address,
852 blockheight: other.blockheight,
853 }
854 }
855}
856
857impl From<clnpb::ListfundsChannels> for FundChannel {
858 fn from(other: clnpb::ListfundsChannels) -> Self {
859 let state = ChannelState::from_i32(other.state);
860 Self {
861 peer_id: other.peer_id,
862 our_amount_msat: other.our_amount_msat.map(|a| a.msat).unwrap_or(0),
863 amount_msat: other.amount_msat.map(|a| a.msat).unwrap_or(0),
864 funding_txid: other.funding_txid,
865 funding_output: other.funding_output,
866 connected: other.connected,
867 state,
868 short_channel_id: other.short_channel_id,
869 channel_id: other.channel_id,
870 }
871 }
872}
873
874#[derive(Clone, uniffi::Enum)]
880pub enum ListIndex {
881 CREATED,
882 UPDATED,
883}
884
885impl ListIndex {
886 fn to_i32(&self) -> i32 {
887 match self {
888 ListIndex::CREATED => 0,
889 ListIndex::UPDATED => 1,
890 }
891 }
892}
893
894#[derive(Clone, uniffi::Enum)]
899pub enum InvoiceStatus {
900 UNPAID,
901 PAID,
902 EXPIRED,
903}
904
905impl From<i32> for InvoiceStatus {
906 fn from(i: i32) -> Self {
907 match i {
908 0 => InvoiceStatus::UNPAID,
909 1 => InvoiceStatus::PAID,
910 2 => InvoiceStatus::EXPIRED,
911 o => panic!("Unknown invoice status {}", o),
912 }
913 }
914}
915
916#[derive(Clone, uniffi::Record)]
917pub struct Invoice {
918 pub label: String,
919 pub description: String,
920 pub payment_hash: Vec<u8>,
921 pub status: InvoiceStatus,
922 pub amount_msat: Option<u64>,
923 pub amount_received_msat: Option<u64>,
924 pub bolt11: Option<String>,
925 pub bolt12: Option<String>,
926 pub paid_at: Option<u64>,
927 pub expires_at: u64,
928 pub payment_preimage: Option<Vec<u8>>,
929 pub destination_pubkey: Option<Vec<u8>>,
930}
931
932fn pubkey_from_bolt11(bolt11: &str) -> Option<Vec<u8>> {
934 let invoice: Bolt11Invoice = bolt11.parse().ok()?;
935 Some(invoice.recover_payee_pub_key().serialize().to_vec())
936}
937
938impl From<clnpb::ListinvoicesInvoices> for Invoice {
939 fn from(other: clnpb::ListinvoicesInvoices) -> Self {
940 let destination_pubkey = other.bolt11.as_deref().and_then(pubkey_from_bolt11);
941 Self {
942 label: other.label,
943 description: other.description.unwrap_or_default(),
944 payment_hash: other.payment_hash,
945 status: other.status.into(),
946 amount_msat: other.amount_msat.map(|a| a.msat),
947 amount_received_msat: other.amount_received_msat.map(|a| a.msat),
948 bolt11: other.bolt11,
949 bolt12: other.bolt12,
950 paid_at: other.paid_at,
951 expires_at: other.expires_at,
952 payment_preimage: other.payment_preimage,
953 destination_pubkey,
954 }
955 }
956}
957
958#[derive(Clone, uniffi::Record)]
959pub struct ListInvoicesResponse {
960 pub invoices: Vec<Invoice>,
961}
962
963impl From<clnpb::ListinvoicesResponse> for ListInvoicesResponse {
964 fn from(other: clnpb::ListinvoicesResponse) -> Self {
965 Self {
966 invoices: other.invoices.into_iter().map(|i| i.into()).collect(),
967 }
968 }
969}
970
971#[derive(Clone, uniffi::Record)]
976pub struct Pay {
977 pub payment_hash: Vec<u8>,
978 pub status: PayStatus,
979 pub destination_pubkey: Option<Vec<u8>>,
980 pub amount_msat: Option<u64>,
981 pub amount_sent_msat: Option<u64>,
982 pub label: Option<String>,
983 pub bolt11: Option<String>,
984 pub description: Option<String>,
985 pub bolt12: Option<String>,
986 pub preimage: Option<Vec<u8>>,
987 pub created_at: u64,
988 pub completed_at: Option<u64>,
989 pub number_of_parts: Option<u64>,
990}
991
992impl From<clnpb::ListpaysPays> for Pay {
993 fn from(other: clnpb::ListpaysPays) -> Self {
994 let status = match other.status {
995 0 => PayStatus::PENDING, 1 => PayStatus::FAILED, 2 => PayStatus::COMPLETE, o => panic!("Unknown listpays status {}", o),
999 };
1000 Self {
1001 payment_hash: other.payment_hash,
1002 status,
1003 destination_pubkey: other.destination,
1004 amount_msat: other.amount_msat.map(|a| a.msat),
1005 amount_sent_msat: other.amount_sent_msat.map(|a| a.msat),
1006 label: other.label,
1007 bolt11: other.bolt11,
1008 description: other.description,
1009 bolt12: other.bolt12,
1010 preimage: other.preimage,
1011 created_at: other.created_at,
1012 completed_at: other.completed_at,
1013 number_of_parts: other.number_of_parts,
1014 }
1015 }
1016}
1017
1018#[derive(Clone, uniffi::Record)]
1019pub struct ListPaysResponse {
1020 pub pays: Vec<Pay>,
1021}
1022
1023impl From<clnpb::ListpaysResponse> for ListPaysResponse {
1024 fn from(other: clnpb::ListpaysResponse) -> Self {
1025 Self {
1026 pays: other.pays.into_iter().map(|p| p.into()).collect(),
1027 }
1028 }
1029}
1030
1031#[derive(Clone, Default, uniffi::Record)]
1036pub struct ListPaymentsRequest {
1037 pub filters: Option<Vec<PaymentTypeFilter>>,
1039 pub from_timestamp: Option<u64>,
1041 pub to_timestamp: Option<u64>,
1043 pub include_failures: Option<bool>,
1045 pub offset: Option<u32>,
1047 pub limit: Option<u32>,
1049}
1050
1051#[derive(Clone, uniffi::Enum)]
1052pub enum PaymentTypeFilter {
1053 Sent,
1054 Received,
1055}
1056
1057#[derive(Clone, uniffi::Record)]
1058pub struct Payment {
1059 pub id: String,
1060 pub payment_type: PaymentType,
1061 pub payment_time: u64,
1062 pub amount_msat: u64,
1063 pub fee_msat: u64,
1064 pub status: PaymentStatus,
1065 pub description: Option<String>,
1066 pub bolt11: Option<String>,
1067 pub preimage: Option<Vec<u8>>,
1068 pub destination: Option<Vec<u8>>,
1069}
1070
1071#[derive(Clone, uniffi::Enum)]
1072pub enum PaymentType {
1073 Sent,
1074 Received,
1075}
1076
1077#[derive(Clone, uniffi::Enum)]
1078pub enum PaymentStatus {
1079 Pending,
1080 Complete,
1081 Failed,
1082}
1083
1084impl From<clnpb::ListinvoicesInvoices> for Payment {
1085 fn from(inv: clnpb::ListinvoicesInvoices) -> Self {
1086 let status = match inv.status() {
1087 clnpb::listinvoices_invoices::ListinvoicesInvoicesStatus::Paid => {
1088 PaymentStatus::Complete
1089 }
1090 clnpb::listinvoices_invoices::ListinvoicesInvoicesStatus::Expired => {
1091 PaymentStatus::Failed
1092 }
1093 clnpb::listinvoices_invoices::ListinvoicesInvoicesStatus::Unpaid => {
1094 PaymentStatus::Pending
1095 }
1096 };
1097
1098 let payment_time = inv.paid_at.unwrap_or(inv.expires_at);
1099 let amount_msat = inv
1100 .amount_received_msat
1101 .or(inv.amount_msat)
1102 .map(|a| a.msat)
1103 .unwrap_or(0);
1104
1105 Payment {
1106 id: inv.payment_hash.iter().map(|b| format!("{:02x}", b)).collect::<String>(),
1107 payment_type: PaymentType::Received,
1108 payment_time,
1109 amount_msat,
1110 fee_msat: 0,
1111 status,
1112 description: inv.description,
1113 bolt11: inv.bolt11,
1114 preimage: inv.payment_preimage,
1115 destination: None,
1116 }
1117 }
1118}
1119
1120impl From<clnpb::ListpaysPays> for Payment {
1121 fn from(pay: clnpb::ListpaysPays) -> Self {
1122 let status = match pay.status() {
1123 clnpb::listpays_pays::ListpaysPaysStatus::Complete => PaymentStatus::Complete,
1124 clnpb::listpays_pays::ListpaysPaysStatus::Failed => PaymentStatus::Failed,
1125 clnpb::listpays_pays::ListpaysPaysStatus::Pending => PaymentStatus::Pending,
1126 };
1127
1128 let payment_time = pay.completed_at.unwrap_or(pay.created_at);
1129 let amount_msat = pay.amount_msat.as_ref().map(|a| a.msat).unwrap_or(0);
1130 let amount_sent_msat = pay.amount_sent_msat.as_ref().map(|a| a.msat).unwrap_or(0);
1131 let fee_msat = amount_sent_msat.saturating_sub(amount_msat);
1132
1133 Payment {
1134 id: pay.payment_hash.iter().map(|b| format!("{:02x}", b)).collect::<String>(),
1135 payment_type: PaymentType::Sent,
1136 payment_time,
1137 amount_msat,
1138 fee_msat,
1139 status,
1140 description: pay.description,
1141 bolt11: pay.bolt11,
1142 preimage: pay.preimage,
1143 destination: pay.destination,
1144 }
1145 }
1146}
1147
1148#[derive(uniffi::Object)]
1159pub struct NodeEventStream {
1160 inner: Mutex<tonic::codec::Streaming<glpb::NodeEvent>>,
1161}
1162
1163#[uniffi::export]
1164impl NodeEventStream {
1165 pub fn next(&self) -> Result<Option<NodeEvent>, Error> {
1171 let mut stream = self.inner.lock().map_err(|e| Error::Other(e.to_string()))?;
1172 match exec(stream.message()) {
1173 Ok(Some(event)) => Ok(Some(event.into())),
1174 Ok(None) => Ok(None),
1175 Err(e) if e.code() == tonic::Code::Unknown => Ok(None),
1176 Err(e) => Err(Error::Rpc(e.to_string())),
1177 }
1178 }
1179}
1180
1181#[derive(Clone, uniffi::Enum)]
1183pub enum NodeEvent {
1184 InvoicePaid { details: InvoicePaidEvent },
1186 Unknown,
1189}
1190
1191#[derive(Clone, uniffi::Record)]
1193pub struct InvoicePaidEvent {
1194 pub payment_hash: Vec<u8>,
1196 pub bolt11: String,
1198 pub preimage: Vec<u8>,
1200 pub label: String,
1202 pub amount_msat: u64,
1204}
1205
1206impl From<glpb::NodeEvent> for NodeEvent {
1207 fn from(other: glpb::NodeEvent) -> Self {
1208 match other.event {
1209 Some(glpb::node_event::Event::InvoicePaid(paid)) => NodeEvent::InvoicePaid {
1210 details: InvoicePaidEvent {
1211 payment_hash: paid.payment_hash,
1212 bolt11: paid.bolt11,
1213 preimage: paid.preimage,
1214 label: paid.label,
1215 amount_msat: paid.amount_msat,
1216 },
1217 },
1218 None => NodeEvent::Unknown,
1219 }
1220 }
1221}