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

lightningnetwork / lnd / 17774622768

16 Sep 2025 05:57PM UTC coverage: 66.505% (-0.2%) from 66.657%
17774622768

Pull #10067

github

web-flow
Merge 4ec7abb62 into cbed86e21
Pull Request #10067: add sats_per_kweight option when crafting a transaction (continue)

72 of 233 new or added lines in 13 files covered. (30.9%)

355 existing lines in 32 files now uncovered.

136113 of 204666 relevant lines covered (66.5%)

21396.84 hits per line

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

0.0
/cmd/commands/main.go
1
// Copyright (c) 2013-2017 The btcsuite developers
2
// Copyright (c) 2015-2016 The Decred developers
3
// Copyright (C) 2015-2024 The Lightning Network Developers
4

5
package commands
6

7
import (
8
        "context"
9
        "crypto/tls"
10
        "fmt"
11
        "net"
12
        "os"
13
        "path/filepath"
14
        "strings"
15
        "syscall"
16

17
        "github.com/btcsuite/btcd/blockchain"
18
        "github.com/btcsuite/btcd/btcutil"
19
        "github.com/btcsuite/btcd/chaincfg"
20
        "github.com/lightningnetwork/lnd"
21
        "github.com/lightningnetwork/lnd/build"
22
        "github.com/lightningnetwork/lnd/lncfg"
23
        "github.com/lightningnetwork/lnd/lnrpc"
24
        "github.com/lightningnetwork/lnd/macaroons"
25
        "github.com/lightningnetwork/lnd/tor"
26
        "github.com/shopspring/decimal"
27
        "github.com/urfave/cli"
28
        "golang.org/x/term"
29
        "google.golang.org/grpc"
30
        "google.golang.org/grpc/credentials"
31
        "google.golang.org/grpc/metadata"
32
)
33

34
const (
35
        defaultDataDir          = "data"
36
        defaultChainSubDir      = "chain"
37
        defaultTLSCertFilename  = "tls.cert"
38
        defaultMacaroonFilename = "admin.macaroon"
39
        defaultRPCPort          = "10009"
40
        defaultRPCHostPort      = "localhost:" + defaultRPCPort
41

42
        envVarRPCServer       = "LNCLI_RPCSERVER"
43
        envVarLNDDir          = "LNCLI_LNDDIR"
44
        envVarSOCKSProxy      = "LNCLI_SOCKSPROXY"
45
        envVarTLSCertPath     = "LNCLI_TLSCERTPATH"
46
        envVarChain           = "LNCLI_CHAIN"
47
        envVarNetwork         = "LNCLI_NETWORK"
48
        envVarMacaroonPath    = "LNCLI_MACAROONPATH"
49
        envVarMacaroonTimeout = "LNCLI_MACAROONTIMEOUT"
50
        envVarMacaroonIP      = "LNCLI_MACAROONIP"
51
        envVarProfile         = "LNCLI_PROFILE"
52
        envVarMacFromJar      = "LNCLI_MACFROMJAR"
53
)
54

55
var (
56
        DefaultLndDir      = btcutil.AppDataDir("lnd", false)
57
        defaultTLSCertPath = filepath.Join(
58
                DefaultLndDir, defaultTLSCertFilename,
59
        )
60

61
        // maxMsgRecvSize is the largest message our client will receive. We
62
        // set this to 200MiB atm.
63
        maxMsgRecvSize = grpc.MaxCallRecvMsgSize(lnrpc.MaxGrpcMsgSize)
64
)
65

66
func fatal(err error) {
×
67
        fmt.Fprintf(os.Stderr, "[lncli] %v\n", err)
×
68
        os.Exit(1)
×
69
}
×
70

71
func getWalletUnlockerClient(ctx *cli.Context) (lnrpc.WalletUnlockerClient,
72
        func()) {
×
73

×
74
        conn := getClientConn(ctx, true)
×
75

×
76
        cleanUp := func() {
×
77
                conn.Close()
×
78
        }
×
79

80
        return lnrpc.NewWalletUnlockerClient(conn), cleanUp
×
81
}
82

