Skip to content

Commit 2f24d76

Browse files
committed
rpc: implement DecodeAssetInvoice
In this commit, we implement the `DecodeAssetInvoice` 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
1 parent ad994f1 commit 2f24d76

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

rpcserver.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
77347751
func (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

Comments
 (0)