asset: add function to get asset price

This commit is contained in:
sputn1ck 2025-02-17 15:55:05 +01:00
parent 3c79e9d028
commit 9f7249f14f
No known key found for this signature in database
GPG key ID: 671103D881A5F0E4
2 changed files with 110 additions and 0 deletions

View file

@ -9,6 +9,7 @@ import (
"time" "time"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/taproot-assets/rfqmath"
"github.com/lightninglabs/taproot-assets/tapcfg" "github.com/lightninglabs/taproot-assets/tapcfg"
"github.com/lightninglabs/taproot-assets/taprpc" "github.com/lightninglabs/taproot-assets/taprpc"
"github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc" "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
@ -184,6 +185,75 @@ func (c *TapdClient) GetAssetName(ctx context.Context,
return assetName, nil return assetName, nil
} }
// GetAssetPrice returns the price of an asset in satoshis. NOTE: this currently
// uses the rfq process for the asset price. A future implementation should
// use a price oracle to not spam a peer.
func (c *TapdClient) GetAssetPrice(ctx context.Context, assetID string,
peerPubkey []byte, assetAmt uint64, paymentMaxAmt btcutil.Amount) (
btcutil.Amount, error) {
// We'll allow a short rfq expiry as we'll only use this rfq to
// gauge a price.
rfqExpiry := time.Now().Add(time.Minute).Unix()
msatAmt := lnwire.NewMSatFromSatoshis(paymentMaxAmt)
// First we'll rfq a random peer for the asset.
rfq, err := c.RfqClient.AddAssetSellOrder(
ctx, &rfqrpc.AddAssetSellOrderRequest{
AssetSpecifier: &rfqrpc.AssetSpecifier{
Id: &rfqrpc.AssetSpecifier_AssetIdStr{
AssetIdStr: assetID,
},
},
PaymentMaxAmt: uint64(msatAmt),
Expiry: uint64(rfqExpiry),
TimeoutSeconds: uint32(c.cfg.RFQtimeout.Seconds()),
PeerPubKey: peerPubkey,
})
if err != nil {
return 0, err
}
if rfq == nil {
return 0, fmt.Errorf("no RFQ response")
}
if rfq.GetInvalidQuote() != nil {
return 0, fmt.Errorf("peer %v sent an invalid quote response %v for "+
"asset %v", peerPubkey, rfq.GetInvalidQuote(), assetID)
}
if rfq.GetRejectedQuote() != nil {
return 0, fmt.Errorf("peer %v rejected the quote request for "+
"asset %v, %v", peerPubkey, assetID, rfq.GetRejectedQuote())
}
acceptedRes := rfq.GetAcceptedQuote()
if acceptedRes == nil {
return 0, fmt.Errorf("no accepted quote")
}
// We'll use the accepted quote to calculate the price.
return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate)
}
// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount
// and asset rate.
func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) (
btcutil.Amount, error) {
rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate)
if err != nil {
return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err)
}
assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0)
msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP)
return msatAmt.ToSatoshis(), nil
}
// getPaymentMaxAmount returns the milisat amount we are willing to pay for the // getPaymentMaxAmount returns the milisat amount we are willing to pay for the
// payment. // payment.
func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) ( func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (

View file

@ -4,7 +4,9 @@ import (
"testing" "testing"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/taproot-assets/taprpc/rfqrpc"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/stretchr/testify/require"
) )
func TestGetPaymentMaxAmount(t *testing.T) { func TestGetPaymentMaxAmount(t *testing.T) {
@ -65,3 +67,41 @@ func TestGetPaymentMaxAmount(t *testing.T) {
} }
} }
} }
func TestGetSatsFromAssetAmt(t *testing.T) {
tests := []struct {
assetAmt uint64
assetRate *rfqrpc.FixedPoint
expected btcutil.Amount
expectError bool
}{
{
assetAmt: 1000,
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000", Scale: 0},
expected: btcutil.Amount(1000000),
expectError: false,
},
{
assetAmt: 500000,
assetRate: &rfqrpc.FixedPoint{Coefficient: "200000000", Scale: 0},
expected: btcutil.Amount(250000),
expectError: false,
},
{
assetAmt: 0,
assetRate: &rfqrpc.FixedPoint{Coefficient: "100000000", Scale: 0},
expected: btcutil.Amount(0),
expectError: false,
},
}
for _, test := range tests {
result, err := getSatsFromAssetAmt(test.assetAmt, test.assetRate)
if test.expectError {
require.NotNil(t, err)
} else {
require.Nil(t, err)
require.Equal(t, test.expected, result)
}
}
}