Compare commits

...

80 Commits

Author SHA1 Message Date
ebc053aca5 Update Changelog () 2023-12-21 23:46:29 +09:00
96da7f0322 UI: Form validation summary matches alert style ()
Fixes .
2023-12-21 23:43:12 +09:00
8ae9e59d9d Lightning Address: Use lowercased username when resolving ()
* Lightning Address: Use lowercased username when resolving

* Use static NormalizeUsername
2023-12-21 23:42:17 +09:00
c94dc87cb8 Fix: Setup a boltcard for the second time wouldn't generate new keys 2023-12-21 18:16:25 +09:00
20512a59b3 Fix API doc for boltcard related feature 2023-12-21 18:02:13 +09:00
b3f9216c54 Use PullPaymentId to derive the cardkey of Boltcard () 2023-12-21 10:29:28 +09:00
1cda0360e9 Fix test 2023-12-20 22:00:08 +09:00
7f75117bfa Fix flaky 2023-12-20 20:59:27 +09:00
5a70345499 Do not redirect to archived store after login ()
Now that we have archived stores, we need to exclude them from the selection of the default store the user gets redirected to after login.
2023-12-20 19:27:02 +09:00
5114a3a2ea Lightning: Fix connection display name in LN settings ()
* Lightning: Fix connection display name in LN settings

Builds on .

* Upgrade Lightning lib
2023-12-20 19:26:24 +09:00
93ab219124 Lightning: Allow LND to be used with non-admin macaroons ()
* Lightning: Allow LND to be used with non-admin macaroons

Requires .

* Upgrade Lightning lib
2023-12-20 19:23:46 +09:00
61bf6d33b2 Handle disabled plugin in ui ()
When a plugin is disabled, we should at least show the uninstall option in the plugin option. Eventually we should also detect what version was disabled and offer an update instead
2023-12-20 18:56:21 +09:00
3fc687a2d4 Fix: Payments to Top-Up could be undetected due to race condition () 2023-12-20 18:41:28 +09:00
8da04fd7e2 Better error message in Vault if hardware device isn't supported 2023-12-20 17:17:19 +09:00
cb54f8f6d1 Avoid updating Apps if no inventory has been modified 2023-12-19 21:48:11 +09:00
6ecfe073e7 disable cj plugin on next btcpay release 2023-12-19 12:58:52 +01:00
ea2648f08f Fix: Update of inventory could override app settings being updated () 2023-12-19 20:53:11 +09:00
40adf7acd2 Add flaky test debug statements 2023-12-19 13:55:33 +09:00
850af216bd Add debug statments in flaky tests 2023-12-19 13:00:48 +09:00
bf6200d55c Changelog v1.12 () 2023-12-19 12:39:23 +09:00
93bb85ffaa Fix tests 2023-12-19 12:35:35 +09:00
2fa7745886 Select 1 hour as default fee rate 2023-12-19 12:23:20 +09:00
2714907aef Improve exception message if Bitpay rates are unavailable 2023-12-19 11:44:10 +09:00
0d61e45cc6 Increase absurdfee from mempool space 2023-12-17 11:54:56 +09:00
541cef55b8 Random feerate and ensure sanity ()
Suggested at https://github.com/btcpayserver/btcpayserver/pull/5490#issuecomment-1851066223
We can also configure this httpclient to use tor
2023-12-14 21:20:45 +09:00
e3863ac076 Allow users with CanViewPaymentRequests to view payment requests () 2023-12-14 12:42:07 +01:00
0e2379caa6 Plugins: Add disclaimer () 2023-12-14 12:41:37 +01:00
a17c486f81 POS: Remove forced center alignment for description ()
Allows to specify the text alignment in the description container via the richt text editor. Before it was center aligned, no matter what one did in the editor.

This is feedback we got in yesterdays call with Start9.
2023-12-14 12:09:45 +01:00
e4aaff5e34 Greenfield: Fix invoice refund permission () 2023-12-14 11:15:36 +01:00
97fda9d362 not lndhub specific 2023-12-13 13:40:18 +01:00
7a06423bc7 Allow scheduling installs/updates of future plugins ()
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-12-13 12:36:23 +01:00
26374ef476 Policies: Add warnings for certain options () 2023-12-13 10:53:37 +01:00
6324a1a1e8 Remove bittrex ()
* Remove bittrex

* Test fix

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-12-12 17:38:28 +01:00
b751e23e93 dont crash if the plugin builder provides more instances of the same plugin but different v 2023-12-12 13:23:33 +01:00
72ee65843d bump bitcoin core 2023-12-12 13:08:40 +09:00
d413dd9257 UI: Improve invoice's webhooks table () 2023-12-11 14:45:45 +09:00
433adf4668 Fix Email rules validation and command index 2023-12-08 11:33:29 +01:00
d78267d7ee Bump NTag lib 2023-12-08 16:22:49 +09:00
0c16492d1c Payment details: Re-add unit for displayed amount ()
* Payment details: Re-add unit for displayed amount

Fixes .

* Ensure we are not using the symbol for BTC
2023-12-07 20:40:13 +09:00
eda437995f Show Warning is browser safari/brave is incompatible with vault on all pages 2023-12-07 14:00:30 +09:00
379286c366 Webhooks: Remove OverPaid property from invoice payment events
In addition to  and . This aligns it with [what we have in the docs](https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Webhooks).
2023-12-06 14:48:34 +01:00
3f344f2c0c Webhooks: Re-add OverPaid property to WebhookInvoiceSettledEvent ()
Fixes .
2023-12-06 09:21:04 +09:00
d050c8e3b2 Boltcard integration ()
* Boltcard integration

* Add API for boltcard registration
2023-12-06 09:17:58 +09:00
b13f140b86 bump 2023-12-05 20:23:58 +09:00
7066a2a577 Keypad: Show recent transactions only when logged in ()
Fixes . For the use case of giving access to cashiers we need to find another solution than showing the recent transactions for signed out users.
2023-12-04 22:14:37 +09:00
a8ebaa6784 bump lightning libs and dapper 2023-12-04 18:52:40 +09:00
60ff7e86b8 Fix typos () 2023-12-02 10:13:28 +01:00
44b7ed0e6e Store Branding: Refactoring and logo as favicon ()
* Store Branding: Refactoring and logo as favicon

- Encapsulates store branding properties into their own view model
- Uses the logo as favicon on public pages

* Refactorings

* Updates
2023-12-01 16:13:44 +01:00
afed3a0899 Dev env: Fix Lightning config warning () 2023-12-01 10:55:05 +01:00
28265b30d2 Support BIP129 Multisig wallet import () 2023-12-01 10:54:13 +01:00
a97172cea6 Pluginize Webhooks and support Payouts ()
Co-authored-by: d11n <mail@dennisreimann.de>
2023-12-01 10:50:05 +01:00
605741182d enhance fine grain permissions ()
Co-authored-by: d11n <mail@dennisreimann.de>
2023-12-01 09:12:02 +01:00
2c94a87be4 Support adjusting form invoice amount by multiplier ()
Co-authored-by: d11n <mail@dennisreimann.de>
2023-12-01 09:10:58 +01:00
6f98d5aa20 Fix build and warnings 2023-11-30 16:48:24 +01:00
3d08e70101 Update SSH.NET to 2023.0.0 ()
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-11-30 18:25:09 +09:00
bdf56c0a6f Keypad: List recent transactions ()
* Keypad: List recent transactions

Closes .

* UI updates

* Optional: No border

* Fix class

* Decrease keypad max-width
2023-11-30 18:19:03 +09:00
b9b3860e6b NFC improvements ()
* Move NFC code on Vue app level

* Update NFC result handling and display

* Save a bit of space

* Scroll NFC error into view
2023-11-30 18:17:23 +09:00
b0554bbf17 Send notification when a new plugin version is available () 2023-11-30 18:12:44 +09:00
b31f1812d2 Greenfield: Remove unused checkout type setting from POS ()
Cam across this while browsing the API docs: The checkout type setting isn't used for the POS, so we should simply remove it as this is configured on the store-level.
2023-11-30 18:05:35 +09:00
04292d09e1 Pluginify BTCPayNetworkProvider () 2023-11-29 18:51:40 +09:00
1081eab9db Fix warnings ()
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-11-28 15:20:03 +01:00
4023b24209 Plugins: Improve crash detection on startup and hint at disabled plugins () 2023-11-28 15:19:47 +01:00
bac9ab08d1 Make wallet object system much more performant ()
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-11-28 11:38:09 +01:00
75bf8a5086 Use Mempoolspace fees ()
* Use Mempoolspace fees

Since bitcoind's fee estiomates are horrible, I would use an altenrative, but that adds a third party to the mix. We can either:
* Accept the risk (it is only for fee estimation anyway)
* Offer a toggle in the server settings
* Move this code to a plugin

* refactor

* Refactor

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-11-28 18:26:35 +09:00
3ffae30b95 bump bitcoin core in tests 2023-11-28 10:34:00 +09:00
2fda9cf539 Fix qemu package in ARM Docker files ()
With the new debian bookworm, the `qemu` package has been split into one package per architecture.
2023-11-28 09:33:19 +09:00
c8b9a425b8 Receipt fixes and improvements ()
* Fix additional div

* Don't show payment number if there is only one

* Bump max-width to prevent wrapping in top container

* Fix colspan

* Re-add POS data

Closes .

* Right-align amounts

* Re-order

* Don't show redundant receive date if there is only one payment

* Table improvements

* Unify crypto amount display

* More formatting improvements

* Only show Subtotal if there are calculations applicable to it

* Making margin on the bottom smaller to reduce expansion on Bitcoinize machines

---------

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>
2023-11-23 12:05:08 -06:00
62865d7d88 Fix tabindex order in login view 2023-11-22 18:33:48 +09:00
3afd24fcd7 Print button 2023-11-21 06:49:17 -08:00
fd582aad75 Cleanup receipt print template 2023-11-21 06:49:17 -08:00
f9155772f5 Optimizing receipt printing, now works on POS terminal 2023-11-21 06:49:17 -08:00
2e4313bf18 Greenfield: Make checkout type V2 default for new stores () 2023-11-21 13:38:01 +01:00
5ad320ee4b Domain mapping: Redirect root app to canonical URL ()
* Domain mapping: Redirect root app to canonical URL

We already redirect public app URLs to the canonical URL if there's a domain mapping — this adds the same behaviour for apps that are defined as root app as well.

* Refactor

* Refactor once more

Minor cleanups

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-11-21 20:00:31 +09:00
d46543ae16 Public LN Node view: Consistency update () 2023-11-21 11:53:24 +01:00
2f23bad3bc Support the new LN lib ()
* Support the new LN lib

* fix test

* do not cache factories

* try without useless userinfo in lnd

* Remove monero wallet files

* support simpler DI too

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-11-21 18:55:02 +09:00
6d288271cd Unify public page styles ()
Based on  and needs it to get merged first.

- Uses `--wrap-max-width` on `.public-page-wrap` rather than inner `.container` classes
- Applies `.tile` class to boxes and makes them connect to the edge of the screen below `400px` width.
2023-11-21 10:13:26 +01:00
b4daa76aeb Theme Switch: Refactor and add system option ()
* Theme Switch: Refactor and add system option

Before, we had no way to reset the theme option to the system default. This introduces that option and refactors the theme switch to work in a simpler manner.

* Prevent account menu close on click inside

Context: 
2023-11-21 09:56:10 +01:00
5bd8067328 bump some deps 2023-11-21 14:19:11 +09:00
9ccc42f556 Bump .NET 8.0 () 2023-11-21 14:11:17 +09:00
3ee4f43eb5 Remove CurrentRefund property in InvoiceData () 2023-11-21 12:52:40 +09:00
434 changed files with 6937 additions and 3762 deletions
.gitignore
BTCPayServer.Abstractions
BTCPayServer.Client
BTCPayServer.Common
BTCPayServer.Data
BTCPayServer.PluginPacker
BTCPayServer.Rating
BTCPayServer.Tests
BTCPayServer
APDUVaultTransport.csBTCPayServer.csproj
Components
Configuration
Controllers
Data
DerivationSchemeSettings.csExtensions.cs
Extensions
Filters
Forms
HostedServices
Hosting
Models
PaymentRequest
Payments
PayoutProcessors
Plugins
Altcoins
Bitcoin
Crowdfund
LightningAddressResolver.csLightningAddresssResolver.csPluginManager.csPluginService.csPluginServiceCollection.cs
PointOfSale
Program.cs
Properties
Security
Services
Views
Shared
UIAccount
UIApps
UICustodianAccounts
UIForms
UIHome
UIInvoice
UILightningAutomatedPayoutProcessors
UIManage
UIOnChainAutomatedPayoutProcessors
UIPaymentRequest
UIPublicLightningNodeInfo
UIPullPayment
UIServer
UIShopify
UIStorePullPayments
UIStores
UIUserStores
UIWallets
wwwroot
Build
Changelog.mdamd64.Dockerfilearm32v7.Dockerfilearm64v8.Dockerfile

3
.gitignore vendored

@ -298,4 +298,5 @@ Packed Plugins
Plugins/packed
BTCPayServer/wwwroot/swagger/v1/openapi.json
BTCPayServer/appsettings.dev.json
BTCPayServer/appsettings.dev.json
BTCPayServer.Tests/monero_wallet

@ -32,10 +32,10 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="8.0.723" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />

@ -84,6 +84,7 @@ namespace BTCPayServer.Abstractions.Contracts
.UseNpgsql(_options.Value.ConnectionString, o =>
{
o.EnableRetryOnFailure(10);
o.SetPostgresVersion(12, 0);
if (!string.IsNullOrEmpty(_schemaPrefix))
{
o.MigrationsHistoryTable(_schemaPrefix);

@ -5,7 +5,6 @@ using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
using Npgsql.Internal.TypeHandlers.GeometricHandlers;
namespace BTCPayServer.Abstractions.Form;

@ -2,12 +2,13 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
namespace BTCPayServer.Abstractions.TagHelpers;
[HtmlTargetElement(Attributes = "[permission]")]
[HtmlTargetElement(Attributes = "[not-permission]" )]
[HtmlTargetElement(Attributes = "[not-permission]")]
public class PermissionTagHelper : TagHelper
{
private readonly IAuthorizationService _authorizationService;
@ -22,29 +23,72 @@ public class PermissionTagHelper : TagHelper
public string Permission { get; set; }
public string NotPermission { get; set; }
public string PermissionResource { get; set; }
public bool AndMode { get; set; } = false;
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
if (string.IsNullOrEmpty(Permission) && string.IsNullOrEmpty(NotPermission))
var permissions = Permission?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
var notPermissions = NotPermission?.Split(',', StringSplitOptions.RemoveEmptyEntries) ?? Array.Empty<string>();
if (!permissions.Any() && !notPermissions.Any())
return;
if (_httpContextAccessor.HttpContext is null)
return;
var expectedResult = !string.IsNullOrEmpty(Permission);
var key = $"{Permission??NotPermission}_{PermissionResource}";
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var o) ||
o is not AuthorizationResult res)
bool shouldRender = true; // Assume tag should be rendered unless a check fails
// Process 'Permission' - User must have these permissions
if (permissions.Any())
{
res = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User,
PermissionResource,
Permission);
_httpContextAccessor.HttpContext.Items.Add(key, res);
bool finalResult = AndMode;
foreach (var perm in permissions)
{
var key = $"{perm}_{PermissionResource}";
AuthorizationResult res = await GetOrAddAuthorizationResult(key, perm);
if (AndMode)
finalResult &= res.Succeeded;
else
finalResult |= res.Succeeded;
if (!AndMode && finalResult) break;
}
shouldRender = finalResult;
}
if (expectedResult != res.Succeeded)
// Process 'NotPermission' - User must not have these permissions
if (shouldRender && notPermissions.Any())
{
foreach (var notPerm in notPermissions)
{
var key = $"{notPerm}_{PermissionResource}";
AuthorizationResult res = await GetOrAddAuthorizationResult(key, notPerm);
if (res.Succeeded) // If the user has a 'NotPermission', they should not see the tag
{
shouldRender = false;
break;
}
}
}
if (!shouldRender)
{
output.SuppressOutput();
}
}
private async Task<AuthorizationResult> GetOrAddAuthorizationResult(string key, string permission)
{
if (!_httpContextAccessor.HttpContext.Items.TryGetValue(key, out var cachedResult))
{
var res = await _authorizationService.AuthorizeAsync(_httpContextAccessor.HttpContext.User,
PermissionResource, permission);
_httpContextAccessor.HttpContext.Items[key] = res;
return res;
}
return cachedResult as AuthorizationResult;
}
}

