@@ -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