@@ -7729,9 +7729,132 @@ func (r *rpcServer) getInboundPolicy(ctx context.Context, chanID uint64,
77297729
77307730 return policy , nil
77317731}
7732+
7733+ // assetInvoiceAmt calculates the amount of asset units to pay for an invoice
7734+ // which is expressed in sats.
7735+ func (r * rpcServer ) assetInvoiceAmt (ctx context.Context , assetID asset.ID ,
7736+ invoiceAmt lnwire.MilliSatoshi , peerPubKey * route.Vertex ,
7737+ expiryTimestamp time.Time ) (uint64 , error ) {
7738+
7739+ acceptedQuote , err := r .fetchSendRfqQuote (
7740+ ctx , assetID , invoiceAmt , peerPubKey , expiryTimestamp ,
7741+ )
7742+ if err != nil {
7743+ return 0 , fmt .Errorf ("error sending RFQ quote: %w" , err )
7744+ }
7745+
7746+ return acceptedQuote .AssetAmount , nil
7747+ }
7748+
77327749// DecodeAssetPayReq decodes an incoming invoice, then uses the RFQ system to
77337750// map the BTC amount to the amount of asset units for the specified asset ID.
77347751func (r * rpcServer ) DecodeAssetPayReq (ctx context.Context ,
77357752 payReq * tchrpc.AssetPayReqString ) (* tchrpc.AssetPayReq , error ) {
7753+
7754+ // First, we'll perform some basic input validation.
7755+ switch {
7756+ case len (payReq .AssetId ) == 0 :
7757+ return nil , fmt .Errorf ("asset ID must be specified" )
7758+
7759+ case len (payReq .AssetId ) != 32 :
7760+ return nil , fmt .Errorf ("asset ID must be 32 bytes, " +
7761+ "was %d" , len (payReq .AssetId ))
7762+
7763+ case len (payReq .PayReqString .PayReq ) == 0 :
7764+ return nil , fmt .Errorf ("payment request must be specified" )
7765+ }
7766+
7767+ var (
7768+ resp tchrpc.AssetPayReq
7769+ assetID asset.ID
7770+ )
7771+
7772+ copy (assetID [:], payReq .AssetId )
7773+
7774+ // With the inputs validated, we'll first call out to lnd to decode the
7775+ // payment request.
7776+ rpcCtx , _ , rawClient := r .cfg .Lnd .Client .RawClientWithMacAuth (ctx )
7777+ payReqInfo , err := rawClient .DecodePayReq (rpcCtx , & lnrpc.PayReqString {
7778+ PayReq : payReq .PayReqString .PayReq ,
7779+ })
7780+ if err != nil {
7781+ return nil , fmt .Errorf ("unable to fetch channel: %w" , err )
7782+ }
7783+
7784+ resp .PayReq = payReqInfo
7785+
7786+ // TODO(roasbeef): add dry run mode?
7787+ // * obtains quote, but doesn't actually treat as standing order
7788+
7789+ // Now that we have the basic invoice information, we'll query the RFQ
7790+ // system to obtain a quote to send this amount of BTC. Note that this
7791+ // doesn't factor in the fee limit, so this attempts just to map the
7792+ // sats amount to an asset unit.
7793+ timestamp := time .Unix (int64 (payReqInfo .Timestamp ), 0 )
7794+ expiryTimestamp := timestamp .Add (time .Duration (payReqInfo .Expiry ))
7795+ numMsat := lnwire .NewMSatFromSatoshis (
7796+ btcutil .Amount (payReqInfo .NumSatoshis ),
7797+ )
7798+ invoiceAmt , err := r .assetInvoiceAmt (
7799+ ctx , assetID , numMsat , nil ,
7800+ expiryTimestamp ,
7801+ )
7802+ if err != nil {
7803+ return nil , fmt .Errorf ("error deriving asset amount: %w" , err )
7804+ }
7805+
7806+ resp .AssetAmount = invoiceAmt
7807+
7808+ // Next, we'll fetch the information for this asset ID through the addr
7809+ // book. This'll automatically fetch the asset if needed.
7810+ assetGroup , err := r .cfg .AddrBook .QueryAssetInfo (ctx , assetID )
7811+ if err != nil {
7812+ return nil , fmt .Errorf ("unable to fetch asset info for " +
7813+ "asset_id=%x: %w" , assetID [:], err )
7814+ }
7815+
7816+ resp .GenesisInfo = & taprpc.GenesisInfo {
7817+ GenesisPoint : assetGroup .FirstPrevOut .String (),
7818+ AssetType : taprpc .AssetType (assetGroup .Type ),
7819+ Name : assetGroup .Tag ,
7820+ MetaHash : assetGroup .MetaHash [:],
7821+ AssetId : assetID [:],
7822+ }
7823+
7824+ // If this asset ID belongs to an asset group, then we'll display thiat
7825+ // information as well.
7826+ if assetGroup .GroupKey != nil {
7827+ groupInfo := assetGroup .GroupKey
7828+ resp .AssetGroup = & taprpc.AssetGroup {
7829+ RawGroupKey : groupInfo .RawKey .PubKey .SerializeCompressed (),
7830+ TweakedGroupKey : groupInfo .GroupPubKey .SerializeCompressed (),
7831+ TapscriptRoot : groupInfo .TapscriptRoot ,
7832+ }
7833+
7834+ if len (groupInfo .Witness ) != 0 {
7835+ resp .AssetGroup .AssetWitness , err = asset .SerializeGroupWitness (
7836+ groupInfo .Witness ,
7837+ )
7838+ if err != nil {
7839+ return nil , err
7840+ }
7841+ }
7842+ }
7843+
7844+ // The final piece of information we need is the decimal display
7845+ // information for this asset ID.
7846+ decDisplay , err := r .DecDisplayForAssetID (ctx , assetID )
7847+ if err != nil {
7848+ return nil , err
7849+ }
7850+
7851+ resp .DecimalDisplay = fn .MapOptionZ (
7852+ decDisplay , func (d uint32 ) * taprpc.DecimalDisplay {
7853+ return & taprpc.DecimalDisplay {
7854+ DecimalDisplay : d ,
7855+ }
7856+ },
7857+ )
7858+
77367859 return & resp , nil
77377860}
0 commit comments