• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 14699173586

28 Apr 2025 02:48AM UTC coverage: 68.922% (+10.3%) from 58.589%
14699173586

Pull #9127

github

web-flow
Merge 7e573b967 into 7e50b8438
Pull Request #9127: Add the option on path creator to specify the incoming channel on blinded path

122 of 150 new or added lines in 7 files covered. (81.33%)

305 existing lines in 22 files now uncovered.

133772 of 194091 relevant lines covered (68.92%)

22103.45 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cmd/commands/cmd_invoice.go
1
package commands
2

3
import (
4
        "encoding/hex"
5
        "fmt"
6
        "strconv"
7
        "strings"
8

9
        "github.com/lightningnetwork/lnd/lnrpc"
10
        "github.com/urfave/cli"
11
)
12

13
var AddInvoiceCommand = cli.Command{
14
        Name:     "addinvoice",
15
        Category: "Invoices",
16
        Usage:    "Add a new invoice.",
17
        Description: `
18
        Add a new invoice, expressing intent for a future payment.
19

20
        Invoices without an amount can be created by not supplying any
21
        parameters or providing an amount of 0. These invoices allow the payer
22
        to specify the amount of satoshis they wish to send.`,
23
        ArgsUsage: "value preimage",
24
        Flags: []cli.Flag{
25
                cli.StringFlag{
26
                        Name: "memo",
27
                        Usage: "a description of the payment to attach along " +
28
                                "with the invoice (default=\"\")",
29
                },
30
                cli.StringFlag{
31
                        Name: "preimage",
32
                        Usage: "the hex-encoded preimage (32 byte) which will " +
33
                                "allow settling an incoming HTLC payable to this " +
34
                                "preimage. If not set, a random preimage will be " +
35
                                "created.",
36
                },
37
                cli.Int64Flag{
38
                        Name:  "amt",
39
                        Usage: "the amt of satoshis in this invoice",
40
                },
41
                cli.Int64Flag{
42
                        Name:  "amt_msat",
43
                        Usage: "the amt of millisatoshis in this invoice",
44
                },
45
                cli.StringFlag{
46
                        Name: "description_hash",
47
                        Usage: "SHA-256 hash of the description of the payment. " +
48
                                "Used if the purpose of payment cannot naturally " +
49
                                "fit within the memo. If provided this will be " +
50
                                "used instead of the description(memo) field in " +
51
                                "the encoded invoice.",
52
                },
53
                cli.StringFlag{
54
                        Name: "fallback_addr",
55
                        Usage: "fallback on-chain address that can be used in " +
56
                                "case the lightning payment fails",
57
                },
58
                cli.Int64Flag{
59
                        Name: "expiry",
60
                        Usage: "the invoice's expiry time in seconds. If not " +
61
                                "specified, an expiry of " +
62
                                "86400 seconds (24 hours) is implied.",
63
                },
64
                cli.Uint64Flag{
65
                        Name: "cltv_expiry_delta",
66
                        Usage: "The minimum CLTV delta to use for the final " +
67
                                "hop. If this is set to 0, the default value " +
68
                                "is used. The default value for " +
69
                                "cltv_expiry_delta is configured by the " +
70
                                "'bitcoin.timelockdelta' option.",
71
                },
72
                cli.BoolFlag{
73
                        Name: "private",
74
                        Usage: "encode routing hints in the invoice with " +
75
                                "private channels in order to assist the " +
76
                                "payer in reaching you. If amt and amt_msat " +
77
                                "are zero, a large number of hints with " +
78
                                "these channels can be included, which " +
79
                                "might not be desirable.",
80
                },
81
                cli.BoolFlag{
82
                        Name: "amp",
83
                        Usage: "creates an AMP invoice. If true, preimage " +
84
                                "should not be set.",
85
                },
86
                cli.BoolFlag{
87
                        Name: "blind",
88
                        Usage: "creates an invoice that contains blinded " +
89
                                "paths. Note that invoices with blinded " +
90
                                "paths will be signed using a random " +
91
                                "ephemeral key so as not to reveal the real " +
92
                                "node ID of this node.",
93
                },
94
                cli.UintFlag{
95
                        Name: "min_real_blinded_hops",
96
                        Usage: "The minimum number of real hops to use in a " +
97
                                "blinded path. This option will only be used " +
98
                                "if `--blind` has also been set.",
99
                },
100
                cli.UintFlag{
101
                        Name: "num_blinded_hops",
102
                        Usage: "The number of hops to use for each " +
103
                                "blinded path included in the invoice. This " +
104
                                "option will only be used if `--blind` has " +
105
                                "also been set. Dummy hops will be used to " +
106
                                "pad paths shorter than this.",
107
                },
108
                cli.UintFlag{
109
                        Name: "max_blinded_paths",
110
                        Usage: "The maximum number of blinded paths to add " +
111
                                "to an invoice. This option will only be " +
112
                                "used if `--blind` has also been set.",
113
                },
114
                cli.StringSliceFlag{
115
                        Name: "blinded_path_omit_node",
116
                        Usage: "The pub key (in hex) of a node not to " +
117
                                "use on a blinded path. The flag may be " +
118
                                "specified multiple times.",
119
                },
120
                cli.StringFlag{
121
                        Name: "blinded_path_incoming_channel_list",
122
                        Usage: "The chained channels specified via channel " +
123
                                "id (separated by commas), starting from a " +
124
                                "channel which points to the self node.",
125
                },
126
        },
127
        Action: actionDecorator(addInvoice),
128
}
129

