1tonic::include_proto!("greenlight");
2use self::amount::Unit;
3use crate::{messages, requests, responses};
4use anyhow::{anyhow, Context, Result};
5use bytes::{Buf, BufMut, Bytes, BytesMut};
6use cln_grpc::pb::RouteHop;
7use cln_rpc::primitives::{self, ShortChannelId};
8use log::warn;
9use std::str::FromStr;
10
11impl TryFrom<responses::GetInfo> for GetInfoResponse {
12 type Error = anyhow::Error;
13 fn try_from(i: responses::GetInfo) -> Result<GetInfoResponse> {
14 Ok(GetInfoResponse {
15 alias: i.alias,
16 blockheight: i.blockheight as u32,
17 color: hex::decode(i.color)?,
18 addresses: vec![],
19 node_id: hex::decode(i.id)?,
20 version: i.version,
21 num_peers: i.num_peers as u32,
22 network: i.network,
23 })
24 }
25}
26impl From<responses::Connect> for ConnectResponse {
27 fn from(c: responses::Connect) -> ConnectResponse {
28 ConnectResponse {
29 node_id: c.id,
30 features: c.features,
31 }
32 }
33}
34
35impl From<responses::Aliases> for Aliases {
36 fn from(a: responses::Aliases) -> Self {
37 Self {
38 local: a.local.unwrap_or_default(),
39 remote: a.remote.unwrap_or_default(),
40 }
41 }
42}
43
44impl From<&responses::Channel> for Channel {
45 fn from(c: &responses::Channel) -> Self {
46 Channel {
47 state: c.state.to_string(),
48 owner: c.owner.clone().unwrap_or_default(),
49 alias: c.alias.clone().map(|c| c.into()),
50 short_channel_id: c.short_channel_id.clone().unwrap_or_default(),
51 direction: c.direction.unwrap_or_default() as u32,
52 channel_id: c.channel_id.clone(),
53 funding_txid: c.funding_txid.clone(),
54 close_to_addr: c.close_to_addr.clone().unwrap_or_default(),
55 close_to: c.close_to.clone().unwrap_or_default(),
56 private: c.private,
57 total: c.total_msat.to_string(),
58 dust_limit: c.dust_limit_msat.to_string(),
59 spendable: c.spendable_msat.to_string(),
60 receivable: c.receivable_msat.to_string(),
61 their_to_self_delay: c.their_to_self_delay as u32,
62 our_to_self_delay: c.our_to_self_delay as u32,
63 status: vec![],
64 htlcs: vec![], }
66 }
67}
68
69impl TryFrom<&responses::Peer> for Peer {
70 type Error = anyhow::Error;
71
72 fn try_from(p: &responses::Peer) -> Result<Peer> {
73 let features = match &p.features {
74 Some(f) => f.to_string(),
75 None => "".to_string(),
76 };
77
78 Ok(Peer {
79 id: hex::decode(&p.id)?,
80 connected: p.connected,
81 features: features,
82 addresses: vec![], channels: p.channels.iter().map(|c| c.into()).collect(),
84 })
85 }
86}
87
88impl TryFrom<responses::ListPeers> for ListPeersResponse {
89 type Error = anyhow::Error;
90 fn try_from(lp: responses::ListPeers) -> Result<ListPeersResponse> {
91 let peers: Result<Vec<Peer>> = lp.peers.iter().map(|p| p.try_into()).collect();
92
93 Ok(ListPeersResponse { peers: peers? })
94 }
95}
96
97impl From<&str> for OutputStatus {
98 fn from(s: &str) -> Self {
99 match s {
100 "confirmed" => OutputStatus::Confirmed,
101 "unconfirmed" => OutputStatus::Unconfirmed,
102 _ => panic!("Unknown output status {}", s),
103 }
104 }
105}
106
107impl From<&responses::ListFundsOutput> for ListFundsOutput {
108 fn from(o: &responses::ListFundsOutput) -> Self {
109 let status: OutputStatus = o.status[..].into();
110 ListFundsOutput {
111 address: o.address.clone(),
112 amount: Some(Amount {
113 unit: Some(amount::Unit::Millisatoshi(o.amount_msat.0)),
114 }),
115 reserved: o.reserved,
116 reserved_to_block: o.reserved_to_block.unwrap_or_default(),
117 output: Some(Outpoint {
118 outnum: o.output as u32,
119 txid: hex::decode(&o.txid).unwrap(),
120 }),
121 status: status as i32,
122 }
123 }
124}
125
126fn parse_scid(scid: &Option<String>) -> u64 {
128 match &scid {
129 Some(i) => match ShortChannelId::from_str(&i) {
130 Ok(i) => (i.block() as u64) << 40 | (i.txindex() as u64) << 16 | (i.outnum() as u64),
131 Err(e) => {
132 warn!(
133 "JSON-RPC returned an unparseable short_channel_id {}: {}",
134 i, e
135 );
136 0
137 }
138 },
139 None => 0,
140 }
141}
142
143impl From<&responses::ListFundsChannel> for ListFundsChannel {
144 fn from(c: &responses::ListFundsChannel) -> Self {
145 ListFundsChannel {
146 peer_id: hex::decode(&c.peer_id).unwrap(),
147 connected: c.connected,
148 short_channel_id: parse_scid(&c.short_channel_id),
149 our_amount_msat: c.our_amount_msat.0,
150 amount_msat: c.amount_msat.0,
151 funding_txid: hex::decode(&c.funding_txid).unwrap(),
152 funding_output: c.funding_output as u32,
153 }
154 }
155}
156
157impl From<responses::ListFunds> for ListFundsResponse {
158 fn from(lf: responses::ListFunds) -> Self {
159 ListFundsResponse {
160 outputs: lf.outputs.iter().map(|o| o.into()).collect(),
161 channels: lf.channels.iter().map(|c| c.into()).collect(),
162 }
163 }
164}
165
166impl From<responses::Withdraw> for WithdrawResponse {
167 fn from(r: responses::Withdraw) -> Self {
168 WithdrawResponse {
169 tx: hex::decode(r.tx).unwrap(),
170 txid: hex::decode(r.txid).unwrap(),
171 }
172 }
173}
174
175impl TryFrom<FundChannelRequest> for requests::FundChannel {
176 type Error = anyhow::Error;
177 fn try_from(f: FundChannelRequest) -> Result<Self> {
178 let amount: requests::Amount = match f.amount {
179 Some(v) => v.try_into()?,
180 None => return Err(anyhow!("Funding amount cannot be omitted.")),
181 };
182
183 if let requests::Amount::Any = amount {
184 return Err(anyhow!(
185 "Funding amount cannot be 'any'. Did you mean to use 'all'?"
186 ));
187 }
188
189 if let requests::Amount::Millisatoshi(a) = amount {
190 if a % 1000 != 0 {
191 return Err(anyhow!("Funding amount must be expressed integer satoshis. Millisatoshi amount {} is not.", a));
192 }
193 }
194
195 Ok(requests::FundChannel {
196 id: hex::encode(f.node_id),
197 amount: amount,
198 feerate: None,
199 announce: Some(f.announce),
200 minconf: f.minconf.map(|c| c.blocks),
201 close_to: match f.close_to.as_ref() {
202 "" => None,
203 v => Some(v.to_string()),
204 },
205 })
206 }
207}
208
209impl From<responses::FundChannel> for FundChannelResponse {
210 fn from(r: responses::FundChannel) -> Self {
211 FundChannelResponse {
212 tx: hex::decode(r.tx).unwrap(),
213 outpoint: Some(Outpoint {
214 txid: hex::decode(r.txid).unwrap(),
215 outnum: r.outpoint,
216 }),
217 channel_id: hex::decode(r.channel_id).unwrap(),
218 close_to: r.close_to.unwrap_or("".to_string()),
219 }
220 }
221}
222
223impl HsmRequestContext {
224 pub fn to_client_hsmfd_msg(&self) -> Result<Bytes> {
225 let mut buf = BytesMut::with_capacity(2 + 33 + 8 + 8);
226
227 buf.put_u16(9); buf.put_slice(&self.node_id[..]);
229 buf.put_u64(self.dbid);
230 buf.put_u64(self.capabilities);
231
232 Ok(buf.freeze())
233 }
234
235 pub fn from_client_hsmfd_msg(msg: &mut Bytes) -> Result<HsmRequestContext> {
236 let typ = msg.get_u16();
237
238 if typ != 9 {
239 return Err(anyhow!("message is not an init"));
240 }
241
242 let mut node_id = [0u8; 33];
243 msg.copy_to_slice(&mut node_id);
244 let dbid = msg.get_u64();
245 let caps = msg.get_u64();
246
247 Ok(HsmRequestContext {
248 dbid,
249 node_id: node_id.to_vec(),
250 capabilities: caps,
251 })
252 }
253}
254
255impl HsmRequest {
256 pub fn get_type(&self) -> u16 {
257 (self.raw[0] as u16) << 8 | (self.raw[1] as u16)
258 }
259}
260
261impl TryFrom<responses::CloseChannel> for CloseChannelResponse {
262 type Error = anyhow::Error;
263 fn try_from(c: responses::CloseChannel) -> Result<Self, Self::Error> {
264 Ok(CloseChannelResponse {
265 close_type: match c.close_type.as_ref() {
266 "mutual" => CloseChannelType::Mutual as i32,
267 "unilateral" => CloseChannelType::Unilateral as i32,
268 _ => return Err(anyhow!("Unexpected close type: {}", c.close_type)),
269 },
270 tx: hex::decode(c.tx).unwrap(),
271 txid: hex::decode(c.txid).unwrap(),
272 })
273 }
274}
275impl From<CloseChannelRequest> for requests::CloseChannel {
276 fn from(r: CloseChannelRequest) -> Self {
277 requests::CloseChannel {
278 node_id: hex::encode(r.node_id),
279 timeout: match r.unilateraltimeout {
280 Some(v) => Some(v.seconds),
281 None => None,
282 },
283 destination: match r.destination {
284 None => None,
285 Some(v) => Some(v.address),
286 },
287 }
288 }
289}
290
291impl TryFrom<InvoiceRequest> for requests::Invoice {
292 type Error = anyhow::Error;
293
294 fn try_from(i: InvoiceRequest) -> Result<Self, Self::Error> {
295 if i.label.is_empty() {
296 return Err(anyhow!(
297 "Label must be set, not empty and unique from all other invoice labels."
298 ));
299 }
300
301 let amt: Amount = i.amount.ok_or(anyhow!(
302 "No amount specified. Use Amount(any=true) if you want an amountless invoice"
303 ))?;
304
305 let preimage = (!i.preimage.is_empty()).then(|| hex::encode(&i.preimage));
306 let amount_msat: primitives::AmountOrAny = match amt.unit.ok_or(
307 anyhow!("No amount specified. Use Amount(any=true) if you want an amountless invoice")
308 )? {
309 Unit::All(_) => return Err(anyhow!(
310 "Amount cannot be set to `all`. Use Amount(any=true) if you want an amountless invoice"
311 )),
312 Unit::Any(_) => primitives::AmountOrAny::Any,
313 Unit::Millisatoshi(a) => primitives::AmountOrAny::Amount(primitives::Amount::from_msat(a)),
314 Unit::Satoshi(a) => primitives::AmountOrAny::Amount(primitives::Amount::from_sat(a)),
315 Unit::Bitcoin(a) => primitives::AmountOrAny::Amount(primitives::Amount::from_btc(a)),
316
317 };
318
319 Ok(requests::Invoice {
320 amount_msat,
321 label: i.label,
322 description: i.description,
323 exposeprivatechannels: None,
324 expiry: None,
325 fallbacks: None,
326 preimage,
327 cltv: None,
328 deschashonly: None,
329 dev_routes: None,
330 })
331 }
332}
333
334impl From<u64> for Amount {
335 fn from(i: u64) -> Self {
336 Amount {
337 unit: Some(amount::Unit::Millisatoshi(i)),
338 }
339 }
340}
341
342impl From<responses::Invoice> for Invoice {
346 fn from(i: responses::Invoice) -> Invoice {
347 Invoice {
348 label: "".to_string(),
349 description: "".to_string(),
350 payment_preimage: vec![],
351 amount: None,
352 received: None,
353 payment_time: 0,
354 status: InvoiceStatus::Unpaid as i32,
355 bolt11: i.bolt11,
356 payment_hash: hex::decode(i.payment_hash).unwrap(),
357 expiry_time: i.expiry_time,
358 }
359 }
360}
361
362impl TryFrom<Amount> for requests::Amount {
363 type Error = anyhow::Error;
364 fn try_from(a: Amount) -> Result<Self, Self::Error> {
365 match a.unit {
366 Some(amount::Unit::Millisatoshi(v)) => Ok(requests::Amount::Millisatoshi(v)),
367 Some(amount::Unit::Satoshi(v)) => Ok(requests::Amount::Satoshi(v)),
368 Some(amount::Unit::Bitcoin(v)) => Ok(requests::Amount::Bitcoin(v)),
369 Some(amount::Unit::All(_)) => Ok(requests::Amount::All),
370 Some(amount::Unit::Any(_)) => Ok(requests::Amount::Any),
371 None => Err(anyhow!("cannot convert a unit-less amount")),
372 }
373 }
374}
375
376impl From<responses::Pay> for Payment {
377 fn from(p: responses::Pay) -> Self {
378 Payment {
379 destination: hex::decode(p.destination).unwrap(),
380 payment_hash: hex::decode(p.payment_hash).unwrap(),
381 payment_preimage: p
382 .preimage
383 .map(|p| hex::decode(p).unwrap())
384 .unwrap_or_default(),
385 amount: Some(Amount {
386 unit: Some(amount::Unit::Millisatoshi(p.msatoshi)),
387 }),
388 amount_sent: Some(Amount {
389 unit: Some(amount::Unit::Millisatoshi(p.msatoshi_sent)),
390 }),
391 status: match p.status.as_ref() {
392 "pending" => PayStatus::Pending as i32,
393 "complete" => PayStatus::Complete as i32,
394 "failed" => PayStatus::Failed as i32,
395 o => panic!("Unmapped pay status: {}", o),
396 },
397 bolt11: p.bolt11.unwrap_or("".to_string()),
398 created_at: p.created_at,
399 completed_at: p.completed_at.unwrap_or_default(),
400 }
401 }
402}
403
404impl From<PayRequest> for requests::Pay {
405 fn from(p: PayRequest) -> Self {
406 let maxfeepercent = if p.maxfeepercent.eq(&0.0) {
409 None
410 } else {
411 Some(p.maxfeepercent)
412 };
413
414 requests::Pay {
415 bolt11: p.bolt11,
416 amount: p.amount.map(|a| a.try_into().unwrap()),
417 retry_for: match p.timeout {
418 0 => None,
419 v => Some(v),
420 },
421 maxfee: p.maxfee.map(|a| a.try_into().unwrap()),
422 maxfeepercent,
423 }
424 }
425}
426
427impl TryFrom<Feerate> for requests::Feerate {
428 type Error = anyhow::Error;
429
430 fn try_from(value: Feerate) -> Result<requests::Feerate, anyhow::Error> {
431 use requests as r;
432 let res = match value.value {
433 Some(v) => match v {
434 feerate::Value::Preset(p) => match p {
435 0 => r::Feerate::Normal,
436 1 => r::Feerate::Slow,
437 2 => r::Feerate::Urgent,
438 n => return Err(anyhow!("no such feerate preset {}", n)),
439 },
440 feerate::Value::Perkw(v) => r::Feerate::PerKw(v),
441 feerate::Value::Perkb(v) => r::Feerate::PerKb(v),
442 },
443 None => return Err(anyhow!("Feerate must have a value set")),
444 };
445 Ok(res)
446 }
447}
448
449impl TryFrom<Outpoint> for requests::Outpoint {
450 type Error = anyhow::Error;
451
452 fn try_from(value: Outpoint) -> Result<Self, Self::Error> {
453 if value.txid.len() != 32 {
454 return Err(anyhow!(
455 "{} is not a valid transaction ID",
456 hex::encode(&value.txid)
457 ));
458 }
459
460 Ok(requests::Outpoint {
461 txid: value.txid,
462 outnum: value.outnum.try_into().context("outnum out of range")?,
463 })
464 }
465}
466
467impl TryFrom<ListPaymentsRequest> for requests::ListPays {
468 type Error = anyhow::Error;
469
470 fn try_from(v: ListPaymentsRequest) -> Result<Self, Self::Error> {
471 match v.identifier {
472 Some(identifier) => match identifier.id {
473 Some(payment_identifier::Id::Bolt11(b)) => Ok(requests::ListPays {
474 payment_hash: None,
475 bolt11: Some(b),
476 }),
477 Some(payment_identifier::Id::PaymentHash(h)) => Ok(requests::ListPays {
478 payment_hash: Some(hex::encode(h)),
479 bolt11: None,
480 }),
481 None => Ok(requests::ListPays {
482 bolt11: None,
483 payment_hash: None,
484 }),
485 },
486 None => Ok(requests::ListPays {
487 bolt11: None,
488 payment_hash: None,
489 }),
490 }
491 }
492}
493
494impl TryFrom<responses::ListPays> for ListPaymentsResponse {
495 type Error = anyhow::Error;
496
497 fn try_from(v: responses::ListPays) -> Result<Self, Self::Error> {
498 let payments: Result<Vec<Payment>> = v.pays.iter().map(|p| p.clone().try_into()).collect();
499 Ok(ListPaymentsResponse {
500 payments: payments?,
501 })
502 }
503}
504
505impl TryFrom<responses::ListPaysPay> for Payment {
506 type Error = anyhow::Error;
507 fn try_from(p: responses::ListPaysPay) -> Result<Self, Self::Error> {
508 Ok(Payment {
509 destination: hex::decode(p.destination).unwrap(),
510 payment_hash: hex::decode(p.payment_hash).unwrap(),
511 payment_preimage: p
512 .payment_preimage
513 .map_or(vec![], |p| hex::decode(p).unwrap()),
514 status: match p.status.as_ref() {
515 "pending" => PayStatus::Pending as i32,
516 "complete" => PayStatus::Complete as i32,
517 "failed" => PayStatus::Failed as i32,
518 o => panic!("Unmapped pay status: {}", o),
519 },
520 amount: p.amount_msat.map(|a| a.try_into().unwrap()),
521 amount_sent: Some(p.amount_sent_msat.try_into()?),
522 bolt11: p.bolt11.unwrap_or("".to_string()),
523 created_at: p.created_at,
524 completed_at: p.completed_at.unwrap_or_default(),
525 })
526 }
527}
528
529impl TryFrom<String> for Amount {
530 type Error = anyhow::Error;
531 fn try_from(s: String) -> Result<Amount, Self::Error> {
532 match s.strip_suffix("msat") {
533 Some(v) => Ok(Amount {
534 unit: Some(amount::Unit::Millisatoshi(v.parse()?)),
535 }),
536 None => Err(anyhow!("Amount {} does not have 'msat' suffix", s)),
537 }
538 }
539}
540
541impl TryFrom<crate::responses::MSat> for Amount {
542 type Error = anyhow::Error;
543 fn try_from(s: crate::responses::MSat) -> Result<Amount, Self::Error> {
544 Ok(Amount {
545 unit: Some(amount::Unit::Millisatoshi(s.0)),
546 })
547 }
548}
549
550impl TryFrom<String> for InvoiceStatus {
551 type Error = anyhow::Error;
552
553 fn try_from(v: String) -> Result<Self, anyhow::Error> {
554 match v.as_ref() {
555 "unpaid" => Ok(InvoiceStatus::Unpaid),
556 "paid" => Ok(InvoiceStatus::Paid),
557 "expired" => Ok(InvoiceStatus::Expired),
558 s => Err(anyhow!("{} is not a valid InvoiceStatus", s)),
559 }
560 }
561}
562
563impl TryFrom<ListInvoicesRequest> for requests::ListInvoices {
564 type Error = anyhow::Error;
565
566 fn try_from(v: ListInvoicesRequest) -> Result<Self, Self::Error> {
567 match v.identifier {
568 None => Ok(requests::ListInvoices {
569 label: None,
570 invstring: None,
571 payment_hash: None,
572 }),
573 Some(i) => match i.id {
574 None => Ok(requests::ListInvoices {
575 label: None,
576 invstring: None,
577 payment_hash: None,
578 }),
579 Some(invoice_identifier::Id::Label(s)) => Ok(requests::ListInvoices {
580 label: Some(s),
581 invstring: None,
582 payment_hash: None,
583 }),
584 Some(invoice_identifier::Id::PaymentHash(s)) => Ok(requests::ListInvoices {
585 label: None,
586 invstring: None,
587 payment_hash: Some(hex::encode(s)),
588 }),
589 Some(invoice_identifier::Id::Invstring(s)) => Ok(requests::ListInvoices {
590 label: None,
591 invstring: Some(s),
592 payment_hash: None,
593 }),
594 },
595 }
596 }
597}
598
599impl From<&responses::ListInvoiceInvoice> for Invoice {
600 fn from(i: &responses::ListInvoiceInvoice) -> Invoice {
601 let status: InvoiceStatus = i.status.clone().try_into().unwrap();
602 let amount: Amount = if i.amount.is_none() {
603 Amount {
604 unit: Some(crate::pb::amount::Unit::Any(true)),
605 }
606 } else {
607 i.amount.clone().unwrap().try_into().unwrap()
608 };
609
610 Invoice {
611 amount: Some(amount),
612 bolt11: i.bolt11.clone(),
613 description: i.description.clone(),
614 expiry_time: i.expiry_time,
615 label: i.label.clone(),
616 payment_hash: hex::decode(&i.payment_hash).unwrap(),
617 payment_preimage: i
618 .payment_preimage
619 .clone()
620 .map(|p| hex::decode(p).unwrap())
621 .unwrap_or_default(),
622 status: status as i32,
623 received: i.received.clone().map(|i| i.try_into().unwrap()),
624 payment_time: i.payment_time.clone().unwrap_or_default(),
625 }
626 }
627}
628
629impl TryFrom<responses::ListInvoices> for ListInvoicesResponse {
630 type Error = anyhow::Error;
631 fn try_from(l: responses::ListInvoices) -> Result<Self, Self::Error> {
632 let invoices: Vec<Invoice> = l.invoices.iter().map(|i| i.into()).collect();
633 Ok(ListInvoicesResponse { invoices })
634 }
635}
636
637impl From<messages::TlvField> for TlvField {
638 fn from(f: messages::TlvField) -> TlvField {
639 TlvField {
640 r#type: f.typ,
641 value: hex::decode(f.value).unwrap(),
642 }
643 }
644}
645
646impl TryFrom<KeysendRequest> for requests::Keysend {
647 type Error = anyhow::Error;
648 fn try_from(r: KeysendRequest) -> Result<requests::Keysend> {
649 use std::collections::HashMap;
650 let mut tlvs: HashMap<u64, String> = HashMap::new();
652
653 for e in r.extratlvs {
654 tlvs.insert(e.r#type, hex::encode(e.value));
655 }
656
657 let mut routehints = vec![];
658 for rh in r.routehints {
659 routehints.push(rh.into())
660 }
661
662 Ok(requests::Keysend {
663 destination: hex::encode(r.node_id),
664 msatoshi: r.amount.unwrap().try_into()?,
665 label: r.label,
666 exemptfee: None,
667 maxfeepercent: None,
668 maxdelay: None,
669 extratlvs: Some(tlvs),
670 routehints: Some(routehints),
671 retry_for: None,
672 })
673 }
674}
675
676impl From<responses::Keysend> for Payment {
677 fn from(r: responses::Keysend) -> Payment {
678 use std::time::SystemTime;
679
680 let amount_msat = r.msatoshi.unwrap_or_else(|| {
681 r.amount_msat
682 .expect("neither amount_msat nor msatoshi is set in keysend response")
683 .0
684 });
685
686 let amount_sent_msat = r.msatoshi_sent.unwrap_or_else(|| {
687 r.amount_sent_msat
688 .expect("neither amount_sent_msat nor msatoshi_sent is set in keysend response")
689 .0
690 });
691
692 Payment {
693 destination: hex::decode(r.destination).unwrap(),
694 payment_hash: hex::decode(r.payment_hash).unwrap(),
695 payment_preimage: hex::decode(r.payment_preimage.unwrap_or("".to_string())).unwrap(),
696 status: match r.status.as_ref() {
697 "pending" => PayStatus::Pending as i32,
698 "complete" => PayStatus::Complete as i32,
699 "failed" => PayStatus::Failed as i32,
700 o => panic!("Unmapped pay status: {}", o),
701 },
702 amount: Some(Amount {
703 unit: Some(amount::Unit::Millisatoshi(amount_msat)),
704 }),
705 amount_sent: Some(Amount {
706 unit: Some(amount::Unit::Millisatoshi(amount_sent_msat)),
707 }),
708 bolt11: "".to_string(),
709 created_at: SystemTime::now()
710 .duration_since(SystemTime::UNIX_EPOCH)
711 .unwrap()
712 .as_secs_f64(),
713 completed_at: 0,
714 }
715 }
716}
717
718impl From<RoutehintHop> for requests::RoutehintHop {
719 fn from(r: RoutehintHop) -> requests::RoutehintHop {
720 requests::RoutehintHop {
721 id: hex::encode(r.node_id),
722 scid: r.short_channel_id,
723 feebase: r.fee_base,
724 feeprop: r.fee_prop,
725 expirydelta: r.cltv_expiry_delta as u16,
726 }
727 }
728}
729
730impl From<Routehint> for Vec<requests::RoutehintHop> {
731 fn from(r: Routehint) -> Vec<requests::RoutehintHop> {
732 let mut hops = vec![];
733 for h in r.hops {
734 hops.push(h.into())
735 }
736 hops
737 }
738}
739
740impl From<RoutehintHop> for requests::RoutehintHopDev {
741 fn from(r: RoutehintHop) -> requests::RoutehintHopDev {
742 requests::RoutehintHopDev {
743 id: hex::encode(r.node_id),
744 short_channel_id: r.short_channel_id,
745 fee_base_msat: r.fee_base,
746 fee_proportional_millionths: r.fee_prop,
747 cltv_expiry_delta: r.cltv_expiry_delta as u16,
748 }
749 }
750}
751
752impl From<RouteHop> for requests::RoutehintHopDev {
753 fn from(r: RouteHop) -> requests::RoutehintHopDev {
754 requests::RoutehintHopDev {
755 id: hex::encode(r.id),
756 short_channel_id: r.short_channel_id,
757 fee_base_msat: r.feebase.map(|f| f.msat).unwrap(),
758 fee_proportional_millionths: r.feeprop,
759 cltv_expiry_delta: r.expirydelta as u16,
760 }
761 }
762}
763
764impl From<cln_grpc::pb::InvoiceRequest> for requests::Invoice {
765 fn from(ir: cln_grpc::pb::InvoiceRequest) -> Self {
766 let fallbacks = (!ir.fallbacks.is_empty()).then(|| ir.fallbacks);
767 Self {
768 amount_msat: ir
769 .amount_msat
770 .map(|a| a.into())
771 .unwrap_or(primitives::AmountOrAny::Any),
772 description: ir.description,
773 dev_routes: None,
774 label: ir.label,
775 exposeprivatechannels: None,
776 preimage: ir.preimage.map(|p| hex::encode(p)),
777 expiry: ir.expiry,
778 fallbacks,
779 cltv: ir.cltv,
780 deschashonly: ir.deschashonly,
781 }
782 }
783}
784
785impl From<responses::Invoice> for cln_grpc::pb::InvoiceResponse {
786 fn from(i: responses::Invoice) -> Self {
787 cln_grpc::pb::InvoiceResponse {
788 bolt11: i.bolt11,
789 expires_at: i.expiry_time as u64,
790 payment_hash: hex::decode(i.payment_hash).unwrap(),
791 payment_secret: i
792 .payment_secret
793 .map(|s| hex::decode(s).unwrap())
794 .unwrap_or_default(),
795 warning_capacity: None,
796 warning_mpp: None,
797 warning_deadends: None,
798 warning_offline: None,
799 warning_private_unused: None,
800 created_index: None,
801 }
802 }
803}
804impl From<cln_grpc::pb::AmountOrAny> for requests::Amount {
805 fn from(a: cln_grpc::pb::AmountOrAny) -> Self {
806 match a.value {
807 Some(cln_grpc::pb::amount_or_any::Value::Any(_)) => requests::Amount::Any,
808 Some(cln_grpc::pb::amount_or_any::Value::Amount(a)) => {
809 requests::Amount::Millisatoshi(a.msat)
810 }
811 None => panic!(),
812 }
813 }
814}