Skip to content

Commit a697844

Browse files
committed
rpcserver: allow adding invoice with sats amount
The previously ignored value/value_msat fields are now properly being accounted for. We now accept only one value related field to be set.
1 parent 5010a0f commit a697844

File tree

1 file changed

+103
-17
lines changed

1 file changed

+103
-17
lines changed

rpcserver.go

Lines changed: 103 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7701,6 +7701,29 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77017701
return nil, err
77027702
}
77037703

7704+
amtMsat, err := lnrpc.UnmarshallAmt(
7705+
req.InvoiceRequest.Value, req.InvoiceRequest.ValueMsat,
7706+
)
7707+
if err != nil {
7708+
return nil, err
7709+
}
7710+
7711+
// Let's make sure that only one type of amount is set, in order to
7712+
// avoid ambiguous behavior. This field dictates the actual value of the
7713+
// invoice so let's be strict and only allow one possible value to be
7714+
// set.
7715+
if req.AssetAmount > 0 && amtMsat != 0 {
7716+
return nil, fmt.Errorf("cannot set both asset amount and sats" +
7717+
"amount")
7718+
}
7719+
7720+
// In order to avoid repeating the following check let's assign it to a
7721+
// boolean for easier access.
7722+
var satsMode bool
7723+
if amtMsat != 0 {
7724+
satsMode = true
7725+
}
7726+
77047727
// The peer public key is optional if there is only a single asset
77057728
// channel.
77067729
var peerPubKey *route.Vertex
@@ -7735,11 +7758,38 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77357758
time.Duration(expirySeconds) * time.Second,
77367759
)
77377760

7761+
maxUnits := req.AssetAmount
7762+
7763+
// If the invoice defines the desired amount in satoshis, we need to
7764+
// query our oracle first to get an estimation on the asset rate. This
7765+
// will help us establish a quote with the correct amount of asset
7766+
// units.
7767+
if satsMode {
7768+
oracleRes, err := r.cfg.PriceOracle.QueryBidPrice(
7769+
ctx, specifier, fn.None[uint64](), fn.Some(amtMsat),
7770+
fn.None[rfqmsg.AssetRate](),
7771+
)
7772+
if err != nil {
7773+
return nil, err
7774+
}
7775+
7776+
if oracleRes.Err != nil {
7777+
return nil, fmt.Errorf("cannot query oracle: %v",
7778+
oracleRes.Err.Error())
7779+
}
7780+
7781+
assetUnits := rfqmath.MilliSatoshiToUnits(
7782+
amtMsat, oracleRes.AssetRate.Rate,
7783+
)
7784+
7785+
maxUnits = assetUnits.ToUint64()
7786+
}
7787+
77387788
rpcSpecifier := marshalAssetSpecifier(specifier)
77397789

77407790
resp, err := r.AddAssetBuyOrder(ctx, &rfqrpc.AddAssetBuyOrderRequest{
77417791
AssetSpecifier: &rpcSpecifier,
7742-
AssetMaxAmt: req.AssetAmount,
7792+
AssetMaxAmt: maxUnits,
77437793
Expiry: uint64(expiryTimestamp.Unix()),
77447794
PeerPubKey: peerPubKey[:],
77457795
TimeoutSeconds: uint32(
@@ -7769,17 +7819,6 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77697819
return nil, fmt.Errorf("unexpected response type: %T", r)
77707820
}
77717821

7772-
// If the invoice is for an asset unit amount smaller than the minimal
7773-
// transportable amount, we'll return an error, as it wouldn't be
7774-
// payable by the network.
7775-
if acceptedQuote.MinTransportableUnits > req.AssetAmount {
7776-
return nil, fmt.Errorf("cannot create invoice over %d asset "+
7777-
"units, as the minimal transportable amount is %d "+
7778-
"units with the current rate of %v units/BTC",
7779-
req.AssetAmount, acceptedQuote.MinTransportableUnits,
7780-
acceptedQuote.AskAssetRate)
7781-
}
7782-
77837822
// Now that we have the accepted quote, we know the amount in Satoshi
77847823
// that we need to pay. We can now update the invoice with this amount.
77857824
//
@@ -7792,12 +7831,59 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77927831
err)
77937832
}
77947833

7795-
// Convert the asset amount into a fixed-point.
7796-
assetAmount := rfqmath.NewBigIntFixedPoint(req.AssetAmount, 0)
7834+
invUnits := req.AssetAmount
7835+
7836+
if satsMode {
7837+
// If the invoice was created over a satoshi amount, we need to
7838+
// calculate the units.
7839+
invUnits = rfqmath.MilliSatoshiToUnits(
7840+
amtMsat, *askAssetRate,
7841+
).ScaleTo(0).ToUint64()
7842+
7843+
// Now let's see if the negotiated quote can actually route the
7844+
// amount we need in msat.
7845+
maxFixedUnits := rfqmath.NewBigIntFixedPoint(
7846+
acceptedQuote.AssetMaxAmount, 0,
7847+
)
7848+
maxRoutableMsat := rfqmath.UnitsToMilliSatoshi(
7849+
maxFixedUnits, *askAssetRate,
7850+
)
7851+
7852+
if maxRoutableMsat <= amtMsat {
7853+
return nil, fmt.Errorf("cannot create invoice for %v "+
7854+
"msat, max routable amount is %v msat", amtMsat,
7855+
maxRoutableMsat)
7856+
}
7857+
}
7858+
7859+
// If the invoice is for an asset unit amount smaller than the minimal
7860+
// transportable amount, we'll return an error, as it wouldn't be
7861+
// payable by the network.
7862+
if acceptedQuote.MinTransportableUnits > invUnits {
7863+
return nil, fmt.Errorf("cannot create invoice over %d asset "+
7864+
"units, as the minimal transportable amount is %d "+
7865+
"units with the current rate of %v units/BTC",
7866+
invUnits, acceptedQuote.MinTransportableUnits,
7867+
acceptedQuote.AskAssetRate)
7868+
}
7869+
7870+
switch {
7871+
case satsMode:
7872+
amtMsat := lnwire.MilliSatoshi(req.InvoiceRequest.ValueMsat +
7873+
req.InvoiceRequest.Value*(1000))
7874+
7875+
iReq.ValueMsat = int64(amtMsat)
7876+
7877+
default:
7878+
// Convert the asset amount into a fixed-point.
7879+
assetAmount := rfqmath.NewBigIntFixedPoint(req.AssetAmount, 0)
77977880

7798-
// Calculate the invoice amount in msat.
7799-
valMsat := rfqmath.UnitsToMilliSatoshi(assetAmount, *askAssetRate)
7800-
iReq.ValueMsat = int64(valMsat)
7881+
// Calculate the invoice amount in msat.
7882+
valMsat := rfqmath.UnitsToMilliSatoshi(
7883+
assetAmount, *askAssetRate,
7884+
)
7885+
iReq.ValueMsat = int64(valMsat)
7886+
}
78017887

78027888
// The last step is to create a hop hint that includes the fake SCID of
78037889
// the quote, alongside the channel's routing policy. We need to choose

0 commit comments

Comments
 (0)