Skip to content

Commit 1094c64

Browse files
committed
staticaddr: fractional loop-in amount
1 parent 3ff44dd commit 1094c64

File tree

6 files changed

+195
-38
lines changed

6 files changed

+195
-38
lines changed

interface.go

+5
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,11 @@ 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 is the amount that the user selected for the swap. If
343+
// the user did not select an amount, the amount of all deposits is
344+
// used.
345+
SelectedAmount btcutil.Amount
341346
}
342347

343348
// 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
@@ -36,8 +36,10 @@ import (
3636
"github.com/lightninglabs/loop/swap"
3737
"github.com/lightninglabs/loop/swapserverrpc"
3838
"github.com/lightninglabs/taproot-assets/rfqmath"
39+
"github.com/lightningnetwork/lnd/input"
3940
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
4041
"github.com/lightningnetwork/lnd/lntypes"
42+
"github.com/lightningnetwork/lnd/lnwallet"
4143
"github.com/lightningnetwork/lnd/queue"
4244
"github.com/lightningnetwork/lnd/routing/route"
4345
"github.com/lightningnetwork/lnd/zpay32"
@@ -884,21 +886,26 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
884886
infof("Loop in quote request received")
885887

886888
var (
887-
numDeposits = uint32(len(req.DepositOutpoints))
888-
err error
889+
selectedAmount = btcutil.Amount(req.Amt)
890+
totalDepositAmount btcutil.Amount
891+
numDeposits = len(req.DepositOutpoints)
892+
err error
889893
)
890894

891895
htlcConfTarget, err := validateLoopInRequest(
892-
req.ConfTarget, req.ExternalHtlc, numDeposits, req.Amt,
896+
req.ConfTarget, req.ExternalHtlc, uint32(numDeposits),
897+
int64(selectedAmount),
893898
)
894899
if err != nil {
895900
return nil, err
896901
}
897902

898903
// Retrieve deposits to calculate their total value.
899904
var depositList *looprpc.ListStaticAddressDepositsResponse
900-
amount := btcutil.Amount(req.Amt)
901-
if len(req.DepositOutpoints) > 0 {
905+
906+
// If deposits are selected, we need to retrieve them to calculate the
907+
// total value which we request a quote for.
908+
if numDeposits > 0 {
902909
depositList, err = s.ListStaticAddressDeposits(
903910
ctx, &looprpc.ListStaticAddressDepositsRequest{
904911
Outpoints: req.DepositOutpoints,
@@ -913,20 +920,45 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
913920
"deposit outpoints")
914921
}
915922

916-
// The requested amount should be 0 here if the request
917-
// contained deposit outpoints.
918-
if amount != 0 && len(depositList.FilteredDeposits) > 0 {
919-
return nil, fmt.Errorf("amount should be 0 for " +
920-
"deposit quotes")
923+
if numDeposits != len(depositList.FilteredDeposits) {
924+
return nil, fmt.Errorf("expected %d deposits, got %d",
925+
numDeposits, len(depositList.FilteredDeposits))
921926
}
922927

923928
// In case we quote for deposits we send the server both the
924-
// total value and the number of deposits. This is so the server
925-
// can probe the total amount and calculate the per input fee.
926-
if amount == 0 && len(depositList.FilteredDeposits) > 0 {
927-
for _, deposit := range depositList.FilteredDeposits {
928-
amount += btcutil.Amount(deposit.Value)
929-
}
929+
// selected value and the number of deposits. This is so the
930+
// server can probe the selected value and calculate the per
931+
// input fee.
932+
for _, deposit := range depositList.FilteredDeposits {
933+
totalDepositAmount += btcutil.Amount(
934+
deposit.Value,
935+
)
936+
}
937+
938+
// If the selected amount would leave a dust change output or
939+
// exceeds the total deposits value, we return an error.
940+
dustLimit := lnwallet.DustLimitForSize(input.P2TRSize)
941+
remainingAmount := totalDepositAmount - selectedAmount
942+
switch {
943+
case remainingAmount < 0:
944+
return nil, fmt.Errorf("selected amount %v exceeds "+
945+
"total deposit value %v", selectedAmount,
946+
totalDepositAmount)
947+
948+
case remainingAmount > 0 && remainingAmount < dustLimit:
949+
return nil, fmt.Errorf("selected amount %v leaves "+
950+
"dust change %v", selectedAmount,
951+
totalDepositAmount)
952+
953+
default:
954+
// If the remaining amount is 0 or equal or greater than
955+
// the dust limit, we can proceed with the swap.
956+
}
957+
958+
// If the client didn't select an amount we quote for the total
959+
// deposits value.
960+
if selectedAmount == 0 {
961+
selectedAmount = totalDepositAmount
930962
}
931963
}
932964

@@ -953,14 +985,14 @@ func (s *swapClientServer) GetLoopInQuote(ctx context.Context,
953985
}
954986

955987
quote, err := s.impl.LoopInQuote(ctx, &loop.LoopInQuoteRequest{
956-
Amount: amount,
988+
Amount: selectedAmount,
957989
HtlcConfTarget: htlcConfTarget,
958990
ExternalHtlc: req.ExternalHtlc,
959991
LastHop: lastHop,
960992
RouteHints: routeHints,
961993
Private: req.Private,
962994
Initiator: defaultLoopdInitiator,
963-
NumDeposits: numDeposits,
995+
NumDeposits: uint32(numDeposits),
964996
})
965997
if err != nil {
966998
return nil, err
@@ -1804,6 +1836,7 @@ func (s *swapClientServer) StaticAddressLoopIn(ctx context.Context,
18041836
}
18051837

18061838
req := &loop.StaticAddressLoopInRequest{
1839+
SelectedAmount: btcutil.Amount(in.Amount),
18071840
DepositOutpoints: in.Outpoints,
18081841
MaxSwapFee: btcutil.Amount(in.MaxSwapFeeSatoshis),
18091842
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 := &swapserverrpc.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)