From 3b630667a5ece2c1fae258e1c111f53bb4a36037 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 20 Apr 2020 18:14:43 +0200 Subject: [PATCH 1/3] cmd/loop: improve displayed limits --- cmd/loop/loopin.go | 5 ++++- cmd/loop/loopout.go | 5 ++++- cmd/loop/main.go | 35 ++++++++++++++++++++++------------- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/cmd/loop/loopin.go b/cmd/loop/loopin.go index 3037f9b..dee777f 100644 --- a/cmd/loop/loopin.go +++ b/cmd/loop/loopin.go @@ -119,7 +119,10 @@ func loopIn(ctx *cli.Context) error { } limits := getInLimits(amt, quote) - err = displayLimits(swap.TypeIn, amt, limits, external, "") + err = displayLimits( + swap.TypeIn, amt, btcutil.Amount(quote.MinerFee), limits, + external, "", + ) if err != nil { return err } diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index a740e3b..328a5b7 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -135,7 +135,10 @@ func loopOut(ctx *cli.Context) error { ctx.Int64("max_swap_routing_fee"), ) } - err = displayLimits(swap.TypeOut, amt, limits, false, warning) + err = displayLimits( + swap.TypeOut, amt, btcutil.Amount(quote.MinerFee), limits, + false, warning, + ) if err != nil { return err } diff --git a/cmd/loop/main.go b/cmd/loop/main.go index 0a7f82b..9d11e47 100644 --- a/cmd/loop/main.go +++ b/cmd/loop/main.go @@ -141,7 +141,7 @@ func getLimits(amt btcutil.Amount, quote *looprpc.QuoteResponse) *limits { } } -func displayLimits(swapType swap.Type, amt btcutil.Amount, l *limits, +func displayLimits(swapType swap.Type, amt, minerFees btcutil.Amount, l *limits, externalHtlc bool, warning string) error { totalSuccessMax := l.maxMinerFee + l.maxSwapFee @@ -159,7 +159,7 @@ func displayLimits(swapType swap.Type, amt btcutil.Amount, l *limits, "wallet.\n\n") } - fmt.Printf("Max swap fees for %d Loop %v: %d\n", amt, swapType, + fmt.Printf("Max swap fees for %d sat Loop %v: %d sat\n", amt, swapType, totalSuccessMax) if warning != "" { @@ -176,26 +176,35 @@ func displayLimits(swapType swap.Type, amt btcutil.Amount, l *limits, return nil case "x": fmt.Println() - if swapType != swap.TypeIn || !externalHtlc { - fmt.Printf("Max on-chain fee: %d\n", - l.maxMinerFee) + f := "%-36s %d sat\n" + + switch swapType { + case swap.TypeOut: + fmt.Printf(f, "Estimated on-chain sweep fee:", + minerFees) + fmt.Printf(f, "Max on-chain sweep fee:", l.maxMinerFee) + + case swap.TypeIn: + if !externalHtlc { + fmt.Printf(f, "Estimated on-chain HTLC fee:", + minerFees) + } } if l.maxSwapRoutingFee != nil { - fmt.Printf("Max off-chain swap routing fee: %d\n", + fmt.Printf(f, "Max off-chain swap routing fee:", *l.maxSwapRoutingFee) } - if l.maxPrepayRoutingFee != nil { - fmt.Printf("Max off-chain prepay routing fee: %d\n", - *l.maxPrepayRoutingFee) - } - fmt.Printf("Max swap fee: %d\n", l.maxSwapFee) - if l.maxPrepayAmt != nil { - fmt.Printf("Max no show penalty: %d\n", + fmt.Printf(f, "Max no show penalty (prepay):", *l.maxPrepayAmt) } + if l.maxPrepayRoutingFee != nil { + 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) From 3ea07f167f0d7a19a4ef5c4b934db2b55e5229f4 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 20 Apr 2020 18:14:45 +0200 Subject: [PATCH 2/3] cmd/loop: clarify some flag descriptions --- cmd/loop/loopout.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/cmd/loop/loopout.go b/cmd/loop/loopout.go index 328a5b7..5aa589a 100644 --- a/cmd/loop/loopout.go +++ b/cmd/loop/loopout.go @@ -17,7 +17,7 @@ var loopOutCommand = cli.Command{ Usage: "perform an off-chain to on-chain swap (looping out)", ArgsUsage: "amt [addr]", Description: ` - Attempts loop out the target amount into either the backing lnd's + Attempts to loop out the target amount into either the backing lnd's wallet, or a targeted address. The amount is to be specified in satoshis. @@ -26,8 +26,9 @@ var loopOutCommand = cli.Command{ specified. If not specified, a new wallet address will be generated.`, Flags: []cli.Flag{ cli.Uint64Flag{ - Name: "channel", - Usage: "the 8-byte compact channel ID of the channel to loop out", + Name: "channel", + Usage: "the 8-byte compact channel ID of the channel " + + "to loop out", }, cli.StringFlag{ Name: "addr", @@ -48,8 +49,9 @@ var loopOutCommand = cli.Command{ }, cli.Int64Flag{ Name: "max_swap_routing_fee", - Usage: "the max off-chain swap routing fee in satoshis, " + - "if let blank a default max fee will be used", + Usage: "the max off-chain swap routing fee in " + + "satoshis, if not specified, a default max " + + "fee will be used", }, cli.BoolFlag{ Name: "fast", @@ -57,9 +59,9 @@ var loopOutCommand = cli.Command{ "paying potentially a higher fee. If not " + "set the swap server might choose to wait up " + "to 30 minutes before publishing the swap " + - "HTLC on-chain, to save on chain fees. Not " + - "setting this flag might result in a lower " + - "swap fee.", + "HTLC on-chain, to save on its chain fees. " + + "Not setting this flag therefore might " + + "result in a lower swap fee.", }, }, Action: loopOut, From 24066bb1dd0d8b739882f961d77caccdfb61a89a Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Mon, 20 Apr 2020 18:14:46 +0200 Subject: [PATCH 3/3] README: describe fee structure --- README.md | 120 ++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 112 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 9dedc11..5f5700b 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ USAGE: DESCRIPTION: - Attempts loop out the target amount into either the backing lnd's + Attempts to loop out the target amount into either the backing lnd's wallet, or a targeted address. The amount is to be specified in satoshis. @@ -158,9 +158,12 @@ DESCRIPTION: specified. If not specified, a new wallet address will be generated. OPTIONS: - --channel value the 8-byte compact channel ID of the channel to loop out (default: 0) - --addr value the optional address that the looped out funds should be sent to, if left blank the funds will go to lnd's wallet - --amt value the amount in satoshis to loop out (default: 0) + --channel value the 8-byte compact channel ID of the channel to loop out (default: 0) + --addr value the optional address that the looped out funds should be sent to, if let blank the funds will go to lnd's wallet + --amt value the amount in satoshis to loop out (default: 0) + --conf_target value the number of blocks from the swap initiation height that the on-chain HTLC should be swept within (default: 6) + --max_swap_routing_fee value the max off-chain swap routing fee in satoshis, if not specified, a default max fee will be used (default: 0) + --fast Indicate you want to swap immediately, paying potentially a higher fee. If not set the swap server might choose to wait up to 30 minutes before publishing the swap HTLC on-chain, to save on its chain fees. Not setting this flag therefore might result in a lower swap fee. ``` It's possible to receive more inbound capacity on a particular channel @@ -178,6 +181,79 @@ swap is initiated successfully, `loopd` will see the process through. To query in-flight swap statuses, run `loop monitor`. +### Fees explained + +The following is an example output of a 0.01 BTC fast (non-batched) Loop Out +swap from `testnet`: + +```bash +$ loop out --amt 1000000 --fast +Max swap fees for 1000000 sat Loop Out: 36046 sat +Fast swap requested. +CONTINUE SWAP? (y/n), expand fee detail (x): x + +Estimated on-chain sweep fee: 149 sat +Max on-chain sweep fee: 14900 sat +Max off-chain swap routing fee: 20010 sat +Max off-chain prepay routing fee: 36 sat +Max no show penalty (prepay): 1337 sat +Max swap fee: 1100 sat +CONTINUE SWAP? (y/n): +``` + +Explanation: + +- **Max swap fees for sat Loop Out** (36046 sat): The absolute maximum in + fees that need to be paid. This includes on-chain and off-chain fees. This + represents the ceiling or worst-case scenario. The actual fees will likely be + lower. This is the sum of `14900 + 20010 + 36 + 1100` (see below). +- **Estimated on-chain sweep fee** (149 sat): The estimated cost to sweep the + HTLC in case of success, calculated based on the _current_ on-chain fees. + This value is called `miner_fee` in the gRPC/REST responses. +- **Max on-chain sweep fee** (14900 sat): The maximum on-chain fee the daemon + is going to allow for sweeping the HTLC in case of success. A fee estimation + based on the `--conf_target` flag is always performed before sweeping. The + factor of `100` times the estimated fee is applied in case the fees spike + between the time the swap is initiated and the time the HTLC can be swept. But + that is the absolute worst-case fee that will be paid. If there is no fee + spike, a normal, much lower fee will be used. +- **Max off-chain swap routing fee** (20010 sat): The maximum off-chain + routing fee that the daemon should pay when finding a route to pay the + Lightning invoice. This is a hard limit. If no route with a lower or equal fee + is found, the payment (and the swap) is aborted. This value is calculated + statically based on the swap amount (see `maxRoutingFeeBase` and + `maxRoutingFeeRate` in `cmd/loop/main.go`). +- **Max off-chain prepay routing fee** (36 sat): The maximum off-chain routing + fee that the daemon should pay when finding a route to pay the prepay fee. + This is a hard limit. If no route with a lower or equal fee is found, the + payment (and the swap) is aborted. This value is calculated statically based + on the prepay amount (see `maxRoutingFeeBase` and `maxRoutingFeeRate` in + `cmd/loop/main.go`). +- **Max no show penalty (prepay)** (1337 sat): This is the amount that has to be + pre-paid (off-chain) before the server publishes the HTLC on-chain. This is + necessary to ensure the server's on-chain fees are paid if the client aborts + and never completes the swap _after_ the HTLC has been published on-chain. + If the swap completes normally, this amount is counted towards the full swap + amount and therefore is actually a pre-payment and not a fee. This value is + called `prepay_amt` in the gRPC/REST responses. +- **Max swap fee** (1100 sat): The maximum amount of service fees we allow the + server to charge for executing the swap. The client aborts the swap if the + fee proposed by the server exceeds this maximum. It is therefore recommended + to obtain the maximum by asking the server for a quote first. The actual fees + might be lower than this maximum if user specific discounts are applied. This + value is called `swap_fee` in the gRPC/REST responses. + +#### Fast vs. batched swaps + +By default, Loop Outs are executed as normal speed swaps. This means the server +will wait up to 30 minutes until it publishes the HTLC on-chain to improve the +chances that it can be batched together with other user's swaps to reduce the +on-chain footprint and fees. The server offers a reduced swap fee for slow swaps +to incentivize users to batch more. + +If a swap should be executed immediately, the `--fast` flag can be used. Fast +swaps won't benefit from a reduced swap fee. + ### Loop In Swaps Additionally, Loop In is now also supported for mainnet as well. A Loop In swap @@ -195,9 +271,10 @@ DESCRIPTION: Send the amount in satoshis specified by the amt argument off-chain. OPTIONS: - --amt value the amount in satoshis to loop in (default: 0) - --external expect htlc to be published externally - --conf_target the confirmation target for the on chain htlc, if not being published externally + --amt value the amount in satoshis to loop in (default: 0) + --external expect htlc to be published externally + --conf_target value the target number of blocks the on-chain htlc broadcast by the swap client should confirm within (default: 0) + --last_hop value the pubkey of the last hop to use for this swap ``` The `--external` argument allows the on-chain HTLC transacting to be published @@ -208,6 +285,34 @@ A Loop In swap can be executed a follows: ``` loop in ``` + +#### Fees explained + +The following is an example output of a 0.01 BTC Loop In swap from `testnet`: + +```bash +$ loop in --amt 1000000 +Max swap fees for 1000000 sat Loop In: 1562 sat +CONTINUE SWAP? (y/n), expand fee detail (x): x + +Estimated on-chain HTLC fee: 154 sat +Max swap fee: 1100 sat +CONTINUE SWAP? (y/n): +``` + +Explanation: + +- **Estimated on-chain HTLC fee** (154 sat): The estimated on-chain fee that the + daemon has to pay to publish the HTLC. This is an estimation from `lnd`'s + wallet based on the available UTXOs and current network fees. This value is + called `miner_fee` in the gRPC/REST responses. +- **Max swap fee** (1100 sat): The maximum amount of service fees we allow the + server to charge for executing the swap. The client aborts the swap if the + fee proposed by the server exceeds this maximum. It is therefore recommended + to obtain the maximum by asking the server for a quote first. The actual fees + might be lower than this maximum if user specific discounts are applied. This + value is called `swap_fee` in the gRPC/REST responses. + ## Resume When `loopd` is terminated (or killed) for whatever reason, it will pickup @@ -220,4 +325,3 @@ Its location is `~/.loopd//loop.db`. It is possible to execute multiple swaps simultaneously. Just keep loopd running. -