Merge pull request #1 from btcpayserver/master

upstream pull
This commit is contained in:
Matthew
2020-07-01 11:36:58 -07:00
committed by GitHub
816 changed files with 44958 additions and 12269 deletions

View File

@@ -66,6 +66,20 @@ jobs:
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
arm64v8:
machine:
enabled: true
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
#
sudo docker build --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
multiarch:
machine:
enabled: true
@@ -80,9 +94,10 @@ jobs:
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
#
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
workflows:
@@ -104,22 +119,30 @@ workflows:
# ignore any commit on any branch by default
branches:
ignore: /.*/
# only act on version tags v1.0.0.88
# only act on version tags v1.0.0.88 or v1.0.2-1
# OR feature tags like vlndseedbackup
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
- arm32v7:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
- multiarch:
requires:
- amd64
- arm32v7
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
- arm64v8:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*)|(v[a-z]+)/
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/
- multiarch:
requires:
- amd64
- arm32v7
- arm64v8
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[0-9]+)?)|(v[a-z]+)/

View File

@@ -10,6 +10,7 @@ root = true
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8
[project.json]
indent_size = 2
@@ -146,4 +147,4 @@ indent_size = 2
[*.sh]
end_of_line = lf
[*.{cmd, bat}]
end_of_line = crlf
end_of_line = crlf

View File

@@ -14,6 +14,9 @@ A clear and concise description of what the bug is.
**Logs (if applicable)**
Basic logs can be found in Server Settings > Logs.
**Setup Parameters**
If you're reporting a deployment issue run `. btcpay-setup.sh -i` and paste your the paremeters by obscuring private information.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'

1
.gitignore vendored
View File

