Skip to content

Commit 01003bf

Browse files
committed
asset: add function to get asset price
1 parent 0c3ea4e commit 01003bf

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

assets/client.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"time"
1010

1111
"github.com/btcsuite/btcd/btcutil"
12+
"github.com/lightninglabs/taproot-assets/rfqmath"
1213
"github.com/lightninglabs/taproot-assets/tapcfg"
1314
"github.com/lightninglabs/taproot-assets/taprpc"
1415
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
@@ -184,6 +185,66 @@ func (c *TapdClient) GetAssetName(ctx context.Context,
184185
return assetName, nil
185186
}
186187

188+
// GetAssetPrice returns the price of an asset in satoshis. NOTE: this currently
189+
// uses the rfq process for the asset price. A future implementation should
190+
// use a price oracle to not spam a peer.
191+
func (c *TapdClient) GetAssetPrice(ctx context.Context, assetId string,
192+
assetAmt uint64, satMinAmt btcutil.Amount) (btcutil.Amount,
193+
error) {
194+
195+
// We'll allow a short rfq expiry as we'll only use this rfq to
196+
// gauge a price.
197+
rfqExpiry := time.Now().Add(time.Minute).Unix()
198+
199+
// First we'll rfq a random peer for the asset.
200+
rfq, err := c.RfqClient.AddAssetSellOrder(
201+
ctx, &rfqrpc.AddAssetSellOrderRequest{
202+
AssetSpecifier: &rfqrpc.AssetSpecifier{
203+
Id: &rfqrpc.AssetSpecifier_AssetIdStr{
204+
AssetIdStr: assetId,
205+
},
206+
},
207+
PaymentMaxAmt: uint64(satMinAmt),
208+
Expiry: uint64(rfqExpiry),
209+
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
210+
})
211+
if err != nil {
212+
return 0, err
213+
}
214+
if rfq.GetInvalidQuote() != nil {
215+
return 0, fmt.Errorf("invalid RFQ: %v", rfq.GetInvalidQuote())
216+
}
217+
218+
if rfq.GetRejectedQuote() != nil {
219+
return 0, fmt.Errorf("rejected RFQ: %v", rfq.GetRejectedQuote())
220+
}
221+
222+
acceptedRes := rfq.GetAcceptedQuote()
223+
if acceptedRes == nil {
224+
return 0, fmt.Errorf("no accepted quote")
225+
}
226+
227+
// We'll use the accepted quote to calculate the price.
228+
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate)
229+
}
230+
231+
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount
232+
// and asset rate.
233+
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
234+
btcutil.Amount, error) {
235+
236+
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate)
237+
if err != nil {
238+
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
239+
}
240+
241+
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0)
242+
243+
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP)
244+
245+
return msatAmt.ToSatoshis(), nil
246+
}
247+
187248
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
188249
// payment.
189250
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (

assets/client_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"testing"
55

66
"github.com/btcsuite/btcd/btcutil"
7+
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
78
"github.com/lightningnetwork/lnd/lnwire"
89
)
910

@@ -65,3 +66,53 @@ func TestGetPaymentMaxAmount(t *testing.T) {
6566
}
6667
}
6768
}
69+
70+
func TestGetSatsFromAssetAmt(t *testing.T) {
71+
tests := []struct {
72+
assetAmt uint64
73+
assetRate *rfqrpc.FixedPoint
74+
expected btcutil.Amount
75+
expectError bool
76+
}{
77+
{
78+
assetAmt: 1000000,
79+
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000000", Scale: 8},
80+
expected: btcutil.Amount(1000000),
81+
expectError: false,
82+
},
83+
{
84+
assetAmt: 500000,
85+
assetRate: &rfqrpc.FixedPoint{Coefficient: "200000000", Scale: 8},
86+
expected: btcutil.Amount(1000000),
87+
expectError: false,
88+
},
89+
{
90+
assetAmt: 0,
91+
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000000", Scale: 8},
92+
expected: btcutil.Amount(0),
93+
expectError: false,
94+
},
95+
{
96+
assetAmt: 1000000,
97+
assetRate: &rfqrpc.FixedPoint{Coefficient: "0", Scale: 8},
98+
expected: btcutil.Amount(0),
99+
expectError: true,
100+
},
101+
}
102+
103+
for _, test := range tests {
104+
result, err := getSatsFromAssetAmt(test.assetAmt, test.assetRate)
105+
if test.expectError {
106+
if err == nil {
107+
t.Fatalf("expected error but got none")
108+
}
109+
} else {
110+
if err != nil {
111+
t.Fatalf("unexpected error: %v", err)
112+
}
113+
if result != test.expected {
114+
t.Fatalf("expected %v, got %v", test.expected, result)
115+
}
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)