diff --git a/.circleci/config.yml b/.circleci/config.yml
index 69a7c25c7..19669605e 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -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]+)/
diff --git a/.editorconfig b/.editorconfig
index 3a1882a26..9d3f6f768 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -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
\ No newline at end of file
+end_of_line = crlf
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index fcd47f7d3..f9c04659e 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -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 '...'
diff --git a/.gitignore b/.gitignore
index 35908eca9..fbf55eb42 100644
--- a/.gitignore
+++ b/.gitignore
@@ -293,3 +293,4 @@ BTCPayServer/wwwroot/bundles/*
!BTCPayServer/wwwroot/bundles/.gitignore
.vscode
+BTCPayServer/testpwd
diff --git a/BTCPayServer.Client/BTCPayServer.Client.csproj b/BTCPayServer.Client/BTCPayServer.Client.csproj
new file mode 100644
index 000000000..b3cd8c197
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServer.Client.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.1
+
+
+
+
+
+
+
+
+
diff --git a/BTCPayServer.Client/BTCPayServerClient.APIKeys.cs b/BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
new file mode 100644
index 000000000..219f026f2
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.APIKeys.cs
@@ -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 GetCurrentAPIKeyInfo(CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/api-keys/current"), token);
+ return await HandleResponse(response);
+ }
+
+ public virtual async Task 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(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);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Authorization.cs b/BTCPayServer.Client/BTCPayServerClient.Authorization.cs
new file mode 100644
index 000000000..9ec8062aa
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Authorization.cs
@@ -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()
+ {
+ {"strict", strict}, {"selectiveStores", selectiveStores}, {"permissions", permissions}
+ });
+
+ return result.Uri;
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Health.cs b/BTCPayServer.Client/BTCPayServerClient.Health.cs
new file mode 100644
index 000000000..0643b364d
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Health.cs
@@ -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 GetHealth(CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/health"), token);
+ return await HandleResponse(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs b/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs
new file mode 100644
index 000000000..953829bb6
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Lightning.Internal.cs
@@ -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 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(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> 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>(response);
+ }
+
+ public async Task 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(response);
+ }
+
+ public async Task 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(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 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(response);
+ }
+
+ public async Task 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(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs b/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs
new file mode 100644
index 000000000..f07e06c47
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Lightning.Store.cs
@@ -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 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(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> 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>(response);
+ }
+
+ public async Task 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(response);
+ }
+
+ public async Task 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(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 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(response);
+ }
+
+ public async Task 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(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.PaymentRequests.cs b/BTCPayServer.Client/BTCPayServerClient.PaymentRequests.cs
new file mode 100644
index 000000000..feeea0151
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.PaymentRequests.cs
@@ -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> GetPaymentRequests(string storeId,
+ CancellationToken token = default)
+ {
+ var response =
+ await _httpClient.SendAsync(CreateHttpRequest($"api/v1/stores/{storeId}/payment-requests"), token);
+ return await HandleResponse>(response);
+ }
+
+ public virtual async Task 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(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 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(response);
+ }
+
+ public virtual async Task 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(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.PullPayments.cs b/BTCPayServer.Client/BTCPayServerClient.PullPayments.cs
new file mode 100644
index 000000000..294c5a120
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.PullPayments.cs
@@ -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 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(response);
+ }
+ public async Task 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(response);
+ }
+
+ public async Task GetPullPayments(string storeId, bool includeArchived = false, CancellationToken cancellationToken = default)
+ {
+ Dictionary query = new Dictionary();
+ 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(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 GetPayouts(string pullPaymentId, bool includeCancelled = false, CancellationToken cancellationToken = default)
+ {
+ Dictionary query = new Dictionary();
+ 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(response);
+ }
+ public async Task 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(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 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(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.ServerInfo.cs b/BTCPayServer.Client/BTCPayServerClient.ServerInfo.cs
new file mode 100644
index 000000000..deefb5adc
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.ServerInfo.cs
@@ -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 GetServerInfo(CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/server/info"), token);
+ return await HandleResponse(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Stores.cs b/BTCPayServer.Client/BTCPayServerClient.Stores.cs
new file mode 100644
index 000000000..56e3a2a07
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Stores.cs
@@ -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> GetStores(CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/stores"), token);
+ return await HandleResponse>(response);
+ }
+
+ public virtual async Task GetStore(string storeId, CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(
+ CreateHttpRequest($"api/v1/stores/{storeId}"), token);
+ return await HandleResponse(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 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(response);
+ }
+
+ public virtual async Task 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(response);
+ }
+
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.Users.cs b/BTCPayServer.Client/BTCPayServerClient.Users.cs
new file mode 100644
index 000000000..0879bd7ce
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.Users.cs
@@ -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 GetCurrentUser(CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users/me"), token);
+ return await HandleResponse(response);
+ }
+
+ public virtual async Task CreateUser(CreateApplicationUserRequest request,
+ CancellationToken token = default)
+ {
+ var response = await _httpClient.SendAsync(CreateHttpRequest("api/v1/users", null, request, HttpMethod.Post), token);
+ return await HandleResponse(response);
+ }
+ }
+}
diff --git a/BTCPayServer.Client/BTCPayServerClient.cs b/BTCPayServer.Client/BTCPayServerClient.cs
new file mode 100644
index 000000000..833320c83
--- /dev/null
+++ b/BTCPayServer.Client/BTCPayServerClient.cs
@@ -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(await message.Content.ReadAsStringAsync());
+ ;
+ throw new GreenFieldValidationException(err);
+ }
+ else if (message.StatusCode == System.Net.HttpStatusCode.BadRequest)
+ {
+ var err = JsonConvert.DeserializeObject(await message.Content.ReadAsStringAsync());
+ throw new GreenFieldAPIException(err);
+ }
+
+ message.EnsureSuccessStatusCode();
+ }
+
+ protected async Task HandleResponse(HttpResponseMessage message)
+ {
+ await HandleResponse(message);
+ return JsonConvert.DeserializeObject(await message.Content.ReadAsStringAsync());
+ }
+
+ protected virtual HttpRequestMessage CreateHttpRequest(string path,
+ Dictionary 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(string path,
+ Dictionary queryPayload = null,
+ T bodyPayload = default, HttpMethod method = null)
+ {
+ var request = CreateHttpRequest(path, queryPayload, method);
+ if (typeof(T).IsPrimitive || !EqualityComparer.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 payload)
+ {
+ if (uri.Query.Length > 1)
+ uri.Query += "&";
+ foreach (KeyValuePair 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('&');
+ }
+ }
+}
diff --git a/BTCPayServer.Client/GreenFieldAPIException.cs b/BTCPayServer.Client/GreenFieldAPIException.cs
new file mode 100644
index 000000000..c6a1d88d7
--- /dev/null
+++ b/BTCPayServer.Client/GreenFieldAPIException.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/GreenFieldValidationException.cs b/BTCPayServer.Client/GreenFieldValidationException.cs
new file mode 100644
index 000000000..6c0a67664
--- /dev/null
+++ b/BTCPayServer.Client/GreenFieldValidationException.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/DecimalStringJsonConverter.cs b/BTCPayServer.Client/JsonConverters/DecimalStringJsonConverter.cs
new file mode 100644
index 000000000..f3ec5c797
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/DecimalStringJsonConverter.cs
@@ -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));
+ }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/LightMoneyJsonConverter.cs b/BTCPayServer.Client/JsonConverters/LightMoneyJsonConverter.cs
new file mode 100644
index 000000000..71581c2e6
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/LightMoneyJsonConverter.cs
@@ -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));
+ }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/MoneyJsonConverter.cs b/BTCPayServer.Client/JsonConverters/MoneyJsonConverter.cs
new file mode 100644
index 000000000..c6d0cfa7a
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/MoneyJsonConverter.cs
@@ -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));
+ }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs b/BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
new file mode 100644
index 000000000..c47eab9aa
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/NodeUriJsonConverter.cs
@@ -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
+ {
+ 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());
+ }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/PermissionJsonConverter.cs b/BTCPayServer.Client/JsonConverters/PermissionJsonConverter.cs
new file mode 100644
index 000000000..a408d56fd
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/PermissionJsonConverter.cs
@@ -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());
+ }
+ }
+}
diff --git a/BTCPayServer.Client/JsonConverters/TimeSpanJsonConverter.cs b/BTCPayServer.Client/JsonConverters/TimeSpanJsonConverter.cs
new file mode 100644
index 000000000..621fb783a
--- /dev/null
+++ b/BTCPayServer.Client/JsonConverters/TimeSpanJsonConverter.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ApiHealthData.cs b/BTCPayServer.Client/Models/ApiHealthData.cs
new file mode 100644
index 000000000..bc0f44588
--- /dev/null
+++ b/BTCPayServer.Client/Models/ApiHealthData.cs
@@ -0,0 +1,7 @@
+namespace BTCPayServer.Client.Models
+{
+ public class ApiHealthData
+ {
+ public bool Synchronized { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ApiKeyData.cs b/BTCPayServer.Client/Models/ApiKeyData.cs
new file mode 100644
index 000000000..9bdbc1c9c
--- /dev/null
+++ b/BTCPayServer.Client/Models/ApiKeyData.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ApplicationUserData.cs b/BTCPayServer.Client/Models/ApplicationUserData.cs
new file mode 100644
index 000000000..2ef97a166
--- /dev/null
+++ b/BTCPayServer.Client/Models/ApplicationUserData.cs
@@ -0,0 +1,25 @@
+namespace BTCPayServer.Client.Models
+{
+ public class ApplicationUserData
+ {
+ ///
+ /// the id of the user
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// the email AND username of the user
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// Whether the user has verified their email
+ ///
+ public bool EmailConfirmed { get; set; }
+
+ ///
+ /// whether the user needed to verify their email on account creation
+ ///
+ public bool RequiresEmailConfirmation { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ApprovePayoutRequest.cs b/BTCPayServer.Client/Models/ApprovePayoutRequest.cs
new file mode 100644
index 000000000..1f2191458
--- /dev/null
+++ b/BTCPayServer.Client/Models/ApprovePayoutRequest.cs
@@ -0,0 +1,8 @@
+namespace BTCPayServer.Client.Models
+{
+ public class ApprovePayoutRequest
+ {
+ public int Revision { get; set; }
+ public string RateRule { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ConnectToNodeRequest.cs b/BTCPayServer.Client/Models/ConnectToNodeRequest.cs
new file mode 100644
index 000000000..d877b6fa6
--- /dev/null
+++ b/BTCPayServer.Client/Models/ConnectToNodeRequest.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreateApiKeyRequest.cs b/BTCPayServer.Client/Models/CreateApiKeyRequest.cs
new file mode 100644
index 000000000..7239cf94f
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreateApiKeyRequest.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreateApplicationUserRequest.cs b/BTCPayServer.Client/Models/CreateApplicationUserRequest.cs
new file mode 100644
index 000000000..b348d179f
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreateApplicationUserRequest.cs
@@ -0,0 +1,20 @@
+namespace BTCPayServer.Client.Models
+{
+ public class CreateApplicationUserRequest
+ {
+ ///
+ /// the email AND username of the new user
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// password of the new user
+ ///
+ public string Password { get; set; }
+
+ ///
+ /// 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.
+ ///
+ public bool? IsAdministrator { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreateLightningInvoiceRequest.cs b/BTCPayServer.Client/Models/CreateLightningInvoiceRequest.cs
new file mode 100644
index 000000000..4282b422c
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreateLightningInvoiceRequest.cs
@@ -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; }
+
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreatePaymentRequestRequest.cs b/BTCPayServer.Client/Models/CreatePaymentRequestRequest.cs
new file mode 100644
index 000000000..17b64128d
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreatePaymentRequestRequest.cs
@@ -0,0 +1,6 @@
+namespace BTCPayServer.Client.Models
+{
+ public class CreatePaymentRequestRequest : PaymentRequestBaseData
+ {
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreatePayoutRequest.cs b/BTCPayServer.Client/Models/CreatePayoutRequest.cs
new file mode 100644
index 000000000..d8b665b4b
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreatePayoutRequest.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreatePullPaymentRequest.cs b/BTCPayServer.Client/Models/CreatePullPaymentRequest.cs
new file mode 100644
index 000000000..474c35624
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreatePullPaymentRequest.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/CreateStoreRequest.cs b/BTCPayServer.Client/Models/CreateStoreRequest.cs
new file mode 100644
index 000000000..cbba444f8
--- /dev/null
+++ b/BTCPayServer.Client/Models/CreateStoreRequest.cs
@@ -0,0 +1,6 @@
+namespace BTCPayServer.Client.Models
+{
+ public class CreateStoreRequest : StoreBaseData
+ {
+ }
+}
diff --git a/BTCPayServer.Client/Models/GreenfieldAPIError.cs b/BTCPayServer.Client/Models/GreenfieldAPIError.cs
new file mode 100644
index 000000000..f37f76df5
--- /dev/null
+++ b/BTCPayServer.Client/Models/GreenfieldAPIError.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/GreenfieldValidationError.cs b/BTCPayServer.Client/Models/GreenfieldValidationError.cs
new file mode 100644
index 000000000..f4afaf9bf
--- /dev/null
+++ b/BTCPayServer.Client/Models/GreenfieldValidationError.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/LightningInvoiceData.cs b/BTCPayServer.Client/Models/LightningInvoiceData.cs
new file mode 100644
index 000000000..7f855902c
--- /dev/null
+++ b/BTCPayServer.Client/Models/LightningInvoiceData.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/LightningNodeInformationData.cs b/BTCPayServer.Client/Models/LightningNodeInformationData.cs
new file mode 100644
index 000000000..80ac5fa6d
--- /dev/null
+++ b/BTCPayServer.Client/Models/LightningNodeInformationData.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/OpenLightningChannelRequest.cs b/BTCPayServer.Client/Models/OpenLightningChannelRequest.cs
new file mode 100644
index 000000000..390a72e4e
--- /dev/null
+++ b/BTCPayServer.Client/Models/OpenLightningChannelRequest.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs b/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs
new file mode 100644
index 000000000..4cfb0d37c
--- /dev/null
+++ b/BTCPayServer.Client/Models/PayLightningInvoiceRequest.cs
@@ -0,0 +1,8 @@
+namespace BTCPayServer.Client.Models
+{
+ public class PayLightningInvoiceRequest
+ {
+ [Newtonsoft.Json.JsonProperty("BOLT11")]
+ public string BOLT11 { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/PaymentRequestBaseData.cs b/BTCPayServer.Client/Models/PaymentRequestBaseData.cs
new file mode 100644
index 000000000..4a61af99f
--- /dev/null
+++ b/BTCPayServer.Client/Models/PaymentRequestBaseData.cs
@@ -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 AdditionalData { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/PaymentRequestData.cs b/BTCPayServer.Client/Models/PaymentRequestData.cs
new file mode 100644
index 000000000..c77d028bf
--- /dev/null
+++ b/BTCPayServer.Client/Models/PaymentRequestData.cs
@@ -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
+ }
+ }
+}
diff --git a/BTCPayServer.Client/Models/PayoutData.cs b/BTCPayServer.Client/Models/PayoutData.cs
new file mode 100644
index 000000000..ea8698bf1
--- /dev/null
+++ b/BTCPayServer.Client/Models/PayoutData.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/PullPaymentBaseData.cs b/BTCPayServer.Client/Models/PullPaymentBaseData.cs
new file mode 100644
index 000000000..e9256621e
--- /dev/null
+++ b/BTCPayServer.Client/Models/PullPaymentBaseData.cs
@@ -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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/ServerInfoData.cs b/BTCPayServer.Client/Models/ServerInfoData.cs
new file mode 100644
index 000000000..d028ed618
--- /dev/null
+++ b/BTCPayServer.Client/Models/ServerInfoData.cs
@@ -0,0 +1,47 @@
+using System.Collections.Generic;
+
+namespace BTCPayServer.Client.Models
+{
+ public class ServerInfoData
+ {
+ ///
+ /// the BTCPay Server version
+ ///
+ public string Version { get; set; }
+
+ ///
+ /// the Tor hostname
+ ///
+ public string Onion { get; set; }
+
+ ///
+ /// the payment methods this server supports
+ ///
+ public IEnumerable SupportedPaymentMethods { get; set; }
+
+ ///
+ /// are all chains fully synched
+ ///
+ public bool FullySynched { get; set; }
+
+ ///
+ /// detailed sync information per chain
+ ///
+ public IEnumerable 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; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/StoreBaseData.cs b/BTCPayServer.Client/Models/StoreBaseData.cs
new file mode 100644
index 000000000..33cb6c00f
--- /dev/null
+++ b/BTCPayServer.Client/Models/StoreBaseData.cs
@@ -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
+ {
+ ///
+ /// the name of the store
+ ///
+ 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 AdditionalData { get; set; }
+ }
+
+ public enum NetworkFeeMode
+ {
+ MultiplePaymentsOnly,
+ Always,
+ Never
+ }
+
+ public enum SpeedPolicy
+ {
+ HighSpeed = 0,
+ MediumSpeed = 1,
+ LowSpeed = 2,
+ LowMediumSpeed = 3
+ }
+}
diff --git a/BTCPayServer.Client/Models/StoreData.cs b/BTCPayServer.Client/Models/StoreData.cs
new file mode 100644
index 000000000..779fc1978
--- /dev/null
+++ b/BTCPayServer.Client/Models/StoreData.cs
@@ -0,0 +1,10 @@
+namespace BTCPayServer.Client.Models
+{
+ public class StoreData : StoreBaseData
+ {
+ ///
+ /// the id of the store
+ ///
+ public string Id { get; set; }
+ }
+}
diff --git a/BTCPayServer.Client/Models/UpdatePaymentRequestRequest.cs b/BTCPayServer.Client/Models/UpdatePaymentRequestRequest.cs
new file mode 100644
index 000000000..5ae327ae9
--- /dev/null
+++ b/BTCPayServer.Client/Models/UpdatePaymentRequestRequest.cs
@@ -0,0 +1,6 @@
+namespace BTCPayServer.Client.Models
+{
+ public class UpdatePaymentRequestRequest : PaymentRequestBaseData
+ {
+ }
+}
diff --git a/BTCPayServer.Client/Models/UpdateStoreRequest.cs b/BTCPayServer.Client/Models/UpdateStoreRequest.cs
new file mode 100644
index 000000000..27267c7e1
--- /dev/null
+++ b/BTCPayServer.Client/Models/UpdateStoreRequest.cs
@@ -0,0 +1,6 @@
+namespace BTCPayServer.Client.Models
+{
+ public class UpdateStoreRequest : StoreBaseData
+ {
+ }
+}
diff --git a/BTCPayServer.Client/Permissions.cs b/BTCPayServer.Client/Permissions.cs
new file mode 100644
index 000000000..94c7f19f4
--- /dev/null
+++ b/BTCPayServer.Client/Permissions.cs
@@ -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 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 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();
+ }
+ }
+}
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoin.cs
index cb6155a0b..26bce2f00 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoin.cs
@@ -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()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
- {0x4b24746U, DerivationType.Segwit }, //zpub
+ {0x04b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary()
{
- {0x043587cfU, DerivationType.Legacy},
- {0x044a5262U, DerivationType.SegwitP2SH},
- {0x045f1cf6U, DerivationType.Segwit}
+ {0x043587cfU, DerivationType.Legacy}, // tpub
+ {0x044a5262U, DerivationType.SegwitP2SH}, // upub
+ {0x045f1cf6U, DerivationType.Segwit} // vpub
}
});
}
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.BitcoinGold.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.BitcoinGold.cs
index 891483763..774c486ca 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.BitcoinGold.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.BitcoinGold.cs
@@ -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[]
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoinplus.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoinplus.cs
index 08d638dd1..ada2627ea 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoinplus.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcoinplus.cs
@@ -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[]
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcore.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcore.cs
index 8f2d85825..5a4eac655 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcore.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Bitcore.cs
@@ -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[]
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Chaincoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Chaincoin.cs
new file mode 100644
index 000000000..894c44212
--- /dev/null
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Chaincoin.cs
@@ -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'")
+ });
+ }
+ }
+}
+
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dash.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dash.cs
index 0794cb460..d293c0f4a 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dash.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dash.cs
@@ -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[]
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dogecoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dogecoin.cs
index ba70d681c..e76e23a81 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dogecoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Dogecoin.cs
@@ -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)"
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Feathercoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Feathercoin.cs
index 327ac5d5d..559e4f911 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Feathercoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Feathercoin.cs
@@ -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)"
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Groestlcoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Groestlcoin.cs
index c20bd80bd..69a650744 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Groestlcoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Groestlcoin.cs
@@ -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
});
}
}
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
index fd3f386fe..a7bd452c5 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Litecoin.cs
@@ -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),
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Monacoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Monacoin.cs
index 4c9f056d2..937373e7a 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Monacoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Monacoin.cs
@@ -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)"
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Polis.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Polis.cs
index 9ae9a8dbc..2fbc99c56 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Polis.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Polis.cs
@@ -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),
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Ufo.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Ufo.cs
index 01b2b3b83..014ec57ec 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Ufo.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Ufo.cs
@@ -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)"
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Viacoin.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Viacoin.cs
index b7260d4ad..43effbaad 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Viacoin.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.Viacoin.cs
@@ -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[]
diff --git a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs
index 0384ff5d8..6456d6cfd 100644
--- a/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs
+++ b/BTCPayServer.Common/Altcoins/BTCPayNetworkProvider.cs
@@ -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 _Networks = new Dictionary();
+ readonly Dictionary _Networks = new Dictionary();
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())
{
- if(network.ElectrumMapping.Count == 0)
+ if (network.ElectrumMapping.Count == 0)
{
network.ElectrumMapping = GetNetwork("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("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(cryptoCode);
+ return GetNetwork(cryptoCode.ToUpperInvariant());
}
- public T GetNetwork(string cryptoCode) where T: BTCPayNetworkBase
+ public T GetNetwork(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("BTC");
diff --git a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs
new file mode 100644
index 000000000..bef65cd3f
--- /dev/null
+++ b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.Liquid.cs
@@ -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.PeggedAssetId : ElementsParams.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
+ });
+ }
+ }
+
+
+}
diff --git a/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs
new file mode 100644
index 000000000..a14c49c2b
--- /dev/null
+++ b/BTCPayServer.Common/Altcoins/Liquid/BTCPayNetworkProvider.LiquidAssets.cs
@@ -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
+ });
+ }
+ }
+
+
+}
diff --git a/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs b/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs
new file mode 100644
index 000000000..47321e83f
--- /dev/null
+++ b/BTCPayServer.Common/Altcoins/Liquid/ElementsLikeBtcPayNetwork.cs
@@ -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}";
+ }
+ }
+}
diff --git a/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs b/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
index 208aea77c..012c4bdd3 100644
--- a/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/BTCPayNetworkProvider.Monero.cs
@@ -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"
});
}
diff --git a/BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs b/BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs
index 8c4a73f66..73650a523 100644
--- a/BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/RPC/JsonRpcClient.cs
@@ -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 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
{
-
+
[JsonProperty("result")] public T Result { get; set; }
[JsonProperty("error")] public JsonRpcResultError Error { get; set; }
diff --git a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransferByTransactionIdResponse.cs b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransferByTransactionIdResponse.cs
index b620e1028..025c22151 100644
--- a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransferByTransactionIdResponse.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransferByTransactionIdResponse.cs
@@ -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; }
diff --git a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransfersResponse.cs b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransfersResponse.cs
index ffc7997a3..f0d3b4076 100644
--- a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransfersResponse.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/GetTransfersResponse.cs
@@ -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; }
diff --git a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/ParseStringConverter.cs b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/ParseStringConverter.cs
index 22e34fbeb..3e929f3da 100644
--- a/BTCPayServer.Common/Altcoins/Monero/RPC/Models/ParseStringConverter.cs
+++ b/BTCPayServer.Common/Altcoins/Monero/RPC/Models/ParseStringConverter.cs
@@ -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(reader);
long l;
if (Int64.TryParse(value, out l))
diff --git a/BTCPayServer.Common/BTCPayNetwork.cs b/BTCPayServer.Common/BTCPayNetwork.cs
index 55aa6e72b..155649991 100644
--- a/BTCPayServer.Common/BTCPayNetwork.cs
+++ b/BTCPayServer.Common/BTCPayNetwork.cs
@@ -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 _Settings;
+ static readonly Dictionary _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 ElectrumMapping = new Dictionary();
+ 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
{
diff --git a/BTCPayServer.Common/BTCPayServer.Common.csproj b/BTCPayServer.Common/BTCPayServer.Common.csproj
index f0a8011e3..6293fed52 100644
--- a/BTCPayServer.Common/BTCPayServer.Common.csproj
+++ b/BTCPayServer.Common/BTCPayServer.Common.csproj
@@ -3,8 +3,7 @@
-
-
-
+
+
diff --git a/BTCPayServer.Common/CustomThreadPool.cs b/BTCPayServer.Common/CustomThreadPool.cs
index cfa09a52a..8117663f1 100644
--- a/BTCPayServer.Common/CustomThreadPool.cs
+++ b/BTCPayServer.Common/CustomThreadPool.cs
@@ -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 _Exited;
+ readonly CancellationTokenSource _Cancel = new CancellationTokenSource();
+ readonly TaskCompletionSource _Exited;
int _ExitedCount = 0;
- Thread[] _Threads;
+ readonly Thread[] _Threads;
Exception _UnhandledException;
- BlockingCollection<(Action, TaskCompletionSource