diff --git a/cmd/loop/loopin.go b/cmd/loop/loopin.go index 467028a..6049fd4 100644 --- a/cmd/loop/loopin.go +++ b/cmd/loop/loopin.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/btcsuite/btcutil" "github.com/lightninglabs/loop" "github.com/lightninglabs/loop/labels" "github.com/lightninglabs/loop/looprpc" @@ -61,6 +60,7 @@ var ( confTargetFlag, lastHopFlag, labelFlag, + verboseFlag, }, Action: loopIn, } @@ -109,14 +109,12 @@ func loopIn(ctx *cli.Context) error { return err } - quote, err := client.GetLoopInQuote( - context.Background(), - &looprpc.QuoteRequest{ - Amt: int64(amt), - ConfTarget: htlcConfTarget, - ExternalHtlc: external, - }, - ) + quoteReq := &looprpc.QuoteRequest{ + Amt: int64(amt), + ConfTarget: htlcConfTarget, + ExternalHtlc: external, + } + quote, err := client.GetLoopInQuote(context.Background(), quoteReq) if err != nil { return err } @@ -136,9 +134,7 @@ func loopIn(ctx *cli.Context) error { } limits := getInLimits(quote) - err = displayInLimits( - amt, btcutil.Amount(quote.HtlcPublishFeeSat), limits, external, - ) + err = displayInDetails(quoteReq, quote, ctx.Bool("verbose")) if err != nil { return err } diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index 129ee83..d999ffa 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -74,6 +74,7 @@ var loopOutCommand = cli.Command{ "result in a lower swap fee.", }, labelFlag, + verboseFlag, }, Action: loopOut, } @@ -175,8 +176,8 @@ func loopOut(ctx *cli.Context) error { ctx.Int64("max_swap_routing_fee"), ) } - err = displayOutLimits( - amt, btcutil.Amount(quote.HtlcSweepFeeSat), limits, warning, + err = displayOutDetails( + limits, warning, quoteReq, quote, ctx.Bool("verbose"), ) if err != nil { return err diff --git a/cmd/loop/main.go b/cmd/loop/main.go index d1cdd7c..898e866 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -74,6 +74,27 @@ var ( Usage: "path to macaroon file", Value: loopd.DefaultMacaroonPath, } + verboseFlag = cli.BoolFlag{ + Name: "verbose, v", + Usage: "show expanded details", + } +) + +const ( + + // satAmtFmt formats a satoshi value into a one line string, intended to + // prettify the terminal output. For Instance, + // fmt.Printf(f, "Estimated on-chain fee:", fee) + // prints out as, + // Estimated on-chain fee: 7262 sat + satAmtFmt = "%-36s %12d sat\n" + + // blkFmt formats the number of blocks into a one line string, intended + // to prettify the terminal output. For Instance, + // fmt.Printf(f, "Conf target", target) + // prints out as, + // Conf target: 9 block + blkFmt = "%-36s %12d block\n" ) func printJSON(resp interface{}) { @@ -246,89 +267,56 @@ func getOutLimits(amt btcutil.Amount, } } -func displayInLimits(amt, minerFees btcutil.Amount, l *inLimits, - externalHtlc bool) error { +func displayInDetails(req *looprpc.QuoteRequest, + resp *looprpc.InQuoteResponse, verbose bool) error { - totalSuccessMax := l.maxMinerFee + l.maxSwapFee - - if externalHtlc { + if req.ExternalHtlc { fmt.Printf("On-chain fee for external loop in is not " + "included.\nSufficient fees will need to be paid " + "when constructing the transaction in the external " + "wallet.\n\n") } - fmt.Printf("Max swap fees for %d sat Loop %v: %d sat\n", amt, swap.TypeIn, - totalSuccessMax) + printQuoteInResp(req, resp, verbose) - fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ") + fmt.Printf("\nCONTINUE SWAP? (y/n): ") var answer string fmt.Scanln(&answer) - - switch answer { - case "y": + if answer == "y" { return nil - case "x": - fmt.Println() - f := "%-36s %d sat\n" - - if !externalHtlc { - fmt.Printf(f, "Estimated on-chain HTLC fee:", - minerFees) - } - - fmt.Printf(f, "Max swap fee:", l.maxSwapFee) - - fmt.Printf("CONTINUE SWAP? (y/n): ") - fmt.Scanln(&answer) - if answer == "y" { - return nil - } } return errors.New("swap canceled") } -func displayOutLimits(amt, minerFees btcutil.Amount, l *outLimits, - warning string) error { +func displayOutDetails(l *outLimits, warning string, req *looprpc.QuoteRequest, + resp *looprpc.OutQuoteResponse, verbose bool) error { - totalSuccessMax := l.maxMinerFee + l.maxSwapFee + l.maxSwapRoutingFee + - l.maxPrepayRoutingFee + printQuoteOutResp(req, resp, verbose) - fmt.Printf("Max swap fees for %d sat Loop %v: %d sat\n", amt, swap.TypeOut, - totalSuccessMax) + // Display fee limits. + if verbose { + fmt.Println() + fmt.Printf(satAmtFmt, "Max on-chain fee:", l.maxMinerFee) + fmt.Printf(satAmtFmt, + "Max off-chain swap routing fee:", l.maxSwapRoutingFee, + ) + fmt.Printf(satAmtFmt, "Max off-chain prepay routing fee:", + l.maxPrepayRoutingFee) + } + // show warning if warning != "" { - fmt.Println(warning) + fmt.Printf("\n%s\n\n", warning) } - fmt.Printf("CONTINUE SWAP? (y/n), expand fee detail (x): ") + fmt.Printf("CONTINUE SWAP? (y/n): ") var answer string fmt.Scanln(&answer) - - switch answer { - case "y": + if answer == "y" { return nil - case "x": - fmt.Println() - f := "%-36s %d sat\n" - - fmt.Printf(f, "Estimated on-chain sweep fee:", minerFees) - fmt.Printf(f, "Max on-chain sweep fee:", l.maxMinerFee) - fmt.Printf(f, "Max off-chain swap routing fee:", - l.maxSwapRoutingFee) - fmt.Printf(f, "Max no show penalty (prepay):", l.maxPrepayAmt) - fmt.Printf(f, "Max off-chain prepay routing fee:", - l.maxPrepayRoutingFee) - fmt.Printf(f, "Max swap fee:", l.maxSwapFee) - - fmt.Printf("CONTINUE SWAP? (y/n): ") - fmt.Scanln(&answer) - if answer == "y" { - return nil - } } return errors.New("swap canceled") diff --git a/cmd/loop/quote.go b/cmd/loop/quote.go index b63bac9..40c03af 100644 --- a/cmd/loop/quote.go +++ b/cmd/loop/quote.go @@ -22,7 +22,7 @@ var quoteInCommand = cli.Command{ Usage: "get a quote for the cost of a loop in swap", ArgsUsage: "amt", Description: "Allows to determine the cost of a swap up front", - Flags: []cli.Flag{confTargetFlag}, + Flags: []cli.Flag{confTargetFlag, verboseFlag}, Action: quoteIn, } @@ -66,7 +66,7 @@ func quoteIn(ctx *cli.Context) error { "amount.\n") } - printRespJSON(quoteResp) + printQuoteInResp(quoteReq, quoteResp, ctx.Bool("verbose")) return nil } @@ -93,6 +93,7 @@ var quoteOutCommand = cli.Command{ "setting this flag might result in a lower " + "swap fee.", }, + verboseFlag, }, Action: quoteOut, } @@ -132,6 +133,68 @@ func quoteOut(ctx *cli.Context) error { return err } - printRespJSON(quoteResp) + printQuoteOutResp(quoteReq, quoteResp, ctx.Bool("verbose")) return nil } + +func printQuoteInResp(req *looprpc.QuoteRequest, + resp *looprpc.InQuoteResponse, verbose bool) { + + totalFee := resp.HtlcPublishFeeSat + resp.SwapFeeSat + + fmt.Printf(satAmtFmt, "Send on-chain:", req.Amt) + fmt.Printf(satAmtFmt, "Receive off-chain:", req.Amt-totalFee) + + switch { + case req.ExternalHtlc && !verbose: + // If it's external then we don't know the miner fee hence the + // total cost. + fmt.Printf(satAmtFmt, "Loop service fee:", resp.SwapFeeSat) + + case req.ExternalHtlc && verbose: + fmt.Printf(satAmtFmt, "Loop service fee:", resp.SwapFeeSat) + fmt.Println() + fmt.Printf(blkFmt, "CLTV expiry delta:", resp.CltvDelta) + + case verbose: + fmt.Println() + fmt.Printf( + satAmtFmt, "Estimated on-chain fee:", + resp.HtlcPublishFeeSat, + ) + fmt.Printf(satAmtFmt, "Loop service fee:", resp.SwapFeeSat) + fmt.Printf(satAmtFmt, "Estimated total fee:", totalFee) + fmt.Println() + fmt.Printf(blkFmt, "Conf target:", resp.ConfTarget) + fmt.Printf(blkFmt, "CLTV expiry delta:", resp.CltvDelta) + default: + fmt.Printf(satAmtFmt, "Estimated total fee:", totalFee) + } +} + +func printQuoteOutResp(req *looprpc.QuoteRequest, + resp *looprpc.OutQuoteResponse, verbose bool) { + + totalFee := resp.HtlcSweepFeeSat + resp.SwapFeeSat + + fmt.Printf(satAmtFmt, "Send off-chain:", req.Amt) + fmt.Printf(satAmtFmt, "Receive on-chain:", req.Amt-totalFee) + + if !verbose { + fmt.Printf(satAmtFmt, "Estimated total fee:", totalFee) + return + } + + fmt.Println() + fmt.Printf(satAmtFmt, "Estimated on-chain fee:", resp.HtlcSweepFeeSat) + fmt.Printf(satAmtFmt, "Loop service fee:", resp.SwapFeeSat) + fmt.Printf(satAmtFmt, "Estimated total fee:", totalFee) + fmt.Println() + fmt.Printf(satAmtFmt, "No show penalty (prepay):", resp.PrepayAmtSat) + fmt.Printf(blkFmt, "Conf target:", resp.ConfTarget) + fmt.Printf(blkFmt, "CLTV expiry delta:", resp.CltvDelta) + fmt.Printf("%-38s %s\n", + "Publication deadline:", + time.Unix(int64(req.SwapPublicationDeadline), 0), + ) +} diff --git a/release_notes.md b/release_notes.md index a914627..debc18c 100644 --- a/release_notes.md +++ b/release_notes.md @@ -15,7 +15,11 @@ This file tracks release notes for the loop client. ## Next release #### New Features - +* A new flag, `--verbose`, or `-v`, is added to `loop in`, `loop out` and + `loop quote`. Responses from these commands are also updated to provide more + verbose info, giving users a more intuitive view about money paid + on/off-chain and fees incurred. Use `loop in -v`, `loop out -v`, + `loop quote in -v` or `loop quote out -v` to view the details. #### Breaking Changes