83
func getStateServiceClient(ctx *cli.Context) (lnrpc.StateClient, func()) {
×
84
        conn := getClientConn(ctx, true)
×
85

×
86
        cleanUp := func() {
×
87
                conn.Close()
×
88
        }
×
89

90
        return lnrpc.NewStateClient(conn), cleanUp
×
91
}
92

93
func getClient(ctx *cli.Context) (lnrpc.LightningClient, func()) {
×
94
        conn := getClientConn(ctx, false)
×
95

×
96
        cleanUp := func() {
×
97
                conn.Close()
×
98
        }
×
99

100
        return lnrpc.NewLightningClient(conn), cleanUp
×
101
}
102

103
func getClientConn(ctx *cli.Context, skipMacaroons bool) *grpc.ClientConn {
×
104
        // First, we'll get the selected stored profile or an ephemeral one
×
105
        // created from the global options in the CLI context.
×
106
        profile, err := getGlobalOptions(ctx, skipMacaroons)
×
107
        if err != nil {
×
108
                fatal(fmt.Errorf("could not load global options: %w", err))
×
109
        }
×
110

111
        // Create a dial options array.
112
        opts := []grpc.DialOption{
×
113
                grpc.WithUnaryInterceptor(
×
114
                        addMetadataUnaryInterceptor(profile.Metadata),
×
115
                ),
×
116
                grpc.WithStreamInterceptor(
×
117
                        addMetaDataStreamInterceptor(profile.Metadata),
×
118
                ),
×
119
        }
×
120

×
121
        if profile.Insecure {
×
122
                opts = append(opts, grpc.WithInsecure())
×
123
        } else {
×
124
                // Load the specified TLS certificate.
×
125
                certPool, err := profile.cert()
×
126
                if err != nil {
×
127
                        fatal(fmt.Errorf("could not create cert pool: %w", err))
×
128
                }
×
129

130
                // Build transport credentials from the certificate pool. If
131
                // there is no certificate pool, we expect the server to use a
132
                // non-self-signed certificate such as a certificate obtained
133
                // from Let's Encrypt.
134
                var creds credentials.TransportCredentials
×
135
                if certPool != nil {
×
136
                        creds = credentials.NewClientTLSFromCert(certPool, "")
×
137
                } else {
×
138
                        // Fallback to the system pool. Using an empty tls
×
139
                        // config is an alternative to x509.SystemCertPool().
×
140
                        // That call is not supported on Windows.
×
141
                        creds = credentials.NewTLS(&tls.Config{})
×
142
                }
×
143

144
                opts = append(opts, grpc.WithTransportCredentials(creds))
×
145
        }
146

147
        // Only process macaroon credentials if --no-macaroons isn't set and
148
        // if we're not skipping macaroon processing.
149
        if !profile.NoMacaroons && !skipMacaroons {
×
150
                // Find out which macaroon to load.
×
151
                macName := profile.Macaroons.Default
×
152
                if ctx.GlobalIsSet("macfromjar") {
×
153
                        macName = ctx.GlobalString("macfromjar")
×
154
                }
×
155
                var macEntry *macaroonEntry
×
156
                for _, entry := range profile.Macaroons.Jar {
×
157
                        if entry.Name == macName {
×
158
                                macEntry = entry
×
159
                                break
×
160
                        }
161
                }
162
                if macEntry == nil {
×
163
                        fatal(fmt.Errorf("macaroon with name '%s' not found "+
×
164
                                "in profile", macName))
×
165
                }
×
166

167
                // Get and possibly decrypt the specified macaroon.
168
                //
169
                // TODO(guggero): Make it possible to cache the password so we
170
                // don't need to ask for it every time.
171
                mac, err := macEntry.loadMacaroon(readPassword)
×
172
                if err != nil {
×
173
                        fatal(fmt.Errorf("could not load macaroon: %w", err))
×
174
                }
×
175

176
                macConstraints := []macaroons.Constraint{
×
177
                        // We add a time-based constraint to prevent replay of
×
178
                        // the macaroon. It's good for 60 seconds by default to
×
179
                        // make up for any discrepancy between client and server
×
180
                        // clocks, but leaking the macaroon before it becomes
×
181
                        // invalid makes it possible for an attacker to reuse
×
182
                        // the macaroon. In addition, the validity time of the
×
183
                        // macaroon is extended by the time the server clock is
×
184
                        // behind the client clock, or shortened by the time the
×
185
                        // server clock is ahead of the client clock (or invalid
×
186
                        // altogether if, in the latter case, this time is more
×
187
                        // than 60 seconds).
×
188
                        // TODO(aakselrod): add better anti-replay protection.
×
189
                        macaroons.TimeoutConstraint(profile.Macaroons.Timeout),
×
190

×
191
                        // Lock macaroon down to a specific IP address.
×
192
                        macaroons.IPLockConstraint(profile.Macaroons.IP),
×
193

×
194
                        // ... Add more constraints if needed.
×
195
                }
×
196

×
197
                // Apply constraints to the macaroon.
×
198
                constrainedMac, err := macaroons.AddConstraints(
×
199
                        mac, macConstraints...,
×
200
                )
×
201
                if err != nil {
×
202
                        fatal(err)
×
203
                }
×
204

205
                // Now we append the macaroon credentials to the dial options.
206
                cred, err := macaroons.NewMacaroonCredential(constrainedMac)
×
207
                if err != nil {
×
208
                        fatal(fmt.Errorf("error cloning mac: %w", err))
×
209
                }
×
210
                opts = append(opts, grpc.WithPerRPCCredentials(cred))
×
211
        }
212

213
        // If a socksproxy server is specified we use a tor dialer
214
        // to connect to the grpc server.
215
        if ctx.GlobalIsSet("socksproxy") {
×
216
                socksProxy := ctx.GlobalString("socksproxy")
×
217
                torDialer := func(_ context.Context, addr string) (net.Conn,
×
218
                        error) {
×
219

×
220
                        return tor.Dial(
×
221
                                addr, socksProxy, false, false,
×
222
                                tor.DefaultConnTimeout,
×
223
                        )
×
224
                }
×
225
                opts = append(opts, grpc.WithContextDialer(torDialer))
×
226
        } else {
×
227
                // We need to use a custom dialer so we can also connect to
×
228
                // unix sockets and not just TCP addresses.
×
229
                genericDialer := lncfg.ClientAddressDialer(defaultRPCPort)
×
230
                opts = append(opts, grpc.WithContextDialer(genericDialer))
×
231
        }
×
232

233
        opts = append(opts, grpc.WithDefaultCallOptions(maxMsgRecvSize))
×
234

×
235
        conn, err := grpc.Dial(profile.RPCServer, opts...)
×
236
        if err != nil {
×
237
                fatal(fmt.Errorf("unable to connect to RPC server: %w", err))
×
238
        }
×
239

240
        return conn
×
241
}
242

