Skip to content

Commit 4a8d69a

Browse files
authored
Merge pull request #697 from sputn1ck/instantout_quote
Instantout: Add fee estimation to CLI
2 parents c6e8664 + cf65529 commit 4a8d69a

12 files changed

+796
-224
lines changed

cmd/loop/instantout.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"strconv"
78
"strings"
@@ -97,14 +98,18 @@ func instantOut(ctx *cli.Context) error {
9798
fmt.Scanln(&answer)
9899

99100
// Parse
100-
var selectedReservations [][]byte
101+
var (
102+
selectedReservations [][]byte
103+
selectedAmt uint64
104+
)
101105
switch answer {
102106
case "ALL":
103107
for _, res := range confirmedReservations {
104108
selectedReservations = append(
105109
selectedReservations,
106110
res.ReservationId,
107111
)
112+
selectedAmt += res.Amount
108113
}
109114

110115
case "":
@@ -135,9 +140,33 @@ func instantOut(ctx *cli.Context) error {
135140
)
136141

137142
selectedIndexMap[idx] = struct{}{}
143+
selectedAmt += confirmedReservations[idx-1].Amount
138144
}
139145
}
140146

147+
// Now that we have the selected reservations we can estimate the
148+
// fee-rates.
149+
quote, err := client.InstantOutQuote(
150+
context.Background(), &looprpc.InstantOutQuoteRequest{
151+
Amt: selectedAmt,
152+
NumReservations: int32(len(selectedReservations)),
153+
},
154+
)
155+
if err != nil {
156+
return err
157+
}
158+
fmt.Println()
159+
fmt.Printf(satAmtFmt, "Estimated on-chain fee:", quote.SweepFeeSat)
160+
fmt.Printf(satAmtFmt, "Service fee:", quote.ServiceFeeSat)
161+
fmt.Println()
162+
163+
fmt.Printf("CONTINUE SWAP? (y/n): ")
164+
165+
fmt.Scanln(&answer)
166+
if answer != "y" {
167+
return errors.New("swap canceled")
168+
}
169+
141170
fmt.Println("Starting instant swap out")
142171

143172
// Now we can request the instant out swap.

instantout/manager.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@ import (
77
"sync"
88
"time"
99

10+
"github.com/btcsuite/btcd/btcutil"
1011
"github.com/lightninglabs/loop/instantout/reservation"
12+
"github.com/lightninglabs/loop/swapserverrpc"
1113
"github.com/lightningnetwork/lnd/lntypes"
1214
)
1315

@@ -199,3 +201,50 @@ func (m *Manager) GetActiveInstantOut(swapHash lntypes.Hash) (*FSM, error) {
199201

200202
return fsm, nil
201203
}
204+
205+
type Quote struct {
206+
// ServiceFee is the fee in sat that is paid to the loop service.
207+
ServiceFee btcutil.Amount
208+
209+
// OnChainFee is the estimated on chain fee in sat.
210+
OnChainFee btcutil.Amount
211+
}
212+
213+
// GetInstantOutQuote returns a quote for an instant out.
214+
func (m *Manager) GetInstantOutQuote(ctx context.Context,
215+
amt btcutil.Amount, numReservations int) (Quote, error) {
216+
217+
if numReservations <= 0 {
218+
return Quote{}, fmt.Errorf("no reservations selected")
219+
}
220+
221+
if amt <= 0 {
222+
return Quote{}, fmt.Errorf("no amount selected")
223+
}
224+
225+
// Get the service fee.
226+
quoteRes, err := m.cfg.InstantOutClient.GetInstantOutQuote(
227+
ctx, &swapserverrpc.GetInstantOutQuoteRequest{
228+
Amount: uint64(amt),
229+
},
230+
)
231+
if err != nil {
232+
return Quote{}, err
233+
}
234+
235+
// Get the offchain fee by getting the fee estimate from the lnd client
236+
// and multiplying it by the estimated sweepless sweep transaction.
237+
feeRate, err := m.cfg.Wallet.EstimateFeeRate(ctx, normalConfTarget)
238+
if err != nil {
239+
return Quote{}, err
240+
}
241+
242+
// The on chain chainFee is the chainFee rate times the estimated
243+
// sweepless sweep transaction size.
244+
chainFee := feeRate.FeeForWeight(sweeplessSweepWeight(numReservations))
245+
246+
return Quote{
247+
ServiceFee: btcutil.Amount(quoteRes.SwapFee),
248+
OnChainFee: chainFee,
249+
}, nil
250+
}

loopd/perms/perms.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,8 @@ var RequiredPermissions = map[string][]bakery.Op{
104104
Entity: "swap",
105105
Action: "execute",
106106
}},
107+
"/looprpc.SwapClient/InstantOutQuote": {{
108+
Entity: "swap",
109+
Action: "read",
110+
}},
107111
}

loopd/swapclient_server.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1209,6 +1209,25 @@ func (s *swapClientServer) InstantOut(ctx context.Context,
12091209
return res, nil
12101210
}
12111211

1212+
// InstantOutQuote returns a quote for an instant out swap with the provided
1213+
// parameters.
1214+
func (s *swapClientServer) InstantOutQuote(ctx context.Context,
1215+
req *clientrpc.InstantOutQuoteRequest) (
1216+
*clientrpc.InstantOutQuoteResponse, error) {
1217+
1218+
quote, err := s.instantOutManager.GetInstantOutQuote(
1219+
ctx, btcutil.Amount(req.Amt), int(req.NumReservations),
1220+
)
1221+
if err != nil {
1222+
return nil, err
1223+
}
1224+
1225+
return &clientrpc.InstantOutQuoteResponse{
1226+
ServiceFeeSat: int64(quote.ServiceFee),
1227+
SweepFeeSat: int64(quote.OnChainFee),
1228+
}, nil
1229+
}
1230+
12121231
func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) {
12131232
switch reason {
12141233
case liquidity.ReasonNone:

0 commit comments

Comments
 (0)