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)> _Actions = new BlockingCollection<(Action, TaskCompletionSource)>(new ConcurrentQueue<(Action, TaskCompletionSource)>()); + readonly BlockingCollection<(Action, TaskCompletionSource)> _Actions = new BlockingCollection<(Action, TaskCompletionSource)>(new ConcurrentQueue<(Action, TaskCompletionSource)>()); public CustomThreadPool(int threadCount, string threadName) { diff --git a/BTCPayServer.Common/Extensions.cs b/BTCPayServer.Common/Extensions.cs index 6fd3b0025..c2d0fe45b 100644 --- a/BTCPayServer.Common/Extensions.cs +++ b/BTCPayServer.Common/Extensions.cs @@ -1,6 +1,4 @@ -using System; using System.Collections.Generic; -using System.Text; namespace BTCPayServer { diff --git a/BTCPayServer.Common/Logging/ConsoleLogger.cs b/BTCPayServer.Common/Logging/ConsoleLogger.cs index 5c7404b74..def1a79f4 100644 --- a/BTCPayServer.Common/Logging/ConsoleLogger.cs +++ b/BTCPayServer.Common/Logging/ConsoleLogger.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Runtime.InteropServices; using System.Text; @@ -13,7 +13,7 @@ namespace BTCPayServer.Logging { public class CustomConsoleLogProvider : ILoggerProvider { - ConsoleLoggerProcessor _Processor; + readonly ConsoleLoggerProcessor _Processor; public CustomConsoleLogProvider(ConsoleLoggerProcessor processor) { _Processor = processor; diff --git a/BTCPayServer.Common/Logging/Logs.cs b/BTCPayServer.Common/Logging/Logs.cs index 52977c5d6..81846a910 100644 --- a/BTCPayServer.Common/Logging/Logs.cs +++ b/BTCPayServer.Common/Logging/Logs.cs @@ -1,9 +1,6 @@ -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; namespace BTCPayServer.Logging { @@ -43,7 +40,7 @@ namespace BTCPayServer.Logging public class FuncLoggerFactory : ILoggerFactory { - private Func createLogger; + private readonly Func createLogger; public FuncLoggerFactory(Func createLogger) { this.createLogger = createLogger; diff --git a/BTCPayServer.Common/Shims.cs b/BTCPayServer.Common/Shims.cs index e5966b6ff..b85db38f8 100644 --- a/BTCPayServer.Common/Shims.cs +++ b/BTCPayServer.Common/Shims.cs @@ -1,13 +1,9 @@ -#if !NETCOREAPP21 // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.IO; using System.Text; -using System.Collections; -using System.Collections.Generic; -using Newtonsoft.Json; namespace Microsoft.Extensions.Logging.Console.Internal @@ -129,7 +125,7 @@ namespace Microsoft.Extensions.Logging.Console.Internal } } - internal class AnsiSystemConsole : IAnsiSystemConsole + internal class AnsiSystemConsole : IAnsiSystemConsole { private readonly TextWriter _textWriter; @@ -152,13 +148,13 @@ namespace Microsoft.Extensions.Logging.Console { void Write(string message); } - public interface IConsole + public interface IConsole { void Write(string message, ConsoleColor? background, ConsoleColor? foreground); void WriteLine(string message, ConsoleColor? background, ConsoleColor? foreground); void Flush(); } - internal class WindowsLogConsole : IConsole + internal class WindowsLogConsole : IConsole { private readonly TextWriter _textWriter; @@ -235,25 +231,3 @@ namespace Microsoft.Extensions.Logging.Abstractions.Internal } } } - - -#else -using Microsoft.Extensions.Options; -using Newtonsoft.Json; - -namespace Microsoft.AspNetCore.Mvc -{ - /// - /// Provides programmatic configuration for JSON formatters using Newtonsoft.JSON. - /// - public class MvcNewtonsoftJsonOptions - { - IOptions jsonOptions; - public MvcNewtonsoftJsonOptions(IOptions jsonOptions) - { - this.jsonOptions = jsonOptions; - } - public JsonSerializerSettings SerializerSettings => this.jsonOptions.Value.SerializerSettings; - } -} -#endif diff --git a/BTCPayServer.Common/SynchronizationContextRemover.cs b/BTCPayServer.Common/SynchronizationContextRemover.cs index edd06e3ff..363ee2897 100644 --- a/BTCPayServer.Common/SynchronizationContextRemover.cs +++ b/BTCPayServer.Common/SynchronizationContextRemover.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System; using System.Runtime.CompilerServices; using System.Threading; -using System.Threading.Tasks; namespace BTCPayServer { diff --git a/BTCPayServer.Common/ZipUtils.cs b/BTCPayServer.Common/ZipUtils.cs index 90eb42b5a..bd41d7792 100644 --- a/BTCPayServer.Common/ZipUtils.cs +++ b/BTCPayServer.Common/ZipUtils.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Text; diff --git a/BTCPayServer.Data/BTCPayServer.Data.csproj b/BTCPayServer.Data/BTCPayServer.Data.csproj index 9589d1924..7436b550d 100644 --- a/BTCPayServer.Data/BTCPayServer.Data.csproj +++ b/BTCPayServer.Data/BTCPayServer.Data.csproj @@ -1,19 +1,18 @@  - - - - - - - - + - - - - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/BTCPayServer.Data/Data/APIKeyData.cs b/BTCPayServer.Data/Data/APIKeyData.cs index b1a27af6d..db02b20cd 100644 --- a/BTCPayServer.Data/Data/APIKeyData.cs +++ b/BTCPayServer.Data/Data/APIKeyData.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; using System.ComponentModel.DataAnnotations; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -11,15 +8,47 @@ namespace BTCPayServer.Data [MaxLength(50)] public string Id { - get; set; + get; + set; } - [MaxLength(50)] - public string StoreId - { - get; set; - } + [MaxLength(50)] public string StoreId { get; set; } + [MaxLength(50)] public string UserId { get; set; } + + public APIKeyType Type { get; set; } = APIKeyType.Legacy; + + public byte[] Blob { get; set; } public StoreData StoreData { get; set; } + public ApplicationUser User { get; set; } + public string Label { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.APIKeys) + .HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .HasOne(o => o.User) + .WithMany(i => i.APIKeys) + .HasForeignKey(i => i.UserId).OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .HasIndex(o => o.StoreId); + } + } + + public class APIKeyBlob + { + public string[] Permissions { get; set; } + + } + + public enum APIKeyType + { + Legacy, + Permanent } } diff --git a/BTCPayServer.Data/Data/AddressInvoiceData.cs b/BTCPayServer.Data/Data/AddressInvoiceData.cs index 4ae68a567..f71d338e9 100644 --- a/BTCPayServer.Data/Data/AddressInvoiceData.cs +++ b/BTCPayServer.Data/Data/AddressInvoiceData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -32,5 +30,15 @@ namespace BTCPayServer.Data get; set; } + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade); + builder.Entity() +#pragma warning disable CS0618 + .HasKey(o => o.Address); +#pragma warning restore CS0618 + } } } diff --git a/BTCPayServer.Data/Data/AppData.cs b/BTCPayServer.Data/Data/AppData.cs index bf8743fed..946fa7065 100644 --- a/BTCPayServer.Data/Data/AppData.cs +++ b/BTCPayServer.Data/Data/AppData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; namespace BTCPayServer.Data @@ -37,5 +35,14 @@ namespace BTCPayServer.Data { Settings = value == null ? null : JsonConvert.SerializeObject(value); } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasOne(a => a.StoreData); + } } } diff --git a/BTCPayServer.Data/Data/ApplicationDbContext.cs b/BTCPayServer.Data/Data/ApplicationDbContext.cs index d68f4f240..1de87caa7 100644 --- a/BTCPayServer.Data/Data/ApplicationDbContext.cs +++ b/BTCPayServer.Data/Data/ApplicationDbContext.cs @@ -1,109 +1,68 @@ -using System; +using System; using System.Linq; using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; using Microsoft.EntityFrameworkCore.Infrastructure; -using OpenIddict.EntityFrameworkCore.Models; namespace BTCPayServer.Data { - public class ApplicationDbContext : IdentityDbContext + public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory { - public ApplicationDbContext() + public ApplicationDbContext CreateDbContext(string[] args) { + var builder = new DbContextOptionsBuilder(); + + builder.UseSqlite("Data Source=temp.db"); + + return new ApplicationDbContext(builder.Options, true); } - public ApplicationDbContext(DbContextOptions options) + } + + public class ApplicationDbContext : IdentityDbContext + { + private readonly bool _designTime; + + public ApplicationDbContext(DbContextOptions options, bool designTime = false) : base(options) { + _designTime = designTime; } public DbSet Invoices { get; set; } - - public DbSet Apps - { - get; set; - } - - public DbSet InvoiceEvents - { - get; set; - } - - public DbSet HistoricalAddressInvoices - { - get; set; - } - - public DbSet PendingInvoices - { - get; set; - } - public DbSet RefundAddresses - { - get; set; - } - - public DbSet Payments - { - get; set; - } - - public DbSet PaymentRequests + public DbSet Refunds { get; set; } + public DbSet PlannedTransactions { get; set; } + public DbSet PayjoinLocks { get; set; } + public DbSet Apps { get; set; } + public DbSet InvoiceEvents { get; set; } + public DbSet OffchainTransactions { get; set; } + public DbSet HistoricalAddressInvoices { get; set; } + public DbSet PendingInvoices { get; set; } + public DbSet Payments { get; set; } + public DbSet PaymentRequests { get; set; } + public DbSet PullPayments { get; set; } + public DbSet Payouts { get; set; } public DbSet Wallets { get; set; } public DbSet WalletTransactions { get; set; } + public DbSet Stores { get; set; } + public DbSet UserStore { get; set; } + public DbSet AddressInvoices { get; set; } + public DbSet Settings { get; set; } + public DbSet PairingCodes { get; set; } + public DbSet PairedSINData { get; set; } + public DbSet ApiKeys { get; set; } + public DbSet Files { get; set; } + public DbSet U2FDevices { get; set; } + public DbSet Notifications { get; set; } - public DbSet Stores - { - get; set; - } - - public DbSet UserStore - { - get; set; - } - - public DbSet AddressInvoices - { - get; set; - } - - public DbSet Settings - { - get; set; - } - - - public DbSet PairingCodes - { - get; set; - } - - public DbSet PairedSINData - { - get; set; - } - - public DbSet ApiKeys - { - get; set; - } - - public DbSet Files - { - get; set; - } - - - public DbSet U2FDevices { get; set; } - protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var isConfigured = optionsBuilder.Options.Extensions.OfType().Any(); @@ -114,136 +73,44 @@ namespace BTCPayServer.Data protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade); - builder.Entity().HasIndex(o => o.StoreDataId); + NotificationData.OnModelCreating(builder); + InvoiceData.OnModelCreating(builder); + PaymentData.OnModelCreating(builder); + Data.UserStore.OnModelCreating(builder); + APIKeyData.OnModelCreating(builder); + AppData.OnModelCreating(builder); + AddressInvoiceData.OnModelCreating(builder); + PairingCodeData.OnModelCreating(builder); + PendingInvoiceData.OnModelCreating(builder); + Data.PairedSINData.OnModelCreating(builder); + HistoricalAddressInvoiceData.OnModelCreating(builder); + InvoiceEventData.OnModelCreating(builder); + PaymentRequestData.OnModelCreating(builder); + WalletTransactionData.OnModelCreating(builder); + PullPaymentData.OnModelCreating(builder); + PayoutData.OnModelCreating(builder); + RefundData.OnModelCreating(builder); - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasIndex(o => o.InvoiceDataId); - - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(i => i.RefundAddresses).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasIndex(o => o.InvoiceDataId); - - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(i => i.UserStores).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasKey(t => new - { - t.ApplicationUserId, - t.StoreDataId - }); - - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(i => i.APIKeys) - .HasForeignKey(i => i.StoreId).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasIndex(o => o.StoreId); - - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasOne(a => a.StoreData); - - builder.Entity() - .HasOne(pt => pt.ApplicationUser) - .WithMany(p => p.UserStores) - .HasForeignKey(pt => pt.ApplicationUserId); - - builder.Entity() - .HasOne(pt => pt.StoreData) - .WithMany(t => t.UserStores) - .HasForeignKey(pt => pt.StoreDataId); - - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(i => i.AddressInvoices).OnDelete(DeleteBehavior.Cascade); - builder.Entity() -#pragma warning disable CS0618 - .HasKey(o => o.Address); -#pragma warning restore CS0618 - - builder.Entity() - .HasKey(o => o.Id); - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(o => o.PendingInvoices) - .HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade); - - - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade); - builder.Entity(b => + if (Database.IsSqlite() && !_designTime) { - b.HasIndex(o => o.SIN); - b.HasIndex(o => o.StoreDataId); - }); - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasKey(o => new + // SQLite does not have proper support for DateTimeOffset via Entity Framework Core, see the limitations + // here: https://docs.microsoft.com/en-us/ef/core/providers/sqlite/limitations#query-limitations + // To work around this, when the Sqlite database provider is used, all model properties of type DateTimeOffset + // use the DateTimeOffsetToBinaryConverter + // Based on: https://github.com/aspnet/EntityFrameworkCore/issues/10784#issuecomment-415769754 + // This only supports millisecond precision, but should be sufficient for most use cases. + foreach (var entityType in builder.Model.GetEntityTypes()) { - o.InvoiceDataId, -#pragma warning disable CS0618 - o.Address -#pragma warning restore CS0618 - }); - - - builder.Entity() - .HasOne(o => o.InvoiceData) - .WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .HasKey(o => new - { - o.InvoiceDataId, -#pragma warning disable CS0618 - o.UniqueId -#pragma warning restore CS0618 - }); - - - builder.Entity() - .HasOne(o => o.StoreData) - .WithMany(i => i.PaymentRequests) - .OnDelete(DeleteBehavior.Cascade); - builder.Entity() - .Property(e => e.Created) - .HasDefaultValue(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)); - - builder.Entity() - .HasIndex(o => o.Status); - - builder.Entity() - .HasKey(o => new - { - o.WalletDataId, -#pragma warning disable CS0618 - o.TransactionId -#pragma warning restore CS0618 - }); - builder.Entity() - .HasOne(o => o.WalletData) - .WithMany(w => w.WalletTransactions).OnDelete(DeleteBehavior.Cascade); - - builder.UseOpenIddict, BTCPayOpenIdToken, string>(); - + var properties = entityType.ClrType.GetProperties().Where(p => p.PropertyType == typeof(DateTimeOffset)); + foreach (var property in properties) + { + builder + .Entity(entityType.Name) + .Property(property.Name) + .HasConversion(new Microsoft.EntityFrameworkCore.Storage.ValueConversion.DateTimeOffsetToBinaryConverter()); + } + } + } } } diff --git a/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs b/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs index 25093fc9b..cdbcfe3ef 100644 --- a/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs +++ b/BTCPayServer.Data/Data/ApplicationDbContextFactory.cs @@ -1,13 +1,9 @@ -using Microsoft.EntityFrameworkCore; using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Migrations; using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations; -using JetBrains.Annotations; using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations; -using Microsoft.EntityFrameworkCore.Metadata; namespace BTCPayServer.Data { @@ -19,8 +15,8 @@ namespace BTCPayServer.Data } public class ApplicationDbContextFactory { - string _ConnectionString; - DatabaseType _Type; + readonly string _ConnectionString; + readonly DatabaseType _Type; public ApplicationDbContextFactory(DatabaseType type, string connectionString) { _ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); @@ -45,11 +41,7 @@ namespace BTCPayServer.Data class CustomNpgsqlMigrationsSqlGenerator : NpgsqlMigrationsSqlGenerator { -#if NETCOREAPP21 - public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies) : base(dependencies) -#else - public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts) -#endif + public CustomNpgsqlMigrationsSqlGenerator(MigrationsSqlGeneratorDependencies dependencies, IMigrationsAnnotationProvider annotations, Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal.INpgsqlOptions opts) : base(dependencies, annotations, opts) { } diff --git a/BTCPayServer.Data/Data/ApplicationUser.cs b/BTCPayServer.Data/Data/ApplicationUser.cs index fd2b1b5d2..8f853392a 100644 --- a/BTCPayServer.Data/Data/ApplicationUser.cs +++ b/BTCPayServer.Data/Data/ApplicationUser.cs @@ -1,15 +1,12 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; -using BTCPayServer.Data; namespace BTCPayServer.Data { // Add profile data for application users by adding properties to the ApplicationUser class public class ApplicationUser : IdentityUser { + public List Notifications { get; set; } public List UserStores { get; @@ -20,15 +17,14 @@ namespace BTCPayServer.Data { get; set; } - - public List OpenIdClients { get; set; } - + public List StoredFiles { get; set; } - + public List U2FDevices { get; set; } + public List APIKeys { get; set; } } } diff --git a/BTCPayServer.Data/Data/BTCPayOpenIdAuthorization.cs b/BTCPayServer.Data/Data/BTCPayOpenIdAuthorization.cs deleted file mode 100644 index cfc3c3bfc..000000000 --- a/BTCPayServer.Data/Data/BTCPayOpenIdAuthorization.cs +++ /dev/null @@ -1,6 +0,0 @@ -using OpenIddict.EntityFrameworkCore.Models; - -namespace BTCPayServer.Data -{ - public class BTCPayOpenIdAuthorization : OpenIddictAuthorization { } -} diff --git a/BTCPayServer.Data/Data/BTCPayOpenIdClient.cs b/BTCPayServer.Data/Data/BTCPayOpenIdClient.cs deleted file mode 100644 index eb4e94b25..000000000 --- a/BTCPayServer.Data/Data/BTCPayOpenIdClient.cs +++ /dev/null @@ -1,10 +0,0 @@ -using OpenIddict.EntityFrameworkCore.Models; - -namespace BTCPayServer.Data -{ - public class BTCPayOpenIdClient: OpenIddictApplication - { - public string ApplicationUserId { get; set; } - public ApplicationUser ApplicationUser { get; set; } - } -} diff --git a/BTCPayServer.Data/Data/BTCPayOpenIdToken.cs b/BTCPayServer.Data/Data/BTCPayOpenIdToken.cs deleted file mode 100644 index 94951a118..000000000 --- a/BTCPayServer.Data/Data/BTCPayOpenIdToken.cs +++ /dev/null @@ -1,6 +0,0 @@ -using OpenIddict.EntityFrameworkCore.Models; - -namespace BTCPayServer.Data -{ - public class BTCPayOpenIdToken : OpenIddictToken { } -} diff --git a/BTCPayServer.Data/Data/HistoricalAddressInvoiceData.cs b/BTCPayServer.Data/Data/HistoricalAddressInvoiceData.cs index 9bcc2fbdc..c77d2beac 100644 --- a/BTCPayServer.Data/Data/HistoricalAddressInvoiceData.cs +++ b/BTCPayServer.Data/Data/HistoricalAddressInvoiceData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -40,5 +38,20 @@ namespace BTCPayServer.Data { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.HistoricalAddressInvoices).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasKey(o => new + { + o.InvoiceDataId, +#pragma warning disable CS0618 + o.Address +#pragma warning restore CS0618 + }); + } } } diff --git a/BTCPayServer.Data/Data/InvoiceData.cs b/BTCPayServer.Data/Data/InvoiceData.cs index 22484a81e..4bfc065f7 100644 --- a/BTCPayServer.Data/Data/InvoiceData.cs +++ b/BTCPayServer.Data/Data/InvoiceData.cs @@ -1,7 +1,7 @@ -using System; +using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -25,7 +25,6 @@ namespace BTCPayServer.Data { get; set; } - public List Payments { get; set; @@ -36,11 +35,6 @@ namespace BTCPayServer.Data get; set; } - public List RefundAddresses - { - get; set; - } - public List HistoricalAddressInvoices { get; set; @@ -79,6 +73,20 @@ namespace BTCPayServer.Data { get; set; } + public bool Archived { get; set; } public List PendingInvoices { get; set; } + public List Refunds { get; set; } + public string CurrentRefundId { get; set; } + [ForeignKey("Id,CurrentRefundId")] + public RefundData CurrentRefund { get; set; } + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade); + builder.Entity().HasIndex(o => o.StoreDataId); + builder.Entity() + .HasOne(o => o.CurrentRefund); + } } } diff --git a/BTCPayServer.Data/Data/InvoiceEventData.cs b/BTCPayServer.Data/Data/InvoiceEventData.cs index 23dd7901e..d41c65f17 100644 --- a/BTCPayServer.Data/Data/InvoiceEventData.cs +++ b/BTCPayServer.Data/Data/InvoiceEventData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -22,5 +20,20 @@ namespace BTCPayServer.Data } public string Message { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.Events).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasKey(o => new + { + o.InvoiceDataId, +#pragma warning disable CS0618 + o.UniqueId +#pragma warning restore CS0618 + }); + } } } diff --git a/BTCPayServer.Data/Data/NotificationData.cs b/BTCPayServer.Data/Data/NotificationData.cs new file mode 100644 index 000000000..aa9d496b4 --- /dev/null +++ b/BTCPayServer.Data/Data/NotificationData.cs @@ -0,0 +1,30 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; + +namespace BTCPayServer.Data +{ + public class NotificationData + { + [MaxLength(36)] + public string Id { get; set; } + public DateTimeOffset Created { get; set; } + [MaxLength(50)] + [Required] + public string ApplicationUserId { get; set; } + public ApplicationUser ApplicationUser { get; set; } + [MaxLength(100)] + [Required] + public string NotificationType { get; set; } + public bool Seen { get; set; } + public byte[] Blob { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.ApplicationUser) + .WithMany(n => n.Notifications) + .HasForeignKey(k => k.ApplicationUserId).OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/BTCPayServer.Data/Data/OffchainTransactionData.cs b/BTCPayServer.Data/Data/OffchainTransactionData.cs new file mode 100644 index 000000000..17a17eb9d --- /dev/null +++ b/BTCPayServer.Data/Data/OffchainTransactionData.cs @@ -0,0 +1,12 @@ +using System.ComponentModel.DataAnnotations; + +namespace BTCPayServer.Data +{ + public class OffchainTransactionData + { + [Key] + [MaxLength(32 * 2)] + public string Id { get; set; } + public byte[] Blob { get; set; } + } +} diff --git a/BTCPayServer.Data/Data/PairedSINData.cs b/BTCPayServer.Data/Data/PairedSINData.cs index 396cd5275..36e7c00c6 100644 --- a/BTCPayServer.Data/Data/PairedSINData.cs +++ b/BTCPayServer.Data/Data/PairedSINData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -33,5 +31,17 @@ namespace BTCPayServer.Data { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.PairedSINs).OnDelete(DeleteBehavior.Cascade); + builder.Entity(b => + { + b.HasIndex(o => o.SIN); + b.HasIndex(o => o.StoreDataId); + }); + } } } diff --git a/BTCPayServer.Data/Data/PairingCodeData.cs b/BTCPayServer.Data/Data/PairingCodeData.cs index cf1226e69..21f6fed6f 100644 --- a/BTCPayServer.Data/Data/PairingCodeData.cs +++ b/BTCPayServer.Data/Data/PairingCodeData.cs @@ -1,7 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -46,5 +44,11 @@ namespace BTCPayServer.Data get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasKey(o => o.Id); + } } } diff --git a/BTCPayServer.Data/Data/PayjoinLock.cs b/BTCPayServer.Data/Data/PayjoinLock.cs new file mode 100644 index 000000000..65bc9fb85 --- /dev/null +++ b/BTCPayServer.Data/Data/PayjoinLock.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace BTCPayServer.Data +{ + /// + /// We represent the locks of the PayjoinRepository + /// with this table. (Both, our utxo we locked as part of a payjoin + /// and the utxo of the payer which were used to pay us) + /// + public class PayjoinLock + { + [Key] + [MaxLength(100)] + public string Id { get; set; } + } +} diff --git a/BTCPayServer.Data/Data/PaymentData.cs b/BTCPayServer.Data/Data/PaymentData.cs index fb7c98784..32210eb97 100644 --- a/BTCPayServer.Data/Data/PaymentData.cs +++ b/BTCPayServer.Data/Data/PaymentData.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -29,5 +26,14 @@ namespace BTCPayServer.Data { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(i => i.Payments).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasIndex(o => o.InvoiceDataId); + } } } diff --git a/BTCPayServer.Data/Data/PaymentRequestData.cs b/BTCPayServer.Data/Data/PaymentRequestData.cs index 0911b233b..0ac26af41 100644 --- a/BTCPayServer.Data/Data/PaymentRequestData.cs +++ b/BTCPayServer.Data/Data/PaymentRequestData.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +using System; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -12,34 +11,25 @@ namespace BTCPayServer.Data get; set; } public string StoreDataId { get; set; } + public bool Archived { get; set; } public StoreData StoreData { get; set; } - public PaymentRequestStatus Status { get; set; } + public Client.Models.PaymentRequestData.PaymentRequestStatus Status { get; set; } public byte[] Blob { get; set; } - public class PaymentRequestBlob + internal static void OnModelCreating(ModelBuilder builder) { - 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; } - } - - public enum PaymentRequestStatus - { - Pending = 0, - Completed = 1, - Expired = 2 + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.PaymentRequests) + .OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .Property(e => e.Created) + .HasDefaultValue(new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)); + builder.Entity() + .HasIndex(o => o.Status); } } } diff --git a/BTCPayServer.Data/Data/PayoutData.cs b/BTCPayServer.Data/Data/PayoutData.cs new file mode 100644 index 000000000..9ef8e22c7 --- /dev/null +++ b/BTCPayServer.Data/Data/PayoutData.cs @@ -0,0 +1,62 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; +using NBitcoin; + +namespace BTCPayServer.Data +{ + public class PayoutData + { + [Key] + [MaxLength(30)] + public string Id { get; set; } + public DateTimeOffset Date { get; set; } + public string PullPaymentDataId { get; set; } + public PullPaymentData PullPaymentData { get; set; } + [MaxLength(20)] + public PayoutState State { get; set; } + [MaxLength(20)] + [Required] + public string PaymentMethodId { get; set; } + public string Destination { get; set; } + public byte[] Blob { get; set; } + public byte[] Proof { get; set; } + public bool IsInPeriod(PullPaymentData pp, DateTimeOffset now) + { + var period = pp.GetPeriod(now); + if (period is { } p) + { + return p.Start <= Date && (p.End is DateTimeOffset end ? Date < end : true); + } + else + { + return false; + } + } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.PullPaymentData) + .WithMany(o => o.Payouts).OnDelete(DeleteBehavior.Cascade); + + builder.Entity() + .Property(o => o.State) + .HasConversion(); + builder.Entity() + .HasIndex(o => o.Destination) + .IsUnique(); + builder.Entity() + .HasIndex(o => o.State); + } + } + + public enum PayoutState + { + AwaitingApproval, + AwaitingPayment, + InProgress, + Completed, + Cancelled + } +} diff --git a/BTCPayServer.Data/Data/PendingInvoiceData.cs b/BTCPayServer.Data/Data/PendingInvoiceData.cs index 2bfdde074..43e7d2380 100644 --- a/BTCPayServer.Data/Data/PendingInvoiceData.cs +++ b/BTCPayServer.Data/Data/PendingInvoiceData.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -12,5 +9,13 @@ namespace BTCPayServer.Data get; set; } public InvoiceData InvoiceData { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(o => o.PendingInvoices) + .HasForeignKey(o => o.Id).OnDelete(DeleteBehavior.Cascade); + } } } diff --git a/BTCPayServer.Data/Data/PlannedTransaction.cs b/BTCPayServer.Data/Data/PlannedTransaction.cs new file mode 100644 index 000000000..54d8848c6 --- /dev/null +++ b/BTCPayServer.Data/Data/PlannedTransaction.cs @@ -0,0 +1,15 @@ +using System; +using System.ComponentModel.DataAnnotations; + +namespace BTCPayServer.Data +{ + public class PlannedTransaction + { + [Key] + [MaxLength(100)] + // Id in the format [cryptocode]-[txid] + public string Id { get; set; } + public DateTimeOffset BroadcastAt { get; set; } + public byte[] Blob { get; set; } + } +} diff --git a/BTCPayServer.Data/Data/PullPaymentData.cs b/BTCPayServer.Data/Data/PullPaymentData.cs new file mode 100644 index 000000000..36c416227 --- /dev/null +++ b/BTCPayServer.Data/Data/PullPaymentData.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; +using Microsoft.EntityFrameworkCore; +using NBitcoin; + +namespace BTCPayServer.Data +{ + public static class PayoutExtensions + { + public static IQueryable GetPayoutInPeriod(this IQueryable payouts, PullPaymentData pp) + { + return GetPayoutInPeriod(payouts, pp, DateTimeOffset.UtcNow); + } + public static IQueryable GetPayoutInPeriod(this IQueryable payouts, PullPaymentData pp, DateTimeOffset now) + { + var request = payouts.Where(p => p.PullPaymentDataId == pp.Id); + var period = pp.GetPeriod(now); + if (period is { } p) + { + var start = p.Start; + if (p.End is DateTimeOffset end) + { + return payouts.Where(p => p.Date >= start && p.Date < end); + } + else + { + return payouts.Where(p => p.Date >= start); + } + } + else + { + return payouts.Where(p => false); + } + } + } + public class PullPaymentData + { + [Key] + [MaxLength(30)] + public string Id { get; set; } + [ForeignKey("StoreId")] + public StoreData StoreData { get; set; } + [MaxLength(50)] + public string StoreId { get; set; } + public long? Period { get; set; } + public DateTimeOffset StartDate { get; set; } + public DateTimeOffset? EndDate { get; set; } + public bool Archived { get; set; } + public List Payouts { get; set; } + public byte[] Blob { get; set; } + + public (DateTimeOffset Start, DateTimeOffset? End)? GetPeriod(DateTimeOffset now) + { + if (now < StartDate) + return null; + if (EndDate is DateTimeOffset end && now >= end) + return null; + DateTimeOffset startPeriod = StartDate; + DateTimeOffset? endPeriod = null; + if (Period is long periodSeconds) + { + var period = TimeSpan.FromSeconds(periodSeconds); + var timeToNow = now - StartDate; + var periodCount = (long)timeToNow.TotalSeconds / (long)period.TotalSeconds; + startPeriod = StartDate + (period * periodCount); + endPeriod = startPeriod + period; + } + if (EndDate is DateTimeOffset end2 && + ((endPeriod is null) || + (endPeriod is DateTimeOffset endP && endP > end2))) + endPeriod = end2; + return (startPeriod, endPeriod); + } + + public bool HasStarted() + { + return HasStarted(DateTimeOffset.UtcNow); + } + public bool HasStarted(DateTimeOffset now) + { + return StartDate <= now; + } + + public bool IsExpired() + { + return IsExpired(DateTimeOffset.UtcNow); + } + public bool IsExpired(DateTimeOffset now) + { + return EndDate is DateTimeOffset dt && now > dt; + } + + public bool IsRunning() + { + return IsRunning(DateTimeOffset.UtcNow); + } + + public bool IsRunning(DateTimeOffset now) + { + return !Archived && !IsExpired(now) && HasStarted(now); + } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasIndex(o => o.StoreId); + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(o => o.PullPayments).OnDelete(DeleteBehavior.Cascade); + } + } +} diff --git a/BTCPayServer.Data/Data/RefundData.cs b/BTCPayServer.Data/Data/RefundData.cs index 4d4473909..da3754807 100644 --- a/BTCPayServer.Data/Data/RefundData.cs +++ b/BTCPayServer.Data/Data/RefundData.cs @@ -1,27 +1,26 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using System.ComponentModel.DataAnnotations; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { - public class RefundAddressesData + public class RefundData { - public string Id + [Required] + public string InvoiceDataId { get; set; } + [Required] + public string PullPaymentDataId { get; set; } + public PullPaymentData PullPaymentData { get; set; } + public InvoiceData InvoiceData { get; set; } + + internal static void OnModelCreating(ModelBuilder builder) { - get; set; - } - public string InvoiceDataId - { - get; set; - } - public InvoiceData InvoiceData - { - get; set; - } - public byte[] Blob - { - get; set; + builder.Entity() + .HasKey(nameof(InvoiceDataId), nameof(PullPaymentDataId)); + builder.Entity() + .HasOne(o => o.InvoiceData) + .WithMany(o => o.Refunds) + .HasForeignKey(o => o.InvoiceDataId) + .OnDelete(DeleteBehavior.Cascade); } } } diff --git a/BTCPayServer.Data/Data/SettingData.cs b/BTCPayServer.Data/Data/SettingData.cs index 34e2693ef..a7421bdc1 100644 --- a/BTCPayServer.Data/Data/SettingData.cs +++ b/BTCPayServer.Data/Data/SettingData.cs @@ -1,8 +1,3 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - namespace BTCPayServer.Data { public class SettingData diff --git a/BTCPayServer.Data/Data/StoreData.cs b/BTCPayServer.Data/Data/StoreData.cs index a00a0f640..11c7269ba 100644 --- a/BTCPayServer.Data/Data/StoreData.cs +++ b/BTCPayServer.Data/Data/StoreData.cs @@ -1,98 +1,48 @@ -using System; +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; -using System.Text; -using Newtonsoft.Json.Linq; -using System.Security.Claims; +using BTCPayServer.Client.Models; namespace BTCPayServer.Data { - public enum SpeedPolicy - { - HighSpeed = 0, - MediumSpeed = 1, - LowSpeed = 2, - LowMediumSpeed = 3 - } + public class StoreData { - public string Id - { - get; - set; - } + public string Id { get; set; } + public List UserStores { get; set; } - public List UserStores - { - get; set; - } - public List Apps - { - get; set; - } - - public List PaymentRequests - { - get; set; - } + public List Apps { get; set; } + + public List PaymentRequests { get; set; } + + public List PullPayments { get; set; } public List Invoices { get; set; } [Obsolete("Use GetDerivationStrategies instead")] - public string DerivationStrategy - { - get; set; - } + public string DerivationStrategy { get; set; } [Obsolete("Use GetDerivationStrategies instead")] - public string DerivationStrategies - { - get; - set; - } + public string DerivationStrategies { get; set; } - public string StoreName - { - get; set; - } + public string StoreName { get; set; } - public SpeedPolicy SpeedPolicy - { - get; set; - } + public SpeedPolicy SpeedPolicy { get; set; } = SpeedPolicy.MediumSpeed; - public string StoreWebsite - { - get; set; - } + public string StoreWebsite { get; set; } - public byte[] StoreCertificate - { - get; set; - } + public byte[] StoreCertificate { get; set; } - [NotMapped] - public string Role - { - get; set; - } + [NotMapped] public string Role { get; set; } + + public byte[] StoreBlob { get; set; } - public byte[] StoreBlob - { - get; - set; - } [Obsolete("Use GetDefaultPaymentId instead")] public string DefaultCrypto { get; set; } + public List PairedSINs { get; set; } public IEnumerable APIKeys { get; set; } } - public enum NetworkFeeMode - { - MultiplePaymentsOnly, - Always, - Never - } + } diff --git a/BTCPayServer.Data/Data/StoredFile.cs b/BTCPayServer.Data/Data/StoredFile.cs index 27d3918d9..1281afa15 100644 --- a/BTCPayServer.Data/Data/StoredFile.cs +++ b/BTCPayServer.Data/Data/StoredFile.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.ComponentModel.DataAnnotations.Schema; namespace BTCPayServer.Data diff --git a/BTCPayServer.Data/Data/U2FDevice.cs b/BTCPayServer.Data/Data/U2FDevice.cs index 87b09679b..abf752310 100644 --- a/BTCPayServer.Data/Data/U2FDevice.cs +++ b/BTCPayServer.Data/Data/U2FDevice.cs @@ -1,4 +1,3 @@ -using System; using System.ComponentModel.DataAnnotations; namespace BTCPayServer.Data diff --git a/BTCPayServer.Data/Data/UserStore.cs b/BTCPayServer.Data/Data/UserStore.cs index 89e6fab7d..fc488cef7 100644 --- a/BTCPayServer.Data/Data/UserStore.cs +++ b/BTCPayServer.Data/Data/UserStore.cs @@ -1,7 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; namespace BTCPayServer.Data { @@ -29,5 +26,27 @@ namespace BTCPayServer.Data get; set; } + + internal static void OnModelCreating(ModelBuilder builder) + { + builder.Entity() + .HasOne(o => o.StoreData) + .WithMany(i => i.UserStores).OnDelete(DeleteBehavior.Cascade); + builder.Entity() + .HasKey(t => new + { + t.ApplicationUserId, + t.StoreDataId + }); + builder.Entity() + .HasOne(pt => pt.ApplicationUser) + .WithMany(p => p.UserStores) + .HasForeignKey(pt => pt.ApplicationUserId); + + builder.Entity() + .HasOne(pt => pt.StoreData) + .WithMany(t => t.UserStores) + .HasForeignKey(pt => pt.StoreDataId); + } } } diff --git a/BTCPayServer.Data/Data/WalletData.cs b/BTCPayServer.Data/Data/WalletData.cs index b259c4f0f..6e011ff4b 100644 --- a/BTCPayServer.Data/Data/WalletData.cs +++ b/BTCPayServer.Data/Data/WalletData.cs @@ -1,8 +1,4 @@ -using System; using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Newtonsoft.Json; namespace BTCPayServer.Data { @@ -16,69 +12,9 @@ namespace BTCPayServer.Data public byte[] Blob { get; set; } } - public class Label - { - public Label(string value, string color) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (color == null) - throw new ArgumentNullException(nameof(color)); - Value = value; - Color = color; - } - - public string Value { get; } - public string Color { get; } - - public override bool Equals(object obj) - { - Label item = obj as Label; - if (item == null) - return false; - return Value.Equals(item.Value, StringComparison.OrdinalIgnoreCase); - } - public static bool operator ==(Label a, Label b) - { - if (System.Object.ReferenceEquals(a, b)) - return true; - if (((object)a == null) || ((object)b == null)) - return false; - return a.Value == b.Value; - } - - public static bool operator !=(Label a, Label b) - { - return !(a == b); - } - - public override int GetHashCode() - { - return Value.GetHashCode(StringComparison.OrdinalIgnoreCase); - } - } public class WalletBlobInfo { public Dictionary LabelColors { get; set; } = new Dictionary(); - - public IEnumerable