Skip to content

Commit bb859c5

Browse files
authored
Merge pull request #876 from sputn1ck/asset_loop_out_fixes
Minor asset loop out fixes
2 parents 684db85 + d0191d2 commit bb859c5

File tree

10 files changed

+825
-413
lines changed

10 files changed

+825
-413
lines changed

assets/client.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"github.com/lightninglabs/taproot-assets/taprpc/tapchannelrpc"
1717
"github.com/lightninglabs/taproot-assets/taprpc/universerpc"
1818
"github.com/lightningnetwork/lnd/lnrpc"
19+
"github.com/lightningnetwork/lnd/lnwire"
1920
"github.com/lightningnetwork/lnd/macaroons"
2021
"google.golang.org/grpc"
2122
"google.golang.org/grpc/credentials"
@@ -105,9 +106,12 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
105106
expiry int64, feeLimitMultiplier float64) (
106107
*rfqrpc.PeerAcceptedSellQuote, error) {
107108

108-
feeLimit, err := lnrpc.UnmarshallAmt(
109-
int64(satAmount)+int64(satAmount.MulF64(feeLimitMultiplier)), 0,
110-
)
109+
// paymentMaxAmt is the maximum amount we are willing to pay for the
110+
// payment.
111+
// E.g. on a 250k sats payment we'll multiply the sat amount by 1.2.
112+
// The resulting maximum amount we're willing to pay is 300k sats.
113+
// The response asset amount will be for those 300k sats.
114+
paymentMaxAmt, err := getPaymentMaxAmount(satAmount, feeLimitMultiplier)
111115
if err != nil {
112116
return nil, err
113117
}
@@ -120,7 +124,7 @@ func (c *TapdClient) GetRfqForAsset(ctx context.Context,
120124
},
121125
},
122126
PeerPubKey: peerPubkey,
123-
PaymentMaxAmt: uint64(feeLimit),
127+
PaymentMaxAmt: uint64(paymentMaxAmt),
124128
Expiry: uint64(expiry),
125129
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
126130
})
@@ -180,6 +184,28 @@ func (c *TapdClient) GetAssetName(ctx context.Context,
180184
return assetName, nil
181185
}
182186

