From 61c6a2ab57695fe22df92fee98e33c2645d965ce Mon Sep 17 00:00:00 2001 From: d11n Date: Thu, 23 Jun 2022 06:42:28 +0200 Subject: [PATCH] Greenfield: Add balance endpoint (#3887) * Greenfield: Add balance endpoint * Remove superfluous try/catch --- .../BTCPayServerClient.Lightning.Internal.cs | 9 +++ .../BTCPayServerClient.Lightning.Store.cs | 9 +++ .../Models/LightningNodeBalanceData.cs | 52 ++++++++++++++++ ...ieldLightningNodeApiController.Internal.cs | 9 ++- ...enfieldLightningNodeApiController.Store.cs | 9 +++ .../GreenfieldLightningNodeApiController.cs | 27 ++++++++ .../GreenField/LocalBTCPayServerClient.cs | 14 +++++ .../v1/swagger.template.lightning.common.json | 62 +++++++++++++++++++ .../swagger.template.lightning.internal.json | 48 ++++++++++++++ .../v1/swagger.template.lightning.store.json | 59 +++++++++++++++++- 10 files changed, 296 insertions(+), 2 deletions(-) create mode 100644 BTCPayServer.Client/Models/LightningNodeBalanceData.cs diff --git a/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs b/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs index ce3ddd4f5..ff5e1fa81 100644 --- a/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs +++ b/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs @@ -17,6 +17,15 @@ namespace BTCPayServer.Client method: HttpMethod.Get), token); return await HandleResponse(response); } + + public virtual async Task GetLightningNodeBalance(string cryptoCode, + CancellationToken token = default) + { + var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/balance", + method: HttpMethod.Get), token); + return await HandleResponse(response); + } public virtual async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request, CancellationToken token = default) diff --git a/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs b/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs index 8f07ee0b0..72a224ff3 100644 --- a/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs +++ b/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs @@ -17,6 +17,15 @@ namespace BTCPayServer.Client method: HttpMethod.Get), token); return await HandleResponse(response); } + + public virtual async Task GetLightningNodeBalance(string storeId, string cryptoCode, + CancellationToken token = default) + { + var response = await _httpClient.SendAsync( + CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/balance", + method: HttpMethod.Get), token); + return await HandleResponse(response); + } public virtual async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request, CancellationToken token = default) diff --git a/BTCPayServer.Client/Models/LightningNodeBalanceData.cs b/BTCPayServer.Client/Models/LightningNodeBalanceData.cs new file mode 100644 index 000000000..85ad394cc --- /dev/null +++ b/BTCPayServer.Client/Models/LightningNodeBalanceData.cs @@ -0,0 +1,52 @@ +using BTCPayServer.Client.JsonConverters; +using BTCPayServer.Lightning; +using Newtonsoft.Json; + +namespace BTCPayServer.Client.Models +{ + public class LightningNodeBalanceData + { + [JsonProperty("onchain")] + public OnchainBalanceData OnchainBalance { get; set; } + + [JsonProperty("offchain")] + public OffchainBalanceData OffchainBalance { get; set; } + + public LightningNodeBalanceData() + { + } + + public LightningNodeBalanceData(OnchainBalanceData onchain, OffchainBalanceData offchain) + { + OnchainBalance = onchain; + OffchainBalance = offchain; + } + } + + public class OnchainBalanceData + { + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Confirmed { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Unconfirmed { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Reserved { get; set; } + } + + public class OffchainBalanceData + { + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Opening { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Local { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Remote { get; set; } + + [JsonConverter(typeof(LightMoneyJsonConverter))] + public LightMoney Closing { get; set; } + } +} diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Internal.cs b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Internal.cs index 815a0e3a6..37c53358d 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Internal.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Internal.cs @@ -26,7 +26,6 @@ namespace BTCPayServer.Controllers.Greenfield private readonly LightningClientFactoryService _lightningClientFactory; private readonly IOptions _lightningNetworkOptions; - public GreenfieldInternalLightningNodeApiController( BTCPayNetworkProvider btcPayNetworkProvider, PoliciesSettings policiesSettings, LightningClientFactoryService lightningClientFactory, IOptions lightningNetworkOptions, @@ -46,6 +45,14 @@ namespace BTCPayServer.Controllers.Greenfield return base.GetInfo(cryptoCode, cancellationToken); } + [Authorize(Policy = Policies.CanUseInternalLightningNode, + AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [HttpGet("~/api/v1/server/lightning/{cryptoCode}/balance")] + public override Task GetBalance(string cryptoCode, CancellationToken cancellationToken = default) + { + return base.GetBalance(cryptoCode, cancellationToken); + } + [Authorize(Policy = Policies.CanUseInternalLightningNode, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [HttpPost("~/api/v1/server/lightning/{cryptoCode}/connect")] diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs index b531298a5..9394c5ddf 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.Store.cs @@ -40,6 +40,7 @@ namespace BTCPayServer.Controllers.Greenfield _lightningClientFactory = lightningClientFactory; _btcPayNetworkProvider = btcPayNetworkProvider; } + [Authorize(Policy = Policies.CanUseLightningNodeInStore, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/info")] @@ -48,6 +49,14 @@ namespace BTCPayServer.Controllers.Greenfield return base.GetInfo(cryptoCode, cancellationToken); } + [Authorize(Policy = Policies.CanUseLightningNodeInStore, + AuthenticationSchemes = AuthenticationSchemes.Greenfield)] + [HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/balance")] + public override Task GetBalance(string cryptoCode, CancellationToken cancellationToken = default) + { + return base.GetBalance(cryptoCode, cancellationToken); + } + [Authorize(Policy = Policies.CanUseLightningNodeInStore, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/connect")] diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs index 2a4792c7d..80284e0ac 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldLightningNodeApiController.cs @@ -24,6 +24,7 @@ namespace BTCPayServer.Controllers.Greenfield // Do not mark handled, it is possible filters above have better errors } } + public abstract class GreenfieldLightningNodeApiController : Controller { private readonly BTCPayNetworkProvider _btcPayNetworkProvider; @@ -49,6 +50,32 @@ namespace BTCPayServer.Controllers.Greenfield }); } + public virtual async Task GetBalance(string cryptoCode, CancellationToken cancellationToken = default) + { + var lightningClient = await GetLightningClient(cryptoCode, true); + var balance = await lightningClient.GetBalance(cancellationToken); + return Ok(new LightningNodeBalanceData + { + OnchainBalance = balance.OnchainBalance != null + ? new OnchainBalanceData + { + Confirmed = balance.OnchainBalance.Confirmed, + Unconfirmed = balance.OnchainBalance.Unconfirmed, + Reserved = balance.OnchainBalance.Reserved + } + : null, + OffchainBalance = balance.OffchainBalance != null + ? new OffchainBalanceData + { + Opening = balance.OffchainBalance.Opening, + Local = balance.OffchainBalance.Local, + Remote = balance.OffchainBalance.Remote, + Closing = balance.OffchainBalance.Closing, + } + : null + }); + } + public virtual async Task ConnectToNode(string cryptoCode, ConnectToNodeRequest request, CancellationToken cancellationToken = default) { var lightningClient = await GetLightningClient(cryptoCode, true); diff --git a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs index 21be4f3ff..918ca12da 100644 --- a/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs +++ b/BTCPayServer/Controllers/GreenField/LocalBTCPayServerClient.cs @@ -533,6 +533,13 @@ namespace BTCPayServer.Controllers.Greenfield await _storeLightningNodeApiController.GetInfo(cryptoCode, token)); } + public override async Task GetLightningNodeBalance(string storeId, string cryptoCode, + CancellationToken token = default) + { + return GetFromActionResult( + await _storeLightningNodeApiController.GetBalance(cryptoCode)); + } + public override async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request, CancellationToken token = default) { @@ -587,6 +594,13 @@ namespace BTCPayServer.Controllers.Greenfield await _lightningNodeApiController.GetInfo(cryptoCode)); } + public override async Task GetLightningNodeBalance(string cryptoCode, + CancellationToken token = default) + { + return GetFromActionResult( + await _lightningNodeApiController.GetBalance(cryptoCode)); + } + public override async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request, CancellationToken token = default) { diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json index 0878e3916..d32e7db91 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.common.json @@ -197,6 +197,68 @@ } } }, + "LightningNodeBalanceData": { + "type": "object", + "properties": { + "onchain": { + "type": "object", + "description": "On-chain balance of the Lightning node", + "nullable": true, + "$ref": "#/components/schemas/OnchainBalanceData" + }, + "offchain": { + "type": "object", + "description": "Off-chain balance of the Lightning node", + "nullable": true, + "$ref": "#/components/schemas/OffchainBalanceData" + } + } + }, + "OnchainBalanceData": { + "type": "object", + "properties": { + "confirmed": { + "type": "string", + "description": "The confirmed amount in millisatoshi", + "nullable": true + }, + "unconfirmed": { + "type": "string", + "description": "The unconfirmed amount in millisatoshi", + "nullable": true + }, + "reserved": { + "type": "string", + "description": "The reserved amount in millisatoshi", + "nullable": true + } + } + }, + "OffchainBalanceData": { + "type": "object", + "properties": { + "opening": { + "type": "string", + "description": "The amount of current channel openings in millisatoshi", + "nullable": true + }, + "local": { + "type": "string", + "description": "The amount that is available on the local end of active channels in millisatoshi", + "nullable": true + }, + "remote": { + "type": "string", + "description": "The amount that is available on the remote end of active channels in millisatoshi", + "nullable": true + }, + "closing": { + "type": "string", + "description": "The amount of current channel closings in millisatoshi", + "nullable": true + } + } + }, "PayLightningInvoiceRequest": { "type": "object", "properties": { diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json index 2fc769d5a..d33c09224 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.internal.json @@ -48,6 +48,54 @@ ] } }, + "/api/v1/server/lightning/{cryptoCode}/balance": { + "get": { + "tags": [ + "Lightning (Internal Node)" + ], + "summary": "Get node balance", + "parameters": [ + { + "name": "cryptoCode", + "in": "path", + "required": true, + "description": "The cryptoCode of the lightning-node to query", + "schema": { + "type": "string" + }, + "example": "BTC" + } + ], + "description": "View balance of the lightning node", + "operationId": "InternalLightningNodeApi_GetBalance", + "responses": { + "200": { + "description": "Lightning node balance for on-chain and off-chain funds", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightningNodeBalanceData" + } + } + } + }, + "503": { + "description": "Unable to access the lightning node" + }, + "404": { + "description": "The lightning node configuration was not found" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.server.canuseinternallightningnode" + ], + "Basic": [] + } + ] + } + }, "/api/v1/server/lightning/{cryptoCode}/connect": { "post": { "tags": [ diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json index b9d24e223..dad643f61 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.lightning.store.json @@ -50,7 +50,64 @@ "security": [ { "API_Key": [ - "btcpay.store.cancreatelightninginvoice" + "btcpay.store.canuselightningnode" + ], + "Basic": [] + } + ] + } + }, + "/api/v1/stores/{storeId}/lightning/{cryptoCode}/balance": { + "get": { + "tags": [ + "Lightning (Store)" + ], + "summary": "Get node balance", + "parameters": [ + { + "name": "cryptoCode", + "in": "path", + "required": true, + "description": "The cryptoCode of the lightning-node to query", + "schema": { + "type": "string" + }, + "example": "BTC" + }, + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store id with the lightning-node configuration to query", + "schema": { + "type": "string" + } + } + ], + "description": "View balance of the lightning node", + "operationId": "StoreLightningNodeApi_GetBalance", + "responses": { + "200": { + "description": "Lightning node balance for on-chain and off-chain funds", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LightningNodeBalanceData" + } + } + } + }, + "503": { + "description": "Unable to access the lightning node" + }, + "404": { + "description": "The lightning node configuration was not found" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canuselightningnode" ], "Basic": [] }