mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2025-02-20 13:34:37 +01:00
Fix lightning implementation, docs and tests
This commit is contained in:
parent
a9dbbe1955
commit
8dd6ecc0b8
36 changed files with 615 additions and 528 deletions
|
@ -5,8 +5,8 @@
|
|||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.39" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.1.0.22" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.40" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace BTCPayServer.Client
|
|||
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task RevokeAPIKey(string apikey, CancellationToken token = default)
|
||||
|
@ -33,7 +33,7 @@ namespace BTCPayServer.Client
|
|||
if (apikey == null)
|
||||
throw new ArgumentNullException(nameof(apikey));
|
||||
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace BTCPayServer.Client
|
|||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/connect", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string cryptoCode,
|
||||
|
@ -61,9 +61,9 @@ namespace BTCPayServer.Client
|
|||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/pay", bodyPayload: request,
|
||||
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
|
||||
|
|
|
@ -4,6 +4,7 @@ using System.Net.Http;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
|
@ -26,7 +27,7 @@ namespace BTCPayServer.Client
|
|||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/connect", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string storeId, string cryptoCode,
|
||||
|
@ -62,9 +63,9 @@ namespace BTCPayServer.Client
|
|||
if (request == null)
|
||||
throw new ArgumentNullException(nameof(request));
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/pay", bodyPayload: request,
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
|
||||
method: HttpMethod.Post), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
|
||||
|
|
|
@ -31,7 +31,7 @@ namespace BTCPayServer.Client
|
|||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}",
|
||||
method: HttpMethod.Delete), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<PaymentRequestData> CreatePaymentRequest(string storeId,
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace BTCPayServer.Client
|
|||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}", method: HttpMethod.Delete), token);
|
||||
HandleResponse(response);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<StoreData> CreateStore(CreateStoreRequest request, CancellationToken token = default)
|
||||
|
|
|
@ -43,14 +43,25 @@ namespace BTCPayServer.Client
|
|||
_httpClient = httpClient ?? new HttpClient();
|
||||
}
|
||||
|
||||
protected void HandleResponse(HttpResponseMessage message)
|
||||
protected async Task HandleResponse(HttpResponseMessage message)
|
||||
{
|
||||
if (message.StatusCode == System.Net.HttpStatusCode.UnprocessableEntity)
|
||||
{
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldValidationError[]>(await message.Content.ReadAsStringAsync()); ;
|
||||
throw new GreenFieldValidationException(err);
|
||||
}
|
||||
else if (message.StatusCode == System.Net.HttpStatusCode.BadRequest)
|
||||
{
|
||||
var err = JsonConvert.DeserializeObject<Models.GreenfieldAPIError>(await message.Content.ReadAsStringAsync());
|
||||
throw new GreenFieldAPIException(err);
|
||||
}
|
||||
|
||||
message.EnsureSuccessStatusCode();
|
||||
}
|
||||
|
||||
protected async Task<T> HandleResponse<T>(HttpResponseMessage message)
|
||||
{
|
||||
HandleResponse(message);
|
||||
await HandleResponse(message);
|
||||
return JsonConvert.DeserializeObject<T>(await message.Content.ReadAsStringAsync());
|
||||
}
|
||||
|
||||
|
|
18
BTCPayServer.Client/GreenFieldAPIException.cs
Normal file
18
BTCPayServer.Client/GreenFieldAPIException.cs
Normal file
|
@ -0,0 +1,18 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class GreenFieldAPIException : Exception
|
||||
{
|
||||
public GreenFieldAPIException(Models.GreenfieldAPIError error):base(error.Message)
|
||||
{
|
||||
if (error == null)
|
||||
throw new ArgumentNullException(nameof(error));
|
||||
APIError = error;
|
||||
}
|
||||
public Models.GreenfieldAPIError APIError { get; }
|
||||
}
|
||||
}
|
30
BTCPayServer.Client/GreenFieldValidationException.cs
Normal file
30
BTCPayServer.Client/GreenFieldValidationException.cs
Normal file
|
@ -0,0 +1,30 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public class GreenFieldValidationException : Exception
|
||||
{
|
||||
public GreenFieldValidationException(Models.GreenfieldValidationError[] errors) : base(BuildMessage(errors))
|
||||
{
|
||||
ValidationErrors = errors;
|
||||
}
|
||||
|
||||
private static string BuildMessage(GreenfieldValidationError[] errors)
|
||||
{
|
||||
if (errors == null)
|
||||
throw new ArgumentNullException(nameof(errors));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
foreach (var error in errors)
|
||||
{
|
||||
builder.AppendLine($"{error.Path}: {error.Message}");
|
||||
}
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public Models.GreenfieldValidationError[] ValidationErrors { get; }
|
||||
}
|
||||
}
|
29
BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
Normal file
29
BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.JsonConverters
|
||||
{
|
||||
public class NodeUriJsonConverter : JsonConverter<NodeInfo>
|
||||
{
|
||||
public override NodeInfo ReadJson(JsonReader reader, Type objectType, [AllowNull] NodeInfo existingValue, bool hasExistingValue, JsonSerializer serializer)
|
||||
{
|
||||
if (reader.TokenType != JsonToken.String)
|
||||
throw new JsonObjectException(reader.Path, "Unexpected token type for NodeUri");
|
||||
if (NodeInfo.TryParse((string)reader.Value, out var info))
|
||||
return info;
|
||||
throw new JsonObjectException(reader.Path, "Invalid NodeUri");
|
||||
}
|
||||
|
||||
public override void WriteJson(JsonWriter writer, [AllowNull] NodeInfo value, JsonSerializer serializer)
|
||||
{
|
||||
if (value is NodeInfo)
|
||||
writer.WriteValue(value.ToString());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +1,24 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class ConnectToNodeRequest
|
||||
{
|
||||
public string NodeInfo { get; set; }
|
||||
public string NodeId { get; set; }
|
||||
public string NodeHost { get; set; }
|
||||
public int NodePort { get; set; }
|
||||
public ConnectToNodeRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public ConnectToNodeRequest(NodeInfo nodeInfo)
|
||||
{
|
||||
NodeURI = nodeInfo;
|
||||
}
|
||||
[JsonConverter(typeof(NodeUriJsonConverter))]
|
||||
[JsonProperty("nodeURI")]
|
||||
public NodeInfo NodeURI { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using System.Security.Cryptography;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -7,9 +8,20 @@ namespace BTCPayServer.Client.Models
|
|||
{
|
||||
public class CreateLightningInvoiceRequest
|
||||
{
|
||||
[JsonProperty(ItemConverterType = typeof(LightMoneyJsonConverter))]
|
||||
public CreateLightningInvoiceRequest()
|
||||
{
|
||||
|
||||
}
|
||||
public CreateLightningInvoiceRequest(LightMoney amount, string description, TimeSpan expiry)
|
||||
{
|
||||
Amount = amount;
|
||||
Description = description;
|
||||
Expiry = expiry;
|
||||
}
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonConverter(typeof(JsonConverters.TimeSpanJsonConverter))]
|
||||
public TimeSpan Expiry { get; set; }
|
||||
public bool PrivateRouteHints { get; set; }
|
||||
|
||||
|
|
25
BTCPayServer.Client/Models/GreenfieldAPIError.cs
Normal file
25
BTCPayServer.Client/Models/GreenfieldAPIError.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class GreenfieldAPIError
|
||||
{
|
||||
public GreenfieldAPIError()
|
||||
{
|
||||
|
||||
}
|
||||
public GreenfieldAPIError(string code, string message)
|
||||
{
|
||||
if (code == null)
|
||||
throw new ArgumentNullException(nameof(code));
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
Code = code;
|
||||
Message = message;
|
||||
}
|
||||
public string Code { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
26
BTCPayServer.Client/Models/GreenfieldValidationError.cs
Normal file
26
BTCPayServer.Client/Models/GreenfieldValidationError.cs
Normal file
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class GreenfieldValidationError
|
||||
{
|
||||
public GreenfieldValidationError()
|
||||
{
|
||||
|
||||
}
|
||||
public GreenfieldValidationError(string path, string message)
|
||||
{
|
||||
if (path == null)
|
||||
throw new ArgumentNullException(nameof(path));
|
||||
if (message == null)
|
||||
throw new ArgumentNullException(nameof(message));
|
||||
Path = path;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Path { get; set; }
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
|
@ -10,19 +10,21 @@ namespace BTCPayServer.Client.Models
|
|||
{
|
||||
public string Id { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public LightningInvoiceStatus Status { get; set; }
|
||||
|
||||
[JsonProperty("BOLT11")]
|
||||
public string BOLT11 { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset? PaidAt { get; set; }
|
||||
|
||||
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset ExpiresAt { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(LightMoneyJsonConverter))]
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(LightMoneyJsonConverter))]
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney AmountReceived { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,8 @@ namespace BTCPayServer.Client.Models
|
|||
{
|
||||
public class LightningNodeInformationData
|
||||
{
|
||||
public IEnumerable<string> NodeInfoList { get; set; }
|
||||
[JsonProperty("nodeURIs", ItemConverterType = typeof(NodeUriJsonConverter))]
|
||||
public NodeInfo[] NodeURIs { get; set; }
|
||||
public int BlockHeight { get; set; }
|
||||
}
|
||||
|
||||
|
@ -21,10 +22,10 @@ namespace BTCPayServer.Client.Models
|
|||
|
||||
public bool IsActive { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(LightMoneyJsonConverter))]
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney Capacity { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(LightMoneyJsonConverter))]
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
public LightMoney LocalBalance { get; set; }
|
||||
|
||||
public string ChannelPoint { get; set; }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
using BTCPayServer.Client.JsonConverters;
|
||||
using BTCPayServer.Lightning;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
@ -7,11 +9,13 @@ namespace BTCPayServer.Client.Models
|
|||
{
|
||||
public class OpenLightningChannelRequest
|
||||
{
|
||||
public ConnectToNodeRequest Node { get; set; }
|
||||
[JsonProperty(ItemConverterType = typeof(MoneyJsonConverter))]
|
||||
[JsonConverter(typeof(NodeUriJsonConverter))]
|
||||
[JsonProperty("nodeURI")]
|
||||
public NodeInfo NodeURI { get; set; }
|
||||
[JsonConverter(typeof(MoneyJsonConverter))]
|
||||
public Money ChannelAmount { get; set; }
|
||||
|
||||
[JsonProperty(ItemConverterType = typeof(FeeRateJsonConverter))]
|
||||
[JsonConverter(typeof(FeeRateJsonConverter))]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ namespace BTCPayServer.Client.Models
|
|||
{
|
||||
public class PayLightningInvoiceRequest
|
||||
{
|
||||
public string Invoice { get; set; }
|
||||
[Newtonsoft.Json.JsonProperty("BOLT11")]
|
||||
public string BOLT11 { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.4" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -2,6 +2,7 @@ using System;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
|
@ -16,6 +17,7 @@ using NBitpayClient;
|
|||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using Org.BouncyCastle.Utilities.Collections;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
|
||||
|
@ -105,13 +107,13 @@ namespace BTCPayServer.Tests
|
|||
tester.PayTester.DisableRegistration = true;
|
||||
await tester.StartAsync();
|
||||
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
|
||||
await AssertHttpError(422,
|
||||
await AssertValidationError(new[] { "Email", "Password" },
|
||||
async () => await unauthClient.CreateUser(new CreateApplicationUserRequest()));
|
||||
await AssertHttpError(422,
|
||||
await AssertValidationError(new[] { "Password" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test@gmail.com"}));
|
||||
// Pass too simple
|
||||
await AssertHttpError(422,
|
||||
await AssertValidationError(new[] { "Password" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test3@gmail.com", Password = "a"}));
|
||||
|
||||
|
@ -123,7 +125,7 @@ namespace BTCPayServer.Tests
|
|||
new CreateApplicationUserRequest() {Email = "test2@gmail.com", Password = "abceudhqw"});
|
||||
|
||||
// Duplicate email
|
||||
await AssertHttpError(422,
|
||||
await AssertValidationError(new[] { "Email" },
|
||||
async () => await unauthClient.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = "test2@gmail.com", Password = "abceudhqw"}));
|
||||
|
||||
|
@ -252,6 +254,18 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
}
|
||||
|
||||
private async Task AssertValidationError(string[] fields, Func<Task> act)
|
||||
{
|
||||
var remainingFields = fields.ToHashSet();
|
||||
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
|
||||
foreach (var field in fields)
|
||||
{
|
||||
Assert.Contains(field, ex.ValidationErrors.Select(e => e.Path).ToArray());
|
||||
remainingFields.Remove(field);
|
||||
}
|
||||
Assert.Empty(remainingFields);
|
||||
}
|
||||
|
||||
private async Task AssertHttpError(int code, Func<Task> act)
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<HttpRequestException>(act);
|
||||
|
@ -303,17 +317,17 @@ namespace BTCPayServer.Tests
|
|||
});
|
||||
Assert.NotNull(newUser2);
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
await AssertValidationError(new[] { "Email" }, async () =>
|
||||
await clientServer.CreateUser(new CreateApplicationUserRequest()
|
||||
{
|
||||
Email = $"{Guid.NewGuid()}", Password = Guid.NewGuid().ToString()
|
||||
}));
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
await AssertValidationError(new[] { "Password" }, async () =>
|
||||
await clientServer.CreateUser(
|
||||
new CreateApplicationUserRequest() {Email = $"{Guid.NewGuid()}@g.com",}));
|
||||
|
||||
await Assert.ThrowsAsync<HttpRequestException>(async () =>
|
||||
await AssertValidationError(new[] { "Email" }, async () =>
|
||||
await clientServer.CreateUser(
|
||||
new CreateApplicationUserRequest() {Password = Guid.NewGuid().ToString()}));
|
||||
}
|
||||
|
@ -375,16 +389,16 @@ namespace BTCPayServer.Tests
|
|||
//create payment request
|
||||
|
||||
//validation errors
|
||||
await AssertHttpError(422, async () =>
|
||||
await AssertValidationError(new[] { "Amount", "Currency" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId, new CreatePaymentRequestRequest() {Title = "A"});
|
||||
});
|
||||
await AssertHttpError(422, async () =>
|
||||
await AssertValidationError(new[] { "Amount" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "BTC", Amount = 0});
|
||||
});
|
||||
await AssertHttpError(422, async () =>
|
||||
await AssertValidationError(new[] { "Currency" }, async () =>
|
||||
{
|
||||
await client.CreatePaymentRequest(user.StoreId,
|
||||
new CreatePaymentRequestRequest() {Title = "A", Currency = "helloinvalid", Amount = 1});
|
||||
|
|
|
@ -252,23 +252,39 @@ namespace BTCPayServer.Tests
|
|||
|
||||
public bool IsAdmin { get; internal set; }
|
||||
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
public void RegisterLightningNode(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType).GetAwaiter().GetResult();
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType)
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
|
||||
{
|
||||
var storeController = this.GetController<StoresController>();
|
||||
|
||||
string connectionString = null;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri;
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=charge;server=" + parent.MerchantCharge.Client.Uri.AbsoluteUri;
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
|
||||
else
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.CustomerLightningD).Address.AbsoluteUri;
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ using MemoryCache = Microsoft.Extensions.Caching.Memory.MemoryCache;
|
|||
using Newtonsoft.Json.Schema;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using TwentyTwenty.Storage;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
|
@ -742,6 +743,91 @@ namespace BTCPayServer.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningAPI()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning, false);
|
||||
|
||||
var merchant = tester.NewAccount();
|
||||
merchant.GrantAccess(true);
|
||||
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||
var merchantClient = await merchant.CreateClient($"btcpay.store.canuselightningnode:{merchant.StoreId}");
|
||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var client = await user.CreateClient("btcpay.server.canuseinternallightningnode");
|
||||
var err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||
Assert.Contains("503", err.Message);
|
||||
// Not permission for the store!
|
||||
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels(user.StoreId, "BTC"));
|
||||
Assert.Contains("403", err.Message);
|
||||
var invoiceData = await client.CreateLightningInvoice("BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = LightMoney.Satoshis(1000),
|
||||
Description = "lol",
|
||||
Expiry = TimeSpan.FromSeconds(400),
|
||||
PrivateRouteHints = false
|
||||
});
|
||||
var chargeInvoice = invoiceData;
|
||||
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
|
||||
|
||||
client = await user.CreateClient($"btcpay.store.canuselightningnode:{user.StoreId}");
|
||||
// Not permission for the server
|
||||
err = await Assert.ThrowsAsync<HttpRequestException>(async () => await client.GetLightningNodeChannels("BTC"));
|
||||
Assert.Contains("403", err.Message);
|
||||
|
||||
var data = await client.GetLightningNodeChannels(user.StoreId, "BTC");
|
||||
Assert.Equal(2, data.Count());
|
||||
BitcoinAddress.Create(await client.GetLightningDepositAddress(user.StoreId, "BTC"), Network.RegTest);
|
||||
|
||||
invoiceData = await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = LightMoney.Satoshis(1000),
|
||||
Description = "lol",
|
||||
Expiry = TimeSpan.FromSeconds(400),
|
||||
PrivateRouteHints = false
|
||||
});
|
||||
|
||||
Assert.NotNull(await client.GetLightningInvoice(user.StoreId, "BTC", invoiceData.Id));
|
||||
|
||||
await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||
{
|
||||
BOLT11 = merchantInvoice.BOLT11
|
||||
});
|
||||
await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.PayLightningInvoice(user.StoreId, "BTC", new PayLightningInvoiceRequest()
|
||||
{
|
||||
BOLT11 = "lol"
|
||||
}));
|
||||
|
||||
var validationErr = await Assert.ThrowsAsync<GreenFieldValidationException>(async () => await client.CreateLightningInvoice(user.StoreId, "BTC", new CreateLightningInvoiceRequest()
|
||||
{
|
||||
Amount = -1,
|
||||
Expiry = TimeSpan.FromSeconds(-1),
|
||||
Description = null
|
||||
}));
|
||||
Assert.Equal(2, validationErr.ValidationErrors.Length);
|
||||
|
||||
var invoice = await merchantClient.GetLightningInvoice(merchant.StoreId, "BTC", merchantInvoice.Id);
|
||||
Assert.NotNull(invoice.PaidAt);
|
||||
Assert.Equal(LightMoney.Satoshis(1000), invoice.Amount);
|
||||
// Amount received might be bigger because of internal implementation shit from lightning
|
||||
Assert.True(LightMoney.Satoshis(1000) <= invoice.AmountReceived);
|
||||
|
||||
var info = await client.GetLightningNodeInfo(user.StoreId, "BTC");
|
||||
Assert.Single(info.NodeURIs);
|
||||
Assert.NotEqual(0, info.BlockHeight);
|
||||
}
|
||||
}
|
||||
|
||||
async Task CanSendLightningPaymentCore(ServerTester tester, TestAccount user)
|
||||
{
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice()
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.1.15" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.0" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
public async Task<ActionResult<ApiKeyData>> CreateKey(CreateApiKeyRequest request)
|
||||
{
|
||||
if (request is null)
|
||||
return BadRequest();
|
||||
return NotFound();
|
||||
var key = new APIKeyData()
|
||||
{
|
||||
Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)),
|
||||
|
@ -74,7 +74,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
public async Task<IActionResult> RevokeKey(string apikey)
|
||||
{
|
||||
if (string.IsNullOrEmpty(apikey))
|
||||
return BadRequest();
|
||||
return NotFound();
|
||||
if (await _apiKeyRepository.Remove(apikey, _userManager.GetUserId(User)))
|
||||
return Ok();
|
||||
else
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BTCPayServer.Client.Models;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
public static class GreenFieldUtils
|
||||
{
|
||||
public static IActionResult GetValidationResponse(this ControllerBase controller)
|
||||
public static IActionResult CreateValidationError(this ControllerBase controller, ModelStateDictionary modelState)
|
||||
{
|
||||
return controller.UnprocessableEntity( new ValidationProblemDetails(controller.ModelState));
|
||||
}
|
||||
public static IActionResult GetExceptionResponse(this ControllerBase controller, Exception e)
|
||||
{
|
||||
return GetGeneralErrorResponse(controller, e.Message);
|
||||
}
|
||||
|
||||
public static IActionResult GetGeneralErrorResponse(this ControllerBase controller, string error)
|
||||
{
|
||||
return controller.BadRequest( new ProblemDetails()
|
||||
List<GreenfieldValidationError> errors = new List<GreenfieldValidationError>();
|
||||
foreach (var error in modelState)
|
||||
{
|
||||
Detail = error
|
||||
});
|
||||
foreach (var errorMessage in error.Value.Errors)
|
||||
{
|
||||
errors.Add(new GreenfieldValidationError(error.Key, errorMessage.ErrorMessage));
|
||||
}
|
||||
}
|
||||
return controller.UnprocessableEntity(errors.ToArray());
|
||||
}
|
||||
public static IActionResult CreateAPIError(this ControllerBase controller, string errorCode, string errorMessage)
|
||||
{
|
||||
return controller.BadRequest(new GreenfieldAPIError(errorCode, errorMessage));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[LightningUnavailableExceptionFilter]
|
||||
public class InternalLightningNodeApiController : LightningNodeApiController
|
||||
{
|
||||
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||
|
@ -64,7 +65,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
[Authorize(Policy = Policies.CanUseInternalLightningNode,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/server/lightning/{cryptoCode}/address")]
|
||||
[HttpPost("~/api/v1/server/lightning/{cryptoCode}/address")]
|
||||
public override Task<IActionResult> GetDepositAddress(string cryptoCode)
|
||||
{
|
||||
return base.GetDepositAddress(cryptoCode);
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[LightningUnavailableExceptionFilter]
|
||||
public class StoreLightningNodeApiController : LightningNodeApiController
|
||||
{
|
||||
private readonly BTCPayServerOptions _btcPayServerOptions;
|
||||
|
@ -65,7 +66,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/address")]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/address")]
|
||||
public override Task<IActionResult> GetDepositAddress(string cryptoCode)
|
||||
{
|
||||
return base.GetDepositAddress(cryptoCode);
|
||||
|
@ -81,7 +82,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
[Authorize(Policy = Policies.CanUseLightningNodeInStore,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/{id}")]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/{id}")]
|
||||
public override Task<IActionResult> GetInvoice(string cryptoCode, string id)
|
||||
{
|
||||
return base.GetInvoice(cryptoCode, id);
|
||||
|
|
|
@ -5,12 +5,30 @@ using System.Threading.Tasks;
|
|||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments.Changelly.Models;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using NBitcoin;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
public class LightningUnavailableExceptionFilter : Attribute, IExceptionFilter
|
||||
{
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
if (context.Exception is NBitcoin.JsonConverters.JsonObjectException jsonObject)
|
||||
{
|
||||
context.Result = new ObjectResult(new GreenfieldValidationError(jsonObject.Path, jsonObject.Message));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Result = new StatusCodeResult(503);
|
||||
}
|
||||
context.ExceptionHandled = true;
|
||||
}
|
||||
}
|
||||
public abstract class LightningNodeApiController : Controller
|
||||
{
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
|
@ -32,20 +50,12 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
var info = await lightningClient.GetInfo();
|
||||
return Ok(new LightningNodeInformationData()
|
||||
{
|
||||
var info = await lightningClient.GetInfo();
|
||||
return Ok(new LightningNodeInformationData()
|
||||
{
|
||||
BlockHeight = info.BlockHeight,
|
||||
NodeInfoList = info.NodeInfoList.Select(nodeInfo => nodeInfo.ToString())
|
||||
});
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
}
|
||||
BlockHeight = info.BlockHeight,
|
||||
NodeURIs = info.NodeInfoList.Select(nodeInfo => nodeInfo).ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> ConnectToNode(string cryptoCode, ConnectToNodeRequest request)
|
||||
|
@ -56,23 +66,23 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
if (TryGetNodeInfo(request, out var nodeInfo))
|
||||
if (request?.NodeURI is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.NodeId), "A valid node info was not provided to connect to");
|
||||
ModelState.AddModelError(nameof(request.NodeURI), "A valid node info was not provided to connect to");
|
||||
}
|
||||
|
||||
if (CheckValidation(out var errorActionResult))
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return errorActionResult;
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
try
|
||||
var result = await lightningClient.ConnectTo(request.NodeURI);
|
||||
switch (result)
|
||||
{
|
||||
await lightningClient.ConnectTo(nodeInfo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
case ConnectionResult.Ok:
|
||||
return Ok();
|
||||
case ConnectionResult.CouldNotConnect:
|
||||
return this.CreateAPIError("could-not-connect", "Could not connect to the remote node");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
|
@ -86,26 +96,19 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
var channels = await lightningClient.ListChannels();
|
||||
return Ok(channels.Select(channel => new LightningChannelData()
|
||||
{
|
||||
var channels = await lightningClient.ListChannels();
|
||||
return Ok(channels.Select(channel => new LightningChannelData()
|
||||
{
|
||||
Capacity = channel.Capacity,
|
||||
ChannelPoint = channel.ChannelPoint.ToString(),
|
||||
IsActive = channel.IsActive,
|
||||
IsPublic = channel.IsPublic,
|
||||
LocalBalance = channel.LocalBalance,
|
||||
RemoteNode = channel.RemoteNode.ToString()
|
||||
}));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
}
|
||||
Capacity = channel.Capacity,
|
||||
ChannelPoint = channel.ChannelPoint.ToString(),
|
||||
IsActive = channel.IsActive,
|
||||
IsPublic = channel.IsPublic,
|
||||
LocalBalance = channel.LocalBalance,
|
||||
RemoteNode = channel.RemoteNode.ToString()
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
|
||||
public virtual async Task<IActionResult> OpenChannel(string cryptoCode, OpenLightningChannelRequest request)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, true);
|
||||
|
@ -114,9 +117,9 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
if (TryGetNodeInfo(request.Node, out var nodeInfo))
|
||||
if (request?.NodeURI is null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Node),
|
||||
ModelState.AddModelError(nameof(request.NodeURI),
|
||||
"A valid node info was not provided to open a channel with");
|
||||
}
|
||||
|
||||
|
@ -138,28 +141,43 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
ModelState.AddModelError(nameof(request.FeeRate), "FeeRate must be more than 0");
|
||||
}
|
||||
|
||||
if (CheckValidation(out var errorActionResult))
|
||||
if (ModelState.IsValid)
|
||||
{
|
||||
return errorActionResult;
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
try
|
||||
var response = await lightningClient.OpenChannel(new Lightning.OpenChannelRequest()
|
||||
{
|
||||
var response = await lightningClient.OpenChannel(new Lightning.OpenChannelRequest()
|
||||
{
|
||||
ChannelAmount = request.ChannelAmount, FeeRate = request.FeeRate, NodeInfo = nodeInfo
|
||||
});
|
||||
if (response.Result == OpenChannelResult.Ok)
|
||||
{
|
||||
ChannelAmount = request.ChannelAmount,
|
||||
FeeRate = request.FeeRate,
|
||||
NodeInfo = request.NodeURI
|
||||
});
|
||||
|
||||
string errorCode, errorMessage;
|
||||
switch (response.Result)
|
||||
{
|
||||
case OpenChannelResult.Ok:
|
||||
return Ok();
|
||||
}
|
||||
|
||||
return this.GetGeneralErrorResponse(response.Result.ToString());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
case OpenChannelResult.AlreadyExists:
|
||||
errorCode = "channel-already-exists";
|
||||
errorMessage = "The channel already exists";
|
||||
break;
|
||||
case OpenChannelResult.CannotAffordFunding:
|
||||
errorCode = "cannot-afford-funding";
|
||||
errorMessage = "Not enough money to open a channel";
|
||||
break;
|
||||
case OpenChannelResult.NeedMoreConf:
|
||||
errorCode = "need-more-confirmations";
|
||||
errorMessage = "Need to wait for more confirmations";
|
||||
break;
|
||||
case OpenChannelResult.PeerNotConnected:
|
||||
errorCode = "peer-not-connected";
|
||||
errorMessage = "Not connected to peer";
|
||||
break;
|
||||
default:
|
||||
throw new NotSupportedException("Unknown OpenChannelResult");
|
||||
}
|
||||
return this.CreateAPIError(errorCode, errorMessage);
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetDepositAddress(string cryptoCode)
|
||||
|
@ -170,7 +188,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok((await lightningClient.GetDepositAddress()).ToString());
|
||||
return Ok(new JValue((await lightningClient.GetDepositAddress()).ToString()));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> PayInvoice(string cryptoCode, PayLightningInvoiceRequest lightningInvoice)
|
||||
|
@ -182,85 +200,78 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
if (lightningInvoice?.BOLT11 is null ||
|
||||
!BOLT11PaymentRequest.TryParse(lightningInvoice.BOLT11, out _, network.NBitcoinNetwork))
|
||||
{
|
||||
BOLT11PaymentRequest.TryParse(lightningInvoice.Invoice, out var bolt11PaymentRequest, network.NBitcoinNetwork);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
ModelState.AddModelError(nameof(lightningInvoice.Invoice), "The BOLT11 invoice was invalid.");
|
||||
ModelState.AddModelError(nameof(lightningInvoice.BOLT11), "The BOLT11 invoice was invalid.");
|
||||
}
|
||||
|
||||
if (CheckValidation(out var errorActionResult))
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return errorActionResult;
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
var result = await lightningClient.Pay(lightningInvoice.Invoice);
|
||||
var result = await lightningClient.Pay(lightningInvoice.BOLT11);
|
||||
switch (result.Result)
|
||||
{
|
||||
case PayResult.CouldNotFindRoute:
|
||||
return this.GetGeneralErrorResponse("Could not find route");
|
||||
return this.CreateAPIError("could-not-find-route", "Impossible to find a route to the peer");
|
||||
case PayResult.Error:
|
||||
return this.GetGeneralErrorResponse(result.ErrorDetail);
|
||||
return this.CreateAPIError("generic-error", result.ErrorDetail);
|
||||
case PayResult.Ok:
|
||||
return Ok();
|
||||
default:
|
||||
throw new NotSupportedException("Unsupported Payresult");
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInvoice(string cryptoCode, string id)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
|
||||
|
||||
if (lightningClient == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
try
|
||||
var inv = await lightningClient.GetInvoice(id);
|
||||
if (inv == null)
|
||||
{
|
||||
var inv = await lightningClient.GetInvoice(id);
|
||||
if (inv == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(ToModel(inv));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
return NotFound();
|
||||
}
|
||||
return Ok(ToModel(inv));
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> CreateInvoice(string cryptoCode, CreateLightningInvoiceRequest request)
|
||||
{
|
||||
var lightningClient = await GetLightningClient(cryptoCode, false);
|
||||
|
||||
|
||||
if (lightningClient == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (CheckValidation(out var errorActionResult))
|
||||
if (request.Amount < LightMoney.Zero)
|
||||
{
|
||||
return errorActionResult;
|
||||
ModelState.AddModelError(nameof(request.Amount), "Amount should be more or equals to 0");
|
||||
}
|
||||
|
||||
try
|
||||
if (request.Expiry <= TimeSpan.Zero)
|
||||
{
|
||||
var invoice = await lightningClient.CreateInvoice(
|
||||
new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints
|
||||
},
|
||||
CancellationToken.None);
|
||||
ModelState.AddModelError(nameof(request.Expiry), "Expiry should be more than 0");
|
||||
}
|
||||
|
||||
return Ok(ToModel(invoice));
|
||||
}
|
||||
catch (Exception e)
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.GetExceptionResponse(e);
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var invoice = await lightningClient.CreateInvoice(
|
||||
new CreateInvoiceParams(request.Amount, request.Description, request.Expiry)
|
||||
{
|
||||
PrivateRouteHints = request.PrivateRouteHints
|
||||
},
|
||||
CancellationToken.None);
|
||||
return Ok(ToModel(invoice));
|
||||
}
|
||||
|
||||
private LightningInvoiceData ToModel(LightningInvoice invoice)
|
||||
|
@ -277,40 +288,12 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
};
|
||||
}
|
||||
|
||||
private bool CheckValidation(out IActionResult result)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
result = this.GetValidationResponse();
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
protected bool CanUseInternalLightning(bool doingAdminThings)
|
||||
{
|
||||
return (_btcPayServerEnvironment.IsDevelopping || User.IsInRole(Roles.ServerAdmin) ||
|
||||
(_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings));
|
||||
}
|
||||
|
||||
|
||||
private bool TryGetNodeInfo(ConnectToNodeRequest request, out NodeInfo nodeInfo)
|
||||
{
|
||||
nodeInfo = null;
|
||||
if (!string.IsNullOrEmpty(request.NodeInfo)) return NodeInfo.TryParse(request.NodeInfo, out nodeInfo);
|
||||
try
|
||||
{
|
||||
nodeInfo = new NodeInfo(new PubKey(request.NodeId), request.NodeHost, request.NodePort);
|
||||
return true;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
if (!string.IsNullOrEmpty(data.CustomCSSLink) && data.CustomCSSLink.Length > 500)
|
||||
ModelState.AddModelError(nameof(data.CustomCSSLink), "CustomCSSLink is 500 chars max");
|
||||
|
||||
return !ModelState.IsValid ? this.GetValidationResponse() :null;
|
||||
return !ModelState.IsValid ? this.CreateValidationError(ModelState) :null;
|
||||
}
|
||||
|
||||
private static Client.Models.PaymentRequestData FromModel(PaymentRequestData data)
|
||||
|
|
|
@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
if (!_storeRepository.CanDeleteStores())
|
||||
{
|
||||
return this.GetGeneralErrorResponse(
|
||||
return this.CreateAPIError("unsupported",
|
||||
"BTCPay Server is using a database server that does not allow you to remove stores.");
|
||||
}
|
||||
await _storeRepository.RemoveStore(storeId, _userManager.GetUserId(User));
|
||||
|
@ -195,7 +195,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
if(request.PaymentTolerance < 0 && request.PaymentTolerance > 100)
|
||||
ModelState.AddModelError(nameof(request.PaymentTolerance), "PaymentTolerance can only be between 0 and 100 percent");
|
||||
|
||||
return !ModelState.IsValid ? this.GetValidationResponse() : null;
|
||||
return !ModelState.IsValid ? this.CreateValidationError(ModelState) : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc;
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using NicolasDorier.RateLimits;
|
||||
using BTCPayServer.Client;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
|
@ -75,7 +76,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.GetValidationResponse();
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
var anyAdmin = (await _userManager.GetUsersInRoleAsync(Roles.ServerAdmin)).Any();
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>() ?? new PoliciesSettings();
|
||||
|
@ -118,7 +119,7 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
ModelState.AddModelError(nameof(request.Password), error.Description);
|
||||
}
|
||||
return this.GetValidationResponse();
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (!isAdmin)
|
||||
{
|
||||
|
@ -130,9 +131,12 @@ namespace BTCPayServer.Controllers.GreenField
|
|||
{
|
||||
foreach (var error in identityResult.Errors)
|
||||
{
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
if (error.Code == "DuplicateUserName")
|
||||
ModelState.AddModelError(nameof(request.Email), error.Description);
|
||||
else
|
||||
ModelState.AddModelError(string.Empty, error.Description);
|
||||
}
|
||||
return this.GetValidationResponse();
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (request.IsAdministrator is true)
|
||||
|
|
|
@ -51,7 +51,7 @@ namespace BTCPayServer
|
|||
derivationSchemeSettings.AccountOriginal = null; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -14,57 +14,38 @@
|
|||
"components": {
|
||||
"schemas": {
|
||||
"ValidationProblemDetails": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"errors": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
"type": "array",
|
||||
"description": "An array of validation errors of the request",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"description": "A specific validation error on a json property",
|
||||
"properties": {
|
||||
"path": {
|
||||
"type": "string",
|
||||
"nullable": false,
|
||||
"description": "The json path of the property which failed validation"
|
||||
},
|
||||
"message": {
|
||||
"type": "string",
|
||||
"nullable": false,
|
||||
"description": "User friendly error message about the validation"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ProblemDetails": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "Description of an error happening during processing of the request",
|
||||
"properties": {
|
||||
"type": {
|
||||
"code": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"nullable": false,
|
||||
"description": "An error code describing the error"
|
||||
},
|
||||
"title": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"status": {
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"nullable": true
|
||||
},
|
||||
"detail": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"instance": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"extensions": {
|
||||
"type": "object",
|
||||
"nullable": true,
|
||||
"additionalProperties": {}
|
||||
"nullable": false,
|
||||
"description": "User friendly error message about the error"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,68 +2,40 @@
|
|||
"components": {
|
||||
"schemas": {
|
||||
"ConnectToNodeRequest": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"nodeInfo": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"nodeId": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"nodeHost": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"nodePort": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"CreateLightningInvoiceRequest": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"amount": {
|
||||
"nodeURI": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LightMoney"
|
||||
}
|
||||
]
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
},
|
||||
"expiry": {
|
||||
"type": "string",
|
||||
"format": "time-span"
|
||||
},
|
||||
"privateRouteHints": {
|
||||
"type": "boolean",
|
||||
"nullable": true
|
||||
"description": "Node URI in the form `pubkey@endpoint[:port]`"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LightMoney": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "a number amount wrapped in a string, represented in millistatoshi (00000000001BTC = 1 mSAT)",
|
||||
"additionalProperties": false
|
||||
"CreateLightningInvoiceRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"amount": {
|
||||
"type": "string",
|
||||
"description": "Amount wrapped in a string, represented in a millistatoshi string. (1000 millisatoshi = 1 satoshi)",
|
||||
"nullable": false
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"nullable": true,
|
||||
"description": "Description of the invoice in the BOLT11"
|
||||
},
|
||||
"expiry": {
|
||||
"type": "integer",
|
||||
"description": "Expiration time in seconds"
|
||||
},
|
||||
"privateRouteHints": {
|
||||
"type": "boolean",
|
||||
"nullable": true,
|
||||
"default": false,
|
||||
"description": "True if the invoice should include private route hints"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LightningChannelData": {
|
||||
"type": "object",
|
||||
|
@ -71,29 +43,26 @@
|
|||
"properties": {
|
||||
"remoteNode": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"nullable": false,
|
||||
"description": "The public key of the node (Node ID)"
|
||||
},
|
||||
"isPublic": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Whether the node is public"
|
||||
},
|
||||
"isActive": {
|
||||
"type": "boolean"
|
||||
"type": "boolean",
|
||||
"description": "Whether the node is online"
|
||||
},
|
||||
"capacity": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LightMoney"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"description": "The capacity of the channel in millisatoshi",
|
||||
"nullable": false
|
||||
},
|
||||
"localBalance": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LightMoney"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"description": "The local balance of the channel in millisatoshi",
|
||||
"nullable": false
|
||||
},
|
||||
"channelPoint": {
|
||||
"type": "string",
|
||||
|
@ -107,39 +76,32 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"description": "The invoice's ID"
|
||||
},
|
||||
"status": {
|
||||
"$ref": "#/components/schemas/LightningInvoiceStatus"
|
||||
},
|
||||
"bolT11": {
|
||||
"BOLT11": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"description": "The BOLT11 representation of the invoice",
|
||||
"nullable": false
|
||||
},
|
||||
"paidAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"type": "integer",
|
||||
"description": "The unix timestamp when the invoice got paid",
|
||||
"nullable": true
|
||||
},
|
||||
"expiresAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
"type": "integer",
|
||||
"description": "The unix timestamp when the invoice expires"
|
||||
},
|
||||
"amount": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LightMoney"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"description": "The amount of the invoice in millisatoshi"
|
||||
},
|
||||
"amountReceived": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/LightMoney"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"description": "The amount received in millisatoshi"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -159,28 +121,26 @@
|
|||
},
|
||||
"LightningNodeInformationData": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"nodeInfoList": {
|
||||
"nodeURIs": {
|
||||
"type": "array",
|
||||
"nullable": true,
|
||||
"description": "Node URIs to connect to this node in the form `pubkey@endpoint[:port]`",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"blockHeight": {
|
||||
"type": "integer",
|
||||
"format": "int32"
|
||||
"description": "The block height of the lightning node"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PayLightningInvoiceRequest": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"invoice": {
|
||||
"BOLT11": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
"description": "The BOLT11 of the invoice to pay"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -188,48 +148,19 @@
|
|||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"node": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/ConnectToNodeRequest"
|
||||
}
|
||||
]
|
||||
"nodeURI": {
|
||||
"type": "string",
|
||||
"description": "Node URI in the form `pubkey@endpoint[:port]`"
|
||||
},
|
||||
"channelAmount": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/Money"
|
||||
}
|
||||
]
|
||||
"type": "string",
|
||||
"description": "The amount to fund (in satoshi)"
|
||||
},
|
||||
"feeRate": {
|
||||
"nullable": true,
|
||||
"oneOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/FeeRate"
|
||||
}
|
||||
]
|
||||
"type": "number",
|
||||
"description": "The amount to fund (in satoshi per byte)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Money": {
|
||||
"type": "string",
|
||||
"format": "int64",
|
||||
"description": "a number amount wrapped in a string, represented in satoshi (00000001BTC = 1 sat)"
|
||||
},
|
||||
"FeeRate": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
{
|
||||
"type": "number",
|
||||
"format": "float"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,28 +30,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -91,7 +71,7 @@
|
|||
"description": "Successfully connected"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -101,7 +81,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `could-not-connect`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -110,8 +90,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -170,19 +150,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
}
|
||||
|
@ -219,7 +186,7 @@
|
|||
"description": "Successfully opened"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -229,7 +196,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `channel-already-exists`, `cannot-afford-funding`, `need-more-confirmations`, `peer-not-connected`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -238,9 +205,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
}
|
||||
|
@ -266,7 +230,7 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/server/lightning/{cryptoCode}/address": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Lightning (Internal Node)"
|
||||
],
|
||||
|
@ -290,13 +254,14 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "A bitcoin address belonging to the lightning node"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -351,8 +316,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration or the specified invoice was not found "
|
||||
|
@ -392,7 +357,7 @@
|
|||
"description": "Successfully paid"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -402,7 +367,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `could-not-find-route`, `generic-error`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -411,8 +376,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -468,28 +433,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -500,7 +445,7 @@
|
|||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PayLightningInvoiceRequest"
|
||||
"$ref": "#/components/schemas/CreateLightningInvoiceRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,28 +39,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -109,7 +89,7 @@
|
|||
"description": "Successfully connected"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -119,7 +99,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `could-not-connect`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -128,8 +108,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -197,19 +177,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
}
|
||||
|
@ -255,7 +222,7 @@
|
|||
"description": "Successfully opened"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -265,7 +232,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `channel-already-exists`, `cannot-afford-funding`, `need-more-confirmations`, `peer-not-connected`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -274,8 +241,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -302,7 +269,7 @@
|
|||
}
|
||||
},
|
||||
"/api/v1/stores/{storeId}/lightning/{cryptoCode}/address": {
|
||||
"get": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Lightning (Store)"
|
||||
],
|
||||
|
@ -314,7 +281,8 @@
|
|||
"required": true,
|
||||
"description": "The cryptoCode of the lightning-node to query",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"description": "A bitcoin address belonging to the lightning node"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -340,8 +308,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -406,8 +374,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration or the specified invoice was not found "
|
||||
|
@ -456,7 +424,7 @@
|
|||
"description": "Successfully paid"
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"description": "Unable to validate the request",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -466,7 +434,7 @@
|
|||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"description": "Wellknown error codes are: `could-not-find-route`, `generic-error`",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
@ -475,8 +443,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
@ -541,28 +509,8 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "A list of errors that occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ValidationProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "An error occurred",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProblemDetails"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "If you are authenticated but forbidden"
|
||||
"503": {
|
||||
"description": "Unable to access the lightning node"
|
||||
},
|
||||
"404": {
|
||||
"description": "The lightning node configuration was not found"
|
||||
|
|
Loading…
Add table
Reference in a new issue