130
func addInvoice(ctx *cli.Context) error {
×
131
        var (
×
132
                preimage []byte
×
133
                descHash []byte
×
134
                amt      int64
×
135
                amtMsat  int64
×
136
                err      error
×
137
        )
×
138
        ctxc := getContext()
×
139
        client, cleanUp := getClient(ctx)
×
140
        defer cleanUp()
×
141

×
142
        args := ctx.Args()
×
143

×
144
        amt = ctx.Int64("amt")
×
145
        amtMsat = ctx.Int64("amt_msat")
×
146
        if !ctx.IsSet("amt") && !ctx.IsSet("amt_msat") && args.Present() {
×
147
                amt, err = strconv.ParseInt(args.First(), 10, 64)
×
148
                args = args.Tail()
×
149
                if err != nil {
×
150
                        return fmt.Errorf("unable to decode amt argument: %w",
×
151
                                err)
×
152
                }
×
153
        }
154

155
        switch {
×
156
        case ctx.IsSet("preimage"):
×
157
                preimage, err = hex.DecodeString(ctx.String("preimage"))
×
158
        case args.Present():
×
159
                preimage, err = hex.DecodeString(args.First())
×
160
        }
161

162
        if err != nil {
×
163
                return fmt.Errorf("unable to parse preimage: %w", err)
×
164
        }
×
165

166
        descHash, err = hex.DecodeString(ctx.String("description_hash"))
×
167
        if err != nil {
×
168
                return fmt.Errorf("unable to parse description_hash: %w", err)
×
169
        }
×
170

171
        if ctx.IsSet("private") && ctx.IsSet("blind") {
×
172
                return fmt.Errorf("cannot include both route hints and " +
×
173
                        "blinded paths in the same invoice")
×
174
        }
×
175

176
        blindedPathCfg, err := parseBlindedPathCfg(ctx)
×
177
        if err != nil {
×
178
                return fmt.Errorf("could not parse blinded path config: %w",
×
179
                        err)
×
180
        }
×
181

182
        invoice := &lnrpc.Invoice{
×
183
                Memo:              ctx.String("memo"),
×
184
                RPreimage:         preimage,
×
185
                Value:             amt,
×
186
                ValueMsat:         amtMsat,
×
187
                DescriptionHash:   descHash,
×
188
                FallbackAddr:      ctx.String("fallback_addr"),
×
189
                Expiry:            ctx.Int64("expiry"),
×
190
                CltvExpiry:        ctx.Uint64("cltv_expiry_delta"),
×
191
                Private:           ctx.Bool("private"),
×
192
                IsAmp:             ctx.Bool("amp"),
×
193
                IsBlinded:         ctx.Bool("blind"),
×
194
                BlindedPathConfig: blindedPathCfg,
×
195
        }
×
196

×
197
        resp, err := client.AddInvoice(ctxc, invoice)
×
198
        if err != nil {
×
199
                return err
×
200
        }
×
201

202
        printRespJSON(resp)
×
203

×
204
        return nil
×
205
}
206

