Lightning payment info and fee handling (#3454)

* Lightning payment info and fee handling

Builds on the additions in btcpayserver/BTCPayServer.Lightning#59 and btcpayserver/BTCPayServer.Lightning#61.

Adds payment information (total amount and fees) to the API response and allows to set an optional maximum fee percentage when paying.

* Add max fee flat
This commit is contained in:
d11n 2022-02-17 10:01:39 +01:00 committed by GitHub
parent 2a884d6f38
commit cd3807a3d8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 97 additions and 26 deletions

View file

@ -28,8 +28,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="6.0.19" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.0" />
<PackageReference Include="NBitcoin" Version="7.0.1" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
<ItemGroup>

View file

@ -54,8 +54,7 @@ namespace BTCPayServer.Client
return await HandleResponse<string>(response);
}
public virtual async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
public virtual async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
@ -63,7 +62,7 @@ namespace BTCPayServer.Client
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
method: HttpMethod.Post), token);
await HandleResponse(response);
return await HandleResponse<LightningPaymentData>(response);
}
public virtual async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,

View file

@ -0,0 +1,15 @@
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class LightningPaymentData
{
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney TotalAmount { get; set; }
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney FeeAmount { get; set; }
}
}

View file

@ -1,8 +1,20 @@
#nullable enable
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class PayLightningInvoiceRequest
{
[Newtonsoft.Json.JsonProperty("BOLT11")]
[JsonProperty("BOLT11")]
public string BOLT11 { get; set; }
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public float? MaxFeePercent { get; set; }
[JsonConverter(typeof(MoneyJsonConverter))]
public Money? MaxFeeFlat { get; set; }
}
}

View file

@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.0.1" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="6.0.19" />
<PackageReference Include="NBitcoin" Version="7.0.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>

View file

@ -48,7 +48,7 @@
<ItemGroup>
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.0" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.3.2" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.449" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />

View file

@ -178,19 +178,23 @@ namespace BTCPayServer.Controllers.Greenfield
{
return this.CreateValidationError(ModelState);
}
var result = await lightningClient.Pay(lightningInvoice.BOLT11);
switch (result.Result)
var param = lightningInvoice?.MaxFeeFlat != null || lightningInvoice?.MaxFeePercent != null
? new PayInvoiceParams { MaxFeePercent = lightningInvoice.MaxFeePercent, MaxFeeFlat = lightningInvoice.MaxFeeFlat }
: null;
var result = await lightningClient.Pay(lightningInvoice.BOLT11, param);
return result.Result switch
{
case PayResult.CouldNotFindRoute:
return this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer");
case PayResult.Error:
return this.CreateAPIError("generic-error", result.ErrorDetail);
case PayResult.Ok:
return Ok();
default:
throw new NotSupportedException("Unsupported Payresult");
}
PayResult.CouldNotFindRoute => this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer"),
PayResult.Error => this.CreateAPIError("generic-error", result.ErrorDetail),
PayResult.Ok => Ok(new LightningPaymentData
{
TotalAmount = result.Details?.TotalAmount,
FeeAmount = result.Details?.FeeAmount
}),
_ => throw new NotSupportedException("Unsupported Payresult")
};
}
public virtual async Task<IActionResult> GetInvoice(string cryptoCode, string id)
@ -253,7 +257,7 @@ namespace BTCPayServer.Controllers.Greenfield
private LightningInvoiceData ToModel(LightningInvoice invoice)
{
return new LightningInvoiceData()
return new LightningInvoiceData
{
Amount = invoice.Amount,
Id = invoice.Id,

View file

@ -497,10 +497,11 @@ namespace BTCPayServer.Controllers.Greenfield
await _lightningNodeApiController.GetDepositAddress(cryptoCode));
}
public override async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
public override async Task<LightningPaymentData> PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
HandleActionResult(await _lightningNodeApiController.PayInvoice(cryptoCode, request));
return GetFromActionResult<LightningPaymentData>(
await _lightningNodeApiController.PayInvoice(cryptoCode, request));
}
public override async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode, string invoiceId,
@ -518,7 +519,6 @@ namespace BTCPayServer.Controllers.Greenfield
await _lightningNodeApiController.CreateInvoice(cryptoCode, request));
}
private T GetFromActionResult<T>(IActionResult result)
{
HandleActionResult(result);

View file

@ -70,6 +70,20 @@
}
}
},
"LightningPaymentData": {
"type": "object",
"additionalProperties": false,
"properties": {
"totalAmount": {
"type": "string",
"description": "The total amount (including fees) in millisatoshi"
},
"feeAmount": {
"type": "string",
"description": "The total fees in millisatoshi"
}
}
},
"LightningInvoiceData": {
"type": "object",
"additionalProperties": false,
@ -141,6 +155,19 @@
"BOLT11": {
"type": "string",
"description": "The BOLT11 of the invoice to pay"
},
"maxFeePercent": {
"type": "string",
"format": "float",
"nullable": true,
"description": "The fee limit expressed as a percentage of the payment amount",
"example": "6.15"
},
"maxFeeFlat": {
"type": "string",
"nullable": true,
"description": "The fee limit expressed as a fixed amount in satoshi",
"example": "21"
}
}
},

View file

@ -361,7 +361,14 @@
"operationId": "InternalLightningNodeApi_PayInvoice",
"responses": {
"200": {
"description": "Successfully paid"
"description": "Successfully paid",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LightningPaymentData"
}
}
}
},
"422": {
"description": "Unable to validate the request",

View file

@ -428,7 +428,14 @@
"operationId": "StoreLightningNodeApi_PayInvoice",
"responses": {
"200": {
"description": "Successfully paid"
"description": "Successfully paid",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/LightningPaymentData"
}
}
}
},
"422": {
"description": "Unable to validate the request",