@ -30,8 +30,8 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.21" />
<PackageReference Include="NBitcoin" Version="7.0.24" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.1" />
<PackageReference Include="NBitcoin" Version="7.0.32" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>

@ -20,6 +20,12 @@ namespace BTCPayServer.Client
return await HandleResponse<PullPaymentData>(response);
}
public virtual async Task<RegisterBoltcardResponse> RegisterBoltcard(string pullPaymentId, RegisterBoltcardRequest request, CancellationToken cancellationToken = default)
{
var response = await _httpClient.SendAsync(CreateHttpRequest($"api/v1/pull-payments/{HttpUtility.UrlEncode(pullPaymentId)}/boltcards", bodyPayload: request, method: HttpMethod.Post), cancellationToken);
return await HandleResponse<RegisterBoltcardResponse>(response);
}
public virtual async Task<PullPaymentData[]> GetPullPayments(string storeId, bool includeArchived = false, CancellationToken cancellationToken = default)
{
Dictionary<string, object> query = new Dictionary<string, object>();

@ -42,7 +42,6 @@ namespace BTCPayServer.Client.Models
public bool? Archived { get; set; } = null;
public string FormId { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
public CheckoutType? CheckoutType { get; set; } = null;
}
public enum CrowdfundResetEvery

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Text;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
namespace BTCPayServer.Client.Models
{
public enum OnExistingBehavior
{
KeepVersion,
UpdateVersion
}
public class RegisterBoltcardRequest
{
[JsonConverter(typeof(HexJsonConverter))]
[JsonProperty("UID")]
public byte[] UID { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public OnExistingBehavior? OnExisting { get; set; }
}
public class RegisterBoltcardResponse
{
[JsonProperty("LNURLW")]
public string LNURLW { get; set; }
public int Version { get; set; }
[JsonProperty("K0")]
public string K0 { get; set; }
[JsonProperty("K1")]
public string K1 { get; set; }
[JsonProperty("K2")]
public string K2 { get; set; }
[JsonProperty("K3")]
public string K3 { get; set; }
[JsonProperty("K4")]
public string K4 { get; set; }
}
}

@ -37,8 +37,11 @@ namespace BTCPayServer.Client.Models
public bool AnyoneCanCreateInvoice { get; set; }
public string DefaultCurrency { get; set; }
public bool RequiresRefundEmail { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CheckoutType CheckoutType { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public CheckoutType? CheckoutType { get; set; }
public bool LightningAmountInSatoshi { get; set; }
public bool LightningPrivateRouteHints { get; set; }
public bool OnChainWithLnInvoiceFallback { get; set; }

@ -11,8 +11,7 @@ namespace BTCPayServer.Client.Models
{
public bool Everything { get; set; } = true;
[JsonProperty(ItemConverterType = typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
public WebhookEventType[] SpecificEvents { get; set; } = Array.Empty<WebhookEventType>();
public string[] SpecificEvents { get; set; } = Array.Empty<string>();
}
public bool Enabled { get; set; } = true;

@ -9,7 +9,7 @@ namespace BTCPayServer.Client.Models
{
public class WebhookEvent
{
public readonly static JsonSerializerSettings DefaultSerializerSettings;
public static readonly JsonSerializerSettings DefaultSerializerSettings;
static WebhookEvent()
{
DefaultSerializerSettings = new JsonSerializerSettings();
@ -45,8 +45,7 @@ namespace BTCPayServer.Client.Models
}
}
public bool IsRedelivery { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public WebhookEventType Type { get; set; }
public string Type { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset Timestamp { get; set; }
[JsonExtensionData]

@ -1,13 +1,20 @@
namespace BTCPayServer.Client.Models
namespace BTCPayServer.Client.Models;
public static class WebhookEventType
{
public enum WebhookEventType
{
InvoiceCreated,
InvoiceReceivedPayment,
InvoiceProcessing,
InvoiceExpired,
InvoiceSettled,
InvoiceInvalid,
InvoicePaymentSettled,
}
public const string InvoiceCreated = nameof(InvoiceCreated);
public const string InvoiceReceivedPayment = nameof(InvoiceReceivedPayment);
public const string InvoiceProcessing = nameof(InvoiceProcessing);
public const string InvoiceExpired = nameof(InvoiceExpired);
public const string InvoiceSettled = nameof(InvoiceSettled);
public const string InvoiceInvalid = nameof(InvoiceInvalid);
public const string InvoicePaymentSettled = nameof(InvoicePaymentSettled);
public const string PayoutCreated = nameof(PayoutCreated);
public const string PayoutApproved = nameof(PayoutApproved);
public const string PayoutUpdated = nameof(PayoutUpdated);
public const string PaymentRequestUpdated = nameof(PaymentRequestUpdated);
public const string PaymentRequestCreated = nameof(PaymentRequestCreated);
public const string PaymentRequestArchived = nameof(PaymentRequestArchived);
public const string PaymentRequestStatusChanged = nameof(PaymentRequestStatusChanged);
}

@ -1,44 +1,74 @@
using System;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models
{
public class WebhookInvoiceEvent : WebhookEvent
public class WebhookPayoutEvent : StoreWebhookEvent
{
public WebhookPayoutEvent(string evtType, string storeId)
{
if (!evtType.StartsWith("payout", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(evtType));
Type = evtType;
StoreId = storeId;
}
[JsonProperty(Order = 2)] public string PayoutId { get; set; }
[JsonProperty(Order = 3)] public string PullPaymentId { get; set; }
[JsonProperty(Order = 4)] [JsonConverter(typeof(StringEnumConverter))]public PayoutState PayoutState { get; set; }
}
public class WebhookPaymentRequestEvent : StoreWebhookEvent
{
public WebhookPaymentRequestEvent(string evtType, string storeId)
{
if (!evtType.StartsWith("paymentrequest", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(evtType));
Type = evtType;
StoreId = storeId;
}
[JsonProperty(Order = 2)] public string PaymentRequestId { get; set; }
[JsonProperty(Order = 3)] [JsonConverter(typeof(StringEnumConverter))]public PaymentRequestData.PaymentRequestStatus Status { get; set; }
}
public abstract class StoreWebhookEvent : WebhookEvent
{
[JsonProperty(Order = 1)] public string StoreId { get; set; }
}
public class WebhookInvoiceEvent : StoreWebhookEvent
{
public WebhookInvoiceEvent()
{
}
public WebhookInvoiceEvent(WebhookEventType evtType)
{
this.Type = evtType;
public WebhookInvoiceEvent(string evtType, string storeId)
{
if (!evtType.StartsWith("invoice", StringComparison.InvariantCultureIgnoreCase))
throw new ArgumentException("Invalid event type", nameof(evtType));
Type = evtType;
StoreId = storeId;
}
[JsonProperty(Order = 1)] public string StoreId { get; set; }
[JsonProperty(Order = 2)] public string InvoiceId { get; set; }
[JsonProperty(Order = 3)] public JObject Metadata { get; set; }
}
public class WebhookInvoiceSettledEvent : WebhookInvoiceEvent
{
public WebhookInvoiceSettledEvent()
{
}
public WebhookInvoiceSettledEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoiceSettledEvent(string storeId) : base(WebhookEventType.InvoiceSettled, storeId)
{
}
public bool ManuallyMarked { get; set; }
public bool OverPaid { get; set; }
}
public class WebhookInvoiceInvalidEvent : WebhookInvoiceEvent
{
public WebhookInvoiceInvalidEvent()
{
}
public WebhookInvoiceInvalidEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoiceInvalidEvent(string storeId) : base(WebhookEventType.InvoiceInvalid, storeId)
{
}
@ -47,11 +77,7 @@ namespace BTCPayServer.Client.Models
public class WebhookInvoiceProcessingEvent : WebhookInvoiceEvent
{
public WebhookInvoiceProcessingEvent()
{
}
public WebhookInvoiceProcessingEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoiceProcessingEvent(string storeId) : base(WebhookEventType.InvoiceProcessing, storeId)
{
}
@ -60,38 +86,25 @@ namespace BTCPayServer.Client.Models
public class WebhookInvoiceReceivedPaymentEvent : WebhookInvoiceEvent
{
public WebhookInvoiceReceivedPaymentEvent()
{
}
public WebhookInvoiceReceivedPaymentEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoiceReceivedPaymentEvent(string type, string storeId) : base(type, storeId)
{
}
public bool AfterExpiration { get; set; }
public string PaymentMethod { get; set; }
public InvoicePaymentMethodDataModel.Payment Payment { get; set; }
public bool OverPaid { get; set; }
}
public class WebhookInvoicePaymentSettledEvent : WebhookInvoiceReceivedPaymentEvent
{
public WebhookInvoicePaymentSettledEvent()
{
}
public WebhookInvoicePaymentSettledEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoicePaymentSettledEvent(string storeId) : base(WebhookEventType.InvoicePaymentSettled, storeId)
{
}
}
public class WebhookInvoiceExpiredEvent : WebhookInvoiceEvent
{
public WebhookInvoiceExpiredEvent()
{
}
public WebhookInvoiceExpiredEvent(WebhookEventType evtType) : base(evtType)
public WebhookInvoiceExpiredEvent(string storeId) : base(WebhookEventType.InvoiceExpired, storeId)
{
}

@ -19,6 +19,7 @@ namespace BTCPayServer.Client
public const string CanModifyStoreWebhooks = "btcpay.store.webhooks.canmodifywebhooks";
public const string CanModifyStoreSettingsUnscoped = "btcpay.store.canmodifystoresettings:";
public const string CanViewStoreSettings = "btcpay.store.canviewstoresettings";
public const string CanViewReports = "btcpay.store.canviewreports";
public const string CanViewInvoices = "btcpay.store.canviewinvoices";
public const string CanCreateInvoice = "btcpay.store.cancreateinvoice";
public const string CanModifyInvoices = "btcpay.store.canmodifyinvoices";
@ -34,7 +35,10 @@ namespace BTCPayServer.Client
public const string CanDeleteUser = "btcpay.user.candeleteuser";
public const string CanManagePullPayments = "btcpay.store.canmanagepullpayments";
public const string CanArchivePullPayments = "btcpay.store.canarchivepullpayments";
public const string CanManagePayouts = "btcpay.store.canmanagepayouts";
public const string CanViewPayouts = "btcpay.store.canviewpayouts";
public const string CanCreatePullPayments = "btcpay.store.cancreatepullpayments";
public const string CanViewPullPayments = "btcpay.store.canviewpullpayments";
public const string CanCreateNonApprovedPullPayments = "btcpay.store.cancreatenonapprovedpullpayments";
public const string CanViewCustodianAccounts = "btcpay.store.canviewcustodianaccounts";
public const string CanManageCustodianAccounts = "btcpay.store.canmanagecustodianaccounts";
@ -53,6 +57,7 @@ namespace BTCPayServer.Client
yield return CanModifyServerSettings;
yield return CanModifyStoreSettings;
yield return CanViewStoreSettings;
yield return CanViewReports;
yield return CanViewPaymentRequests;
yield return CanModifyPaymentRequests;
yield return CanModifyProfile;
@ -72,6 +77,7 @@ namespace BTCPayServer.Client
yield return CanManagePullPayments;
yield return CanArchivePullPayments;
yield return CanCreatePullPayments;
yield return CanViewPullPayments;
yield return CanCreateNonApprovedPullPayments;
yield return CanViewCustodianAccounts;
yield return CanManageCustodianAccounts;
@ -79,6 +85,8 @@ namespace BTCPayServer.Client
yield return CanWithdrawFromCustodianAccounts;
yield return CanTradeCustodianAccount;
yield return CanManageUsers;
yield return CanManagePayouts;
yield return CanViewPayouts;
}
}
public static bool IsValidPolicy(string policy)
@ -252,11 +260,13 @@ namespace BTCPayServer.Client
Policies.CanViewStoreSettings,
Policies.CanModifyStoreWebhooks,
Policies.CanModifyPaymentRequests,
Policies.CanManagePayouts,
Policies.CanUseLightningNodeInStore);
PolicyHasChild(policyMap,Policies.CanManageUsers, Policies.CanCreateUser);
PolicyHasChild(policyMap,Policies.CanManagePullPayments, Policies.CanCreatePullPayments, Policies.CanArchivePullPayments);
PolicyHasChild(policyMap,Policies.CanCreatePullPayments, Policies.CanCreateNonApprovedPullPayments);
PolicyHasChild(policyMap, Policies.CanCreateNonApprovedPullPayments, Policies.CanViewPullPayments);
PolicyHasChild(policyMap,Policies.CanModifyPaymentRequests, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap,Policies.CanModifyProfile, Policies.CanViewProfile);
PolicyHasChild(policyMap,Policies.CanUseLightningNodeInStore, Policies.CanViewLightningInvoiceInStore, Policies.CanCreateLightningInvoiceInStore);
@ -267,7 +277,8 @@ namespace BTCPayServer.Client
PolicyHasChild(policyMap, Policies.CanUseInternalLightningNode, Policies.CanCreateLightningInvoiceInternalNode, Policies.CanViewLightningInvoiceInternalNode);
PolicyHasChild(policyMap, Policies.CanManageCustodianAccounts, Policies.CanViewCustodianAccounts);
PolicyHasChild(policyMap, Policies.CanModifyInvoices, Policies.CanViewInvoices, Policies.CanCreateInvoice, Policies.CanCreateLightningInvoiceInStore);
PolicyHasChild(policyMap, Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests);
PolicyHasChild(policyMap, Policies.CanViewStoreSettings, Policies.CanViewInvoices, Policies.CanViewPaymentRequests, Policies.CanViewReports, Policies.CanViewPullPayments, Policies.CanViewPayouts);
PolicyHasChild(policyMap, Policies.CanManagePayouts, Policies.CanViewPayouts);
var missingPolicies = Policies.AllPolicies.ToHashSet();
//recurse through the tree to see which policies are not included in the tree

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitAlthash()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("HTML");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Htmlcoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.htmlcoin.com/api/tx/{0}" : "https://explorer.htmlcoin.com/api/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"HTML_X = HTML_USD",
"HTML_USD = hitbtc(HTML_USD)"
},
CryptoImagePath = "imlegacy/althash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("172'") : new KeyPath("1'")
});
}
}
}

@ -1,30 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitArgoneum()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("AGM");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Argoneum",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://chainz.cryptoid.info/agm/tx.dws?{0}"
: "https://chainz.cryptoid.info/agm-test/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"AGM_X = AGM_BTC * BTC_X",
"AGM_BTC = argoneum(AGM_BTC)"
},
CryptoImagePath = "imlegacy/argoneum.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("421'")
: new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBGold()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTG");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BGold",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"BTG_X = BTG_BTC * BTC_X",
"BTG_BTC = gate(BTG_BTC)",
},
CryptoImagePath = "imlegacy/btg.svg",
LightningImagePath = "imlegacy/btg-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBPlus()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("XBC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BPlus",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"XBC_X = XBC_BTC * BTC_X",
"XBC_BTC = cryptopia(XBC_BTC)"
},
CryptoImagePath = "imlegacy/xbc.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
});
}
}
}

@ -1,29 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBitcore()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTX");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BitCore",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.bitcore.cc/tx/{0}" : "https://explorer.bitcore.cc/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"BTX_X = BTX_BTC * BTC_X",
"BTX_BTC = graviex(BTX_BTC)"
},
CryptoImagePath = "imlegacy/bitcore.svg",
LightningImagePath = "imlegacy/bitcore-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
});
}
}
}

