|
9 | 9 | "time"
|
10 | 10 |
|
11 | 11 | "github.com/btcsuite/btcd/btcutil"
|
| 12 | + "github.com/lightninglabs/taproot-assets/rfqmath" |
12 | 13 | "github.com/lightninglabs/taproot-assets/tapcfg"
|
13 | 14 | "github.com/lightninglabs/taproot-assets/taprpc"
|
14 | 15 | "github.com/lightninglabs/taproot-assets/taprpc/priceoraclerpc"
|
@@ -184,6 +185,75 @@ func (c *TapdClient) GetAssetName(ctx context.Context,
|
184 | 185 | return assetName, nil
|
185 | 186 | }
|
186 | 187 |
|
| 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("peer %v sent an invalid quote response %v for "+ |
| 223 | + "asset %v", peerPubkey, rfq.GetInvalidQuote(), assetID) |
| 224 | + } |
| 225 | + |
| 226 | + if rfq.GetRejectedQuote() != nil { |
| 227 | + return 0, fmt.Errorf("peer %v rejected the quote request for "+ |
| 228 | + "asset %v, %v", peerPubkey, assetID, rfq.GetRejectedQuote()) |
| 229 | + } |
| 230 | + |
| 231 | + acceptedRes := rfq.GetAcceptedQuote() |
| 232 | + if acceptedRes == nil { |
| 233 | + return 0, fmt.Errorf("no accepted quote") |
| 234 | + } |
| 235 | + |
| 236 | + // We'll use the accepted quote to calculate the price. |
| 237 | + return getSatsFromAssetAmt(assetAmt, acceptedRes.BidAssetRate) |
| 238 | +} |
| 239 | + |
| 240 | +// getSatsFromAssetAmt returns the amount in satoshis for the given asset amount |
| 241 | +// and asset rate. |
| 242 | +func getSatsFromAssetAmt(assetAmt uint64, assetRate *rfqrpc.FixedPoint) ( |
| 243 | + btcutil.Amount, error) { |
| 244 | + |
| 245 | + rateFP, err := rfqrpc.UnmarshalFixedPoint(assetRate) |
| 246 | + if err != nil { |
| 247 | + return 0, fmt.Errorf("cannot unmarshal asset rate: %w", err) |
| 248 | + } |
| 249 | + |
| 250 | + assetUnits := rfqmath.NewBigIntFixedPoint(assetAmt, 0) |
| 251 | + |
| 252 | + msatAmt := rfqmath.UnitsToMilliSatoshi(assetUnits, *rateFP) |
| 253 | + |
| 254 | + return msatAmt.ToSatoshis(), nil |
| 255 | +} |
| 256 | + |
187 | 257 | // getPaymentMaxAmount returns the milisat amount we are willing to pay for the
|
188 | 258 | // payment.
|
189 | 259 | func getPaymentMaxAmount(satAmount btcutil.Amount, feeLimitMultiplier float64) (
|
|
0 commit comments