-
Notifications
You must be signed in to change notification settings - Fork 2.1k
/
Copy pathwalletrpc_active.go
2163 lines (1867 loc) · 58.2 KB
/
walletrpc_active.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//go:build walletrpc
// +build walletrpc
package main
import (
"bytes"
"context"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"sort"
"strconv"
"strings"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/btcutil/psbt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/lnwallet/chanfunding"
"github.com/urfave/cli"
)
var (
// psbtCommand is a wallet subcommand that is responsible for PSBT
// operations.
psbtCommand = cli.Command{
Name: "psbt",
Usage: "Interact with partially signed bitcoin transactions " +
"(PSBTs).",
Subcommands: []cli.Command{
fundPsbtCommand,
fundTemplatePsbtCommand,
finalizePsbtCommand,
},
}
// accountsCommand is a wallet subcommand that is responsible for
// account management operations.
accountsCommand = cli.Command{
Name: "accounts",
Usage: "Interact with wallet accounts.",
Subcommands: []cli.Command{
listAccountsCommand,
importAccountCommand,
importPubKeyCommand,
},
}
// addressesCommand is a wallet subcommand that is responsible for
// address management operations.
addressesCommand = cli.Command{
Name: "addresses",
Usage: "Interact with wallet addresses.",
Subcommands: []cli.Command{
listAddressesCommand,
signMessageWithAddrCommand,
verifyMessageWithAddrCommand,
},
}
p2TrChangeType = walletrpc.ChangeAddressType_CHANGE_ADDRESS_TYPE_P2TR
)
// walletCommands will return the set of commands to enable for walletrpc
// builds.
func walletCommands() []cli.Command {
return []cli.Command{
{
Name: "wallet",
Category: "Wallet",
Usage: "Interact with the wallet.",
Description: "",
Subcommands: []cli.Command{
pendingSweepsCommand,
bumpFeeCommand,
bumpCloseFeeCommand,
bumpForceCloseFeeCommand,
listSweepsCommand,
labelTxCommand,
publishTxCommand,
getTxCommand,
removeTxCommand,
releaseOutputCommand,
leaseOutputCommand,
listLeasesCommand,
psbtCommand,
accountsCommand,
requiredReserveCommand,
addressesCommand,
},
},
}
}
func parseAddrType(addrTypeStr string) (walletrpc.AddressType, error) {
switch addrTypeStr {
case "":
return walletrpc.AddressType_UNKNOWN, nil
case "p2wkh":
return walletrpc.AddressType_WITNESS_PUBKEY_HASH, nil
case "np2wkh":
return walletrpc.AddressType_NESTED_WITNESS_PUBKEY_HASH, nil
case "np2wkh-p2wkh":
return walletrpc.AddressType_HYBRID_NESTED_WITNESS_PUBKEY_HASH, nil
case "p2tr":
return walletrpc.AddressType_TAPROOT_PUBKEY, nil
default:
return 0, errors.New("invalid address type, supported address " +
"types are: p2wkh, p2tr, np2wkh, and np2wkh-p2wkh")
}
}
func getWalletClient(ctx *cli.Context) (walletrpc.WalletKitClient, func()) {
conn := getClientConn(ctx, false)
cleanUp := func() {
conn.Close()
}
return walletrpc.NewWalletKitClient(conn), cleanUp
}
var pendingSweepsCommand = cli.Command{
Name: "pendingsweeps",
Usage: "List all outputs that are pending to be swept within lnd.",
ArgsUsage: "",
Description: `
List all on-chain outputs that lnd is currently attempting to sweep
within its central batching engine. Outputs with similar fee rates are
batched together in order to sweep them within a single transaction.
`,
Flags: []cli.Flag{},
Action: actionDecorator(pendingSweeps),
}
func pendingSweeps(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletClient(ctx)
defer cleanUp()
req := &walletrpc.PendingSweepsRequest{}
resp, err := client.PendingSweeps(ctxc, req)
if err != nil {
return err
}
// Sort them in ascending fee rate order for display purposes.
sort.Slice(resp.PendingSweeps, func(i, j int) bool {
return resp.PendingSweeps[i].SatPerVbyte <
resp.PendingSweeps[j].SatPerVbyte
})
var pendingSweepsResp = struct {
PendingSweeps []*PendingSweep `json:"pending_sweeps"`
}{
PendingSweeps: make([]*PendingSweep, 0, len(resp.PendingSweeps)),
}
for _, protoPendingSweep := range resp.PendingSweeps {
pendingSweep := NewPendingSweepFromProto(protoPendingSweep)
pendingSweepsResp.PendingSweeps = append(
pendingSweepsResp.PendingSweeps, pendingSweep,
)
}
printJSON(pendingSweepsResp)
return nil
}
var bumpFeeCommand = cli.Command{
Name: "bumpfee",
Usage: "Bumps the fee of an arbitrary input/transaction.",
ArgsUsage: "outpoint",
Description: `
BumpFee is an endpoint that allows users to interact with lnd's sweeper
directly. It takes an outpoint from an unconfirmed transaction and
sends it to the sweeper for potential fee bumping. Depending on whether
the outpoint has been registered in the sweeper (an existing input,
e.g., an anchor output) or not (a new input, e.g., an unconfirmed
wallet utxo), this will either be an RBF or CPFP attempt.
When receiving an input, lnd’s sweeper needs to understand its time
sensitivity to make economical fee bumps - internally a fee function is
created using the deadline and budget to guide the process. When the
deadline is approaching, the fee function will increase the fee rate
and perform an RBF.
When a force close happens, all the outputs from the force closing
transaction will be registered in the sweeper. The sweeper will then
handle the creation, publish, and fee bumping of the sweeping
transactions. Everytime a new block comes in, unless the sweeping
transaction is confirmed, an RBF is attempted. To interfere with this
automatic process, users can use BumpFee to specify customized fee
rate, budget, deadline, and whether the sweep should happen
immediately. It's recommended to call listsweeps to understand the
shape of the existing sweeping transaction first - depending on the
number of inputs in this transaction, the RBF requirements can be quite
different.
This RPC also serves useful when wanting to perform a
Child-Pays-For-Parent (CPFP), where the child transaction pays for its
parent's fee. This can be done by specifying an outpoint within the low
fee transaction that is under the control of the wallet.
`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "conf_target",
Usage: `
The deadline in number of blocks that the input should be spent within.
When not set, for new inputs, the default value (1008) is used; for
exiting inputs, their current values will be retained.`,
},
cli.Uint64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.BoolFlag{
Name: "force",
Usage: "Deprecated, use immediate instead.",
Hidden: true,
},
cli.Uint64Flag{
Name: "sat_per_vbyte",
Usage: `
The starting fee rate, expressed in sat/vbyte, that will be used to
spend the input with initially. This value will be used by the
sweeper's fee function as its starting fee rate. When not set, the
sweeper will use the estimated fee rate using the target_conf as the
starting fee rate.`,
},
cli.BoolFlag{
Name: "immediate",
Usage: `
Whether this input will be swept immediately. When set to true, the
sweeper will sweep this input without waiting for the next batch.`,
},
cli.Uint64Flag{
Name: "budget",
Usage: `
The max amount in sats that can be used as the fees. Setting this value
greater than the input's value may result in CPFP - one or more wallet
utxos will be used to pay the fees specified by the budget. If not set,
for new inputs, by default 50% of the input's value will be treated as
the budget for fee bumping; for existing inputs, their current budgets
will be retained.`,
},
},
Action: actionDecorator(bumpFee),
}
func bumpFee(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "bumpfee")
}
// Validate and parse the relevant arguments/flags.
protoOutPoint, err := NewProtoOutPoint(ctx.Args().Get(0))
if err != nil {
return err
}
client, cleanUp := getWalletClient(ctx)
defer cleanUp()
// Parse immediate flag (force flag was deprecated).
immediate := false
switch {
case ctx.IsSet("immediate") && ctx.IsSet("force"):
return fmt.Errorf("cannot set immediate and force flag at " +
"the same time")
case ctx.Bool("immediate"):
immediate = true
case ctx.Bool("force"):
immediate = true
}
resp, err := client.BumpFee(ctxc, &walletrpc.BumpFeeRequest{
Outpoint: protoOutPoint,
TargetConf: uint32(ctx.Uint64("conf_target")),
Immediate: immediate,
Budget: ctx.Uint64("budget"),
SatPerVbyte: ctx.Uint64("sat_per_vbyte"),
})
if err != nil {
return err
}
printRespJSON(resp)
return nil
}
var bumpCloseFeeCommand = cli.Command{
Name: "bumpclosefee",
Usage: "Bumps the fee of a channel force closing transaction.",
ArgsUsage: "channel_point",
Hidden: true,
Description: `
This command works only for unilateral closes of anchor channels. It
allows the fee of a channel force closing transaction to be increased by
using the child-pays-for-parent mechanism. It will instruct the sweeper
to sweep the anchor outputs of the closing transaction at the requested
fee rate or confirmation target. The specified fee rate will be the
effective fee rate taking the parent fee into account.
NOTE: This cmd is DEPRECATED please use bumpforceclosefee instead.
`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "conf_target",
Usage: `
The deadline in number of blocks that the input should be spent within.
When not set, for new inputs, the default value (1008) is used; for
exiting inputs, their current values will be retained.`,
},
cli.Uint64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.BoolFlag{
Name: "force",
Usage: "Deprecated, use immediate instead.",
Hidden: true,
},
cli.Uint64Flag{
Name: "sat_per_vbyte",
Usage: `
The starting fee rate, expressed in sat/vbyte, that will be used to
spend the input with initially. This value will be used by the
sweeper's fee function as its starting fee rate. When not set, the
sweeper will use the estimated fee rate using the target_conf as the
starting fee rate.`,
},
cli.BoolFlag{
Name: "immediate",
Usage: `
Whether this input will be swept immediately. When set to true, the
sweeper will sweep this input without waiting for the next batch.`,
},
cli.Uint64Flag{
Name: "budget",
Usage: `
The max amount in sats that can be used as the fees. Setting this value
greater than the input's value may result in CPFP - one or more wallet
utxos will be used to pay the fees specified by the budget. If not set,
for new inputs, by default 50% of the input's value will be treated as
the budget for fee bumping; for existing inputs, their current budgets
will be retained.`,
},
},
Action: actionDecorator(bumpForceCloseFee),
}
var bumpForceCloseFeeCommand = cli.Command{
Name: "bumpforceclosefee",
Usage: "Bumps the fee of a channel force closing transaction.",
ArgsUsage: "channel_point",
Description: `
This command works only for unilateral closes of anchor channels. It
allows the fee of a channel force closing transaction to be increased by
using the child-pays-for-parent mechanism. It will instruct the sweeper
to sweep the anchor outputs of the closing transaction at the requested
fee rate or confirmation target. The specified fee rate will be the
effective fee rate taking the parent fee into account.
`,
Flags: []cli.Flag{
cli.Uint64Flag{
Name: "conf_target",
Usage: `
The deadline in number of blocks that the input should be spent within.
When not set, for new inputs, the default value (1008) is used; for
exiting inputs, their current values will be retained.`,
},
cli.Uint64Flag{
Name: "sat_per_byte",
Usage: "Deprecated, use sat_per_vbyte instead.",
Hidden: true,
},
cli.BoolFlag{
Name: "force",
Usage: "Deprecated, use immediate instead.",
Hidden: true,
},
cli.Uint64Flag{
Name: "sat_per_vbyte",
Usage: `
The starting fee rate, expressed in sat/vbyte, that will be used to
spend the input with initially. This value will be used by the
sweeper's fee function as its starting fee rate. When not set, the
sweeper will use the estimated fee rate using the target_conf as the
starting fee rate.`,
},
cli.BoolFlag{
Name: "immediate",
Usage: `
Whether this input will be swept immediately. When set to true, the
sweeper will sweep this input without waiting for the next batch.`,
},
cli.Uint64Flag{
Name: "budget",
Usage: `
The max amount in sats that can be used as the fees. Setting this value
greater than the input's value may result in CPFP - one or more wallet
utxos will be used to pay the fees specified by the budget. If not set,
for new inputs, by default 50% of the input's value will be treated as
the budget for fee bumping; for existing inputs, their current budgets
will be retained.`,
},
},
Action: actionDecorator(bumpForceCloseFee),
}
func bumpForceCloseFee(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "bumpclosefee")
}
// Validate the channel point.
channelPoint := ctx.Args().Get(0)
_, err := NewProtoOutPoint(channelPoint)
if err != nil {
return err
}
// Fetch all waiting close channels.
client, cleanUp := getClient(ctx)
defer cleanUp()
// Fetch waiting close channel commitments.
commitments, err := getWaitingCloseCommitments(
ctxc, client, channelPoint,
)
if err != nil {
return err
}
// Retrieve pending sweeps.
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
sweeps, err := walletClient.PendingSweeps(
ctxc, &walletrpc.PendingSweepsRequest{},
)
if err != nil {
return err
}
// Match pending sweeps with commitments of the channel for which a bump
// is requested and bump their fees.
commitSet := map[string]struct{}{
commitments.LocalTxid: {},
commitments.RemoteTxid: {},
}
if commitments.RemotePendingTxid != "" {
commitSet[commitments.RemotePendingTxid] = struct{}{}
}
for _, sweep := range sweeps.PendingSweeps {
// Only bump anchor sweeps.
if sweep.WitnessType != walletrpc.WitnessType_COMMITMENT_ANCHOR {
continue
}
// Skip unrelated sweeps.
sweepTxID, err := chainhash.NewHash(sweep.Outpoint.TxidBytes)
if err != nil {
return err
}
if _, match := commitSet[sweepTxID.String()]; !match {
continue
}
resp, err := walletClient.BumpFee(
ctxc, &walletrpc.BumpFeeRequest{
Outpoint: sweep.Outpoint,
TargetConf: uint32(ctx.Uint64("conf_target")),
Budget: ctx.Uint64("budget"),
Immediate: ctx.Bool("immediate"),
SatPerVbyte: ctx.Uint64("sat_per_vbyte"),
})
if err != nil {
return err
}
// Bump fee of the anchor sweep.
fmt.Printf("Bumping fee of %v:%v: %v\n",
sweepTxID, sweep.Outpoint.OutputIndex, resp.GetStatus())
}
return nil
}
func getWaitingCloseCommitments(ctxc context.Context,
client lnrpc.LightningClient, channelPoint string) (
*lnrpc.PendingChannelsResponse_Commitments, error) {
req := &lnrpc.PendingChannelsRequest{}
resp, err := client.PendingChannels(ctxc, req)
if err != nil {
return nil, err
}
// Lookup the channel commit tx hashes.
for _, channel := range resp.WaitingCloseChannels {
if channel.Channel.ChannelPoint == channelPoint {
return channel.Commitments, nil
}
}
return nil, errors.New("channel not found")
}
var listSweepsCommand = cli.Command{
Name: "listsweeps",
Usage: "Lists all sweeps that have been published by our node.",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "verbose",
Usage: "lookup full transaction",
},
cli.IntFlag{
Name: "startheight",
Usage: "The start height to use when fetching " +
"sweeps. If not specified (0), the result " +
"will start from the earliest sweep. If set " +
"to -1 the result will only include " +
"unconfirmed sweeps (at the time of the call).",
},
},
Description: `
Get a list of the hex-encoded transaction ids of every sweep that our
node has published. Note that these sweeps may not be confirmed on chain
yet, as we store them on transaction broadcast, not confirmation.
If the verbose flag is set, the full set of transactions will be
returned, otherwise only the sweep transaction ids will be returned.
`,
Action: actionDecorator(listSweeps),
}
func listSweeps(ctx *cli.Context) error {
ctxc := getContext()
client, cleanUp := getWalletClient(ctx)
defer cleanUp()
resp, err := client.ListSweeps(
ctxc, &walletrpc.ListSweepsRequest{
Verbose: ctx.IsSet("verbose"),
StartHeight: int32(ctx.Int("startheight")),
},
)
if err != nil {
return err
}
printJSON(resp)
return nil
}
var labelTxCommand = cli.Command{
Name: "labeltx",
Usage: "Adds a label to a transaction.",
ArgsUsage: "txid label",
Description: `
Add a label to a transaction. If the transaction already has a label,
this call will fail unless the overwrite option is set. The label is
limited to 500 characters. Note that multi word labels must be contained
in quotation marks ("").
`,
Flags: []cli.Flag{
cli.BoolFlag{
Name: "overwrite",
Usage: "set to overwrite existing labels",
},
},
Action: actionDecorator(labelTransaction),
}
func labelTransaction(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 2 {
return cli.ShowCommandHelp(ctx, "labeltx")
}
// Get the transaction id and check that it is a valid hash.
txid := ctx.Args().Get(0)
hash, err := chainhash.NewHashFromStr(txid)
if err != nil {
return err
}
label := ctx.Args().Get(1)
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
_, err = walletClient.LabelTransaction(
ctxc, &walletrpc.LabelTransactionRequest{
Txid: hash[:],
Label: label,
Overwrite: ctx.Bool("overwrite"),
},
)
if err != nil {
return err
}
fmt.Printf("Transaction: %v labelled with: %v\n", txid, label)
return nil
}
var publishTxCommand = cli.Command{
Name: "publishtx",
Usage: "Attempts to publish the passed transaction to the network.",
ArgsUsage: "tx_hex",
Description: `
Publish a hex-encoded raw transaction to the on-chain network. The
wallet will continually attempt to re-broadcast the transaction on start up, until it
enters the chain. The label parameter is optional and limited to 500 characters. Note
that multi word labels must be contained in quotation marks ("").
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "label",
Usage: "(optional) transaction label",
},
},
Action: actionDecorator(publishTransaction),
}
func publishTransaction(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 || ctx.NumFlags() > 1 {
return cli.ShowCommandHelp(ctx, "publishtx")
}
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
tx, err := hex.DecodeString(ctx.Args().First())
if err != nil {
return err
}
// Deserialize the transaction to get the transaction hash.
msgTx := &wire.MsgTx{}
txReader := bytes.NewReader(tx)
if err := msgTx.Deserialize(txReader); err != nil {
return err
}
req := &walletrpc.Transaction{
TxHex: tx,
}
_, err = walletClient.PublishTransaction(ctxc, req)
if err != nil {
return err
}
printJSON(&struct {
TXID string `json:"txid"`
}{
TXID: msgTx.TxHash().String(),
})
return nil
}
var getTxCommand = cli.Command{
Name: "gettx",
Usage: "Returns details of a transaction.",
ArgsUsage: "txid",
Description: `
Query the transaction using the given transaction id and return its
details. An error is returned if the transaction is not found.
`,
Action: actionDecorator(getTransaction),
}
func getTransaction(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "gettx")
}
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
req := &walletrpc.GetTransactionRequest{
Txid: ctx.Args().First(),
}
res, err := walletClient.GetTransaction(ctxc, req)
if err != nil {
return err
}
printRespJSON(res)
return nil
}
var removeTxCommand = cli.Command{
Name: "removetx",
Usage: "Attempts to remove the unconfirmed transaction with the " +
"specified txid and all its children from the underlying " +
"internal wallet.",
ArgsUsage: "txid",
Description: `
Removes the transaction with the specified txid from the underlying
wallet which must still be unconfirmmed (in mempool). This command is
useful when a transaction is RBFed by another transaction. The wallet
will only resolve this conflict when the other transaction is mined
(which can take time). If a transaction was removed erronously a simple
rebroadcast of the former transaction with the "publishtx" cmd will
register the relevant outputs of the raw tx again with the wallet
(if there are no errors broadcasting this transaction due to an RBF
replacement sitting in the mempool). As soon as a removed transaction
is confirmed funds will be registered with the wallet again.`,
Flags: []cli.Flag{},
Action: actionDecorator(removeTransaction),
}
func removeTransaction(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if we do not have the expected
// number of arguments/flags.
if ctx.NArg() != 1 {
return cli.ShowCommandHelp(ctx, "removetx")
}
// Fetch the only cmd argument which must be a valid txid.
txid := ctx.Args().First()
txHash, err := chainhash.NewHashFromStr(txid)
if err != nil {
return err
}
walletClient, cleanUp := getWalletClient(ctx)
defer cleanUp()
req := &walletrpc.GetTransactionRequest{
Txid: txHash.String(),
}
resp, err := walletClient.RemoveTransaction(ctxc, req)
if err != nil {
return err
}
printJSON(&struct {
Status string `json:"status"`
TxID string `json:"txid"`
}{
Status: resp.GetStatus(),
TxID: txHash.String(),
})
return nil
}
// utxoLease contains JSON annotations for a lease on an unspent output.
type utxoLease struct {
ID string `json:"id"`
OutPoint OutPoint `json:"outpoint"`
Expiration uint64 `json:"expiration"`
PkScript []byte `json:"pk_script"`
Value uint64 `json:"value"`
}
// fundPsbtResponse is a struct that contains JSON annotations for nice result
// serialization.
type fundPsbtResponse struct {
Psbt string `json:"psbt"`
ChangeOutputIndex int32 `json:"change_output_index"`
Locks []*utxoLease `json:"locks"`
}
var fundTemplatePsbtCommand = cli.Command{
Name: "fundtemplate",
Usage: "Fund a Partially Signed Bitcoin Transaction (PSBT) from a " +
"template.",
ArgsUsage: "[--template_psbt=T | [--outputs=O [--inputs=I]]] " +
"[--conf_target=C | --sat_per_vbyte=S] " +
"[--change_type=A] [--change_output_index=I]",
Description: `
The fund command creates a fully populated PSBT that contains enough
inputs to fund the outputs specified in either the template.
The main difference to the 'fund' command is that the template PSBT
is allowed to already contain both inputs and outputs and coin selection
and fee estimation is still performed.
If '--inputs' and '--outputs' are provided instead of a template, then
those are used to create a new PSBT template.
The 'outputs' flag decodes addresses and the amount to send respectively
in the following JSON format:
--outputs='["ExampleAddr:NumCoinsInSatoshis", "SecondAddr:Sats"]'
The 'outputs' format is different from the 'fund' command as the order
is important for being able to specify the change output index, so an
array is used rather than a map.
The optional 'inputs' flag decodes a JSON list of UTXO outpoints as
returned by the listunspent command for example:
--inputs='["<txid1>:<output-index1>","<txid2>:<output-index2>",...]
Any inputs specified that belong to this lnd node MUST be locked/leased
(by using 'lncli wallet leaseoutput') manually to make sure they aren't
selected again by the coin selection algorithm.
After verifying and possibly adding new inputs, all input UTXOs added by
the command are locked with an internal app ID. Inputs already present
in the template are NOT locked, as they must already be locked when
invoking the command.
The '--change_output_index' flag can be used to specify the index of the
output in the PSBT that should be used as the change output. If '-1' is
specified, the wallet will automatically add a change output if one is
required!
The optional '--change-type' flag permits to choose the address type
for the change for default accounts and single imported public keys.
The custom address type can only be p2tr at the moment (p2wkh will be
used by default). No custom address type should be provided for custom
accounts as we will always generate the change address using the coin
selection key scope.
`,
Flags: []cli.Flag{
cli.StringFlag{
Name: "template_psbt",
Usage: "the outputs to fund and optional inputs to " +
"spend provided in the base64 PSBT format",
},
cli.StringFlag{
Name: "outputs",
Usage: "a JSON compatible map of destination " +
"addresses to amounts to send, must not " +
"include a change address as that will be " +
"added automatically by the wallet",
},
cli.StringFlag{
Name: "inputs",
Usage: "an optional JSON compatible list of UTXO " +
"outpoints to use as the PSBT's inputs",
},
cli.Uint64Flag{
Name: "conf_target",
Usage: "the number of blocks that the transaction " +
"should be confirmed on-chain within",
Value: 6,
},
cli.Uint64Flag{
Name: "sat_per_vbyte",
Usage: "a manual fee expressed in sat/vbyte that " +
"should be used when creating the transaction",
},
cli.StringFlag{
Name: "account",
Usage: "(optional) the name of the account to use to " +
"create/fund the PSBT",
},
cli.StringFlag{
Name: "change_type",
Usage: "(optional) the type of the change address to " +
"use to create/fund the PSBT. If no address " +
"type is provided, p2wpkh will be used for " +
"default accounts and single imported public " +
"keys. No custom address type should be " +
"provided for custom accounts as we will " +
"always use the coin selection key scope to " +
"generate the change address",
},
cli.Uint64Flag{
Name: "min_confs",
Usage: "(optional) the minimum number of " +
"confirmations each input used for the PSBT " +
"transaction must satisfy",
Value: defaultUtxoMinConf,
},
cli.IntFlag{
Name: "change_output_index",
Usage: "(optional) define an existing output in the " +
"PSBT template that should be used as the " +
"change output. The value of -1 means a " +
"change output will be added automatically " +
"if required",
Value: -1,
},
coinSelectionStrategyFlag,
},
Action: actionDecorator(fundTemplatePsbt),
}
// fundTemplatePsbt implements the fundtemplate sub command.
//
//nolint:funlen
func fundTemplatePsbt(ctx *cli.Context) error {
ctxc := getContext()
// Display the command's help message if there aren't any flags
// specified.
if ctx.NumFlags() == 0 {
return cli.ShowCommandHelp(ctx, "fund")
}
chainParams, err := networkParams(ctx)
if err != nil {
return err
}
coinSelect := &walletrpc.PsbtCoinSelect{}
// Parse template flags.
switch {
// The PSBT flag is mutually exclusive with the outputs/inputs flags.
case ctx.IsSet("template_psbt") &&
(ctx.IsSet("inputs") || ctx.IsSet("outputs")):
return fmt.Errorf("cannot set template_psbt and inputs/" +
"outputs flags at the same time")
// Use a pre-existing PSBT as the transaction template.
case len(ctx.String("template_psbt")) > 0:
psbtBase64 := ctx.String("template_psbt")
psbtBytes, err := base64.StdEncoding.DecodeString(psbtBase64)
if err != nil {
return err
}
coinSelect.Psbt = psbtBytes
// The user manually specified outputs and/or inputs in JSON
// format.
case len(ctx.String("outputs")) > 0 || len(ctx.String("inputs")) > 0:
var (
inputs []*wire.OutPoint
outputs []*wire.TxOut
)
if len(ctx.String("outputs")) > 0 {
var outputStrings []string
// Parse the address to amount map as JSON now. At least
// one entry must be present.
jsonMap := []byte(ctx.String("outputs"))
err := json.Unmarshal(jsonMap, &outputStrings)
if err != nil {
return fmt.Errorf("error parsing outputs "+
"JSON: %w", err)
}
// Parse the addresses and amounts into a slice of
// transaction outputs.
for idx, addrAndAmount := range outputStrings {
parts := strings.Split(addrAndAmount, ":")
if len(parts) != 2 {
return fmt.Errorf("invalid output "+
"format at index %d", idx)
}
addrStr, amountStr := parts[0], parts[1]
amount, err := strconv.ParseInt(
amountStr, 10, 64,
)
if err != nil {
return fmt.Errorf("error parsing "+
"amount at index %d: %w", idx,