243
// addMetadataUnaryInterceptor returns a grpc client side interceptor that
244
// appends any key-value metadata strings to the outgoing context of a grpc
245
// unary call.
246
func addMetadataUnaryInterceptor(
247
        md map[string]string) grpc.UnaryClientInterceptor {
×
248

×
249
        return func(ctx context.Context, method string, req, reply interface{},
×
250
                cc *grpc.ClientConn, invoker grpc.UnaryInvoker,
×
251
                opts ...grpc.CallOption) error {
×
252

×
253
                outCtx := contextWithMetadata(ctx, md)
×
254
                return invoker(outCtx, method, req, reply, cc, opts...)
×
255
        }
×
256
}
257

258
// addMetaDataStreamInterceptor returns a grpc client side interceptor that
259
// appends any key-value metadata strings to the outgoing context of a grpc
260
// stream call.
261
func addMetaDataStreamInterceptor(
262
        md map[string]string) grpc.StreamClientInterceptor {
×
263

×
264
        return func(ctx context.Context, desc *grpc.StreamDesc,
×
265
                cc *grpc.ClientConn, method string, streamer grpc.Streamer,
×
266
                opts ...grpc.CallOption) (grpc.ClientStream, error) {
×
267

×
268
                outCtx := contextWithMetadata(ctx, md)
×
269
                return streamer(outCtx, desc, cc, method, opts...)
×
270
        }
×
271
}
272

