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

lightningnetwork / lnd / 16626557198

30 Jul 2025 03:14PM UTC coverage: 67.054% (-0.2%) from 67.216%
16626557198

push

github

web-flow
Merge pull request #9625 from MPins/issue-8161

Add deletecanceledinvoice RPC call

31 of 81 new or added lines in 4 files covered. (38.27%)

579 existing lines in 34 files now uncovered.

135260 of 201718 relevant lines covered (67.05%)

21683.86 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 deleteCanceledInvoiceCommand = cli.Command{
446
        Name:      "deletecanceledinvoice",
447
        Category:  "Invoices",
448
        Usage:     "Delete a canceled invoice from the database.",
449
        ArgsUsage: "invoice_hash",
450
        Description: `
451
        This command deletes a canceled invoice from the database. Note that
452
        expired invoices are automatically moved to the canceled state.
453
        Once canceled, they can be deleted using this command.
454
        `,
455
        Flags: []cli.Flag{
456
                cli.StringFlag{
457
                        Name:  "invoice_hash",
458
                        Usage: "the invoice hash to be deleted",
459
                },
460
        },
461
        Action: actionDecorator(deleteCanceledInvoice),
462
}
463

NEW
464
func deleteCanceledInvoice(ctx *cli.Context) error {
×
NEW
465
        ctxc := getContext()
×
NEW
466
        client, cleanUp := getClient(ctx)
×
NEW
467
        defer cleanUp()
×
NEW
468

×
NEW
469
        var (
×
NEW
470
                invoiceHash string
×
NEW
471
                err         error
×
NEW
472
                resp        proto.Message
×
NEW
473
        )
×
NEW
474

×
NEW
475
        switch {
×
NEW
476
        case ctx.IsSet("invoice_hash"):
×
NEW
477
                invoiceHash = ctx.String("invoice_hash")
×
NEW
478
        case ctx.Args().Present():
×
NEW
479
                invoiceHash = ctx.Args().First()
×
NEW
480
        default:
×
NEW
481
                return fmt.Errorf("invoice_hash argument missing")
×
482
        }
483

NEW
484
        req := &lnrpc.DelCanceledInvoiceReq{
×
NEW
485
                InvoiceHash: invoiceHash,
×
NEW
486
        }
×
NEW
487

×
NEW
488
        resp, err = client.DeleteCanceledInvoice(ctxc, req)
×
NEW
489
        if err != nil {
×
NEW
490
                return err
×
NEW
491
        }
×
492

NEW
493
        printJSON(resp)
×
NEW
494

×
NEW
495
        return nil
×
496
}
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