207
func parseBlindedPathCfg(ctx *cli.Context) (*lnrpc.BlindedPathConfig, error) {
×
208
        if !ctx.Bool("blind") {
×
209
                if ctx.IsSet("min_real_blinded_hops") ||
×
210
                        ctx.IsSet("num_blinded_hops") ||
×
211
                        ctx.IsSet("max_blinded_paths") ||
×
NEW
212
                        ctx.IsSet("blinded_path_omit_node") ||
×
NEW
213
                        ctx.IsSet("blinded_path_incoming_channel_list") {
×
214

×
215
                        return nil, fmt.Errorf("blinded path options are " +
×
216
                                "only used if the `--blind` options is set")
×
217
                }
×
218

219
                return nil, nil
×
220
        }
221

222
        var blindCfg lnrpc.BlindedPathConfig
×
223

×
224
        if ctx.IsSet("min_real_blinded_hops") {
×
225
                minNumRealHops := uint32(ctx.Uint("min_real_blinded_hops"))
×
226
                blindCfg.MinNumRealHops = &minNumRealHops
×
227
        }
×
228

229
        if ctx.IsSet("num_blinded_hops") {
×
230
                numHops := uint32(ctx.Uint("num_blinded_hops"))
×
231
                blindCfg.NumHops = &numHops
×
232
        }
×
233

234
        if ctx.IsSet("max_blinded_paths") {
×
235
                maxPaths := uint32(ctx.Uint("max_blinded_paths"))
×
236
                blindCfg.MaxNumPaths = &maxPaths
×
237
        }
×
238

239
        for _, pubKey := range ctx.StringSlice("blinded_path_omit_node") {
×
240
                pubKeyBytes, err := hex.DecodeString(pubKey)
×
241
                if err != nil {
×
242
                        return nil, err
×
243
                }
×
244

245
                blindCfg.NodeOmissionList = append(
×
246
                        blindCfg.NodeOmissionList, pubKeyBytes,
×
247
                )
×
248
        }
249

NEW
250
        if ctx.IsSet("blinded_path_incoming_channel_list") {
×
NEW
251
                channels := strings.Split(
×
NEW
252
                        ctx.String("blinded_path_incoming_channel_list"), ",",
×
NEW
253
                )
×
NEW
254
                for _, channelID := range channels {
×
NEW
255
                        chanID, err := strconv.ParseUint(channelID, 10, 64)
×
NEW
256
                        if err != nil {
×
NEW
257
                                return nil, err
×
NEW
258
                        }
×
NEW
259
                        blindCfg.IncomingChannelList = append(
×
NEW
260
                                blindCfg.IncomingChannelList, chanID,
×
NEW
261
                        )
×
262
                }
263
        }
264

265
        return &blindCfg, nil
×
266
}
267

268
var lookupInvoiceCommand = cli.Command{
269
        Name:      "lookupinvoice",
270
        Category:  "Invoices",
271
        Usage:     "Lookup an existing invoice by its payment hash.",
272
        ArgsUsage: "rhash",
273
        Flags: []cli.Flag{
274
                cli.StringFlag{
275
                        Name: "rhash",
276
                        Usage: "the 32 byte payment hash of the invoice to query for, the hash " +
277
                                "should be a hex-encoded string",
278
                },
279
        },
280
        Action: actionDecorator(lookupInvoice),
281
}
282

283
func lookupInvoice(ctx *cli.Context) error {
×
284
        ctxc := getContext()
×
285
        client, cleanUp := getClient(ctx)
×
286
        defer cleanUp()
×
287

×
288
        var (
×
289
                rHash []byte
×
290
                err   error
×
291
        )
×
292

×
293
        switch {
×
294
        case ctx.IsSet("rhash"):
×
295
                rHash, err = hex.DecodeString(ctx.String("rhash"))
×
296
        case ctx.Args().Present():
×
297
                rHash, err = hex.DecodeString(ctx.Args().First())
×
298
        default:
×
299
                return fmt.Errorf("rhash argument missing")
×
300
        }
301

302
        if err != nil {
×
303
                return fmt.Errorf("unable to decode rhash argument: %w", err)
×
304
        }
×
305

306
        req := &lnrpc.PaymentHash{
×
307
                RHash: rHash,
×
308
        }
×
309

×
310
        invoice, err := client.LookupInvoice(ctxc, req)
×
311
        if err != nil {
×
312
                return err
×
313
        }
×
314

315
        printRespJSON(invoice)
×
316

×
317
        return nil
×
318
}
319