273
// contextWithMetaData appends the given metadata key-value pairs to the given
274
// context.
275
func contextWithMetadata(ctx context.Context,
276
        md map[string]string) context.Context {
×
277

×
278
        kvPairs := make([]string, 0, 2*len(md))
×
279
        for k, v := range md {
×
280
                kvPairs = append(kvPairs, k, v)
×
281
        }
×
282

283
        return metadata.AppendToOutgoingContext(ctx, kvPairs...)
×
284
}
285

286
// extractPathArgs parses the TLS certificate and macaroon paths from the
287
// command.
288
func extractPathArgs(ctx *cli.Context) (string, string, error) {
×
289
        network := strings.ToLower(ctx.GlobalString("network"))
×
290
        switch network {
×
291
        case "mainnet", "testnet", "testnet4", "regtest", "simnet", "signet":
×
292
        default:
×
293
                return "", "", fmt.Errorf("unknown network: %v", network)
×
294
        }
295

296
        // We'll now fetch the lnddir so we can make a decision  on how to
297
        // properly read the macaroons (if needed) and also the cert. This will
298
        // either be the default, or will have been overwritten by the end
299
        // user.
300
        lndDir := lncfg.CleanAndExpandPath(ctx.GlobalString("lnddir"))
×
301

×
302
        // If the macaroon path as been manually provided, then we'll only
×
303
        // target the specified file.
×
304
        var macPath string
×
305
        if ctx.GlobalString("macaroonpath") != "" {
×
306
                macPath = lncfg.CleanAndExpandPath(ctx.GlobalString(
×
307
                        "macaroonpath",
×
308
                ))
×
309
        } else {
×
310
                // Otherwise, we'll go into the path:
×
311
                // lnddir/data/chain/<chain>/<network> in order to fetch the
×
312
                // macaroon that we need.
×
313
                macPath = filepath.Join(
×
314
                        lndDir, defaultDataDir, defaultChainSubDir,
×
315
                        lnd.BitcoinChainName, network, defaultMacaroonFilename,
×
316
                )
×
317
        }
×
318

319
        tlsCertPath := lncfg.CleanAndExpandPath(ctx.GlobalString("tlscertpath"))
×
320

×
321
        // If a custom lnd directory was set, we'll also check if custom paths
×
322
        // for the TLS cert and macaroon file were set as well. If not, we'll
×
323
        // override their paths so they can be found within the custom lnd
×
324
        // directory set. This allows us to set a custom lnd directory, along
×
325
        // with custom paths to the TLS cert and macaroon file.
×
326
        if lndDir != DefaultLndDir {
×
327
                tlsCertPath = filepath.Join(lndDir, defaultTLSCertFilename)
×
328
        }
×
329

330
        return tlsCertPath, macPath, nil
×
331
}
332

333
// parseFeeRate converts fee from sat/vB to sat/kw using fixed-point
334
// math to avoid rounding errors from floating-point arithmetic.
NEW
335
func parseFeeRate(ctx *cli.Context, flagName string) (uint64, error) {
×
NEW
336
        satPerVb := ctx.String(flagName)
×
NEW
337
        if !ctx.IsSet(flagName) {
×
NEW
338
                return 0, nil
×
NEW
339
        }
×
340

NEW
341
        satPerVbyte, err := decimal.NewFromString(satPerVb)
×
NEW
342
        if err != nil {
×
NEW
343
                return 0, fmt.Errorf("invalid --%s: %w", flagName, err)
×
NEW
344
        }
×
345

346
        // kwPerVbyte is the factor used to go from sats/vbyte to
347
        // sats/kw.
NEW
348
        kwPerVbyte := decimal.NewFromInt(
×
NEW
349
                1000 / blockchain.WitnessScaleFactor,
×
NEW
350
        )
×
NEW
351

×
NEW
352
        satPerKw := satPerVbyte.Mul(kwPerVbyte).IntPart()
×
NEW
353
        if satPerKw <= 0 {
×
NEW
354
                return 0, fmt.Errorf(
×
NEW
355
                        "invalid --%s: %v", flagName, satPerVb,
×
NEW
356
                )
×
NEW
357
        }
×
358

NEW
359
        return uint64(satPerKw), nil
×
360
}
361

362
// checkNotBothSet accepts two flag names, a and b, and checks that only flag a
363
// or flag b can be set, but not both. It returns the name of the flag or an
364
// error.
365
func checkNotBothSet(ctx *cli.Context, a, b string) (string, error) {
×
366
        if ctx.IsSet(a) && ctx.IsSet(b) {
×
367
                return "", fmt.Errorf(
×
368
                        "either %s or %s should be set, but not both", a, b,
×
369
                )
×
370
        }
×
371

372
        if ctx.IsSet(a) {
×
373
                return a, nil
×
374
        }
×
375

376
        return b, nil
×
377
}
378

379
func Main() {
×
380
        app := cli.NewApp()
×
381
        app.Name = "lncli"
×
382
        app.Version = build.Version() + " commit=" + build.Commit
×
383
        app.Usage = "control plane for your Lightning Network Daemon (lnd)"
×
384
        app.Flags = []cli.Flag{
×
385
                cli.StringFlag{
×
386
                        Name:   "rpcserver",
×
387
                        Value:  defaultRPCHostPort,
×
388
                        Usage:  "The host:port of LN daemon.",
×
389
                        EnvVar: envVarRPCServer,
×
390
                },
×
391
                cli.StringFlag{
×
392
                        Name:      "lnddir",
×
393
                        Value:     DefaultLndDir,
×
394
                        Usage:     "The path to lnd's base directory.",
×
395
                        TakesFile: true,
×
396
                        EnvVar:    envVarLNDDir,
×
397
                },
×
398
                cli.StringFlag{
×
399
                        Name: "socksproxy",
×
400
                        Usage: "The host:port of a SOCKS proxy through " +
×
401
                                "which all connections to the LN " +
×
402
                                "daemon will be established over.",
×
403
                        EnvVar: envVarSOCKSProxy,
×
404
                },
×
405
                cli.StringFlag{
×
406
                        Name:      "tlscertpath",
×
407
                        Value:     defaultTLSCertPath,
×
408
                        Usage:     "The path to lnd's TLS certificate.",
×
409
                        TakesFile: true,
×
410
                        EnvVar:    envVarTLSCertPath,
×
411
                },
×
412
                cli.StringFlag{
×
413
                        Name:   "chain, c",
×
414
                        Usage:  "The chain lnd is running on, e.g. bitcoin.",
×
415
                        Value:  "bitcoin",
×
416
                        EnvVar: envVarChain,
×
417
                },
×
418
                cli.StringFlag{
×
419
                        Name: "network, n",
×
420
                        Usage: "The network lnd is running on; valid values " +
×
421
                                "are: mainnet, testnet, testnet4, regtest, " +
×
422
                                "signet and simnet.",
×
423
                        Value:  "mainnet",
×
424
                        EnvVar: envVarNetwork,
×
425
                },
×
426
                cli.BoolFlag{
×
427
                        Name:  "no-macaroons",
×
428
                        Usage: "Disable macaroon authentication.",
×
429
                },
×
430
                cli.StringFlag{
×
431
                        Name:      "macaroonpath",
×
432
                        Usage:     "The path to macaroon file.",
×
433
                        TakesFile: true,
×
434
                        EnvVar:    envVarMacaroonPath,
×
435
                },
×
436
                cli.Int64Flag{
×
437
                        Name:  "macaroontimeout",
×
438
                        Value: 60,
×
439
                        Usage: "Anti-replay macaroon validity time in " +
×
440
                                "seconds.",
×
441
                        EnvVar: envVarMacaroonTimeout,
×
442
                },
×
443
                cli.StringFlag{
×
444
                        Name:   "macaroonip",
×
445
                        Usage:  "If set, lock macaroon to specific IP address.",
×
446
                        EnvVar: envVarMacaroonIP,
×
447
                },
×
448
                cli.StringFlag{
×
449
                        Name: "profile, p",
×
450
                        Usage: "Instead of reading settings from command " +
×
451
                                "line parameters or using the default " +
×
452
                                "profile, use a specific profile. If " +
×
453
                                "a default profile is set, this flag can be " +
×
454
                                "set to an empty string to disable reading " +
×
455
                                "values from the profiles file.",
×
456
                        EnvVar: envVarProfile,
×
457
                },
×
458
                cli.StringFlag{
×
459
                        Name: "macfromjar",
×
460
                        Usage: "Use this macaroon from the profile's " +
×
461
                                "macaroon jar instead of the default one. " +
×
462
                                "Can only be used if profiles are defined.",
×
463
                        EnvVar: envVarMacFromJar,
×
464
                },
×
465
                cli.StringSliceFlag{
×
466
                        Name: "metadata",
×
467
                        Usage: "This flag can be used to specify a key-value " +
×
468
                                "pair that should be appended to the " +
×
469
                                "outgoing context before the request is sent " +
×
470
                                "to lnd. This flag may be specified multiple " +
×
471
                                "times. The format is: \"key:value\".",
×
472
                },
×
473
                cli.BoolFlag{
×
474
                        Name: "insecure",
×
475
                        Usage: "Connect to the rpc server without TLS " +
×
476
                                "authentication",
×
477
                        Hidden: true,
×
478
                },
×
479
        }
×
480
        app.Commands = []cli.Command{
×
481
                createCommand,
×
482
                createWatchOnlyCommand,
×
483
                unlockCommand,
×
484
                changePasswordCommand,
×
485
                newAddressCommand,
×
486
                estimateFeeCommand,
×
487
                sendManyCommand,
×
488
                sendCoinsCommand,
×
489
                listUnspentCommand,
×
490
                connectCommand,
×
491
                disconnectCommand,
×
492
                openChannelCommand,
×
493
                batchOpenChannelCommand,
×
494
                closeChannelCommand,
×
495
                closeAllChannelsCommand,
×
496
                abandonChannelCommand,
×
497
                listPeersCommand,
×
498
                walletBalanceCommand,
×
499
                ChannelBalanceCommand,
×
500
                getInfoCommand,
×
501
                getDebugInfoCommand,
×
502
                encryptDebugPackageCommand,
×
503
                decryptDebugPackageCommand,
×
504
                getRecoveryInfoCommand,
×
505
                pendingChannelsCommand,
×
506
                SendPaymentCommand,
×
507
                payInvoiceCommand,
×
508
                sendToRouteCommand,
×
509
                AddInvoiceCommand,
×
510
                lookupInvoiceCommand,
×
511
                listInvoicesCommand,
×
512
                deleteCanceledInvoiceCommand,
×
513
                ListChannelsCommand,
×
514
                closedChannelsCommand,
×
515
                listPaymentsCommand,
×
516
                describeGraphCommand,
×
517
                getNodeMetricsCommand,
×
518
                getChanInfoCommand,
×
519
                getNodeInfoCommand,
×
520
                queryRoutesCommand,
×
521
                getNetworkInfoCommand,
×
522
                debugLevelCommand,
×
523
                decodePayReqCommand,
×
524
                listChainTxnsCommand,
×
525
                stopCommand,
×
526
                signMessageCommand,
×
527
                verifyMessageCommand,
×
528
                feeReportCommand,
×
529
                updateChannelPolicyCommand,
×
530
                forwardingHistoryCommand,
×
531
                exportChanBackupCommand,
×
532
                verifyChanBackupCommand,
×
533
                restoreChanBackupCommand,
×
534
                bakeMacaroonCommand,
×
535
                listMacaroonIDsCommand,
×
536
                deleteMacaroonIDCommand,
×
537
                listPermissionsCommand,
×
538
                printMacaroonCommand,
×
539
                constrainMacaroonCommand,
×
540
                trackPaymentCommand,
×
541
                versionCommand,
×
542
                profileSubCommand,
×
543
                getStateCommand,
×
544
                deletePaymentsCommand,
×
545
                sendCustomCommand,
×
546
                subscribeCustomCommand,
×
547
                fishCompletionCommand,
×
548
                listAliasesCommand,
×
549
                estimateRouteFeeCommand,
×
550
                generateManPageCommand,
×
551
        }
×
552

×
553
        // Add any extra commands determined by build flags.
×
554
        app.Commands = append(app.Commands, autopilotCommands()...)
×
555
        app.Commands = append(app.Commands, invoicesCommands()...)
×
556
        app.Commands = append(app.Commands, neutrinoCommands()...)
×
557
        app.Commands = append(app.Commands, routerCommands()...)
×
558
        app.Commands = append(app.Commands, walletCommands()...)
×
559
        app.Commands = append(app.Commands, watchtowerCommands()...)
×
560
        app.Commands = append(app.Commands, wtclientCommands()...)
×
561
        app.Commands = append(app.Commands, devCommands()...)
×
562
        app.Commands = append(app.Commands, peersCommands()...)
×
563
        app.Commands = append(app.Commands, chainCommands()...)
×
564

×
565
        if err := app.Run(os.Args); err != nil {
×
566
                fatal(err)
×
567
        }
×
568
}
569