@ -1,32 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitDash()
{
//not needed: NBitcoin.Altcoins.Dash.Instance.EnsureRegistered();
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DASH");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dash",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://insight.dash.org/insight/tx/{0}"
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"DASH_X = DASH_BTC * BTC_X",
"DASH_BTC = bitfinex(DSH_BTC)"
},
CryptoImagePath = "imlegacy/dash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("5'")
: new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitDogecoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("DOGE");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dogecoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"DOGE_X = DOGE_BTC * BTC_X",
"DOGE_BTC = bittrex(DOGE_BTC)"
},
CryptoImagePath = "imlegacy/dogecoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitFeathercoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("FTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Feathercoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"FTC_X = FTC_BTC * BTC_X",
"FTC_BTC = bittrex(FTC_BTC)"
},
CryptoImagePath = "imlegacy/feathercoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
});
}
}
}

@ -1,33 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitGroestlcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("GRS");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Groestlcoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"GRS_X = GRS_BTC * BTC_X",
"GRS_BTC = bittrex(GRS_BTC)"
},
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
VaultSupported = true
});
}
}
}

@ -1,46 +0,0 @@
using System.Collections.Generic;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLitecoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Litecoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
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),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy },
{0x049d7cb2U, DerivationType.SegwitP2SH },
{0x04b24746U, DerivationType.Segwit },
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy },
{0x044a5262U, DerivationType.SegwitP2SH },
{0x045f1cf6U, DerivationType.Segwit }
}
});
}
}
}

@ -1,29 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonacoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("MONA");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Monacoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"MONA_X = MONA_BTC * BTC_X",
"MONA_BTC = bittrex(MONA_BTC)"
},
CryptoImagePath = "imlegacy/monacoin.png",
LightningImagePath = "imlegacy/mona-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitPolis()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("POLIS");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Polis",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"POLIS_X = POLIS_BTC * BTC_X",
"POLIS_BTC = polispay(POLIS_BTC)"
},
CryptoImagePath = "imlegacy/polis.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitUfo()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("UFO");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Ufo",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"UFO_X = UFO_BTC * BTC_X",
"UFO_BTC = coinexchange(UFO_BTC)"
},
CryptoImagePath = "imlegacy/ufo.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
});
}
}
}

@ -1,28 +0,0 @@
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitViacoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("VIA");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Viacoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"VIA_X = VIA_BTC * BTC_X",
"VIA_BTC = bittrex(VIA_BTC)"
},
CryptoImagePath = "imlegacy/viacoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
});
}
}
}

@ -1,37 +0,0 @@
#if ALTCOINS
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 == ChainName.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
CryptoCode = "LBTC",
NetworkCryptoCode = "LBTC",
DisplayName = "Liquid Bitcoin",
DefaultRateRules = new[]
{
"LBTC_X = LBTC_BTC * BTC_X",
"LBTC_BTC = 1",
},
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/liquid.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true
});
}
}
}
#endif

@ -1,83 +0,0 @@
#if ALTCOINS
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 = NetworkType == ChainName.Regtest? null: new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/liquid-tether.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.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 = NetworkType == ChainName.Regtest? null: new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
DisplayName = "Ethiopian Birr",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/etb.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.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)",
"CAD_BTC = LCAD_BTC"
},
AssetId = NetworkType == ChainName.Regtest? null: new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
DisplayName = "Liquid CAD",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://liquid.network/tx/{0}" : "https://liquid.network/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/lcad.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
}
}
}
#endif

@ -1,51 +0,0 @@
#if ALTCOINS
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Common;
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 not AssetMoney && NetworkCryptoCode.Equals(evtOutputs.CryptoCode, StringComparison.InvariantCultureIgnoreCase)) ||
(output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId)).Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public override List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
{
return transactionInformationSet.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));
}
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
{
//precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000
//precision 8: 10 = 10
var money = cryptoInfoDue / (decimal)Math.Pow(10, 8 - Divisibility);
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
builder.QueryParams.Add("assetid", AssetId.ToString());
return builder;
}
}
}
#endif

@ -1,18 +0,0 @@
#if ALTCOINS
using System.Collections.Generic;
using System.Linq;
namespace BTCPayServer
{
public static class LiquidExtensions
{
public static IEnumerable<string> GetAllElementsSubChains(this BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider unfiltered)
{
var elementsBased = networkProvider.GetAll().OfType<ElementsBTCPayNetwork>();
var parentChains = elementsBased.Select(network => network.NetworkCryptoCode.ToUpperInvariant()).Distinct();
return unfiltered.GetAll().OfType<ElementsBTCPayNetwork>()
.Where(network => parentChains.Contains(network.NetworkCryptoCode)).Select(network => network.CryptoCode.ToUpperInvariant());
}
}
}
#endif

@ -1,28 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonero()
{
Add(new MoneroLikeSpecificBtcPayNetwork()
{
CryptoCode = "XMR",
DisplayName = "Monero",
Divisibility = 12,
BlockExplorerLink =
NetworkType == ChainName.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",
UriScheme = "monero"
});
}
}
}

@ -1,8 +0,0 @@
namespace BTCPayServer
{
public class MoneroLikeSpecificBtcPayNetwork : BTCPayNetworkBase
{
public int MaxTrackedConfirmation = 10;
public string UriScheme { get; set; }
}
}

@ -1,29 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
// Change this if you want another zcash coin
public void InitZcash()
{
Add(new ZcashLikeSpecificBtcPayNetwork()
{
CryptoCode = "ZEC",
DisplayName = "Zcash",
Divisibility = 8,
BlockExplorerLink =
NetworkType == ChainName.Mainnet
? "https://www.exploreZcash.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
DefaultRateRules = new[]
{
"ZEC_X = ZEC_BTC * BTC_X",
"ZEC_BTC = kraken(ZEC_BTC)"
},
CryptoImagePath = "/imlegacy/zcash.png",
UriScheme = "zcash"
});
}
}
}

@ -1,8 +0,0 @@
namespace BTCPayServer
{
public class ZcashLikeSpecificBtcPayNetwork : BTCPayNetworkBase
{
public int MaxTrackedConfirmation = 10;
public string UriScheme { get; set; }
}
}

@ -4,6 +4,8 @@ using System.Globalization;
using System.IO;
using System.Linq;
using BTCPayServer.Common;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
@ -62,6 +64,31 @@ namespace BTCPayServer
public KeyPath CoinType { get; set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
public BTCPayNetwork SetDefaultElectrumMapping(ChainName chainName)
{
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = chainName == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x04b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy}, // tpub
{0x044a5262U, DerivationType.SegwitP2SH}, // upub
{0x045f1cf6U, DerivationType.Segwit} // vpub
};
if (!NBitcoinNetwork.Consensus.SupportSegwit)
{
ElectrumMapping =
ElectrumMapping
.Where(kv => kv.Value == DerivationType.Legacy)
.ToDictionary(k => k.Key, k => k.Value);
}
return this;
}
public virtual bool WalletSupported { get; set; } = true;
public virtual bool ReadonlyWallet { get; set; } = false;
@ -107,25 +134,8 @@ namespace BTCPayServer
public abstract class BTCPayNetworkBase
{
private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; set; }
public string BlockExplorerLink
{
get => _blockExplorerLink;
set
{
if (string.IsNullOrEmpty(BlockExplorerLinkDefault))
{
BlockExplorerLinkDefault = value;
}
_blockExplorerLink = value;
}
}
public string BlockExplorerLinkDefault { get; set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
public bool IsBTC
@ -153,5 +163,8 @@ namespace BTCPayServer
{
return NBitcoin.JsonConverters.Serializer.ToString(obj, null);
}
[Obsolete("Use TransactionLinkProviders service instead")]
public string BlockExplorerLink { get; set; }
}
}

@ -1,44 +0,0 @@
using System.Collections.Generic;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitBitcoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("BTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mempool.space/tx/{0}" :
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://mempool.space/signet/tx/{0}"
: "https://mempool.space/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
CryptoImagePath = "imlegacy/bitcoin.svg",
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
VaultSupported = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub
{0x049d7cb2U, DerivationType.SegwitP2SH }, // ypub
{0x04b24746U, DerivationType.Segwit }, //zpub
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy}, // tpub
{0x044a5262U, DerivationType.SegwitP2SH}, // upub
{0x045f1cf6U, DerivationType.Segwit} // vpub
}
});
}
}
}

