Compare commits

...

115 Commits

Author SHA1 Message Date
Kukks
ebc46eb7d2 fix swagger docs missing query param
fixes #2268
2021-02-15 10:05:54 +01:00
Nicolas Dorier
cb83669802 Merge pull request #2283 from bumbummen99/patch-2
Fix cart pay button loading spinner vertical alignment
2021-02-15 11:17:57 +09:00
Patrick
f6f616a21d Fix cart pay button loading spinner vertical alignment 2021-02-14 21:05:26 +01:00
Nicolas Dorier
62d50c0189 Profile email change should check email's availability (#2281) 2021-02-12 16:48:43 +09:00
Nicolas Dorier
dd5b143c13 Update BTCPayServer/Controllers/ManageController.cs
Co-authored-by: d11n <mail@dennisreimann.de>
2021-02-12 16:48:26 +09:00
nicolas.dorier
2e864c32fa Profile email change should check email's availability 2021-02-12 12:48:05 +09:00
d11n
f4fa7c927c Wallet setup redesign (#2164)
* Prepare existing layouts and views

* Add icon view component and sprite svg

* Add wallet setup basics

* Add import method view basics

* Use external sprite file instead of inline svg

* Refactor hardware wallet setup flow

* Manually enter an xpub

* Prepare other views

* Update views and models

* Finalize wallet setup flow

* Updat tests, part 1

* Update tests, part 2

* Vaul: Fix missing retry button

* Add better Scan QR subtext

Still tbd.

* Make wallet account an advanced setting

* Prevent empty xpub

* Use textarea for seed input

* Remove redundant error message for missing file upload

* Confirm store updates after generating a new wallet

* Update wording

* Modify existing wallets

* Fix proposed method name

* Suggest using ColdCard Electrum export option only

Advise the user to use the electrum export of the coldcard instead of saying either electrum or wasabi export file … the electurm one contains more info, e.g. the wasabi one doesn't include the account key path.

* More concise WalletSetupMethod setting

* Test fix

* Update wallet removal code

* Fix back navigation quirk in change wallet case

* Fix behaviour on wallet enable/disable

* Fix initial wallet setup

* Improve modify view and messages

* Test fixes

* Seed import fix

Uses the correct form url for confirming addresses

* Quickfixes from design meeting

* Add enable toggle switch on modify page

* Confirm wallet removal

* Update setup view

* Update import view

* Icon finetuning

* Improve import options page

* Refactor QR code scanner

Allow for usage with and without modal

* Update copy and instructions on import pages

* Split generate options: Hot wallet and watch-only

* Implement hot wallet options correctly

* Minor test changes

* Navbar improvements

* Fix tables

* Fix badge color

* Routing related updates

Thanks @kukks for the suggestions!

* Wording updates

Thanks @kukks for the suggestions!

* Extend address types table for xpub import

Thanks @kukks for the suggestions!

* Rename controller

* Unify precondition checks

* Improve removal warning for hot wallets

* Add tooltip on why seed import is not recommended

* Add tooltip icon

* Add Specter import info
2021-02-11 19:48:54 +09:00
Nicolas Dorier
3736cbc107 Merge pull request #2252 from dennisreimann/specter-wallet-file
Add Specter wallet file import
2021-02-10 23:02:26 +09:00
Nicolas Dorier
776825cc66 Merge pull request #2240 from dennisreimann/onchain-symbol
Invoices list: Remove icon indicator for onchain
2021-02-10 22:48:34 +09:00
Nicolas Dorier
5cb647e57f Merge pull request #2248 from bolatovumar/fix-2247
Ensure "No" selection is maintained for custom price in POS app
2021-02-10 22:42:55 +09:00
Nicolas Dorier
9c5f826bb4 Merge pull request #2256 from bumbummen99/patch-1
Fix current version
2021-02-10 22:35:34 +09:00
Nicolas Dorier
6b1803629d Merge pull request #2258 from dennisreimann/tabindex
Login: Improve tab navigation for input fields
2021-02-10 22:35:09 +09:00
Nicolas Dorier
5cea0571e3 Merge pull request #2265 from dennisreimann/checkout-tabs
Checkout: Fix scan/copy tab sizes with varying content
2021-02-10 22:34:24 +09:00
Nicolas Dorier
95c8afd5ba Merge pull request #2261 from ketominer/fix-mysql-2021
fixed mysql support
2021-02-10 18:26:20 +09:00
Dennis Reimann
ecc9a34359 Checkout: Fix scan/copy tab sizes with varying content
Fixes #2264.
2021-02-08 17:54:40 +01:00
ketominer
1fed7fb5af fix for sqlite 2021-02-07 21:14:31 +01:00
ketominer
93d1ded4c2 fixed mysql support 2021-02-07 20:57:48 +01:00
Dennis Reimann
f199775437 Register: Autofocus email field on page load 2021-02-05 13:04:21 +01:00
Dennis Reimann
40271f420d Login: Improve tab navigation for input fields
Improves the tab indexes so that keyboard navigation goes: Email > Password > Sign in > Create account > Forgot password.

Also autofocuses the email field so that you can start typing right away.

Closes #2257.
2021-02-05 13:01:55 +01:00
Pavlenex
9e71c02eb9 Merge pull request #2239 from pavlenex/readme-improvements
Readme overhaul
2021-02-05 11:29:16 +01:00
Patrick
c9cbfe630d Fix current version 2021-02-05 03:02:15 +01:00
Nicolas Dorier
bf9331b147 Merge pull request #2251 from dennisreimann/api-lightning
Fix empty GetLightningClient return value
2021-02-04 11:26:15 +09:00
Dennis Reimann
f223d2e00c Add Specter wallet file import
Closes #2223.
2021-02-03 16:36:45 +01:00
Dennis Reimann
5246e7f035 Fix empty GetLightningClient return value 2021-02-03 11:19:35 +01:00
Umar Bolatov
42de0803c9 Ensure "No" selection is maintained for custom price in POS app 2021-02-02 20:13:39 -08:00
Nicolas Dorier
242f9c6197 Merge pull request #2241 from btcpayserver/feat/lnd-0.12.0-beta
Updating development LND to v0.12.0-beta
2021-02-02 12:56:29 +09:00
rockstardev
5288198474 Updating connection to merchant_lnd for tests 2021-02-01 01:37:58 -06:00
rockstardev
b21353c4f9 Updating LND in altcoins docker file as well 2021-02-01 01:37:12 -06:00
rockstardev
65a0c6a4b3 Updating development LND to v0.12.0-beta 2021-02-01 00:55:13 -06:00
Dennis Reimann
af5bd89f34 Invoices list: Remove icon indicator for onchain
As [discussed on the chat](https://chat.btcpayserver.org/btcpayserver/pl/tcjy5ornhbd8i8jm4yj9y3maie) the icon feels unnecessary and isn't clear to users. Leaving it off and only indicating Lightning transactions avoids confusion.
2021-01-30 18:53:21 +01:00
Pavlenex
73aeffd13c Apply suggestions from code review
Co-authored-by: britttttk <39231115+britttttk@users.noreply.github.com>
2021-01-30 10:58:48 +01:00
Nicolas Dorier
03d2f6c017 Merge pull request #2209 from btcpayserver/invoice-metadata-test
Improve test on Greenfield Inoice metadata update
2021-01-30 11:17:11 +09:00
Pavlenex
7e1481c43f Apply suggestions from code review
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-01-29 18:31:41 +01:00
Pavlenex
8a1069bf70 Apply suggestions from code review
Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-01-29 18:30:57 +01:00
Pavlenex
6097ab5d12 Update README.md
Co-authored-by: d11n <mail@dennisreimann.de>
2021-01-29 14:04:35 +01:00
Pavlenex
d951575f80 Merge branch 'readme-improvements' of https://github.com/pavlenex/btcpayserver into readme-improvements 2021-01-29 12:38:47 +01:00
Pavlenex
d887546e58 remove chat 2021-01-29 12:37:59 +01:00
Pavlenex
ec3b80cec9 Apply suggestions from code review
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-01-29 12:36:12 +01:00
Pavlenex
2ef442cf83 Improve readme 2021-01-29 12:19:51 +01:00
nicolas.dorier
6e5a4a7546 Update changelog.md 2021-01-29 18:36:26 +09:00
nicolas.dorier
e0d46002cb Changelog 1.0.6.8 2021-01-29 18:31:40 +09:00
Nicolas Dorier
739f13b7a3 Merge pull request #2234 from dennisreimann/safe-browsing
Safe browsing quick fixes
2021-01-29 18:24:11 +09:00
d11n
34af74b288 Apply URL changes from code review
Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>

Update BTCPayServer/Controllers/AccountController.cs

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2021-01-28 12:33:12 +01:00
Dennis Reimann
4ee0cc8145 Remove clipboard code from main-bundle
See https://github.com/btcpayserver/btcpayserver/issues/2139#issuecomment-768216462
2021-01-28 10:48:06 +01:00
Dennis Reimann
4984674b1d Remove Tor URL from login and register page
Brave and Tor Browser now show availaibility of Tor automatically in the url box of the browser.
2021-01-28 10:47:28 +01:00
Dennis Reimann
f5ee67aafb Remove allowtransparency from checkout overlay 2021-01-28 10:34:34 +01:00
Dennis Reimann
98a1b0da71 Update public account URLs
- /Account/Login -> /login
- /Account/Register -> /register
- /Account/ForgotPassword -> /forgot-password
2021-01-28 10:08:22 +01:00
Nicolas Dorier
074ff76d49 Merge pull request #2221 from dennisreimann/selenium-tests
Selenium tests: Remove hacks, make them more reliable
2021-01-27 17:50:57 +09:00
Dennis Reimann
b75409a6bf Remove Firefox as option for Selenium tests 2021-01-27 09:35:14 +01:00
nicolas.dorier
94abda6e3e bump lightning libs 2021-01-27 17:21:18 +09:00
Salie Hendricks
68419a9510 Improve password reset email copy (#2211)
* Reset password email copy unified and updated

* Grammar fix for reset email copy

* Email strings added to facilitate html and style and call to action

* First pass html. Saving for later

* Compacted the html and inline styles and removed logo

Co-authored-by: Salie Hendricks <salie@safarinow.com>
2021-01-27 08:42:47 +01:00
Nicolas Dorier
db0854f203 Update NBitcoin, NetworkType => ChainName, signet support (#2224) 2021-01-27 14:39:38 +09:00
Nicolas Dorier
994301ea4c Bump Bitcoin Core and NBXPlorer (#2222) 2021-01-26 21:01:32 +09:00
Dennis Reimann
5e344b9be7 Fix missing user secrets code 2021-01-26 10:22:17 +01:00
Dennis Reimann
66d3da8ac9 Remove function checks as they didn't help 2021-01-26 09:40:31 +01:00
Dennis Reimann
71bb876c9e Configure browser settings with user secrets
Default to headless Chrome
2021-01-25 17:23:08 +01:00
Dennis Reimann
1e029f3290 Fix EthereumTests 2021-01-25 16:49:20 +01:00
Dennis Reimann
7926b689fd Default to headless, add Firefox, update Chrome 2021-01-25 15:01:53 +01:00
Dennis Reimann
4638f781f9 Fix JS error 2021-01-25 14:11:11 +01:00
Dennis Reimann
8bea1505dd Cleanups, remove WaitForPageLoad hack 2021-01-25 14:10:40 +01:00
Dennis Reimann
e567f7a80c Refactor tests
Remove the hacky `ScrollTo`, `ForceClick` and `WaitForElement`.
Add the hacky `WaitForPageLoad`.
2021-01-25 13:04:58 +01:00
rockstardev
3774e8dc51 Merge pull request #2207 from btcpayserver/feat/new-error-pages
New Error Pages
2021-01-24 23:49:36 -06:00
rockstardev
dc27ffa6ba Http error page 406 for our dear man Jack
Yes, acceptable
2021-01-24 13:41:30 -06:00
rockstardev
ca25eedfbc Extracting common layout for Error Pages 2021-01-24 13:33:34 -06:00
rockstardev
1627c05224 Fixing typos and grammar
Co-authored-by: Umar Bolatov <bolatovumar@gmail.com>
2021-01-24 13:19:26 -06:00
rockstardev
f65ca04507 Http error 502, Miles page 2021-01-24 13:19:26 -06:00
rockstardev
a662b6ef6a Http error 417, page for Pavlenex 2021-01-24 13:19:26 -06:00
Dennis Reimann
bd6d38b3b0 Tests: Move extension method 2021-01-22 12:57:46 +01:00
nicolas.dorier
7147dadb2a Remove dependency to DBriize 2021-01-19 17:19:32 +09:00
Kukks
79e8ce9226 Improve test on Greenfield Inoice metadata update 2021-01-18 14:24:38 +01:00
rockstardev
89857ca77c Merge pull request #2202 from btcpayserver/fix/error-pages
Improving styling of error pages in preparation for adding new ones
2021-01-17 10:10:02 -06:00
rockstardev
5ce60be78a Update BTCPayServer/Views/Shared/_BTCPaySupporters.cshtml
Co-authored-by: d11n <mail@dennisreimann.de>
2021-01-17 10:09:50 -06:00
nicolas.dorier
600cc20423 changelog 2021-01-17 21:50:02 +09:00
nicolas.dorier
987d09041d bump 2021-01-17 21:45:54 +09:00
nicolas.dorier
df52d01a1d Revert "GreenField API: Configure Store Lightning Payment Method"
This reverts commit b40095f603.
2021-01-17 21:40:16 +09:00
nicolas.dorier
07de4af581 Revert "Apply suggestions from code review"
This reverts commit 48b2e682bf.
2021-01-17 21:39:20 +09:00
Andrew Camilleri
95c50dcc0d Changelog and new release (#2204)
* Changelog and new release

* Update Changelog.md
2021-01-17 19:14:36 +09:00
Kukks
71a192d0ba Fix SQLite COnnection string setter 2021-01-17 10:02:59 +01:00
rockstardev
9acb8c2ba2 Restoring project name in partial 2021-01-16 16:26:35 -06:00
rockstardev
307f7b6bd7 Improving styling of error pages in preparation for adding new ones 2021-01-16 14:55:31 -06:00
Dennis Reimann
83dd80ae86 Prevent access to wallet features if derivation scheme isn't set (#2196) 2021-01-16 19:48:05 +09:00
Kukks
ccfa85b5e0 fix compile errors 2021-01-16 09:22:07 +01:00
Salie Hendricks
e59821caa1 GetInvoiceRaw used its own dbContext while calling code used a different one which causes the calling context not to track changes - thus nothing is saved. (#2197)
Co-authored-by: Salie Hendricks <salie@safarinow.com>
2021-01-16 09:00:19 +01:00
Andrew Camilleri
2215e5e069 Merge pull request #2194 from btcpayserver/gf/ln-payment-emthod
GreenField API: Configure Store Lightning Payment Method
2021-01-14 10:07:00 +01:00
Andrew Camilleri
48b2e682bf Apply suggestions from code review
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2021-01-14 09:19:16 +01:00
Dennis Reimann
5750da574a Add info on how to run the altcoin docker-compose (#2193) 2021-01-13 19:26:53 +01:00
Kukks
b40095f603 GreenField API: Configure Store Lightning Payment Method 2021-01-12 09:25:35 +01:00
nicolas.dorier
ec11585223 Update Changelog 2021-01-11 23:46:17 +09:00
nicolas.dorier
eedf189d44 Improve logs of the invoice text search import 2021-01-11 23:41:05 +09:00
nicolas.dorier
f027d36db8 Do not spam logs for inventory events 2021-01-11 23:33:21 +09:00
nicolas.dorier
0c79474e0c Do not index metadata 2021-01-11 23:32:32 +09:00
nicolas.dorier
610f9e2b22 Log text search migration progress 2021-01-11 23:06:59 +09:00
nicolas.dorier
4db4b28369 Fix crash if DerivationSchemeSettings.Parse parse a 2-of scheme 2021-01-11 22:47:32 +09:00
nicolas.dorier
18464f5cd5 bump 2021-01-11 19:12:48 +09:00
Nicolas Dorier
cf24c1307a Changelog 1.0.6.5 (#2192)
* Changelog 1.0.6.5

* Update Changelog.md

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>

Co-authored-by: Zaxounette <51208677+Zaxounette@users.noreply.github.com>
2021-01-11 19:12:00 +09:00
nicolas.dorier
35e870a8e7 Revert "Remove tests of chaincoin default rate provider (#2189)"
This reverts commit 56daf347b9.
2021-01-11 12:11:25 +09:00
Andrew Camilleri
01be74b219 Add Bitcoin Output descriptor support (#2169)
* Add Output descriptor support

* Fix exception message and fix Parse Deriv Scheme Settings when electrum

* fix test
2021-01-11 11:22:42 +09:00
Andrew Camilleri
b8da6847b9 Plugins flexibility PR (#2129)
* Plugins flexibility PR

* Makes the BTCPayServerOptions.LoadArgs async to support Plugin hooks and actions
* relax the private set modifiers in the BTCPayNetwork
* Separate IPluginHookService from PluginService to reduce dependencies on DI
* fix some small bugs around image path
* Fix bug with new dbreeze migration where data dir was incorrect

* Update BTCPayServer/Plugins/PluginHookService.cs

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>

* Update BTCPayServer/Plugins/PluginHookService.cs

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>

Co-authored-by: rockstardev <5191402+rockstardev@users.noreply.github.com>
2021-01-07 14:49:53 +01:00
nicolas.dorier
e2e37a0db4 Make sure that the invoice currency is normalized to uppercase (Fix #2146) 2021-01-07 22:30:57 +09:00
nicolas.dorier
56daf347b9 Remove tests of chaincoin default rate provider (#2189) 2021-01-07 22:11:14 +09:00
Andrew Camilleri
5098f63175 Merge pull request #2188 from dennisreimann/patch-1
UI: Wrap notification text
2021-01-07 14:01:21 +01:00
Dennis Reimann
d9b12a6927 UI: Wrap notification text
Fixes #2187.
2021-01-07 13:58:23 +01:00
Umar Bolatov
fe4ffcfc62 Update notification dropdown styling (#2167)
* Update notification dropdown strings

* Update notification dropdown styling

address #2131

* Update border color

* Update notification hover color

* Revert "was confirmed paid" to "has been paid" change

* Move styles to site.css

* Update style="border-bottom: 0;" to border-bottom-0

* Update heading

* Add icon sprite

* Add default icon styles

* Use "currentColor" instead of specific color for icon

* Update icon color definition

* Adjust dropdown position

* Update h4 to h5
2021-01-07 18:07:42 +09:00
Andrew Camilleri
64a68b95b0 Add API Key QR scan + click to reveal api key in List view (#2174) 2021-01-07 17:56:35 +09:00
Andrew Camilleri
58d01738ab More Options refactoring (#2179)
* More Options refactoring

Continues refactoring config classes to use the propert Options pattern where possible.
DataDirectories and DatabaseOptions are now configured the Options pattern and the BTCPayOptions is now moved alongside the other config setup

* Move COnfigure logic for Options to the Startup
2021-01-06 23:51:13 +09:00
Nicolas Dorier
a18dae6d04 Merge pull request #2185 from btcpayserver/fix/deleting-dbriize
Deleting DBriize database folder if present
2021-01-06 10:59:24 +09:00
nicolas.dorier
b2959e583a Use default color for label if the color of a label is not set 2021-01-05 16:33:24 +09:00
Nicolas Dorier
a38a4d6f69 Merge pull request #2180 from btcpayserver/attempt-onion-modal-fix
Do not tell tor browser to redirect if invoice is loaded as a modal
2021-01-05 14:04:14 +09:00
nicolas.dorier
0a6ea59254 Rewrite the condition to show Onion-Location in a more readable way 2021-01-05 13:44:08 +09:00
rockstardev
04984a51e6 Fixing bug when MigratedInvoiceTextSearchPages is null 2021-01-04 22:40:50 -06:00
rockstardev
8947f13dbe Deleting legacy DBriize database if present 2021-01-04 22:35:00 -06:00
dependabot[bot]
c0549d872c Bump HtmlSanitizer from 4.0.217 to 5.0.372 in /BTCPayServer (#2183)
Bumps [HtmlSanitizer](https://github.com/mganss/HtmlSanitizer) from 4.0.217 to 5.0.372.
- [Release notes](https://github.com/mganss/HtmlSanitizer/releases)
- [Commits](https://github.com/mganss/HtmlSanitizer/compare/v4.0.217...v5.0.372)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-01-05 13:22:39 +09:00
Pavlenex
adcd5f0737 Update year (#2184) 2021-01-05 12:38:30 +09:00
Nicolas Dorier
0929857b12 Attempt to solve webhooks disappearing (#2178) 2021-01-05 12:38:12 +09:00
Kukks
7d21b39534 Do not tell browser to redirect if invoice is loaded as a modal
fixes #2089
2021-01-03 14:29:19 +01:00
184 changed files with 3975 additions and 1586 deletions

View File

@@ -1,6 +0,0 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Build and pack plugins" type="CompoundRunConfigurationType">
<toRun name="Pack Test Plugin" type="DotNetProject" />
<method v="2" />
</configuration>
</component>

View File

@@ -0,0 +1,12 @@
namespace BTCPayServer.Configuration
{
public class DataDirectories
{
public string DataDir { get; set; }
public string PluginDir { get; set; }
public string TempStorageDir { get; set; }
public string StorageDir { get; set; }
}
}

View File

@@ -3,6 +3,7 @@ using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.Options;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations.Operations;
@@ -10,10 +11,10 @@ namespace BTCPayServer.Abstractions.Contracts
{
public abstract class BaseDbContextFactory<T> where T: DbContext
{
private readonly DatabaseOptions _options;
private readonly IOptions<DatabaseOptions> _options;
private readonly string _schemaPrefix;
public BaseDbContextFactory(DatabaseOptions options, string schemaPrefix)
public BaseDbContextFactory(IOptions<DatabaseOptions> options, string schemaPrefix)
{
_options = options;
_schemaPrefix = schemaPrefix;
@@ -65,10 +66,10 @@ namespace BTCPayServer.Abstractions.Contracts
public void ConfigureBuilder(DbContextOptionsBuilder builder)
{
switch (_options.DatabaseType)
switch (_options.Value.DatabaseType)
{
case DatabaseType.Sqlite:
builder.UseSqlite(_options.ConnectionString, o =>
builder.UseSqlite(_options.Value.ConnectionString, o =>
{
if (!string.IsNullOrEmpty(_schemaPrefix))
{
@@ -78,7 +79,7 @@ namespace BTCPayServer.Abstractions.Contracts
break;
case DatabaseType.Postgres:
builder
.UseNpgsql(_options.ConnectionString, o =>
.UseNpgsql(_options.Value.ConnectionString, o =>
{
o.EnableRetryOnFailure(10);
if (!string.IsNullOrEmpty(_schemaPrefix))
@@ -89,7 +90,7 @@ namespace BTCPayServer.Abstractions.Contracts
.ReplaceService<IMigrationsSqlGenerator, CustomNpgsqlMigrationsSqlGenerator>();
break;
case DatabaseType.MySQL:
builder.UseMySql(_options.ConnectionString, o =>
builder.UseMySql(_options.Value.ConnectionString, o =>
{
o.EnableRetryOnFailure(10);

View File

@@ -21,7 +21,6 @@ namespace BTCPayServer.Abstractions.Models
public abstract string Description { get; }
public bool SystemPlugin { get; set; }
public bool SystemExtension { get; set; }
public virtual IBTCPayServerPlugin.PluginDependency[] Dependencies { get; } = Array.Empty<IBTCPayServerPlugin.PluginDependency>();
public virtual void Execute(IApplicationBuilder applicationBuilder,

View File

@@ -1,43 +1,7 @@
using System;
using System.IO;
using System.Runtime.InteropServices.ComTypes;
using Microsoft.Extensions.Configuration;
namespace BTCPayServer.Abstractions.Models
{
public class DatabaseOptions
{
public DatabaseOptions(IConfiguration conf, string dataDir)
{
var postgresConnectionString = conf["postgres"];
var mySQLConnectionString = conf["mysql"];
var sqliteFileName = conf["sqlitefile"];
if (!string.IsNullOrEmpty(postgresConnectionString))
{
DatabaseType = DatabaseType.Postgres;
ConnectionString = postgresConnectionString;
}
else if (!string.IsNullOrEmpty(mySQLConnectionString))
{
DatabaseType = DatabaseType.MySQL;
ConnectionString = mySQLConnectionString;
}
else if (!string.IsNullOrEmpty(sqliteFileName))
{
var connStr = "Data Source=" + (Path.IsPathRooted(sqliteFileName)
? sqliteFileName
: Path.Combine(dataDir, sqliteFileName));
DatabaseType = DatabaseType.Sqlite;
ConnectionString = sqliteFileName;
}
else
{
throw new InvalidOperationException("No database option was configured.");
}
}
public DatabaseType DatabaseType { get; set; }
public string ConnectionString { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace BTCPayServer.Abstractions.Services
{
public abstract class PluginAction<T>:IPluginHookAction
{
public string Hook { get; }
public abstract string Hook { get; }
public Task Execute(object args)
{
return Execute(args is T args1 ? args1 : default);

View File

@@ -5,7 +5,8 @@ namespace BTCPayServer.Abstractions.Services
{
public abstract class PluginHookFilter<T>:IPluginHookFilter
{
public string Hook { get; }
public abstract string Hook { get; }
public Task<object> Execute(object args)
{
return Execute(args is T args1 ? args1 : default).ContinueWith(task => task.Result as object);

View File

@@ -27,7 +27,7 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="NBitcoin" Version="5.0.60" />
<PackageReference Include="NBitcoin" Version="5.0.73" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

View File

@@ -17,7 +17,7 @@ namespace BTCPayServer.Client.Models
Description = description;
Expiry = expiry;
}
[JsonConverter(typeof(LightMoneyJsonConverter))]
[JsonConverter(typeof(BTCPayServer.Client.JsonConverters.LightMoneyJsonConverter))]
public LightMoney Amount { get; set; }
public string Description { get; set; }
[JsonConverter(typeof(JsonConverters.TimeSpanJsonConverter.Seconds))]

View File

@@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Argoneum",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://chainz.cryptoid.info/agm/tx.dws?{0}"
: "https://chainz.cryptoid.info/agm-test/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
@@ -23,7 +23,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/argoneum.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("421'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("421'")
: new KeyPath("1'")
});
}

View File

@@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BGold",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoingold",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/btg.svg",
LightningImagePath = "imlegacy/btg-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BPlus",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bplus-fix-it",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/xbc.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcore",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcore",
DefaultRateRules = new[]
@@ -23,7 +23,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/bitcore.svg",
LightningImagePath = "imlegacy/bitcore-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
});
}
}

View File

@@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Chaincoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
@@ -24,7 +24,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/chaincoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("711'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("711'")
: new KeyPath("1'")
});
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dash",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://insight.dash.org/insight/tx/{0}"
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
@@ -25,7 +25,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/dash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("5'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("5'")
: new KeyPath("1'")
});
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dogecoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "dogecoin",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/dogecoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Feathercoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "feathercoin",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/feathercoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
});
}
}

View File

@@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Groestlcoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
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,
@@ -24,7 +24,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true
});

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Litecoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
@@ -26,9 +26,9 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/litecoin.svg",
LightningImagePath = "imlegacy/litecoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy },

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Monacoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
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,
UriScheme = "monacoin",
DefaultRateRules = new[]
@@ -23,7 +23,7 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/monacoin.png",
LightningImagePath = "imlegacy/mona-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "MonetaryUnit",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "monetaryunit",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/monetaryunit.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Polis",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "polis",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/polis.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Ufo",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "ufo",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/ufo.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
});
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Viacoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "viacoin",
DefaultRateRules = new[]
@@ -22,7 +22,7 @@ namespace BTCPayServer
},
CryptoImagePath = "imlegacy/viacoin.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
});
}
}

View File

@@ -13,20 +13,20 @@ namespace BTCPayServer
DisplayName = "Ethereum",
DefaultRateRules = new[] {"ETH_X = ETH_BTC * BTC_X", "ETH_BTC = kraken(ETH_BTC)"},
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
NetworkType == ChainName.Mainnet
? "https://etherscan.io/address/{0}"
: "https://ropsten.etherscan.io/address/{0}",
CryptoImagePath = "/imlegacy/eth.png",
ShowSyncSummary = true,
CoinType = NetworkType == NetworkType.Mainnet? 60 : 1,
ChainId = NetworkType == NetworkType.Mainnet ? 1 : 3,
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
Divisibility = 18,
});
}
public void InitERC20()
{
if (NetworkType != NetworkType.Mainnet)
if (NetworkType != ChainName.Mainnet)
{
Add(new ERC20BTCPayNetwork()
{
@@ -60,13 +60,13 @@ namespace BTCPayServer
"USDT20_BTC = bitfinex(UST_BTC)",
},
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
NetworkType == ChainName.Mainnet
? "https://etherscan.io/address/{0}#tokentxns"
: "https://ropsten.etherscan.io/address/{0}#tokentxns",
CryptoImagePath = "/imlegacy/liquid-tether.svg",
ShowSyncSummary = false,
CoinType = NetworkType == NetworkType.Mainnet? 60 : 1,
ChainId = NetworkType == NetworkType.Mainnet ? 1 : 3,
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
SmartContractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
Divisibility = 6
});

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
AssetId = NetworkType == NetworkType.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
AssetId = NetworkType == ChainName.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
CryptoCode = "LBTC",
NetworkCryptoCode = "LBTC",
DisplayName = "Liquid Bitcoin",
@@ -22,12 +22,12 @@ namespace BTCPayServer
"LBTC_X = LBTC_BTC * BTC_X",
"LBTC_BTC = 1",
},
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true
});
}

View File

@@ -21,12 +21,12 @@ namespace BTCPayServer
},
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/liquid-tether.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
@@ -45,12 +45,12 @@ namespace BTCPayServer
Divisibility = 2,
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
DisplayName = "Ethiopian Birr",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/etb.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});
@@ -68,12 +68,12 @@ namespace BTCPayServer
},
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
DisplayName = "Liquid CAD",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "liquidnetwork",
CryptoImagePath = "imlegacy/lcad.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer
DisplayName = "Monero",
Divisibility = 12,
BlockExplorerLink =
NetworkType == NetworkType.Mainnet
NetworkType == ChainName.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
DefaultRateRules = new[]

View File

@@ -18,25 +18,29 @@ namespace BTCPayServer
{
static BTCPayDefaultSettings()
{
_Settings = new Dictionary<NetworkType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
_Settings = new Dictionary<ChainName, BTCPayDefaultSettings>();
}
static readonly Dictionary<ChainName, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(ChainName chainType)
{
if (_Settings.TryGetValue(chainType, out var v))
return v;
lock (_Settings)
{
if (_Settings.TryGetValue(chainType, out v))
return v;
var settings = new BTCPayDefaultSettings();
_Settings.Add(chainType, settings);
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
settings.DefaultPluginDirectory =
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
chainType == NetworkType.Regtest ? 23002 :
chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString()));
settings.DefaultPort = (chainType == ChainName.Mainnet ? 23000 :
chainType == ChainName.Regtest ? 23002
: 23001);
}
}
static readonly Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
{
return _Settings[chainType];
}
@@ -53,7 +57,7 @@ namespace BTCPayServer
public bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; internal set; }
public KeyPath CoinType { get; set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
@@ -132,7 +136,7 @@ namespace BTCPayServer
{
private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; internal set; }
public string CryptoCode { get; set; }
public string BlockExplorerLink
{
@@ -161,7 +165,7 @@ namespace BTCPayServer
}
public string CryptoImagePath { get; set; }
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
public string[] DefaultRateRules { get; set; } = Array.Empty<string>();
public override string ToString()
{
return CryptoCode;

View File

@@ -13,17 +13,19 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/tx/{0}" : "https://blockstream.info/testnet/tx/{0}",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/tx/{0}" :
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/"
: "https://blockstream.info/testnet/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
UriScheme = "bitcoin",
CryptoImagePath = "imlegacy/bitcoin.svg",
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == NetworkType.Mainnet
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub

View File

@@ -8,8 +8,7 @@ namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
readonly Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
protected readonly Dictionary<string, BTCPayNetworkBase> _Networks = new Dictionary<string, BTCPayNetworkBase>();
private readonly NBXplorerNetworkProvider _NBXplorerNetworkProvider;
public NBXplorerNetworkProvider NBXplorerNetworkProvider
@@ -36,8 +35,8 @@ namespace BTCPayServer
}
public NetworkType NetworkType { get; private set; }
public BTCPayNetworkProvider(NetworkType networkType)
public ChainName NetworkType { get; private set; }
public BTCPayNetworkProvider(ChainName networkType)
{
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
NetworkType = networkType;

View File

@@ -1,10 +1,10 @@
<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="NBXplorer.Client" Version="3.0.19" />
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>

View File

@@ -1,12 +1,13 @@
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Data
{
public class ApplicationDbContextFactory : BaseDbContextFactory<ApplicationDbContext>
{
public ApplicationDbContextFactory(DatabaseOptions options) : base(options, "")
public ApplicationDbContextFactory(IOptions<DatabaseOptions> options) : base(options, "")
{
}

View File

@@ -11,13 +11,16 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
migrationBuilder.DropTable(
name: "RefundAddresses");
migrationBuilder.AddColumn<string>(
name: "CurrentRefundId",
table: "Invoices",
nullable: true);
nullable: true,
maxLength: maxLength);
migrationBuilder.CreateTable(
name: "Notifications",
@@ -73,7 +76,7 @@ namespace BTCPayServer.Migrations
PullPaymentDataId = table.Column<string>(maxLength: 30, nullable: true),
State = table.Column<string>(maxLength: 20, nullable: false),
PaymentMethodId = table.Column<string>(maxLength: 20, nullable: false),
Destination = table.Column<string>(nullable: true),
Destination = table.Column<string>(maxLength: maxLength, nullable: true),
Blob = table.Column<byte[]>(nullable: true),
Proof = table.Column<byte[]>(nullable: true)
},
@@ -92,8 +95,8 @@ namespace BTCPayServer.Migrations
name: "Refunds",
columns: table => new
{
InvoiceDataId = table.Column<string>(nullable: false),
PullPaymentDataId = table.Column<string>(nullable: false)
InvoiceDataId = table.Column<string>(maxLength: maxLength, nullable: false),
PullPaymentDataId = table.Column<string>(maxLength: maxLength, nullable: false)
},
constraints: table =>
{

View File

@@ -27,8 +27,8 @@ namespace BTCPayServer.Migrations
name: "StoreWebhooks",
columns: table => new
{
StoreId = table.Column<string>(nullable: false),
WebhookId = table.Column<string>(nullable: false)
StoreId = table.Column<string>(maxLength: 50, nullable: false),
WebhookId = table.Column<string>(maxLength: 25, nullable: false)
},
constraints: table =>
{
@@ -71,8 +71,8 @@ namespace BTCPayServer.Migrations
name: "InvoiceWebhookDeliveries",
columns: table => new
{
InvoiceId = table.Column<string>(nullable: false),
DeliveryId = table.Column<string>(nullable: false)
InvoiceId = table.Column<string>(maxLength: 255, nullable: false),
DeliveryId = table.Column<string>(maxLength: 100, nullable: false)
},
constraints: table =>
{

View File

@@ -10,7 +10,17 @@ namespace BTCPayServer.Migrations
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
if (!migrationBuilder.IsSqlite())
{
migrationBuilder.AlterColumn<string>(
name: "OrderId",
table: "Invoices",
maxLength: 100,
nullable: true,
oldClrType: typeof(string));
}
migrationBuilder.CreateIndex(
name: "IX_Invoices_OrderId",
table: "Invoices",
column: "OrderId");

View File

@@ -21,8 +21,8 @@ namespace BTCPayServer.Migrations
.Annotation("MySql:ValueGeneratedOnAdd", true)
.Annotation("Sqlite:Autoincrement", true),
// eof manually added
InvoiceDataId = table.Column<string>(nullable: true),
Value = table.Column<string>(nullable: true)
InvoiceDataId = table.Column<string>(maxLength: 255, nullable: true),
Value = table.Column<string>(maxLength: 512, nullable: true)
},
constraints: table =>
{

View File

@@ -43,6 +43,7 @@ namespace BTCPayServer.PluginPacker
}
ZipFile.CreateFromDirectory(directory, outputFile + ".btcpay", CompressionLevel.Optimal, false);
File.WriteAllText(outputFile + ".btcpay.json", json);
Console.WriteLine($"Created {outputFile}.btcpay at {directory}");
}
private static Type[] GetAllExtensionTypesFromAssembly(Assembly assembly)

View File

@@ -1,9 +1,8 @@
using System.Reflection;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Plugins.Test.Migrations;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Options;
namespace BTCPayServer.Plugins.Test
{
@@ -23,7 +22,7 @@ namespace BTCPayServer.Plugins.Test
public class TestPluginDbContextFactory : BaseDbContextFactory<TestPluginDbContext>
{
public TestPluginDbContextFactory(DatabaseOptions options) : base(options, "BTCPayServer.Plugins.Test")
public TestPluginDbContextFactory(IOptions<DatabaseOptions> options) : base(options, "BTCPayServer.Plugins.Test")
{
}

View File

@@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="5.0.60" />
<PackageReference Include="NBitcoin" Version="5.0.73" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
</ItemGroup>

View File

@@ -44,6 +44,7 @@ using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
using NBitcoin.Scripting.Parser;
using NBitpayClient;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
@@ -478,21 +479,21 @@ namespace BTCPayServer.Tests
//there should be three now
invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("BTC", currencyDropdownButton.Text);
currencyDropdownButton.Click();
var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
Assert.Equal(3, elements.Count);
elements.Single(element => element.Text.Contains("LTC")).Click();
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("LTC", currencyDropdownButton.Text);
currencyDropdownButton.Click();
elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
elements.Single(element => element.Text.Contains("Lightning")).Click();
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("Lightning", currencyDropdownButton.Text);
s.Driver.Quit();
@@ -871,7 +872,7 @@ normal:
{
#pragma warning disable CS0618
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
@@ -951,9 +952,9 @@ normal:
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme()
{
var testnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var testnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
@@ -1040,6 +1041,99 @@ normal:
Assert.Equal(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
parsed.ToString());
//let's test output descriptor parsing support
//we don't support every descriptor, only the ones which represent an HD wallet with stndard derivation paths
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))"));
//let's see what we actually support now
//standard legacy hd wallet
var parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal(KeyPath.Parse("44'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//masterfingerprint and key path are optional
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh([d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//a master fingerprint must always be present if youre providing rooted path
Assert.Throws<ParsingException>(() => mainnetParser.ParseOutputDescriptor("pkh([44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//but a different deriv path from standard (0/*) is not supported
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
//p2sh-segwit hd wallet
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(wpkh([d34db33f/49'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(KeyPath.Parse("49'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[p2sh]",parsedDescriptor.Item1.ToString() );
//segwit hd wallet
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"wpkh([d34db33f/84'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal(KeyPath.Parse("84'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",parsedDescriptor.Item1.ToString() );
//multisig tests
//legacy
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(multi(1,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
var strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner);
Assert.True(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]-[keeporder]",parsedDescriptor.Item1.ToString() );
//segwit
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(parsedDescriptor.Item1).Inner);
Assert.False(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]",parsedDescriptor.Item1.ToString() );
//segwit-p2sh
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner).Inner);
Assert.False(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]-[p2sh]",parsedDescriptor.Item1.ToString() );
//sorted
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(sortedmulti(1,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
}
}
}

View File

@@ -35,7 +35,7 @@ namespace BTCPayServer.Tests
InitialData = new[] {new KeyValuePair<string, string>("chains", "usdt20"),}
})
});
var networkProvider = config.ConfigureNetworkProvider();
Assert.NotNull(networkProvider.GetNetwork("ETH"));
Assert.NotNull(networkProvider.GetNetwork("USDT20"));
@@ -60,7 +60,7 @@ namespace BTCPayServer.Tests
web3Link.Click();
s.Driver.FindElement(By.Id("Web3ProviderUrl")).SendKeys("https://ropsten-rpc.linkpool.io");
s.Driver.FindElement(By.Id("saveButton")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
TestUtils.Eventually(() =>
{
s.Driver.Navigate().Refresh();
@@ -76,17 +76,17 @@ namespace BTCPayServer.Tests
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("ModifyUSDT20")).Click();
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
var invoiceId = s.CreateInvoice(store.storeName, 10);
s.GoToInvoiceCheckout(invoiceId);
var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
Assert.Contains("ETH", currencyDropdownButton.Text);
s.Driver.FindElement(By.Id("copy-tab")).Click();

View File

@@ -65,7 +65,7 @@ namespace BTCPayServer.Tests
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.SetCheckbox(s, "btcpay.user.canviewprofile", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
//this api key has access to everything
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile);
@@ -74,7 +74,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
Policies.CanModifyServerSettings);
@@ -82,7 +82,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
Policies.CanModifyStoreSettings);
@@ -94,13 +94,13 @@ namespace BTCPayServer.Tests
var storeId = option.GetAttribute("value");
option.Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.FindElement(By.Id("Generate")).Click();
var noPermissionsApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
@@ -188,7 +188,7 @@ namespace BTCPayServer.Tests
checkbox.Click();
}
s.Driver.FindElement(By.Id("Generate")).Click();
var allAPIKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
}

View File

@@ -23,7 +23,7 @@
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
<PackageReference Include="Selenium.Support" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="87.0.4280.8800" />
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="88.0.4324.9600" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
<PrivateAssets>all</PrivateAssets>

View File

@@ -91,7 +91,7 @@ namespace BTCPayServer.Tests
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
string chain = NBXplorerDefaultSettings.GetFolderName(NetworkType.Regtest);
string chain = NBXplorerDefaultSettings.GetFolderName(ChainName.Regtest);
string chainDirectory = Path.Combine(_Directory, chain);
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);

View File

@@ -1,13 +1,10 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Lightning;
using BTCPayServer.Payments;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Stores;
using NBitcoin;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
@@ -36,7 +33,7 @@ namespace BTCPayServer.Tests
s.AddDerivationScheme("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
s.Driver.FindElement(By.Name("command")).ForceClick();
s.Driver.FindElement(By.Name("command")).Click();
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
@@ -119,7 +116,7 @@ namespace BTCPayServer.Tests
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
var command = s.Driver.FindElement(By.Name("command"));
command.ForceClick();
command.Click();
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
s.GoToInvoiceCheckout(invoiceId);
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
@@ -166,30 +163,4 @@ namespace BTCPayServer.Tests
}
}
}
public static class SeleniumExtensions
{
/// <summary>
/// Utility method to wait until timeout for element to be present (optionally displayed)
/// </summary>
/// <param name="context">Wait context</param>
/// <param name="by">How we search for element</param>
/// <param name="displayed">Flag to wait for element to be displayed or just present</param>
/// <param name="timeout">How long to wait for element to be present/displayed</param>
/// <returns>Element we were waiting for</returns>
public static IWebElement WaitForElement(this IWebDriver context, By by, bool displayed = true, uint timeout = 3)
{
var wait = new DefaultWait<IWebDriver>(context);
wait.Timeout = TimeSpan.FromSeconds(timeout);
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
return wait.Until(ctx =>
{
var elem = ctx.FindElement(by);
if (displayed && !elem.Displayed)
return null;
return elem;
});
}
}
}

View File

@@ -13,24 +13,17 @@ namespace BTCPayServer.Tests
{
public static class Extensions
{
private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
public static string ToJson(this object o)
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
public static string ToJson(this object o) => JsonConvert.SerializeObject(o, Formatting.None, JsonSettings);
public static void LogIn(this SeleniumTester s, string email)
{
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
return res;
}
public static void ScrollTo(this IWebDriver driver, By by)
{
var element = driver.FindElement(by);
}
/// <summary>
/// Sometimes the chrome driver is fucked up and we need some magic to click on the element.
/// </summary>
/// <param name="element"></param>
public static void ForceClick(this IWebElement element)
{
element.SendKeys(Keys.Return);
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
s.Driver.AssertNoError();
}
public static void AssertNoError(this IWebDriver driver)
{
try
@@ -57,14 +50,18 @@ namespace BTCPayServer.Tests
builder.AppendLine($"[{entry.Level}]: {entry.Message}");
}
}
catch { }
builder.AppendLine($"---------");
catch
{
// ignored
}
builder.AppendLine("---------");
}
Logs.Tester.LogInformation(builder.ToString());
builder = new StringBuilder();
builder.AppendLine($"Selenium [Sources]:");
builder.AppendLine("Selenium [Sources]:");
builder.AppendLine(driver.PageSource);
builder.AppendLine($"---------");
builder.AppendLine("---------");
Logs.Tester.LogInformation(builder.ToString());
throw;
}

View File

@@ -994,6 +994,12 @@ namespace BTCPayServer.Tests
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
//also test the the metadata actually got saved
invoice = await client.GetInvoice(user.StoreId, newInvoice.Id);
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
//archive
await AssertHttpError(403, async () =>
{
@@ -1080,7 +1086,7 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = 60 * 2 * 1000)]
[Fact(Timeout = 60 * 20 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanUseLightningAPI()
@@ -1098,7 +1104,7 @@ namespace BTCPayServer.Tests
merchant.GrantAccess(true);
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
// The default client is using charge, so we should not be able to query channels
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);

View File

@@ -25,6 +25,7 @@ using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.UI;
using Xunit;
using Xunit.Abstractions;
@@ -239,6 +240,7 @@ namespace BTCPayServer.Tests
s.SetCheckbox(s, "PayJoinEnabled", true);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
var sender = s.CreateNewStore();
var senderSeed = s.GenerateWallet("BTC", "", true, true, format);
var senderWalletId = new WalletId(sender.storeId, "BTC");
@@ -257,16 +259,17 @@ namespace BTCPayServer.Tests
s.Driver.SwitchTo().Alert().Accept();
Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinBIP21"))
.GetAttribute("value")));
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
s.Driver.FindElement(By.Id("SendMenu")).Click();
var nbxSeedButton = s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]"));
new WebDriverWait(s.Driver, SeleniumTester.ImplicitWait).Until(d=> nbxSeedButton.Enabled);
nbxSeedButton.Click();
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
return Task.CompletedTask;
});
//no funds in receiver wallet to do payjoin
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Warning);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Warning);
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
@@ -294,15 +297,14 @@ namespace BTCPayServer.Tests
.GetAttribute("value")));
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear();
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2");
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
var txId = await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
return Task.CompletedTask;
});
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Success);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await invoiceRepository.GetInvoice(invoiceId);

View File

@@ -57,10 +57,29 @@ The `./docker-lightning-channel-teardown.sh` script closes any existing lightnin
## FAQ
`docker-compose up dev` failed or tests are not passing, what should I do?
### `docker-compose up dev` failed or tests are not passing, what should I do?
1. Run `docker-compose down --v` (this will reset your test environment)
2. Run `docker-compose pull` (this will ensure you have the lastest images)
3. Run again with `docker-compose up dev`
### How to run the Altcoin environment?
`docker-compose -f docker-compose.altcoins.yml up dev`
If you still have issues, try to restart docker.
### How to run the Selenium test with a browser?
Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser.
To switch back to headless mode (recommended) you can run `dotnet user-secrets remove RunSeleniumInBrowser`.
### Session not created: This version of ChromeDriver only supports Chrome version 88
When you run tests for selenium, you may end up with this error.
This happen when we update the selenium packages on BTCPay Server while you did not update your chrome version.
If you want to use a older chrome driver on [this page](https://chromedriver.chromium.org/downloads) then point to it with
`dotnet user-secrets set ChromeDriverDirectory "path/to/the/driver/directory"`

View File

@@ -3,23 +3,20 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.CLightning;
using BTCPayServer.Models;
using BTCPayServer.Services;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Manage;
using BTCPayServer.Views.Server;
using BTCPayServer.Views.Stores;
using BTCPayServer.Views.Wallets;
using Microsoft.Extensions.Configuration;
using NBitcoin;
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
using OpenQA.Selenium.Interactions;
using Xunit;
namespace BTCPayServer.Tests
@@ -28,64 +25,68 @@ namespace BTCPayServer.Tests
{
public IWebDriver Driver { get; set; }
public ServerTester Server { get; set; }
public WalletId WalletId { get; set; }
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false)
{
var server = ServerTester.Create(scope, newDb);
return new SeleniumTester()
{
Server = server
};
}
public string StoreId { get; set; }
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) =>
new SeleniumTester { Server = ServerTester.Create(scope, newDb) };
public async Task StartAsync()
{
await Server.StartAsync();
ChromeOptions options = new ChromeOptions();
var windowSize = (Width: 1200, Height: 1000);
var builder = new ConfigurationBuilder();
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
var config = builder.Build();
// Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser
var runInBrowser = config["RunSeleniumInBrowser"] == "true";
// Reset this using `dotnet user-secrets remove RunSeleniumInBrowser`
var chromeDriverPath = config["ChromeDriverDirectory"] ??
(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory());
var options = new ChromeOptions();
if (Server.PayTester.InContainer)
{
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
options.AddArgument("no-sandbox");
}
var isDebug = !Server.PayTester.InContainer;
if (!isDebug)
if (!runInBrowser)
{
options.AddArguments("headless"); // Comment to view browser
options.AddArguments("window-size=1200x1000"); // Comment to view browser
options.AddArguments("headless");
}
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
options.AddArgument("shm-size=2g");
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
if (isDebug)
Driver = new ChromeDriver(chromeDriverPath, options);
if (runInBrowser)
{
//when running locally, depending on your resolution, the website may go into mobile responsive mode and screw with navigation of tests
// ensure maximized window size
Driver.Manage().Window.Maximize();
}
Logs.Tester.LogInformation("Selenium: Using chrome driver");
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}");
Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}");
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
GoToRegister();
Driver.AssertNoError();
}
internal IWebElement AssertHappyMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
{
using var cts = new CancellationTokenSource(20_000);
while (!cts.IsCancellationRequested)
{
var result = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).Where(el => el.Displayed);
if (result.Any())
return result.First();
Thread.Sleep(100);
}
Logs.Tester.LogInformation(this.Driver.PageSource);
Assert.True(false, $"Should have shown {severity} message");
return null;
var el = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).FirstOrDefault(e => e.Displayed);
if (el is null)
throw new NoSuchElementException($"Unable to find alert-{StatusMessageModel.ToString(severity)}");
return el;
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
@@ -93,8 +94,9 @@ namespace BTCPayServer.Tests
public void GoToRegister()
{
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
Driver.Navigate().GoToUrl(Link("/register"));
}
public string RegisterNewUser(bool isAdmin = false)
{
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
@@ -111,34 +113,55 @@ namespace BTCPayServer.Tests
public (string storeName, string storeId) CreateNewStore()
{
var usr = "Store" + RandomUtils.GetUInt64().ToString();
Driver.FindElement(By.Id("Stores")).Click();
Driver.FindElement(By.Id("CreateStore")).Click();
Driver.FindElement(By.Id("Name")).SendKeys(usr);
var name = "Store" + RandomUtils.GetUInt64();
Driver.FindElement(By.Id("Name")).SendKeys(name);
Driver.FindElement(By.Id("Create")).Click();
StoreId = Driver.FindElement(By.Id("Id")).GetAttribute("value");
return (usr, StoreId);
return (name, StoreId);
}
public string StoreId { get; set; }
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
Driver.FindElement(By.Id("import-from-btn")).ForceClick();
Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick();
Driver.WaitForElement(By.Id("ExistingMnemonic")).SendKeys(seed);
SetCheckbox(Driver.WaitForElement(By.Id("SavePrivateKeys")), privkeys);
SetCheckbox(Driver.WaitForElement(By.Id("ImportKeysToRPC")), importkeys);
Driver.WaitForElement(By.Id("ScriptPubKeyType")).Click();
Driver.WaitForElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
Logs.Tester.LogInformation("Trying to click btn-generate");
Driver.WaitForElement(By.Id("btn-generate")).ForceClick();
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
// Modify case
if (Driver.PageSource.Contains("id=\"change-wallet-link\""))
{
Driver.FindElement(By.Id("change-wallet-link")).Click();
}
if (string.IsNullOrEmpty(seed))
{
var option = privkeys ? "hotwallet" : "watchonly";
Logs.Tester.LogInformation($"Generating new seed ({option})");
Driver.FindElement(By.Id("generate-wallet-link")).Click();
Driver.FindElement(By.Id($"generate-{option}-link")).Click();
}
else
{
Logs.Tester.LogInformation("Progressing with existing seed");
Driver.FindElement(By.Id("import-wallet-options-link")).Click();
Driver.FindElement(By.Id("import-seed-link")).Click();
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
SetCheckbox(Driver.FindElement(By.Id("SavePrivateKeys")), privkeys);
}
Driver.FindElement(By.Id("ScriptPubKeyType")).Click();
Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
Driver.FindElement(By.Id("advanced-settings-button")).Click();
SetCheckbox(Driver.FindElement(By.Id("ImportKeysToRPC")), importkeys);
Driver.FindElement(By.Id("advanced-settings-button")).Click(); // close settings again , otherwise the button might not be clickable for Selenium
Logs.Tester.LogInformation("Trying to click Continue button");
Driver.FindElement(By.Id("Continue")).Click();
// Seed backup page
AssertHappyMessage();
FindAlertMessage();
if (string.IsNullOrEmpty(seed))
{
seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
}
// Confirm seed backup
Driver.FindElement(By.Id("confirm")).Click();
Driver.FindElement(By.Id("submit")).Click();
@@ -146,19 +169,21 @@ namespace BTCPayServer.Tests
WalletId = new WalletId(StoreId, cryptoCode);
return new Mnemonic(seed);
}
public WalletId WalletId { get; set; }
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
{
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
Driver.FindElement(By.Id("Continue")).ForceClick();
Driver.FindElement(By.Id("Confirm")).ForceClick();
AssertHappyMessage();
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
Driver.FindElement(By.Id("import-wallet-options-link")).Click();
Driver.FindElement(By.Id("import-xpub-link")).Click();
Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
Driver.FindElement(By.Id("Continue")).Click();
Driver.FindElement(By.Id("Confirm")).Click();
FindAlertMessage();
}
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
{
string connectionString = null;
string connectionString;
if (connectionType == LightningConnectionType.Charge)
connectionString = $"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
else if (connectionType == LightningConnectionType.CLightning)
@@ -168,16 +193,16 @@ namespace BTCPayServer.Tests
else
throw new NotSupportedException(connectionType.ToString());
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
Driver.FindElement(By.Id($"save")).ForceClick();
Driver.FindElement(By.Id($"save")).Click();
}
public void AddInternalLightningNode(string cryptoCode)
{
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
Driver.FindElement(By.Id($"internal-ln-node-setter")).ForceClick();
Driver.FindElement(By.Id($"save")).ForceClick();
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
Driver.FindElement(By.Id($"internal-ln-node-setter")).Click();
Driver.FindElement(By.Id($"save")).Click();
}
public void ClickOnAllSideMenus()
@@ -193,21 +218,23 @@ namespace BTCPayServer.Tests
}
}
public void Dispose()
{
if (Driver != null)
{
try
{
Driver.Close();
Driver.Quit();
}
catch { }
catch
{
// ignored
}
Driver.Dispose();
}
if (Server != null)
Server.Dispose();
Server?.Dispose();
}
internal void AssertNotFound()
@@ -241,6 +268,7 @@ namespace BTCPayServer.Tests
{
Driver.FindElement(By.Id("Stores")).Click();
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
if (storeNavPage != StoreNavPages.Index)
{
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
@@ -271,14 +299,7 @@ namespace BTCPayServer.Tests
public void SetCheckbox(SeleniumTester s, string checkboxId, bool value)
{
SetCheckbox(s.Driver.WaitForElement(By.Id(checkboxId)), value);
}
public void ScrollToElement(IWebElement element)
{
Actions actions = new Actions(Driver);
actions.MoveToElement(element);
actions.Perform();
SetCheckbox(s.Driver.FindElement(By.Id(checkboxId)), value);
}
public void GoToInvoices()
@@ -297,19 +318,13 @@ namespace BTCPayServer.Tests
public void GoToLogin()
{
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "Account/Login"));
}
public void GoToCreateInvoicePage()
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "/login"));
}
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var currencyEl = Driver.FindElement(By.Id("Currency"));
currencyEl.Clear();
@@ -318,7 +333,7 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
Driver.FindElement(By.Id("Create")).Click();
AssertHappyMessage();
FindAlertMessage();
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
var id = statusElement.Text.Split(" ")[1];
return id;
@@ -331,7 +346,7 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Id("generateButton")).Click();
var addressStr = Driver.FindElement(By.Id("address")).GetProperty("value");
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
for (int i = 0; i < coins; i++)
for (var i = 0; i < coins; i++)
{
await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination));
}
@@ -344,19 +359,15 @@ namespace BTCPayServer.Tests
.GetAttribute("href");
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
GoToWallet(walletId, WalletsNavPages.Send);
GoToWallet(walletId);
Driver.FindElement(By.Id("bip21parse")).Click();
Driver.SwitchTo().Alert().SendKeys(bip21);
Driver.SwitchTo().Alert().Accept();
Driver.ScrollTo(By.Id("SendMenu"));
Driver.FindElement(By.Id("SendMenu")).ForceClick();
Driver.FindElement(By.Id("SendMenu")).Click();
Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
}
private void CheckForJSErrors()
{
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
@@ -402,7 +413,6 @@ namespace BTCPayServer.Tests
{
Driver.FindElement(By.Id($"Server-{navPages}")).Click();
}
}
public void GoToInvoice(string id)

