bs_gl_plugin/
pb.rs

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![], // TODO implement
65        }
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![], // TODO Add field
83            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
126/// Small helper to encode short_channel_ids as protobuf ints
127fn 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); // client_hsmfd type
228        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
342/// Converts the result of the `invoice` call into the shared format
343/// for invoices in protobuf, hence the subset of fields that are
344/// initialized to default values.
345impl 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        // PartialEq papers over the differences between 0.0 and -0.0
407        // in floats.
408        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        // Transform the extratlvs into aa key-value dict:
651        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}