diff --git a/interface.go b/interface.go index 310bda33d..03b31b5b9 100644 --- a/interface.go +++ b/interface.go @@ -338,6 +338,8 @@ type StaticAddressLoopInRequest struct { // swap payment. If the timeout is reached the swap will be aborted and // the client can retry the swap if desired with different parameters. PaymentTimeoutSeconds uint32 + + SelectedAmount btcutil.Amount } // LoopInTerms are the server terms on which it executes loop in swaps. diff --git a/staticaddr/loopin/actions.go b/staticaddr/loopin/actions.go index 54d697ebc..5ed51fdc7 100644 --- a/staticaddr/loopin/actions.go +++ b/staticaddr/loopin/actions.go @@ -68,9 +68,14 @@ func (f *FSM) InitHtlcAction(ctx context.Context, } // Calculate the swap invoice amount. The server needs to pay us the - // sum of all deposits minus the fees that the server charges for the - // swap. - swapInvoiceAmt := f.loopIn.TotalDepositAmount() - f.loopIn.QuotedSwapFee + // swap amount minus the fees that the server charges for the swap. The + // swap amount is either the total value of the selected deposits, or + // the selected amount if a specific amount was requested. + swapAmount := f.loopIn.TotalDepositAmount() + if f.loopIn.SelectedAmount > 0 { + swapAmount = f.loopIn.SelectedAmount + } + swapInvoiceAmt := swapAmount - f.loopIn.QuotedSwapFee // Generate random preimage. var swapPreimage lntypes.Preimage diff --git a/staticaddr/loopin/loopin.go b/staticaddr/loopin/loopin.go index aa7c3a2da..4e743d747 100644 --- a/staticaddr/loopin/loopin.go +++ b/staticaddr/loopin/loopin.go @@ -93,6 +93,8 @@ type StaticAddressLoopIn struct { // swap. DepositOutpoints []string + SelectedAmount btcutil.Amount + // state is the current state of the swap. state fsm.StateType @@ -287,10 +289,18 @@ func (l *StaticAddressLoopIn) createHtlcTx(chainParams *chaincfg.Params, weight := l.htlcWeight() fee := feeRate.FeeForWeight(weight) - // Check if the server breaches our fee limits. - amt := float64(l.TotalDepositAmount()) - feeLimit := btcutil.Amount(amt * maxFeePercentage) + // Determine the swap amount. If the user selected a specific amount, we + // use that and use the difference to the total deposit amount as the + // change. + swapAmt := float64(l.TotalDepositAmount()) + var changeAmount btcutil.Amount + if l.SelectedAmount > 0 { + swapAmt = float64(l.SelectedAmount) + changeAmount = l.TotalDepositAmount() - l.SelectedAmount + } + // Check if the server breaches our fee limits. + feeLimit := btcutil.Amount(swapAmt * maxFeePercentage) if fee > feeLimit { return nil, fmt.Errorf("htlc tx fee %v exceeds max fee %v", fee, feeLimit) @@ -314,6 +324,14 @@ func (l *StaticAddressLoopIn) createHtlcTx(chainParams *chaincfg.Params, msgTx.AddTxOut(sweepOutput) + // We expect change to be sent back to our static address output script. + if changeAmount > 0 { + msgTx.AddTxOut(&wire.TxOut{ + Value: int64(changeAmount), + PkScript: l.AddressParams.PkScript, + }) + } + return msgTx, nil } diff --git a/staticaddr/loopin/manager.go b/staticaddr/loopin/manager.go index 150657700..d1553b976 100644 --- a/staticaddr/loopin/manager.go +++ b/staticaddr/loopin/manager.go @@ -20,7 +20,9 @@ import ( "github.com/lightninglabs/loop/staticaddr/deposit" "github.com/lightninglabs/loop/swapserverrpc" looprpc "github.com/lightninglabs/loop/swapserverrpc" + "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/routing/route" ) @@ -205,8 +207,8 @@ func (m *Manager) Run(ctx context.Context, currentHeight uint32) error { case request.respChan <- resp: case <-ctx.Done(): - // Noify subroutines that the main loop has been - // canceled. + // Notify subroutines that the main loop has + // been canceled. close(m.exitChan) return ctx.Err() @@ -549,6 +551,15 @@ func (m *Manager) initiateLoopIn(ctx context.Context, } totalDepositAmount := tmp.TotalDepositAmount() + // If the selected amount would leave a dust change output or exceeds + // the total deposits value, we return an error. + dustLimit := lnwallet.DustLimitForSize(input.P2TRSize) + if totalDepositAmount-req.SelectedAmount < dustLimit { + return nil, fmt.Errorf("selected amount %v leaves "+ + "dust or exceeds total deposit value %v", + req.SelectedAmount, totalDepositAmount) + } + // Check that the label is valid. err := labels.Validate(req.Label) if err != nil { @@ -616,6 +627,7 @@ func (m *Manager) initiateLoopIn(ctx context.Context, } swap := &StaticAddressLoopIn{ + SelectedAmount: req.SelectedAmount, DepositOutpoints: req.DepositOutpoints, Deposits: deposits, Label: req.Label,