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

lightningnetwork / lnd / 15759452760

19 Jun 2025 01:48PM UTC coverage: 69.436% (+1.3%) from 68.161%
15759452760

Pull #9625

github

web-flow
Merge 469117657 into e0a9705d5
Pull Request #9625: Add DeleteInvoice gRPC call

88 of 164 new or added lines in 5 files covered. (53.66%)

65 existing lines in 22 files now uncovered.

137612 of 198185 relevant lines covered (69.44%)

22066.8 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
        "google.golang.org/protobuf/proto"
12
)
13

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

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

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

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

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

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

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

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

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

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

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

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

203
        printRespJSON(resp)
×
204

×
205
        return nil
×
206
}
207

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

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

220
                return nil, nil
×
221
        }
222

223
        var blindCfg lnrpc.BlindedPathConfig
×
224

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

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

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

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

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

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

266
        return &blindCfg, nil
×
267
}
268

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

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

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

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

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

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

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

316
        printRespJSON(invoice)
×
317

×
318
        return nil
×
319
}
320

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

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

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

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

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

398
        printRespJSON(invoices)
×
399

×
400
        return nil
×
401
}
402

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

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

×
423
        var payreq string
×
424

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

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

441
        printRespJSON(resp)
×
442
        return nil
×
443
}
444

445
var deleteCanceledInvoicesCommand = cli.Command{
446
        Name:      "deletecanceledinvoices",
447
        Category:  "Invoices",
448
        Usage:     "Delete a list or all canceled invoices from the database.",
449
        ArgsUsage: "--all --invoice_hashes hash1,hash2",
450
        Description: `
451
        This command either deletes all canceled invoices or a list of invoices
452
        from the database.
453

454
        If the --all flag is used, then all canceled invoices are removed. 
455

456
        If --invoice_hashes is specified, those invoices are deleted,
457
        if they are on canceled state already.
458
        `,
459
        Action: actionDecorator(deleteCanceledInvoices),
460
        Flags: []cli.Flag{
461
                cli.BoolFlag{
462
                        Name:  "all",
463
                        Usage: "delete all canceled invoices",
464
                },
465
                cli.StringFlag{
466
                        Name: "invoice_hashes",
467
                        Usage: "delete a list of invoices identified by " +
468
                                "their payment hashes",
469
                },
470
        },
471
}
472

NEW
473
func deleteCanceledInvoices(ctx *cli.Context) error {
×
NEW
474
        ctxc := getContext()
×
NEW
475
        client, cleanUp := getClient(ctx)
×
NEW
476
        defer cleanUp()
×
NEW
477

×
NEW
478
        // Show command help if arguments or no flags are provided.
×
NEW
479
        if ctx.NArg() > 0 || ctx.NumFlags() == 0 {
×
NEW
480
                _ = cli.ShowCommandHelp(ctx, "deletecanceledinvoices")
×
NEW
481
                return nil
×
NEW
482
        }
×
483

NEW
484
        var (
×
NEW
485
                hashArray     []string
×
NEW
486
                hashList      = ctx.String("invoice_hashes")
×
NEW
487
                all           = ctx.Bool("all")
×
NEW
488
                hashListIsSet = ctx.IsSet("invoice_hashes")
×
NEW
489
                err           error
×
NEW
490
                resp          proto.Message
×
NEW
491
        )
×
NEW
492

×
NEW
493
        // Checking a non-valid combination of the flags.
×
NEW
494
        if all && hashListIsSet {
×
NEW
495
                return fmt.Errorf("cannot use --all and --invoice_hashes at " +
×
NEW
496
                        "the same time")
×
NEW
497
        }
×
498

499
        // Deleting a list of invoices is implemented in a different RPC than
500
        // removing all invoices.
NEW
501
        switch {
×
NEW
502
        case hashListIsSet:
×
NEW
503
                invoiceHashList := strings.Split(hashList, ",")
×
NEW
504
                hashArray = append(hashArray, invoiceHashList...)
×
NEW
505

×
NEW
506
                resp, err = client.DeleteCanceledInvoices(
×
NEW
507
                        ctxc, &lnrpc.DeleteInvoicesRequest{
×
NEW
508
                                InvoiceHashes: hashArray,
×
NEW
509
                        },
×
NEW
510
                )
×
NEW
511
                if err != nil {
×
NEW
512
                        return err
×
NEW
513
                }
×
514

NEW
515
        case all:
×
NEW
516
                resp, err = client.DeleteCanceledInvoices(
×
NEW
517
                        ctxc, &lnrpc.DeleteInvoicesRequest{
×
NEW
518
                                AllInvoices: true,
×
NEW
519
                        },
×
NEW
520
                )
×
NEW
521
                if err != nil {
×
NEW
522
                        return err
×
NEW
523
                }
×
524
        }
525

NEW
526
        printJSON(resp)
×
NEW
527

×
NEW
528
        return nil
×
529
}
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