@@ -293,3 +293,4 @@ BTCPayServer/wwwroot/bundles/*
!BTCPayServer/wwwroot/bundles/.gitignore
.vscode
BTCPayServer/testpwd

View File

@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="5.0.43" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<ApiKeyData> GetCurrentAPIKeyInfo(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token);
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task<ApiKeyData> CreateAPIKey(CreateApiKeyRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<ApiKeyData>(response);
}
public virtual async Task RevokeCurrentAPIKeyInfo(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task RevokeAPIKey(string apikey, CancellationToken token = default)
{
if (apikey == null)
throw new ArgumentNullException(nameof(apikey));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/api-keys/{apikey}", null, HttpMethod.Delete), token);
await HandleResponse(response);
}
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public static Uri GenerateAuthorizeUri(Uri btcpayHost, string[] permissions, bool strict = true,
bool selectiveStores = false)
{
var result = new UriBuilder(btcpayHost);
result.Path = "api-keys/authorize";
AppendPayloadToQuery(result,
new Dictionary<string, object>()
{
{"strict", strict}, {"selectiveStores", selectiveStores}, {"permissions", permissions}
});
return result.Uri;
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<ApiHealthData> GetHealth(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/health"), token);
return await HandleResponse<ApiHealthData>(response);
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/info",
method: HttpMethod.Get), token);
return await HandleResponse<LightningNodeInformationData>(response);
}
public async Task ConnectToLightningNode(string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/connect", bodyPayload: request,
method: HttpMethod.Post), token);
await HandleResponse(response);
}
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/channels",
method: HttpMethod.Get), token);
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
}
public async Task<string> OpenLightningChannel(string cryptoCode, OpenLightningChannelRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/channels", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<string>(response);
}
public async Task<string> GetLightningDepositAddress(string cryptoCode, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/address", method: HttpMethod.Post), token);
return await HandleResponse<string>(response);
}
public async Task PayLightningInvoice(string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
method: HttpMethod.Post), token);
await HandleResponse(response);
}
public async Task<LightningInvoiceData> GetLightningInvoice(string cryptoCode,
string invoiceId, CancellationToken token = default)
{
if (invoiceId == null)
throw new ArgumentNullException(nameof(invoiceId));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices/{invoiceId}",
method: HttpMethod.Get), token);
return await HandleResponse<LightningInvoiceData>(response);
}
public async Task<LightningInvoiceData> CreateLightningInvoice(string cryptoCode, CreateLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/server/lightning/{cryptoCode}/invoices", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<LightningInvoiceData>(response);
}
}
}

View File

@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<LightningNodeInformationData> GetLightningNodeInfo(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/info",
method: HttpMethod.Get), token);
return await HandleResponse<LightningNodeInformationData>(response);
}
public async Task ConnectToLightningNode(string storeId, string cryptoCode, ConnectToNodeRequest request,
CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/connect", bodyPayload: request,
method: HttpMethod.Post), token);
await HandleResponse(response);
}
public async Task<IEnumerable<LightningChannelData>> GetLightningNodeChannels(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/channels",
method: HttpMethod.Get), token);
return await HandleResponse<IEnumerable<LightningChannelData>>(response);
}
public async Task<string> OpenLightningChannel(string storeId, string cryptoCode, OpenLightningChannelRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/channels", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<string>(response);
}
public async Task<string> GetLightningDepositAddress(string storeId, string cryptoCode,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/address", method: HttpMethod.Post),
token);
return await HandleResponse<string>(response);
}
public async Task PayLightningInvoice(string storeId, string cryptoCode, PayLightningInvoiceRequest request,
CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/pay", bodyPayload: request,
method: HttpMethod.Post), token);
await HandleResponse(response);
}
public async Task<LightningInvoiceData> GetLightningInvoice(string storeId, string cryptoCode,
string invoiceId, CancellationToken token = default)
{
if (invoiceId == null)
throw new ArgumentNullException(nameof(invoiceId));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices/{invoiceId}",
method: HttpMethod.Get), token);
return await HandleResponse<LightningInvoiceData>(response);
}
public async Task<LightningInvoiceData> CreateLightningInvoice(string storeId, string cryptoCode,
CreateLightningInvoiceRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/lightning/{cryptoCode}/invoices", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<LightningInvoiceData>(response);
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<PaymentRequestData>> GetPaymentRequests(string storeId,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests"), token);
return await HandleResponse<IEnumerable<PaymentRequestData>>(response);
}
public virtual async Task<PaymentRequestData> GetPaymentRequest(string storeId, string paymentRequestId,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}"), token);
return await HandleResponse<PaymentRequestData>(response);
}
public virtual async Task ArchivePaymentRequest(string storeId, string paymentRequestId,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}",
method: HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task<PaymentRequestData> CreatePaymentRequest(string storeId,
CreatePaymentRequestRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests", bodyPayload: request,
method: HttpMethod.Post), token);
return await HandleResponse<PaymentRequestData>(response);
}
public virtual async Task<PaymentRequestData> UpdatePaymentRequest(string storeId, string paymentRequestId,
UpdatePaymentRequestRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests/{paymentRequestId}", bodyPayload: request,
method: HttpMethod.Put), token);
return await HandleResponse<PaymentRequestData>(response);
}
}
}

View File

@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public async Task<PullPaymentData> CreatePullPayment(string storeId, CreatePullPaymentRequest request, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/pull-payments", bodyPayload: request, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PullPaymentData>(response);
}
public async Task<PullPaymentData> GetPullPayment(string pullPaymentId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}", method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PullPaymentData>(response);
}
public async Task<PullPaymentData[]> GetPullPayments(string storeId, bool includeArchived = false, CancellationToken cancellationToken = default)
{
Dictionary<string, object> query = new Dictionary<string, object>();
query.Add("includeArchived", includeArchived);
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/pull-payments", queryPayload: query, method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PullPaymentData[]>(response);
}
public async Task ArchivePullPayment(string storeId, string pullPaymentId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}", method: HttpMethod.Delete), cancellationToken);
await HandleResponse(response);
}
public async Task<PayoutData[]> GetPayouts(string pullPaymentId, bool includeCancelled = false, CancellationToken cancellationToken = default)
{
Dictionary<string, object> query = new Dictionary<string, object>();
query.Add("includeCancelled", includeCancelled);
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", queryPayload: query, method: HttpMethod.Get), cancellationToken);
return await HandleResponse<PayoutData[]>(response);
}
public async Task<PayoutData> CreatePayout(string pullPaymentId, CreatePayoutRequest payoutRequest, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/payouts", bodyPayload: payoutRequest, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PayoutData>(response);
}
public async Task CancelPayout(string storeId, string payoutId, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}", method: HttpMethod.Delete), cancellationToken);
await HandleResponse(response);
}
public async Task<PayoutData> ApprovePayout(string storeId, string payoutId, ApprovePayoutRequest request, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{HttpUtility.UrlEncode(storeId)}/payouts/{HttpUtility.UrlEncode(payoutId)}", bodyPayload: request, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<PayoutData>(response);
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<ServerInfoData> GetServerInfo(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/server/info"), token);
return await HandleResponse<ServerInfoData>(response);
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<StoreData>> GetStores(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/stores"), token);
return await HandleResponse<IEnumerable<StoreData>>(response);
}
public virtual async Task<StoreData> GetStore(string storeId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}"), token);
return await HandleResponse<StoreData>(response);
}
public virtual async Task RemoveStore(string storeId, CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}", method: HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task<StoreData> CreateStore(CreateStoreRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/stores", bodyPayload: request, method: HttpMethod.Post), token);
return await HandleResponse<StoreData>(response);
}
public virtual async Task<StoreData> UpdateStore(string storeId, UpdateStoreRequest request, CancellationToken token = default)
{
if (request == null)
throw new ArgumentNullException(nameof(request));
if (storeId == null)
throw new ArgumentNullException(nameof(storeId));
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}", bodyPayload: request, method: HttpMethod.Put), token);
return await HandleResponse<StoreData>(response);
}
}
}

View File

@@ -0,0 +1,23 @@
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<ApplicationUserData> GetCurrentUser(CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
return await HandleResponse<ApplicationUserData>(response);
}
public virtual async Task<ApplicationUserData> CreateUser(CreateApplicationUserRequest request,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
return await HandleResponse<ApplicationUserData>(response);
}
}
}

View File

@@ -0,0 +1,131 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
private readonly string _apiKey;
private readonly Uri _btcpayHost;
private readonly string _username;
private readonly string _password;
private readonly HttpClient _httpClient;
public Uri Host => _btcpayHost;
public string APIKey => _apiKey;
public BTCPayServerClient(Uri btcpayHost, HttpClient httpClient = null)
{
if (btcpayHost == null)
throw new ArgumentNullException(nameof(btcpayHost));
_btcpayHost = btcpayHost;
_httpClient = httpClient ?? new HttpClient();
}
public BTCPayServerClient(Uri btcpayHost, string APIKey, HttpClient httpClient = null)
{
_apiKey = APIKey;
_btcpayHost = btcpayHost;
_httpClient = httpClient ?? new HttpClient();
}
public BTCPayServerClient(Uri btcpayHost, string username, string password, HttpClient httpClient = null)
{
_apiKey = APIKey;
_btcpayHost = btcpayHost;
_username = username;
_password = password;
_httpClient = httpClient ?? new HttpClient();
}
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)
{
await HandleResponse(message);
return JsonConvert.DeserializeObject<T>(await message.Content.ReadAsStringAsync());
}
protected virtual HttpRequestMessage CreateHttpRequest(string path,
Dictionary<string, object> queryPayload = null,
HttpMethod method = null)
{
UriBuilder uriBuilder = new UriBuilder(_btcpayHost) { Path = path };
if (queryPayload != null && queryPayload.Any())
{
AppendPayloadToQuery(uriBuilder, queryPayload);
}
var httpRequest = new HttpRequestMessage(method ?? HttpMethod.Get, uriBuilder.Uri);
if (_apiKey != null)
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("token", _apiKey);
else if (!string.IsNullOrEmpty(_username))
{
httpRequest.Headers.Authorization = new AuthenticationHeaderValue("Basic", System.Convert.ToBase64String(Encoding.ASCII.GetBytes(_username + ":" + _password)));
}
return httpRequest;
}
protected virtual HttpRequestMessage CreateHttpRequest<T>(string path,
Dictionary<string, object> queryPayload = null,
T bodyPayload = default, HttpMethod method = null)
{
var request = CreateHttpRequest(path, queryPayload, method);
if (typeof(T).IsPrimitive || !EqualityComparer<T>.Default.Equals(bodyPayload, default(T)))
{
request.Content = new StringContent(JsonConvert.SerializeObject(bodyPayload), Encoding.UTF8, "application/json");
}
return request;
}
private static void AppendPayloadToQuery(UriBuilder uri, Dictionary<string, object> payload)
{
if (uri.Query.Length > 1)
uri.Query += "&";
foreach (KeyValuePair<string, object> keyValuePair in payload)
{
UriBuilder uriBuilder = uri;
if (!(keyValuePair.Value is string) && keyValuePair.Value.GetType().GetInterfaces().Contains((typeof(IEnumerable))))
{
foreach (var item in (IEnumerable)keyValuePair.Value)
{
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
Uri.EscapeDataString(item.ToString()) + "&";
}
}
else
{
uriBuilder.Query = uriBuilder.Query + Uri.EscapeDataString(keyValuePair.Key) + "=" +
Uri.EscapeDataString(keyValuePair.Value.ToString()) + "&";
}
}
uri.Query = uri.Query.Trim('&');
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
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; }
}
}

View File

@@ -0,0 +1,28 @@
using System;
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; }
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Globalization;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.JsonConverters
{
public class DecimalStringJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return (objectType == typeof(decimal) || objectType == typeof(decimal?));
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
JToken token = JToken.Load(reader);
switch (token.Type)
{
case JTokenType.Float:
case JTokenType.Integer:
case JTokenType.String:
return decimal.Parse(token.ToString(), CultureInfo.InvariantCulture);
case JTokenType.Null when objectType == typeof(decimal?):
return null;
default:
throw new JsonSerializationException("Unexpected token type: " +
token.Type);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(((decimal)value).ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Globalization;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
namespace BTCPayServer.Client.JsonConverters
{
public class LightMoneyJsonConverter : BTCPayServer.Lightning.JsonConverters.LightMoneyJsonConverter
{
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(((LightMoney)value).MilliSatoshi.ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Globalization;
using NBitcoin;
using Newtonsoft.Json;
namespace BTCPayServer.Client.JsonConverters
{
public class MoneyJsonConverter : NBitcoin.JsonConverters.MoneyJsonConverter
{
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.String)
{
return new Money(long.Parse((string)reader.Value));
}
return base.ReadJson(reader, objectType, existingValue, serializer);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value != null)
writer.WriteValue(((Money)value).Satoshi.ToString(CultureInfo.InvariantCulture));
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Diagnostics.CodeAnalysis;
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());
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Reflection;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.JsonConverters
{
public class PermissionJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return typeof(Permission).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo());
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null)
return null;
if (reader.TokenType != JsonToken.String)
throw new JsonObjectException("Type 'Permission' is expected to be a 'String'", reader);
if (reader.Value is String s && Permission.TryParse(s, out var permission))
return permission;
throw new JsonObjectException("Invalid 'Permission' String", reader);
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is Permission v)
writer.WriteValue(v.ToString());
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.JsonConverters
{
public class TimeSpanJsonConverter : JsonConverter
{
public override bool CanConvert(Type objectType)
{
return objectType == typeof(TimeSpan) || objectType == typeof(TimeSpan?);
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
{
try
{
var nullable = objectType == typeof(TimeSpan?);
if (reader.TokenType == JsonToken.Null)
{
if (nullable)
return null;
return TimeSpan.Zero;
}
if (reader.TokenType != JsonToken.Integer)
throw new JsonObjectException("Invalid timespan, expected integer", reader);
return TimeSpan.FromSeconds((long)reader.Value);
}
catch
{
throw new JsonObjectException("Invalid locktime", reader);
}
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
if (value is TimeSpan s)
{
writer.WriteValue((long)s.TotalSeconds);
}
}
}
}

View File

@@ -0,0 +1,7 @@
namespace BTCPayServer.Client.Models
{
public class ApiHealthData
{
public bool Synchronized { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using BTCPayServer.Client.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class ApiKeyData
{
public string ApiKey { get; set; }
public string Label { get; set; }
[JsonProperty(ItemConverterType = typeof(PermissionJsonConverter))]
public Permission[] Permissions { get; set; }
}
}

View File

@@ -0,0 +1,25 @@
namespace BTCPayServer.Client.Models
{
public class ApplicationUserData
{
/// <summary>
/// the id of the user
/// </summary>
public string Id { get; set; }
/// <summary>
/// the email AND username of the user
/// </summary>
public string Email { get; set; }
/// <summary>
/// Whether the user has verified their email
/// </summary>
public bool EmailConfirmed { get; set; }
/// <summary>
/// whether the user needed to verify their email on account creation
/// </summary>
public bool RequiresEmailConfirmation { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace BTCPayServer.Client.Models
{
public class ApprovePayoutRequest
{
public int Revision { get; set; }
public string RateRule { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class ConnectToNodeRequest
{
public ConnectToNodeRequest()
{
}
public ConnectToNodeRequest(NodeInfo nodeInfo)
{
NodeURI = nodeInfo;
}
[JsonConverter(typeof(NodeUriJsonConverter))]
[JsonProperty("nodeURI")]
public NodeInfo NodeURI { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using BTCPayServer.Client.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class CreateApiKeyRequest
{
public string Label { get; set; }
[JsonProperty(ItemConverterType = typeof(PermissionJsonConverter))]
public Permission[] Permissions { get; set; }
}
}

View File

@@ -0,0 +1,20 @@
namespace BTCPayServer.Client.Models
{
public class CreateApplicationUserRequest
{
/// <summary>
/// the email AND username of the new user
/// </summary>
public string Email { get; set; }
/// <summary>
/// password of the new user
/// </summary>
public string Password { get; set; }
/// <summary>
/// Whether this user is an administrator. If left null and there are no admins in the system, the user will be created as an admin.
/// </summary>
public bool? IsAdministrator { get; set; }
}
}

View File

@@ -0,0 +1,28 @@
using System;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class CreateLightningInvoiceRequest
{
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; }
}
}

View File

@@ -0,0 +1,6 @@
namespace BTCPayServer.Client.Models
{
public class CreatePaymentRequestRequest : PaymentRequestBaseData
{
}
}

View File

@@ -0,0 +1,13 @@
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class CreatePayoutRequest
{
public string Destination { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
public decimal? Amount { get; set; }
public string PaymentMethod { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class CreatePullPaymentRequest
{
public string Name { get; set; }
[JsonProperty(ItemConverterType = typeof(DecimalStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter))]
public TimeSpan? Period { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? ExpiresAt { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? StartsAt { get; set; }
public string[] PaymentMethods { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace BTCPayServer.Client.Models
{
public class CreateStoreRequest : StoreBaseData
{
}
}

View File

@@ -0,0 +1,23 @@
using System;
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; }
}
}

View File

@@ -0,0 +1,24 @@
using System;
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; }
}
}

View File

@@ -0,0 +1,30 @@
using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models
{
public class LightningInvoiceData
{
public string Id { get; set; }
[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; }
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney Amount { get; set; }
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney AmountReceived { get; set; }
}
}

View File

@@ -0,0 +1,30 @@
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class LightningNodeInformationData
{
[JsonProperty("nodeURIs", ItemConverterType = typeof(NodeUriJsonConverter))]
public NodeInfo[] NodeURIs { get; set; }
public int BlockHeight { get; set; }
}
public class LightningChannelData
{
public string RemoteNode { get; set; }
public bool IsPublic { get; set; }
public bool IsActive { get; set; }
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney Capacity { get; set; }
[JsonConverter(typeof(LightMoneyJsonConverter))]
public LightMoney LocalBalance { get; set; }
public string ChannelPoint { get; set; }
}
}

View File

@@ -0,0 +1,21 @@
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.Lightning;
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using MoneyJsonConverter = BTCPayServer.Client.JsonConverters.MoneyJsonConverter;
namespace BTCPayServer.Client.Models
{
public class OpenLightningChannelRequest
{
[JsonConverter(typeof(NodeUriJsonConverter))]
[JsonProperty("nodeURI")]
public NodeInfo NodeURI { get; set; }
[JsonConverter(typeof(MoneyJsonConverter))]
public Money ChannelAmount { get; set; }
[JsonConverter(typeof(FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
}
}

View File

@@ -0,0 +1,8 @@
namespace BTCPayServer.Client.Models
{
public class PayLightningInvoiceRequest
{
[Newtonsoft.Json.JsonProperty("BOLT11")]
public string BOLT11 { get; set; }
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public class PaymentRequestBaseData
{
[JsonProperty(ItemConverterType = typeof(DecimalStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
public DateTime? ExpiryDate { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public string Email { get; set; }
public string EmbeddedCSS { get; set; }
public string CustomCSSLink { get; set; }
public bool AllowCustomPaymentAmounts { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models
{
public class PaymentRequestData : PaymentRequestBaseData
{
[JsonConverter(typeof(StringEnumConverter))]
public PaymentRequestData.PaymentRequestStatus Status { get; set; }
public DateTimeOffset Created { get; set; }
public string Id { get; set; }
public bool Archived { get; set; }
public enum PaymentRequestStatus
{
Pending = 0,
Completed = 1,
Expired = 2
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models
{
public enum PayoutState
{
AwaitingApproval,
AwaitingPayment,
InProgress,
Completed,
Cancelled
}
public class PayoutData
{
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset Date { get; set; }
public string Id { get; set; }
public string PullPaymentId { get; set; }
public string Destination { get; set; }
public string PaymentMethod { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
public decimal? PaymentMethodAmount { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public PayoutState State { get; set; }
public int Revision { get; set; }
}
}

View File

@@ -0,0 +1,24 @@
using System;
using BTCPayServer.Client.JsonConverters;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class PullPaymentData
{
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset StartsAt { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? ExpiresAt { get; set; }
public string Id { get; set; }
public string Name { get; set; }
public string Currency { get; set; }
[JsonConverter(typeof(DecimalStringJsonConverter))]
public decimal Amount { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter))]
public TimeSpan? Period { get; set; }
public bool Archived { get; set; }
public string ViewLink { get; set; }
}
}

View File

@@ -0,0 +1,47 @@
using System.Collections.Generic;
namespace BTCPayServer.Client.Models
{
public class ServerInfoData
{
/// <summary>
/// the BTCPay Server version
/// </summary>
public string Version { get; set; }
/// <summary>
/// the Tor hostname
/// </summary>
public string Onion { get; set; }
/// <summary>
/// the payment methods this server supports
/// </summary>
public IEnumerable<string> SupportedPaymentMethods { get; set; }
/// <summary>
/// are all chains fully synched
/// </summary>
public bool FullySynched { get; set; }
/// <summary>
/// detailed sync information per chain
/// </summary>
public IEnumerable<ServerInfoSyncStatusData> SyncStatus { get; set; }
}
public class ServerInfoSyncStatusData
{
public string CryptoCode { get; set; }
public int ChainHeight { get; set; }
public int? SyncHeight { get; set; }
public ServerInfoNodeData NodeInformation { get; set; }
}
public class ServerInfoNodeData
{
public int Headers { get; set; }
public int Blocks { get; set; }
public double VerificationProgress { get; set; }
}
}

View File

@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using BTCPayServer.Client.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public abstract class StoreBaseData
{
/// <summary>
/// the name of the store
/// </summary>
public string Name { get; set; }
public string Website { get; set; }
[JsonConverter(typeof(TimeSpanJsonConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TimeSpan InvoiceExpiration { get; set; } = TimeSpan.FromMinutes(15);
[JsonConverter(typeof(TimeSpanJsonConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TimeSpan MonitoringExpiration { get; set; } = TimeSpan.FromMinutes(60);
[JsonConverter(typeof(StringEnumConverter))]
public SpeedPolicy SpeedPolicy { get; set; }
public string LightningDescriptionTemplate { get; set; }
public double PaymentTolerance { get; set; } = 0;
public bool AnyoneCanCreateInvoice { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool ShowRecommendedFee { get; set; } = true;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public int RecommendedFeeBlockTarget { get; set; } = 1;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string DefaultLang { get; set; } = "en";
public bool LightningAmountInSatoshi { get; set; }
public string CustomLogo { get; set; }
public string CustomCSS { get; set; }
public string HtmlTitle { get; set; }
public bool RedirectAutomatically { get; set; }
public bool RequiresRefundEmail { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
public bool PayJoinEnabled { get; set; }
public bool LightningPrivateRouteHints { get; set; }
[JsonExtensionData]
public IDictionary<string, JToken> AdditionalData { get; set; }
}
public enum NetworkFeeMode
{
MultiplePaymentsOnly,
Always,
Never
}
public enum SpeedPolicy
{
HighSpeed = 0,
MediumSpeed = 1,
LowSpeed = 2,
LowMediumSpeed = 3
}
}

View File

@@ -0,0 +1,10 @@
namespace BTCPayServer.Client.Models
{
public class StoreData : StoreBaseData
{
/// <summary>
/// the id of the store
/// </summary>
public string Id { get; set; }
}
}

View File

@@ -0,0 +1,6 @@
namespace BTCPayServer.Client.Models
{
public class UpdatePaymentRequestRequest : PaymentRequestBaseData
{
}
}

View File

@@ -0,0 +1,6 @@
namespace BTCPayServer.Client.Models
{
public class UpdateStoreRequest : StoreBaseData
{
}
}

View File

@@ -0,0 +1,208 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace BTCPayServer.Client
{
public class Policies
{
public const string CanCreateLightningInvoiceInternalNode = "btcpay.server.cancreatelightninginvoiceinternalnode";
public const string CanCreateLightningInvoiceInStore = "btcpay.store.cancreatelightninginvoice";
public const string CanUseInternalLightningNode = "btcpay.server.canuseinternallightningnode";
public const string CanUseLightningNodeInStore = "btcpay.store.canuselightningnode";
public const string CanModifyServerSettings = "btcpay.server.canmodifyserversettings";
public const string CanModifyStoreSettings = "btcpay.store.canmodifystoresettings";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanViewPaymentRequests = "btcpay.store.canviewpaymentrequests";
public const string CanModifyPaymentRequests = "btcpay.store.canmodifypaymentrequests";
public const string CanModifyProfile = "btcpay.user.canmodifyprofile";
public const string CanViewProfile = "btcpay.user.canviewprofile";
public const string CanCreateUser = "btcpay.server.cancreateuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string Unrestricted = "unrestricted";
public static IEnumerable<string> AllPolicies
{
get
{
yield return CanCreateInvoice;
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
yield return CanViewStoreSettings;
yield return CanViewPaymentRequests;
yield return CanModifyPaymentRequests;
yield return CanModifyProfile;
yield return CanViewProfile;
yield return CanCreateUser;
yield return Unrestricted;
yield return CanUseInternalLightningNode;
yield return CanCreateLightningInvoiceInternalNode;
yield return CanUseLightningNodeInStore;
yield return CanCreateLightningInvoiceInStore;
yield return CanManagePullPayments;
}
}
public static bool IsValidPolicy(string policy)
{
return AllPolicies.Any(p => p.Equals(policy, StringComparison.OrdinalIgnoreCase));
}
public static bool IsStorePolicy(string policy)
{
return policy.StartsWith("btcpay.store", StringComparison.OrdinalIgnoreCase);
}
public static bool IsStoreModifyPolicy(string policy)
{
return policy.StartsWith("btcpay.store.canmodify", StringComparison.OrdinalIgnoreCase);
}
public static bool IsServerPolicy(string policy)
{
return policy.StartsWith("btcpay.server", StringComparison.OrdinalIgnoreCase);
}
}
public class Permission
{
public static Permission Create(string policy, string scope = null)
{
if (TryCreatePermission(policy, scope, out var r))
return r;
throw new ArgumentException("Invalid Permission");
}
public static bool TryCreatePermission(string policy, string scope, out Permission permission)
{
permission = null;
if (policy == null)
throw new ArgumentNullException(nameof(policy));
policy = policy.Trim().ToLowerInvariant();
if (!Policies.IsValidPolicy(policy))
return false;
if (scope != null && !Policies.IsStorePolicy(policy))
return false;
permission = new Permission(policy, scope);
return true;
}
public static bool TryParse(string str, out Permission permission)
{
permission = null;
if (str == null)
throw new ArgumentNullException(nameof(str));
str = str.Trim();
var separator = str.IndexOf(':');
if (separator == -1)
{
str = str.ToLowerInvariant();
if (!Policies.IsValidPolicy(str))
return false;
permission = new Permission(str, null);
return true;
}
else
{
var policy = str.Substring(0, separator).ToLowerInvariant();
if (!Policies.IsValidPolicy(policy))
return false;
if (!Policies.IsStorePolicy(policy))
return false;
var storeId = str.Substring(separator + 1);
if (storeId.Length == 0)
return false;
permission = new Permission(policy, storeId);
return true;
}
}
internal Permission(string policy, string scope)
{
Policy = policy;
Scope = scope;
}
public bool Contains(Permission subpermission)
{
if (subpermission is null)
throw new ArgumentNullException(nameof(subpermission));
if (!ContainsPolicy(subpermission.Policy))
{
return false;
}
if (!Policies.IsStorePolicy(subpermission.Policy))
return true;
return Scope == null || subpermission.Scope == this.Scope;
}
public static IEnumerable<Permission> ToPermissions(string[] permissions)
{
if (permissions == null)
throw new ArgumentNullException(nameof(permissions));
foreach (var p in permissions)
{
if (TryParse(p, out var pp))
yield return pp;
}
}
private bool ContainsPolicy(string subpolicy)
{
if (this.Policy == Policies.Unrestricted)
return true;
if (this.Policy == subpolicy)
return true;
switch (subpolicy)
{
case Policies.CanViewStoreSettings when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanCreateInvoice when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewProfile when this.Policy == Policies.CanModifyProfile:
case Policies.CanModifyPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanModifyStoreSettings:
case Policies.CanViewPaymentRequests when this.Policy == Policies.CanViewStoreSettings:
case Policies.CanCreateLightningInvoiceInternalNode when this.Policy == Policies.CanUseInternalLightningNode:
case Policies.CanCreateLightningInvoiceInStore when this.Policy == Policies.CanUseLightningNodeInStore:
return true;
default:
return false;
}
}
public string Scope { get; }
public string Policy { get; }
public override string ToString()
{
if (Scope != null)
{
return $"{Policy}:{Scope}";
}
return Policy;
}
public override bool Equals(object obj)
{
Permission item = obj as Permission;
if (item == null)
return false;
return ToString().Equals(item.ToString());
}
public static bool operator ==(Permission a, Permission b)
{
if (System.Object.ReferenceEquals(a, b))
return true;
if (((object)a == null) || ((object)b == null))
return false;
return a.ToString() == b.ToString();
}
public static bool operator !=(Permission a, Permission b)
{
return !(a == b);
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,7 +14,6 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/tx/{0}" : "https://blockstream.info/testnet/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
CryptoImagePath = "imlegacy/bitcoin.svg",
@@ -25,19 +21,20 @@ namespace BTCPayServer
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x4b24746U, DerivationType.Segwit }, //zpub
{0x04b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy},
{0x044a5262U, DerivationType.SegwitP2SH},
{0x045f1cf6U, DerivationType.Segwit}
{0x043587cfU, DerivationType.Legacy}, // tpub
{0x044a5262U, DerivationType.SegwitP2SH}, // upub
{0x045f1cf6U, DerivationType.Segwit} // vpub
}
});
}

View File

@@ -1,4 +1,4 @@
using NBitcoin;
using NBitcoin;
namespace BTCPayServer
{
@@ -12,7 +12,6 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BGold",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.bitcoingold.org/insight/tx/{0}/" : "https://test-explorer.bitcoingold.org/insight/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoingold",
DefaultRateRules = new[]

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,7 +13,6 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoinplus",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoinplus",
DefaultRateRules = new[]

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,7 +13,6 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcore",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcore",
DefaultRateRules = new[]

View File

@@ -0,0 +1,33 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitChaincoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("CHC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Chaincoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "chaincoin",
DefaultRateRules = new[]
{
"CHC_X = CHC_BTC * BTC_X",
"CHC_BTC = txbit(CHC_X)"
},
CryptoImagePath = "imlegacy/chaincoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("711'")
: new KeyPath("1'")
});
}
}
}

View File

@@ -1,4 +1,4 @@
using NBitcoin;
using NBitcoin;
namespace BTCPayServer
{
@@ -15,7 +15,6 @@ namespace BTCPayServer
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://insight.dash.org/insight/tx/{0}"
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "dash",
DefaultRateRules = new[]

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,10 +13,9 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dogecoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "dogecoin",
DefaultRateRules = new[]
DefaultRateRules = new[]
{
"DOGE_X = DOGE_BTC * BTC_X",
"DOGE_BTC = bittrex(DOGE_BTC)"

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,10 +13,9 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Feathercoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "feathercoin",
DefaultRateRules = new[]
DefaultRateRules = new[]
{
"FTC_X = FTC_BTC * BTC_X",
"FTC_BTC = bittrex(FTC_BTC)"

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
namespace BTCPayServer
@@ -18,7 +14,6 @@ namespace BTCPayServer
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "groestlcoin",
DefaultRateRules = new[]
@@ -29,7 +24,9 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true
});
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -19,9 +16,13 @@ namespace BTCPayServer
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "litecoin",
DefaultRateRules = new[]
{
"LTC_X = LTC_BTC * BTC_X",
"LTC_BTC = coingecko(LTC_BTC)"
},
CryptoImagePath = "imlegacy/litecoin.svg",
LightningImagePath = "imlegacy/litecoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,10 +13,9 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Monacoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "monacoin",
DefaultRateRules = new[]
DefaultRateRules = new[]
{
"MONA_X = MONA_BTC * BTC_X",
"MONA_BTC = bittrex(MONA_BTC)"

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -16,14 +12,13 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Polis",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.polispay.org/tx/{0}" : "https://insight.polispay.org/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "polis",
DefaultRateRules = new[]
{
"POLIS_X = POLIS_BTC * BTC_X",
"POLIS_BTC = cryptopia(POLIS_BTC)"
"POLIS_BTC = polispay(POLIS_BTC)"
},
CryptoImagePath = "imlegacy/polis.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,10 +13,9 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Ufo",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "ufo",
DefaultRateRules = new[]
DefaultRateRules = new[]
{
"UFO_X = UFO_BTC * BTC_X",
"UFO_BTC = coinexchange(UFO_BTC)"

View File

@@ -1,7 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -17,7 +13,6 @@ namespace BTCPayServer
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Viacoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
NBitcoinNetwork = nbxplorerNetwork.NBitcoinNetwork,
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "viacoin",
DefaultRateRules = new[]

View File

@@ -1,8 +1,6 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
@@ -10,7 +8,7 @@ namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
readonly Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
@@ -31,7 +29,7 @@ namespace BTCPayServer
cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray();
foreach (var network in unfiltered._Networks)
{
if(cryptoCodes.Contains(network.Key))
if (cryptoCodes.Contains(network.Key))
{
_Networks.Add(network.Key, network.Value);
}
@@ -47,6 +45,8 @@ namespace BTCPayServer
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
NetworkType = networkType;
InitBitcoin();
InitLiquid();
InitLiquidAssets();
InitLitecoin();
InitBitcore();
InitDogecoin();
@@ -57,11 +57,13 @@ namespace BTCPayServer
InitGroestlcoin();
InitViacoin();
InitMonero();
InitPolis();
InitChaincoin();
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
{
if(network.ElectrumMapping.Count == 0)
if (network.ElectrumMapping.Count == 0)
{
network.ElectrumMapping = GetNetwork<BTCPayNetwork>("BTC").ElectrumMapping;
if (!network.NBitcoinNetwork.Consensus.SupportSegwit)
@@ -75,7 +77,6 @@ namespace BTCPayServer
}
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
//InitPolis();
//InitBitcoinplus();
//InitUfo();
}
@@ -93,6 +94,12 @@ namespace BTCPayServer
[Obsolete("To use only for legacy stuff")]
public BTCPayNetwork BTC => GetNetwork<BTCPayNetwork>("BTC");
public void Add(BTCPayNetwork network)
{
if (network.NBitcoinNetwork == null)
return;
Add(network as BTCPayNetworkBase);
}
public void Add(BTCPayNetworkBase network)
{
_Networks.Add(network.CryptoCode.ToUpperInvariant(), network);
@@ -109,13 +116,13 @@ namespace BTCPayServer
}
public BTCPayNetworkBase GetNetwork(string cryptoCode)
{
return GetNetwork<BTCPayNetworkBase>(cryptoCode);
return GetNetwork<BTCPayNetworkBase>(cryptoCode.ToUpperInvariant());
}
public T GetNetwork<T>(string cryptoCode) where T: BTCPayNetworkBase
public T GetNetwork<T>(string cryptoCode) where T : BTCPayNetworkBase
{
if (cryptoCode == null)
throw new ArgumentNullException(nameof(cryptoCode));
if(!_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetworkBase network))
if (!_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetworkBase network))
{
if (cryptoCode == "XBT")
return GetNetwork<T>("BTC");

View File

@@ -0,0 +1,36 @@
using NBitcoin;
using NBitcoin.Altcoins;
using NBitcoin.Altcoins.Elements;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLiquid()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
AssetId = NetworkType == NetworkType.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
CryptoCode = "LBTC",
NetworkCryptoCode = "LBTC",
DisplayName = "Liquid Bitcoin",
DefaultRateRules = new[]
{
"LBTC_X = LBTC_BTC * BTC_X",
"LBTC_BTC = 1",
},
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true
});
}
}
}

View File

@@ -0,0 +1,83 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLiquidAssets()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "USDt",
NetworkCryptoCode = "LBTC",
ShowSyncSummary = false,
DefaultRateRules = new[]
{
"USDT_UST = 1",
"USDT_X = USDT_BTC * BTC_X",
"USDT_BTC = bitfinex(UST_BTC)",
},
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid-tether.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "ETB",
NetworkCryptoCode = "LBTC",
ShowSyncSummary = false,
DefaultRateRules = new[]
{
"ETB_X = ETB_BTC * BTC_X",
"ETB_BTC = bitpay(ETB_BTC)"
},
Divisibility = 2,
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
DisplayName = "Ethiopian Birr",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/etb.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
Add(new ElementsBTCPayNetwork()
{
CryptoCode = "LCAD",
NetworkCryptoCode = "LBTC",
ShowSyncSummary = false,
DefaultRateRules = new[]
{
"LCAD_CAD = 1",
"LCAD_X = CAD_BTC * BTC_X",
"LCAD_BTC = bylls(CAD_BTC)",
},
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
DisplayName = "Liquid CAD",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/lcad.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
namespace BTCPayServer
{
public class ElementsBTCPayNetwork : BTCPayNetwork
{
public string NetworkCryptoCode { get; set; }
public uint256 AssetId { get; set; }
public override bool ReadonlyWallet { get; set; } = true;
public override IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(
NewTransactionEvent evtOutputs)
{
return evtOutputs.Outputs.Where(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId).Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public override GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
{
TransactionInformationSet Filter(TransactionInformationSet transactionInformationSet)
{
return new TransactionInformationSet()
{
Transactions =
transactionInformationSet.Transactions.FindAll(information =>
information.Outputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
information.Inputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId))
};
}
return new GetTransactionsResponse()
{
Height = response.Height,
ConfirmedTransactions = Filter(response.ConfirmedTransactions),
ReplacedTransactions = Filter(response.ReplacedTransactions),
UnconfirmedTransactions = Filter(response.UnconfirmedTransactions)
};
}
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
//precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000
//precision 8: 10 = 10
var money = new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}&assetid={AssetId}";
}
}
}

View File

@@ -10,10 +10,16 @@ namespace BTCPayServer
{
CryptoCode = "XMR",
DisplayName = "Monero",
Divisibility = 12,
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
DefaultRateRules = new[]
{
"XMR_X = XMR_BTC * BTC_X",
"XMR_BTC = kraken(XMR_BTC)"
},
CryptoImagePath = "/imlegacy/monero.svg"
});
}

View File

@@ -46,7 +46,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
Convert.ToBase64String(Encoding.Default.GetBytes($"{_username}:{_password}")));
var rawResult = await _httpClient.SendAsync(httpRequest, cts);
var rawJson = await rawResult.Content.ReadAsStringAsync();
rawResult.EnsureSuccessStatusCode();
JsonRpcResult<TResponse> response;
@@ -68,7 +68,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
Error = response.Error
};
}
return response.Result;
}
@@ -92,7 +92,7 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC
}
internal class JsonRpcResult<T>
{
[JsonProperty("result")] public T Result { get; set; }
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }

View File

@@ -14,7 +14,6 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }

View File

@@ -18,7 +18,6 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
[JsonProperty("amount")] public long Amount { get; set; }
[JsonProperty("confirmations")] public long Confirmations { get; set; }
[JsonProperty("double_spend_seen")] public bool DoubleSpendSeen { get; set; }
[JsonProperty("fee")] public long Fee { get; set; }
[JsonProperty("height")] public long Height { get; set; }
[JsonProperty("note")] public string Note { get; set; }
[JsonProperty("payment_id")] public string PaymentId { get; set; }

View File

@@ -10,7 +10,8 @@ namespace BTCPayServer.Services.Altcoins.Monero.RPC.Models
public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer)
{
if (reader.TokenType == JsonToken.Null) return null;
if (reader.TokenType == JsonToken.Null)
return null;
var value = serializer.Deserialize<string>(reader);
long l;
if (Int64.TryParse(value, out l))

View File

@@ -1,11 +1,10 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using NBitcoin;
using NBXplorer;
using Newtonsoft.Json;
using NBXplorer.Models;
namespace BTCPayServer
{
@@ -32,7 +31,7 @@ namespace BTCPayServer
}
}
static Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
static readonly Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
{
@@ -44,19 +43,25 @@ namespace BTCPayServer
public int DefaultPort { get; set; }
}
public class BTCPayNetwork:BTCPayNetworkBase
public class BTCPayNetwork : BTCPayNetworkBase
{
public Network NBitcoinNetwork { get; set; }
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
public bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
public virtual bool WalletSupported { get; set; } = true;
public virtual bool ReadonlyWallet { get; set; } = false;
public int MaxTrackedConfirmation { get; internal set; } = 6;
public string UriScheme { get; internal set; }
public bool SupportPayJoin { get; set; } = false;
public bool SupportLightning { get; set; } = true;
public KeyPath GetRootKeyPath(DerivationType type)
{
KeyPath baseKey;
@@ -100,14 +105,33 @@ namespace BTCPayServer
{
return NBXplorerNetwork.Serializer.ToString(obj);
}
public virtual IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(NewTransactionEvent evtOutputs)
{
return evtOutputs.Outputs.Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
}
public virtual GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
{
return response;
}
}
public abstract class BTCPayNetworkBase
{
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; }
public string BlockExplorerLink { get; internal set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
[Obsolete("Should not be needed")]
public bool IsBTC
{

View File

@@ -3,8 +3,7 @@
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.9" Condition="'$(TargetFramework)' == 'netcoreapp2.1'" />
<FrameworkReference Include="Microsoft.AspNetCore.App" Condition="'$(TargetFramework)' != 'netcoreapp2.1'" />
<PackageReference Include="NBXplorer.Client" Version="3.0.1" />
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.16" />
</ItemGroup>
</Project>

View File

@@ -1,6 +1,5 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -9,12 +8,12 @@ namespace BTCPayServer
{
public class CustomThreadPool : IDisposable
{
CancellationTokenSource _Cancel = new CancellationTokenSource();
TaskCompletionSource<bool> _Exited;
readonly CancellationTokenSource _Cancel = new CancellationTokenSource();
readonly TaskCompletionSource<bool> _Exited;
int _ExitedCount = 0;
Thread[] _Threads;
readonly Thread[] _Threads;
Exception _UnhandledException;
BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
readonly BlockingCollection<(Action, TaskCompletionSource<object>)> _Actions = new BlockingCollection<(Action, TaskCompletionSource<object>)>(new ConcurrentQueue<(Action, TaskCompletionSource<object>)>());
public CustomThreadPool(int threadCount, string threadName)
{

View File

@@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BTCPayServer
{

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Text;
@@ -13,7 +13,7 @@ namespace BTCPayServer.Logging
{
public class CustomConsoleLogProvider : ILoggerProvider
{
ConsoleLoggerProcessor _Processor;
readonly ConsoleLoggerProcessor _Processor;
public CustomConsoleLogProvider(ConsoleLoggerProcessor processor)
{
_Processor = processor;

View File

@@ -1,9 +1,6 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace BTCPayServer.Logging
{
@@ -43,7 +40,7 @@ namespace BTCPayServer.Logging
public class FuncLoggerFactory : ILoggerFactory
{
private Func<string, ILogger> createLogger;
private readonly Func<string, ILogger> createLogger;
public FuncLoggerFactory(Func<string, ILogger> createLogger)
{
this.createLogger = createLogger;

View File

@@ -1,13 +1,9 @@
#if !NETCOREAPP21
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using Newtonsoft.Json;
namespace Microsoft.Extensions.Logging.Console.Internal
@@ -129,7 +125,7 @@ namespace Microsoft.Extensions.Logging.Console.Internal
}
}
internal class AnsiSystemConsole : IAnsiSystemConsole
internal class AnsiSystemConsole : IAnsiSystemConsole
{
private readonly TextWriter _textWriter;
@@ -152,13 +148,13 @@ namespace Microsoft.Extensions.Logging.Console
{
void Write(string message);
}
public interface IConsole
public interface IConsole
{
void Write(string message, ConsoleColor? background, ConsoleColor? foreground);
void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground);
void Flush();
}
internal class WindowsLogConsole : IConsole
internal class WindowsLogConsole : IConsole
{
private readonly TextWriter _textWriter;
@@ -235,25 +231,3 @@ namespace Microsoft.Extensions.Logging.Abstractions.Internal
}
}
}
#else
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Mvc
{
/// <summary>
/// Provides programmatic configuration for JSON formatters using Newtonsoft.JSON.
/// </summary>
public class MvcNewtonsoftJsonOptions
{
IOptions<MvcJsonOptions> jsonOptions;
public MvcNewtonsoftJsonOptions(IOptions<MvcJsonOptions> jsonOptions)
{
this.jsonOptions = jsonOptions;
}
public JsonSerializerSettings SerializerSettings => this.jsonOptions.Value.SerializerSettings;
}
}
#endif

View File

@@ -1,9 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace BTCPayServer
{

View File

@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Text;

View File

@@ -1,19 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<PackageReference Include="Microsoft.AspNetCore.App" AllowExplicitVersion="true" Version="2.1.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.2" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="2.1.2" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' != 'netcoreapp2.1'">
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.0.1" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.0.0-rc1.final" />
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="3.0.0-alpha1.19515.63" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
<PrivateAssets>all</PrivateAssets>
<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.4" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="3.1.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />
</ItemGroup>
</Project>

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -11,15 +8,47 @@ namespace BTCPayServer.Data
[MaxLength(50)]
public string Id
{
get; set;
get;
set;
}
[MaxLength(50)]
public string StoreId
{
get; set;
}
[MaxLength(50)] public string StoreId { get; set; }
[MaxLength(50)] public string UserId { get; set; }
public APIKeyType Type { get; set; } = APIKeyType.Legacy;
public byte[] Blob { get; set; }
public StoreData StoreData { get; set; }
public ApplicationUser User { get; set; }
public string Label { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<APIKeyData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.APIKeys)
.HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade);
builder.Entity<APIKeyData>()
.HasOne(o => o.User)
.WithMany(i => i.APIKeys)
.HasForeignKey(i => i.UserId).OnDelete(DeleteBehavior.Cascade);
builder.Entity<APIKeyData>()
.HasIndex(o => o.StoreId);
}
}
public class APIKeyBlob
{
public string[] Permissions { get; set; }
}
public enum APIKeyType
{
Legacy,
Permanent
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -32,5 +30,15 @@ namespace BTCPayServer.Data
get; set;
}
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618
.HasKey(o => o.Address);
#pragma warning restore CS0618
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
using Newtonsoft.Json;
namespace BTCPayServer.Data
@@ -37,5 +35,14 @@ namespace BTCPayServer.Data
{
Settings = value == null ? null : JsonConvert.SerializeObject(value);
}
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<AppData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AppData>()
.HasOne(a => a.StoreData);
}
}
}

View File

@@ -1,109 +1,68 @@
using System;
using System;
using System.Linq;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContext()
public ApplicationDbContext CreateDbContext(string[] args)
{
var builder = new DbContextOptionsBuilder<ApplicationDbContext>();
builder.UseSqlite("Data Source=temp.db");
return new ApplicationDbContext(builder.Options, true);
}
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
}
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
private readonly bool _designTime;
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options, bool designTime = false)
: base(options)
{
_designTime = designTime;
}
public DbSet<InvoiceData> Invoices
{
get; set;
}
public DbSet<AppData> Apps
{
get; set;
}
public DbSet<InvoiceEventData> InvoiceEvents
{
get; set;
}
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices
{
get; set;
}
public DbSet<PendingInvoiceData> PendingInvoices
{
get; set;
}
public DbSet<RefundAddressesData> RefundAddresses
{
get; set;
}
public DbSet<PaymentData> Payments
{
get; set;
}
public DbSet<PaymentRequestData> PaymentRequests
public DbSet<RefundData> Refunds
{
get; set;
}
public DbSet<PlannedTransaction> PlannedTransactions { get; set; }
public DbSet<PayjoinLock> PayjoinLocks { get; set; }
public DbSet<AppData> Apps { get; set; }
public DbSet<InvoiceEventData> InvoiceEvents { get; set; }
public DbSet<OffchainTransactionData> OffchainTransactions { get; set; }
public DbSet<HistoricalAddressInvoiceData> HistoricalAddressInvoices { get; set; }
public DbSet<PendingInvoiceData> PendingInvoices { get; set; }
public DbSet<PaymentData> Payments { get; set; }
public DbSet<PaymentRequestData> PaymentRequests { get; set; }
public DbSet<PullPaymentData> PullPayments { get; set; }
public DbSet<PayoutData> Payouts { get; set; }
public DbSet<WalletData> Wallets { get; set; }
public DbSet<WalletTransactionData> WalletTransactions { get; set; }
public DbSet<StoreData> Stores { get; set; }
public DbSet<UserStore> UserStore { get; set; }
public DbSet<AddressInvoiceData> AddressInvoices { get; set; }
public DbSet<SettingData> Settings { get; set; }
public DbSet<PairingCodeData> PairingCodes { get; set; }
public DbSet<PairedSINData> PairedSINData { get; set; }
public DbSet<APIKeyData> ApiKeys { get; set; }
public DbSet<StoredFile> Files { get; set; }
public DbSet<U2FDevice> U2FDevices { get; set; }
public DbSet<NotificationData> Notifications { get; set; }
public DbSet<StoreData> Stores
{
get; set;
}
public DbSet<UserStore> UserStore
{
get; set;
}
public DbSet<AddressInvoiceData> AddressInvoices
{
get; set;
}
public DbSet<SettingData> Settings
{
get; set;
}
public DbSet<PairingCodeData> PairingCodes
{
get; set;
}
public DbSet<PairedSINData> PairedSINData
{
get; set;
}
public DbSet<APIKeyData> ApiKeys
{
get; set;
}
public DbSet<StoredFile> Files
{
get; set;
}
public DbSet<U2FDevice> U2FDevices { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var isConfigured = optionsBuilder.Options.Extensions.OfType<RelationalOptionsExtension>().Any();
@@ -114,136 +73,44 @@ namespace BTCPayServer.Data
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<InvoiceData>()
.HasOne(o => o.StoreData)
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
NotificationData.OnModelCreating(builder);
InvoiceData.OnModelCreating(builder);
PaymentData.OnModelCreating(builder);
Data.UserStore.OnModelCreating(builder);
APIKeyData.OnModelCreating(builder);
AppData.OnModelCreating(builder);
AddressInvoiceData.OnModelCreating(builder);
PairingCodeData.OnModelCreating(builder);
PendingInvoiceData.OnModelCreating(builder);
Data.PairedSINData.OnModelCreating(builder);
HistoricalAddressInvoiceData.OnModelCreating(builder);
InvoiceEventData.OnModelCreating(builder);
PaymentRequestData.OnModelCreating(builder);
WalletTransactionData.OnModelCreating(builder);
PullPaymentData.OnModelCreating(builder);
PayoutData.OnModelCreating(builder);
RefundData.OnModelCreating(builder);
builder.Entity<PaymentData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PaymentData>()
.HasIndex(o => o.InvoiceDataId);
builder.Entity<RefundAddressesData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.RefundAddresses).OnDelete(DeleteBehavior.Cascade);
builder.Entity<RefundAddressesData>()
.HasIndex(o => o.InvoiceDataId);
builder.Entity<UserStore>()
.HasOne(o => o.StoreData)
.WithMany(i => i.UserStores).OnDelete(DeleteBehavior.Cascade);
builder.Entity<UserStore>()
.HasKey(t => new
{
t.ApplicationUserId,
t.StoreDataId
});
builder.Entity<APIKeyData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.APIKeys)
.HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade);
builder.Entity<APIKeyData>()
.HasIndex(o => o.StoreId);
builder.Entity<AppData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AppData>()
.HasOne(a => a.StoreData);
builder.Entity<UserStore>()
.HasOne(pt => pt.ApplicationUser)
.WithMany(p => p.UserStores)
.HasForeignKey(pt => pt.ApplicationUserId);
builder.Entity<UserStore>()
.HasOne(pt => pt.StoreData)
.WithMany(t => t.UserStores)
.HasForeignKey(pt => pt.StoreDataId);
builder.Entity<AddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AddressInvoiceData>()
#pragma warning disable CS0618
.HasKey(o => o.Address);
#pragma warning restore CS0618
builder.Entity<PairingCodeData>()
.HasKey(o => o.Id);
builder.Entity<PendingInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(o => o.PendingInvoices)
.HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PairedSINData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PairedSINData>(b =>
if (Database.IsSqlite() && !_designTime)
{
b.HasIndex(o => o.SIN);
b.HasIndex(o => o.StoreDataId);
});
builder.Entity<HistoricalAddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<HistoricalAddressInvoiceData>()
.HasKey(o => new
// SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations
// here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations
// To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset
// use the DateTimeOffsetToBinaryConverter
// Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754
// This only supports millisecond precision, but should be sufficient for most use cases.
foreach (var entityType in builder.Model.GetEntityTypes())
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.Address
#pragma warning restore CS0618
});
builder.Entity<InvoiceEventData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceEventData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.UniqueId
#pragma warning restore CS0618
});
builder.Entity<PaymentRequestData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.PaymentRequests)
.OnDelete(DeleteBehavior.Cascade);
builder.Entity<PaymentRequestData>()
.Property(e => e.Created)
.HasDefaultValue(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero));
builder.Entity<PaymentRequestData>()
.HasIndex(o => o.Status);
builder.Entity<WalletTransactionData>()
.HasKey(o => new
{
o.WalletDataId,
#pragma warning disable CS0618
o.TransactionId
#pragma warning restore CS0618
});
builder.Entity<WalletTransactionData>()
.HasOne(o => o.WalletData)
.WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade);
builder.UseOpenIddict<BTCPayOpenIdClient, BTCPayOpenIdAuthorization, OpenIddictScope<string>, BTCPayOpenIdToken, string>();
var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset));
foreach (var property in properties)
{
builder
.Entity(entityType.Name)
.Property(property.Name)
.HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter());
}
}
}
}
}

View File

@@ -1,13 +1,9 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
using JetBrains.Annotations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Metadata;
namespace BTCPayServer.Data
{
@@ -19,8 +15,8 @@ namespace BTCPayServer.Data
}
public class ApplicationDbContextFactory
{
string _ConnectionString;
DatabaseType _Type;
readonly string _ConnectionString;
readonly DatabaseType _Type;
public ApplicationDbContextFactory(DatabaseType type, string connectionString)
{
_ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
@@ -45,11 +41,7 @@ namespace BTCPayServer.Data
class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator
{
#if NETCOREAPP21
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies)
#else
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
#endif
public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts)
{
}

View File

@@ -1,15 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using BTCPayServer.Data;
namespace BTCPayServer.Data
{
// Add profile data for application users by adding properties to the ApplicationUser class
public class ApplicationUser : IdentityUser
{
public List<NotificationData> Notifications { get; set; }
public List<UserStore> UserStores
{
get;
@@ -20,15 +17,14 @@ namespace BTCPayServer.Data
{
get; set;
}
public List<BTCPayOpenIdClient> OpenIdClients { get; set; }
public List<StoredFile> StoredFiles
{
get;
set;
}
public List<U2FDevice> U2FDevices { get; set; }
public List<APIKeyData> APIKeys { get; set; }
}
}

View File

@@ -1,6 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdAuthorization : OpenIddictAuthorization<string, BTCPayOpenIdClient, BTCPayOpenIdToken> { }
}

View File

@@ -1,10 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdClient: OpenIddictApplication<string, BTCPayOpenIdAuthorization, BTCPayOpenIdToken>
{
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
}
}

View File

@@ -1,6 +0,0 @@
using OpenIddict.EntityFrameworkCore.Models;
namespace BTCPayServer.Data
{
public class BTCPayOpenIdToken : OpenIddictToken<string, BTCPayOpenIdClient, BTCPayOpenIdAuthorization> { }
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -40,5 +38,20 @@ namespace BTCPayServer.Data
{
get; set;
}
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<HistoricalAddressInvoiceData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<HistoricalAddressInvoiceData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.Address
#pragma warning restore CS0618
});
}
}
}

View File

@@ -1,7 +1,7 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -25,7 +25,6 @@ namespace BTCPayServer.Data
{
get; set;
}
public List<PaymentData> Payments
{
get; set;
@@ -36,11 +35,6 @@ namespace BTCPayServer.Data
get; set;
}
public List<RefundAddressesData> RefundAddresses
{
get; set;
}
public List<HistoricalAddressInvoiceData> HistoricalAddressInvoices
{
get; set;
@@ -79,6 +73,20 @@ namespace BTCPayServer.Data
{
get; set;
}
public bool Archived { get; set; }
public List<PendingInvoiceData> PendingInvoices { get; set; }
public List<RefundData> Refunds { get; set; }
public string CurrentRefundId { get; set; }
[ForeignKey("Id,CurrentRefundId")]
public RefundData CurrentRefund { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<InvoiceData>()
.HasOne(o => o.StoreData)
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
builder.Entity<InvoiceData>()
.HasOne(o => o.CurrentRefund);
}
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -22,5 +20,20 @@ namespace BTCPayServer.Data
}
public string Message { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<InvoiceEventData>()
.HasOne(o => o.InvoiceData)
.WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceEventData>()
.HasKey(o => new
{
o.InvoiceDataId,
#pragma warning disable CS0618
o.UniqueId
#pragma warning restore CS0618
});
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
public class NotificationData
{
[MaxLength(36)]
public string Id { get; set; }
public DateTimeOffset Created { get; set; }
[MaxLength(50)]
[Required]
public string ApplicationUserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
[MaxLength(100)]
[Required]
public string NotificationType { get; set; }
public bool Seen { get; set; }
public byte[] Blob { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<NotificationData>()
.HasOne(o => o.ApplicationUser)
.WithMany(n => n.Notifications)
.HasForeignKey(k => k.ApplicationUserId).OnDelete(DeleteBehavior.Cascade);
}
}
}

View File

@@ -0,0 +1,12 @@
using System.ComponentModel.DataAnnotations;
namespace BTCPayServer.Data
{
public class OffchainTransactionData
{
[Key]
[MaxLength(32 * 2)]
public string Id { get; set; }
public byte[] Blob { get; set; }
}
}

View File

@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System;
using Microsoft.EntityFrameworkCore;
namespace BTCPayServer.Data
{
@@ -33,5 +31,17 @@ namespace BTCPayServer.Data
{
get; set;
}
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<PairedSINData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade);
builder.Entity<PairedSINData>(b =>
{
b.HasIndex(o => o.SIN);
b.HasIndex(o => o.StoreDataId);
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More