Skip to content

Commit 490768f

Browse files
committed
asset: add function to get asset price
1 parent 4da89ec commit 490768f

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

assets/client.go

Lines changed: 68 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,73 @@ 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+
peerPubkey []byte, assetAmt uint64, paymentMaxAmt btcutil.Amount) (
193+
btcutil.Amount, 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+
msatAmt := lnwire.NewMSatFromSatoshis(paymentMaxAmt)
200+
201+
// First we'll rfq a random peer for the asset.
202+
rfq, err := c.RfqClient.AddAssetSellOrder(
203+
ctx, &rfqrpc.AddAssetSellOrderRequest{
204+
AssetSpecifier: &rfqrpc.AssetSpecifier{
205+
Id: &rfqrpc.AssetSpecifier_AssetIdStr{
206+
AssetIdStr: assetID,
207+
},
208+
},
209+
PaymentMaxAmt: uint64(msatAmt),
210+
Expiry: uint64(rfqExpiry),
211+
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
212+
PeerPubKey: peerPubkey,
213+
})
214+
if err != nil {
215+
return 0, err
216+
}
217+
if rfq == nil {
218+
return 0, fmt.Errorf("no RFQ response")
219+
}
220+
221+
if rfq.GetInvalidQuote() != nil {
222+
return 0, fmt.Errorf("invalid RFQ: %v", rfq.GetInvalidQuote())
223+
}
224+
225+
if rfq.GetRejectedQuote() != nil {
226+
return 0, fmt.Errorf("rejected RFQ: %v", rfq.GetRejectedQuote())
227+
}
228+
229+
acceptedRes := rfq.GetAcceptedQuote()
230+
if acceptedRes == nil {
231+
return 0, fmt.Errorf("no accepted quote")
232+
}
233+
234+
// We'll use the accepted quote to calculate the price.
235+
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate)
236+
}
237+
238+
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount
239+
// and asset rate.
240+
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
241+
btcutil.Amount, error) {
242+
243+
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate)
244+
if err != nil {
245+
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
246+
}
247+
248+
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0)
249+
250+
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP)
251+
252+
return msatAmt.ToSatoshis(), nil
253+
}
254+
187255
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
188256
// payment.
189257
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (

assets/client_test.go

Lines changed: 45 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,47 @@ 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: 1000,
79+
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000", Scale: 0},
80+
expected: btcutil.Amount(1000000),
81+
expectError: false,
82+
},
83+
{
84+
assetAmt: 500000,
85+
assetRate: &rfqrpc.FixedPoint{Coefficient: "200000000", Scale: 0},
86+
expected: btcutil.Amount(250000),
87+
expectError: false,
88+
},
89+
{
90+
assetAmt: 0,
91+
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000000", Scale: 0},
92+
expected: btcutil.Amount(0),
93+
expectError: false,
94+
},
95+
}
96+
97+
for _, test := range tests {
98+
result, err := getSatsFromAssetAmt(test.assetAmt, test.assetRate)
99+
if test.expectError {
100+
if err == nil {
101+
t.Fatalf("expected error but got none")
102+
}
103+
} else {
104+
if err != nil {
105+
t.Fatalf("unexpected error: %v", err)
106+
}
107+
if result != test.expected {
108+
t.Fatalf("expected %v, got %v", test.expected, result)
109+
}
110+
}
111+
}
112+
}

0 commit comments

Comments
 (0)