diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index d999ffa..0f55943 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -63,6 +63,20 @@ var loopOutCommand = cli.Command{ "satoshis, if not specified, a default max " + "fee will be used", }, + cli.Float64Flag{ + Name: "max_sweep_fee_percent", + Usage: "the max on-chain miner fee used for sweeping " + + "HTLCs, expressed as a percent (0 to 1.0) to " + + "be deducted from the total swap amount. If " + + "not set, will use the default value of " + + "max_sweep_fee instead.", + }, + cli.Uint64Flag{ + Name: "max_sweep_fee", + Usage: "the max on-chain miner fee (in satoshis) " + + "used for sweeping HTLCs. If not set, will " + + "use the prepay amount as the default value.", + }, cli.BoolFlag{ Name: "fast", Usage: "Indicate you want to swap immediately, " + @@ -176,6 +190,25 @@ func loopOut(ctx *cli.Context) error { ctx.Int64("max_swap_routing_fee"), ) } + + // Calculate max miner fee. The prepay amount will be used if not set. + maxMinerFee, err := calculateMaxMinerFee(ctx, amt) + if err != nil { + return err + } + if maxMinerFee != 0 { + // Override the default if maxMinerFee is set. + limits.maxMinerFee = maxMinerFee + } + + // Check that the maxMinerFee is sane. The prepay amount should also be + // greater then the minMinerFee. + if limits.maxMinerFee < limits.minMinerFee { + return fmt.Errorf("max sweep fee is too small, must be "+ + "greater than %d sats", limits.minMinerFee, + ) + } + err = displayOutDetails( limits, warning, quoteReq, quote, ctx.Bool("verbose"), ) @@ -213,3 +246,43 @@ func loopOut(ctx *cli.Context) error { return nil } + +// calculateMaxMinerFee validates the max miner fee set by cli is sane. +func calculateMaxMinerFee(ctx *cli.Context, + amt btcutil.Amount) (btcutil.Amount, error) { + + useSatoshi := ctx.IsSet("max_sweep_fee") + usePercent := ctx.IsSet("max_sweep_fee_percent") + + switch { + case useSatoshi && usePercent: + // Max sweep fee can only be set either in the format of + // satoshi or percent. + return 0, fmt.Errorf("either max_sweep_fee or " + + "max_sweep_fee_percent should be set, not both", + ) + + case useSatoshi: + fee := btcutil.Amount(ctx.Uint64("max_sweep_fee")) + if fee >= amt { + return 0, fmt.Errorf("max_sweep_fee (%d) cannot be "+ + "greater than total swap amount (%d)", fee, amt, + ) + } + return btcutil.Amount(ctx.Uint64("max_sweep_fee")), nil + + case usePercent: + // Validate percent is sane. + percent := ctx.Float64("max_sweep_fee_percent") + if percent < 0 || percent >= 1 { + return 0, fmt.Errorf("max_sweep_fee_percent is "+ + "invalid, must be in range(0, 1), "+ + "instead got %f", percent, + ) + } + return amt.MulF64(percent), nil + } + + // Return zero if neither is set. + return 0, nil +} diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 898e866..bb0297e 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -243,6 +243,10 @@ type outLimits struct { maxMinerFee btcutil.Amount maxSwapFee btcutil.Amount maxPrepayAmt btcutil.Amount + + // minMinerFee guards the min value can be used when specifying the max + // miner fee. + minMinerFee btcutil.Amount } func getOutLimits(amt btcutil.Amount, @@ -258,10 +262,9 @@ func getOutLimits(amt btcutil.Amount, maxSwapRoutingFee: maxSwapRoutingFee, maxPrepayRoutingFee: maxPrepayRoutingFee, - // Apply a multiplier to the estimated miner fee, to not get - // the swap canceled because fees increased in the mean time. - maxMinerFee: btcutil.Amount(quote.HtlcSweepFeeSat) * 100, - + // maxMinerFee is default to the prepay amount. + maxMinerFee: maxPrepayAmt, + minMinerFee: btcutil.Amount(quote.HtlcSweepFeeSat), maxSwapFee: btcutil.Amount(quote.SwapFeeSat), maxPrepayAmt: maxPrepayAmt, } diff --git a/release_notes.md b/release_notes.md index 9a87269..5f9d847 100644 --- a/release_notes.md +++ b/release_notes.md @@ -15,6 +15,14 @@ This file tracks release notes for the loop client. ## Next release #### New Features +* Two new flags, `--max_sweep_fee` and `--max_sweep_fee_percent`, are added to + `loop out`. Users now can specify the max on-chain fee to be paid when + sweeping the HTLCs. `max_sweep_fee` specifies the max fee can be used in + satoshis, default to the prepay amount. `max_sweep_fee_percent` specifies a + portion based on the total swap amount. Note that these flags cannot be used + together. When neither is specified, the default value of `max_sweep_fee` will + be used. Be cautious when setting the max fee as a low value might cause + forfeiting the prepay. #### Breaking Changes