From b3fe9a9c616efa38cf896d0ae299ea00fd2b6423 Mon Sep 17 00:00:00 2001 From: sputn1ck Date: Wed, 7 Feb 2024 17:33:11 +0100 Subject: [PATCH] loopd: add instantout quote --- instantout/manager.go | 49 ++++++++++++++++++++++++++++++++++++++ loopd/perms/perms.go | 4 ++++ loopd/swapclient_server.go | 19 +++++++++++++++ 3 files changed, 72 insertions(+) diff --git a/instantout/manager.go b/instantout/manager.go index ab4aad4..1de7f5c 100644 --- a/instantout/manager.go +++ b/instantout/manager.go @@ -7,7 +7,9 @@ import ( "sync" "time" + "github.com/btcsuite/btcd/btcutil" "github.com/lightninglabs/loop/instantout/reservation" + "github.com/lightninglabs/loop/swapserverrpc" "github.com/lightningnetwork/lnd/lntypes" ) @@ -199,3 +201,50 @@ func (m *Manager) GetActiveInstantOut(swapHash lntypes.Hash) (*FSM, error) { return fsm, nil } + +type Quote struct { + // ServiceFee is the fee in sat that is paid to the loop service. + ServiceFee btcutil.Amount + + // OnChainFee is the estimated on chain fee in sat. + OnChainFee btcutil.Amount +} + +// GetInstantOutQuote returns a quote for an instant out. +func (m *Manager) GetInstantOutQuote(ctx context.Context, + amt btcutil.Amount, numReservations int) (Quote, error) { + + if numReservations <= 0 { + return Quote{}, fmt.Errorf("no reservations selected") + } + + if amt <= 0 { + return Quote{}, fmt.Errorf("no amount selected") + } + + // Get the service fee. + quoteRes, err := m.cfg.InstantOutClient.GetInstantOutQuote( + ctx, &swapserverrpc.GetInstantOutQuoteRequest{ + Amount: uint64(amt), + }, + ) + if err != nil { + return Quote{}, err + } + + // Get the offchain fee by getting the fee estimate from the lnd client + // and multiplying it by the estimated sweepless sweep transaction. + feeRate, err := m.cfg.Wallet.EstimateFeeRate(ctx, normalConfTarget) + if err != nil { + return Quote{}, err + } + + // The on chain chainFee is the chainFee rate times the estimated + // sweepless sweep transaction size. + chainFee := feeRate.FeeForWeight(sweeplessSweepWeight(numReservations)) + + return Quote{ + ServiceFee: btcutil.Amount(quoteRes.SwapFee), + OnChainFee: chainFee, + }, nil +} diff --git a/loopd/perms/perms.go b/loopd/perms/perms.go index 7ac85a2..07eafd8 100644 --- a/loopd/perms/perms.go +++ b/loopd/perms/perms.go @@ -104,4 +104,8 @@ var RequiredPermissions = map[string][]bakery.Op{ Entity: "swap", Action: "execute", }}, + "/looprpc.SwapClient/InstantOutQuote": {{ + Entity: "swap", + Action: "read", + }}, } diff --git a/loopd/swapclient_server.go b/loopd/swapclient_server.go index 7cec2ad..a8fe10f 100644 --- a/loopd/swapclient_server.go +++ b/loopd/swapclient_server.go @@ -1209,6 +1209,25 @@ func (s *swapClientServer) InstantOut(ctx context.Context, return res, nil } +// InstantOutQuote returns a quote for an instant out swap with the provided +// parameters. +func (s *swapClientServer) InstantOutQuote(ctx context.Context, + req *clientrpc.InstantOutQuoteRequest) ( + *clientrpc.InstantOutQuoteResponse, error) { + + quote, err := s.instantOutManager.GetInstantOutQuote( + ctx, btcutil.Amount(req.Amt), int(req.NumReservations), + ) + if err != nil { + return nil, err + } + + return &clientrpc.InstantOutQuoteResponse{ + ServiceFeeSat: int64(quote.ServiceFee), + SweepFeeSat: int64(quote.OnChainFee), + }, nil +} + func rpcAutoloopReason(reason liquidity.Reason) (clientrpc.AutoReason, error) { switch reason { case liquidity.ReasonNone: