Skip to content

Commit b9f6c1d

Browse files
committed
asset: add function to get asset price
1 parent 3a1d90a commit b9f6c1d

File tree

2 files changed

+109
-0
lines changed

2 files changed

+109
-0
lines changed

assets/client.go

Lines changed: 64 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,69 @@ 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.GetInvalidQuote() != nil {
218+
return 0, fmt.Errorf("invalid RFQ: %v", rfq.GetInvalidQuote())
219+
}
220+
221+
if rfq.GetRejectedQuote() != nil {
222+
return 0, fmt.Errorf("rejected RFQ: %v", rfq.GetRejectedQuote())
223+
}
224+
225+
acceptedRes := rfq.GetAcceptedQuote()
226+
if acceptedRes == nil {
227+
return 0, fmt.Errorf("no accepted quote")
228+
}
229+
230+
// We'll use the accepted quote to calculate the price.
231+
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate)
232+
}
233+
234+
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount
235+
// and asset rate.
236+
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
237+
btcutil.Amount, error) {
238+
239+
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate)
240+
if err != nil {
241+
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
242+
}
243+
244+
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0)
245+
246+
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP)
247+
248+
return msatAmt.ToSatoshis(), nil
249+
}
250+
187251
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
188252
// payment.
189253
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)