Skip to content

Commit 3d1d859

Browse files
committed
asset: add function to get asset price
1 parent e601386 commit 3d1d859

File tree

2 files changed

+107
-0
lines changed

2 files changed

+107
-0
lines changed

assets/client.go

Lines changed: 62 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,67 @@ 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, paymentMaxAmt uint64) (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: paymentMaxAmt,
208+
Expiry: uint64(rfqExpiry),
209+
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
210+
PeerPubKey: peerPubkey,
211+
})
212+
if err != nil {
213+
return 0, err
214+
}
215+
if rfq.GetInvalidQuote() != nil {
216+
return 0, fmt.Errorf("invalid RFQ: %v", rfq.GetInvalidQuote())
217+
}
218+
219+
if rfq.GetRejectedQuote() != nil {
220+
return 0, fmt.Errorf("rejected RFQ: %v", rfq.GetRejectedQuote())
221+
}
222+
223+
acceptedRes := rfq.GetAcceptedQuote()
224+
if acceptedRes == nil {
225+
return 0, fmt.Errorf("no accepted quote")
226+
}
227+
228+
// We'll use the accepted quote to calculate the price.
229+
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate)
230+
}
231+
232+
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount
233+
// and asset rate.
234+
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
235+
btcutil.Amount, error) {
236+
237+
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate)
238+
if err != nil {
239+
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
240+
}
241+
242+
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0)
243+
244+
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP)
245+
246+
return msatAmt.ToSatoshis(), nil
247+
}
248+
187249
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the
188250
// payment.
189251
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)