View File

@@ -3,37 +3,35 @@ using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client.Models;
using BTCPayServer.Data;
using BTCPayServer.Models;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Tests.Logging;
using BTCPayServer.Views.Manage;
using BTCPayServer.Views.Server;
using BTCPayServer.Views.Wallets;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
using NBitpayClient;
using Newtonsoft.Json.Linq;
using OpenQA.Selenium;
using OpenQA.Selenium.Support.Extensions;
using OpenQA.Selenium.Support.UI;
using Org.BouncyCastle.Ocsp;
using Renci.SshNet.Security.Cryptography;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
namespace BTCPayServer.Tests
{
[Trait("Selenium", "Selenium")]
public class ChromeTests
{
public const int TestTimeout = TestUtils.TestTimeout;
private const int TestTimeout = TestUtils.TestTimeout;
public ChromeTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
@@ -85,12 +83,51 @@ namespace BTCPayServer.Tests
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
s.Driver.FindElement(By.Id("delete")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Selenium", "Selenium")]
public async Task CanChangeUserMail()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var tester = s.Server;
var u1 = tester.NewAccount();
u1.GrantAccess();
await u1.MakeAdmin(false);
var u2 = tester.NewAccount();
u2.GrantAccess();
await u2.MakeAdmin(false);
s.GoToLogin();
s.Login(u1.RegisterDetails.Email, u1.RegisterDetails.Password);
s.GoToProfile(ManageNavPages.Index);
s.Driver.FindElement(By.Id("Email")).Clear();
s.Driver.FindElement(By.Id("Email")).SendKeys(u2.RegisterDetails.Email);
s.Driver.FindElement(By.Id("save")).Click();
s.FindAlertMessage(Abstractions.Models.StatusMessageModel.StatusSeverity.Error);
s.GoToProfile(ManageNavPages.Index);
s.Driver.FindElement(By.Id("Email")).Clear();
var changedEmail = Guid.NewGuid() + "@lol.com";
s.Driver.FindElement(By.Id("Email")).SendKeys(changedEmail);
s.Driver.FindElement(By.Id("save")).Click();
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
var manager = tester.PayTester.GetService<UserManager<ApplicationUser>>();
Assert.NotNull(await manager.FindByNameAsync(changedEmail));
Assert.NotNull(await manager.FindByEmailAsync(changedEmail));
}
}
[Fact(Timeout = TestTimeout)]
public async Task NewUserLogin()
{
@@ -101,9 +138,7 @@ namespace BTCPayServer.Tests
var email = s.RegisterNewUser();
s.Logout();
s.Driver.AssertNoError();
Assert.Contains("Account/Login", s.Driver.Url);
// Should show the Tor address
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
Assert.Contains("/login", s.Driver.Url);
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
@@ -143,15 +178,15 @@ namespace BTCPayServer.Tests
//let's test invite link
s.Logout();
s.GoToRegister();
var newAdminUser = s.RegisterNewUser(true);
s.RegisterNewUser(true);
s.GoToServer(ServerNavPages.Users);
s.Driver.FindElement(By.Id("CreateUser")).Click();
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
s.Driver.FindElement(By.Id("Save")).Click();
var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text;
;
var url = s.FindAlertMessage().FindElement(By.TagName("a")).Text;
s.Logout();
s.Driver.Navigate().GoToUrl(url);
Assert.Equal("hidden", s.Driver.FindElement(By.Id("Email")).GetAttribute("type"));
@@ -160,7 +195,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
s.Driver.FindElement(By.Id("SetPassword")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
@@ -170,23 +205,16 @@ namespace BTCPayServer.Tests
}
}
static void LogIn(SeleniumTester s, string email)
{
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
s.Driver.AssertNoError();
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseSSHService()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("server/services/ssh", s.Driver.PageSource);
using (var client = await s.Server.PayTester.GetService<BTCPayServer.Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
{
var result = await client.RunBash("echo hello");
Assert.Equal(string.Empty, result.Error);
@@ -197,7 +225,7 @@ namespace BTCPayServer.Tests
s.Driver.AssertNoError();
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2");
s.Driver.FindElement(By.Id("submit")).ForceClick();
s.Driver.FindElement(By.Id("submit")).Click();
s.Driver.AssertNoError();
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
@@ -207,7 +235,7 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
s.Driver.FindElement(By.Id("submit")).ForceClick();
s.Driver.FindElement(By.Id("submit")).Click();
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
Assert.DoesNotContain("test2", text);
@@ -220,12 +248,12 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/emails"));
if (s.Driver.PageSource.Contains("Configured"))
{
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.AssertHappyMessage();
s.FindAlertMessage();
}
CanSetupEmailCore(s);
s.CreateNewStore();
@@ -234,33 +262,13 @@ namespace BTCPayServer.Tests
}
}
private static void CanSetupEmailCore(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
s.AssertHappyMessage();
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.AssertHappyMessage();
Assert.DoesNotContain("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
}
[Fact(Timeout = TestTimeout)]
public async Task CanUseDynamicDns()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var alice = s.RegisterNewUser(isAdmin: true);
s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
@@ -337,22 +345,22 @@ namespace BTCPayServer.Tests
s.ClickOnAllSideMenus();
s.GoToInvoices();
var invoiceId = s.CreateInvoice(storeData.storeName);
s.AssertHappyMessage();
s.FindAlertMessage();
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
var invoiceUrl = s.Driver.Url;
//let's test archiving an invoice
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.AssertHappyMessage();
Assert.Contains("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
//check that it no longer appears in list
s.GoToInvoices();
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
//ok, let's unarchive and see that it shows again
s.Driver.Navigate().GoToUrl(invoiceUrl);
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.GoToInvoices();
Assert.Contains(invoiceId, s.Driver.PageSource);
@@ -374,14 +382,14 @@ namespace BTCPayServer.Tests
s.Logout();
// Let's add Bob as a guest to alice's store
LogIn(s, alice);
s.LogIn(alice);
s.Driver.Navigate().GoToUrl(storeUrl + "/users");
s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter);
Assert.Contains("User added successfully", s.Driver.PageSource);
s.Logout();
// Bob should not have access to store, but should have access to invoice
LogIn(s, bob);
s.LogIn(bob);
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
@@ -389,7 +397,7 @@ namespace BTCPayServer.Tests
// Alice should be able to delete the store
s.Logout();
LogIn(s, alice);
s.LogIn(alice);
s.Driver.FindElement(By.Id("Stores")).Click();
// there shouldn't be any hints now
@@ -412,17 +420,17 @@ namespace BTCPayServer.Tests
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.RegisterNewUser();
s.CreateNewStore();
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Tokens")).Click();
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click();
string pairingCode = AssertUrlHasPairingCode(s);
var pairingCode = AssertUrlHasPairingCode(s);
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
@@ -454,15 +462,6 @@ namespace BTCPayServer.Tests
}
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppPoS()
{
@@ -470,17 +469,17 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
var (storeName, _) = s.CreateNewStore();
s.Driver.FindElement(By.Id("Apps")).Click();
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale" + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale");
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart" + Keys.Enter);
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart");
s.Driver.FindElement(By.Id("SaveSettings")).Click();
s.Driver.FindElement(By.Id("ViewApp")).Click();
var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
@@ -491,36 +490,32 @@ namespace BTCPayServer.Tests
s.Driver.Url = posBaseUrl + "/cart";
Assert.True(s.Driver.PageSource.Contains("Cart"), "Cart PoS not showing correct view");
s.Driver.Quit();
}
}
[Fact(Timeout = TestTimeout)]
public async Task CanCreateAppCF()
public async Task CanCreateCrowdfundingApp()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
var store = s.CreateNewStore();
var (storeName, _) = s.CreateNewStore();
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Apps")).Click();
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
s.Driver.FindElement(By.Name("Name")).SendKeys("CF" + Guid.NewGuid());
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund" + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter");
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF");
s.Driver.Quit();
s.Driver.FindElement(By.Id("SaveSettings")).Click();
s.Driver.FindElement(By.Id("ViewApp")).Click();
Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text);
}
}
@@ -539,11 +534,10 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
s.Driver.FindElement(By.Id("SaveButton")).ForceClick();
s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return);
s.Driver.FindElement(By.Id("SaveButton")).Click();
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request");
s.Driver.Quit();
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
}
}
@@ -554,8 +548,8 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
var userId = s.RegisterNewUser(true);
var storeId = s.CreateNewStore().storeId;
s.RegisterNewUser(true);
var (_, storeId) = s.CreateNewStore();
s.GenerateWallet("BTC", "", false, true);
var walletId = new WalletId(storeId, "BTC");
s.GoToWallet(walletId, WalletsNavPages.Receive);
@@ -582,10 +576,10 @@ namespace BTCPayServer.Tests
coin => coin.OutPoint == spentOutpoint);
});
await s.Server.ExplorerNode.GenerateAsync(1);
s.GoToWallet(walletId, WalletsNavPages.Send);
s.GoToWallet(walletId);
s.Driver.FindElement(By.Id("advancedSettings")).Click();
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
@@ -596,8 +590,8 @@ namespace BTCPayServer.Tests
SetTransactionOutput(s, 0, bob, 0.3m);
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
var happyElement = s.AssertHappyMessage();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
var happyElement = s.FindAlertMessage();
var happyText = happyElement.Text;
var txid = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;
@@ -614,11 +608,11 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var store = s.CreateNewStore();
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
var (storeName, storeId) = s.CreateNewStore();
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
Logs.Tester.LogInformation("Let's create two webhooks");
for (int i = 0; i < 2; i++)
for (var i = 0; i < 2; i++)
{
s.Driver.FindElement(By.Id("CreateWebhook")).Click();
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}");
@@ -636,7 +630,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("continue")).Click();
deletes = s.Driver.FindElements(By.LinkText("Delete"));
Assert.Single(deletes);
s.AssertHappyMessage();
s.FindAlertMessage();
Logs.Tester.LogInformation("Let's try to update one of them");
s.Driver.FindElement(By.LinkText("Modify")).Click();
@@ -648,7 +642,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Name("Secret")).Clear();
s.Driver.FindElement(By.Name("Secret")).SendKeys("HelloWorld");
s.Driver.FindElement(By.Name("update")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
s.Driver.FindElement(By.LinkText("Modify")).Click();
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
{
@@ -664,13 +658,13 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
s.Driver.FindElement(By.Name("update")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
Logs.Tester.LogInformation("Let's see if we can generate an event");
s.GoToStore(store.storeId);
s.GoToStore(storeId);
s.AddDerivationScheme();
s.CreateInvoice(store.storeName);
s.CreateInvoice(storeName);
var request = await server.GetNextRequest();
var headers = request.Request.Headers;
var actualSig = headers["BTCPay-Sig"].First();
@@ -681,21 +675,22 @@ namespace BTCPayServer.Tests
server.Done();
Logs.Tester.LogInformation("Let's make a failed event");
s.CreateInvoice(store.storeName);
s.CreateInvoice(storeName);
request = await server.GetNextRequest();
request.Response.StatusCode = 404;
server.Done();
// The delivery is done asynchronously, so small wait here
await Task.Delay(500);
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
s.Driver.FindElement(By.LinkText("Modify")).Click();
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
// One worked, one failed
s.Driver.FindElement(By.ClassName("fa-times"));
s.Driver.FindElement(By.ClassName("fa-check"));
elements[0].Click();
s.AssertHappyMessage();
s.FindAlertMessage();
request = await server.GetNextRequest();
request.Response.StatusCode = 404;
server.Done();
@@ -708,32 +703,22 @@ namespace BTCPayServer.Tests
CanBrowseContent(s);
var element = s.Driver.FindElement(By.ClassName("redeliver"));
element.Click();
s.AssertHappyMessage();
s.FindAlertMessage();
request = await server.GetNextRequest();
request.Response.StatusCode = 404;
server.Done();
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
s.GoToStore(store.storeId);
s.GoToStore(storeId);
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
s.Driver.FindElement(By.Id("delete-store")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.AssertHappyMessage();
s.FindAlertMessage();
}
}
private static void CanBrowseContent(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
var windows = s.Driver.WindowHandles;
Assert.Equal(2, windows.Count);
s.Driver.SwitchTo().Window(windows[1]);
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
s.Driver.Close();
s.Driver.SwitchTo().Window(windows[0]);
}
[Fact(Timeout = TestTimeout)]
public async Task CanManageWallet()
{
@@ -741,20 +726,19 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var storeId = s.CreateNewStore();
var (storeName, storeId) = s.CreateNewStore();
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
// to sign the transaction
s.GenerateWallet("BTC", "", true, false);
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0',
// then try to use the seed to sign the transaction
s.GenerateWallet("BTC", "", true);
//let's test quickly the receive wallet page
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
s.Driver.FindElement(By.Id("WalletSend")).Click();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
//you cant use the Sign with NBX option without saving private keys when generating the wallet.
s.Driver.FindElement(By.Id("SendMenu")).Click();
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
s.Driver.FindElement(By.Id("WalletReceive")).Click();
@@ -764,17 +748,16 @@ namespace BTCPayServer.Tests
var receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
//unreserve
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
//generate it again, should be the same one as before as nothign got used in the meantime
//generate it again, should be the same one as before as nothing got used in the meantime
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
//send money to addr and ensure it changed
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
sess.ListenAllTrackedSource();
await sess.ListenAllTrackedSourceAsync();
var nextEvent = sess.NextEventAsync();
s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(receiveAddr, Network.RegTest),
Money.Parse("0.1"));
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest), Money.Parse("0.1"));
await nextEvent;
await Task.Delay(200);
s.Driver.Navigate().Refresh();
@@ -783,34 +766,35 @@ namespace BTCPayServer.Tests
receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
s.GoToStore(storeId.storeId);
s.GenerateWallet("BTC", "", true, false);
s.GoToStore(storeId);
s.GenerateWallet("BTC", "", true);
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
s.Driver.FindElement(By.Id("WalletReceive")).Click();
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
var invoiceId = s.CreateInvoice(storeId.storeName);
var invoiceId = s.CreateInvoice(storeName);
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
var address = invoice.EntityToDTO().Addresses["BTC"];
//wallet should have been imported to bitcoin core wallet in watch only mode.
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
Assert.True(result.IsWatchOnly);
s.GoToStore(storeId.storeId);
s.GoToStore(storeId);
var mnemonic = s.GenerateWallet("BTC", "", true, true);
//lets import and save private keys
var root = mnemonic.DeriveExtKey();
invoiceId = s.CreateInvoice(storeId.storeName);
invoiceId = s.CreateInvoice(storeName);
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
address = invoice.EntityToDTO().Addresses["BTC"];
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
//spendable from bitcoin core wallet!
Assert.False(result.IsWatchOnly);
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
s.Server.ExplorerNode.Generate(1);
await s.Server.ExplorerNode.GenerateAsync(1);
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
@@ -818,16 +802,17 @@ namespace BTCPayServer.Tests
s.ClickOnAllSideMenus();
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
s.Driver.FindElement(By.Id("WalletRescan")).Click();
Assert.Contains("The batch size make sure", s.Driver.PageSource);
// We setup the fingerprint and the account key path
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
s.Driver.FindElement(By.Id("WalletSettings")).Click();
// s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
// s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
// Check the tx sent earlier arrived
s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
s.Driver.FindElement(By.Id("WalletTransactions")).Click();
var walletTransactionLink = s.Driver.Url;
Assert.Contains(tx.ToString(), s.Driver.PageSource);
@@ -838,17 +823,16 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("WalletSend")).Click();
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, bob, 1);
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
// Input the seed
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + Keys.Enter);
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
// Broadcast
Assert.Contains(bob.ToString(), s.Driver.PageSource);
Assert.Contains("1.00000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.Equal(walletTransactionLink, s.Driver.Url);
}
@@ -860,18 +844,17 @@ namespace BTCPayServer.Tests
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, jack, 0.01m);
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
Assert.Contains(jack.ToString(), s.Driver.PageSource);
Assert.Contains("0.01000000", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).Click();
Assert.EndsWith("psbt", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
s.Driver.FindElement(By.CssSelector("#OtherActions")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.EndsWith("psbt/ready", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Assert.Equal(walletTransactionLink, s.Driver.Url);
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
@@ -884,14 +867,14 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("bip21parse")).Click();
s.Driver.SwitchTo().Alert().SendKeys(bip21);
s.Driver.SwitchTo().Alert().Accept();
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Info);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
var walletUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
s.Driver.FindElement(By.Id("SettingsMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
// Seed backup page
@@ -905,18 +888,6 @@ namespace BTCPayServer.Tests
Assert.Equal(walletUrl, s.Driver.Url);
}
}
void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
{
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
amountElement.Clear();
amountElement.SendKeys(amount.ToString());
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
if (checkboxElement.Selected != subtract)
{
checkboxElement.Click();
}
}
[Fact]
[Trait("Selenium", "Selenium")]
@@ -926,45 +897,47 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var receiver = s.CreateNewStore();
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
s.CreateNewStore();
s.GenerateWallet("BTC", "", true, true);
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(denomination: 50.0m);
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");;
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.LinkText("View")).Click();
Thread.Sleep(1000);
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
s.Driver.FindElement(By.Id("Amount")).Clear();
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0" + Keys.Enter);
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0");
s.Driver.FindElement(By.Id("Create")).Click();
// This should select the first View, ie, the last one PP2
s.Driver.FindElement(By.LinkText("View")).Click();
Thread.Sleep(1000);
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter);
s.AssertHappyMessage();
s.FindAlertMessage();
// We should not be able to use an address already used
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Error);
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
address = await s.Server.ExplorerNode.GetNewAddressAsync();
s.Driver.FindElement(By.Id("Destination")).Clear();
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
s.AssertHappyMessage();
s.FindAlertMessage();
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
var viewPullPaymentUrl = s.Driver.Url;
@@ -982,11 +955,11 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain("No payout waiting for approval", s.Driver.PageSource);
s.Driver.FindElement(By.Id("selectAllCheckbox")).Click();
s.Driver.FindElement(By.Id("payCommand")).Click();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
s.AssertHappyMessage();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
s.FindAlertMessage();
TestUtils.Eventually(() =>
{
@@ -1027,5 +1000,57 @@ namespace BTCPayServer.Tests
});
}
}
private static void CanBrowseContent(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
var windows = s.Driver.WindowHandles;
Assert.Equal(2, windows.Count);
s.Driver.SwitchTo().Window(windows[1]);
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
s.Driver.Close();
s.Driver.SwitchTo().Window(windows[0]);
}
private static void CanSetupEmailCore(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
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")).SendKeys("mypassword");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.FindAlertMessage();
Assert.DoesNotContain("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
private void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
{
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
amountElement.Clear();
amountElement.SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
if (checkboxElement.Selected != subtract)
{
checkboxElement.Click();
}
}
}
}

View File

@@ -33,7 +33,7 @@ namespace BTCPayServer.Tests
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
NetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
ExplorerNode.ScanRPCCapabilities();
@@ -91,7 +91,7 @@ namespace BTCPayServer.Tests
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);
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", "https://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
}

View File

@@ -10,7 +10,6 @@ using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
using BTCPayServer.Storage.ViewModels;
using BTCPayServer.Tests.Logging;
using DBriize.Utils;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Xunit;
@@ -212,10 +211,9 @@ namespace BTCPayServer.Tests
Assert.NotNull(statusMessageModel);
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
{
{"</a>", string.Empty}, {"target='_blank'>", string.Empty}
});
var url = statusMessageModel.Html.Substring(index)
.Replace("</a>", string.Empty)
.Replace("target='_blank'>", string.Empty);
//verify tmpfile is available and the same
data = await net.DownloadStringTaskAsync(new Uri(url));
Assert.Equal(fileContent, data);

View File

@@ -43,7 +43,6 @@ using BTCPayServer.Services.Rates;
using BTCPayServer.Tests.Logging;
using BTCPayServer.U2F.Models;
using BTCPayServer.Validation;
using DBriize.Utils;
using ExchangeSharp;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
@@ -263,21 +262,12 @@ namespace BTCPayServer.Tests
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
description = description.ReplaceMultiple(new Dictionary<string, string>()
{
{
"#OTHERPERMISSIONS#",
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
},
{
"#SERVERPERMISSIONS#",
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
},
{
"#STOREPERMISSIONS#",
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
}
});
description = description.Replace("#OTHERPERMISSIONS#",
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#SERVERPERMISSIONS#",
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
.Replace("#STOREPERMISSIONS#",
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")));
Logs.Tester.LogInformation(description);
var sresp = Assert
@@ -417,7 +407,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanCalculateCryptoDue()
{
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
@@ -705,7 +695,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanAcceptInvoiceWithTolerance()
{
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
@@ -883,7 +873,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(NetworkType.Regtest),
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
new BTCPayServerOptions() { TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc") });
await tor.Refresh();
@@ -1180,7 +1170,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
public void CanSolveTheDogesRatesOnKraken()
{
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var factory = CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
@@ -2058,7 +2048,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void HasCurrencyDataForNetworks()
{
var btcPayNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var btcPayNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
foreach (var network in btcPayNetworkProvider.GetAll())
{
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
@@ -2996,7 +2986,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
public void CanGetRateCryptoCurrenciesByDefault()
{
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var factory = CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var pairs =
@@ -3068,35 +3058,35 @@ namespace BTCPayServer.Tests
var unusedUri = new Uri("https://toto.com");
Assert.True(ExternalConnectionString.TryParse("server=/test", out var connStr, out var error));
var expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge,
NetworkType.Mainnet);
ChainName.Mainnet);
Assert.Equal(new Uri("https://toto.com/test"), expanded.Server);
expanded = await connStr.Expand(new Uri("http://toto.onion"), ExternalServiceTypes.Charge,
NetworkType.Mainnet);
ChainName.Mainnet);
Assert.Equal(new Uri("http://toto.onion/test"), expanded.Server);
await Assert.ThrowsAsync<SecurityException>(() =>
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet));
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Testnet);
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Mainnet));
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Testnet);
// Make sure absolute paths are not expanded
Assert.True(ExternalConnectionString.TryParse("server=https://tow/test", out connStr, out error));
expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge,
NetworkType.Mainnet);
ChainName.Mainnet);
Assert.Equal(new Uri("https://tow/test"), expanded.Server);
// Error if directory not exists
Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};macaroondirectorypath=pouet",
out connStr, out error));
await Assert.ThrowsAsync<DirectoryNotFoundException>(() =>
connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet));
connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, ChainName.Mainnet));
await Assert.ThrowsAsync<DirectoryNotFoundException>(() =>
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet));
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet));
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, ChainName.Mainnet);
var macaroonDirectory = CreateDirectory();
Assert.True(ExternalConnectionString.TryParse(
$"server={unusedUri};macaroondirectorypath={macaroonDirectory}", out connStr, out error));
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, ChainName.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet);
Assert.NotNull(expanded.Macaroons);
Assert.Null(expanded.MacaroonFilePath);
Assert.Null(expanded.Macaroons.AdminMacaroon);
@@ -3106,7 +3096,7 @@ namespace BTCPayServer.Tests
File.WriteAllBytes($"{macaroonDirectory}/admin.macaroon", new byte[] { 0xaa });
File.WriteAllBytes($"{macaroonDirectory}/invoice.macaroon", new byte[] { 0xab });
File.WriteAllBytes($"{macaroonDirectory}/readonly.macaroon", new byte[] { 0xac });
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet);
Assert.NotNull(expanded.Macaroons.AdminMacaroon);
Assert.NotNull(expanded.Macaroons.InvoiceMacaroon);
Assert.Equal("ab", expanded.Macaroons.InvoiceMacaroon.Hex);
@@ -3116,7 +3106,7 @@ namespace BTCPayServer.Tests
Assert.True(ExternalConnectionString.TryParse(
$"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie", out connStr, out error));
File.WriteAllText($"{macaroonDirectory}/charge.cookie", "apitoken");
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, ChainName.Mainnet);
Assert.Equal("apitoken", expanded.APIToken);
}
@@ -3201,10 +3191,12 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void ParseDerivationSchemeSettings()
{
var mainnet = new BTCPayNetworkProvider(NetworkType.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = new BTCPayNetworkProvider(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();
// ColdCard
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
mainnet, out var settings));
@@ -3218,8 +3210,7 @@ namespace BTCPayServer.Tests
settings.AccountOriginal);
Assert.Equal(root.Derive(new KeyPath("m/49'/0'/0'")).Neuter().PubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey,
settings.AccountDerivation.GetDerivation().ScriptPubKey);
var testnet = new BTCPayNetworkProvider(NetworkType.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
// Should be legacy
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
@@ -3239,6 +3230,15 @@ namespace BTCPayServer.Tests
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
// Specter
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
"{\"label\": \"Specter\", \"blockheight\": 123456, \"descriptor\": \"wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48\"}",
mainnet, out var specter));
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), specter.AccountKeySettings[0].RootFingerprint);
Assert.Equal(specter.AccountKeySettings[0].RootFingerprint, hd);
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("Specter", specter.Label);
}

View File

@@ -24,7 +24,7 @@ services:
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_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@@ -69,20 +69,22 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:0.20.1
image: btcpayserver/bitcoin:0.21.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
BITCOIN_EXTRA_ARGS: |
deprecatedrpc=signrawtransaction
connect=bitcoind:39388
fallbackfee=0.0002
rpcallowip=0.0.0.0/0
links:
- nbxplorer
- postgres
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.45
image: nicolasdorier/nbxplorer:2.1.49
restart: unless-stopped
ports:
- "32838:32838"
@@ -116,14 +118,16 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:0.20.1
image: btcpayserver/bitcoin:0.21.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
rpcport=43782
rpcbind=0.0.0.0:43782
rpcallowip=0.0.0.0/0
port=39388
whitelist=0.0.0.0/0
zmqpubrawblock=tcp://0.0.0.0:28332
@@ -142,7 +146,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.9.0-1-dev
image: btcpayserver/lightning:v0.9.3-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@@ -189,7 +193,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.9.0-1-dev
image: btcpayserver/lightning:v0.9.3-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@@ -221,18 +225,21 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.11.0-beta
image: btcpayserver/lnd:v0.12.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
restlisten=merchant_lnd:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
rpclisten=merchant_lnd:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.rpcuser=ceiwHEbqWI83
bitcoind.rpcpass=DwubwWsoo3
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=merchant_lnd:9735
@@ -240,6 +247,7 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35531:8080"
expose:
@@ -251,18 +259,21 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.11.0-beta
image: btcpayserver/lnd:v0.12.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://customer_lnd:8080
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
restlisten=customer_lnd:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
rpclisten=customer_lnd:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.rpcuser=ceiwHEbqWI83
bitcoind.rpcpass=DwubwWsoo3
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=customer_lnd:10009
@@ -270,6 +281,7 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35532:8080"
expose:

View File

@@ -22,7 +22,7 @@ services:
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_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@@ -66,12 +66,14 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:0.20.1
image: btcpayserver/bitcoin:0.21.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
BITCOIN_EXTRA_ARGS: |
deprecatedrpc=signrawtransaction
connect=bitcoind:39388
rpcallowip=0.0.0.0/0
fallbackfee=0.0002
links:
- nbxplorer
@@ -79,7 +81,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.45
image: nicolasdorier/nbxplorer:2.1.49
restart: unless-stopped
ports:
- "32838:32838"
@@ -103,14 +105,16 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:0.20.1
image: btcpayserver/bitcoin:0.21.0
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
BITCOIN_EXTRA_ARGS: |-
rpcuser=ceiwHEbqWI83
rpcpassword=DwubwWsoo3
rpcport=43782
rpcbind=0.0.0.0:43782
rpcallowip=0.0.0.0/0
port=39388
whitelist=0.0.0.0/0
zmqpubrawblock=tcp://0.0.0.0:28332
@@ -129,7 +133,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.9.0-1-dev
image: btcpayserver/lightning:v0.9.3-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@@ -176,7 +180,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.9.0-1-dev
image: btcpayserver/lightning:v0.9.3-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@@ -209,18 +213,21 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.11.0-beta
image: btcpayserver/lnd:v0.12.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
restlisten=merchant_lnd:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
rpclisten=merchant_lnd:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.rpcuser=ceiwHEbqWI83
bitcoind.rpcpass=DwubwWsoo3
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=merchant_lnd:9735
@@ -228,6 +235,7 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35531:8080"
expose:
@@ -239,18 +247,21 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.11.0-beta
image: btcpayserver/lnd:v0.12.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://customer_lnd:8080
LND_EXTRA_ARGS: |
restlisten=0.0.0.0:8080
restlisten=customer_lnd:8080
rpclisten=127.0.0.1:10008
rpclisten=0.0.0.0:10009
rpclisten=customer_lnd:10009
bitcoin.node=bitcoind
bitcoind.rpchost=bitcoind:43782
bitcoind.rpcuser=ceiwHEbqWI83
bitcoind.rpcpass=DwubwWsoo3
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
externalip=customer_lnd:10009
@@ -258,6 +269,7 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35532:8080"
expose:

View File

@@ -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" />
@@ -45,12 +45,12 @@
<ItemGroup>
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.4" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.7" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
@@ -60,7 +60,6 @@
<PackageReference Include="QRCoder" Version="1.4.1" />
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
<PackageReference Include="DBriize" Version="1.0.1.3" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />

View File

@@ -0,0 +1,5 @@
@model BTCPayServer.Components.Icon.IconViewModel
<svg role="img" class="icon icon-@Model.Symbol">
<use href="/img/icon-sprite.svg#@Model.Symbol"></use>
</svg>

View File

@@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Components.Icon
{
public class Icon : ViewComponent
{
public IViewComponentResult Invoke(string symbol)
{
var vm = new IconViewModel
{
Symbol = symbol
};
return View(vm);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace BTCPayServer.Components.Icon
{
public class IconViewModel
{
public string Symbol { get; set; }
}
}

View File

@@ -4,30 +4,42 @@
@using BTCPayServer.HostedServices
@using Microsoft.AspNetCore.Http.Extensions
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
@addTagHelper *, BundlerMinifier.TagHelpers
@if (Model.UnseenCount > 0)
{
<li class="nav-item dropdown" id="notifications-nav-item">
<a class="nav-link js-scroll-trigger" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
<a class="nav-link js-scroll-trigger border-bottom-0" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
<i class="fa fa-bell"></i>
</a>
<span class="alerts-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
<div class="dropdown-menu dropdown-menu-right text-center notification-items" aria-labelledby="navbarDropdown">
<div class="dropdown-menu dropdown-menu-right text-center notification-dropdown" aria-labelledby="navbarDropdown">
<div class="d-flex align-items-center justify-content-between py-3 px-4 border-bottom border-light">
<h5 class="m-0">Notifications</h5>
<form asp-controller="Notifications" asp-action="MarkAllAsSeen" asp-route-returnUrl="@Context.Request.GetCurrentPathWithQueryString()" method="post">
<button class="btn btn-link p-0 font-weight-semibold" type="submit">Mark all as seen</button>
</form>
</div>
@foreach (var notif in Model.Last5)
{
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="dropdown-item border-bottom py-2 px-3">
<div class="text-left" style="width: 200px; white-space:normal;">
@notif.Body
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="notification d-flex align-items-center dropdown-item border-bottom border-light py-3 px-4">
<div class="mr-3">
<vc:icon symbol="note" />
</div>
<div class="text-left">
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
<div class="notification-item__content">
<div class="text-left text-wrap font-weight-semibold">
@notif.Body
</div>
<div class="text-left d-flex">
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
</div>
</div>
</a>
}
<a class="dropdown-item text-secondary" asp-controller="Notifications" asp-action="Index">See All</a>
<form asp-controller="Notifications" asp-action="MarkAllAsSeen" asp-route-returnUrl="@Context.Request.GetCurrentPathWithQueryString()" method="post">
<button class="dropdown-item text-secondary" type="submit"><i class="fa fa-eye"></i> Mark all as seen</button>
</form>
<div class="p-3">
<a class="font-weight-semibold" asp-controller="Notifications" asp-action="Index">View all</a>
</div>
</div>
</li>
}

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Configuration
{
public class BTCPayServerOptions
{
public NetworkType NetworkType
public ChainName NetworkType
{
get; set;
}
@@ -44,7 +44,7 @@ namespace BTCPayServer.Configuration
{
if (!Path.IsPathRooted(logfile))
{
logfile = Path.Combine(new DataDirectories(configuration).DataDir, logfile);
logfile = Path.Combine(new DataDirectories().Configure(configuration).DataDir, logfile);
}
}
return logfile;
@@ -61,7 +61,7 @@ namespace BTCPayServer.Configuration
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != ChainName.Regtest)
throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script");

View File

@@ -1,26 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.Configuration;
namespace BTCPayServer.Configuration
{
public class DataDirectories
{
public DataDirectories(IConfiguration conf)
{
var networkType = DefaultConfiguration.GetNetworkType(conf);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
DataDir = conf["datadir"] ?? defaultSettings.DefaultDataDirectory;
PluginDir = conf["plugindir"] ?? defaultSettings.DefaultPluginDirectory;
StorageDir = Path.Combine(DataDir, Storage.Services.Providers.FileSystemStorage.FileSystemFileProviderService.LocalStorageDirectoryName);
TempStorageDir = Path.Combine(StorageDir, "tmp");
}
public string DataDir { get; }
public string PluginDir { get; }
public string TempStorageDir { get; }
public string StorageDir { get; set; }
}
}

View File

@@ -12,7 +12,7 @@ namespace BTCPayServer.Configuration
{
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
CommandLineApplication app = new CommandLineApplication(true)
{
@@ -23,6 +23,7 @@ namespace BTCPayServer.Configuration
app.Option("-n | --network", $"Set the network among (mainnet,testnet,regtest) (default: mainnet)", CommandOptionType.SingleValue);
app.Option("--testnet | -testnet", $"Use testnet (deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--signet | -signet", $"Use signet (deprecated, use --network instead)", CommandOptionType.BoolValue);
app.Option("--allow-admin-registration", $"For debug only, will show a checkbox when a new user register to add himself as admin. (default: false)", CommandOptionType.BoolValue);
app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue);
app.Option("--postgres", $"Connection string to a PostgreSQL database", CommandOptionType.SingleValue);
@@ -89,7 +90,7 @@ namespace BTCPayServer.Configuration
return Path.Combine(chainDir, fileName);
}
public static NetworkType GetNetworkType(IConfiguration conf)
public static ChainName GetNetworkType(IConfiguration conf)
{
var network = conf.GetOrDefault<string>("network", null);
if (network != null)
@@ -99,10 +100,12 @@ namespace BTCPayServer.Configuration
{
throw new ConfigException($"Invalid network parameter '{network}'");
}
return n.NetworkType;
return n.ChainName;
}
var net = conf.GetOrDefault<bool>("regtest", false) ? NetworkType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? NetworkType.Testnet : NetworkType.Mainnet;
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainName.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? ChainName.Testnet :
conf.GetOrDefault<bool>("signet", false) ? Bitcoin.Instance.Signet.ChainName :
ChainName.Mainnet;
return net;
}

View File

@@ -30,12 +30,12 @@ namespace BTCPayServer.Configuration
/// Return a connectionString which does not depends on external resources or information like relative path or file path
/// </summary>
/// <returns></returns>
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, NetworkType network)
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, ChainName network)
{
var connectionString = this.Clone();
// Transform relative URI into absolute URI
var serviceUri = connectionString.Server.IsAbsoluteUri ? connectionString.Server : ToRelative(absoluteUrlBase, connectionString.Server.ToString());
var isSecure = network != NetworkType.Mainnet ||
var isSecure = network != ChainName.Mainnet ||
serviceUri.Scheme == "https" ||
serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase) ||
Extensions.IsLocalNetwork(serviceUri.DnsSafeHost);

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
using NBitcoin;
namespace BTCPayServer.Configuration
{
@@ -11,28 +8,5 @@ namespace BTCPayServer.Configuration
public Dictionary<string, Uri> OtherExternalServices { get; set; } = new Dictionary<string, Uri>();
public ExternalServices ExternalServices { get; set; } = new ExternalServices();
public void Configure(IConfiguration configuration, BTCPayNetworkProvider btcPayNetworkProvider)
{
foreach (var net in btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>())
{
ExternalServices.Load(net.CryptoCode, configuration);
}
ExternalServices.LoadNonCryptoServices(configuration);
var services = configuration.GetOrDefault<string>("externalservices", null);
if (services != null)
{
foreach (var service in services.Split(new[] {';', ','}, StringSplitOptions.RemoveEmptyEntries)
.Select(p => (p, SeparatorIndex: p.IndexOf(':', StringComparison.OrdinalIgnoreCase)))
.Where(p => p.SeparatorIndex != -1)
.Select(p => (Name: p.p.Substring(0, p.SeparatorIndex),
Link: p.p.Substring(p.SeparatorIndex + 1))))
{
if (Uri.TryCreate(service.Link, UriKind.RelativeOrAbsolute, out var uri))
OtherExternalServices.AddOrReplace(service.Name, uri);
}
}
}
}
}

View File

@@ -1,10 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Lightning;
using BTCPayServer.Logging;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.Configuration
{
@@ -12,46 +7,5 @@ namespace BTCPayServer.Configuration
{
public Dictionary<string, LightningConnectionString> InternalLightningByCryptoCode { get; set; } =
new Dictionary<string, LightningConnectionString>();
public void Configure(IConfiguration conf, BTCPayNetworkProvider networkProvider)
{
foreach (var net in networkProvider.GetAll().OfType<BTCPayNetwork>())
{
var lightning = conf.GetOrDefault<string>($"{net.CryptoCode}.lightning", string.Empty);
if (lightning.Length != 0)
{
if (!LightningConnectionString.TryParse(lightning, true, out var connectionString,
out var error))
{
Logs.Configuration.LogWarning($"Invalid setting {net.CryptoCode}.lightning, " +
Environment.NewLine +
$"If you have a c-lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " +
Environment.NewLine +
$"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" +
Environment.NewLine +
$"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" +
Environment.NewLine +
$" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" +
Environment.NewLine +
$"If you have an eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393;bitcoin-auth=bitcoinrpcuser:bitcoinrpcpassword" +
Environment.NewLine +
$" eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393" +
Environment.NewLine +
$"Error: {error}" + Environment.NewLine +
"This service will not be exposed through BTCPay Server");
}
else
{
if (connectionString.IsLegacy)
{
Logs.Configuration.LogWarning(
$"Setting {net.CryptoCode}.lightning is a deprecated format, it will work now, but please replace it for future versions with '{connectionString.ToString()}'");
}
InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString);
}
}
}
}
}
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Configuration;
namespace BTCPayServer.Configuration
{
@@ -12,19 +9,5 @@ namespace BTCPayServer.Configuration
get;
set;
} = new List<NBXplorerConnectionSetting>();
public void Configure(IConfiguration conf, BTCPayNetworkProvider provider)
{
foreach (BTCPayNetwork btcPayNetwork in provider.GetAll().OfType<BTCPayNetwork>())
{
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting();
setting.CryptoCode = btcPayNetwork.CryptoCode;
setting.ExplorerUri = conf.GetOrDefault<Uri>($"{btcPayNetwork.CryptoCode}.explorer.url",
btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultUrl);
setting.CookieFile = conf.GetOrDefault<string>($"{btcPayNetwork.CryptoCode}.explorer.cookiefile",
btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultCookieFile);
NBXplorerConnectionSettings.Add(setting);
}
}
}
}

View File

@@ -66,6 +66,8 @@ namespace BTCPayServer.Controllers
[HttpGet]
[AllowAnonymous]
[Route("~/login", Order = 1)]
[Route("~/Account/Login", Order = 2)]
public async Task<IActionResult> Login(string returnUrl = null, string email = null)
{
@@ -89,6 +91,8 @@ namespace BTCPayServer.Controllers
[HttpPost]
[AllowAnonymous]
[Route("~/login", Order = 1)]
[Route("~/Account/Login", Order = 2)]
[ValidateAntiForgeryToken]
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
@@ -396,6 +400,8 @@ namespace BTCPayServer.Controllers
[HttpGet]
[AllowAnonymous]
[Route("~/register", Order = 1)]
[Route("~/Account/Register", Order = 2)]
[RateLimitsFilter(ZoneLimits.Register, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true)
{
@@ -413,6 +419,8 @@ namespace BTCPayServer.Controllers
[HttpPost]
[AllowAnonymous]
[Route("~/register", Order = 1)]
[Route("~/Account/Register", Order = 2)]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, bool logon = true)
{

View File

@@ -13,7 +13,7 @@ namespace BTCPayServer.Controllers
{
if (statusCode.HasValue)
{
var specialPages = new[] { 404, 429, 500 };
var specialPages = new[] { 404, 406, 417, 429, 500, 502 };
if (specialPages.Any(a => a == statusCode.Value))
{
var viewName = statusCode.ToString();

View File

@@ -107,7 +107,7 @@ namespace BTCPayServer.Controllers.GreenField
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
if (network == null || !CanUseInternalLightning(doingAdminThings) || internalLightningNode == null)
{
return null;
return Task.FromResult<ILightningClient>(null);
}
return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network));

View File

@@ -120,7 +120,7 @@ namespace BTCPayServer.Controllers.GreenField
if (existing == null || (existing.GetLightningUrl().IsInternalNode(internalLightningNode) &&
!CanUseInternalLightning(doingAdminThings)))
{
return null;
return Task.FromResult<ILightningClient>(null);
}
return Task.FromResult(_lightningClientFactory.Create(existing.GetLightningUrl(), network));

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
@@ -72,7 +73,7 @@ namespace BTCPayServer.Controllers.GreenField
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/preview")]
public ActionResult<OnChainPaymentMethodPreviewResultData> GetOnChainPaymentMethodPreview(
public IActionResult GetOnChainPaymentMethodPreview(
string cryptoCode,
int offset = 0, int amount = 10)
{
@@ -86,25 +87,34 @@ namespace BTCPayServer.Controllers.GreenField
{
return NotFound();
}
var strategy = DerivationSchemeSettings.Parse(paymentMethod.DerivationScheme, network);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line = strategy.AccountDerivation.GetLineFor(deposit);
var result = new OnChainPaymentMethodPreviewResultData();
for (var i = offset; i < amount; i++)
try
{
var address = line.Derive((uint)i);
result.Addresses.Add(
new OnChainPaymentMethodPreviewResultData.OnChainPaymentMethodPreviewResultAddressItem()
{
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
Address = address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork)
.ToString()
});
var strategy = DerivationSchemeSettings.Parse(paymentMethod.DerivationScheme, network);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line = strategy.AccountDerivation.GetLineFor(deposit);
var result = new OnChainPaymentMethodPreviewResultData();
for (var i = offset; i < amount; i++)
{
var address = line.Derive((uint)i);
result.Addresses.Add(
new OnChainPaymentMethodPreviewResultData.OnChainPaymentMethodPreviewResultAddressItem()
{
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
Address = address.ScriptPubKey.GetDestinationAddress(strategy.Network.NBitcoinNetwork)
.ToString()
});
}
return Ok(result);
}
catch
{
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
"Invalid Derivation Scheme");
return this.CreateValidationError(ModelState);
}
return Ok(result);
}
@@ -125,36 +135,37 @@ namespace BTCPayServer.Controllers.GreenField
}
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
DerivationSchemeSettings strategy;
try
{
var strategy = DerivationSchemeSettings.Parse(paymentMethodData.DerivationScheme, network);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line = strategy.AccountDerivation.GetLineFor(deposit);
var result = new OnChainPaymentMethodPreviewResultData();
for (var i = offset; i < amount; i++)
{
var derivation = line.Derive((uint)i);
result.Addresses.Add(
new
OnChainPaymentMethodPreviewResultData.
OnChainPaymentMethodPreviewResultAddressItem()
{
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
Address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
line.KeyPathTemplate.GetKeyPath((uint)i),
derivation.ScriptPubKey).ToString()
});
}
return Ok(result);
strategy = DerivationSchemeSettings.Parse(paymentMethodData.DerivationScheme, network);
}
catch
{
ModelState.AddModelError(nameof(OnChainPaymentMethodData.DerivationScheme),
"Invalid Derivation Scheme");
return this.CreateValidationError(ModelState);
}
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line = strategy.AccountDerivation.GetLineFor(deposit);
var result = new OnChainPaymentMethodPreviewResultData();
for (var i = offset; i < amount; i++)
{
var derivation = line.Derive((uint)i);
result.Addresses.Add(
new
OnChainPaymentMethodPreviewResultData.
OnChainPaymentMethodPreviewResultAddressItem()
{
KeyPath = deposit.GetKeyPath((uint)i).ToString(),
Address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
line.KeyPathTemplate.GetKeyPath((uint)i),
derivation.ScriptPubKey).ToString()
});
}
return Ok(result);
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]

View File

@@ -36,36 +36,39 @@ namespace BTCPayServer.Controllers.GreenField
public WebhookNotificationManager WebhookNotificationManager { get; }
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId?}")]
public async Task<IActionResult> ListWebhooks(string storeId, string webhookId)
public async Task<IActionResult> ListWebhooks(string webhookId)
{
if (webhookId is null)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
return Ok((await StoreRepository.GetWebhooks(storeId))
return Ok((await StoreRepository.GetWebhooks(CurrentStoreId))
.Select(o => FromModel(o, false))
.ToList());
}
else
{
var w = await StoreRepository.GetWebhook(storeId, webhookId);
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
if (w is null)
return NotFound();
return Ok(FromModel(w, false));
}
}
[HttpPost("~/api/v1/stores/{storeId}/webhooks")]
public async Task<IActionResult> CreateWebhook(string storeId, Client.Models.CreateStoreWebhookRequest create)
string CurrentStoreId
{
get
{
return this.HttpContext.GetStoreData()?.Id;
}
}
[HttpPost("~/api/v1/stores/{storeId}/webhooks")]
public async Task<IActionResult> CreateWebhook(Client.Models.CreateStoreWebhookRequest create)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
ValidateWebhookRequest(create);
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
var webhookId = await StoreRepository.CreateWebhook(storeId, ToModel(create));
var w = await StoreRepository.GetWebhook(storeId, webhookId);
var webhookId = await StoreRepository.CreateWebhook(CurrentStoreId, ToModel(create));
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
if (w is null)
return NotFound();
return Ok(FromModel(w, true));
@@ -83,25 +86,19 @@ namespace BTCPayServer.Controllers.GreenField
ValidateWebhookRequest(update);
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var w = await StoreRepository.GetWebhook(storeId, webhookId);
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
if (w is null)
return NotFound();
await StoreRepository.UpdateWebhook(storeId, webhookId, ToModel(update));
return await ListWebhooks(storeId, webhookId);
return await ListWebhooks(webhookId);
}
[HttpDelete("~/api/v1/stores/{storeId}/webhooks/{webhookId}")]
public async Task<IActionResult> DeleteWebhook(string storeId, string webhookId)
public async Task<IActionResult> DeleteWebhook(string webhookId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var w = await StoreRepository.GetWebhook(storeId, webhookId);
var w = await StoreRepository.GetWebhook(CurrentStoreId, webhookId);
if (w is null)
return NotFound();
await StoreRepository.DeleteWebhook(storeId, webhookId);
await StoreRepository.DeleteWebhook(CurrentStoreId, webhookId);
return Ok();
}
private WebhookBlob ToModel(StoreWebhookBaseData create)
@@ -124,41 +121,35 @@ namespace BTCPayServer.Controllers.GreenField
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId?}")]
public async Task<IActionResult> ListDeliveries(string storeId, string webhookId, string deliveryId, int? count = null)
public async Task<IActionResult> ListDeliveries(string webhookId, string deliveryId, int? count = null)
{
if (deliveryId is null)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
return Ok((await StoreRepository.GetWebhookDeliveries(storeId, webhookId, count))
return Ok((await StoreRepository.GetWebhookDeliveries(CurrentStoreId, webhookId, count))
.Select(o => FromModel(o))
.ToList());
}
else
{
var delivery = await StoreRepository.GetWebhookDelivery(storeId, webhookId, deliveryId);
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
if (delivery is null)
return NotFound();
return Ok(FromModel(delivery));
}
}
[HttpPost("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
public async Task<IActionResult> RedeliverWebhook(string storeId, string webhookId, string deliveryId)
public async Task<IActionResult> RedeliverWebhook(string webhookId, string deliveryId)
{
var delivery = await StoreRepository.GetWebhookDelivery(HttpContext.GetStoreData().Id, webhookId, deliveryId);
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
if (delivery is null)
return NotFound();
return this.Ok(new JValue(await WebhookNotificationManager.Redeliver(deliveryId)));
}
[HttpGet("~/api/v1/stores/{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
public async Task<IActionResult> GetDeliveryRequest(string storeId, string webhookId, string deliveryId)
public async Task<IActionResult> GetDeliveryRequest(string webhookId, string deliveryId)
{
var store = HttpContext.GetStoreData();
if (store == null)
return NotFound();
var delivery = await StoreRepository.GetWebhookDelivery(storeId, webhookId, deliveryId);
var delivery = await StoreRepository.GetWebhookDelivery(CurrentStoreId, webhookId, deliveryId);
if (delivery is null)
return NotFound();
return File(delivery.GetBlob().Request, "application/json");

View File

@@ -25,7 +25,6 @@ using BTCPayServer.Security;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Invoices.Export;
using BTCPayServer.Services.Rates;
using DBriize.Utils;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;

View File

@@ -98,8 +98,7 @@ namespace BTCPayServer.Controllers
{
throw new BitpayHttpException(400, "The expirationTime is set too soon");
}
invoice.Currency = invoice.Currency?.ToUpperInvariant() ?? "USD";
entity.Currency = invoice.Currency;
invoice.Currency = invoice.Currency?.Trim().ToUpperInvariant() ?? "USD";
entity.Metadata.OrderId = invoice.OrderId;
entity.Metadata.PosData = invoice.PosData;
entity.ServerUrl = serverUrl;
@@ -169,6 +168,7 @@ namespace BTCPayServer.Controllers
if (invoice.Metadata != null)
entity.Metadata = InvoiceMetadata.FromJObject(invoice.Metadata);
invoice.Checkout ??= new CreateInvoiceRequest.CheckoutOptions();
invoice.Currency = invoice.Currency?.Trim().ToUpperInvariant() ?? "USD";
entity.Currency = invoice.Currency;
entity.Price = invoice.Amount;
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;

View File

@@ -82,7 +82,6 @@ namespace BTCPayServer.Controllers
{
Username = user.UserName,
Email = user.Email,
PhoneNumber = user.PhoneNumber,
IsEmailConfirmed = user.EmailConfirmed
};
return View(model);
@@ -97,8 +96,6 @@ namespace BTCPayServer.Controllers
return View(model);
}
bool needUpdate = false;
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
@@ -108,33 +105,22 @@ namespace BTCPayServer.Controllers
var email = user.Email;
if (model.Email != email)
{
if (!(await _userManager.FindByEmailAsync(model.Email) is null))
{
TempData[WellKnownTempData.ErrorMessage] = "The email address is already in use with an other account.";
return RedirectToAction(nameof(Index));
}
var setUserResult = await _userManager.SetUserNameAsync(user, model.Email);
if (!setUserResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
}
var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
if (!setEmailResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
}
await _userManager.SetUserNameAsync(user, model.Username);
}
var phoneNumber = user.PhoneNumber;
if (model.PhoneNumber != phoneNumber)
{
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
if (!setPhoneResult.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
}
}
if (needUpdate)
{
var result = await _userManager.UpdateAsync(user);
if (!result.Succeeded)
{
throw new ApplicationException($"Unexpected error occurred updating user with ID '{user.Id}'.");
}
}
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated";
return RedirectToAction(nameof(Index));
}

View File

@@ -119,7 +119,7 @@ namespace BTCPayServer.Controllers
[HttpGet]
public async Task<IActionResult> Generate(string version)
{
if (_env.NetworkType != NBitcoin.NetworkType.Regtest)
if (_env.NetworkType != NBitcoin.ChainName.Regtest)
return NotFound();
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(version));
return RedirectToAction(nameof(Index));

View File

@@ -1,7 +1,5 @@
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Storage.Services;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage;
using Microsoft.AspNetCore.Mvc;
namespace BTCPayServer.Storage
@@ -10,12 +8,10 @@ namespace BTCPayServer.Storage
public class StorageController : Controller
{
private readonly FileService _FileService;
private readonly string _dir;
public StorageController(FileService fileService, DataDirectories datadirs)
public StorageController(FileService fileService)
{
_FileService = fileService;
_dir = datadirs.TempStorageDir;
}
[HttpGet("{fileId}")]

View File

@@ -7,7 +7,6 @@ using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services;

View File

@@ -0,0 +1,513 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Data;
using BTCPayServer.Events;
using BTCPayServer.Models;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Payments;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Mvc;
using NBitcoin;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
namespace BTCPayServer.Controllers
{
public partial class StoresController
{
[HttpGet("{storeId}/onchain/{cryptoCode}")]
public ActionResult SetupWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
vm.DerivationScheme = derivation?.AccountDerivation.ToString();
return View(vm);
}
[HttpGet("{storeId}/onchain/{cryptoCode}/import/{method?}")]
public async Task<IActionResult> ImportWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out _, out var network);
if (checkResult != null)
{
return checkResult;
}
var (hotWallet, rpcImport) = await CanUseHotWallet();
vm.Network = network;
vm.RootKeyPath = network.GetRootKeyPath();
vm.CanUseHotWallet = hotWallet;
vm.CanUseRPCImport = rpcImport;
if (vm.Method == null)
{
vm.Method = WalletSetupMethod.ImportOptions;
}
else if (vm.Method == WalletSetupMethod.Seed)
{
vm.SetupRequest = new GenerateWalletRequest();
}
return View(vm.ViewName, vm);
}
[HttpPost("{storeId}/onchain/{cryptoCode}/modify")]
[HttpPost("{storeId}/onchain/{cryptoCode}/import/{method}")]
public async Task<IActionResult> UpdateWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
vm.Network = network;
vm.RootKeyPath = network.GetRootKeyPath();
DerivationSchemeSettings strategy = null;
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
{
return NotFound();
}
if (!string.IsNullOrEmpty(vm.Config))
{
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
{
ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
return View(vm.ViewName, vm);
}
}
if (vm.WalletFile != null)
{
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
{
ModelState.AddModelError(nameof(vm.WalletFile), "Wallet file was not in the correct format");
return View(vm.ViewName, vm);
}
}
else if (!string.IsNullOrEmpty(vm.WalletFileContent))
{
if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
{
ModelState.AddModelError(nameof(vm.WalletFileContent), "QR import was not in the correct format");
return View(vm.ViewName, vm);
}
}
else
{
try
{
if (!string.IsNullOrEmpty(vm.DerivationScheme))
{
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
{
var accountKey = string.IsNullOrEmpty(vm.AccountKey)
? null
: new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
if (accountKey != null)
{
var accountSettings =
newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
if (accountSettings != null)
{
accountSettings.AccountKeyPath =
vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
? (HDFingerprint?)null
: new HDFingerprint(
NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
}
}
strategy = newStrategy;
strategy.Source = vm.Source;
vm.DerivationScheme = strategy.AccountDerivation.ToString();
}
}
else
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key");
return View(vm.ViewName, vm);
}
}
catch
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid wallet format");
return View(vm.ViewName, vm);
}
}
var oldConfig = vm.Config;
vm.Config = strategy?.ToJson();
var configChanged = oldConfig != vm.Config;
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
var storeBlob = store.GetStoreBlob();
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
var willBeExcluded = !vm.Enabled;
var excludedChanged = willBeExcluded != wasExcluded;
var showAddress = // Show addresses if:
// - If the user is testing the hint address in confirmation screen
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
// - The user is clicking on continue after changing the config
(!vm.Confirmation && configChanged);
showAddress = showAddress && strategy != null;
if (!showAddress)
{
try
{
if (strategy != null)
await wallet.TrackAsync(strategy.AccountDerivation);
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
storeBlob.Hints.Wallet = false;
store.SetStoreBlob(storeBlob);
}
catch
{
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme");
return View(vm.ViewName, vm);
}
await _Repo.UpdateStore(store);
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(vm.StoreId, vm.CryptoCode)});
if (excludedChanged)
{
var label = willBeExcluded ? "disabled" : "enabled";
TempData[WellKnownTempData.SuccessMessage] =
$"On-Chain payments for {network.CryptoCode} have been {label}.";
}
else
{
TempData[WellKnownTempData.SuccessMessage] =
$"Derivation settings for {network.CryptoCode} have been modified.";
}
// This is success case when derivation scheme is added to the store
return RedirectToAction(nameof(UpdateStore), new {storeId = vm.StoreId});
}
if (!string.IsNullOrEmpty(vm.HintAddress))
{
BitcoinAddress address;
try
{
address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork);
}
catch
{
ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address");
return ConfirmAddresses(vm, strategy);
}
try
{
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network);
if (newStrategy.AccountDerivation != strategy.AccountDerivation)
{
strategy.AccountDerivation = newStrategy.AccountDerivation;
strategy.AccountOriginal = null;
}
}
catch
{
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address. Are you sure the wallet and address provided are correct and from the same source?");
return ConfirmAddresses(vm, strategy);
}
vm.HintAddress = "";
TempData[WellKnownTempData.SuccessMessage] =
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
ModelState.Remove(nameof(vm.HintAddress));
ModelState.Remove(nameof(vm.DerivationScheme));
}
return ConfirmAddresses(vm, strategy);
}
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/{method?}")]
public async Task<IActionResult> GenerateWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var isHotWallet = vm.Method == WalletSetupMethod.HotWallet;
var (hotWallet, rpcImport) = await CanUseHotWallet();
if (isHotWallet && !hotWallet)
{
return NotFound();
}
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
if (derivation != null)
{
vm.DerivationScheme = derivation.AccountDerivation.ToString();
vm.Config = derivation.ToJson();
}
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
vm.CanUseHotWallet = hotWallet;
vm.CanUseRPCImport = rpcImport;
vm.RootKeyPath = network.GetRootKeyPath();
vm.Network = network;
if (vm.Method == null)
{
vm.Method = WalletSetupMethod.GenerateOptions;
}
else
{
vm.SetupRequest = new GenerateWalletRequest { SavePrivateKeys = isHotWallet };
}
return View(vm.ViewName, vm);
}
[HttpPost("{storeId}/onchain/{cryptoCode}/generate/{method}")]
public async Task<IActionResult> GenerateWallet(string storeId, string cryptoCode, WalletSetupMethod method, GenerateWalletRequest request)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var (hotWallet, rpcImport) = await CanUseHotWallet();
if (!hotWallet && request.SavePrivateKeys || !rpcImport && request.ImportKeysToRPC)
{
return NotFound();
}
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
var isImport = method == WalletSetupMethod.Seed;
var vm = new WalletSetupViewModel
{
StoreId = storeId,
CryptoCode = cryptoCode,
Method = method,
SetupRequest = request,
Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic),
Network = network,
RootKeyPath = network.GetRootKeyPath(),
Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike)),
Source = "NBXplorer",
DerivationSchemeFormat = "BTCPay",
CanUseHotWallet = true,
CanUseRPCImport = rpcImport
};
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
{
ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed");
return View(vm.ViewName, vm);
}
GenerateWalletResponse response;
try
{
response = await client.GenerateWalletAsync(request);
if (response == null)
{
throw new Exception("Node unavailable");
}
}
catch (Exception e)
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Error,
Html = $"There was an error generating your wallet: {e.Message}"
});
return View(vm.ViewName, vm);
}
// Set wallet properties from generate response
vm.RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString();
vm.DerivationScheme = response.DerivationScheme.ToString();
vm.AccountKey = response.AccountHDKey.Neuter().ToWif();
vm.KeyPath = response.AccountKeyPath.KeyPath.ToString();
var result = await UpdateWallet(vm);
if (!ModelState.IsValid || !(result is RedirectToActionResult))
return result;
if (!isImport)
{
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Success,
Html = "<span class='text-centered'>Your wallet has been generated.</span>"
});
var seedVm = new RecoverySeedBackupViewModel
{
CryptoCode = cryptoCode,
Mnemonic = response.Mnemonic,
Passphrase = response.Passphrase,
IsStored = request.SavePrivateKeys,
ReturnUrl = Url.Action(nameof(GenerateWalletConfirm), new {storeId, cryptoCode})
};
return this.RedirectToRecoverySeedBackup(seedVm);
}
TempData.SetStatusMessageModel(new StatusMessageModel
{
Severity = StatusMessageModel.StatusSeverity.Warning,
Html = "Please check your addresses and confirm."
});
return result;
}
// The purpose of this action is to show the user a success message, which confirms
// that the store settings have been updated after generating a new wallet.
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/confirm")]
public ActionResult GenerateWalletConfirm(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out _, out var network);
if (checkResult != null)
{
return checkResult;
}
TempData[WellKnownTempData.SuccessMessage] =
$"Derivation settings for {network.CryptoCode} have been modified.";
return RedirectToAction(nameof(UpdateStore), new {storeId});
}
[HttpGet("{storeId}/onchain/{cryptoCode}/modify")]
public async Task<IActionResult> ModifyWallet(WalletSetupViewModel vm)
{
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
if (derivation == null)
{
return NotFound();
}
var (hotWallet, rpcImport) = await CanUseHotWallet();
vm.CanUseHotWallet = hotWallet;
vm.CanUseRPCImport = rpcImport;
vm.RootKeyPath = network.GetRootKeyPath();
vm.Network = network;
vm.Source = derivation.Source;
vm.RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString();
vm.DerivationScheme = derivation.AccountDerivation.ToString();
vm.KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString();
vm.Config = derivation.ToJson();
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
return View(vm);
}
[HttpGet("{storeId}/onchain/{cryptoCode}/delete")]
public IActionResult DeleteWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
var description =
(derivation.IsHotWallet ? "<p class=\"text-danger font-weight-bold\">Please note that this is a hot wallet!</p> " : "") +
"<p class=\"text-danger font-weight-bold\">Do not remove the wallet if you have not backed it up!</p>" +
"<p class=\"text-left mb-0\">Removing the wallet will erase the wallet data from the server. " +
$"The store won't be able to receive {network.CryptoCode} onchain payments until a new wallet is set up.</p>";
return View("Confirm", new ConfirmModel
{
Title = $"Remove {network.CryptoCode} wallet",
Description = description,
DescriptionHtml = true,
Action = "Remove"
});
}
[HttpPost("{storeId}/onchain/{cryptoCode}/delete")]
public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode)
{
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
if (checkResult != null)
{
return checkResult;
}
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
if (derivation == null)
{
return NotFound();
}
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
store.SetSupportedPaymentMethod(paymentMethodId, null);
await _Repo.UpdateStore(store);
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(storeId, cryptoCode)});
TempData[WellKnownTempData.SuccessMessage] =
$"On-Chain payment for {network.CryptoCode} has been removed.";
return RedirectToAction(nameof(UpdateStore), new {storeId});
}
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)
{
vm.DerivationScheme = strategy.AccountDerivation.ToString();
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
if (!string.IsNullOrEmpty(vm.DerivationScheme))
{
var line = strategy.AccountDerivation.GetLineFor(deposit);
for (uint i = 0; i < 10; i++)
{
var keyPath = deposit.GetKeyPath(i);
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
var derivation = line.Derive(i);
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
line.KeyPathTemplate.GetKeyPath(i),
derivation.ScriptPubKey).ToString();
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
}
}
vm.Confirmation = true;
ModelState.Remove(nameof(vm.Config)); // Remove the cached value
return View("ImportWallet/ConfirmAddresses", vm);
}
private ActionResult IsAvailable(string cryptoCode, out StoreData store, out BTCPayNetwork network)
{
store = HttpContext.GetStoreData();
network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
return store == null || network == null ? NotFound() : null;
}
}
}

View File

@@ -36,6 +36,7 @@ using Microsoft.Extensions.Options;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers
@@ -699,6 +700,26 @@ namespace BTCPayServer.Controllers
{
var parser = new DerivationSchemeParser(network);
parser.HintScriptPubKey = hint;
try
{
var derivationSchemeSettings = new DerivationSchemeSettings();
derivationSchemeSettings.Network = network;
var result = parser.ParseOutputDescriptor(derivationScheme);
derivationSchemeSettings.AccountOriginal = derivationScheme.Trim();
derivationSchemeSettings.AccountDerivation = result.Item1;
derivationSchemeSettings.AccountKeySettings = result.Item2?.Select((path, i) => new AccountKeySettings()
{
RootFingerprint = path?.MasterFingerprint,
AccountKeyPath = path?.KeyPath,
AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(parser.Network)
}).ToArray() ?? new AccountKeySettings[result.Item1.GetExtPubKeys().Count()];
return derivationSchemeSettings;
}
catch (Exception)
{
// ignored
}
return new DerivationSchemeSettings(parser.Parse(derivationScheme), network);
}

View File

@@ -75,9 +75,14 @@ namespace BTCPayServer.Controllers
{
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
vm.CryptoCode = network.CryptoCode;
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null)
return NotFound();
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
WellknownMetadataKeys.Mnemonic));
.GetMetadataAsync<string>(derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.Mnemonic));
if (await vm.GetPSBT(network.NBitcoinNetwork) is PSBT psbt)
{
vm.Decoded = psbt.ToString();
@@ -98,9 +103,13 @@ namespace BTCPayServer.Controllers
return await WalletPSBT(walletId, vm);
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId.CryptoCode);
vm.CryptoCode = network.CryptoCode;
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
if (derivationSchemeSettings == null)
return NotFound();
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
WellknownMetadataKeys.Mnemonic));
.GetMetadataAsync<string>(derivationSchemeSettings.AccountDerivation, WellknownMetadataKeys.Mnemonic));
var psbt = await vm.GetPSBT(network.NBitcoinNetwork);
if (psbt == null)
{
@@ -127,7 +136,6 @@ namespace BTCPayServer.Controllers
return View(vm);
case "update":
var derivationSchemeSettings = GetDerivationSchemeSettings(walletId);
psbt = await ExplorerClientProvider.UpdatePSBT(derivationSchemeSettings, psbt);
if (psbt == null)
{

View File

@@ -27,7 +27,10 @@ namespace BTCPayServer.Controllers
public IActionResult NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId)
{
return View(new NewPullPaymentModel()
if (GetDerivationSchemeSettings(walletId) == null)
return NotFound();
return View(new NewPullPaymentModel
{
Name = "",
Currency = "BTC",
@@ -41,6 +44,9 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> NewPullPayment([ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, NewPullPaymentModel model)
{
if (GetDerivationSchemeSettings(walletId) == null)
return NotFound();
model.Name ??= string.Empty;
model.Currency = model.Currency.ToUpperInvariant().Trim();
if (_currencyTable.GetCurrencyData(model.Currency, false) is null)
@@ -99,7 +105,10 @@ namespace BTCPayServer.Controllers
.Where(p => p.State == PayoutState.Completed || p.State == PayoutState.InProgress)
})
.ToListAsync();
var vm = new PullPaymentsModel();
var vm = new PullPaymentsModel
{ HasDerivationSchemeSettings = GetDerivationSchemeSettings(walletId) != null };
foreach (var o in pps)
{
var pp = o.PullPayment;
@@ -175,8 +184,9 @@ namespace BTCPayServer.Controllers
[ModelBinder(typeof(WalletIdModelBinder))]
WalletId walletId, PayoutsModel vm, CancellationToken cancellationToken)
{
if (vm is null)
if (vm is null || GetDerivationSchemeSettings(walletId) == null)
return NotFound();
var storeId = walletId.StoreId;
var paymentMethodId = new PaymentMethodId(walletId.CryptoCode, PaymentTypes.BTCLike);
var payoutIds = vm.WaitingForApproval.Where(p => p.Selected).Select(p => p.PayoutId).ToArray();

View File

@@ -1186,7 +1186,7 @@ namespace BTCPayServer.Controllers
var res = paymentMethodId.PaymentType == PaymentTypes.BTCLike
? Url.Content(network.CryptoImagePath)
: Url.Content(network.LightningImagePath);
return "/" + res;
return Request.GetRelativePathOrAbsolute(res);
}
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using NBitcoin;
using NBitcoin.Scripting;
using NBXplorer.DerivationStrategy;
namespace BTCPayServer
@@ -21,6 +22,77 @@ namespace BTCPayServer
BtcPayNetwork = expectedNetwork;
}
public (DerivationStrategyBase, RootedKeyPath[]) ParseOutputDescriptor(string str)
{
(DerivationStrategyBase, RootedKeyPath[]) ExtractFromPkProvider(PubKeyProvider pubKeyProvider,
string suffix = "")
{
switch (pubKeyProvider)
{
case PubKeyProvider.Const _:
throw new FormatException("Only HD output descriptors are supported.");
case PubKeyProvider.HD hd:
if (hd.Path != null && hd.Path.ToString() != "0")
{
throw new FormatException("Custom change paths are not supported.");
}
return (Parse($"{hd.Extkey}{suffix}"), null);
case PubKeyProvider.Origin origin:
var innerResult = ExtractFromPkProvider(origin.Inner, suffix);
return (innerResult.Item1, new[] {origin.KeyOriginInfo});
default:
throw new ArgumentOutOfRangeException();
}
}
if (str == null)
throw new ArgumentNullException(nameof(str));
str = str.Trim();
var outputDescriptor = OutputDescriptor.Parse(str);
switch(outputDescriptor)
{
case OutputDescriptor.PK _:
case OutputDescriptor.Raw _:
case OutputDescriptor.Addr _:
throw new FormatException("Only HD output descriptors are supported.");
case OutputDescriptor.Combo _:
throw new FormatException("Only output descriptors of one format are supported.");
case OutputDescriptor.Multi multi:
var xpubs = multi.PkProviders.Select(provider => ExtractFromPkProvider(provider));
return (
Parse(
$"{multi.Threshold}-of-{(string.Join('-', xpubs.Select(tuple => tuple.Item1.ToString())))}{(multi.IsSorted?"":"-[keeporder]")}"),
xpubs.SelectMany(tuple => tuple.Item2).ToArray());
case OutputDescriptor.PKH pkh:
return ExtractFromPkProvider(pkh.PkProvider, "-[legacy]");
case OutputDescriptor.SH sh:
var suffix = "-[p2sh]";
if (sh.Inner is OutputDescriptor.Multi)
{
//non segwit
suffix = "-[legacy]";
}
if (sh.Inner is OutputDescriptor.Multi || sh.Inner is OutputDescriptor.WPKH ||
sh.Inner is OutputDescriptor.WSH)
{
var ds = ParseOutputDescriptor(sh.Inner.ToString());
return (Parse(ds.Item1 + suffix), ds.Item2);
};
throw new FormatException("sh descriptors are only supported with multsig(legacy or p2wsh) and segwit(p2wpkh)");
case OutputDescriptor.WPKH wpkh:
return ExtractFromPkProvider(wpkh.PkProvider, "");
case OutputDescriptor.WSH wsh:
if (wsh.Inner is OutputDescriptor.Multi)
{
return ParseOutputDescriptor(wsh.Inner.ToString());
}
throw new FormatException("wsh descriptors are only supported with multisig");
default:
throw new ArgumentOutOfRangeException(nameof(outputDescriptor));
}
}
public DerivationStrategyBase ParseElectrum(string str)
{

View File

@@ -18,8 +18,15 @@ namespace BTCPayServer
throw new ArgumentNullException(nameof(network));
if (derivationStrategy == null)
throw new ArgumentNullException(nameof(derivationStrategy));
var result = network.NBXplorerNetwork.DerivationStrategyFactory.Parse(derivationStrategy);
return new DerivationSchemeSettings(result, network) { AccountOriginal = derivationStrategy.Trim() };
var result = new DerivationSchemeSettings();
result.Network = network;
var parser = new DerivationSchemeParser(network);
if (TryParseXpub(derivationStrategy, parser, ref result, false) || TryParseXpub(derivationStrategy, parser, ref result, true))
{
return result;
}
throw new FormatException("Invalid Derivation Scheme");
}
public static bool TryParseFromJson(string config, BTCPayNetwork network, out DerivationSchemeSettings strategy)
@@ -40,13 +47,35 @@ namespace BTCPayServer
private static bool TryParseXpub(string xpub, DerivationSchemeParser derivationSchemeParser, ref DerivationSchemeSettings derivationSchemeSettings, bool electrum = true)
{
if (!electrum)
{
try
{
var result = derivationSchemeParser.ParseOutputDescriptor(xpub);
derivationSchemeSettings.AccountOriginal = xpub.Trim();
derivationSchemeSettings.AccountDerivation = result.Item1;
derivationSchemeSettings.AccountKeySettings = result.Item2.Select((path, i) => new AccountKeySettings()
{
RootFingerprint = path?.MasterFingerprint,
AccountKeyPath = path?.KeyPath,
AccountKey = result.Item1.GetExtPubKeys().ElementAt(i).GetWif(derivationSchemeParser.Network)
}).ToArray();
return true;
}
catch (Exception)
{
// ignored
}
}
try
{
derivationSchemeSettings.AccountOriginal = xpub.Trim();
derivationSchemeSettings.AccountDerivation = electrum ? derivationSchemeParser.ParseElectrum(derivationSchemeSettings.AccountOriginal) : derivationSchemeParser.Parse(derivationSchemeSettings.AccountOriginal);
derivationSchemeSettings.AccountKeySettings = new AccountKeySettings[1];
derivationSchemeSettings.AccountKeySettings[0] = new AccountKeySettings();
derivationSchemeSettings.AccountKeySettings[0].AccountKey = derivationSchemeSettings.AccountDerivation.GetExtPubKeys().Single().GetWif(derivationSchemeParser.Network);
derivationSchemeSettings.AccountKeySettings = derivationSchemeSettings.AccountDerivation.GetExtPubKeys()
.Select(key => new AccountKeySettings()
{
AccountKey = key.GetWif(derivationSchemeParser.Network)
}).ToArray();
if (derivationSchemeSettings.AccountDerivation is DirectDerivationStrategy direct && !direct.Segwit)
derivationSchemeSettings.AccountOriginal = null; // Saving this would be confusing for user, as xpub of electrum is legacy derivation, but for btcpay, it is segwit derivation
return true;
@@ -85,7 +114,7 @@ namespace BTCPayServer
return false;
}
//electrum
// Electrum
if (jobj.ContainsKey("keystore"))
{
result.Source = "ElectrumFile";
@@ -124,10 +153,29 @@ namespace BTCPayServer
catch { return false; }
}
}
// Specter
else if (jobj.ContainsKey("descriptor") && jobj.ContainsKey("blockheight"))
{
result.Source = "SpecterFile";
if (!TryParseXpub(jobj["descriptor"].Value<string>(), derivationSchemeParser, ref result, false))
{
return false;
}
if (jobj.ContainsKey("label"))
{
try
{
result.Label = jobj["label"].Value<string>();
}
catch { return false; }
}
}
// Wasabi
else
{
result.Source = "WasabiFile";
//wasabi format
if (!jobj.ContainsKey("ExtPubKey") ||
!TryParseXpub(jobj["ExtPubKey"].Value<string>(), derivationSchemeParser, ref result, false))
{
@@ -226,23 +274,23 @@ namespace BTCPayServer
[JsonIgnore]
public bool IsHotWallet => Source == "NBXplorer";
[Obsolete("Use GetAccountKeySettings().AccountKeyPath instead")]
[Obsolete("Use GetSigningAccountKeySettings().AccountKeyPath instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public KeyPath AccountKeyPath { get; set; }
public DerivationStrategyBase AccountDerivation { get; set; }
public string AccountOriginal { get; set; }
[Obsolete("Use GetAccountKeySettings().RootFingerprint instead")]
[Obsolete("Use GetSigningAccountKeySettings().RootFingerprint instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public HDFingerprint? RootFingerprint { get; set; }
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
[Obsolete("Use GetSigningAccountKeySettings().AccountKey instead")]
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]
public BitcoinExtPubKey ExplicitAccountKey { get; set; }
[JsonIgnore]
[Obsolete("Use GetAccountKeySettings().AccountKey instead")]
[Obsolete("Use GetSigningAccountKeySettings().AccountKey instead")]
public BitcoinExtPubKey AccountKey
{
get

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.WebSockets;
@@ -375,15 +376,6 @@ namespace BTCPayServer
request.Host.ToUriComponent()) + relativeOrAbsolute.ToString().WithStartingSlash(), UriKind.Absolute);
}
public static IServiceCollection ConfigureBTCPayServer(this IServiceCollection services, IConfiguration conf)
{
services.Configure<BTCPayServerOptions>(o =>
{
o.LoadArgs(conf);
});
return services;
}
public static string GetSIN(this ClaimsPrincipal principal)
{
return principal.Claims.Where(c => c.Type == Security.Bitpay.BitpayClaims.SIN).Select(c => c.Value).FirstOrDefault();
@@ -497,6 +489,17 @@ namespace BTCPayServer
return result;
}
public static DataDirectories Configure(this DataDirectories dataDirectories, IConfiguration configuration)
{
var networkType = DefaultConfiguration.GetNetworkType(configuration);
var defaultSettings = BTCPayDefaultSettings.GetDefaultSettings(networkType);
dataDirectories.DataDir = configuration["datadir"] ?? defaultSettings.DefaultDataDirectory;
dataDirectories.PluginDir = configuration["plugindir"] ?? defaultSettings.DefaultPluginDirectory;
dataDirectories.StorageDir = Path.Combine(dataDirectories.DataDir , Storage.Services.Providers.FileSystemStorage.FileSystemFileProviderService.LocalStorageDirectoryName);
dataDirectories.TempStorageDir = Path.Combine(dataDirectories.StorageDir, "tmp");
return dataDirectories;
}
private static object Private(this object obj, string privateField) => obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
private static T Private<T>(this object obj, string privateField) => (T)obj?.GetType().GetField(privateField, BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(obj);
}

View File

@@ -5,6 +5,17 @@ namespace BTCPayServer.Services
{
public static class EmailSenderExtensions
{
private static string BODY_STYLE = "font-family: Open Sans, Helvetica Neue,Arial,sans-serif; font-color: #292929;";
private static string HEADER_HTML = "<h1 style='font-size:1.2rem'>BTCPay Server</h1><br/>";
private static string BUTTON_HTML = "<a href='{button_link}' type='submit' style='min-width: 2em;min-height: 20px;text-decoration-line: none;cursor: pointer;display: inline-block;font-weight: 400;color: #fff;text-align: center;vertical-align: middle;user-select: none;background-color: #51b13e;border-color: #51b13e;border: 1px solid transparent;padding: 0.375rem 0.75rem;font-size: 1rem;line-height: 1.5;border-radius: 0.25rem;transition: color 0.15s ease-in-out, background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;'>{button_description}</a>";
private static string CallToAction(string actionName, string actionLink)
{
string button = $"{BUTTON_HTML}".Replace("{button_description}", actionName, System.StringComparison.InvariantCulture);
button = button.Replace("{button_link}", actionLink, System.StringComparison.InvariantCulture);
return button;
}
public static void SendEmailConfirmation(this IEmailSender emailSender, string email, string link)
{
emailSender.SendEmail(email, "Confirm your email",
@@ -13,9 +24,11 @@ namespace BTCPayServer.Services
public static void SendSetPasswordConfirmation(this IEmailSender emailSender, string email, string link, bool newPassword)
{
var subject = $"{(newPassword ? "Set" : "Update")} Password";
var body = $"A request has been made to {(newPassword ? "set" : "update")} your BTCPay Server password. Please confirm your password by clicking below. <br/><br/> {CallToAction(subject, HtmlEncoder.Default.Encode(link))}";
emailSender.SendEmail(email,
$"{(newPassword ? "Set" : "Reset")} Password",
$"Please {(newPassword ? "set" : "reset")} your password by clicking here: <a href='{link}'>link</a>");
subject,
$"<html><body style='{BODY_STYLE}'>{HEADER_HTML}{body}</body></html>");
}
}
}

View File

@@ -136,6 +136,11 @@ namespace BTCPayServer.HostedServices
public string[] AppId { get; set; }
public Dictionary<string, int> Items { get; set; }
public bool Deduct { get; set; }
public override string ToString()
{
return string.Empty;
}
}
}
}

View File

@@ -1,10 +1,15 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
using BTCPayServer.Logging;
using BTCPayServer.Services;
using BTCPayServer.Services.Invoices;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Logging;
namespace BTCPayServer.HostedServices
{
@@ -16,12 +21,14 @@ namespace BTCPayServer.HostedServices
private readonly InvoiceRepository _invoiceRepository;
private readonly SettingsRepository _settingsRepository;
private readonly ApplicationDbContextFactory _dbContextFactory;
private readonly IOptions<DataDirectories> _datadirs;
public DbMigrationsHostedService(InvoiceRepository invoiceRepository, SettingsRepository settingsRepository, ApplicationDbContextFactory dbContextFactory)
public DbMigrationsHostedService(InvoiceRepository invoiceRepository, SettingsRepository settingsRepository, ApplicationDbContextFactory dbContextFactory, IOptions<DataDirectories> datadirs)
{
_invoiceRepository = invoiceRepository;
_settingsRepository = settingsRepository;
_dbContextFactory = dbContextFactory;
_datadirs = datadirs;
}
@@ -35,7 +42,7 @@ namespace BTCPayServer.HostedServices
var settings = await _settingsRepository.GetSettingAsync<MigrationSettings>();
if (settings.MigratedInvoiceTextSearchPages != int.MaxValue)
{
await MigratedInvoiceTextSearchToDb(settings.MigratedInvoiceTextSearchPages.Value);
await MigratedInvoiceTextSearchToDb(settings.MigratedInvoiceTextSearchPages ?? 0);
}
// Refresh settings since these operations may run for very long time
@@ -43,14 +50,23 @@ namespace BTCPayServer.HostedServices
private async Task MigratedInvoiceTextSearchToDb(int startFromPage)
{
using var ctx = _dbContextFactory.CreateContext();
// deleting legacy DBriize database if present
var dbpath = Path.Combine(_datadirs.Value.DataDir, "InvoiceDB");
if (Directory.Exists(dbpath))
{
Directory.Delete(dbpath, true);
}
var invoiceQuery = new InvoiceQuery { IncludeArchived = true };
var totalCount = await _invoiceRepository.GetInvoicesTotal(invoiceQuery);
const int PAGE_SIZE = 1000;
var totalPages = Math.Ceiling(totalCount * 1.0m / PAGE_SIZE);
Logs.PayServer.LogInformation($"Importing {totalCount} invoices into the search table in {totalPages - startFromPage} pages");
for (int i = startFromPage; i < totalPages && !CancellationToken.IsCancellationRequested; i++)
{
Logs.PayServer.LogInformation($"Import to search table progress: {i + 1}/{totalPages} pages");
// migrate data to new table using invoices from database
using var ctx = _dbContextFactory.CreateContext();
invoiceQuery.Skip = i * PAGE_SIZE;
invoiceQuery.Take = PAGE_SIZE;
var invoices = await _invoiceRepository.GetInvoices(invoiceQuery);
@@ -74,13 +90,11 @@ namespace BTCPayServer.HostedServices
textSearch.Add(invoice.InvoiceTime.ToString(CultureInfo.InvariantCulture));
textSearch.Add(invoice.Price.ToString(CultureInfo.InvariantCulture));
textSearch.Add(invoice.Metadata.OrderId);
textSearch.Add(InvoiceRepository.ToJsonString(invoice.Metadata, null));
textSearch.Add(invoice.StoreId);
textSearch.Add(invoice.Metadata.BuyerEmail);
//
textSearch.Add(invoice.RefundMail);
// TODO: Are there more things to cache? PaymentData?
InvoiceRepository.AddToTextSearch(ctx,
new InvoiceData { Id = invoice.Id, InvoiceSearchData = new List<InvoiceSearchData>() },
textSearch.ToArray());
@@ -102,6 +116,7 @@ namespace BTCPayServer.HostedServices
_settingsRepository.UpdateSettingInContext(ctx, settings);
await ctx.SaveChangesAsync();
}
Logs.PayServer.LogInformation($"Full invoice search import successful");
}
}
}

View File

@@ -189,8 +189,8 @@ namespace BTCPayServer.HostedServices
if (status != null && error == null)
{
if (status.NetworkType != _Network.NBitcoinNetwork.NetworkType)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.NetworkType})";
if (status.NetworkType != _Network.NBitcoinNetwork.ChainName)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.NetworkType}, expected: {_Network.NBitcoinNetwork.ChainName})";
}
if (error != null)

View File

@@ -86,6 +86,8 @@ namespace BTCPayServer.HostedServices
public async Task<string> Redeliver(string deliveryId)
{
var deliveryRequest = await CreateRedeliveryRequest(deliveryId);
if (deliveryRequest is null)
return null;
EnqueueDelivery(deliveryRequest);
return deliveryRequest.Delivery.Id;
}
@@ -208,8 +210,8 @@ namespace BTCPayServer.HostedServices
try
{
var ctx = originalCtx;
var wh = (await StoreRepository.GetWebhook(ctx.WebhookId)).GetBlob();
if (!ShouldDeliver(ctx.WebhookEvent.Type, wh))
var wh = (await StoreRepository.GetWebhook(ctx.WebhookId))?.GetBlob();
if (wh is null || !ShouldDeliver(ctx.WebhookEvent.Type, wh))
continue;
var result = await SendDelivery(ctx);
if (ctx.WebhookBlob.AutomaticRedelivery &&

View File

@@ -11,6 +11,7 @@ using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Lightning;
using BTCPayServer.Logging;
using BTCPayServer.PaymentRequest;
using BTCPayServer.Payments;
@@ -69,7 +70,6 @@ namespace BTCPayServer.Hosting
}
public static IServiceCollection AddBTCPayServer(this IServiceCollection services, IConfiguration configuration)
{
services.AddSingleton<DataDirectories>();
services.AddSingleton<MvcNewtonsoftJsonOptions>(o => o.GetRequiredService<IOptions<MvcNewtonsoftJsonOptions>>().Value);
services.AddDbContext<ApplicationDbContext>((provider, o) =>
{
@@ -105,7 +105,6 @@ namespace BTCPayServer.Hosting
services.AddStartupTask<BlockExplorerLinkStartupTask>();
services.TryAddSingleton<InvoiceRepository>(o =>
{
var datadirs = o.GetRequiredService<DataDirectories>();
var dbContext = o.GetRequiredService<ApplicationDbContextFactory>();
return new InvoiceRepository(dbContext, o.GetRequiredService<BTCPayNetworkProvider>(), o.GetService<EventAggregator>());
});
@@ -115,56 +114,136 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<EventAggregator>();
services.TryAddSingleton<PaymentRequestService>();
services.TryAddSingleton<U2FService>();
services.TryAddSingleton<DataDirectories>();
services.TryAddSingleton<DatabaseOptions>(o =>
{
try
{
var dbOptions = new DatabaseOptions(o.GetRequiredService<IConfiguration>(),
o.GetRequiredService<DataDirectories>().DataDir);
if (dbOptions.DatabaseType == DatabaseType.Postgres)
{
Logs.Configuration.LogInformation($"Postgres DB used");
}
else if (dbOptions.DatabaseType == DatabaseType.MySQL)
{
Logs.Configuration.LogInformation($"MySQL DB used");
Logs.Configuration.LogWarning("MySQL is not widely tested and should be considered experimental, we advise you to use postgres instead.");
}
else if (dbOptions.DatabaseType == DatabaseType.Sqlite)
{
Logs.Configuration.LogInformation($"SQLite DB used");
Logs.Configuration.LogWarning("SQLite is not widely tested and should be considered experimental, we advise you to use postgres instead.");
}
return dbOptions;
}
catch (Exception ex)
{
throw new ConfigException($"No database option was configured. ({ex.Message})");
}
});
services.AddSingleton<ApplicationDbContextFactory>();
services.AddOptions<BTCPayServerOptions>().Configure(
(options) =>
{
options.LoadArgs(configuration);
});
services.AddOptions<DataDirectories>().Configure(
(options) =>
{
options.Configure(configuration);
});
services.AddOptions<DatabaseOptions>().Configure<IOptions<DataDirectories>>(
(options, datadirs) =>
{
var postgresConnectionString = configuration["postgres"];
var mySQLConnectionString = configuration["mysql"];
var sqliteFileName = configuration["sqlitefile"];
if (!string.IsNullOrEmpty(postgresConnectionString))
{
options.DatabaseType = DatabaseType.Postgres;
options.ConnectionString = postgresConnectionString;
}
else if (!string.IsNullOrEmpty(mySQLConnectionString))
{
options.DatabaseType = DatabaseType.MySQL;
options.ConnectionString = mySQLConnectionString;
}
else if (!string.IsNullOrEmpty(sqliteFileName))
{
var connStr = "Data Source=" + (Path.IsPathRooted(sqliteFileName)
? sqliteFileName
: Path.Combine(datadirs.Value.DataDir, sqliteFileName));
options.DatabaseType = DatabaseType.Sqlite;
options.ConnectionString = connStr;
}
else
{
throw new InvalidOperationException("No database option was configured.");
}
});
services.AddOptions<NBXplorerOptions>().Configure<BTCPayNetworkProvider>(
(options, btcPayNetworkProvider) =>
{
options.Configure(configuration, btcPayNetworkProvider);
foreach (BTCPayNetwork btcPayNetwork in btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>())
{
NBXplorerConnectionSetting setting =
new NBXplorerConnectionSetting
{
CryptoCode = btcPayNetwork.CryptoCode,
ExplorerUri = configuration.GetOrDefault<Uri>(
$"{btcPayNetwork.CryptoCode}.explorer.url",
btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultUrl),
CookieFile = configuration.GetOrDefault<string>(
$"{btcPayNetwork.CryptoCode}.explorer.cookiefile",
btcPayNetwork.NBXplorerNetwork.DefaultSettings.DefaultCookieFile)
};
options.NBXplorerConnectionSettings.Add(setting);
}
});
services.AddOptions<LightningNetworkOptions>().Configure<BTCPayNetworkProvider>(
(options, btcPayNetworkProvider) =>
{
options.Configure(configuration, btcPayNetworkProvider);
foreach (var net in btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>())
{
var lightning = configuration.GetOrDefault<string>($"{net.CryptoCode}.lightning", string.Empty);
if (lightning.Length != 0)
{
if (!LightningConnectionString.TryParse(lightning, true, out var connectionString,
out var error))
{
Logs.Configuration.LogWarning($"Invalid setting {net.CryptoCode}.lightning, " +
Environment.NewLine +
$"If you have a c-lightning server use: 'type=clightning;server=/root/.lightning/lightning-rpc', " +
Environment.NewLine +
$"If you have a lightning charge server: 'type=charge;server=https://charge.example.com;api-token=yourapitoken'" +
Environment.NewLine +
$"If you have a lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroon=abf239...;certthumbprint=2abdf302...'" +
Environment.NewLine +
$" lnd server: 'type=lnd-rest;server=https://lnd:lnd@lnd.example.com;macaroonfilepath=/root/.lnd/admin.macaroon;certthumbprint=2abdf302...'" +
Environment.NewLine +
$"If you have an eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393;bitcoin-auth=bitcoinrpcuser:bitcoinrpcpassword" +
Environment.NewLine +
$" eclair server: 'type=eclair;server=http://eclair.com:4570;password=eclairpassword;bitcoin-host=bitcoind:37393" +
Environment.NewLine +
$"Error: {error}" + Environment.NewLine +
"This service will not be exposed through BTCPay Server");
}
else
{
if (connectionString.IsLegacy)
{
Logs.Configuration.LogWarning(
$"Setting {net.CryptoCode}.lightning is a deprecated format, it will work now, but please replace it for future versions with '{connectionString.ToString()}'");
}
options.InternalLightningByCryptoCode.Add(net.CryptoCode, connectionString);
}
}
}
});
services.AddOptions<ExternalServicesOptions>().Configure<BTCPayNetworkProvider>(
(options, btcPayNetworkProvider) =>
{
options.Configure(configuration, btcPayNetworkProvider);
foreach (var net in btcPayNetworkProvider.GetAll().OfType<BTCPayNetwork>())
{
options.ExternalServices.Load(net.CryptoCode, configuration);
}
options.ExternalServices.LoadNonCryptoServices(configuration);
var services = configuration.GetOrDefault<string>("externalservices", null);
if (services != null)
{
foreach (var service in services.Split(new[] {';', ','}, StringSplitOptions.RemoveEmptyEntries)
.Select(p => (p, SeparatorIndex: p.IndexOf(':', StringComparison.OrdinalIgnoreCase)))
.Where(p => p.SeparatorIndex != -1)
.Select(p => (Name: p.p.Substring(0, p.SeparatorIndex),
Link: p.p.Substring(p.SeparatorIndex + 1))))
{
if (Uri.TryCreate(service.Link, UriKind.RelativeOrAbsolute, out var uri))
options.OtherExternalServices.AddOrReplace(service.Name, uri);
}
}
});
services.TryAddSingleton(o => configuration.ConfigureNetworkProvider());
services.TryAddSingleton<AppService>();
services.AddSingleton<PluginService>();
services.AddSingleton<IPluginHookService>(provider => provider.GetService<PluginService>());
services.AddSingleton<IPluginHookService, PluginHookService>();
services.TryAddTransient<Safe>();
services.TryAddSingleton<Ganss.XSS.HtmlSanitizer>(o =>
{
@@ -284,7 +363,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<ExplorerClientProvider>();
services.TryAddSingleton<Bitpay>(o =>
{
if (o.GetRequiredService<BTCPayServerOptions>().NetworkType == NetworkType.Mainnet)
if (o.GetRequiredService<BTCPayServerOptions>().NetworkType == ChainName.Mainnet)
return new Bitpay(new Key(), new Uri("https://bitpay.com/"));
else
return new Bitpay(new Key(), new Uri("https://test.bitpay.com/"));

View File

@@ -58,7 +58,14 @@ namespace BTCPayServer.Hosting
return;
}
if (!httpContext.Request.IsOnion() && (httpContext.Request.Headers["Accept"].ToString().StartsWith("text/html", StringComparison.InvariantCulture)))
var isHtml = httpContext.Request.Headers.TryGetValue("Accept", out var accept)
&& accept.ToString().StartsWith("text/html", StringComparison.OrdinalIgnoreCase);
var isModal = httpContext.Request.Query.TryGetValue("view", out var view)
&& view.ToString().Equals("modal", StringComparison.OrdinalIgnoreCase);
if (!string.IsNullOrEmpty(_Env.OnionUrl) &&
!httpContext.Request.IsOnion() &&
isHtml &&
!isModal)
{
var onionLocation = _Env.OnionUrl + httpContext.Request.GetEncodedPathAndQuery();
httpContext.Response.SetHeader("Onion-Location", onionLocation);

View File

@@ -13,8 +13,6 @@ using BTCPayServer.Logging;
using BTCPayServer.Payments;
using BTCPayServer.Services;
using BTCPayServer.Services.Stores;
using DBriize;
using DBriize.Utils;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

View File

@@ -21,6 +21,7 @@ using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Net.Http.Headers;
using NBitcoin;
@@ -45,11 +46,10 @@ namespace BTCPayServer.Hosting
public void ConfigureServices(IServiceCollection services)
{
Logs.Configure(LoggerFactory);
services.ConfigureBTCPayServer(Configuration);
services.AddMemoryCache();
services.AddDataProtection()
.SetApplicationName("BTCPay Server")
.PersistKeysToFileSystem(new DirectoryInfo(new DataDirectories(Configuration).DataDir));
.PersistKeysToFileSystem(new DirectoryInfo(new DataDirectories().Configure(Configuration).DataDir));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
@@ -156,7 +156,7 @@ namespace BTCPayServer.Hosting
IWebHostEnvironment env,
IServiceProvider prov,
BTCPayServerOptions options,
DataDirectories dataDirectories,
IOptions<DataDirectories> dataDirectories,
ILoggerFactory loggerFactory)
{
Logs.Configuration.LogInformation($"Root Path: {options.RootPath}");
@@ -172,7 +172,7 @@ namespace BTCPayServer.Hosting
});
}
}
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, DataDirectories dataDirectories)
private static void ConfigureCore(IApplicationBuilder app, IWebHostEnvironment env, IServiceProvider prov, ILoggerFactory loggerFactory, IOptions<DataDirectories> dataDirectories)
{
Logs.Configure(loggerFactory);
app.UsePlugins();

View File

@@ -17,10 +17,5 @@ namespace BTCPayServer.Models.ManageViewModels
public bool IsEmailConfirmed { get; set; }
[Phone]
[Display(Name = "Phone number")]
[MaxLength(50)]
public string PhoneNumber { get; set; }
}
}

View File

@@ -8,15 +8,8 @@ namespace BTCPayServer.Models.StoreViewModels
public class DerivationSchemeViewModel
{
public DerivationSchemeViewModel()
{
}
[Display(Name = "Derivation scheme")]
public string DerivationScheme
{
get; set;
}
public string DerivationScheme { get; set; }
public List<(string KeyPath, string Address, RootedKeyPath RootedKeyPath)> AddressSamples
{
@@ -25,6 +18,7 @@ namespace BTCPayServer.Models.StoreViewModels
public string CryptoCode { get; set; }
public string KeyPath { get; set; }
[Display(Name = "Root fingerprint")]
public string RootFingerprint { get; set; }
[Display(Name = "Hint address")]
public string HintAddress { get; set; }
@@ -33,16 +27,20 @@ namespace BTCPayServer.Models.StoreViewModels
public KeyPath RootKeyPath { get; set; }
[Display(Name = "Wallet File")]
[Display(Name = "Wallet file")]
public IFormFile WalletFile { get; set; }
[Display(Name = "Wallet File Content")]
[Display(Name = "Wallet file content")]
public string WalletFileContent { get; set; }
public string Config { get; set; }
public string Source { get; set; }
[Display(Name = "Derivation scheme format")]
public string DerivationSchemeFormat { get; set; }
[Display(Name = "Account key")]
public string AccountKey { get; set; }
public BTCPayNetwork Network { get; set; }
[Display(Name = "Can use hot wallet")]
public bool CanUseHotWallet { get; set; }
[Display(Name = "Can use RPC import")]
public bool CanUseRPCImport { get; set; }
public RootedKeyPath GetAccountKeypath()

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