187+
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
188+
// payment.
189+
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
190+
lnwire.MilliSatoshi, error) {
191+
192+
if satAmount == 0 {
193+
return 0, fmt.Errorf("satAmount cannot be zero")
194+
}
195+
if feeLimitMultiplier < 1 {
196+
return 0, fmt.Errorf("feeLimitMultiplier must be at least 1")
197+
}
198+
199+
// paymentMaxAmt is the maximum amount we are willing to pay for the
200+
// payment.
201+
// E.g. on a 250k sats payment we'll multiply the sat amount by 1.2.
202+
// The resulting maximum amount we're willing to pay is 300k sats.
203+
// The response asset amount will be for those 300k sats.
204+
return lnrpc.UnmarshallAmt(
205+
int64(satAmount.MulF64(feeLimitMultiplier)), 0,
206+
)
207+
}
208+
183209
func getClientConn(config *TapdConfig) (*grpc.ClientConn, error) {
184210
// Load the specified TLS certificate and build transport credentials.
185211
creds, err := credentials.NewClientTLSFromFile(config.TLSPath, "")

assets/client_test.go

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package assets
2+
3+
import (
4+
"testing"
5+
6+
"github.com/btcsuite/btcd/btcutil"
7+
"github.com/lightningnetwork/lnd/lnwire"
8+
)
9+
10+
func TestGetPaymentMaxAmount(t *testing.T) {
11+
tests := []struct {
12+
satAmount btcutil.Amount
13+
feeLimitMultiplier float64
14+
expectedAmount lnwire.MilliSatoshi
15+
expectError bool
16+
}{
17+
{
18+
satAmount: btcutil.Amount(250000),
19+
feeLimitMultiplier: 1.2,
20+
expectedAmount: lnwire.MilliSatoshi(300000000),
21+
expectError: false,
22+
},
23+
{
24+
satAmount: btcutil.Amount(100000),
25+
feeLimitMultiplier: 1.5,
26+
expectedAmount: lnwire.MilliSatoshi(150000000),
27+
expectError: false,
28+
},
29+
{
30+
satAmount: btcutil.Amount(50000),
31+
feeLimitMultiplier: 2.0,
32+
expectedAmount: lnwire.MilliSatoshi(100000000),
33+
expectError: false,
34+
},
35+
{
36+
satAmount: btcutil.Amount(0),
37+
feeLimitMultiplier: 1.2,
38+
expectedAmount: lnwire.MilliSatoshi(0),
39+
expectError: true,
40+
},
41+
{
42+
satAmount: btcutil.Amount(250000),
43+
feeLimitMultiplier: 0.8,
44+
expectedAmount: lnwire.MilliSatoshi(0),
45+
expectError: true,
46+
},
47+
}
48+
49+
for _, test := range tests {
50+
result, err := getPaymentMaxAmount(
51+
test.satAmount, test.feeLimitMultiplier,
52+
)
53+
if test.expectError {
54+
if err == nil {
55+
t.Fatalf("expected error but got none")
56+
}
57+
} else {
58+
if err != nil {
59+
t.Fatalf("unexpected error: %v", err)
60+
}
61+
if result != test.expectedAmount {
62+
t.Fatalf("expected %v, got %v",
63+
test.expectedAmount, result)
64+
}
65+
}
66+
}
67+
}

client.go

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/lightninglabs/loop/sweep"
2121
"github.com/lightninglabs/loop/sweepbatcher"
2222
"github.com/lightninglabs/loop/utils"
23+
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
2324
"github.com/lightningnetwork/lnd/lntypes"
2425
"github.com/lightningnetwork/lnd/routing/route"
2526
"google.golang.org/grpc"
@@ -661,54 +662,12 @@ func (s *Client) LoopOutQuote(ctx context.Context,
661662
// If we use an Asset we'll rfq to get the asset amounts to use for
662663
// the swap.
663664
if request.AssetRFQRequest != nil {
664-
rfqReq := request.AssetRFQRequest
665-
if rfqReq.Expiry == 0 {
666-
rfqReq.Expiry = time.Now().Add(defaultRFQExpiry).Unix()
667-
}
668-
669-
if rfqReq.MaxLimitMultiplier == 0 {
670-
rfqReq.MaxLimitMultiplier = defaultRFQMaxLimitMultiplier
671-
}
672-
673-
// First we'll get the prepay rfq.
674-
prepayRfq, err := s.assetClient.GetRfqForAsset(
675-
ctx, quote.PrepayAmount, rfqReq.AssetId,
676-
rfqReq.AssetEdgeNode, rfqReq.Expiry,
677-
rfqReq.MaxLimitMultiplier,
678-
)
679-
if err != nil {
680-
return nil, err
681-
}
682-
683-
// The actual invoice swap amount is the requested amount plus
684-
// the swap fee minus the prepay amount.
685-
invoiceAmt := request.Amount + quote.SwapFee -
686-
quote.PrepayAmount
687-
688-
swapRfq, err := s.assetClient.GetRfqForAsset(
689-
ctx, invoiceAmt, rfqReq.AssetId,
690-
rfqReq.AssetEdgeNode, rfqReq.Expiry,
691-
rfqReq.MaxLimitMultiplier,
692-
)
665+
rfq, err := s.getAssetRfq(ctx, loopOutQuote, request)
693666
if err != nil {
694667
return nil, err
695668
}
696669

697-
// We'll also want the asset name to verify for the client.
698-
assetName, err := s.assetClient.GetAssetName(
699-
ctx, rfqReq.AssetId,
700-
)
701-
if err != nil {
702-
return nil, err
703-
}
704-
705-
loopOutQuote.LoopOutRfq = &LoopOutRfq{
706-
PrepayRfqId: prepayRfq.Id,
707-
PrepayAssetAmt: prepayRfq.AssetAmount,
708-
SwapRfqId: swapRfq.Id,
709-
SwapAssetAmt: swapRfq.AssetAmount,
710-
AssetName: assetName,
711-
}
670+
loopOutQuote.LoopOutRfq = rfq
712671
}
713672

714673
return loopOutQuote, nil
@@ -1000,3 +959,77 @@ func (s *Client) AbandonSwap(ctx context.Context,
1000959

1001960
return nil
1002961
}
962+
963+
// getAssetRfq returns a prepay and swap rfq for the asset swap.
964+
func (s *Client) getAssetRfq(ctx context.Context, quote *LoopOutQuote,
965+
request *LoopOutQuoteRequest) (*LoopOutRfq, error) {
966+
967+
if s.assetClient == nil {
968+
return nil, errors.New("asset client must be set " +
969+
"when trying to loop out with an asset")
970+
}
971+
rfqReq := request.AssetRFQRequest
972+
if rfqReq.Expiry == 0 {
973+
rfqReq.Expiry = time.Now().Add(defaultRFQExpiry).Unix()
974+
}
975+
976+
if rfqReq.MaxLimitMultiplier == 0 {
977+
rfqReq.MaxLimitMultiplier = defaultRFQMaxLimitMultiplier
978+
}
979+
980+
// First we'll get the prepay rfq.
981+
prepayRfq, err := s.assetClient.GetRfqForAsset(
982+
ctx, quote.PrepayAmount, rfqReq.AssetId,
983+
rfqReq.AssetEdgeNode, rfqReq.Expiry,
984+
rfqReq.MaxLimitMultiplier,
985+
)
986+
if err != nil {
987+
return nil, err
988+
}
989+
990+
prepayAssetRate, err := rfqrpc.UnmarshalFixedPoint(
991+
prepayRfq.BidAssetRate,
992+
)
993+
if err != nil {
994+
return nil, err
995+
}
996+
997+
// The actual invoice swap amount is the requested amount plus
998+
// the swap fee minus the prepay amount.
999+
invoiceAmt := request.Amount + quote.SwapFee -
1000+
quote.PrepayAmount
1001+
1002+
swapRfq, err := s.assetClient.GetRfqForAsset(
1003+
ctx, invoiceAmt, rfqReq.AssetId,
1004+
rfqReq.AssetEdgeNode, rfqReq.Expiry,
1005+
rfqReq.MaxLimitMultiplier,
1006+
)
1007+
if err != nil {
1008+
return nil, err
1009+
}
1010+
1011+
swapAssetRate, err := rfqrpc.UnmarshalFixedPoint(
1012+
swapRfq.BidAssetRate,
1013+
)
1014+
if err != nil {
1015+
return nil, err
1016+
}
1017+
1018+
// We'll also want the asset name to verify for the client.
1019+
assetName, err := s.assetClient.GetAssetName(
1020+
ctx, rfqReq.AssetId,
1021+
)
1022+
if err != nil {
1023+
return nil, err
1024+
}
1025+
1026+
return &LoopOutRfq{
1027+
PrepayRfqId: prepayRfq.Id,
1028+
MaxPrepayAssetAmt: prepayRfq.AssetAmount,
1029+
PrepayAssetRate: prepayAssetRate,
1030+
SwapRfqId: swapRfq.Id,
1031+
MaxSwapAssetAmt: swapRfq.AssetAmount,
1032+
SwapAssetRate: swapAssetRate,
1033+
AssetName: assetName,
1034+
}, nil
1035+
}

cmd/loop/main.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,13 @@ const (
103103
// Amount: 50 USD
104104
assetAmtFmt = "%-36s %12d %s\n"
105105

106+
// rateFmt formats an exchange rate into a one line string, intended to
107+
// prettify the terminal output. For Instance,
108+
// fmt.Printf(f, "Exchange rate:", rate, "USD")
109+
// prints out as,
110+
// Exchange rate: 0.0002 USD/SAT
111+
rateFmt = "%-36s %12.4f %s/SAT\n"
112+
106113
// blkFmt formats the number of blocks into a one line string, intended
107114
// to prettify the terminal output. For Instance,
108115
// fmt.Printf(f, "Conf target", target)

cmd/loop/quote.go

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"github.com/btcsuite/btcd/btcutil"
1010
"github.com/lightninglabs/loop"
1111
"github.com/lightninglabs/loop/looprpc"
12+
"github.com/lightninglabs/taproot-assets/rfqmath"
13+
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
14+
"github.com/lightningnetwork/lnd/lnwire"
1215
"github.com/lightningnetwork/lnd/routing/route"
1316
"github.com/urfave/cli"
1417
)
@@ -268,8 +271,20 @@ func printQuoteOutResp(req *looprpc.QuoteRequest,
268271
totalFee := resp.HtlcSweepFeeSat + resp.SwapFeeSat
269272

270273
if resp.AssetRfqInfo != nil {
274+
assetAmtSwap, err := getAssetAmt(
275+
req.Amt, resp.AssetRfqInfo.SwapAssetRate,
276+
)
277+
if err != nil {
278+
fmt.Printf("Error converting asset amount: %v\n", err)
279+
return
280+
}
281+
exchangeRate := float64(assetAmtSwap) / float64(req.Amt)
271282
fmt.Printf(assetAmtFmt, "Send off-chain:",
272-
resp.AssetRfqInfo.SwapAssetAmt,
283+
assetAmtSwap, resp.AssetRfqInfo.AssetName)
284+
fmt.Printf(rateFmt, "Exchange rate:",
285+
exchangeRate, resp.AssetRfqInfo.AssetName)
286+
fmt.Printf(assetAmtFmt, "Limit Send off-chain:",
287+
resp.AssetRfqInfo.MaxSwapAssetAmt,
273288
resp.AssetRfqInfo.AssetName)
274289
} else {
275290
fmt.Printf(satAmtFmt, "Send off-chain:", req.Amt)
@@ -288,8 +303,18 @@ func printQuoteOutResp(req *looprpc.QuoteRequest,
288303
fmt.Printf(satAmtFmt, "Estimated total fee:", totalFee)
289304
fmt.Println()
290305
if resp.AssetRfqInfo != nil {
306+
assetAmtPrepay, err := getAssetAmt(
307+
resp.PrepayAmtSat, resp.AssetRfqInfo.PrepayAssetRate,
308+
)
309+
if err != nil {
310+
fmt.Printf("Error converting asset amount: %v\n", err)
311+
return
312+
}
291313
fmt.Printf(assetAmtFmt, "No show penalty (prepay):",
292-
resp.AssetRfqInfo.PrepayAssetAmt,
314+
assetAmtPrepay,
315+
resp.AssetRfqInfo.AssetName)
316+
fmt.Printf(assetAmtFmt, "Limit no show penalty (prepay):",
317+
resp.AssetRfqInfo.MaxPrepayAssetAmt,
293318
resp.AssetRfqInfo.AssetName)
294319
} else {
295320
fmt.Printf(satAmtFmt, "No show penalty (prepay):",
@@ -302,3 +327,33 @@ func printQuoteOutResp(req *looprpc.QuoteRequest,
302327
time.Unix(int64(req.SwapPublicationDeadline), 0),
303328
)
304329
}
330+
331+
// getAssetAmt returns the asset amount for the given amount in satoshis and
332+
// the asset rate.
333+
func getAssetAmt(amt int64, assetRate *looprpc.FixedPoint) (
334+
uint64, error) {
335+
336+
askAssetRate, err := unmarshalFixedPoint(assetRate)
337+
if err != nil {
338+
return 0, err
339+
}
340+
341+
msatAmt := lnwire.MilliSatoshi((amt * 1000))
342+
343+
assetAmt := rfqmath.MilliSatoshiToUnits(msatAmt, *askAssetRate)
344+
345+
return assetAmt.ToUint64(), nil
346+
}
347+
348+
// unmarshalFixedPoint converts an RPC FixedPoint to a BigIntFixedPoint.
349+
func unmarshalFixedPoint(fp *looprpc.FixedPoint) (*rfqmath.BigIntFixedPoint,
350+
error) {
351+
352+
// convert the looprpc.FixedPoint to a rfqrpc.FixedPoint
353+
rfqrpcFP := &rfqrpc.FixedPoint{
354+
Coefficient: fp.Coefficient,
355+
Scale: fp.Scale,
356+
}
357+
358+
return rfqrpc.UnmarshalFixedPoint(rfqrpcFP)
359+
}

0 commit comments

Comments
 (0)