@ -1,8 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
using StandardConfiguration;
namespace BTCPayServer
{
@ -19,92 +26,39 @@ namespace BTCPayServer
}
}
BTCPayNetworkProvider(BTCPayNetworkProvider unfiltered, string[] cryptoCodes)
{
NetworkType = unfiltered.NetworkType;
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(unfiltered.NetworkType);
_Networks = new Dictionary<string, BTCPayNetworkBase>();
cryptoCodes = cryptoCodes.Select(c => c.ToUpperInvariant()).ToArray();
foreach (var network in unfiltered._Networks)
{
if (cryptoCodes.Contains(network.Key))
{
_Networks.Add(network.Key, network.Value);
}
}
}
public ChainName NetworkType { get; private set; }
public BTCPayNetworkProvider(ChainName networkType)
public BTCPayNetworkProvider(
IEnumerable<BTCPayNetworkBase> networks,
SelectedChains selectedChains,
NBXplorerNetworkProvider nbxplorerNetworkProvider,
Logs logs,
IConfiguration configuration)
{
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
NetworkType = networkType;
InitBitcoin();
#if ALTCOINS
InitLiquid();
InitLiquidAssets();
InitLitecoin();
InitBitcore();
InitDogecoin();
InitBGold();
InitMonacoin();
InitDash();
InitFeathercoin();
InitAlthash();
InitGroestlcoin();
InitViacoin();
InitMonero();
InitZcash();
// InitArgoneum();//their rate source is down 9/15/20.
// InitMonetaryUnit(); Not supported from Bittrex from 11/23/2022, dead shitcoin
#if !ALTCOINS
var onlyBTC = networks.Count() == 1 && networks.First().IsBTC;
if (!onlyBTC)
throw new ConfigException($"This build of BTCPay Server does not support altcoins");
#endif
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
_NBXplorerNetworkProvider = nbxplorerNetworkProvider;
NetworkType = nbxplorerNetworkProvider.NetworkType;
foreach (var network in networks)
{
if (network.ElectrumMapping.Count == 0)
{
network.ElectrumMapping = GetNetwork<BTCPayNetwork>("BTC").ElectrumMapping;
if (!network.NBitcoinNetwork.Consensus.SupportSegwit)
{
network.ElectrumMapping =
network.ElectrumMapping
.Where(kv => kv.Value == DerivationType.Legacy)
.ToDictionary(k => k.Key, k => k.Value);
}
}
_Networks.Add(network.CryptoCode.ToUpperInvariant(), network);
}
// Disabled because of https://twitter.com/Cryptopia_NZ/status/1085084168852291586
//InitBPlus();
//InitUfo();
#endif
}
foreach (var chain in selectedChains.ExplicitlySelected)
{
if (GetNetwork<BTCPayNetworkBase>(chain) == null)
throw new ConfigException($"Invalid chains \"{chain}\"");
}
/// <summary>
/// Keep only the specified crypto
/// </summary>
/// <param name="cryptoCodes">Crypto to support</param>
/// <returns></returns>
public BTCPayNetworkProvider Filter(string[] cryptoCodes)
{
return new BTCPayNetworkProvider(this, cryptoCodes);
logs.Configuration.LogInformation(
"Supported chains: " + String.Join(',', _Networks.Select(n => n.Key).ToArray()));
}
public BTCPayNetwork BTC => GetNetwork<BTCPayNetwork>("BTC");
public BTCPayNetworkBase DefaultNetwork => BTC ?? GetAll().First();
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);
}
public IEnumerable<BTCPayNetworkBase> GetAll()
{
return _Networks.Values.ToArray();

@ -4,10 +4,13 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.2.5" />
<PackageReference Include="NBXplorer.Client" Version="4.2.6" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="2.0.1" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>
</ItemGroup>
<ItemGroup>
<Folder Include="Altcoins\" />
</ItemGroup>
</Project>

@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json.Bson;
namespace BTCPayServer
{
public class SelectedChains
{
HashSet<string> chains = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
bool all = false;
public SelectedChains(IConfiguration configuration, Logs logs)
{
foreach (var chain in (configuration["chains"] ?? "btc")
.Split(',', StringSplitOptions.RemoveEmptyEntries)
.Select(t => t.ToUpperInvariant()))
{
if (new[] { "ETH", "USDT20", "FAU" }.Contains(chain, StringComparer.OrdinalIgnoreCase))
{
logs.Configuration.LogWarning($"'{chain}' is not anymore supported, please remove it from 'chains'");
continue;
}
if (chain == "*")
{
all = true;
continue;
}
chains.Add(chain);
}
if (chains.Count == 0)
chains.Add("BTC");
if (all)
chains.Clear();
}
public bool Contains(string cryptoCode)
{
return all || chains.Contains(cryptoCode);
}
public void Add(string cryptoCode)
{
chains.Add(cryptoCode);
}
public IEnumerable<string> ExplicitlySelected => chains;
}
}

@ -93,7 +93,7 @@ namespace BTCPayServer.Data
ApplicationUser.OnModelCreating(builder, Database);
AddressInvoiceData.OnModelCreating(builder);
APIKeyData.OnModelCreating(builder, Database);
AppData.OnModelCreating(builder);
AppData.OnModelCreating(builder, Database);
CustodianAccountData.OnModelCreating(builder, Database);
//StoredFile.OnModelCreating(builder);
InvoiceEventData.OnModelCreating(builder);

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.9">
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />

@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace BTCPayServer.Data
{

@ -1,5 +1,6 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json;
namespace BTCPayServer.Data
@ -16,13 +17,20 @@ namespace BTCPayServer.Data
public string Settings { get; set; }
public bool Archived { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<AppData>()
.HasOne(o => o.StoreData)
.WithMany(i => i.Apps).OnDelete(DeleteBehavior.Cascade);
builder.Entity<AppData>()
.HasOne(a => a.StoreData);
if (databaseFacade.IsNpgsql())
{
builder.Entity<AppData>()
.Property(o => o.Settings)
.HasColumnType("JSONB");
}
}
// utility methods

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -30,11 +31,10 @@ namespace BTCPayServer.Data
public List<PendingInvoiceData> PendingInvoices { get; set; }
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
public List<RefundData> Refunds { get; set; }
public string CurrentRefundId { get; set; }
[ForeignKey("Id,CurrentRefundId")]
public RefundData CurrentRefund { get; set; }
[Timestamp]
// With this, update of InvoiceData will fail if the row was modified by another process
public uint XMin { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{
builder.Entity<InvoiceData>()
@ -42,8 +42,6 @@ namespace BTCPayServer.Data
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
builder.Entity<InvoiceData>()
.HasOne(o => o.CurrentRefund);
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
if (databaseFacade.IsNpgsql())

@ -1,5 +1,6 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
namespace BTCPayServer.Data
{
@ -13,7 +14,6 @@ namespace BTCPayServer.Data
public PullPaymentData PullPaymentData { get; set; }
public InvoiceData InvoiceData { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{
builder.Entity<RefundData>()

@ -1,14 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace BTCPayServer.Data
{
public class WalletObjectData
public class WalletObjectData : IEqualityComparer<WalletObjectData>
{
public class Types
{
@ -88,9 +86,30 @@ namespace BTCPayServer.Data
if (databaseFacade.IsNpgsql())
{
builder.Entity<WalletObjectData>()
.Property(o => o.Data)
.HasColumnType("JSONB");
.Property(o => o.Data)
.HasColumnType("JSONB");
}
}
public bool Equals(WalletObjectData x, WalletObjectData y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return string.Equals(x.WalletId, y.WalletId, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(x.Type, y.Type, StringComparison.InvariantCultureIgnoreCase) &&
string.Equals(x.Id, y.Id, StringComparison.InvariantCultureIgnoreCase);
}
public int GetHashCode(WalletObjectData obj)
{
HashCode hashCode = new HashCode();
hashCode.Add(obj.WalletId, StringComparer.InvariantCultureIgnoreCase);
hashCode.Add(obj.Type, StringComparer.InvariantCultureIgnoreCase);
hashCode.Add(obj.Id, StringComparer.InvariantCultureIgnoreCase);
return hashCode.ToHashCode();
}
}
}

@ -1,4 +1,4 @@
using BTCPayServer.Data;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;

@ -0,0 +1,38 @@
using System.Security.Permissions;
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20231020135844_AddBoltcardsTable")]
public partial class AddBoltcardsTable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "boltcards",
columns: table => new
{
id = table.Column<string>(maxLength: 32, nullable: false),
counter = table.Column<int>(type: "INT", nullable: false, defaultValue: 0),
ppid = table.Column<string>(maxLength: 30, nullable: true),
version = table.Column<int>(nullable: false, defaultValue: 0)
},
constraints: table =>
{
table.PrimaryKey("PK_id", x => x.id);
table.ForeignKey("FK_boltcards_PullPayments", x => x.ppid, "PullPayments", "Id", onDelete: ReferentialAction.SetNull);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable("boltcards");
}
}
}

@ -0,0 +1,36 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20231121031609_removecurrentrefund")]
public partial class removecurrentrefund : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.DropForeignKey(
name: "FK_Invoices_Refunds_Id_CurrentRefundId",
table: "Invoices");
migrationBuilder.DropIndex(
name: "IX_Invoices_Id_CurrentRefundId",
table: "Invoices");
migrationBuilder.DropColumn(
name: "CurrentRefundId",
table: "Invoices");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -0,0 +1,26 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20231219031609_appssettingstojson")]
public partial class appssettingstojson : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
if (migrationBuilder.IsNpgsql())
{
migrationBuilder.Sql("ALTER TABLE \"Apps\" ALTER COLUMN \"Settings\" TYPE JSONB USING \"Settings\"::JSONB");
}
}
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

@ -16,25 +16,7 @@ namespace BTCPayServer.Migrations
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "6.0.9");
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("CreatedTime")
.HasColumnType("TEXT");
b.Property<string>("InvoiceDataId")
.HasColumnType("TEXT");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
{
@ -71,6 +53,24 @@ namespace BTCPayServer.Migrations
b.ToTable("ApiKeys");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.Property<string>("Address")
.HasColumnType("TEXT");
b.Property<DateTimeOffset?>("CreatedTime")
.HasColumnType("TEXT");
b.Property<string>("InvoiceDataId")
.HasColumnType("TEXT");
b.HasKey("Address");
b.HasIndex("InvoiceDataId");
b.ToTable("AddressInvoices");
});
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
{
b.Property<string>("Id")
@ -287,9 +287,6 @@ namespace BTCPayServer.Migrations
b.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b.Property<string>("CurrentRefundId")
.HasColumnType("TEXT");
b.Property<string>("CustomerEmail")
.HasColumnType("TEXT");
@ -308,6 +305,11 @@ namespace BTCPayServer.Migrations
b.Property<string>("StoreDataId")
.HasColumnType("TEXT");
b.Property<uint>("XMin")
.IsConcurrencyToken()
.ValueGeneratedOnAddOrUpdate()
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Created");
@ -316,8 +318,6 @@ namespace BTCPayServer.Migrations
b.HasIndex("StoreDataId");
b.HasIndex("Id", "CurrentRefundId");
b.ToTable("Invoices");
});
@ -786,31 +786,6 @@ namespace BTCPayServer.Migrations
b.ToTable("Stores");
});
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ApplicationUserId")
.HasColumnType("TEXT");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("StorageFileName")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("Files");
});
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
{
b.Property<string>("Id")
@ -868,6 +843,31 @@ namespace BTCPayServer.Migrations
b.ToTable("StoreWebhooks");
});
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
{
b.Property<string>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("ApplicationUserId")
.HasColumnType("TEXT");
b.Property<string>("FileName")
.HasColumnType("TEXT");
b.Property<string>("StorageFileName")
.HasColumnType("TEXT");
b.Property<DateTime>("Timestamp")
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ApplicationUserId");
b.ToTable("Files");
});
modelBuilder.Entity("BTCPayServer.Data.U2FDevice", b =>
{
b.Property<string>("Id")
@ -1176,16 +1176,6 @@ namespace BTCPayServer.Migrations
b.ToTable("AspNetUserTokens", (string)null);
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("AddressInvoices")
.HasForeignKey("InvoiceDataId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("InvoiceData");
});
modelBuilder.Entity("BTCPayServer.Data.APIKeyData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
@ -1203,6 +1193,16 @@ namespace BTCPayServer.Migrations
b.Navigation("User");
});
modelBuilder.Entity("BTCPayServer.Data.AddressInvoiceData", b =>
{
b.HasOne("BTCPayServer.Data.InvoiceData", "InvoiceData")
.WithMany("AddressInvoices")
.HasForeignKey("InvoiceDataId")
.OnDelete(DeleteBehavior.Cascade);
b.Navigation("InvoiceData");
});
modelBuilder.Entity("BTCPayServer.Data.AppData", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
@ -1251,12 +1251,6 @@ namespace BTCPayServer.Migrations
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.RefundData", "CurrentRefund")
.WithMany()
.HasForeignKey("Id", "CurrentRefundId");
b.Navigation("CurrentRefund");
b.Navigation("StoreData");
});
@ -1419,15 +1413,6 @@ namespace BTCPayServer.Migrations
b.Navigation("PullPaymentData");
});
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
{
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
.WithMany("StoredFiles")
.HasForeignKey("ApplicationUserId");
b.Navigation("ApplicationUser");
});
modelBuilder.Entity("BTCPayServer.Data.StoreRole", b =>
{
b.HasOne("BTCPayServer.Data.StoreData", "StoreData")
@ -1468,6 +1453,15 @@ namespace BTCPayServer.Migrations
b.Navigation("Webhook");
});
modelBuilder.Entity("BTCPayServer.Data.StoredFile", b =>
{
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")
.WithMany("StoredFiles")
.HasForeignKey("ApplicationUserId");
b.Navigation("ApplicationUser");
});
modelBuilder.Entity("BTCPayServer.Data.U2FDevice", b =>
{
b.HasOne("BTCPayServer.Data.ApplicationUser", "ApplicationUser")

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<Version>1.0.0.0</Version>
<PackAsTool>true</PackAsTool>
<ToolCommandName>btcpay-plugin</ToolCommandName>

@ -4,9 +4,9 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.3.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="NBitcoin" Version="7.0.24" />
<PackageReference Include="NBitcoin" Version="7.0.32" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.4" />
</ItemGroup>

@ -29,7 +29,7 @@ namespace BTCPayServer.Rating.Providers
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.btcturk.com/api/v2/ticker", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.btcturk.com/api/v2/ticker", cancellationToken);
var jarray = (JArray)(await response.Content.ReadAsAsync<JObject>(cancellationToken))["data"];
var tickers = jarray.ToObject<Ticker[]>();
return tickers

@ -21,7 +21,7 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://public.bitbank.cc/tickers", cancellationToken);
using var response = await _httpClient.GetAsync("https://public.bitbank.cc/tickers", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var data = jobj.ContainsKey("data") ? jobj["data"] : null;
if (jobj["success"]?.Value<int>() != 1)

@ -19,7 +19,7 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.bitflyer.jp/v1/ticker", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
if (jobj.Property("error_message")?.Value?.Value<string>() is string err)
{

@ -19,7 +19,9 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);
using var response = await _httpClient.GetAsync("https://bitpay.com/rates", cancellationToken);
if (response.Content.Headers.ContentType?.MediaType is not "application/json")
throw new HttpRequestException($"Unexpected content type when querying currency rates from Bitpay ({response.Content.Headers.ContentType?.MediaType})");
var jarray = (JArray)(await response.Content.ReadAsAsync<JObject>(cancellationToken))["data"];
return jarray
.Children<JObject>()

@ -18,7 +18,7 @@ public class BudaRateProvider : IRateProvider
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://www.buda.com/api/v2/markets/btc-clp/ticker", cancellationToken);
using var response = await _httpClient.GetAsync("https://www.buda.com/api/v2/markets/btc-clp/ticker", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var minAsk = jobj["ticker"]["min_ask"][0].Value<decimal>();
var maxBid = jobj["ticker"]["max_bid"][0].Value<decimal>();

@ -18,7 +18,7 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);
using var response = await _httpClient.GetAsync("https://bylls.com/api/price?from_currency=BTC&to_currency=CAD", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var value = jobj["public_price"]["to_price"].Value<decimal>();
return new[] { new PairRate(new CurrencyPair("BTC", "CAD"), new BidAsk(value)) };

@ -28,7 +28,7 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.exchange.cryptomkt.com/api/3/public/ticker/", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.exchange.cryptomkt.com/api/3/public/ticker/", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
return ((jobj as JObject) ?? new JObject())

@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
@ -18,7 +18,7 @@ public class FreeCurrencyRatesRateProvider : IRateProvider
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
using var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var results = (JObject) jobj["btc"] ;

@ -21,7 +21,7 @@ namespace BTCPayServer.Rating
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.hitbtc.com/api/2/public/ticker", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.hitbtc.com/api/2/public/ticker", cancellationToken);
var jarray = await response.Content.ReadAsAsync<JArray>(cancellationToken);
return jarray
.Children<JObject>()

@ -172,7 +172,7 @@ namespace BTCPayServer.Services.Rates
sb.Append(String.Join('&', payload.Select(kv => $"{kv.Key}={kv.Value}").OfType<object>().ToArray()));
}
var request = new HttpRequestMessage(HttpMethod.Get, sb.ToString());
var response = await HttpClient.SendAsync(request, cancellationToken);
using var response = await HttpClient.SendAsync(request, cancellationToken);
string stringResult = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(stringResult);
if (result is JToken json)

@ -21,7 +21,7 @@ namespace BTCPayServer.Services.Rates
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.ripiotrade.co/v4/public/tickers", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.ripiotrade.co/v4/public/tickers", cancellationToken);
response.EnsureSuccessStatusCode();
var jarray = (JArray)(await response.Content.ReadAsAsync<JObject>(cancellationToken))["data"];
return jarray

@ -23,7 +23,7 @@ namespace BTCPayServer.Services.Rates
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.yadio.io/exrates/BTC", cancellationToken);
using var response = await _httpClient.GetAsync("https://api.yadio.io/exrates/BTC", cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var results = jobj["BTC"];

@ -5,6 +5,7 @@ using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
@ -23,6 +24,7 @@ using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using WalletSettingsViewModel = BTCPayServer.Models.StoreViewModels.WalletSettingsViewModel;
namespace BTCPayServer.Tests
@ -756,24 +758,26 @@ noninventoryitem:
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
//inventoryitem has 1 item available
await tester.WaitForEvent<AppInventoryUpdaterHostedService.UpdateAppInventory>(() =>
async Task AssertCanBuy(string choiceKey, bool expected)
{
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
return Task.CompletedTask;
});
var redirect = Assert.IsType<RedirectToActionResult>(await publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: choiceKey));
if (expected)
Assert.Equal("UIInvoice", redirect.ControllerName);
else
Assert.NotEqual("UIInvoice", redirect.ControllerName);
}
//inventoryitem has 1 item available
await AssertCanBuy("inventoryitem", true);
//we already bought all available stock so this should fail
await Task.Delay(100);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "inventoryitem").Result);
await AssertCanBuy("inventoryitem", false);
//inventoryitem has unlimited items available
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 1, choiceKey: "noninventoryitem").Result);
await AssertCanBuy("noninventoryitem", true);
await AssertCanBuy("noninventoryitem", true);
//verify invoices where created
invoices = user.BitPay.GetInvoices();
@ -805,7 +809,6 @@ btconly:
- BTC
normal:
price: 1.0";
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
Assert.IsType<RedirectToActionResult>(publicApps

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Plugins.Altcoins;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Common.csproj" />
<PropertyGroup>
<NoWarn>$(NoWarn),xUnit1031</NoWarn>
<IsPackable>false</IsPackable>
<UserSecretsId>AB0AC1DD-9D26-485B-9416-56A33F268117</UserSecretsId>
<!--https://devblogs.microsoft.com/aspnet/testing-asp-net-core-mvc-web-apps-in-memory/-->
@ -19,13 +20,13 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.15" />
<PackageReference Include="Selenium.Support" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver" Version="4.1.1" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="119.0.6045.10500" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="119.0.6045.10500" />
<PackageReference Include="xunit" Version="2.6.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>

@ -232,17 +232,13 @@ namespace BTCPayServer.Tests
ndax.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("BTC_CAD"), new BidAsk(6000m)));
rateProvider.Providers.Add("ndax", ndax);
var bittrex = new MockRateProvider();
bittrex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));
rateProvider.Providers.Add("bittrex", bittrex);
var bitfinex = new MockRateProvider();
bitfinex.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("UST_BTC"), new BidAsk(0.000136m)));
rateProvider.Providers.Add("bitfinex", bitfinex);
var bitpay = new MockRateProvider();
bitpay.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("ETB_BTC"), new BidAsk(0.1m)));
bitpay.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("DOGE_BTC"), new BidAsk(0.004m)));
rateProvider.Providers.Add("bitpay", bitpay);
var kraken = new MockRateProvider();
kraken.ExchangeRates.Add(new PairRate(CurrencyPair.Parse("ETH_BTC"), new BidAsk(0.1m)));

@ -12,7 +12,8 @@ namespace BTCPayServer.Tests
{
this._Parent = serverTester;
var url = serverTester.GetEnvironment(environmentName, defaultValue);
Client = (ChargeClient)LightningClientFactory.CreateClient(url, network);
Client = (ChargeClient)new LightningClientFactory(network).Create(url);
P2PHost = _Parent.GetEnvironment(environmentName + "_HOST", defaultHost);
}
public ChargeClient Client { get; set; }

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/sdk:6.0.401-bullseye-slim AS builder
FROM mcr.microsoft.com/dotnet/sdk:8.0.100-bookworm-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*