320
var listInvoicesCommand = cli.Command{
321
        Name:     "listinvoices",
322
        Category: "Invoices",
323
        Usage: "List all invoices currently stored within the database. Any " +
324
                "active debug invoices are ignored.",
325
        Description: `
326
        This command enables the retrieval of all invoices currently stored
327
        within the database. It has full support for paginationed responses,
328
        allowing users to query for specific invoices through their add_index.
329
        This can be done by using either the first_index_offset or
330
        last_index_offset fields included in the response as the index_offset of
331
        the next request. Backward pagination is enabled by default to receive
332
        current invoices first. If you wish to paginate forwards, set the 
333
        paginate-forwards flag.        If none of the parameters are specified, then 
334
        the last 100 invoices will be returned.
335

336
        For example: if you have 200 invoices, "lncli listinvoices" will return
337
        the last 100 created. If you wish to retrieve the previous 100, the
338
        first_offset_index of the response can be used as the index_offset of
339
        the next listinvoices request.`,
340
        Flags: []cli.Flag{
341
                cli.BoolFlag{
342
                        Name: "pending_only",
343
                        Usage: "toggles if all invoices should be returned, " +
344
                                "or only those that are currently unsettled",
345
                },
346
                cli.Uint64Flag{
347
                        Name: "index_offset",
348
                        Usage: "the index of an invoice that will be used as " +
349
                                "either the start or end of a query to " +
350
                                "determine which invoices should be returned " +
351
                                "in the response",
352
                },
353
                cli.Uint64Flag{
354
                        Name:  "max_invoices",
355
                        Usage: "the max number of invoices to return",
356
                },
357
                cli.BoolFlag{
358
                        Name: "paginate-forwards",
359
                        Usage: "if set, invoices succeeding the " +
360
                                "index_offset will be returned",
361
                },
362
                cli.Uint64Flag{
363
                        Name: "creation_date_start",
364
                        Usage: "timestamp in seconds, if set, filter " +
365
                                "invoices with creation date greater than or " +
366
                                "equal to it",
367
                },
368
                cli.Uint64Flag{
369
                        Name: "creation_date_end",
370
                        Usage: "timestamp in seconds, if set, filter " +
371
                                "invoices with creation date less than or " +
372
                                "equal to it",
373
                },
374
        },
375
        Action: actionDecorator(listInvoices),
376
}
377

378
func listInvoices(ctx *cli.Context) error {
×
379
        ctxc := getContext()
×
380
        client, cleanUp := getClient(ctx)
×
381
        defer cleanUp()
×
382

×
383
        req := &lnrpc.ListInvoiceRequest{
×
384
                PendingOnly:       ctx.Bool("pending_only"),
×
385
                IndexOffset:       ctx.Uint64("index_offset"),
×
386
                NumMaxInvoices:    ctx.Uint64("max_invoices"),
×
387
                Reversed:          !ctx.Bool("paginate-forwards"),
×
388
                CreationDateStart: ctx.Uint64("creation_date_start"),
×
389
                CreationDateEnd:   ctx.Uint64("creation_date_end"),
×
390
        }
×
391

×
392
        invoices, err := client.ListInvoices(ctxc, req)
×
393
        if err != nil {
×
394
                return err
×
395
        }
×
396

397
        printRespJSON(invoices)
×
398

×
399
        return nil
×
400
}
401

402
var decodePayReqCommand = cli.Command{
403
        Name:        "decodepayreq",
404
        Category:    "Invoices",
405
        Usage:       "Decode a payment request.",
406
        Description: "Decode the passed payment request revealing the destination, payment hash and value of the payment request",
407
        ArgsUsage:   "pay_req",
408
        Flags: []cli.Flag{
409
                cli.StringFlag{
410
                        Name:  "pay_req",
411
                        Usage: "the bech32 encoded payment request",
412
                },
413
        },
414
        Action: actionDecorator(decodePayReq),
415
}
416

417
func decodePayReq(ctx *cli.Context) error {
×
418
        ctxc := getContext()
×
419
        client, cleanUp := getClient(ctx)
×
420
        defer cleanUp()
×
421

×
422
        var payreq string
×
423

×
424
        switch {
×
425
        case ctx.IsSet("pay_req"):
×
426
                payreq = ctx.String("pay_req")
×
427
        case ctx.Args().Present():
×
428
                payreq = ctx.Args().First()
×
429
        default:
×
430
                return fmt.Errorf("pay_req argument missing")
×
431
        }
432

433
        resp, err := client.DecodePayReq(ctxc, &lnrpc.PayReqString{
×
434
                PayReq: StripPrefix(payreq),
×
435
        })
×
436
        if err != nil {
×
437
                return err
×
438
        }
×
439

440
        printRespJSON(resp)
×
441
        return nil
×
442
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc