diff --git a/cmd/lncli/cmd_open_channel.go b/cmd/lncli/cmd_open_channel.go index 45f032f53c..d7081e4e09 100644 --- a/cmd/lncli/cmd_open_channel.go +++ b/cmd/lncli/cmd_open_channel.go @@ -817,6 +817,7 @@ var batchOpenChannelCommand = cli.Command{ "transaction when storing it to the local " + "wallet after publishing it", }, + coinSelectionStrategyFlag, }, Action: actionDecorator(batchOpenChannel), } @@ -845,13 +846,19 @@ func batchOpenChannel(ctx *cli.Context) error { return nil } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) req := &lnrpc.BatchOpenChannelRequest{ - TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")), - MinConfs: minConfs, - SpendUnconfirmed: minConfs == 0, - Label: ctx.String("label"), + TargetConf: int32(ctx.Int64("conf_target")), + SatPerVbyte: int64(ctx.Uint64("sat_per_vbyte")), + MinConfs: minConfs, + SpendUnconfirmed: minConfs == 0, + Label: ctx.String("label"), + CoinSelectionStrategy: coinSelectionStrategy, } // Let's try and parse the JSON part of the CLI now. Fortunately we can diff --git a/cmd/lncli/commands.go b/cmd/lncli/commands.go index 4b1addeed9..da03b67a3c 100644 --- a/cmd/lncli/commands.go +++ b/cmd/lncli/commands.go @@ -200,6 +200,16 @@ func newAddress(ctx *cli.Context) error { return nil } +var coinSelectionStrategyFlag = cli.StringFlag{ + Name: "coin_selection_strategy", + Usage: "(optional) the strategy to use for selecting " + + "coins. Possible values are 'largest', 'random', or " + + "'global-config'. If either 'largest' or 'random' is " + + "specified, it will override the globally configured " + + "strategy in lnd.conf", + Value: "global-config", +} + var estimateFeeCommand = cli.Command{ Name: "estimatefee", Category: "On-chain", @@ -215,9 +225,10 @@ var estimateFeeCommand = cli.Command{ Flags: []cli.Flag{ cli.Int64Flag{ Name: "conf_target", - Usage: "(optional) the number of blocks that the transaction *should* " + - "confirm in", + Usage: "(optional) the number of blocks that the " + + "transaction *should* confirm in", }, + coinSelectionStrategyFlag, }, Action: actionDecorator(estimateFees), } @@ -231,12 +242,18 @@ func estimateFees(ctx *cli.Context) error { return err } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + client, cleanUp := getClient(ctx) defer cleanUp() resp, err := client.EstimateFee(ctxc, &lnrpc.EstimateFeeRequest{ - AddrToAmount: amountToAddr, - TargetConf: int32(ctx.Int64("conf_target")), + AddrToAmount: amountToAddr, + TargetConf: int32(ctx.Int64("conf_target")), + CoinSelectionStrategy: coinSelectionStrategy, }) if err != nil { return err @@ -313,6 +330,7 @@ var sendCoinsCommand = cli.Command{ "terminal avoid breaking existing shell " + "scripts", }, + coinSelectionStrategyFlag, txLabelFlag, }, Action: actionDecorator(sendCoins), @@ -375,6 +393,11 @@ func sendCoins(ctx *cli.Context) error { "sweep all coins out of the wallet") } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + client, cleanUp := getClient(ctx) defer cleanUp() minConfs := int32(ctx.Uint64("min_confs")) @@ -409,14 +432,15 @@ func sendCoins(ctx *cli.Context) error { } req := &lnrpc.SendCoinsRequest{ - Addr: addr, - Amount: amt, - TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), - SendAll: ctx.Bool("sweepall"), - Label: ctx.String(txLabelFlag.Name), - MinConfs: minConfs, - SpendUnconfirmed: minConfs == 0, + Addr: addr, + Amount: amt, + TargetConf: int32(ctx.Int64("conf_target")), + SatPerVbyte: ctx.Uint64(feeRateFlag), + SendAll: ctx.Bool("sweepall"), + Label: ctx.String(txLabelFlag.Name), + MinConfs: minConfs, + SpendUnconfirmed: minConfs == 0, + CoinSelectionStrategy: coinSelectionStrategy, } txid, err := client.SendCoins(ctxc, req) if err != nil { @@ -585,6 +609,7 @@ var sendManyCommand = cli.Command{ "must satisfy", Value: defaultUtxoMinConf, }, + coinSelectionStrategyFlag, txLabelFlag, }, Action: actionDecorator(sendMany), @@ -615,17 +640,23 @@ func sendMany(ctx *cli.Context) error { return err } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + client, cleanUp := getClient(ctx) defer cleanUp() minConfs := int32(ctx.Uint64("min_confs")) txid, err := client.SendMany(ctxc, &lnrpc.SendManyRequest{ - AddrToAmount: amountToAddr, - TargetConf: int32(ctx.Int64("conf_target")), - SatPerVbyte: ctx.Uint64(feeRateFlag), - Label: ctx.String(txLabelFlag.Name), - MinConfs: minConfs, - SpendUnconfirmed: minConfs == 0, + AddrToAmount: amountToAddr, + TargetConf: int32(ctx.Int64("conf_target")), + SatPerVbyte: ctx.Uint64(feeRateFlag), + Label: ctx.String(txLabelFlag.Name), + MinConfs: minConfs, + SpendUnconfirmed: minConfs == 0, + CoinSelectionStrategy: coinSelectionStrategy, }) if err != nil { return err diff --git a/cmd/lncli/main.go b/cmd/lncli/main.go index 95f82b2347..b1554fb070 100644 --- a/cmd/lncli/main.go +++ b/cmd/lncli/main.go @@ -564,3 +564,31 @@ func networkParams(ctx *cli.Context) (*chaincfg.Params, error) { return nil, fmt.Errorf("unknown network: %v", network) } } + +// parseCoinSelectionStrategy parses a coin selection strategy string +// from the CLI to its lnrpc.CoinSelectionStrategy counterpart proto type. +func parseCoinSelectionStrategy(ctx *cli.Context) ( + lnrpc.CoinSelectionStrategy, error) { + + strategy := ctx.String(coinSelectionStrategyFlag.Name) + if !ctx.IsSet(coinSelectionStrategyFlag.Name) { + return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, + nil + } + + switch strategy { + case "global-config": + return lnrpc.CoinSelectionStrategy_STRATEGY_USE_GLOBAL_CONFIG, + nil + + case "largest": + return lnrpc.CoinSelectionStrategy_STRATEGY_LARGEST, nil + + case "random": + return lnrpc.CoinSelectionStrategy_STRATEGY_RANDOM, nil + + default: + return 0, fmt.Errorf("unknown coin selection strategy "+ + "%v", strategy) + } +} diff --git a/cmd/lncli/walletrpc_active.go b/cmd/lncli/walletrpc_active.go index 3e01ecf142..5118ddd2f7 100644 --- a/cmd/lncli/walletrpc_active.go +++ b/cmd/lncli/walletrpc_active.go @@ -849,6 +849,7 @@ var fundTemplatePsbtCommand = cli.Command{ "if required", Value: -1, }, + coinSelectionStrategyFlag, }, Action: actionDecorator(fundTemplatePsbt), } @@ -997,6 +998,11 @@ func fundTemplatePsbt(ctx *cli.Context) error { "inputs/outputs flag") } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) req := &walletrpc.FundPsbtRequest{ Account: ctx.String("account"), @@ -1005,6 +1011,7 @@ func fundTemplatePsbt(ctx *cli.Context) error { Template: &walletrpc.FundPsbtRequest_CoinSelect{ CoinSelect: coinSelect, }, + CoinSelectionStrategy: coinSelectionStrategy, } // Parse fee flags. @@ -1167,6 +1174,7 @@ var fundPsbtCommand = cli.Command{ "transaction must satisfy", Value: defaultUtxoMinConf, }, + coinSelectionStrategyFlag, }, Action: actionDecorator(fundPsbt), } @@ -1180,11 +1188,17 @@ func fundPsbt(ctx *cli.Context) error { return cli.ShowCommandHelp(ctx, "fund") } + coinSelectionStrategy, err := parseCoinSelectionStrategy(ctx) + if err != nil { + return err + } + minConfs := int32(ctx.Uint64("min_confs")) req := &walletrpc.FundPsbtRequest{ - Account: ctx.String("account"), - MinConfs: minConfs, - SpendUnconfirmed: minConfs == 0, + Account: ctx.String("account"), + MinConfs: minConfs, + SpendUnconfirmed: minConfs == 0, + CoinSelectionStrategy: coinSelectionStrategy, } // Parse template flags.