Skip to content

Commit

Permalink
rpc: implement DecodeAssetPayReq
Browse files Browse the repository at this point in the history
In this commit, we implement the `DecodeAssetPayReq` command. This
command allows a caller to decode a normal LN invoice, adding the asset
specific information along the way. This includes the corresponding
asset unit amount, asset group information, and also the decimal display

information.
Fixes #1238
  • Loading branch information
Roasbeef committed Dec 15, 2024
1 parent 026eca3 commit e656e8e
Showing 1 changed file with 129 additions and 0 deletions.
129 changes: 129 additions & 0 deletions rpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -7729,3 +7729,132 @@ func (r *rpcServer) getInboundPolicy(ctx context.Context, chanID uint64,

return policy, nil
}

// assetInvoiceAmt calculates the amount of asset units to pay for an invoice
// which is expressed in sats.
func (r *rpcServer) assetInvoiceAmt(ctx context.Context, assetID asset.ID,
invoiceAmt lnwire.MilliSatoshi, peerPubKey *route.Vertex,
expiryTimestamp time.Time) (uint64, error) {

acceptedQuote, err := r.fetchSendRfqQuote(
ctx, assetID, invoiceAmt, peerPubKey, expiryTimestamp,
)
if err != nil {
return 0, fmt.Errorf("error sending RFQ quote: %w", err)
}

return acceptedQuote.AssetAmount, nil
}

// DecodeAssetPayReq decodes an incoming invoice, then uses the RFQ system to
// map the BTC amount to the amount of asset units for the specified asset ID.
func (r *rpcServer) DecodeAssetPayReq(ctx context.Context,
payReq *tchrpc.AssetPayReqString) (*tchrpc.AssetPayReq, error) {

// First, we'll perform some basic input validation.
switch {
case len(payReq.AssetId) == 0:
return nil, fmt.Errorf("asset ID must be specified")

case len(payReq.AssetId) != 32:
return nil, fmt.Errorf("asset ID must be 32 bytes, "+
"was %d", len(payReq.AssetId))

case len(payReq.PayReqString.PayReq) == 0:
return nil, fmt.Errorf("payment request must be specified")
}

var (
resp tchrpc.AssetPayReq
assetID asset.ID
)

copy(assetID[:], payReq.AssetId)

// With the inputs validated, we'll first call out to lnd to decode the
// payment request.
rpcCtx, _, rawClient := r.cfg.Lnd.Client.RawClientWithMacAuth(ctx)
payReqInfo, err := rawClient.DecodePayReq(rpcCtx, &lnrpc.PayReqString{
PayReq: payReq.PayReqString.PayReq,
})
if err != nil {
return nil, fmt.Errorf("unable to fetch channel: %w", err)
}

resp.PayReq = payReqInfo

// TODO(roasbeef): add dry run mode?
// * obtains quote, but doesn't actually treat as standing order

// Now that we have the basic invoice information, we'll query the RFQ
// system to obtain a quote to send this amount of BTC. Note that this
// doesn't factor in the fee limit, so this attempts just to map the
// sats amount to an asset unit.
timestamp := time.Unix(payReqInfo.Timestamp, 0)
expiryTimestamp := timestamp.Add(time.Duration(payReqInfo.Expiry))
numMsat := lnwire.NewMSatFromSatoshis(
btcutil.Amount(payReqInfo.NumSatoshis),
)
invoiceAmt, err := r.assetInvoiceAmt(
ctx, assetID, numMsat, nil,
expiryTimestamp,
)
if err != nil {
return nil, fmt.Errorf("error deriving asset amount: %w", err)
}

resp.AssetAmount = invoiceAmt

// Next, we'll fetch the information for this asset ID through the addr
// book. This'll automatically fetch the asset if needed.
assetGroup, err := r.cfg.AddrBook.QueryAssetInfo(ctx, assetID)
if err != nil {
return nil, fmt.Errorf("unable to fetch asset info for "+
"asset_id=%x: %w", assetID[:], err)
}

resp.GenesisInfo = &taprpc.GenesisInfo{
GenesisPoint: assetGroup.FirstPrevOut.String(),
AssetType: taprpc.AssetType(assetGroup.Type),
Name: assetGroup.Tag,
MetaHash: assetGroup.MetaHash[:],
AssetId: assetID[:],
}

// If this asset ID belongs to an asset group, then we'll display thiat
// information as well.
if assetGroup.GroupKey != nil {
groupInfo := assetGroup.GroupKey
resp.AssetGroup = &taprpc.AssetGroup{
RawGroupKey: groupInfo.RawKey.PubKey.SerializeCompressed(), //nolint:lll
TweakedGroupKey: groupInfo.GroupPubKey.SerializeCompressed(), //nolint:lll
TapscriptRoot: groupInfo.TapscriptRoot,
}

if len(groupInfo.Witness) != 0 {
resp.AssetGroup.AssetWitness, err = asset.SerializeGroupWitness(
groupInfo.Witness,
)
if err != nil {
return nil, err
}
}
}

// The final piece of information we need is the decimal display
// information for this asset ID.
decDisplay, err := r.DecDisplayForAssetID(ctx, assetID)
if err != nil {
return nil, err
}

resp.DecimalDisplay = fn.MapOptionZ(
decDisplay, func(d uint32) *taprpc.DecimalDisplay {
return &taprpc.DecimalDisplay{
DecimalDisplay: d,
}
},
)

return &resp, nil
}

0 comments on commit e656e8e

Please sign in to comment.