@ -98,7 +98,7 @@ retry:
}
Thread.Sleep(50);
}
Assert.False(true, "Elements was found");
Assert.Fail("Elements was found");
}
public static void UntilJsIsReady(this WebDriverWait wait)
@ -197,10 +197,11 @@ retry:
driver.FindElement(selector).Click();
}
[DebuggerHidden]
public static bool ElementDoesNotExist(this IWebDriver driver, By selector)
{
Assert.Throws<NoSuchElementException>(() =>
Assert.Throws<NoSuchElementException>(
[DebuggerStepThrough]
() =>
{
driver.FindElement(selector);
});

@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
@ -18,9 +19,12 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.JsonConverters;
using BTCPayServer.Logging;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Plugins;
using BTCPayServer.Plugins.Bitcoin;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
@ -131,7 +135,7 @@ namespace BTCPayServer.Tests
var tags = new HashSet<String>(g.Select(o => o.Tag));
if (tags.Count != 1)
{
Assert.False(true, $"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
Assert.Fail($"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
}
}
}
@ -410,7 +414,7 @@ namespace BTCPayServer.Tests
public void CanCalculateDust()
{
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
#pragma warning disable CS0618
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod()
@ -456,7 +460,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanCalculateCryptoDue()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = networkProvider;
#pragma warning disable CS0618
@ -644,7 +648,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanAcceptInvoiceWithTolerance()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
@ -764,7 +768,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
var tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
@ -776,7 +780,7 @@ namespace BTCPayServer.Tests
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = null,
@ -813,7 +817,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanParseDerivationSchemes()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var parser = new DerivationSchemeParser(networkProvider.BTC);
// xpub
@ -839,7 +843,7 @@ namespace BTCPayServer.Tests
Assert.Equal(expected, inner.ToString());
// Output Descriptor
networkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
networkProvider = CreateNetworkProvider(ChainName.Mainnet);
parser = new DerivationSchemeParser(networkProvider.BTC);
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48";
(strategyBase, rootedKeyPath) = parser.ParseOutputDescriptor(od);
@ -881,8 +885,8 @@ namespace BTCPayServer.Tests
[Fact]
public void ParseDerivationSchemeSettings()
{
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = new BTCPayNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var testnet = CreateNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = CreateNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var root = new Mnemonic(
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
.DeriveExtKey();
@ -954,6 +958,40 @@ namespace BTCPayServer.Tests
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("Specter", specter.Label);
Assert.Null(error);
//BSMS BIP129, Nunchuk
var bsms = @"BSMS 1.0
wsh(sortedmulti(1,[5c9e228d/48'/0'/0'/2']xpub6EgGHjcvovyN3nK921zAGPfuB41cJXkYRdt3tLGmiMyvbgHpss4X1eRZwShbEBb1znz2e2bCkCED87QZpin3sSYKbmCzQ9Sc7LaV98ngdeX/**,[2b0e251e/48'/0'/0'/2']xpub6DrimHB8KUSkPvmJ8Pk8RE769EdDm2VEoZ8MBz76w9QupP8Py4wexs4Pa3aRB1LUEhc9GyY6ypDWEFFRCgqeDQePcyWQfjtmintrehq3JCL/**))
/0/*,/1/*
bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(bsms,
mainnet, out var nunchuk, out error));
Assert.Equal(2, nunchuk.AccountKeySettings.Length);
//check that the account key settings match those in bsms string
Assert.Equal("5c9e228d", nunchuk.AccountKeySettings[0].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("2b0e251e", nunchuk.AccountKeySettings[1].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[1].AccountKeyPath.ToString());
var multsig = Assert.IsType < MultisigDerivationStrategy >
(Assert.IsType<P2WSHDerivationStrategy>(nunchuk.AccountDerivation).Inner);
Assert.True(multsig.LexicographicOrder);
Assert.Equal(1, multsig.RequiredSignatures);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line =nunchuk.AccountDerivation.GetLineFor(deposit).Derive(0);
Assert.Equal(BitcoinAddress.Create("bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku", Network.Main).ScriptPubKey,
line.ScriptPubKey);
Assert.Equal("BSMS", nunchuk.Source);
Assert.Null(error);
// Failure case
Assert.False(DerivationSchemeSettings.TryParseFromWalletFile(
@ -964,10 +1002,10 @@ namespace BTCPayServer.Tests
}
[Fact]
public void CheckRatesProvider()
public async Task CheckRatesProvider()
{
var spy = new SpyRateProvider();
RateRules.TryParse("X_X = bittrex(X_X);", out var rateRules);
RateRules.TryParse("X_X = bitpay(X_X);", out var rateRules);
var factory = CreateBTCPayRateFactory();
factory.Providers.Clear();
@ -975,24 +1013,23 @@ namespace BTCPayServer.Tests
factory.Providers.Clear();
var fetch = new BackgroundFetcherRateProvider(spy);
fetch.DoNotAutoFetchIfExpired = true;
factory.Providers.Add("bittrex", fetch);
var fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter()
.GetResult();
factory.Providers.Add("bitpay", fetch);
var fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertHit();
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertNotHit();
fetch.RefreshRate = TimeSpan.FromSeconds(1.0);
Thread.Sleep(1020);
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.ValidatyTime = TimeSpan.FromSeconds(1.0);
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertHit();
fetch.GetRatesAsync(default).GetAwaiter().GetResult();
await fetch.GetRatesAsync(default);
Thread.Sleep(1000);
Assert.Throws<InvalidOperationException>(() => fetch.GetRatesAsync(default).GetAwaiter().GetResult());
await Assert.ThrowsAsync<InvalidOperationException>(() => fetch.GetRatesAsync(default));
}
public static RateProviderFactory CreateBTCPayRateFactory()
@ -1246,7 +1283,7 @@ namespace BTCPayServer.Tests
[Fact]
public void HasCurrencyDataForNetworks()
{
var btcPayNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var btcPayNetworkProvider = CreateNetworkProvider(ChainName.Regtest);
foreach (var network in btcPayNetworkProvider.GetAll())
{
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
@ -1577,7 +1614,7 @@ namespace BTCPayServer.Tests
StringBuilder builder = new StringBuilder();
builder.AppendLine("// Some cool comments");
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("// Some other cool comments");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X);");
@ -1588,7 +1625,7 @@ namespace BTCPayServer.Tests
Assert.Equal(
"// Some cool comments\n" +
"DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" +
"DOGE_BTC = bittrex(DOGE_BTC);\n" +
"DOGE_BTC = bitpay(DOGE_BTC);\n" +
"// Some other cool comments\n" +
"BTC_USD = kraken(BTC_USD);\n" +
"BTC_X = coinbase(BTC_X);\n" +
@ -1596,10 +1633,10 @@ namespace BTCPayServer.Tests
rules.ToString());
var tests = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
(Pair: "DOGE_USD", Expected: "bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
(Pair: "DOGE_CAD", Expected: "bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
(Pair: "SATS_CAD", Expected: "0.00000001 * coinbase(BTC_CAD)"),
(Pair: "Sats_USD", Expected: "0.00000001 * kraken(BTC_USD)")
@ -1609,13 +1646,13 @@ namespace BTCPayServer.Tests
Assert.Equal(test.Expected, rules.GetRuleFor(CurrencyPair.Parse(test.Pair)).ToString());
}
rules.Spread = 0.2m;
Assert.Equal("(bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
Assert.Equal("(bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
////////////////
// Check errors conditions
builder = new StringBuilder();
builder.AppendLine("DOGE_X = LTC_CAD * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("LTC_CHF = LTC_CHF * 1.01");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
@ -1636,7 +1673,7 @@ namespace BTCPayServer.Tests
// Check if we can resolve exchange rates
builder = new StringBuilder();
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
@ -1644,10 +1681,10 @@ namespace BTCPayServer.Tests
var tests2 = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),kraken(BTC_USD)"),
(Pair: "DOGE_USD", Expected: "bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bitpay(DOGE_BTC),kraken(BTC_USD)"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bitpay(DOGE_BTC),coinbase(BTC_CAD)"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),
(Pair: "SATS_USD", Expected: "0.00000001 * kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
(Pair: "SATS_EUR", Expected: "0.00000001 * coinbase(BTC_EUR)", ExpectedExchangeRates: "coinbase(BTC_EUR)")
@ -1659,11 +1696,11 @@ namespace BTCPayServer.Tests
Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType<object>().ToArray()));
}
var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD"));
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
rule2.ExchangeRates.SetRate("bitpay", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
rule2.Reevaluate();
Assert.True(rule2.HasError);
Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true));
Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
Assert.Equal("bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(2000.4m));
rule2.Reevaluate();
Assert.False(rule2.HasError);
@ -1825,8 +1862,7 @@ namespace BTCPayServer.Tests
new KeyValuePair<string, string>("chains", "usdt")}
})
});
var networkProvider = config.ConfigureNetworkProvider(BTCPayLogs);
var networkProvider = CreateNetworkProvider(config);
Assert.NotNull(networkProvider.GetNetwork("LBTC"));
Assert.NotNull(networkProvider.GetNetwork("USDT"));
}
@ -1834,9 +1870,9 @@ namespace BTCPayServer.Tests
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme()
{
var testnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
var testnetNetworkProvider = CreateNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = CreateNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = CreateNetworkProvider(ChainName.Mainnet);
var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
@ -2002,7 +2038,7 @@ namespace BTCPayServer.Tests
{
#pragma warning disable CS0618
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var networkBTC = networkProvider.GetNetwork("BTC");
var networkLTC = networkProvider.GetNetwork("LTC");
InvoiceEntity invoiceEntity = new InvoiceEntity();
@ -2074,6 +2110,7 @@ namespace BTCPayServer.Tests
[Fact]
public void AllPoliciesShowInUI()
{
var a = new BitpayRateProvider(new System.Net.Http.HttpClient()).GetRatesAsync(default).Result;
foreach (var policy in Policies.AllPolicies)
{
Assert.True(UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.ContainsKey(policy));
@ -2119,7 +2156,7 @@ namespace BTCPayServer.Tests
{
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
};
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", new BTCPayNetworkProvider(ChainName.Regtest).BTC);
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
scheme.Source = "ManualDerivationScheme";
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
@ -2139,7 +2176,7 @@ namespace BTCPayServer.Tests
.Select(o =>
{
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(o.ToString());
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
return entity.DerivationStrategies.ToString();
})
.ToHashSet();

@ -198,6 +198,65 @@ public class FormTests : UnitTestBase
service.SetValues(form, new JObject { ["test"] = "hello" });
obj = service.GetValues(form);
Assert.Equal("hello", obj["test"].Value<string>());
var req = service.GenerateInvoiceParametersFromForm(form);
Assert.Null(req.Amount);
Assert.Null(req.Currency);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount",
Type = "number",
Value = "1"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(1, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_adjustment",
Type = "number",
Value = "1"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(2, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_adjustment2",
Type = "number",
Value = "2"
});
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}currency",
Type = "text",
Value = "eur"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal("eur", req.Currency);
Assert.Equal(4, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_multiply_adjustment",
Type = "number",
Value = "2"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(8, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_multiply_adjustment1",
Type = "number",
Value = "2"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(16, req.Amount);
}
private void Clear(Form form)

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
@ -28,6 +29,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
namespace BTCPayServer.Tests
@ -1084,7 +1086,39 @@ namespace BTCPayServer.Tests
Assert.IsType<string>(lnrURLs.LNURLUri);
Assert.Equal(12.303228134m, test4.Amount);
Assert.Equal("BTC", test4.Currency);
// Check we can register Boltcard
var uid = new byte[7];
RandomNumberGenerator.Fill(uid);
var card = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(0, card.Version);
var card1keys = new[] { card.K0, card.K1, card.K2, card.K3, card.K4 };
Assert.DoesNotContain(null, card1keys);
var card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(1, card2.Version);
Assert.StartsWith("lnurlw://", card2.LNURLW);
Assert.EndsWith("/boltcard", card2.LNURLW);
var card2keys = new[] { card2.K0, card2.K1, card2.K2, card2.K3, card2.K4 };
Assert.DoesNotContain(null, card2keys);
for (int i = 0; i < card1keys.Length; i++)
{
if (i == 1)
Assert.Contains(card1keys[i], card2keys);
else
Assert.DoesNotContain(card1keys[i], card2keys);
}
var card3 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid,
OnExisting = OnExistingBehavior.KeepVersion
});
Assert.Equal(card2.Version, card3.Version);
// Test with SATS denomination values
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
@ -1284,15 +1318,18 @@ namespace BTCPayServer.Tests
var client = await user.CreateClient(Policies.Unrestricted);
//create store
var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" });
var newStore = await client.CreateStore(new CreateStoreRequest { Name = "A" });
Assert.Equal("A", newStore.Name);
Assert.Equal(CheckoutType.V2, newStore.CheckoutType);
//update store
Assert.Empty(newStore.PaymentMethodCriteria);
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest()
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest
{
Name = "B",
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
CheckoutType = CheckoutType.V1,
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>
{
new()
{
@ -1304,6 +1341,7 @@ namespace BTCPayServer.Tests
}
});
Assert.Equal("B", updatedStore.Name);
Assert.Equal(CheckoutType.V1, updatedStore.CheckoutType);
var s = (await client.GetStore(newStore.Id));
Assert.Equal("B", s.Name);
var pmc = Assert.Single(s.PaymentMethodCriteria);
@ -2095,18 +2133,18 @@ namespace BTCPayServer.Tests
//validation errors
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
{
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions() { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
});
await AssertHttpError(403, async () =>
{
await viewOnly.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "helloinvalid", Amount = 1 });
new CreateInvoiceRequest { Currency = "helloinvalid", Amount = 1 });
});
await user.RegisterDerivationSchemeAsync("BTC");
string origOrderId = "testOrder";
var newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
@ -2193,7 +2231,7 @@ namespace BTCPayServer.Tests
//list NonExisting Status
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
status: new[] { BTCPayServer.Client.Models.InvoiceStatus.Invalid });
status: new[] { InvoiceStatus.Invalid });
Assert.NotNull(invoicesNonExistingStatus);
Assert.Empty(invoicesNonExistingStatus);
@ -2211,7 +2249,7 @@ namespace BTCPayServer.Tests
//update
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
@ -2223,7 +2261,7 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
{
Status = InvoiceStatus.Invalid
@ -2238,13 +2276,13 @@ namespace BTCPayServer.Tests
await AssertHttpError(403, async () =>
{
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
});
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
@ -2284,13 +2322,12 @@ namespace BTCPayServer.Tests
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
foreach (var marked in new[] { InvoiceStatus.Settled, InvoiceStatus.Invalid })
{
var inv = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
new CreateInvoiceRequest { Currency = "USD", Amount = 100 });
await user.PayInvoice(inv.Id);
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest()
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest
{
Status = marked
});
@ -2318,13 +2355,12 @@ namespace BTCPayServer.Tests
}
}
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
Checkout = new CreateInvoiceRequest.CheckoutOptions()
Checkout = new CreateInvoiceRequest.CheckoutOptions
{
DefaultLanguage = "it-it ",
RedirectURL = "http://toto.com/lol"
@ -3781,9 +3817,19 @@ namespace BTCPayServer.Tests
{
case "before-automated-payout-processing":
beforeHookTcs.TrySetResult();
var bd = (BeforePayoutActionData)tuple.args;
foreach (var p in bd.Payouts)
{
TestLogs.LogInformation("Before Processed: " + p.Id);
}
break;
case "after-automated-payout-processing":
afterHookTcs.TrySetResult();
var ad = (AfterPayoutActionData)tuple.args;
foreach (var p in ad.Payouts)
{
TestLogs.LogInformation("After Processed: " + p.Id);
}
break;
}
};
@ -3798,7 +3844,21 @@ namespace BTCPayServer.Tests
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
payouts = await adminClient.GetStorePayouts(admin.StoreId);
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
try
{
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
}
catch (SingleException)
{
TestLogs.LogInformation("Debugging flaky test...");
TestLogs.LogInformation("payoutThatShouldBeProcessedStraightAway: " + payoutThatShouldBeProcessedStraightAway.Id);
foreach (var p in payouts)
{
TestLogs.LogInformation("Payout Id: " + p.Id);
TestLogs.LogInformation("Payout State: " + p.State);
}
throw;
}
beforeHookTcs = new TaskCompletionSource();
afterHookTcs = new TaskCompletionSource();
@ -4471,7 +4531,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await AssertHttpError(404, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", simulateWithdrawalRequest));
// Test: SimulateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
// TODO it is weird that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, correct payment method, wrong amount
@ -4502,7 +4562,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await AssertHttpError(404, async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", createWithdrawalRequest));
// Test: CreateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
// TODO it is weird that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.CreateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, createWithdrawalRequest));
// Test: CreateWithdrawal, correct payment method, wrong amount

@ -1,5 +1,4 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
@ -99,14 +98,25 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("FakePayment")).Click();
if (mine)
{
TestUtils.Eventually(() =>
{
Driver.WaitForElement(By.Id("CheatSuccessMessage"));
});
MineBlockOnInvoiceCheckout();
}
}
public void MineBlockOnInvoiceCheckout()
{
Driver.FindElement(By.CssSelector("#mine-block button")).Click();
retry:
try
{
Driver.FindElement(By.CssSelector("#mine-block button")).Click();
}
catch (StaleElementReferenceException)
{
goto retry;
}
}
/// <summary>
@ -295,17 +305,12 @@ namespace BTCPayServer.Tests
public void AddLightningNode()
{
AddLightningNode(null, null, true);
AddLightningNode(null, true);
}
public void AddLightningNode(LightningConnectionType? connectionType = null, bool test = true)
public void AddLightningNode(string connectionType = null, bool test = true)
{
AddLightningNode(null, connectionType, test);
}
public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true)
{
cryptoCode ??= "BTC";
var cryptoCode = "BTC";
if (!Driver.PageSource.Contains("Connect to a Lightning node"))
{
GoToLightningSettings();

@ -6,6 +6,7 @@ using System.Globalization;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
@ -17,6 +18,7 @@ using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Lightning;
using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.NTag424;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
@ -25,6 +27,8 @@ using BTCPayServer.Views.Manage;
using BTCPayServer.Views.Server;
using BTCPayServer.Views.Stores;
using BTCPayServer.Views.Wallets;
using Dapper;
using ExchangeSharp;
using LNURL;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
@ -111,6 +115,7 @@ namespace BTCPayServer.Tests
// Payment Request
s.Driver.FindElement(By.Id("StoreNav-PaymentRequests")).Click();
s.Driver.FindElement(By.Id("CreatePaymentRequest")).Click();
Thread.Sleep(10000);
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
new SelectElement(s.Driver.FindElement(By.Id("FormId"))).SelectByValue("Email");
@ -159,7 +164,7 @@ namespace BTCPayServer.Tests
Assert.Contains("CustomFormInputTest", s.Driver.PageSource);
s.Driver.FindElement(By.Name("buyerEmail")).SendKeys("aa@aa.com");
s.Driver.FindElement(By.CssSelector("input[type='submit']")).Click();
s.PayInvoice(true);
s.PayInvoice(true, 0.001m);
var result = await s.Server.PayTester.HttpClient.GetAsync(formurl);
Assert.Equal(HttpStatusCode.NotFound, result.StatusCode);
@ -461,6 +466,12 @@ namespace BTCPayServer.Tests
using var s = CreateSeleniumTester();
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
// Store Emails without server fallback
s.GoToStore(StoreNavPages.Emails);
s.Driver.FindElement(By.Id("ConfigureEmailRules")).Click();
Assert.Contains("You need to configure email settings before this feature works", s.Driver.PageSource);
// Server Emails
s.Driver.Navigate().GoToUrl(s.Link("/server/emails"));
@ -470,12 +481,11 @@ namespace BTCPayServer.Tests
s.FindAlertMessage();
}
CanSetupEmailCore(s);
s.CreateNewStore();
// Store Emails
// Store Emails with server fallback
s.GoToStore(StoreNavPages.Emails);
s.Driver.FindElement(By.Id("ConfigureEmailRules")).Click();
Assert.Contains("You need to configure email settings before this feature works", s.Driver.PageSource);
Assert.Contains("Emails will be sent with the email settings of the server", s.Driver.PageSource);
s.GoToStore(StoreNavPages.Emails);
CanSetupEmailCore(s);
@ -485,10 +495,11 @@ namespace BTCPayServer.Tests
Assert.Contains("There are no rules yet.", s.Driver.PageSource);
Assert.DoesNotContain("id=\"SaveEmailRules\"", s.Driver.PageSource);
Assert.DoesNotContain("You need to configure email settings before this feature works", s.Driver.PageSource);
Assert.DoesNotContain("Emails will be sent with the email settings of the server", s.Driver.PageSource);
s.Driver.FindElement(By.Id("CreateEmailRule")).Click();
var select = new SelectElement(s.Driver.FindElement(By.Id("Rules_0__Trigger")));
select.SelectByText("InvoiceSettled", true);
select.SelectByText("An invoice has been settled", true);
s.Driver.FindElement(By.Id("Rules_0__To")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.Id("Rules_0__CustomerEmail")).Click();
s.Driver.FindElement(By.Id("Rules_0__Subject")).SendKeys("Thanks!");
@ -938,8 +949,9 @@ namespace BTCPayServer.Tests
Policies.CanModifyServerSettings
});
s.GoToUrl("/logout");
await alice.MakeAdmin();
s.Logout();
s.GoToLogin();
s.LogIn(alice.Email, alice.Password);
s.GoToUrl($"/cheat/permissions/stores/{alice.StoreId}");
@ -1036,20 +1048,20 @@ namespace BTCPayServer.Tests
select.SelectByText("Point of", true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.FindAlertMessage();
s.Logout();
s.GoToLogin();
s.LogIn(userId);
// Make sure after login, we are not redirected to the PoS
s.Logout();
s.LogIn(userId);
Assert.DoesNotContain("Tea shop", s.Driver.PageSource);
var prevUrl = s.Driver.Url;
// We are only if explicitly going to /
s.GoToUrl("/");
Assert.Contains("Tea shop", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(new Uri(prevUrl, UriKind.Absolute));
// Check redirect to canonical url
s.GoToUrl(posBaseUrl);
Assert.Equal("/", new Uri(s.Driver.Url, UriKind.Absolute).AbsolutePath);
// Let's check with domain mapping as well.
s.Driver.Navigate().GoToUrl(new Uri(prevUrl, UriKind.Absolute));
s.GoToServer(ServerNavPages.Policies);
s.Driver.ScrollTo(By.Id("RootAppId"));
select = new SelectElement(s.Driver.FindElement(By.Id("RootAppId")));
@ -1062,19 +1074,20 @@ namespace BTCPayServer.Tests
select.SelectByText("Point of", true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
Assert.Contains("Policies updated successfully", s.FindAlertMessage().Text);
// Make sure after login, we are not redirected to the PoS
s.Logout();
s.LogIn(userId);
// Make sure after login, we are not redirected to the PoS
Assert.DoesNotContain("Tea shop", s.Driver.PageSource);
// We are only if explicitly going to /
s.GoToUrl("/");
Assert.Contains("Tea shop", s.Driver.PageSource);
// Check redirect to canonical url
s.GoToUrl(posBaseUrl);
Assert.Equal("/", new Uri(s.Driver.Url, UriKind.Absolute).AbsolutePath);
// Archive
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
s.Driver.SwitchTo().Window(windows[0]);
Assert.True(s.Driver.ElementDoesNotExist(By.Id("Nav-ArchivedApps")));
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
Assert.Contains("The app has been archived and will no longer appear in the apps list by default.", s.FindAlertMessage().Text);
@ -1136,7 +1149,6 @@ namespace BTCPayServer.Tests
// Contribute
s.Driver.FindElement(By.Id("crowdfund-body-header-cta")).Click();
Thread.Sleep(1000);
s.Driver.WaitUntilAvailable(By.Name("btcpay"));
var frameElement = s.Driver.FindElement(By.Name("btcpay"));
@ -1416,13 +1428,6 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("update")).Click();
s.FindAlertMessage();
s.Driver.FindElement(By.LinkText("Modify")).Click();
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
{
// Here we make sure we did not forget an event type in the list
// However, maybe some event should not appear here because not at the store level.
// Fix as needed.
Assert.Contains($"value=\"{value}\"", s.Driver.PageSource);
}
// This one should be checked
Assert.Contains("value=\"InvoiceProcessing\" checked", s.Driver.PageSource);
@ -2114,6 +2119,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.CssSelector("#lnurlwithdraw-button")).Click();
s.Driver.WaitForElement(By.Id("qr-code-data-input"));
// Try to use lnurlw via the QR Code
var lnurl = new Uri(LNURL.LNURL.Parse(s.Driver.FindElement(By.Id("qr-code-data-input")).GetAttribute("value"), out _).ToString().Replace("https", "http"));
s.Driver.FindElement(By.CssSelector("button[data-bs-dismiss='modal']")).Click();
var info = Assert.IsType<LNURLWithdrawRequest>(await LNURL.LNURL.FetchInformation(lnurl, s.Server.PayTester.HttpClient));
@ -2124,7 +2130,7 @@ namespace BTCPayServer.Tests
Assert.Equal(info.CurrentBalance, new LightMoney(0.0000001m, LightMoneyUnit.BTC));
var bolt2 = (await s.Server.CustomerLightningD.CreateInvoice(
new LightMoney(0.0000001m, LightMoneyUnit.BTC),
new LightMoney(0.00000005m, LightMoneyUnit.BTC),
$"LNurl w payout test {DateTime.UtcNow.Ticks}",
TimeSpan.FromHours(1), CancellationToken.None));
var response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient, null,null);
@ -2139,6 +2145,56 @@ namespace BTCPayServer.Tests
s.Driver.Close();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.First());
// Simulate a boltcard
{
var db = s.Server.PayTester.GetService<ApplicationDbContextFactory>();
var ppid = lnurl.AbsoluteUri.Split("/").Last();
var issuerKey = new IssuerKey(SettingsRepositoryExtensions.FixedKey());
var uid = RandomNumberGenerator.GetBytes(7);
var cardKey = issuerKey.CreatePullPaymentCardKey(uid, 0, ppid);
var keys = cardKey.DeriveBoltcardKeys(issuerKey);
await db.LinkBoltcardToPullPayment(ppid, issuerKey, uid);
var piccData = new byte[] { 0xc7 }.Concat(uid).Concat(new byte[] { 1, 0, 0, 0, 0, 0, 0, 0 }).ToArray();
var p = keys.EncryptionKey.Encrypt(piccData);
var c = keys.AuthenticationKey.GetSunMac(uid, 1);
var boltcardUrl = new Uri(s.Server.PayTester.ServerUri.AbsoluteUri + $"boltcard?p={Encoders.Hex.EncodeData(p).ToStringUpperInvariant()}&c={Encoders.Hex.EncodeData(c).ToStringUpperInvariant()}");
// p and c should work so long as no bolt11 has been submitted
info = (LNURLWithdrawRequest)await LNURL.LNURL.FetchInformation(boltcardUrl, s.Server.PayTester.HttpClient);
info = (LNURLWithdrawRequest)await LNURL.LNURL.FetchInformation(boltcardUrl, s.Server.PayTester.HttpClient);
var fakeBoltcardUrl = new Uri(Regex.Replace(boltcardUrl.AbsoluteUri, "p=([A-F0-9]{32})", $"p={RandomBytes(16)}"));
await Assert.ThrowsAsync<LNUrlException>(() => LNURL.LNURL.FetchInformation(fakeBoltcardUrl, s.Server.PayTester.HttpClient));
fakeBoltcardUrl = new Uri(Regex.Replace(boltcardUrl.AbsoluteUri, "c=([A-F0-9]{16})", $"c={RandomBytes(8)}"));
await Assert.ThrowsAsync<LNUrlException>(() => LNURL.LNURL.FetchInformation(fakeBoltcardUrl, s.Server.PayTester.HttpClient));
bolt2 = (await s.Server.CustomerLightningD.CreateInvoice(
new LightMoney(0.00000005m, LightMoneyUnit.BTC),
$"LNurl w payout test2 {DateTime.UtcNow.Ticks}",
TimeSpan.FromHours(1), CancellationToken.None));
response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient, null, null);
Assert.Equal("OK", response.Status);
// No replay should be possible
await Assert.ThrowsAsync<LNUrlException>(() => LNURL.LNURL.FetchInformation(boltcardUrl, s.Server.PayTester.HttpClient));
response = await info.SendRequest(bolt2.BOLT11, s.Server.PayTester.HttpClient, null, null);
Assert.Equal("ERROR", response.Status);
Assert.Contains("Replayed", response.Reason);
// Check the state of the registration, counter should have increased
var reg = await db.GetBoltcardRegistration(issuerKey, uid);
Assert.Equal((ppid, 1, 0), (reg.PullPaymentId, reg.Counter, reg.Version));
await db.SetBoltcardResetState(issuerKey, uid);
// After reset, counter is 0, version unchanged and ppId null
reg = await db.GetBoltcardRegistration(issuerKey, uid);
Assert.Equal((null, 0, 0), (reg.PullPaymentId, reg.Counter, reg.Version));
await db.LinkBoltcardToPullPayment(ppid, issuerKey, uid);
// Relink should bump Version
reg = await db.GetBoltcardRegistration(issuerKey, uid);
Assert.Equal((ppid, 0, 1), (reg.PullPaymentId, reg.Counter, reg.Version));
await db.LinkBoltcardToPullPayment(ppid, issuerKey, uid);
reg = await db.GetBoltcardRegistration(issuerKey, uid);
Assert.Equal((ppid, 0, 2), (reg.PullPaymentId, reg.Counter, reg.Version));
}
s.GoToStore(s.StoreId, StoreNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
@ -2219,6 +2275,12 @@ namespace BTCPayServer.Tests
s.Driver.Close();
}
private string RandomBytes(int count)
{
var c = RandomNumberGenerator.GetBytes(count);
return Encoders.Hex.EncodeData(c);
}
[Fact]
[Trait("Selenium", "Selenium")]
[Trait("Lightning", "Lightning")]
@ -2709,7 +2771,7 @@ namespace BTCPayServer.Tests
callbacks.Add(request.Callback);
break;
default:
Assert.False(true, "Should have matched");
Assert.Fail("Should have matched");
break;
}
}
@ -3050,13 +3112,18 @@ retry:
{
s.Driver.FindElement(By.Id("QuickFillDropdownToggle")).Click();
s.Driver.FindElement(By.CssSelector("#quick-fill .dropdown-menu .dropdown-item:first-child")).Click();
s.Driver.FindElement(By.Id("Settings_Login")).Clear();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("Settings_Password")).Clear();
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
s.Driver.FindElement(By.Id("Settings_From")).Clear();
s.Driver.FindElement(By.Id("Settings_From")).SendKeys("Firstname Lastname <email@example.com>");
s.Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
Assert.Contains("Configured", s.Driver.PageSource);
s.Driver.FindElement(By.Id("Settings_Login")).Clear();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
s.Driver.FindElement(By.Id("Save")).SendKeys(Keys.Enter);
Assert.Contains("Configured", s.Driver.PageSource);

@ -5,11 +5,14 @@ using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Tests.Lnd;
using BTCPayServer.Tests.Logging;
using Microsoft.Extensions.Configuration.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.RPC;
@ -26,7 +29,7 @@ namespace BTCPayServer.Tests
public ILoggerProvider LoggerProvider { get; }
internal ILog TestLogs;
public ServerTester(string scope, bool newDb, ILog testLogs, ILoggerProvider loggerProvider)
public ServerTester(string scope, bool newDb, ILog testLogs, ILoggerProvider loggerProvider, BTCPayNetworkProvider networkProvider)
{
LoggerProvider = loggerProvider;
this.TestLogs = testLogs;
@ -36,7 +39,7 @@ namespace BTCPayServer.Tests
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
NetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
NetworkProvider = networkProvider;
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
ExplorerNode.ScanRPCCapabilities();
@ -94,17 +97,18 @@ namespace BTCPayServer.Tests
{
ActivateLightning(LightningConnectionType.CLightning);
}
public void ActivateLightning(LightningConnectionType internalNode)
public void ActivateLightning(string internalNode)
{
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
var factory = new LightningClientFactory(btc);
CustomerLightningD = factory.Create(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"));
MerchantLightningD = factory.Create(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"));
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true);
}
public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant)
public string GetLightningConnectionString(string connectionType, bool isMerchant)
{
string connectionString = null;
if (connectionType is null)

@ -278,7 +278,7 @@ namespace BTCPayServer.Tests
public bool IsAdmin { get; internal set; }
public void RegisterLightningNode(string cryptoCode, LightningConnectionType? connectionType = null, bool isMerchant = true)
public void RegisterLightningNode(string cryptoCode, string connectionType = null, bool isMerchant = true)
{
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
}
@ -286,7 +286,7 @@ namespace BTCPayServer.Tests
{
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
public async Task RegisterLightningNodeAsync(string cryptoCode, string connectionType, bool isMerchant = true, string storeId = null)
{
var storeController = GetController<UIStoresController>();
@ -297,7 +297,7 @@ namespace BTCPayServer.Tests
await storeController.SetupLightningNode(storeId ?? StoreId,
vm, "save", cryptoCode);
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
Assert.Fail(storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
public async Task RegisterInternalLightningNodeAsync(string cryptoCode, string storeId = null)
@ -307,7 +307,7 @@ namespace BTCPayServer.Tests
await storeController.SetupLightningNode(storeId ?? StoreId,
vm, "save", cryptoCode);
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
Assert.Fail(storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network = null)
@ -432,8 +432,7 @@ namespace BTCPayServer.Tests
if (!response.IsSuccessStatusCode)
{
var error = JObject.Parse(await response.Content.ReadAsStringAsync());
Assert.True(false,
$"Error: {error["errorCode"].Value<string>()}: {error["message"].Value<string>()}");
Assert.Fail($"Error: {error["errorCode"].Value<string>()}: {error["message"].Value<string>()}");
}
}
@ -488,7 +487,7 @@ namespace BTCPayServer.Tests
}
public List<WebhookInvoiceEvent> WebhookEvents { get; set; } = new List<WebhookInvoiceEvent>();
public TEvent AssertHasWebhookEvent<TEvent>(WebhookEventType eventType, Action<TEvent> assert) where TEvent : class
public TEvent AssertHasWebhookEvent<TEvent>(string eventType, Action<TEvent> assert) where TEvent : class
{
int retry = 0;
retry:
@ -516,12 +515,12 @@ retry:
retry++;
goto retry;
}
Assert.True(false, "No webhook event match the assertion");
Assert.Fail("No webhook event match the assertion");
return null;
}
public async Task SetupWebhook()
{
FakeServer server = new FakeServer();
var server = new FakeServer();
await server.Start();
var client = await CreateClient(Policies.CanModifyStoreWebhooks);
var wh = await client.CreateWebhook(StoreId, new CreateStoreWebhookRequest()
@ -537,7 +536,7 @@ retry:
{
var inv = await BitPay.GetInvoiceAsync(invoiceId);
var net = parent.ExplorerNode.Network;
this.parent.ExplorerNode.SendToAddress(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
await parent.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
await TestUtils.EventuallyAsync(async () =>
{
var localInvoice = await BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using NBitcoin;
using OpenQA.Selenium;
using Xunit;
using Xunit.Sdk;

@ -11,11 +11,14 @@ using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Fees;
using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using NBitcoin;
using NBitpayClient;
@ -73,6 +76,25 @@ namespace BTCPayServer.Tests
await UnitTest1.CanUploadRemoveFiles(controller);
}
[Fact]
public async Task CanQueryMempoolFeeProvider()
{
IServiceCollection collection = new ServiceCollection();
collection.AddMemoryCache();
collection.AddHttpClient();
var prov = collection.BuildServiceProvider();
foreach (var isTestnet in new[] { true, false })
{
var mempoolSpaceFeeProvider = new MempoolSpaceFeeProvider(
prov.GetService<IMemoryCache>(),
"test" + isTestnet,
prov.GetService<IHttpClientFactory>(),
isTestnet);
var rates = await mempoolSpaceFeeProvider.GetFeeRatesAsync();
Assert.NotEmpty(rates);
await mempoolSpaceFeeProvider.GetFeeRateAsync(20);
}
}
[Fact]
public async Task CanQueryDirectProviders()
{
@ -268,17 +290,16 @@ retry:
}
catch (Exception ex)
{
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
var details = ex.Message;
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
throw;
}
}
[Fact()]
[Fact]
public void CanSolveTheDogesRatesOnKraken()
{
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
@ -296,9 +317,9 @@ retry:
{
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = CreateNetworkProvider(ChainName.Mainnet);
var b = new StoreBlob();
string[] temporarilyBroken = { "COP", "UGX" };
string[] temporarilyBroken = Array.Empty<string>();
foreach (var k in StoreBlob.RecommendedExchanges)
{
b.DefaultCurrency = k.Key;
@ -329,7 +350,7 @@ retry:
public async Task CanGetRateCryptoCurrenciesByDefault()
{
using var cts = new CancellationTokenSource(60_000);
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = CreateNetworkProvider(ChainName.Mainnet);
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var pairs =
@ -337,7 +358,7 @@ retry:
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
.ToHashSet();
string[] brokenShitcoins = { "BTG", "BTX" };
string[] brokenShitcoins = { "BTG", "BTX", "GRS" };
bool IsBrokenShitcoin(CurrencyPair p) => brokenShitcoins.Contains(p.Left) || brokenShitcoins.Contains(p.Right);
foreach (var _ in brokenShitcoins)
{

@ -15,6 +15,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
@ -23,6 +24,7 @@ using BTCPayServer.Fido2.Models;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.Charge;
using BTCPayServer.Models;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Models.AppViewModels;
@ -45,6 +47,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
@ -56,6 +59,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
@ -68,6 +72,7 @@ using Newtonsoft.Json.Schema;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
namespace BTCPayServer.Tests
@ -175,7 +180,7 @@ namespace BTCPayServer.Tests
// If this test fail, run `UpdateSwagger` once.
if (description != json["components"]["securitySchemes"]["API_Key"]["description"].Value<string>())
{
Assert.False(true, "Please run manually the test `UpdateSwagger` once");
Assert.Fail("Please run manually the test `UpdateSwagger` once");
}
}
@ -269,7 +274,7 @@ namespace BTCPayServer.Tests
}
catch (Exception ex)
{
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
var details = ex.Message;
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
throw;
@ -390,7 +395,7 @@ namespace BTCPayServer.Tests
BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
}, 40000);
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork) tester.DefaultNetwork)} via lightning");
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork)tester.DefaultNetwork)} via lightning");
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
{
await tester.SendLightningPaymentAsync(newInvoice);
@ -475,7 +480,7 @@ namespace BTCPayServer.Tests
await ProcessLightningPayment(LightningConnectionType.LndREST);
}
async Task ProcessLightningPayment(LightningConnectionType type)
async Task ProcessLightningPayment(string type)
{
// For easier debugging and testing
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
@ -539,7 +544,7 @@ namespace BTCPayServer.Tests
var pairingCode = (string)token.RouteValues["pairingCode"];
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
await acc.BitPay.AuthorizeClient(new PairingCode(pairingCode));
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
}
@ -603,7 +608,7 @@ namespace BTCPayServer.Tests
completed = true;
break;
default:
Assert.False(true, $"{evtName} was not expected");
Assert.Fail($"{evtName} was not expected");
break;
}
}
@ -1294,13 +1299,10 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await tester.ExplorerNode.EnsureGenerateAsync(1);
var rng = new Random();
var seed = rng.Next();
rng = new Random(seed);
TestLogs.LogInformation("Seed: " + seed);
foreach (var networkFeeMode in Enum.GetValues(typeof(NetworkFeeMode)).Cast<NetworkFeeMode>())
{
await user.SetNetworkFeeMode(networkFeeMode);
@ -1313,7 +1315,7 @@ namespace BTCPayServer.Tests
}
}
private static async Task AssertTopUpBtcPrice(ServerTester tester, TestAccount user, Money btcSent, decimal expectedPriceWithoutNetworkFee, NetworkFeeMode networkFeeMode)
private async Task AssertTopUpBtcPrice(ServerTester tester, TestAccount user, Money btcSent, decimal expectedPriceWithoutNetworkFee, NetworkFeeMode networkFeeMode)
{
var cashCow = tester.ExplorerNode;
// First we try payment with a merchant having only BTC
@ -1328,8 +1330,6 @@ namespace BTCPayServer.Tests
var btcmethod = (await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id))[0];
var paid = btcSent;
var invoiceAddress = BitcoinAddress.Create(btcmethod.Destination, cashCow.Network);
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
var networkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
.GetPaymentMethods()[btc]
@ -1340,10 +1340,7 @@ namespace BTCPayServer.Tests
{
networkFee = 0.0m;
}
cashCow.SendToAddress(invoiceAddress, paid);
await cashCow.SendToAddressAsync(invoiceAddress, paid);
await TestUtils.EventuallyAsync(async () =>
{
try
@ -1363,7 +1360,7 @@ namespace BTCPayServer.Tests
}
catch (JsonSerializationException)
{
Assert.False(true, "The bitpay's amount is not set");
Assert.Fail("The bitpay's amount is not set");
}
});
}
@ -1418,7 +1415,7 @@ namespace BTCPayServer.Tests
StringComparison.OrdinalIgnoreCase);
rateVm.ScriptTest = "BTC_USD,BTC_CAD,DOGE_USD,DOGE_CAD";
rateVm.Script = "DOGE_X = bittrex(DOGE_BTC) * BTC_X;\n" +
rateVm.Script = "DOGE_X = bitpay(DOGE_BTC) * BTC_X;\n" +
"X_CAD = ndax(X_CAD);\n" +
"X_X = coingecko(X_X);";
rateVm.Spread = 50;
@ -1821,7 +1818,7 @@ namespace BTCPayServer.Tests
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
Assert.True(app.Role.ToPermissionSet(app.StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
@ -1947,11 +1944,11 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetupWebhook();
var invoice = user.BitPay.CreateInvoice(
new Invoice()
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
TaxIncluded = 1000.0m,
@ -1983,7 +1980,7 @@ namespace BTCPayServer.Tests
Assert.Single(textSearchResult);
});
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id, Facade.Merchant);
Assert.Equal(1000.0m, invoice.TaxIncluded);
Assert.Equal(5000.0m, invoice.Price);
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
@ -1998,11 +1995,8 @@ namespace BTCPayServer.Tests
Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.UtcDateTime - TimeSpan.FromDays(5),
invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)));
var firstPayment = Money.Coins(0.04m);
var txFee = Money.Zero;
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
@ -2030,7 +2024,7 @@ namespace BTCPayServer.Tests
secondPayment = localInvoice.BtcDue;
});
cashCow.SendToAddress(invoiceAddress, secondPayment);
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
TestUtils.Eventually(() =>
{
@ -2044,7 +2038,7 @@ namespace BTCPayServer.Tests
Assert.False((bool)((JValue)localInvoice.ExceptionStatus).Value);
});
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
await cashCow.GenerateAsync(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
TestUtils.Eventually(() =>
{
@ -2052,7 +2046,7 @@ namespace BTCPayServer.Tests
Assert.Equal("confirmed", localInvoice.Status);
});
cashCow.Generate(5); //Now should be complete
await cashCow.GenerateAsync(5); //Now should be complete
TestUtils.Eventually(() =>
{
@ -2061,7 +2055,7 @@ namespace BTCPayServer.Tests
Assert.NotEqual(0.0m, localInvoice.Rate);
});
invoice = user.BitPay.CreateInvoice(new Invoice()
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Price = 5000.0m,
Currency = "USD",
@ -2074,7 +2068,7 @@ namespace BTCPayServer.Tests
}, Facade.Merchant);
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
var txId = cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
var txId = await cashCow.SendToAddressAsync(invoiceAddress, invoice.BtcDue + Money.Coins(1));
TestUtils.Eventually(() =>
{
@ -2091,7 +2085,7 @@ namespace BTCPayServer.Tests
Assert.Single(textSearchResult);
});
cashCow.Generate(1);
await cashCow.GenerateAsync(2);
TestUtils.Eventually(() =>
{
@ -2106,6 +2100,7 @@ namespace BTCPayServer.Tests
c =>
{
Assert.False(c.ManuallyMarked);
Assert.True(c.OverPaid);
});
user.AssertHasWebhookEvent<WebhookInvoiceProcessingEvent>(WebhookEventType.InvoiceProcessing,
c =>
@ -2256,13 +2251,17 @@ namespace BTCPayServer.Tests
}
class MockVersionFetcher : IVersionFetcher
class MockVersionFetcher : GithubVersionFetcher
{
public const string MOCK_NEW_VERSION = "9.9.9.9";
public Task<string> Fetch(CancellationToken cancellation)
public override Task<string> Fetch(CancellationToken cancellation)
{
return Task.FromResult(MOCK_NEW_VERSION);
}
public MockVersionFetcher(IHttpClientFactory httpClientFactory, BTCPayServerOptions options, ILogger<GithubVersionFetcher> logger, SettingsRepository settingsRepository, BTCPayServerEnvironment environment, NotificationSender notificationSender) : base(httpClientFactory, options, logger, settingsRepository, environment, notificationSender)
{
}
}
[Fact(Timeout = LongRunningTestTimeout)]
@ -2281,8 +2280,13 @@ namespace BTCPayServer.Tests
var mockEnv = tester.PayTester.GetService<BTCPayServerEnvironment>();
var mockSender = tester.PayTester.GetService<Services.Notifications.NotificationSender>();
var svc = new NewVersionCheckerHostedService(settings, mockEnv, mockSender, new MockVersionFetcher(), BTCPayLogs);
await svc.ProcessVersionCheck();
var svc = new MockVersionFetcher(tester.PayTester.GetService<IHttpClientFactory>(),
tester.PayTester.GetService<BTCPayServerOptions>(),
tester.PayTester.GetService<ILogger<GithubVersionFetcher>>(),
settings,
mockEnv,
mockSender);
await svc.Do(CancellationToken.None);
// since last version present in database was null, it should've been updated with version mock returned
var lastVersion = await settings.GetSettingAsync<NewVersionCheckerDataHolder>();
@ -2390,9 +2394,14 @@ namespace BTCPayServer.Tests
Assert.NotNull(lnMethod.GetExternalLightningUrl());
var url = lnMethod.GetExternalLightningUrl();
Assert.Equal(LightningConnectionType.Charge, url.ConnectionType);
Assert.Equal("pass", url.Password);
Assert.Equal("usr", url.Username);
var kv = LightningConnectionStringHelper.ExtractValues(url, out var connType);
Assert.Equal(LightningConnectionType.Charge, connType);
var client = Assert.IsType<ChargeClient>(tester.PayTester.GetService<LightningClientFactoryService>()
.Create(url, tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")));
var auth = Assert.IsType<ChargeAuthentication.UserPasswordAuthentication>(client.ChargeAuthentication);
Assert.Equal("pass", auth.NetworkCredential.Password);
Assert.Equal("usr", auth.NetworkCredential.UserName);
// Test if lightning connection strings get migrated to internal
store.DerivationStrategies = new JObject()
@ -2816,7 +2825,7 @@ namespace BTCPayServer.Tests
var app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
{
AppName = "Static",
DefaultView = Client.Models.PosViewType.Static,
DefaultView = Client.Models.PosViewType.Static,
Template = new PointOfSaleSettings().Template
});
var resp = await posController.ViewPointOfSale(app.Id, choiceKey: "green-tea");
@ -2826,7 +2835,7 @@ namespace BTCPayServer.Tests
app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
{
AppName = "Cart",
DefaultView = Client.Models.PosViewType.Cart,
DefaultView = Client.Models.PosViewType.Cart,
Template = new PointOfSaleSettings().Template
});
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()

@ -3,8 +3,18 @@ using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Hosting;
using BTCPayServer.Logging;
using BTCPayServer.Plugins.Bitcoin;
using BTCPayServer.Plugins;
using BTCPayServer.Tests.Logging;
using Microsoft.Extensions.DependencyInjection;
using NBXplorer;
using Xunit.Abstractions;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Configuration.Memory;
using NBitcoin;
namespace BTCPayServer.Tests
{
@ -15,7 +25,53 @@ namespace BTCPayServer.Tests
TestLogs = new XUnitLog(helper) { Name = "Tests" };
TestLogProvider = new XUnitLogProvider(helper);
BTCPayLogs = new BTCPayServer.Logging.Logs();
BTCPayLogs.Configure(new BTCPayServer.Logging.FuncLoggerFactory((n) => new XUnitLog(helper) { Name = n }));
LoggerFactory = new BTCPayServer.Logging.FuncLoggerFactory((n) => new XUnitLog(helper) { Name = n });
BTCPayLogs.Configure(LoggerFactory);
}
public BTCPayNetworkProvider CreateNetworkProvider(ChainName chainName)
{
var conf = new ConfigurationRoot(new List<IConfigurationProvider>()
{
new MemoryConfigurationProvider(new MemoryConfigurationSource()
{
InitialData = new[] {
new KeyValuePair<string, string>("chains", "*"),
new KeyValuePair<string, string>("network", chainName.ToString())
}
})
});
return CreateNetworkProvider(conf);
}
public BTCPayNetworkProvider CreateNetworkProvider(IConfiguration conf = null)
{
conf ??= new ConfigurationRoot(new List<IConfigurationProvider>()
{
new MemoryConfigurationProvider(new MemoryConfigurationSource()
{
InitialData = new[] {
new KeyValuePair<string, string>("chains", "*"),
new KeyValuePair<string, string>("network", "regtest")
}
})
});
var bootstrap = Startup.CreateBootstrap(conf);
var services = new PluginServiceCollection(new ServiceCollection(), bootstrap);
var plugins = new List<BaseBTCPayServerPlugin>() { new BitcoinPlugin() };
#if ALTCOINS
plugins.Add(new BTCPayServer.Plugins.Altcoins.AltcoinsPlugin());
#endif
foreach (var p in plugins)
{
p.Execute(services);
}
services.AddSingleton(services.BootstrapServices.GetRequiredService<SelectedChains>());
services.AddSingleton(services.BootstrapServices.GetRequiredService<NBXplorerNetworkProvider>());
services.AddSingleton(services.BootstrapServices.GetRequiredService<Logs>());
services.AddSingleton(services.BootstrapServices.GetRequiredService<IConfiguration>());
services.AddSingleton<BTCPayNetworkProvider>();
var serviceProvider = services.BuildServiceProvider();
return serviceProvider.GetService<BTCPayNetworkProvider>();
}
public ILog TestLogs
{
@ -26,14 +82,15 @@ namespace BTCPayServer.Tests
get;
}
public BTCPayServer.Logging.Logs BTCPayLogs { get; }
public FuncLoggerFactory LoggerFactory { get; }
public ServerTester CreateServerTester([CallerMemberNameAttribute] string scope = null, bool newDb = false)
{
return new ServerTester(scope, newDb, TestLogs, TestLogProvider);
return new ServerTester(scope, newDb, TestLogs, TestLogProvider, CreateNetworkProvider());
}
public SeleniumTester CreateSeleniumTester([CallerMemberNameAttribute] string scope = null, bool newDb = false)
{
return new SeleniumTester() { Server = new ServerTester(scope, newDb, TestLogs, TestLogProvider) };
return new SeleniumTester() { Server = new ServerTester(scope, newDb, TestLogs, TestLogProvider, CreateNetworkProvider()) };
}
}
}

@ -14,7 +14,7 @@ declare -A xpubs
printf "\n👛 Create descriptor wallets\n\n"
for ((n=1;n<=3;n++)); do
# Create descriptor wallets, surpress error output in case wallet already exists
# Create descriptor wallets, suppress error output in case wallet already exists
./docker-bitcoin-cli.sh -named createwallet wallet_name="${prefix}_part_${n}" descriptors=true > /dev/null 2>&1
# Collect xpubs
@ -38,7 +38,7 @@ multisig_int_desc="{\"desc\": $internal_desc_sum, \"active\": true, \"internal\"
multisig_desc="[$multisig_ext_desc, $multisig_int_desc]"
# Create multisig wallet, surpress error output in case wallet already exists
# Create multisig wallet, suppress error output in case wallet already exists
printf "\n🔐 Create multisig wallet\n"
printf "\nExternal descriptor: $external_desc\n"

@ -24,7 +24,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@ -73,7 +73,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:26.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -99,7 +99,7 @@ services:
custom:
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.66
image: nicolasdorier/nbxplorer:2.4.0
restart: unless-stopped
ports:
- "32838:32838"
@ -135,7 +135,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:26.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"

@ -22,7 +22,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@ -70,7 +70,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:26.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -96,7 +96,7 @@ services:
custom:
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.66
image: nicolasdorier/nbxplorer:2.4.0
restart: unless-stopped
ports:
- "32838:32838"
@ -121,7 +121,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:26.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"

@ -0,0 +1,42 @@
using Newtonsoft.Json.Linq;
using System.Net.WebSockets;
using System.Threading.Tasks;
using System.Threading;
using BTCPayServer.NTag424;
using NBitcoin.DataEncoders;
using System;
using SocketIOClient;
namespace BTCPayServer
{
public class APDUVaultTransport : IAPDUTransport
{
private readonly VaultClient _vaultClient;
public APDUVaultTransport(VaultClient vaultClient)
{
_vaultClient = vaultClient;
}
public async Task WaitForCard(CancellationToken cancellationToken)
{
await _vaultClient.SendVaultRequest("/wait-for-card", null, cancellationToken);
}
public async Task WaitForRemoved(CancellationToken cancellationToken)
{
await _vaultClient.SendVaultRequest("/wait-for-disconnected", null, cancellationToken);
}
public async Task<NtagResponse> SendAPDU(byte[] apdu, CancellationToken cancellationToken)
{
var resp = await _vaultClient.SendVaultRequest("/",
new JObject()
{
["apdu"] = Encoders.Hex.EncodeData(apdu)
}, cancellationToken);
var data = Encoders.Hex.DecodeData(resp["data"].Value<string>());
return new NtagResponse(data, resp["status"].Value<ushort>());
}
}
}

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
 <Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
@ -38,6 +38,7 @@
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Content Remove="Services\Altcoins\**\*" />
<Compile Remove="Plugins\Altcoins\**\*" />
<Content Remove="Views\UIMoneroLikeStore\**\*" />
<Content Remove="Views\UIZcashLikeStore\**\*" />
<Content Remove="Views\Shared\Monero\**\*" />
@ -45,28 +46,29 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.NTag424" Version="1.0.19" />
<PackageReference Include="YamlDotNet" Version="8.0.0" />
<PackageReference Include="BIP78.Sender" Version="0.2.2" />
<PackageReference Include="BTCPayServer.Hwi" Version="2.0.2" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.4.31" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.5.3" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="Dapper" Version="2.1.24" />
<PackageReference Include="Fido2" Version="2.0.2" />
<PackageReference Include="Fido2.AspNet" Version="2.0.2" />
<PackageReference Include="LNURL" Version="0.0.34" />
<PackageReference Include="MailKit" Version="3.3.0" />
<PackageReference Include="BTCPayServer.NETCore.Plugins.Mvc" Version="1.4.4" />
<PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="System.IO.Pipelines" Version="6.0.3" />
<PackageReference Include="System.IO.Pipelines" Version="8.0.0" />
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="NicolasDorier.CommandLine" Version="2.0.0" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="2.0.0" />
<PackageReference Include="NicolasDorier.RateLimits" Version="1.2.3" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.File" Version="4.1.0" />
<PackageReference Include="SSH.NET" Version="2020.0.2" />
<PackageReference Include="Serilog" Version="3.1.1" />
<PackageReference Include="Serilog.AspNetCore" Version="8.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00968" />
<PackageReference Include="SSH.NET" Version="2023.0.0" />
</ItemGroup>
<ItemGroup>
@ -75,8 +77,8 @@
<PackageReference Include="TwentyTwenty.Storage.Azure" Version="2.12.1" />
<PackageReference Include="TwentyTwenty.Storage.Google" Version="2.12.1" />
<PackageReference Include="TwentyTwenty.Storage.Local" Version="2.12.1" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="6.0.9" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="8.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="8.0.0" />
</ItemGroup>
<ItemGroup>
@ -136,6 +138,7 @@
<ItemGroup>
<Watch Include="Views\**\*.*"></Watch>
<Watch Remove="Views\Shared\LocalhostBrowserSupport.cshtml" />
<Watch Remove="Views\UIAccount\CheatPermissions.cshtml" />
<Watch Remove="Views\UIReports\StoreReports.cshtml" />
<Content Update="Views\UIApps\_ViewImports.cshtml">

@ -7,6 +7,7 @@
@using BTCPayServer.Client
@using BTCPayServer.Components.ThemeSwitch
@using BTCPayServer.Components.UIExtensionPoint
@using BTCPayServer.Plugins
@using BTCPayServer.Services
@using BTCPayServer.Views.Apps
@using BTCPayServer.Views.CustodianAccounts
@ -15,6 +16,7 @@
@inject SignInManager<ApplicationUser> SignInManager
@inject PoliciesSettings PoliciesSettings
@inject ThemeSettings Theme
@inject PluginService PluginService
@model BTCPayServer.Components.MainNav.MainNavViewModel
@ -133,25 +135,25 @@
<span>Invoices</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewInvoices">
<li class="nav-item" permission="@Policies.CanViewReports">
<a asp-area="" asp-controller="UIReports" asp-action="StoreReports" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.Reporting)" id="SectionNav-Reporting">
<vc:icon symbol="invoice" />
<span>Reporting</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPaymentRequests">
<a asp-area="" asp-controller="UIPaymentRequest" asp-action="GetPaymentRequests" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActiveCategory(typeof(PaymentRequestsNavPages))" id="StoreNav-PaymentRequests">
<vc:icon symbol="payment-requests"/>
<span>Requests</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanViewStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPullPayments">
<a asp-area="" asp-controller="UIStorePullPayments" asp-action="PullPayments" asp-route-storeId="@Model.Store.Id" class="nav-link @ViewData.IsActivePage(StoreNavPages.PullPayments)" id="StoreNav-PullPayments">
<vc:icon symbol="pull-payments"/>
<span>Pull Payments</span>
</a>
</li>
<li class="nav-item" permission="@Policies.CanModifyStoreSettings">
<li class="nav-item" permission="@Policies.CanViewPayouts">
<a asp-area=""
asp-controller="UIStorePullPayments" asp-action="Payouts"
asp-route-pullPaymentId=""
@ -185,7 +187,14 @@
<ul class="navbar-nav">
<li class="nav-item" permission="@Policies.CanModifyServerSettings">
<a asp-area="" asp-controller="UIServer" asp-action="ListPlugins" class="nav-link @ViewData.IsActivePage(ServerNavPages.Plugins)" id="Nav-ManagePlugins">
<vc:icon symbol="manage-plugins"/>
@if (PluginService.GetDisabledPlugins().Any())
{
<span class="me-2 btcpay-status btcpay-status--disabled"></span>
}
else
{
<vc:icon symbol="manage-plugins" />
}
<span>Manage Plugins</span>
</a>
</li>
@ -249,7 +258,7 @@
</a>
</li>
<li class="nav-item dropup">
<a class="nav-link @ViewData.IsActiveCategory(typeof(ManageNavPages))" role="button" data-bs-toggle="dropdown" aria-expanded="false" id="Nav-Account">
<a class="nav-link @ViewData.IsActiveCategory(typeof(ManageNavPages))" role="button" data-bs-toggle="dropdown" data-bs-auto-close="outside" aria-expanded="false" id="Nav-Account">
<vc:icon symbol="account"/>
<span>Account</span>
</a>
@ -264,7 +273,7 @@
@if (!Theme.CustomTheme)
{
<li class="py-1 px-3">
<vc:theme-switch css-class="nav-link pb-0"/>
<vc:theme-switch css-class="w-100 pt-2"/>
</li>
}
<li class="py-1 px-3">

@ -83,7 +83,7 @@
if (Model.PaginationQuery != null)
{
// merge both, prefering the `query` properties in case of duplicate keys
// merge both, preferring the `query` properties in case of duplicate keys
query = query.Concat(Model.PaginationQuery)
.GroupBy(e => e.Key)
.ToDictionary(g => g.Key, g => g.First().Value);

@ -11,13 +11,14 @@ namespace BTCPayServer.Components.QRCode
{
private static QRCodeGenerator _qrGenerator = new();
public IViewComponentResult Invoke(string data)
public IViewComponentResult Invoke(string data, int size=256)
{
var qrCodeData = _qrGenerator.CreateQrCode(data, QRCodeGenerator.ECCLevel.Q);
var qrCode = new PngByteQRCode(qrCodeData);
var bytes = qrCode.GetGraphic(5, new byte[] { 0, 0, 0, 255 }, new byte[] { 0xf5, 0xf5, 0xf7, 255 });
var b64 = Convert.ToBase64String(bytes);
return new HtmlContentViewComponentResult(new HtmlString($"<img style=\"image-rendering:pixelated;image-rendering:-moz-crisp-edges;min-width:256px;min-height:256px\" src=\"data:image/png;base64,{b64}\" class=\"qr-code\" />"));
return new HtmlContentViewComponentResult(new HtmlString(
$"<img style=\"image-rendering:pixelated;image-rendering:-moz-crisp-edges;min-width:{size}px;min-height:{size}px\" src=\"data:image/png;base64,{b64}\" class=\"qr-code\" />"));
}
}
}

@ -101,7 +101,7 @@ public class StoreLightningBalance : ViewComponent
}
if (existing.IsInternalNode && _lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var internalLightningNode))
{
return _lightningClientFactory.Create(internalLightningNode, network);
return internalLightningNode;
}
return null;

@ -55,7 +55,8 @@ public class StoreNumbers : ViewComponent
.Where(p => p.PullPaymentData.StoreId == vm.Store.Id && !p.PullPaymentData.Archived && p.State == PayoutState.AwaitingApproval)
.CountAsync();
vm.RefundsIssued = await ctx.Invoices
.Where(i => i.StoreData.Id == vm.Store.Id && !i.Archived && i.CurrentRefundId != null && i.Created >= offset)
.Where(i => i.StoreData.Id == vm.Store.Id && !i.Archived && i.Created >= offset)
.SelectMany(i => i.Refunds)
.CountAsync();
return View(vm);

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Stores;
@ -26,18 +27,22 @@ public class StoreRecentTransactions : ViewComponent
private readonly BTCPayWalletProvider _walletProvider;
private readonly WalletRepository _walletRepository;
private readonly LabelService _labelService;
private readonly TransactionLinkProviders _transactionLinkProviders;
public BTCPayNetworkProvider NetworkProvider { get; }
public StoreRecentTransactions(
BTCPayNetworkProvider networkProvider,
BTCPayWalletProvider walletProvider,
WalletRepository walletRepository,
LabelService labelService)
LabelService labelService,
TransactionLinkProviders transactionLinkProviders)
{
NetworkProvider = networkProvider;
_walletProvider = walletProvider;
_walletRepository = walletRepository;
_labelService = labelService;
_transactionLinkProviders = transactionLinkProviders;
}
public async Task<IViewComponentResult> InvokeAsync(StoreRecentTransactionsViewModel vm)
@ -60,7 +65,7 @@ public class StoreRecentTransactions : ViewComponent
var wallet = _walletProvider.GetWallet(network);
var allTransactions = await wallet.FetchTransactionHistory(derivationSettings.AccountDerivation, 0, 5, TimeSpan.FromDays(31.0), cancellationToken: this.HttpContext.RequestAborted);
var walletTransactionsInfo = await _walletRepository.GetWalletTransactionsInfo(vm.WalletId, allTransactions.Select(t => t.TransactionId.ToString()).ToArray());
var pmi = new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike);
transactions = allTransactions
.Select(tx =>
{
@ -73,8 +78,7 @@ public class StoreRecentTransactions : ViewComponent
Balance = tx.BalanceChange.ShowMoney(network),
Currency = vm.CryptoCode,
IsConfirmed = tx.Confirmations != 0,
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
tx.TransactionId.ToString()),
Link = _transactionLinkProviders.GetTransactionLink(pmi, tx.TransactionId.ToString()),
Timestamp = tx.SeenAt,
Labels = labels
};

@ -31,8 +31,7 @@
}
else
{
<a asp-controller="UIStores" asp-action="Dashboard" permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIInvoice" asp-action="ListInvoices" not-permission="@Policies.CanModifyStoreSettings" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@Model.CurrentStoreId" id="StoreSelectorHome" class="navbar-brand py-2">@{await LogoContent();}</a>
}
@if (Model.Options.Any() || Model.ArchivedCount > 0)
{
@ -54,21 +53,14 @@ else
@foreach (var option in Model.Options)
{
<li>
@if (option.IsOwner)
{
<a asp-controller="UIStores" asp-action="Dashboard" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
}
else
{
<a asp-controller="UIInvoice" asp-action="ListInvoices" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
}
<a asp-controller="UIStores" asp-action="Index" asp-route-storeId="@option.Value" class="dropdown-item@(option.Selected ? " active" : "")" id="StoreSelectorMenuItem-@option.Value">@StoreName(option.Text)</a>
</li>
}
@if (Model.Options.Any())
{
<li><hr class="dropdown-divider"></li>
}
<li><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.IsActivePage(StoreNavPages.Create)" id="StoreSelectorCreate">Create Store</a></li>
<li ><a asp-controller="UIUserStores" asp-action="CreateStore" class="dropdown-item @ViewData.IsActivePage(StoreNavPages.Create)" id="StoreSelectorCreate">Create Store</a></li>
@if (Model.ArchivedCount > 0)
{
<li><hr class="dropdown-divider"></li>

@ -41,14 +41,13 @@ namespace BTCPayServer.Components.StoreSelector
.FirstOrDefault()?
.Network.CryptoCode;
var walletId = cryptoCode != null ? new WalletId(store.Id, cryptoCode) : null;
var role = store.GetStoreRoleOfUser(userId);
return new StoreSelectorOption
{
Text = store.StoreName,
Value = store.Id,
Selected = store.Id == currentStore?.Id,
WalletId = walletId,
IsOwner = role != null && role.Permissions.Contains(Policies.CanModifyStoreSettings)
Store = store
};
})
.OrderBy(s => s.Text)

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