570
// readPassword reads a password from the terminal. This requires there to be an
571
// actual TTY so passing in a password from stdin won't work.
572
func readPassword(text string) ([]byte, error) {
×
573
        fmt.Print(text)
×
574

×
575
        // The variable syscall.Stdin is of a different type in the Windows API
×
576
        // that's why we need the explicit cast. And of course the linter
×
577
        // doesn't like it either.
×
578
        pw, err := term.ReadPassword(int(syscall.Stdin)) //nolint:unconvert
×
579
        fmt.Println()
×
580

×
581
        return pw, err
×
582
}
×
583

584
// networkParams parses the global network flag into a chaincfg.Params.
585
func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
×
586
        network := strings.ToLower(ctx.GlobalString("network"))
×
587
        switch network {
×
588
        case "mainnet":
×
589
                return &chaincfg.MainNetParams, nil
×
590

591
        case "testnet", "testnet3":
×
592
                return &chaincfg.TestNet3Params, nil
×
593

594
        case "testnet4":
×
595
                return &chaincfg.TestNet4Params, nil
×
596

597
        case "regtest":
×
598
                return &chaincfg.RegressionNetParams, nil
×
599

600
        case "simnet":
×
601
                return &chaincfg.SimNetParams, nil
×
602

603
        case "signet":
×
604
                return &chaincfg.SigNetParams, nil
×
605

606
        default:
×
607
                return nil, fmt.Errorf("unknown network: %v", network)
×
608
        }
609
}
610

611
// parseCoinSelectionStrategy parses a coin selection strategy string
612
// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type.
613
func parseCoinSelectionStrategy(ctx *cli.Context) (
614
        lnrpc.CoinSelectionStrategy, error) {
×
615

×
616
        strategy := ctx.String(coinSelectionStrategyFlag.Name)
×
617
        if !ctx.IsSet(coinSelectionStrategyFlag.Name) {
×
618
                return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
×
619
                        nil
×
620
        }
×
621

622
        switch strategy {
×
623
        case "global-config":
×
624
                return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG,
×
625
                        nil
×
626

627
        case "largest":
×
628
                return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil
×
629

630
        case "random":
×
631
                return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil
×
632

633
        default:
×
634
                return 0, fmt.Errorf("unknown coin selection strategy "+
×
635
                        "%v", strategy)
×
636
        }
637
}
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