Skip to content

Commit cd6bf9b

Browse files
committed
staticaddr: fractional loop-in amount
1 parent 6077f30 commit cd6bf9b

File tree

6 files changed

+192
-38
lines changed

6 files changed

+192
-38
lines changed

interface.go

+2
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ type StaticAddressLoopInRequest struct {
338338
// swap payment. If the timeout is reached the swap will be aborted and
339339
// the client can retry the swap if desired with different parameters.
340340
PaymentTimeoutSeconds uint32
341+
342+
SelectedAmount btcutil.Amount
341343
}
342344

343345
// LoopInTerms are the server terms on which it executes loop in swaps.

loopd/swapclient_server.go

+51-18
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ import (
3434
"github.com/lightninglabs/loop/swap"
3535
"github.com/lightninglabs/loop/swapserverrpc"
3636
"github.com/lightninglabs/taproot-assets/rfqmath"
37+
"github.com/lightningnetwork/lnd/input"
3738
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
3839
"github.com/lightningnetwork/lnd/lntypes"
40+
"github.com/lightningnetwork/lnd/lnwallet"
3941
"github.com/lightningnetwork/lnd/queue"
4042
"github.com/lightningnetwork/lnd/routing/route"
4143
"github.com/lightningnetwork/lnd/zpay32"
@@ -843,21 +845,26 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
843845
infof("Loop in quote request received")
844846

845847
var (
846-
numDeposits = uint32(len(req.DepositOutpoints))
847-
err error
848+
selectedAmount = btcutil.Amount(req.Amt)
849+
totalDepositAmount btcutil.Amount
850+
numDeposits = len(req.DepositOutpoints)
851+
err error
848852
)
849853

850854
htlcConfTarget, err := validateLoopInRequest(
851-
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
855+
req.ConfTarget, req.ExternalHtlc, uint32(numDeposits),
856+
int64(selectedAmount),
852857
)
853858
if err != nil {
854859
return nil, err
855860
}
856861

857862
// Retrieve deposits to calculate their total value.
858863
var depositList *looprpc.ListStaticAddressDepositsResponse
859-
amount := btcutil.Amount(req.Amt)
860-
if len(req.DepositOutpoints) > 0 {
864+
865+
// If deposits are selected, we need to retrieve them to calculate the
866+
// total value which we request a quote for.
867+
if numDeposits > 0 {
861868
depositList, err = s.ListStaticAddressDeposits(
862869
ctx, &looprpc.ListStaticAddressDepositsRequest{
863870
Outpoints: req.DepositOutpoints,
@@ -872,20 +879,45 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
872879
"deposit outpoints")
873880
}
874881

875-
// The requested amount should be 0 here if the request
876-
// contained deposit outpoints.
877-
if amount != 0 && len(depositList.FilteredDeposits) > 0 {
878-
return nil, fmt.Errorf("amount should be 0 for " +
879-
"deposit quotes")
882+
if numDeposits != len(depositList.FilteredDeposits) {
883+
return nil, fmt.Errorf("expected %d deposits, got %d",
884+
numDeposits, len(depositList.FilteredDeposits))
880885
}
881886

882887
// In case we quote for deposits we send the server both the
883-
// total value and the number of deposits. This is so the server
884-
// can probe the total amount and calculate the per input fee.
885-
if amount == 0 && len(depositList.FilteredDeposits) > 0 {
886-
for _, deposit := range depositList.FilteredDeposits {
887-
amount += btcutil.Amount(deposit.Value)
888-
}
888+
// selected value and the number of deposits. This is so the
889+
// server can probe the selected value and calculate the per
890+
// input fee.
891+
for _, deposit := range depositList.FilteredDeposits {
892+
totalDepositAmount += btcutil.Amount(
893+
deposit.Value,
894+
)
895+
}
896+
897+
// If the selected amount would leave a dust change output or
898+
// exceeds the total deposits value, we return an error.
899+
dustLimit := lnwallet.DustLimitForSize(input.P2TRSize)
900+
remainingAmount := totalDepositAmount - selectedAmount
901+
switch {
902+
case remainingAmount < 0:
903+
return nil, fmt.Errorf("selected amount %v exceeds "+
904+
"total deposit value %v", selectedAmount,
905+
totalDepositAmount)
906+
907+
case remainingAmount > 0 && remainingAmount < dustLimit:
908+
return nil, fmt.Errorf("selected amount %v leaves "+
909+
"dust change %v", selectedAmount,
910+
totalDepositAmount)
911+
912+
default:
913+
// If the remaining amount is 0 or equal or greater than
914+
// the dust limit, we can proceed with the swap.
915+
}
916+
917+
// If the client didn't select an amount we quote for the total
918+
// deposits value.
919+
if selectedAmount == 0 {
920+
selectedAmount = totalDepositAmount
889921
}
890922
}
891923

@@ -912,14 +944,14 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
912944
}
913945

914946
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
915-
Amount: amount,
947+
Amount: selectedAmount,
916948
HtlcConfTarget: htlcConfTarget,
917949
ExternalHtlc: req.ExternalHtlc,
918950
LastHop: lastHop,
919951
RouteHints: routeHints,
920952
Private: req.Private,
921953
Initiator: defaultLoopdInitiator,
922-
NumDeposits: numDeposits,
954+
NumDeposits: uint32(numDeposits),
923955
})
924956
if err != nil {
925957
return nil, err
@@ -1763,6 +1795,7 @@ func (s *swapClientServer) StaticAddressLoopIn(ctx context.Context,
17631795
}
17641796

17651797
req := &loop.StaticAddressLoopInRequest{
1798+
SelectedAmount: btcutil.Amount(in.Amount),
17661799
DepositOutpoints: in.Outpoints,
17671800
MaxSwapFee: btcutil.Amount(in.MaxSwapFeeSatoshis),
17681801
Label: in.Label,

staticaddr/loopin/actions.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,14 @@ func (f *FSM) InitHtlcAction(ctx context.Context,
6868
}
6969

7070
// Calculate the swap invoice amount. The server needs to pay us the
71-
// sum of all deposits minus the fees that the server charges for the
72-
// swap.
73-
swapInvoiceAmt := f.loopIn.TotalDepositAmount() - f.loopIn.QuotedSwapFee
71+
// swap amount minus the fees that the server charges for the swap. The
72+
// swap amount is either the total value of the selected deposits, or
73+
// the selected amount if a specific amount was requested.
74+
swapAmount := f.loopIn.TotalDepositAmount()
75+
if f.loopIn.SelectedAmount > 0 {
76+
swapAmount = f.loopIn.SelectedAmount
77+
}
78+
swapInvoiceAmt := swapAmount - f.loopIn.QuotedSwapFee
7479

7580
// Generate random preimage.
7681
var swapPreimage lntypes.Preimage
@@ -120,6 +125,7 @@ func (f *FSM) InitHtlcAction(ctx context.Context,
120125
loopInReq := &looprpc.ServerStaticAddressLoopInRequest{
121126
SwapHash: f.loopIn.SwapHash[:],
122127
DepositOutpoints: f.loopIn.DepositOutpoints,
128+
Amount: uint64(f.loopIn.SelectedAmount),
123129
HtlcClientPubKey: f.loopIn.ClientPubkey.SerializeCompressed(),
124130
SwapInvoice: f.loopIn.SwapInvoice,
125131
ProtocolVersion: version.CurrentRPCProtocolVersion(),
@@ -204,7 +210,7 @@ func (f *FSM) InitHtlcAction(ctx context.Context,
204210
// We need to defend against the server setting high fees for the htlc
205211
// tx since we might have to sweep the timeout path. We maximally allow
206212
// a configured percentage of the swap value to be spent on fees.
207-
amt := float64(f.loopIn.TotalDepositAmount())
213+
amt := float64(swapAmount)
208214
maxHtlcTxFee := btcutil.Amount(amt *
209215
f.cfg.MaxStaticAddrHtlcFeePercentage)
210216

staticaddr/loopin/loopin.go

+43-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package loopin
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
@@ -93,6 +94,11 @@ type StaticAddressLoopIn struct {
9394
// swap.
9495
DepositOutpoints []string
9596

97+
// SelectedAmount is the amount that the user selected for the swap. If
98+
// the user did not select an amount, the amount of all deposits is
99+
// used.
100+
SelectedAmount btcutil.Amount
101+
96102
// state is the current state of the swap.
97103
state fsm.StateType
98104

@@ -287,10 +293,20 @@ func (l *StaticAddressLoopIn) createHtlcTx(chainParams *chaincfg.Params,
287293
weight := l.htlcWeight()
288294
fee := feeRate.FeeForWeight(weight)
289295

290-
// Check if the server breaches our fee limits.
291-
amt := float64(l.TotalDepositAmount())
292-
feeLimit := btcutil.Amount(amt * maxFeePercentage)
296+
// Determine the swap amount. If the user selected a specific amount, we
297+
// use that and use the difference to the total deposit amount as the
298+
// change.
299+
var (
300+
swapAmt = l.TotalDepositAmount()
301+
changeAmount btcutil.Amount
302+
)
303+
if l.SelectedAmount > 0 {
304+
swapAmt = l.SelectedAmount
305+
changeAmount = l.TotalDepositAmount() - l.SelectedAmount
306+
}
293307

308+
// Check if the server breaches our fee limits.
309+
feeLimit := btcutil.Amount(float64(swapAmt) * maxFeePercentage)
294310
if fee > feeLimit {
295311
return nil, fmt.Errorf("htlc tx fee %v exceeds max fee %v",
296312
fee, feeLimit)
@@ -308,12 +324,20 @@ func (l *StaticAddressLoopIn) createHtlcTx(chainParams *chaincfg.Params,
308324

309325
// Create the sweep output
310326
sweepOutput := &wire.TxOut{
311-
Value: int64(l.TotalDepositAmount()) - int64(fee),
327+
Value: int64(swapAmt - fee),
312328
PkScript: pkscript,
313329
}
314330

315331
msgTx.AddTxOut(sweepOutput)
316332

333+
// We expect change to be sent back to our static address output script.
334+
if changeAmount > 0 {
335+
msgTx.AddTxOut(&wire.TxOut{
336+
Value: int64(changeAmount),
337+
PkScript: l.AddressParams.PkScript,
338+
})
339+
}
340+
317341
return msgTx, nil
318342
}
319343

@@ -373,11 +397,25 @@ func (l *StaticAddressLoopIn) createHtlcSweepTx(ctx context.Context,
373397
return nil, err
374398
}
375399

400+
// Check if the htlc tx has a change output. If so we need to select the
401+
// non-change output index to construct the sweep with.
402+
htlcInputIndex := uint32(0)
403+
if len(htlcTx.TxOut) == 2 {
404+
// If the first htlc tx output matches our static address
405+
// script we need to select the second output to sweep from.
406+
if bytes.Equal(
407+
htlcTx.TxOut[0].PkScript, l.AddressParams.PkScript,
408+
) {
409+
410+
htlcInputIndex = 1
411+
}
412+
}
413+
376414
// Add the htlc input.
377415
sweepTx.AddTxIn(&wire.TxIn{
378416
PreviousOutPoint: wire.OutPoint{
379417
Hash: htlcTx.TxHash(),
380-
Index: 0,
418+
Index: htlcInputIndex,
381419
},
382420
SignatureScript: htlc.SigScript,
383421
Sequence: htlc.SuccessSequence(),

0 commit comments

Comments
 (0)