@@ -7717,13 +7717,6 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77177717 "amount" )
77187718 }
77197719
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-
77277720 // The peer public key is optional if there is only a single asset
77287721 // channel.
77297722 var peerPubKey * route.Vertex
@@ -7758,60 +7751,14 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77587751 time .Duration (expirySeconds ) * time .Second ,
77597752 )
77607753
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-
7788- // Since we used a different oracle price query above calculate the max
7789- // amount of units, we want to add some breathing room to account for
7790- // price fluctuations caused by the small time delay, plus the fact that
7791- // the agreed upon quote may be different. If we don't add this safety
7792- // window the peer may allow a routable amount that evaluates to less
7793- // than what we ask for.
7794- tolerance := rfqmath .NewBigIntFromUint64 (
7754+ maxUnits , err := calculateAssetMaxAmount (
7755+ ctx , r .cfg .PriceOracle , specifier , req .AssetAmount , iReq ,
77957756 r .cfg .RfqManager .GetPriceDeviationPpm (),
77967757 )
7797-
7798- // Parse the max asset units to the rfqmath type.
7799- maxMathUnits := rfqmath .NewBigIntFromUint64 (maxUnits )
7800-
7801- // Calculate the tolerance margin.
7802- toleranceUnits := maxMathUnits .Mul (tolerance ).Div (
7803- rfqmath .NewBigIntFromUint64 (1_000_000 ),
7804- )
7805-
7806- // Apply the tolerance margin twice. Once due to the ask/bid price
7807- // deviation that may occur during rfq negotiation, and once for the
7808- // price movement that may occur between querying the oracle and
7809- // acquiring the quote. We don't really care about this margin being too
7810- // big, this only affects the max units our peer agrees to route.
7811- maxMathUnits = maxMathUnits .Add (toleranceUnits ).Add (toleranceUnits )
7812-
7813- // Now parse the result back to uint64.
7814- maxUnits = maxMathUnits .ToUint64 ()
7758+ if err != nil {
7759+ return nil , fmt .Errorf ("error calculating asset max " +
7760+ "amount: %w" , err )
7761+ }
78157762
78167763 rpcSpecifier := marshalAssetSpecifier (specifier )
78177764
@@ -7847,68 +7794,14 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
78477794 return nil , fmt .Errorf ("unexpected response type: %T" , r )
78487795 }
78497796
7850- // Now that we have the accepted quote, we know the amount in Satoshi
7851- // that we need to pay. We can now update the invoice with this amount.
7852- //
7853- // First, un-marshall the ask asset rate from the accepted quote.
7854- askAssetRate , err := rfqrpc .UnmarshalFixedPoint (
7855- acceptedQuote .AskAssetRate ,
7797+ invoiceAmtMsat , err := validateInvoiceAmount (
7798+ acceptedQuote , req .AssetAmount , iReq ,
78567799 )
78577800 if err != nil {
7858- return nil , fmt .Errorf ("error unmarshalling ask asset rate: %w" ,
7859- err )
7860- }
7861-
7862- invUnits := req .AssetAmount
7863-
7864- if satsMode {
7865- // If the invoice was created over a satoshi amount, we need to
7866- // calculate the units.
7867- invUnits = rfqmath .MilliSatoshiToUnits (
7868- amtMsat , * askAssetRate ,
7869- ).ScaleTo (0 ).ToUint64 ()
7870-
7871- // Now let's see if the negotiated quote can actually route the
7872- // amount we need in msat.
7873- maxFixedUnits := rfqmath .NewBigIntFixedPoint (
7874- acceptedQuote .AssetMaxAmount , 0 ,
7875- )
7876- maxRoutableMsat := rfqmath .UnitsToMilliSatoshi (
7877- maxFixedUnits , * askAssetRate ,
7878- )
7879-
7880- if maxRoutableMsat <= amtMsat {
7881- return nil , fmt .Errorf ("cannot create invoice for %v " +
7882- "msat, max routable amount is %v msat" , amtMsat ,
7883- maxRoutableMsat )
7884- }
7885- }
7886-
7887- // If the invoice is for an asset unit amount smaller than the minimal
7888- // transportable amount, we'll return an error, as it wouldn't be
7889- // payable by the network.
7890- if acceptedQuote .MinTransportableUnits > invUnits {
7891- return nil , fmt .Errorf ("cannot create invoice over %d asset " +
7892- "units, as the minimal transportable amount is %d " +
7893- "units with the current rate of %v units/BTC" ,
7894- invUnits , acceptedQuote .MinTransportableUnits ,
7895- acceptedQuote .AskAssetRate )
7896- }
7897-
7898- switch {
7899- case satsMode :
7900- iReq .ValueMsat = int64 (amtMsat )
7901-
7902- default :
7903- // Convert the asset amount into a fixed-point.
7904- assetAmount := rfqmath .NewBigIntFixedPoint (req .AssetAmount , 0 )
7905-
7906- // Calculate the invoice amount in msat.
7907- valMsat := rfqmath .UnitsToMilliSatoshi (
7908- assetAmount , * askAssetRate ,
7909- )
7910- iReq .ValueMsat = int64 (valMsat )
7801+ return nil , fmt .Errorf ("error validating invoice " +
7802+ "amount: %w" , err )
79117803 }
7804+ iReq .ValueMsat = invoiceAmtMsat
79127805
79137806 // The last step is to create a hop hint that includes the fake SCID of
79147807 // the quote, alongside the channel's routing policy. We need to choose
0 commit comments