@@ -7637,6 +7637,30 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
76377637 if len (req .AssetId ) != sha256 .Size {
76387638 return nil , fmt .Errorf ("asset ID must be 32 bytes" )
76397639 }
7640+
7641+ amtMsat , err := lnrpc .UnmarshallAmt (
7642+ req .InvoiceRequest .Value , req .InvoiceRequest .ValueMsat ,
7643+ )
7644+ if err != nil {
7645+ return nil , err
7646+ }
7647+
7648+ // Let's make sure that only one type of amount is set, in order to
7649+ // avoid ambiguous behavior. This field dictates the actual value of the
7650+ // invoice so let's be strict and only allow one possible value to be
7651+ // set.
7652+ if req .AssetAmount > 0 && amtMsat != 0 {
7653+ return nil , fmt .Errorf ("cannot set both asset amount and sats" +
7654+ "amount" )
7655+ }
7656+
7657+ // In order to avoid repeating the following check let's assign it to a
7658+ // boolean for easier access.
7659+ var satsMode bool
7660+ if amtMsat != 0 {
7661+ satsMode = true
7662+ }
7663+
76407664 var assetID asset.ID
76417665 copy (assetID [:], req .AssetId )
76427666
@@ -7676,13 +7700,40 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
76767700 time .Duration (expirySeconds ) * time .Second ,
76777701 )
76787702
7703+ maxUnits := req .AssetAmount
7704+
7705+ // If the invoice defines the desired amount in satoshis, we need to
7706+ // query our oracle first to get an estimation on the asset rate. This
7707+ // will help us establish a quote with the correct amount of asset
7708+ // units.
7709+ if satsMode {
7710+ oracleRes , err := r .cfg .PriceOracle .QueryBidPrice (
7711+ ctx , specifier , fn .None [uint64 ](), fn .Some (amtMsat ),
7712+ fn .None [rfqmsg.AssetRate ](),
7713+ )
7714+ if err != nil {
7715+ return nil , err
7716+ }
7717+
7718+ if oracleRes .Err != nil {
7719+ return nil , fmt .Errorf ("cannot query oracle: %v" ,
7720+ oracleRes .Err .Error ())
7721+ }
7722+
7723+ assetUnits := rfqmath .MilliSatoshiToUnits (
7724+ amtMsat , oracleRes .AssetRate .Rate ,
7725+ )
7726+
7727+ maxUnits = assetUnits .ToUint64 ()
7728+ }
7729+
76797730 resp , err := r .AddAssetBuyOrder (ctx , & rfqrpc.AddAssetBuyOrderRequest {
76807731 AssetSpecifier : & rfqrpc.AssetSpecifier {
76817732 Id : & rfqrpc.AssetSpecifier_AssetId {
76827733 AssetId : assetID [:],
76837734 },
76847735 },
7685- AssetMaxAmt : req . AssetAmount ,
7736+ AssetMaxAmt : maxUnits ,
76867737 Expiry : uint64 (expiryTimestamp .Unix ()),
76877738 PeerPubKey : peerPubKey [:],
76887739 TimeoutSeconds : uint32 (
@@ -7712,17 +7763,6 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77127763 return nil , fmt .Errorf ("unexpected response type: %T" , r )
77137764 }
77147765
7715- // If the invoice is for an asset unit amount smaller than the minimal
7716- // transportable amount, we'll return an error, as it wouldn't be
7717- // payable by the network.
7718- if acceptedQuote .MinTransportableUnits > req .AssetAmount {
7719- return nil , fmt .Errorf ("cannot create invoice over %d asset " +
7720- "units, as the minimal transportable amount is %d " +
7721- "units with the current rate of %v units/BTC" ,
7722- req .AssetAmount , acceptedQuote .MinTransportableUnits ,
7723- acceptedQuote .AskAssetRate )
7724- }
7725-
77267766 // Now that we have the accepted quote, we know the amount in Satoshi
77277767 // that we need to pay. We can now update the invoice with this amount.
77287768 //
@@ -7735,12 +7775,59 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77357775 err )
77367776 }
77377777
7738- // Convert the asset amount into a fixed-point.
7739- assetAmount := rfqmath .NewBigIntFixedPoint (req .AssetAmount , 0 )
7778+ invUnits := req .AssetAmount
7779+
7780+ if satsMode {
7781+ // If the invoice was created over a satoshi amount, we need to
7782+ // calculate the units.
7783+ invUnits = rfqmath .MilliSatoshiToUnits (
7784+ amtMsat , * askAssetRate ,
7785+ ).ScaleTo (0 ).ToUint64 ()
7786+
7787+ // Now let's see if the negotiated quote can actually route the
7788+ // amount we need in msat.
7789+ maxFixedUnits := rfqmath .NewBigIntFixedPoint (
7790+ acceptedQuote .AssetMaxAmount , 0 ,
7791+ )
7792+ maxRoutableMsat := rfqmath .UnitsToMilliSatoshi (
7793+ maxFixedUnits , * askAssetRate ,
7794+ )
7795+
7796+ if maxRoutableMsat <= amtMsat {
7797+ return nil , fmt .Errorf ("cannot create invoice for %v " +
7798+ "msat, max routable amount is %v msat" , amtMsat ,
7799+ maxRoutableMsat )
7800+ }
7801+ }
77407802
7741- // Calculate the invoice amount in msat.
7742- valMsat := rfqmath .UnitsToMilliSatoshi (assetAmount , * askAssetRate )
7743- iReq .ValueMsat = int64 (valMsat )
7803+ // If the invoice is for an asset unit amount smaller than the minimal
7804+ // transportable amount, we'll return an error, as it wouldn't be
7805+ // payable by the network.
7806+ if acceptedQuote .MinTransportableUnits > invUnits {
7807+ return nil , fmt .Errorf ("cannot create invoice over %d asset " +
7808+ "units, as the minimal transportable amount is %d " +
7809+ "units with the current rate of %v units/BTC" ,
7810+ invUnits , acceptedQuote .MinTransportableUnits ,
7811+ acceptedQuote .AskAssetRate )
7812+ }
7813+
7814+ switch {
7815+ case satsMode :
7816+ amtMsat := lnwire .MilliSatoshi (req .InvoiceRequest .ValueMsat +
7817+ req .InvoiceRequest .Value * (1000 ))
7818+
7819+ iReq .ValueMsat = int64 (amtMsat )
7820+
7821+ default :
7822+ // Convert the asset amount into a fixed-point.
7823+ assetAmount := rfqmath .NewBigIntFixedPoint (req .AssetAmount , 0 )
7824+
7825+ // Calculate the invoice amount in msat.
7826+ valMsat := rfqmath .UnitsToMilliSatoshi (
7827+ assetAmount , * askAssetRate ,
7828+ )
7829+ iReq .ValueMsat = int64 (valMsat )
7830+ }
77447831
77457832 // The last step is to create a hop hint that includes the fake SCID of
77467833 // the quote, alongside the channel's routing policy. We need to choose
0 commit comments