@@ -7637,6 +7637,29 @@ 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+ // Let's make sure that only one type of amount is set, in order to
7642+ // avoid ambiguous behavior. This field dictates the actual value of the
7643+ // invoice so let's be strict and only allow one possible value to be
7644+ // set.
7645+ if req .AssetAmount > 0 && (req .InvoiceRequest .ValueMsat > 0 ||
7646+ req .InvoiceRequest .Value > 0 ) {
7647+
7648+ return nil , fmt .Errorf ("cannot set both asset amount and sats" +
7649+ "amount" )
7650+ }
7651+
7652+ if req .InvoiceRequest .ValueMsat > 0 && req .InvoiceRequest .Value > 0 {
7653+ return nil , fmt .Errorf ("cannot set both sats and msats" )
7654+ }
7655+
7656+ // In order to avoid repeating the following check let's assign it to a
7657+ // boolean for easier access.
7658+ var satsMode bool
7659+ if req .InvoiceRequest .Value > 0 || req .InvoiceRequest .ValueMsat > 0 {
7660+ satsMode = true
7661+ }
7662+
76407663 var assetID asset.ID
76417664 copy (assetID [:], req .AssetId )
76427665
@@ -7676,13 +7699,44 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
76767699 time .Duration (expirySeconds ) * time .Second ,
76777700 )
76787701
7702+ maxUnits := req .AssetAmount
7703+ amtMsat := lnwire .MilliSatoshi (0 )
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+ amtMsat = lnwire .MilliSatoshi (req .InvoiceRequest .ValueMsat +
7711+ req .InvoiceRequest .Value * (1000 ))
7712+
7713+ oracleRes , err := r .cfg .PriceOracle .QueryBidPrice (
7714+ ctx , specifier , fn .None [uint64 ](), fn .Some (amtMsat ),
7715+ fn .None [rfqmsg.AssetRate ](),
7716+ )
7717+ if err != nil {
7718+ return nil , err
7719+ }
7720+
7721+ if oracleRes .Err != nil {
7722+ return nil , fmt .Errorf ("cannot query oracle: %v" ,
7723+ oracleRes .Err .Error ())
7724+ }
7725+
7726+ assetUnits := rfqmath .MilliSatoshiToUnits (
7727+ amtMsat , oracleRes .AssetRate .Rate ,
7728+ )
7729+
7730+ maxUnits = assetUnits .ToUint64 ()
7731+ }
7732+
76797733 resp , err := r .AddAssetBuyOrder (ctx , & rfqrpc.AddAssetBuyOrderRequest {
76807734 AssetSpecifier : & rfqrpc.AssetSpecifier {
76817735 Id : & rfqrpc.AssetSpecifier_AssetId {
76827736 AssetId : assetID [:],
76837737 },
76847738 },
7685- AssetMaxAmt : req . AssetAmount ,
7739+ AssetMaxAmt : maxUnits ,
76867740 Expiry : uint64 (expiryTimestamp .Unix ()),
76877741 PeerPubKey : peerPubKey [:],
76887742 TimeoutSeconds : uint32 (
@@ -7712,17 +7766,6 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77127766 return nil , fmt .Errorf ("unexpected response type: %T" , r )
77137767 }
77147768
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-
77267769 // Now that we have the accepted quote, we know the amount in Satoshi
77277770 // that we need to pay. We can now update the invoice with this amount.
77287771 //
@@ -7735,12 +7778,44 @@ func (r *rpcServer) AddInvoice(ctx context.Context,
77357778 err )
77367779 }
77377780
7738- // Convert the asset amount into a fixed-point.
7739- assetAmount := rfqmath .NewBigIntFixedPoint (req .AssetAmount , 0 )
7781+ invUnits := req .AssetAmount
7782+
7783+ // If the invoice was created over a satoshi amount, we need to
7784+ // calculate the units.
7785+ if satsMode {
7786+ invUnits = rfqmath .MilliSatoshiToUnits (
7787+ amtMsat , * askAssetRate ,
7788+ ).ToUint64 ()
7789+ }
7790+
7791+ // If the invoice is for an asset unit amount smaller than the minimal
7792+ // transportable amount, we'll return an error, as it wouldn't be
7793+ // payable by the network.
7794+ if acceptedQuote .MinTransportableUnits > invUnits {
7795+ return nil , fmt .Errorf ("cannot create invoice over %d asset " +
7796+ "units, as the minimal transportable amount is %d " +
7797+ "units with the current rate of %v units/BTC" ,
7798+ invUnits , acceptedQuote .MinTransportableUnits ,
7799+ acceptedQuote .AskAssetRate )
7800+ }
7801+
7802+ switch {
7803+ case satsMode :
7804+ amtMsat := lnwire .MilliSatoshi (req .InvoiceRequest .ValueMsat +
7805+ req .InvoiceRequest .Value * (1000 ))
7806+
7807+ iReq .ValueMsat = int64 (amtMsat )
7808+
7809+ default :
7810+ // Convert the asset amount into a fixed-point.
7811+ assetAmount := rfqmath .NewBigIntFixedPoint (req .AssetAmount , 0 )
77407812
7741- // Calculate the invoice amount in msat.
7742- valMsat := rfqmath .UnitsToMilliSatoshi (assetAmount , * askAssetRate )
7743- iReq .ValueMsat = int64 (valMsat )
7813+ // Calculate the invoice amount in msat.
7814+ valMsat := rfqmath .UnitsToMilliSatoshi (
7815+ assetAmount , * askAssetRate ,
7816+ )
7817+ iReq .ValueMsat = int64 (valMsat )
7818+ }
77447819
77457820 // The last step is to create a hop hint that includes the fake SCID of
77467821 // the quote, alongside the channel's routing policy. We need to choose
0 commit comments