Compare commits

...

198 Commits

Author SHA1 Message Date
3f344f2c0c Webhooks: Re-add OverPaid property to WebhookInvoiceSettledEvent ()
Fixes .
2023-12-06 09:21:04 +09:00
d050c8e3b2 Boltcard integration ()
* Boltcard integration

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

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

* Refactorings

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

Closes .

* UI updates

* Optional: No border

* Fix class

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

* Update NFC result handling and display

* Save a bit of space

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

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

* refactor

* Refactor

---------

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

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

* Bump max-width to prevent wrapping in top container

* Fix colspan

* Re-add POS data

Closes .

* Right-align amounts

* Re-order

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

* Table improvements

* Unify crypto amount display

* More formatting improvements

* Only show Subtotal if there are calculations applicable to it

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

---------

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

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

* Refactor

* Refactor once more

Minor cleanups

---------

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

* fix test

* do not cache factories

* try without useless userinfo in lnd

* Remove monero wallet files

* support simpler DI too

---------

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

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

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

* Prevent account menu close on click inside

Context: 
2023-11-21 09:56:10 +01:00
5bd8067328 bump some deps 2023-11-21 14:19:11 +09:00
9ccc42f556 Bump .NET 8.0 () 2023-11-21 14:11:17 +09:00
3ee4f43eb5 Remove CurrentRefund property in InvoiceData () 2023-11-21 12:52:40 +09:00
d1bf47a5c0 Bumping LND to 0.17.2-beta 2023-11-20 13:28:20 -08:00
ccf9cfa332 Minor cleanups () 2023-11-20 11:18:19 +01:00
773f8a9aea Apps: Filter list lookups by available app types ()
* Apps: Filter list lookups by available app types

Uniunstalling a plugin might lead to then unavailable app types, as the entries remain in the database. The list lookups need to account for that, otherwise unavailable apps cause crashes and misbehaviour.

Fixes .

* Make a hashset

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-11-20 10:48:56 +09:00
dd62e166a1 BIP329: Use application/jsonl as MIME type ()
There's an [ongoing discussion](https://github.com/wardi/jsonlines/issues/19) about what the MIME type for [JSONL](https://jsonlines.org/) files should be. Making it `application/jsonl` leads to the file being downloaded according to my testing, which prevents browsers from opening them in a new window and parsing them as JSON, which fixes .
2023-11-20 10:46:36 +09:00
2fb72d5aa6 Payment Request: Improve public view ()
* Payment Request: Improve public view

Closes .

* Test fix

* Extract Vue utils

* Improve payment history

* Fix amount display

* Unify receipt and payment request tables

* Re-add text confirmation for copying to clipboard

* Minor print optimizations

* Wording: Rename Description to Memo

* Open view links in new window

* View updates
2023-11-20 10:45:43 +09:00
46f0818765 Bumping LND to 0.17.1-beta 2023-11-14 19:45:41 -08:00
96569ae4aa POS Cart: Add options for search and categories display () 2023-11-13 13:59:14 +01:00
f2b1e5f93e fix report crash when some values are null 2023-11-10 12:30:12 +01:00
2326894a2b Responsive editor improvements () 2023-11-09 10:27:33 +01:00
c15f02ddbf Reporting: UI improvements () 2023-11-09 10:26:00 +01:00
7708084331 Pull payment improvements () 2023-11-09 10:17:52 +01:00
696a414e95 POS Keypad: Add plus and change clear functionality ()
Closes .
2023-11-02 20:03:34 +01:00
c16dfb2dcb POS and Crowdfund: Improve item editor ()
* POS and Crowdfund: Improve item editor

Makes it work the same way as the form editor: Drag and drop for reordering and inline editing without modal.

* Upload component
2023-11-02 19:58:03 +01:00
c979c4774c POS Cart: Horizontal scrollable filters () 2023-11-02 08:36:27 +01:00
e82281d273 switch pos to metadata in invoice create view ()
Co-authored-by: d11n <mail@dennisreimann.de>
2023-11-02 08:13:48 +01:00
27c22d5e33 Unify list views () 2023-11-02 08:12:28 +01:00
6acc545b66 Greenfield: LNURLPay store payment method fixes () 2023-11-02 08:11:32 +01:00
609ec0989f Do not activate Blazor in Wizard screens () 2023-10-27 10:16:36 +02:00
b702621a04 Simplify vault logic by introducing a VaultClient () 2023-10-27 11:54:15 +09:00
89041a6744 Wallet Send: Fill label from BIP21 ()
Fixes .
2023-10-27 09:59:12 +09:00
c485c109e6 Bumping LND to 0.17.0-beta () 2023-10-26 10:47:03 +02:00
29a49d5f71 Fix: In pull payment page, the amount of claims wasn't displayed () 2023-10-25 13:51:27 +02:00
a5fafc4864 Update Passport tooltips () 2023-10-24 13:23:10 +02:00
a921504bcf Bump HtmlSanitizer 2023-10-18 19:33:43 +09:00
027154a4d3 Update changelog 2023-10-18 19:27:20 +09:00
bf1a1368ff Forms: Make zip code a required field in predefined address form ()
Closes .
2023-10-18 19:21:56 +09:00
097ffbf8a3 Greenfield: Add missing checkout (V2) settings ()
* Greenfield: Add missing checkout (V2) settings

Closes .

* Fix swagger
2023-10-18 19:20:05 +09:00
ec076d1560 Fix: BTCPayServer.HostedServices.BitpayIPNSender fail to send notifications on some locale (Fix ) 2023-10-18 19:07:30 +09:00
8dadfa2111 Reporting fixes () 2023-10-18 10:09:03 +02:00
c8ee6ead0b Adjust swagger doc to latest change in Greenfield API 2023-10-18 05:31:00 +02:00
018e4c501d Changelog 1.11.7 ()
* Changelog 1.11.7

* Apply suggestions from code review

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-10-13 23:17:17 +09:00
99a0b70cfa Fix form value setter ()
* Fix form value setter

* Fix test parallelization

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-10-13 10:08:16 +09:00
314a1352ec Design system updates () 2023-10-13 09:06:22 +09:00
901e6be21e Fix processing badge color 2023-10-12 14:52:26 +02:00
d58dde950e Fix pay report ()
* Fix pay report

* Make sure we use 11 decimals in reports for lightning payments

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-10-12 13:51:50 +09:00
8ac18b74df Checkout: Prevent re-rendering of payment details rows ()
Potentially fixes .
2023-10-12 09:35:47 +09:00
2846c38ff5 Invoice: Unify status display and functionality ()
* Invoice: Unify status display and functionality

Consolidates the invoice status display and functionality (mark setted or invalid) across the dashboard, list and details pages.

* Test fix

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-10-11 23:12:45 +09:00
d44efce225 Simplify code 2023-10-11 21:49:51 +09:00
d3dca7e808 fix lq errors and tests ()
* fix lq errors and tests

* more fixes

* more fixes

* fix

* fix xmr
2023-10-11 21:12:33 +09:00
41e3828eea Reporting: Improve rounding and display ()
* Reporting: Improve rounding and display

* Fix test

* Refactor

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-10-11 20:48:40 +09:00
9e76b4d28e Fix swagger () 2023-10-10 14:15:07 +09:00
ef03497350 Fix build warning ()
Removes unused `string payoutSource` and shortens return with to ternary operator.
2023-10-10 12:30:48 +09:00
e5a2aeb145 Pull Payment: Add QR scanner for destination and infer payment method ()
* Pull Payment: Add QR scanner for destination and infer payment method

Closes .

* Test fix
2023-10-10 12:30:09 +09:00
229a4ea56c Invoice: Improve payment details ()
* Invoice: Improve payment details

Clearer description and display, especially for overpayments. Closes .

* Further refinements

* Test fix
2023-10-10 12:28:00 +09:00
f20e6d3768 Greenfield: allow delete user by email too () 2023-10-10 12:26:23 +09:00
1d210eb6e3 Crowdfund: Improve no perks case ()
If there are no perks configured, do not display the perks sidebar and contribute custom amount directly, when the main CTA "Contribute" is clicked.

Before it opened a mopdal, where one had to select the only option (custom amount) manually — so this gets rid of the extra step.

Closes .
2023-10-06 22:58:02 +09:00
d8422a979f Fix number of rates ()
* Ripio had api changed
* Exchange rate host now requires an api key so removed
* Removed unused argoneum rate provider code
* switched cop and ugx to yadio
* bumped exchange sharp lib as poloniex api changed and rate source was not working
2023-10-06 16:08:50 +09:00
0cf6d39f02 If shitcoins are removed, dont try to hash its cryptocode for nbx () 2023-10-06 16:06:17 +09:00
076c20a3b7 attempt to fix different casing in cryptocode of payments 2023-09-29 13:03:18 +02:00
0cfb0ba890 Email Rules: Require either recipients or customer email option () 2023-09-28 08:36:12 +02:00
44a7e9387e bump 2023-09-27 17:02:49 +09:00
e71954ee34 update lnurl 2023-09-27 09:13:12 +02:00
9cd9e84be6 Fix: After a while, a busy server would send error HTTP 500 ()
This was due to Blazor which attempt to reconnect when the connection
is broken.

Before this, it would try again indefinitely, with this PR, it tries
only for around 3 minutes.

After this, the Blazor circuit should be dead anyway, so it's useless
to try again.
2023-09-27 16:05:57 +09:00
25af9c4227 Improve receipt info display ()
* Improve receipt info display

Displays the info in correct order and adds optional info if tip was given with a percentage.

* Test fix

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-09-26 22:50:04 +09:00
72a99bf9a6 Recommend Yadio for ARS currency (see ) 2023-09-26 22:21:53 +09:00
f1228523cb Try fix flackiness of CanUsePullPaymentsViaUI 2023-09-26 22:20:25 +09:00
a45d368115 Use exchangeratehost as recommended rates for COP 2023-09-26 21:19:42 +09:00
16433dc183 Hide 'Connection established' when connection to server come back () 2023-09-26 16:40:02 +09:00
0a956fdc73 Remove some useless intermediary type from Rate Source () 2023-09-26 16:37:40 +09:00
75396f491b Fix: Exchangerate.host falsly appear as Yadio in the UI (Fix ) 2023-09-26 14:45:46 +09:00
66a064e78b Disable prism if old version 2023-09-22 23:43:06 +09:00
c4f8c4c7b4 Update changelog and bump ()
* Update changelog and bump

* Update Changelog.md

Co-authored-by: d11n <mail@dennisreimann.de>

* Update Changelog.md

Co-authored-by: d11n <mail@dennisreimann.de>

* Update Changelog.md

Co-authored-by: d11n <mail@dennisreimann.de>

* Update Changelog.md

Co-authored-by: d11n <mail@dennisreimann.de>

* Update Changelog.md

Co-authored-by: d11n <mail@dennisreimann.de>

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2023-09-22 21:32:57 +09:00
22bbafa659 bump lnurl 2023-09-22 12:22:46 +02:00
8cdfaba20c Fix: Revert to default block explorer button wasn't working ()
* Fix: Revert to default block explorer button wasn't working

* Update BTCPayServer/Views/UIServer/Policies.cshtml

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

* Update BTCPayServer/Views/UIServer/Policies.cshtml

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

* Improve UI

---------

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-22 18:51:54 +09:00
7da82826fb API: Document payment method IDs ()
* API: Document payment method IDs

This seems to be a source of confusion (see e.g. ), so I thought it'd be best to document the payment method IDs as an enum, so that we can refer to it in the several places they are used.

* Remove enum
2023-09-22 18:49:20 +09:00
9a46a64cad Test fixes ()
* Test fixes

* Update BTCPayServer.Tests/ThirdPartyTests.cs

* Update BTCPayServer.Tests/ThirdPartyTests.cs
2023-09-22 11:48:59 +02:00
33198d693d Introduce archive pull payment permission and add Show QR code in view pull payment view ()
* Introduce archive pull payment permission

* Add show qr option on pull payments

* Fix test

* update docs

* fix test

* Minor UI updates

* Update wording

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-22 10:24:53 +02:00
eaeb7021d5 Fix POST redirect form submit ()
The `submit()` method cannot be invoked on forms without a submit button. This changes it to call the method via the JS prototype, which can be seen as a workaround.

Checked this in Firefox and Chrome. Fixes .
2023-09-22 15:03:57 +09:00
b443acd43b Fix: Transient error 500 when accessing the wallet page () () 2023-09-19 17:52:33 +09:00
616883648f Move bitcoin payment data specific stuff in NBXplorerListener () 2023-09-19 10:32:41 +09:00
7873f94848 Email Rules: Add default texts and document placeholders ()
Applies default subject and body text on editing to simplify email rule setup. Once the text is edited manually, the defaus  aren't applied on switching the rule type.

Also documents the placeholders that can be used.
2023-09-19 10:10:36 +09:00
17d1832dad Payment Request: Add processing status for on-chain payments ()
Closes .
2023-09-19 10:10:13 +09:00
f034e2cd65 Wallet Receive: Update address formatting ()
* Wallet Receive: Update address formatting

Closes .

* Fix tests
2023-09-19 09:56:11 +09:00
19de73f9da Allow configuring nfc permission beforehand ()
* Allow configuring nfc permission beforehand

* UI improvements

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-19 09:55:47 +09:00
00acbccd7f Add payouts report () 2023-09-19 09:55:15 +09:00
77d8e202d3 Wallet: Delete custom labels ()
* Tom Select improvements

* Wallet: Delete custom labels

Closes .
2023-09-19 09:55:04 +09:00
44df8cf0c5 Rewrite the Notification dropdown with Blazor ()
* Rewrite the Notification dropdown with Blazor

* Test fix

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-18 10:55:05 +09:00
e694568674 Blazor Server: Improve wording () 2023-09-17 18:45:57 +02:00
163f805f3b Plugins: Add hook for resolving Lightning Address () 2023-09-14 12:53:48 +02:00
492512f527 Dashboard: Show revenue data for keypad ()
* POS: Display fixes

* Dashboard: Fix Top Items component

* Dashboard: Fix App Sales component

* Dashboard: Show revenue data for keypad

Closes .
2023-09-14 09:26:47 +09:00
73a4ac599c Add Blazor server ()
* Add Blazor server

* Improve Blazor status UI

* Improve UX

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-13 13:13:15 +09:00
4aedf76f1f Dashboard: Paid invoices in the last 7 days ()
Adjust the prior number of transactions metric as discussed with @pavlenex. We now show the number of paid invoices instead of transactions, as this metric is more meaningful.

Closes .
2023-09-13 09:02:02 +09:00
2d38113c66 Remove legacy confusing export () 2023-09-12 16:33:37 +09:00
445e1b7bd9 NFC: Fix error display ()
Simple fix, the wrong variable was used. Fixes .
2023-09-12 13:48:19 +09:00
019ac7ae31 Checkout: Cheating improvements ()
Minor updates to the cheating options:
- Some browsers do not submit disabled fields, hence I made the amount field readonly in case of Lightning.
- Convert remaining amount when switching from onchain BTC to Lightning sats.
2023-09-12 13:48:01 +09:00
2b3b025bd8 Login: Re-add Remember Me button ()
Closes .
2023-09-12 12:16:37 +09:00
57bc90ad03 Archive stores and apps ()
* Add flags and migration

* Archive store

* Archive apps
2023-09-11 09:59:17 +09:00
089e16020e Update LND image version ()
See .
2023-09-10 10:07:44 +09:00
0c4f31794d Test fix: Update rate retrieval skipping parameters () 2023-09-09 09:46:09 +02:00
cdffe9b355 Bump LNURL 2023-09-07 10:02:32 +02:00
5a28cf9e87 Release new version of client 2023-09-06 08:21:46 +09:00
3b05de7f30 Fix: Crash caused by very old point of sales invoices () () 2023-09-05 15:32:49 +09:00
79b2f1652b Changelog and bump 2023-09-02 23:22:59 +09:00
b32e0e7cce Fix : Error on the MigrationStartupTask 2023-09-02 23:12:37 +09:00
1f9fbbee22 Update README ()
Co-authored-by: A. I. Oleynikov <self@oleynikov.ai>
2023-09-01 16:03:51 +02:00
8c9f325c9f Display wallet balance in default currency ()
* Display wallet balance in default currency

* Cleanups

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-09-01 15:29:41 +09:00
9bf1e35bf4 Use _blank link targt for payment scheme links ()
In addition to . Fixes .
2023-08-31 09:44:10 +09:00
32e830a1c5 Fix slow 'Fasts' tests 2023-08-28 09:40:44 +09:00
561bae071f Timeout CanGetRateCryptoCurrenciesByDefault 2023-08-26 21:13:55 +09:00
08b6942c59 Bump, changelog 2023-08-26 21:03:45 +09:00
4564f9a46c Small improvements ()
* BUmp LNURL

* Show app view link in nav when not enoguh permission to modify

* FIx permission misalignment on create pull payments

We have explicit permissions for pull payment creation, even allow them to be created through the invoices, but the create ui and cta were blocked behind  canmodify store permission.

* Make Ln address pass an invoiceId in the context to resolve breaking change
2023-08-26 20:50:07 +09:00
58a1c6d2c8 Parse POS string data for invoice details display ()
* Parse POS string data for invoice details display

Fixes .

* Improve POS data display
2023-08-26 20:48:48 +09:00
97acec340c fix lnaddress nav item permission 2023-08-24 16:31:49 +02:00
52790a6954 bump clightning 2023-08-24 20:59:15 +09:00
af6249a741 bump lightning lib 2023-08-24 20:59:15 +09:00
17064ab3c8 POS: Unify item display in editor () 2023-08-24 08:51:22 +02:00
1487bf4ff5 Unset link targt for payment scheme links ()
Potential fix for  — see the discussion in that issue for details.

This change should be non-invasive, I tested the links in regular as well as modal mode and they worked in Firefox, Brave and Chrome.
2023-08-24 13:37:27 +09:00
e8c0858558 POS: Fix alignment of items in static view ()
Items in static view weren't center aligned. This matches the classes in the cart view. Fixes .
2023-08-23 11:11:41 +02:00
56fa3fe8f2 Fix crash on /wallets/transactions with non zero skip parameter (Fix ) 2023-08-23 16:11:25 +09:00
583813883c Simplified logic for receipt amount () 2023-08-23 10:43:34 +09:00
c69f95bdce Do not block payments on LN while syncing if it is not internal node () 2023-08-22 13:45:50 +02:00
b3df403980 Fix LN payout manual payments UI crashing when payouts are not tied to pull payment 2023-08-15 15:11:04 +02:00
90ce75ee21 remove store ID from view request url () 2023-08-13 19:26:21 +02:00
1c5fcfe094 bump v 2023-08-11 15:55:11 +02:00
45c1fb42ee Changelog v1.11.2 2023-08-11 15:39:08 +02:00
64bd493996 POS: Unify item display ()
Display unifications for static and cart view.
2023-08-11 15:37:43 +02:00
ec6029409e Improve invoice filter wording ()
Closes .
2023-08-11 15:08:44 +02:00
c0fc31c69a Improve invoices status filter () 2023-08-10 20:23:18 +02:00
b5d0188f21 Receipt improvements () 2023-08-10 13:57:54 +02:00
0ccbaf4bd6 Greenfield: Fix invoice lookup by capitalized status ()
All statuses need to be lowercased before lookup, this wasn't the case for e.g. `Expired`.

Fixes .
2023-08-10 13:34:09 +02:00
ed43fb2071 POS fixes () 2023-08-09 14:47:28 +02:00
d67ebd957e POS: Handle flexible price items in cart view () 2023-08-09 09:31:19 +02:00
19d360a543 Fix: typo in InvoiceEntity.cs ()
Minumum -> Minimum
2023-08-07 09:26:37 +02:00
7dc41ebcea Email Rules: Improve validation ()
Came across this while testing things and the "Please fill all required fields before testing" message wasn't clear, because the required fields were not marked.

Co-authored-by: Andrew Camilleri <evilkukka@gmail.com>
2023-08-07 09:10:48 +02:00
1eb7c727f3 POS fixes () 2023-08-05 10:44:59 +02:00
ede8171408 Checkout: Fix language select UI bug () 2023-08-04 07:44:50 +02:00
2538f3d8f6 fix https://github.com/Kukks/BTCPayServerPlugins/issues/18 2023-08-03 20:48:46 +02:00
ac64f5e395 Merge pull request from dennisreimann/supporters
Update supporters
2023-08-03 19:45:09 +02:00
1a7a731b54 Update supporters
Improve colors and visual balance
2023-08-03 14:58:32 +02:00
86f4d48bcb c-lightning to CLN; remove ptarmigan. () 2023-08-01 17:21:00 +03:00
83536bee88 Fix BTG rate provider 2023-07-29 10:00:34 +02:00
abfd6ea1dc update changelog and version 2023-07-29 09:48:47 +02:00
688e873f7a fixes 2023-07-29 09:15:12 +02:00
c88df08350 fixes 2023-07-29 09:15:11 +02:00
82586590a7 potentially fixes 2023-07-29 09:15:11 +02:00
88c66f30f2 fixes 2023-07-29 09:15:10 +02:00
9132592717 fixes 2023-07-29 09:15:10 +02:00
c0ffab768a fix ident 2023-07-29 09:15:10 +02:00
69190081c8 ui+checkout: fix language cutoff bug () 2023-07-28 21:24:30 +02:00
093206cf1e add changelog 2023-07-27 15:19:48 +02:00
a0110b7570 Merge remote-tracking branch 'origin/feat/changelog-1.11' 2023-07-27 15:14:53 +02:00
6d65feca4c update changelog 2023-07-27 08:39:58 +02:00
95be0242b6 add opensats and update strike logo ()
Co-authored-by: pavlenex <pavle@pavle.org>
2023-07-27 08:39:40 +02:00
79e121c3af Disabling playing of the invoice sound for existing stores 2023-07-26 10:42:00 -05:00
676ac2fe46 Changelog 1.11.0 2023-07-26 09:11:26 -05:00
8eabdab53a Preventing entering of negative tips and discounts in POS 2023-07-26 07:26:53 -05:00
957fb09ffc Reverting logic of how paid amount is displayed on the receipt 2023-07-26 07:26:32 -05:00
4bffe117a9 Do not show cheatmode in release, fix warnigns 2023-07-25 10:50:34 +09:00
05b01a13c8 Fix NRE error in PoS report 2023-07-24 23:20:17 +09:00
08e21c1a5d Fix report view 2023-07-24 23:13:11 +09:00
4d5245605d bump 2023-07-24 22:59:18 +09:00
453548d614 Checkout v2: Play sound when invoice is paid ()
* Checkout v2: Play sound when invoice is paid

Closes .

* Refactoring: Use low-level audio API to play the sound

Allows to play the sound regardless of browser permissions.

* Add audio file detection

* Use model state for file upload errors

* Add default sound and customizing option

* Fix mp3 detection

* Add sounds

* Update defaults

* Add nfcread and error sounds

* Improve label wording

* Replace sound

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-07-24 22:57:24 +09:00
95a0614ae1 Support accepting 0 amount bolt 11 invoices for payouts ()
* Support accepting 0 amount bolt 11 invoices for payouts

* add test

* handle validation better

* fix case when we just want pp to provide amt

* Update BTCPayServer/HostedServices/PullPaymentHostedService.cs

* Update BTCPayServer/HostedServices/PullPaymentHostedService.cs

* Update BTCPayServer/Data/Payouts/LightningLike/UILightningLikePayoutController.cs

* Update UILightningLikePayoutController.cs

* fix null

* fix payments of payouts on cln

* add comment

* bump lightning lib

---------

Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-07-24 20:40:26 +09:00
36ea17a6b7 Introduce Payout metadata for api and plugins ()
* Introduce Payout metadata for api and plugins

* fix controller

* fix metadata requirement

* save an object

* pr changes
2023-07-24 18:37:18 +09:00
dc986959fd Add reporting feature ()
* Add reporting feature

* Remove nodatime

* Add summaries

* work...

* Add chart title

* Fix error

* Allow to set hour in the field

* UI updates

* Fix fake data

* ViewDefinitions can be dynamic

* Add items sold

* Sticky table headers

* Update JS and remove jQuery usages

* JS click fix

* Handle tag all invoices for app

* fix dup row in items report

* Can cancel invoice request

* Add tests

* Fake data for items sold

* Rename Items to Products, improve navigation F5

* Use bordered table for summaries

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-07-24 09:24:32 +09:00
845e2881fa POS Cart redesign ()
* Move POS assets

* WIP

* Refactor into common Vue mixin

* Offcanvas updates

* Unifications across POS views

* POSData view fix

* Number and test fixes

* Update cart width

* Fix test

* More view unification

* Hide cart when emptied

* Validate cart

* Header improvement

* Increase remove icon size

* Animate add to cart action

* Offcanvas for mobile, sidebar for desktop

* ui+pos: updates icon size + badge + label

* Remove cart table headers

* Use same size for Cart and Shop headlines

* Update search placeholder

* Bump horizontal  input padding

* Increase sidebar width

* Bump badge font size

* Fix manipulating the quantity of line items

* Fix cart icon

* Update cart display

* updates empty button

* Rounded search input

* Remove cart button on desktop

* Fix dark accent color

* More accent fixes

* Fix plus/minus alignment

* Update BTCPayServer/Views/Shared/PointOfSale/Public/Cart.cshtml

* Apply suggestions from code review

---------

Co-authored-by: dstrukt <gfxdsign@gmail.com>
2023-07-22 21:15:41 +09:00
2e4be9310c Design system updates () 2023-07-21 09:27:37 +02:00
a2faa6fd59 Minor fixes () 2023-07-21 09:05:50 +02:00
0a78846e8d Stop using bitpay's CreateInvoice for non bitpay API usage () 2023-07-21 09:08:32 +09:00
589 changed files with 15462 additions and 9215 deletions
.gitignore
BTCPayServer.Abstractions
BTCPayServer.Client
BTCPayServer.Common
BTCPayServer.Data
BTCPayServer.PluginPacker
BTCPayServer.Rating
BTCPayServer.Tests
BTCPayServer
APDUVaultTransport.csBTCPayServer.csproj
Blazor
ColorPalette.cs
Components
Configuration
Controllers
Data
DerivationSchemeSettings.csExtensions.cs
Extensions
FileTypeDetector.cs
Filters
Forms
HostedServices
Hosting
HwiWebSocketTransport.cs
Models
PaymentRequest
Payments
PayoutProcessors
Plugins
Altcoins
Bitcoin
Crowdfund
LightningAddresssResolver.cs
NFC
PluginManager.csPluginServiceCollection.cs
PointOfSale
Program.cs
Properties
Security
Services
Storage
TagHelpers
VaultClient.csVaultHWITransport.cs
Views
Shared
UIAccount
UIApps
UICustodianAccounts
UIForms
UIInvoice
UILNURL
UILightningAutomatedPayoutProcessors
UIManage
UIMoneroLikeStore
UINotifications
UIOnChainAutomatedPayoutProcessors
UIPaymentRequest
UIPublicLightningNodeInfo
UIPullPayment
UIReports
UIServer
UIStorePullPayments
UIStores
UIUserStores
UIWallets
_ViewImports.cshtml
wwwroot
Build
Changelog.mdREADME.mdamd64.Dockerfilearm32v7.Dockerfilearm64v8.Dockerfilebtcpayserver.sln.DotSettings

3
.gitignore vendored

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

@ -31,11 +31,11 @@
<None Include="icon.png" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="6.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="6.0.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="6.0.7" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="6.0.1" />
<PackageReference Include="HtmlSanitizer" Version="8.0.723" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.0" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="8.0.0-beta.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Client\BTCPayServer.Client.csproj" />

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

@ -20,6 +20,15 @@ namespace BTCPayServer.Abstractions.Extensions
Relative
}
public static void SetBlazorAllowed(this ViewDataDictionary viewData, bool allowed)
{
viewData["BlazorAllowed"] = allowed;
}
public static bool IsBlazorAllowed(this ViewDataDictionary viewData)
{
return viewData["BlazorAllowed"] is not false;
}
public static void SetActivePage<T>(this ViewDataDictionary viewData, T activePage, string title = null, string activeId = null)
where T : IConvertible
{

@ -5,7 +5,6 @@ using System.Reflection;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json.Linq;
using Npgsql.Internal.TypeHandlers.GeometricHandlers;
namespace BTCPayServer.Abstractions.Form;
@ -105,31 +104,7 @@ public class Form
}
}
public void SetValues(JObject values)
{
var fields = GetAllFields().ToDictionary(k => k.FullName, k => k.Field);
SetValues(fields, new List<string>(), values);
}
private void SetValues(Dictionary<string, Field> fields, List<string> path, JObject values)
{
foreach (var prop in values.Properties())
{
List<string> propPath = new List<string>(path.Count + 1);
propPath.AddRange(path);
propPath.Add(prop.Name);
if (prop.Value.Type == JTokenType.Object)
{
SetValues(fields, propPath, (JObject)prop.Value);
}
else if (prop.Value.Type == JTokenType.String)
{
var fullName = string.Join('_', propPath.Where(s => !string.IsNullOrEmpty(s)));
if (fields.TryGetValue(fullName, out var f) && !f.Constant)
f.Value = prop.Value.Value<string>();
}
}
}
}

@ -1,4 +1,5 @@
using Ganss.XSS;
using System.Web;
using Ganss.Xss;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;
@ -21,6 +22,11 @@ namespace BTCPayServer.Abstractions.Services
{
return _htmlHelper.Raw(_htmlSanitizer.Sanitize(value));
}
public IHtmlContent RawEncode(string value)
{
return _htmlHelper.Raw(HttpUtility.HtmlEncode(_htmlSanitizer.Sanitize(value)));
}
public IHtmlContent Json(object model)
{

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

@ -16,7 +16,7 @@
<Platforms>AnyCPU</Platforms>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">1.7.2</Version>
<Version Condition=" '$(Version)' == '' ">1.7.3</Version>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@ -30,9 +30,9 @@
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.3.21" />
<PackageReference Include="NBitcoin" Version="7.0.24" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.5.1" />
<PackageReference Include="NBitcoin" Version="7.0.31" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<ItemGroup>
<None Include="icon.png" Pack="true" PackagePath="\" />

@ -55,7 +55,7 @@ namespace BTCPayServer.Client
}
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null, string labelFilter = null,
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null, string labelFilter = null, int skip = 0,
CancellationToken token = default)
{
var query = new Dictionary<string, object>();
@ -67,6 +67,10 @@ namespace BTCPayServer.Client
{
query.Add(nameof(labelFilter), labelFilter);
}
if (skip != 0)
{
query.Add(nameof(skip), skip);
}
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query), token);

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

@ -28,6 +28,8 @@ namespace BTCPayServer.Client.Models
public PosViewType DefaultView { get; set; }
public bool ShowCustomAmount { get; set; } = false;
public bool ShowDiscount { get; set; } = true;
public bool ShowSearch { get; set; } = true;
public bool ShowCategories { get; set; } = true;
public bool EnableTips { get; set; } = true;
public string CustomAmountPayButtonText { get; set; } = null;
public string FixedAmountPayButtonText { get; set; } = null;
@ -37,9 +39,9 @@ namespace BTCPayServer.Client.Models
public string RedirectUrl { get; set; } = null;
public bool? RedirectAutomatically { get; set; } = null;
public bool? RequiresRefundEmail { get; set; } = null;
public bool? Archived { get; set; } = null;
public string FormId { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
public CheckoutType? CheckoutType { get; set; } = null;
}
public enum CrowdfundResetEvery
@ -78,6 +80,7 @@ namespace BTCPayServer.Client.Models
public bool? DisplayPerksValue { get; set; } = null;
public bool? DisplayPerksRanking { get; set; } = null;
public bool? SortPerksByPopularity { get; set; } = null;
public bool? Archived { get; set; } = null;
public string[] Sounds { get; set; } = null;
public string[] AnimationColors { get; set; } = null;
}

@ -1,8 +1,11 @@
#nullable enable
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models;
public class CreatePayoutThroughStoreRequest : CreatePayoutRequest
{
public string? PullPaymentId { get; set; }
public bool? Approved { get; set; }
public JObject? Metadata { get; set; }
}

@ -1,12 +1,12 @@
namespace BTCPayServer.Client.Models
namespace BTCPayServer.Client.Models;
public enum InvoiceExceptionStatus
{
public enum InvoiceExceptionStatus
{
None,
PaidLate,
PaidPartial,
Marked,
Invalid,
PaidOver
}
None,
PaidLate,
PaidPartial,
Marked,
Invalid,
PaidOver
}

@ -1,8 +1,12 @@
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class LNURLPayPaymentMethodBaseData
{
public bool UseBech32Scheme { get; set; }
[JsonProperty("lud12Enabled")]
public bool LUD12Enabled { get; set; }
public LNURLPayPaymentMethodBaseData()

@ -16,11 +16,12 @@ namespace BTCPayServer.Client.Models
{
}
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme)
public LNURLPayPaymentMethodData(string cryptoCode, bool enabled, bool useBech32Scheme, bool lud12Enabled)
{
Enabled = enabled;
CryptoCode = cryptoCode;
UseBech32Scheme = useBech32Scheme;
LUD12Enabled = lud12Enabled;
}
}
}

@ -7,7 +7,7 @@ namespace BTCPayServer.Client.Models
public class PaymentRequestData : PaymentRequestBaseData
{
[JsonConverter(typeof(StringEnumConverter))]
public PaymentRequestData.PaymentRequestStatus Status { get; set; }
public PaymentRequestStatus Status { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset CreatedTime { get; set; }
public string Id { get; set; }
@ -16,7 +16,8 @@ namespace BTCPayServer.Client.Models
{
Pending = 0,
Completed = 1,
Expired = 2
Expired = 2,
Processing = 3
}
}
}

@ -31,5 +31,6 @@ namespace BTCPayServer.Client.Models
public PayoutState State { get; set; }
public int Revision { get; set; }
public JObject PaymentProof { get; set; }
public JObject Metadata { get; set; }
}
}

@ -9,6 +9,8 @@ namespace BTCPayServer.Client.Models
public string AppType { get; set; }
public string Name { get; set; }
public string StoreId { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? Archived { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset Created { get; set; }
}
@ -19,6 +21,8 @@ namespace BTCPayServer.Client.Models
public string DefaultView { get; set; }
public bool ShowCustomAmount { get; set; }
public bool ShowDiscount { get; set; }
public bool ShowSearch { get; set; }
public bool ShowCategories { get; set; }
public bool EnableTips { get; set; }
public string Currency { get; set; }
public object Items { get; set; }

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

@ -37,14 +37,20 @@ namespace BTCPayServer.Client.Models
public bool AnyoneCanCreateInvoice { get; set; }
public string DefaultCurrency { get; set; }
public bool RequiresRefundEmail { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
public CheckoutType CheckoutType { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public CheckoutType? CheckoutType { get; set; }
public bool LightningAmountInSatoshi { get; set; }
public bool LightningPrivateRouteHints { get; set; }
public bool OnChainWithLnInvoiceFallback { get; set; }
public bool LazyPaymentMethods { get; set; }
public bool RedirectAutomatically { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool Archived { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool ShowRecommendedFee { get; set; } = true;
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
@ -70,6 +76,17 @@ namespace BTCPayServer.Client.Models
public bool PayJoinEnabled { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? AutoDetectLanguage { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowPayInWalletButton { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? ShowStoreHeader { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? CelebratePayment { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public bool? PlaySoundOnPayment { get; set; }
public InvoiceData.ReceiptOptions Receipt { get; set; }

@ -0,0 +1,62 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using BTCPayServer.JsonConverters;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Client.Models;
public class StoreReportRequest
{
public string ViewName { get; set; }
public TimePeriod TimePeriod { get; set; }
}
public class StoreReportResponse
{
public class Field
{
public Field()
{
}
public Field(string name, string type)
{
Name = name;
Type = type;
}
public string Name { get; set; }
public string Type { get; set; }
}
public IList<Field> Fields { get; set; } = new List<Field>();
public List<JArray> Data { get; set; }
public DateTimeOffset From { get; set; }
public DateTimeOffset To { get; set; }
public List<ChartDefinition> Charts { get; set; }
public int GetIndex(string fieldName)
{
return Fields.ToList().FindIndex(f => f.Name == fieldName);
}
}
public class ChartDefinition
{
public string Name { get; set; }
public List<string> Groups { get; set; } = new List<string>();
public List<string> Totals { get; set; } = new List<string>();
public bool HasGrandTotal { get; set; }
public List<string> Aggregates { get; set; } = new List<string>();
public List<string> Filters { get; set; } = new List<string>();
}
public class TimePeriod
{
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? From { get; set; }
[JsonConverter(typeof(NBitcoin.JsonConverters.DateTimeToUnixTimeConverter))]
public DateTimeOffset? To { get; set; }
}

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BTCPayServer.Client.Models
{
public class StoreReportsResponse
{
public string ViewName { get; set; }
public StoreReportResponse.Field[] Fields
{
get;
set;
}
}
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1,46 +0,0 @@
using System.Collections.Generic;
using NBitcoin;
using NBXplorer;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitLitecoin()
{
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LTC");
Add(new BTCPayNetwork()
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Litecoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
? "https://live.blockcypher.com/ltc/tx/{0}/"
: "http://explorer.litecointools.com/tx/{0}",
NBXplorerNetwork = nbxplorerNetwork,
DefaultRateRules = new[]
{
"LTC_X = LTC_BTC * BTC_X",
"LTC_BTC = coingecko(LTC_BTC)"
},
CryptoImagePath = "imlegacy/litecoin.svg",
LightningImagePath = "imlegacy/litecoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy },
{0x049d7cb2U, DerivationType.SegwitP2SH },
{0x04b24746U, DerivationType.Segwit },
}
: new Dictionary<uint, DerivationType>()
{
{0x043587cfU, DerivationType.Legacy },
{0x044a5262U, DerivationType.SegwitP2SH },
{0x045f1cf6U, DerivationType.Segwit }
}
});
}
}
}

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

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

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

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

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

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

@ -1,50 +0,0 @@
#if ALTCOINS
using System;
using System.Collections.Generic;
using System.Linq;
using BTCPayServer.Common;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
namespace BTCPayServer
{
public class ElementsBTCPayNetwork : BTCPayNetwork
{
public string NetworkCryptoCode { get; set; }
public uint256 AssetId { get; set; }
public override bool ReadonlyWallet { get; set; } = true;
public override IEnumerable<(MatchedOutput matchedOutput, OutPoint outPoint)> GetValidOutputs(
NewTransactionEvent evtOutputs)
{
return evtOutputs.Outputs.Where(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId).Select(output =>
{
var outpoint = new OutPoint(evtOutputs.TransactionData.TransactionHash, output.Index);
return (output, outpoint);
});
}
public override List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
{
return transactionInformationSet.FindAll(information =>
information.Outputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
information.Inputs.Any(output =>
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId));
}
public override PaymentUrlBuilder GenerateBIP21(string cryptoInfoAddress, decimal? cryptoInfoDue)
{
//precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000
//precision 8: 10 = 10
var money = cryptoInfoDue / (decimal)Math.Pow(10, 8 - Divisibility);
var builder = base.GenerateBIP21(cryptoInfoAddress, money);
builder.QueryParams.Add("assetid", AssetId.ToString());
return builder;
}
}
}
#endif

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

@ -1,28 +0,0 @@
using NBitcoin;
namespace BTCPayServer
{
public partial class BTCPayNetworkProvider
{
public void InitMonero()
{
Add(new MoneroLikeSpecificBtcPayNetwork()
{
CryptoCode = "XMR",
DisplayName = "Monero",
Divisibility = 12,
BlockExplorerLink =
NetworkType == ChainName.Mainnet
? "https://www.exploremonero.com/transaction/{0}"
: "https://testnet.xmrchain.net/tx/{0}",
DefaultRateRules = new[]
{
"XMR_X = XMR_BTC * BTC_X",
"XMR_BTC = kraken(XMR_BTC)"
},
CryptoImagePath = "/imlegacy/monero.svg",
UriScheme = "monero"
});
}
}
}

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

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

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

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

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

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

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

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

@ -1,3 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
using BTCPayServer.Abstractions.Models;
using Microsoft.EntityFrameworkCore;

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

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

@ -14,6 +14,7 @@ namespace BTCPayServer.Data
public DateTimeOffset Created { get; set; }
public bool TagAllInvoices { get; set; }
public string Settings { get; set; }
public bool Archived { get; set; }
internal static void OnModelCreating(ModelBuilder builder)
{

@ -30,9 +30,6 @@ namespace BTCPayServer.Data
public List<PendingInvoiceData> PendingInvoices { get; set; }
public List<InvoiceSearchData> InvoiceSearchData { get; set; }
public List<RefundData> Refunds { get; set; }
public string CurrentRefundId { get; set; }
[ForeignKey("Id,CurrentRefundId")]
public RefundData CurrentRefund { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
@ -42,8 +39,6 @@ namespace BTCPayServer.Data
.WithMany(a => a.Invoices).OnDelete(DeleteBehavior.Cascade);
builder.Entity<InvoiceData>().HasIndex(o => o.StoreDataId);
builder.Entity<InvoiceData>().HasIndex(o => o.OrderId);
builder.Entity<InvoiceData>()
.HasOne(o => o.CurrentRefund);
builder.Entity<InvoiceData>().HasIndex(o => o.Created);
if (databaseFacade.IsNpgsql())

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

@ -48,6 +48,7 @@ namespace BTCPayServer.Data
public IEnumerable<StoreSettingData> Settings { get; set; }
public IEnumerable<FormData> Forms { get; set; }
public IEnumerable<StoreRole> StoreRoles { get; set; }
public bool Archived { get; set; }
internal static void OnModelCreating(ModelBuilder builder, DatabaseFacade databaseFacade)
{

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

@ -0,0 +1,39 @@
using BTCPayServer.Data;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace BTCPayServer.Migrations
{
[DbContext(typeof(ApplicationDbContext))]
[Migration("20230906135844_AddArchivedFlagForStoresAndApps")]
public partial class AddArchivedFlagForStoresAndApps : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<bool>(
name: "Archived",
table: "Stores",
nullable: false,
defaultValue: false);
migrationBuilder.AddColumn<bool>(
name: "Archived",
table: "Apps",
nullable: false,
defaultValue: false);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Archived",
table: "Stores");
migrationBuilder.DropColumn(
name: "Archived",
table: "Apps");
}
}
}

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

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

@ -79,6 +79,9 @@ namespace BTCPayServer.Migrations
b.Property<string>("AppType")
.HasColumnType("TEXT");
b.Property<bool>("Archived")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
@ -284,9 +287,6 @@ namespace BTCPayServer.Migrations
b.Property<DateTimeOffset>("Created")
.HasColumnType("TEXT");
b.Property<string>("CurrentRefundId")
.HasColumnType("TEXT");
b.Property<string>("CustomerEmail")
.HasColumnType("TEXT");
@ -313,8 +313,6 @@ namespace BTCPayServer.Migrations
b.HasIndex("StoreDataId");
b.HasIndex("Id", "CurrentRefundId");
b.ToTable("Invoices");
});
@ -751,6 +749,9 @@ namespace BTCPayServer.Migrations
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<bool>("Archived")
.HasColumnType("INTEGER");
b.Property<string>("DefaultCrypto")
.HasColumnType("TEXT");
@ -1245,12 +1246,6 @@ namespace BTCPayServer.Migrations
.HasForeignKey("StoreDataId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("BTCPayServer.Data.RefundData", "CurrentRefund")
.WithMany()
.HasForeignKey("Id", "CurrentRefundId");
b.Navigation("CurrentRefund");
b.Navigation("StoreData");
});

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

@ -1,37 +0,0 @@
using System;
namespace BTCPayServer.Rating
{
public enum RateSource
{
Coingecko,
Direct
}
public class AvailableRateProvider
{
public string Name { get; }
public string Url { get; }
public string Id { get; }
public RateSource Source { get; }
public AvailableRateProvider(string id, string name, string url) : this(id, name, url, RateSource.Direct)
{
}
public AvailableRateProvider(string id, string name, string url, RateSource source)
{
Id = id;
Name = name;
Url = url;
Source = source;
}
public string DisplayName =>
Source switch
{
RateSource.Direct => Name,
RateSource.Coingecko => $"{Name} (via CoinGecko)",
_ => throw new NotSupportedException(Source.ToString())
};
}
}

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

@ -20,13 +20,13 @@ namespace BTCPayServer.Services.Rates
}
public class CurrencyNameTable
{
public static CurrencyNameTable Instance = new CurrencyNameTable();
public static CurrencyNameTable Instance = new();
public CurrencyNameTable()
{
_Currencies = LoadCurrency().ToDictionary(k => k.Code);
_Currencies = LoadCurrency().ToDictionary(k => k.Code, StringComparer.InvariantCultureIgnoreCase);
}
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new Dictionary<string, IFormatProvider>();
static readonly Dictionary<string, IFormatProvider> _CurrencyProviders = new();
public NumberFormatInfo GetNumberFormatInfo(string currency, bool useFallback)
{

@ -19,7 +19,7 @@ namespace BTCPayServer.Rating
public static CurrencyPair Parse(string str)
{
if (!TryParse(str, out var result))
throw new FormatException("Invalid currency pair");
throw new FormatException($"Invalid currency pair ({str})");
return result;
}
public static bool TryParse(string str, out CurrencyPair value)

@ -1,29 +0,0 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates
{
public class ArgoneumRateProvider : IRateProvider
{
private readonly HttpClient _httpClient;
public ArgoneumRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public RateSourceInfo RateSourceInfo => new("argoneum", "Argoneum", "https://rates.argoneum.net/rates");
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
// Example result: AGM to BTC rate: {"agm":5000000.000000}
var response = await _httpClient.GetAsync("https://rates.argoneum.net/rates/btc", cancellationToken);
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
var value = jobj["agm"].Value<decimal>();
return new[] { new PairRate(new CurrencyPair("BTC", "AGM"), new BidAsk(value)) };
}
}
}

File diff suppressed because one or more lines are too long

@ -1,40 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Rating;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Services.Rates;
public class ExchangeRateHostRateProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("exchangeratehost", "Yadio", "https://api.exchangerate.host/latest?base=BTC");
private readonly HttpClient _httpClient;
public ExchangeRateHostRateProvider(HttpClient httpClient)
{
_httpClient = httpClient ?? new HttpClient();
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync(RateSourceInfo.Url, cancellationToken);
response.EnsureSuccessStatusCode();
var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
if(jobj["success"].Value<bool>() is not true || !jobj["base"].Value<string>().Equals("BTC", StringComparison.InvariantCulture))
throw new Exception("exchangerate.host returned a non success response or the base currency was not the requested one (BTC)");
var results = (JObject) jobj["rates"] ;
//key value is currency code to rate value
var list = new List<PairRate>();
foreach (var item in results)
{
string name = item.Key;
var value = item.Value.Value<decimal>();
list.Add(new PairRate(new CurrencyPair("BTC", name), new BidAsk(value)));
}
return list.ToArray();
}
}

@ -13,7 +13,7 @@ namespace BTCPayServer.Services.Rates
{
public class RipioExchangeProvider : IRateProvider
{
public RateSourceInfo RateSourceInfo => new("ripio", "Ripio", "https://api.exchange.ripio.com/api/v1/rate/all/");
public RateSourceInfo RateSourceInfo => new("ripio", "Ripio", "https://api.ripiotrade.co/v4/public/tickers");
private readonly HttpClient _httpClient;
public RipioExchangeProvider(HttpClient httpClient)
{
@ -21,9 +21,9 @@ namespace BTCPayServer.Services.Rates
}
public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
{
var response = await _httpClient.GetAsync("https://api.exchange.ripio.com/api/v1/rate/all/", cancellationToken);
var response = await _httpClient.GetAsync("https://api.ripiotrade.co/v4/public/tickers", cancellationToken);
response.EnsureSuccessStatusCode();
var jarray = (JArray)(await response.Content.ReadAsAsync<JArray>(cancellationToken));
var jarray = (JArray)(await response.Content.ReadAsAsync<JObject>(cancellationToken))["data"];
return jarray
.Children<JObject>()
.Select(jobj => ParsePair(jobj))

@ -1,21 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BTCPayServer.Rating
#nullable enable
namespace BTCPayServer.Rating;
public enum RateSource
{
public class RateSourceInfo
{
public RateSourceInfo(string id, string displayName, string url)
{
Id = id;
DisplayName = displayName;
Url = url;
}
public string Id { get; set; }
public string DisplayName { get; set; }
public string Url { get; set; }
}
Coingecko,
Direct
}
public record RateSourceInfo(string Id, string DisplayName, string Url, RateSource Source = RateSource.Direct);

@ -85,14 +85,13 @@ namespace BTCPayServer.Services.Rates
bgFetcher.RefreshRate = TimeSpan.FromMinutes(1.0);
bgFetcher.ValidatyTime = TimeSpan.FromMinutes(5.0);
Providers.Add(supportedExchange.Id, bgFetcher);
var rsi = coingecko.RateSourceInfo;
AvailableRateProviders.Add(new(rsi.Id, rsi.DisplayName, rsi.Url, RateSource.Coingecko));
AvailableRateProviders.Add(coingecko.RateSourceInfo);
}
}
AvailableRateProviders.Sort((a, b) => StringComparer.Ordinal.Compare(a.DisplayName, b.DisplayName));
}
public List<AvailableRateProvider> AvailableRateProviders { get; } = new List<AvailableRateProvider>();
public List<RateSourceInfo> AvailableRateProviders { get; } = new List<RateSourceInfo>();
public async Task<QueryRateResult> QueryRates(string exchangeName, CancellationToken cancellationToken)
{

@ -663,7 +663,7 @@ donation:
Assert.Equal(3, vmview.Items.Length);
Assert.Equal("good apple", vmview.Items[0].Title);
Assert.Equal("orange", vmview.Items[1].Title);
Assert.Equal(10.0m, vmview.Items[1].Price.Value);
Assert.Equal(10.0m, vmview.Items[1].Price);
Assert.Equal("{0} Purchase", vmview.ButtonText);
Assert.Equal("Nicolas Sexy Hair", vmview.CustomButtonText);
Assert.Equal("Wanna tip?", vmview.CustomTipText);
@ -680,7 +680,7 @@ donation:
Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
invoices = user.BitPay.GetInvoices();
invoices = await user.BitPay.GetInvoicesAsync();
var appleInvoice = invoices.SingleOrDefault(invoice => invoice.ItemCode.Equals("apple"));
Assert.NotNull(appleInvoice);
Assert.Equal("good apple", appleInvoice.ItemDesc);
@ -689,7 +689,7 @@ donation:
var action = Assert.IsType<RedirectToActionResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 6.6m, choiceKey: "donation").Result);
Assert.Equal(nameof(UIInvoiceController.Checkout), action.ActionName);
invoices = user.BitPay.GetInvoices();
invoices = await user.BitPay.GetInvoicesAsync();
var donationInvoice = invoices.Single(i => i.Price == 6.6m);
Assert.NotNull(donationInvoice);
Assert.Equal("CAD", donationInvoice.Currency);

@ -5,6 +5,7 @@ using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Models.WalletViewModels;
using BTCPayServer.Plugins.Altcoins;
using BTCPayServer.Services.Wallets;
using BTCPayServer.Tests.Logging;
using Microsoft.AspNetCore.Mvc;
@ -52,11 +53,12 @@ namespace BTCPayServer.Tests
{
tester.ActivateLBTC();
await tester.StartAsync();
//https://github.com/ElementsProject/elements/issues/956
await tester.LBTCExplorerNode.SendCommandAsync("rescanblockchain");
var user = tester.NewAccount();
user.GrantAccess();
user.RegisterDerivationScheme("LBTC");
user.RegisterDerivationScheme("USDT");
user.RegisterDerivationScheme("ETB");
await user.GrantAccessAsync();
await tester.LBTCExplorerNode.GenerateAsync(4);
//no tether on our regtest, lets create it and set it
var tether = tester.NetworkProvider.GetNetwork<ElementsBTCPayNetwork>("USDT");
@ -75,6 +77,10 @@ namespace BTCPayServer.Tests
.AssetId = etb.AssetId;
user.RegisterDerivationScheme("LBTC");
user.RegisterDerivationScheme("USDT");
user.RegisterDerivationScheme("ETB");
//test: register 2 assets on the same elements network and make sure paying an invoice on one does not affect the other in any way
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(0.1m, "BTC"));
Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
@ -82,7 +88,7 @@ namespace BTCPayServer.Tests
//1 lbtc = 1 btc
Assert.Equal(1, ci.Rate);
var star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
1, "UNSET", lbtc.AssetId);
1, "UNSET",false, lbtc.AssetId.ToString());
TestUtils.Eventually(() =>
{
@ -95,8 +101,7 @@ namespace BTCPayServer.Tests
ci = invoice.CryptoInfo.Single(info => info.CryptoCode.Equals("USDT"));
Assert.Equal(3, invoice.SupportedTransactionCurrencies.Count);
star = await tester.LBTCExplorerNode.SendCommandAsync("sendtoaddress", ci.Address, ci.Due, "", "", false, true,
1, "UNSET", tether.AssetId);
star = tester.LBTCExplorerNode.SendCommand("sendtoaddress", ci.Address, decimal.Parse(ci.Due), "x", "z", false, true, 1, "unset", false, tether.AssetId.ToString());
TestUtils.Eventually(() =>
{

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

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

@ -54,13 +54,33 @@ namespace BTCPayServer.Tests
Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps2.ListApps(user2.StoreId).Result).Model);
Assert.Single(appList.Apps);
Assert.Empty(appList2.Apps);
Assert.Equal("test", appList.Apps[0].AppName);
Assert.Equal(apps.CreatedAppId, appList.Apps[0].Id);
Assert.True(appList.Apps[0].Role.ToPermissionSet(appList.Apps[0].StoreId).Contains(Policies.CanModifyStoreSettings, appList.Apps[0].StoreId));
Assert.Equal(user.StoreId, appList.Apps[0].StoreId);
Assert.IsType<NotFoundResult>(apps2.DeleteApp(appList.Apps[0].Id));
Assert.IsType<ViewResult>(apps.DeleteApp(appList.Apps[0].Id));
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(appList.Apps[0].Id).Result);
Assert.Equal("test", app.AppName);
Assert.Equal(apps.CreatedAppId, app.Id);
Assert.True(app.Role.ToPermissionSet(app.StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
Assert.Equal(user.StoreId, app.StoreId);
// Archive
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
Assert.EndsWith("/settings/crowdfund", redirect.Url);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
Assert.Empty(appList.Apps);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId, archived: true).Result).Model);
app = appList.Apps[0];
Assert.True(app.Archived);
Assert.IsType<NotFoundResult>(await crowdfund.ViewCrowdfund(app.Id));
// Unarchive
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
Assert.EndsWith("/settings/crowdfund", redirect.Url);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
app = appList.Apps[0];
Assert.False(app.Archived);
var crowdfundViewModel = await crowdfund.UpdateCrowdfund(app.Id).AssertViewModelAsync<UpdateCrowdfundViewModel>();
crowdfundViewModel.Enabled = true;
Assert.IsType<RedirectToActionResult>(crowdfund.UpdateCrowdfund(app.Id, crowdfundViewModel, "save").Result);
Assert.IsType<ViewResult>(await crowdfund.ViewCrowdfund(app.Id));
// Delete
Assert.IsType<NotFoundResult>(apps2.DeleteApp(app.Id));
Assert.IsType<ViewResult>(apps.DeleteApp(app.Id));
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(app.Id).Result);
Assert.Equal(nameof(UIStoresController.Dashboard), redirectToAction.ActionName);
appList = await apps.ListApps(user.StoreId).AssertViewModelAsync<ListAppsViewModel>();
Assert.Empty(appList.Apps);

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

@ -1,4 +1,5 @@
using System;
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -97,7 +98,7 @@ retry:
}
Thread.Sleep(50);
}
Assert.False(true, "Elements was found");
Assert.Fail("Elements was found");
}
public static void UntilJsIsReady(this WebDriverWait wait)
@ -198,7 +199,9 @@ retry:
public static bool ElementDoesNotExist(this IWebDriver driver, By selector)
{
Assert.Throws<NoSuchElementException>(() =>
Assert.Throws<NoSuchElementException>(
[DebuggerStepThrough]
() =>
{
driver.FindElement(selector);
});

@ -10,6 +10,7 @@ using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
@ -18,11 +19,15 @@ using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.JsonConverters;
using BTCPayServer.Logging;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Bitcoin;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.Plugins;
using BTCPayServer.Plugins.Bitcoin;
using BTCPayServer.Rating;
using BTCPayServer.Services;
using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Rates;
@ -130,7 +135,7 @@ namespace BTCPayServer.Tests
var tags = new HashSet<String>(g.Select(o => o.Tag));
if (tags.Count != 1)
{
Assert.False(true, $"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
Assert.Fail($"All docker images '{g.Key}' in docker-compose.yml and docker-compose.altcoins.yml should have the same tags. (Found {string.Join(',', tags)})");
}
}
}
@ -346,11 +351,70 @@ namespace BTCPayServer.Tests
Assert.True(Torrc.TryParse(input, out torrc));
Assert.Equal(expected, torrc.ToString());
}
[Fact]
public void CanParseCartItems()
{
Assert.True(AppService.TryParsePosCartItems(new JObject()
{
{"cart", new JArray()
{
new JObject()
{
{ "id", "ddd"},
{"price", 4},
{"count", 1}
}
}}
}, out var items));
Assert.Equal("ddd", items[0].Id);
Assert.Equal(1, items[0].Count);
Assert.Equal(4, items[0].Price);
// Using legacy parsing
Assert.True(AppService.TryParsePosCartItems(new JObject()
{
{"cart", new JArray()
{
new JObject()
{
{ "id", "ddd"},
{"price", new JObject()
{
{ "value", 8.49m }
}
},
{"count", 1}
}
}}
}, out items));
Assert.Equal("ddd", items[0].Id);
Assert.Equal(1, items[0].Count);
Assert.Equal(8.49m, items[0].Price);
Assert.False(AppService.TryParsePosCartItems(new JObject()
{
{"cart", new JArray()
{
new JObject()
{
{ "id", "ddd"},
{"price", new JObject()
{
{ "value", "nocrahs" }
}
},
{"count", 1}
}
}}
}, out items));
}
[Fact]
public void CanCalculateDust()
{
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
#pragma warning disable CS0618
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod()
@ -396,7 +460,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanCalculateCryptoDue()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = networkProvider;
#pragma warning disable CS0618
@ -584,7 +648,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanAcceptInvoiceWithTolerance()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
@ -659,7 +723,7 @@ namespace BTCPayServer.Tests
}
[Fact]
public void CanDetectImage()
public void CanDetectFileType()
{
Assert.True(FileTypeDetector.IsPicture(new byte[] { 0x42, 0x4D }, "test.bmp"));
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x42, 0x4D }, ".bmp"));
@ -672,6 +736,15 @@ namespace BTCPayServer.Tests
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x3C, 0x73, 0x76, 0x67 }, "test.jpg"));
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0xFF }, "e.jpg"));
Assert.False(FileTypeDetector.IsPicture(new byte[] { }, "empty.jpg"));
Assert.False(FileTypeDetector.IsPicture(new byte[] { 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23 }, "music.mp3"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x49, 0x44, 0x33, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23 }, "music.mp3"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x52, 0x49, 0x46, 0x46, 0x24, 0x9A, 0x08, 0x00, 0x57, 0x41 }, "music.wav"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0xFF, 0xF1, 0x50, 0x80, 0x1C, 0x3F, 0xFC, 0xDA, 0x00, 0x4C }, "music.aac"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22, 0x04, 0x80 }, "music.flac"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00 }, "music.ogg"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0x1A, 0x45, 0xDF, 0xA3, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 }, "music.weba"));
Assert.True(FileTypeDetector.IsAudio(new byte[] { 0xFF, 0xF3, 0xE4, 0x64, 0x00, 0x20, 0xAD, 0xBD, 0x04, 0x00 }, "music.mp3"));
}
[Fact]
@ -695,7 +768,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
var tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
@ -707,7 +780,7 @@ namespace BTCPayServer.Tests
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = null,
@ -744,7 +817,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanParseDerivationSchemes()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var parser = new DerivationSchemeParser(networkProvider.BTC);
// xpub
@ -770,7 +843,7 @@ namespace BTCPayServer.Tests
Assert.Equal(expected, inner.ToString());
// Output Descriptor
networkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
networkProvider = CreateNetworkProvider(ChainName.Mainnet);
parser = new DerivationSchemeParser(networkProvider.BTC);
var od = "wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48";
(strategyBase, rootedKeyPath) = parser.ParseOutputDescriptor(od);
@ -812,8 +885,8 @@ namespace BTCPayServer.Tests
[Fact]
public void ParseDerivationSchemeSettings()
{
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = new BTCPayNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var testnet = CreateNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = CreateNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var root = new Mnemonic(
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
.DeriveExtKey();
@ -885,6 +958,40 @@ namespace BTCPayServer.Tests
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("Specter", specter.Label);
Assert.Null(error);
//BSMS BIP129, Nunchuk
var bsms = @"BSMS 1.0
wsh(sortedmulti(1,[5c9e228d/48'/0'/0'/2']xpub6EgGHjcvovyN3nK921zAGPfuB41cJXkYRdt3tLGmiMyvbgHpss4X1eRZwShbEBb1znz2e2bCkCED87QZpin3sSYKbmCzQ9Sc7LaV98ngdeX/**,[2b0e251e/48'/0'/0'/2']xpub6DrimHB8KUSkPvmJ8Pk8RE769EdDm2VEoZ8MBz76w9QupP8Py4wexs4Pa3aRB1LUEhc9GyY6ypDWEFFRCgqeDQePcyWQfjtmintrehq3JCL/**))
/0/*,/1/*
bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku
";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(bsms,
mainnet, out var nunchuk, out error));
Assert.Equal(2, nunchuk.AccountKeySettings.Length);
//check that the account key settings match those in bsms string
Assert.Equal("5c9e228d", nunchuk.AccountKeySettings[0].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[0].AccountKeyPath.ToString());
Assert.Equal("2b0e251e", nunchuk.AccountKeySettings[1].RootFingerprint.ToString());
Assert.Equal("48'/0'/0'/2'", nunchuk.AccountKeySettings[1].AccountKeyPath.ToString());
var multsig = Assert.IsType < MultisigDerivationStrategy >
(Assert.IsType<P2WSHDerivationStrategy>(nunchuk.AccountDerivation).Inner);
Assert.True(multsig.LexicographicOrder);
Assert.Equal(1, multsig.RequiredSignatures);
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
var line =nunchuk.AccountDerivation.GetLineFor(deposit).Derive(0);
Assert.Equal(BitcoinAddress.Create("bc1qfzu57kgu5jthl934f9xrdzzx8mmemx7gn07tf0grnvz504j6kzusu2v0ku", Network.Main).ScriptPubKey,
line.ScriptPubKey);
Assert.Equal("BSMS", nunchuk.Source);
Assert.Null(error);
// Failure case
Assert.False(DerivationSchemeSettings.TryParseFromWalletFile(
@ -895,7 +1002,7 @@ namespace BTCPayServer.Tests
}
[Fact]
public void CheckRatesProvider()
public async Task CheckRatesProvider()
{
var spy = new SpyRateProvider();
RateRules.TryParse("X_X = bittrex(X_X);", out var rateRules);
@ -907,23 +1014,22 @@ namespace BTCPayServer.Tests
var fetch = new BackgroundFetcherRateProvider(spy);
fetch.DoNotAutoFetchIfExpired = true;
factory.Providers.Add("bittrex", fetch);
var fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter()
.GetResult();
var fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertHit();
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertNotHit();
fetch.RefreshRate = TimeSpan.FromSeconds(1.0);
Thread.Sleep(1020);
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.ValidatyTime = TimeSpan.FromSeconds(1.0);
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertHit();
fetch.GetRatesAsync(default).GetAwaiter().GetResult();
await fetch.GetRatesAsync(default);
Thread.Sleep(1000);
Assert.Throws<InvalidOperationException>(() => fetch.GetRatesAsync(default).GetAwaiter().GetResult());
await Assert.ThrowsAsync<InvalidOperationException>(() => fetch.GetRatesAsync(default));
}
public static RateProviderFactory CreateBTCPayRateFactory()
@ -1177,7 +1283,7 @@ namespace BTCPayServer.Tests
[Fact]
public void HasCurrencyDataForNetworks()
{
var btcPayNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var btcPayNetworkProvider = CreateNetworkProvider(ChainName.Regtest);
foreach (var network in btcPayNetworkProvider.GetAll())
{
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
@ -1756,8 +1862,7 @@ namespace BTCPayServer.Tests
new KeyValuePair<string, string>("chains", "usdt")}
})
});
var networkProvider = config.ConfigureNetworkProvider(BTCPayLogs);
var networkProvider = CreateNetworkProvider(config);
Assert.NotNull(networkProvider.GetNetwork("LBTC"));
Assert.NotNull(networkProvider.GetNetwork("USDT"));
}
@ -1765,9 +1870,9 @@ namespace BTCPayServer.Tests
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme()
{
var testnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
var testnetNetworkProvider = CreateNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = CreateNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = CreateNetworkProvider(ChainName.Mainnet);
var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
@ -1933,7 +2038,7 @@ namespace BTCPayServer.Tests
{
#pragma warning disable CS0618
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var networkBTC = networkProvider.GetNetwork("BTC");
var networkLTC = networkProvider.GetNetwork("LTC");
InvoiceEntity invoiceEntity = new InvoiceEntity();
@ -2050,7 +2155,7 @@ namespace BTCPayServer.Tests
{
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
};
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", new BTCPayNetworkProvider(ChainName.Regtest).BTC);
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
scheme.Source = "ManualDerivationScheme";
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
@ -2070,7 +2175,7 @@ namespace BTCPayServer.Tests
.Select(o =>
{
var entity = JsonConvert.DeserializeObject<InvoiceEntity>(o.ToString());
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
return entity.DerivationStrategies.ToString();
})
.ToHashSet();

@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Form;
using BTCPayServer.Forms;
using Microsoft.AspNetCore.Http;
@ -10,16 +11,25 @@ using Xunit.Abstractions;
namespace BTCPayServer.Tests;
[Trait("Fast", "Fast")]
[Collection(nameof(NonParallelizableCollectionDefinition))]
[Trait("Integration", "Integration")]
public class FormTests : UnitTestBase
{
public FormTests(ITestOutputHelper helper) : base(helper)
{
}
[Fact]
public void CanParseForm()
[Fact(Timeout = TestUtils.TestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanParseForm()
{
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
var service = tester.PayTester.GetService<FormDataService>();
var form = new Form()
{
Fields = new List<Field>
@ -40,8 +50,6 @@ public class FormTests : UnitTestBase
}
}
};
var providers = new FormComponentProviders(new List<IFormComponentProvider>());
var service = new FormDataService(null, providers);
Assert.False(service.IsFormSchemaValid(form.ToString(), out _, out _));
form = new Form
{
@ -164,7 +172,7 @@ public class FormTests : UnitTestBase
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
Clear(form);
form.SetValues(obj);
service.SetValues(form, obj);
obj = service.GetValues(form);
Assert.Equal("original", obj["invoice"]["test"].Value<string>());
Assert.Equal("updated", obj["invoice_item3"].Value<string>());
@ -182,12 +190,73 @@ public class FormTests : UnitTestBase
}
}
};
form.SetValues(obj);
service.SetValues(form, obj);
obj = service.GetValues(form);
Assert.Null(obj["test"].Value<string>());
form.SetValues(new JObject { ["test"] = "hello" });
service.SetValues(form, new JObject { ["test"] = "hello" });
obj = service.GetValues(form);
Assert.Equal("hello", obj["test"].Value<string>());
var req = service.GenerateInvoiceParametersFromForm(form);
Assert.Null(req.Amount);
Assert.Null(req.Currency);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount",
Type = "number",
Value = "1"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(1, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_adjustment",
Type = "number",
Value = "1"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(2, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_adjustment2",
Type = "number",
Value = "2"
});
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}currency",
Type = "text",
Value = "eur"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal("eur", req.Currency);
Assert.Equal(4, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_multiply_adjustment",
Type = "number",
Value = "2"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(8, req.Amount);
form.Fields.Add(new Field
{
Name = $"{FormDataService.InvoiceParameterPrefix}amount_multiply_adjustment1",
Type = "number",
Value = "2"
});
req = service.GenerateInvoiceParametersFromForm(form);
Assert.Equal(16, req.Amount);
}
private void Clear(Form form)

@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
@ -16,8 +16,6 @@ using BTCPayServer.Models.InvoicingModels;
using BTCPayServer.Payments;
using BTCPayServer.Payments.Lightning;
using BTCPayServer.PayoutProcessors;
using BTCPayServer.PayoutProcessors.OnChain;
using BTCPayServer.Plugins;
using BTCPayServer.Services;
using BTCPayServer.Services.Custodian.Client.MockCustodian;
using BTCPayServer.Services.Notifications;
@ -25,7 +23,6 @@ using BTCPayServer.Services.Notifications.Blobs;
using BTCPayServer.Services.Stores;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Hosting;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
@ -301,6 +298,7 @@ namespace BTCPayServer.Tests
Assert.Equal(user.StoreId, app.StoreId);
Assert.Equal("PointOfSale", app.AppType);
Assert.Equal("test app title", app.Title);
Assert.False(app.Archived);
// Make sure we return a 404 if we try to get an app that doesn't exist
await AssertHttpError(404, async () =>
@ -324,17 +322,20 @@ namespace BTCPayServer.Tests
new CreatePointOfSaleAppRequest()
{
AppName = "new app name",
Title = "new app title"
Title = "new app title",
Archived = true
}
);
// Test generic GET app endpoint first
retrievedApp = await client.GetApp(app.Id);
Assert.Equal("new app name", retrievedApp.Name);
Assert.True(retrievedApp.Archived);
// Test the POS-specific endpoint also
var retrievedPosApp = await client.GetPosApp(app.Id);
Assert.Equal("new app name", retrievedPosApp.Name);
Assert.Equal("new app title", retrievedPosApp.Title);
Assert.True(retrievedPosApp.Archived);
// Make sure we return a 404 if we try to delete an app that doesn't exist
await AssertHttpError(404, async () =>
@ -466,6 +467,7 @@ namespace BTCPayServer.Tests
Assert.Equal("test app from API", app.Name);
Assert.Equal(user.StoreId, app.StoreId);
Assert.Equal("Crowdfund", app.AppType);
Assert.False(app.Archived);
// Make sure we return a 404 if we try to get an app that doesn't exist
await AssertHttpError(404, async () =>
@ -482,11 +484,13 @@ namespace BTCPayServer.Tests
Assert.Equal(app.Name, retrievedApp.Name);
Assert.Equal(app.StoreId, retrievedApp.StoreId);
Assert.Equal(app.AppType, retrievedApp.AppType);
Assert.False(retrievedApp.Archived);
// Test the crowdfund-specific endpoint also
var retrievedPosApp = await client.GetCrowdfundApp(app.Id);
Assert.Equal(app.Name, retrievedPosApp.Name);
Assert.Equal(app.Title, retrievedPosApp.Title);
var retrievedCfApp = await client.GetCrowdfundApp(app.Id);
Assert.Equal(app.Name, retrievedCfApp.Name);
Assert.Equal(app.Title, retrievedCfApp.Title);
Assert.False(retrievedCfApp.Archived);
// Make sure we return a 404 if we try to delete an app that doesn't exist
await AssertHttpError(404, async () =>
@ -536,10 +540,12 @@ namespace BTCPayServer.Tests
Assert.Equal(posApp.Name, apps[0].Name);
Assert.Equal(posApp.StoreId, apps[0].StoreId);
Assert.Equal(posApp.AppType, apps[0].AppType);
Assert.False(apps[0].Archived);
Assert.Equal(crowdfundApp.Name, apps[1].Name);
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
Assert.False(apps[1].Archived);
// Get all apps for all store now
apps = await client.GetAllApps();
@ -549,15 +555,17 @@ namespace BTCPayServer.Tests
Assert.Equal(posApp.Name, apps[0].Name);
Assert.Equal(posApp.StoreId, apps[0].StoreId);
Assert.Equal(posApp.AppType, apps[0].AppType);
Assert.False(apps[0].Archived);
Assert.Equal(crowdfundApp.Name, apps[1].Name);
Assert.Equal(crowdfundApp.StoreId, apps[1].StoreId);
Assert.Equal(crowdfundApp.AppType, apps[1].AppType);
Assert.False(apps[1].Archived);
Assert.Equal(newApp.Name, apps[2].Name);
Assert.Equal(newApp.StoreId, apps[2].StoreId);
Assert.Equal(newApp.AppType, apps[2].AppType);
Assert.False(apps[2].Archived);
}
[Fact(Timeout = TestTimeout)]
@ -878,7 +886,7 @@ namespace BTCPayServer.Tests
TestLogs.LogInformation("Can't archive without knowing the walletId");
var ex = await AssertAPIError("missing-permission", async () => await client.ArchivePullPayment("lol", result.Id));
Assert.Equal("btcpay.store.canmanagepullpayments", ((GreenfieldPermissionAPIError)ex.APIError).MissingPermission);
Assert.Equal("btcpay.store.canarchivepullpayments", ((GreenfieldPermissionAPIError)ex.APIError).MissingPermission);
TestLogs.LogInformation("Can't archive without permission");
await AssertAPIError("unauthenticated", async () => await unauthenticated.ArchivePullPayment(storeId, result.Id));
await client.ArchivePullPayment(storeId, result.Id);
@ -1077,7 +1085,39 @@ namespace BTCPayServer.Tests
Assert.IsType<string>(lnrURLs.LNURLUri);
Assert.Equal(12.303228134m, test4.Amount);
Assert.Equal("BTC", test4.Currency);
// Check we can register Boltcard
var uid = new byte[7];
RandomNumberGenerator.Fill(uid);
var card = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(0, card.Version);
var card1keys = new[] { card.K0, card.K1, card.K2, card.K3, card.K4 };
Assert.DoesNotContain(null, card1keys);
var card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(1, card2.Version);
Assert.StartsWith("lnurlw://", card2.LNURLW);
Assert.EndsWith("/boltcard", card2.LNURLW);
var card2keys = new[] { card2.K0, card2.K1, card2.K2, card2.K3, card2.K4 };
Assert.DoesNotContain(null, card2keys);
for (int i = 0; i < card1keys.Length; i++)
{
if (i == 1)
Assert.Contains(card1keys[i], card2keys);
else
Assert.DoesNotContain(card1keys[i], card2keys);
}
var card3 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid,
OnExisting = OnExistingBehavior.KeepVersion
});
Assert.Equal(card2.Version, card3.Version);
// Test with SATS denomination values
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
@ -1152,7 +1192,8 @@ namespace BTCPayServer.Tests
Approved = false,
PaymentMethod = "BTC",
Amount = 0.0001m,
Destination = address.ToString()
Destination = address.ToString(),
});
await AssertAPIError("invalid-state", async () =>
{
@ -1271,20 +1312,23 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
await user.MakeAdmin();
var client = await user.CreateClient(Policies.Unrestricted);
//create store
var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" });
var newStore = await client.CreateStore(new CreateStoreRequest { Name = "A" });
Assert.Equal("A", newStore.Name);
Assert.Equal(CheckoutType.V2, newStore.CheckoutType);
//update store
Assert.Empty(newStore.PaymentMethodCriteria);
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest()
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest
{
Name = "B",
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
CheckoutType = CheckoutType.V1,
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>
{
new()
{
@ -1296,6 +1340,7 @@ namespace BTCPayServer.Tests
}
});
Assert.Equal("B", updatedStore.Name);
Assert.Equal(CheckoutType.V1, updatedStore.CheckoutType);
var s = (await client.GetStore(newStore.Id));
Assert.Equal("B", s.Name);
var pmc = Assert.Single(s.PaymentMethodCriteria);
@ -1350,6 +1395,13 @@ namespace BTCPayServer.Tests
}
tester.DeleteStore = false;
Assert.Empty(await client.GetStores());
// Archive
var archivableStore = await client.CreateStore(new CreateStoreRequest { Name = "Archivable" });
Assert.False(archivableStore.Archived);
archivableStore = await client.UpdateStore(archivableStore.Id, new UpdateStoreRequest { Name = "Archived", Archived = true });
Assert.Equal("Archived", archivableStore.Name);
Assert.True(archivableStore.Archived);
}
private async Task<GreenfieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
@ -1442,7 +1494,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
public async Task CanUseWebhooks()
{
void AssertHook(FakeServer fakeServer, Client.Models.StoreWebhookData hook)
void AssertHook(FakeServer fakeServer, StoreWebhookData hook)
{
Assert.True(hook.Enabled);
Assert.True(hook.AuthorizedEvents.Everything);
@ -1591,7 +1643,7 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
await user.MakeAdmin();
var client = await user.CreateClient(Policies.Unrestricted);
var viewOnly = await user.CreateClient(Policies.CanViewPaymentRequests);
@ -1673,11 +1725,18 @@ namespace BTCPayServer.Tests
BitcoinAddress.Create(invoice.BitcoinAddress, tester.ExplorerNode.Network), invoice.BtcDue);
});
await TestUtils.EventuallyAsync(async () =>
{
Assert.Equal(Invoice.STATUS_PAID, user.BitPay.GetInvoice(invoiceId).Status);
if (!partialPayment)
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
});
{
Assert.Equal(Invoice.STATUS_PAID, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
if (!partialPayment)
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Processing, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
});
await tester.ExplorerNode.GenerateAsync(1);
await TestUtils.EventuallyAsync(async () =>
{
Assert.Equal(Invoice.STATUS_CONFIRMED, (await user.BitPay.GetInvoiceAsync(invoiceId)).Status);
if (!partialPayment)
Assert.Equal(PaymentRequestData.PaymentRequestStatus.Completed, (await client.GetPaymentRequest(user.StoreId, paymentTestPaymentRequest.Id)).Status);
});
}
await Pay(invoiceId);
@ -2073,18 +2132,18 @@ namespace BTCPayServer.Tests
//validation errors
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
{
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions() { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
});
await AssertHttpError(403, async () =>
{
await viewOnly.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "helloinvalid", Amount = 1 });
new CreateInvoiceRequest { Currency = "helloinvalid", Amount = 1 });
});
await user.RegisterDerivationSchemeAsync("BTC");
string origOrderId = "testOrder";
var newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
@ -2171,7 +2230,7 @@ namespace BTCPayServer.Tests
//list NonExisting Status
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
status: new[] { BTCPayServer.Client.Models.InvoiceStatus.Invalid });
status: new[] { InvoiceStatus.Invalid });
Assert.NotNull(invoicesNonExistingStatus);
Assert.Empty(invoicesNonExistingStatus);
@ -2189,7 +2248,7 @@ namespace BTCPayServer.Tests
//update
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
@ -2201,7 +2260,7 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
{
Status = InvoiceStatus.Invalid
@ -2216,13 +2275,13 @@ namespace BTCPayServer.Tests
await AssertHttpError(403, async () =>
{
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
});
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
@ -2262,13 +2321,12 @@ namespace BTCPayServer.Tests
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
foreach (var marked in new[] { InvoiceStatus.Settled, InvoiceStatus.Invalid })
{
var inv = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
new CreateInvoiceRequest { Currency = "USD", Amount = 100 });
await user.PayInvoice(inv.Id);
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest()
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest
{
Status = marked
});
@ -2296,13 +2354,12 @@ namespace BTCPayServer.Tests
}
}
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
Checkout = new CreateInvoiceRequest.CheckoutOptions()
Checkout = new CreateInvoiceRequest.CheckoutOptions
{
DefaultLanguage = "it-it ",
RedirectURL = "http://toto.com/lol"
@ -3211,6 +3268,9 @@ namespace BTCPayServer.Tests
});
var transaction = await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
// Check skip doesn't crash
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode, skip: 1);
Assert.Equal(transaction.TransactionHash, txdata.TransactionHash);
Assert.Equal(String.Empty, transaction.Comment);
#pragma warning disable CS0612 // Type or member is obsolete
@ -3545,6 +3605,7 @@ namespace BTCPayServer.Tests
PaymentMethod = "BTC_LightningNetwork",
Destination = customerInvoice.BOLT11
});
Assert.Equal(payout.Metadata.ToString(), new JObject().ToString()); //empty
Assert.Empty(await adminClient.GetStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork"));
await adminClient.UpdateStoreLightningAutomatedPayoutProcessors(admin.StoreId, "BTC_LightningNetwork",
new LightningAutomatedPayoutSettings() { IntervalSeconds = TimeSpan.FromSeconds(600) });
@ -3555,6 +3616,46 @@ namespace BTCPayServer.Tests
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
Assert.Equal(PayoutState.Completed, payoutC.State);
});
payout = await adminClient.CreatePayout(admin.StoreId,
new CreatePayoutThroughStoreRequest()
{
Approved = true,
PaymentMethod = "BTC",
Destination = (await tester.ExplorerNode.GetNewAddressAsync()).ToString(),
Amount = 0.0001m,
Metadata = JObject.FromObject(new
{
source ="apitest",
sourceLink = "https://chocolate.com"
})
});
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
{
source = "apitest",
sourceLink = "https://chocolate.com"
}).ToString());
payout =
(await adminClient.GetStorePayouts(admin.StoreId, false)).Single(data => data.Id == payout.Id);
Assert.Equal(payout.Metadata.ToString(), JObject.FromObject(new
{
source = "apitest",
sourceLink = "https://chocolate.com"
}).ToString());
customerInvoice = await tester.CustomerLightningD.CreateInvoice(LightMoney.FromUnit(10, LightMoneyUnit.Satoshi),
Guid.NewGuid().ToString(), TimeSpan.FromDays(40));
var payout2 = await adminClient.CreatePayout(admin.StoreId,
new CreatePayoutThroughStoreRequest()
{
Approved = true,
Amount = new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC),
PaymentMethod = "BTC_LightningNetwork",
Destination = customerInvoice.BOLT11
});
Assert.Equal(payout2.Amount, new Money(100, MoneyUnit.Satoshi).ToDecimal(MoneyUnit.BTC));
}
[Fact(Timeout = 60 * 2 * 1000)]
@ -4405,7 +4506,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await AssertHttpError(404, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", simulateWithdrawalRequest));
// Test: SimulateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
// TODO it is weird that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, correct payment method, wrong amount
@ -4436,7 +4537,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await AssertHttpError(404, async () => await withdrawalClient.CreateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", createWithdrawalRequest));
// Test: CreateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
// TODO it is weird that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.CreateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, createWithdrawalRequest));
// Test: CreateWithdrawal, correct payment method, wrong amount

@ -1,8 +1,10 @@
using System.Threading.Tasks;
using BTCPayServer.Client;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Hosting;
using BTCPayServer.Models.AppViewModels;
using BTCPayServer.Plugins.Crowdfund.Models;
using BTCPayServer.Plugins.PointOfSale;
using BTCPayServer.Plugins.PointOfSale.Controllers;
using BTCPayServer.Plugins.PointOfSale.Models;
@ -123,12 +125,14 @@ donation:
price: 1.02
custom: true
";
vmpos.Currency = "EUR";
vmpos.Template = AppService.SerializeTemplate(MigrationStartupTask.ParsePOSYML(vmpos.Template));
Assert.IsType<RedirectToActionResult>(pos.UpdatePointOfSale(app.Id, vmpos).Result);
await pos.UpdatePointOfSale(app.Id).AssertViewModelAsync<UpdatePointOfSaleViewModel>();
var publicApps = user.GetController<UIPointOfSaleController>();
var vmview = await publicApps.ViewPointOfSale(app.Id, PosViewType.Cart).AssertViewModelAsync<ViewPointOfSaleViewModel>();
Assert.Equal("EUR", vmview.CurrencyCode);
// apple shouldn't be available since we it's set to "disabled: true" above
Assert.Equal(2, vmview.Items.Length);
Assert.Equal("orange", vmview.Items[0].Title);
@ -139,6 +143,41 @@ donation:
// apple is not found
Assert.IsType<NotFoundResult>(publicApps
.ViewPointOfSale(app.Id, PosViewType.Cart, 0, choiceKey: "apple").Result);
// List
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
app = appList.Apps[0];
apps = user.GetController<UIAppsController>();
appData = new AppData { Id = app.Id, StoreDataId = app.StoreId, Name = app.AppName, AppType = appType, Settings = "{\"currency\":\"EUR\"}" };
apps.HttpContext.SetAppData(appData);
pos.HttpContext.SetAppData(appData);
Assert.Single(appList.Apps);
Assert.Equal("test", app.AppName);
Assert.True(app.Role.ToPermissionSet(appList.Apps[0].StoreId).Contains(Policies.CanModifyStoreSettings, app.StoreId));
Assert.Equal(user.StoreId, app.StoreId);
Assert.False(app.Archived);
// Archive
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
Assert.EndsWith("/settings/pos", redirect.Url);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
Assert.Empty(appList.Apps);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId, archived: true).Result).Model);
app = appList.Apps[0];
Assert.True(app.Archived);
Assert.IsType<NotFoundResult>(await publicApps.ViewPointOfSale(app.Id, PosViewType.Static));
// Unarchive
redirect = Assert.IsType<RedirectResult>(apps.ToggleArchive(app.Id).Result);
Assert.EndsWith("/settings/pos", redirect.Url);
appList = Assert.IsType<ListAppsViewModel>(Assert.IsType<ViewResult>(apps.ListApps(user.StoreId).Result).Model);
app = appList.Apps[0];
Assert.False(app.Archived);
Assert.IsType<ViewResult>(await publicApps.ViewPointOfSale(app.Id, PosViewType.Static));
// Delete
Assert.IsType<ViewResult>(apps.DeleteApp(app.Id));
var redirectToAction = Assert.IsType<RedirectToActionResult>(apps.DeleteAppPost(app.Id).Result);
Assert.Equal(nameof(UIStoresController.Dashboard), redirectToAction.ActionName);
appList = await apps.ListApps(user.StoreId).AssertViewModelAsync<ListAppsViewModel>();
Assert.Empty(appList.Apps);
}
}
}

@ -31,7 +31,6 @@ namespace BTCPayServer.Tests
user.RegisterDerivationScheme("BTC");
var user2 = tester.NewAccount();
await user2.GrantAccessAsync();
var paymentRequestController = user.GetController<UIPaymentRequestController>();
@ -162,7 +161,7 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var paymentRequestController = user.GetController<UIPaymentRequestController>();
@ -170,7 +169,7 @@ namespace BTCPayServer.Tests
Assert.IsType<NotFoundResult>(await
paymentRequestController.CancelUnpaidPendingInvoice(Guid.NewGuid().ToString(), false));
var request = new UpdatePaymentRequestViewModel()
var request = new UpdatePaymentRequestViewModel
{
Title = "original juice",
Currency = "BTC",

@ -295,17 +295,12 @@ namespace BTCPayServer.Tests
public void AddLightningNode()
{
AddLightningNode(null, null, true);
AddLightningNode(null, true);
}
public void AddLightningNode(LightningConnectionType? connectionType = null, bool test = true)
public void AddLightningNode(string connectionType = null, bool test = true)
{
AddLightningNode(null, connectionType, test);
}
public void AddLightningNode(string cryptoCode = null, LightningConnectionType? connectionType = null, bool test = true)
{
cryptoCode ??= "BTC";
var cryptoCode = "BTC";
if (!Driver.PageSource.Contains("Connect to a Lightning node"))
{
GoToLightningSettings();
@ -565,7 +560,7 @@ namespace BTCPayServer.Tests
walletId ??= WalletId;
GoToWallet(walletId, WalletsNavPages.Receive);
Driver.FindElement(By.Id("generateButton")).Click();
var addressStr = Driver.FindElement(By.Id("Address")).GetAttribute("value");
var addressStr = Driver.FindElement(By.Id("Address")).GetAttribute("data-text");
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
for (var i = 0; i < coins; i++)
{

File diff suppressed because it is too large Load Diff

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

@ -40,6 +40,7 @@ namespace BTCPayServer.Tests
public class TestAccount
{
readonly ServerTester parent;
public string LNAddress;
public TestAccount(ServerTester parent)
{
@ -242,7 +243,7 @@ namespace BTCPayServer.Tests
policies.LockSubscription = false;
await account.Register(RegisterDetails);
}
TestLogs.LogInformation($"UserId: {account.RegisteredUserId} Password: {Password}");
UserId = account.RegisteredUserId;
Email = RegisterDetails.Email;
IsAdmin = account.RegisteredAdmin;
@ -277,7 +278,7 @@ namespace BTCPayServer.Tests
public bool IsAdmin { get; internal set; }
public void RegisterLightningNode(string cryptoCode, LightningConnectionType? connectionType = null, bool isMerchant = true)
public void RegisterLightningNode(string cryptoCode, string connectionType = null, bool isMerchant = true)
{
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
}
@ -285,7 +286,7 @@ namespace BTCPayServer.Tests
{
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
public async Task RegisterLightningNodeAsync(string cryptoCode, string connectionType, bool isMerchant = true, string storeId = null)
{
var storeController = GetController<UIStoresController>();
@ -296,7 +297,7 @@ namespace BTCPayServer.Tests
await storeController.SetupLightningNode(storeId ?? StoreId,
vm, "save", cryptoCode);
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
Assert.Fail(storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
public async Task RegisterInternalLightningNodeAsync(string cryptoCode, string storeId = null)
@ -306,11 +307,12 @@ namespace BTCPayServer.Tests
await storeController.SetupLightningNode(storeId ?? StoreId,
vm, "save", cryptoCode);
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
Assert.Fail(storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network)
public async Task<Coin> ReceiveUTXO(Money value, BTCPayNetwork network = null)
{
network ??= SupportedNetwork;
var cashCow = parent.ExplorerNode;
var btcPayWallet = parent.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
var address = (await btcPayWallet.ReserveAddressAsync(this.DerivationScheme)).Address;
@ -430,8 +432,7 @@ namespace BTCPayServer.Tests
if (!response.IsSuccessStatusCode)
{
var error = JObject.Parse(await response.Content.ReadAsStringAsync());
Assert.True(false,
$"Error: {error["errorCode"].Value<string>()}: {error["message"].Value<string>()}");
Assert.Fail($"Error: {error["errorCode"].Value<string>()}: {error["message"].Value<string>()}");
}
}
@ -486,7 +487,7 @@ namespace BTCPayServer.Tests
}
public List<WebhookInvoiceEvent> WebhookEvents { get; set; } = new List<WebhookInvoiceEvent>();
public TEvent AssertHasWebhookEvent<TEvent>(WebhookEventType eventType, Action<TEvent> assert) where TEvent : class
public TEvent AssertHasWebhookEvent<TEvent>(string eventType, Action<TEvent> assert) where TEvent : class
{
int retry = 0;
retry:
@ -514,12 +515,12 @@ retry:
retry++;
goto retry;
}
Assert.True(false, "No webhook event match the assertion");
Assert.Fail("No webhook event match the assertion");
return null;
}
public async Task SetupWebhook()
{
FakeServer server = new FakeServer();
var server = new FakeServer();
await server.Start();
var client = await CreateClient(Policies.CanModifyStoreWebhooks);
var wh = await client.CreateWebhook(StoreId, new CreateStoreWebhookRequest()
@ -535,7 +536,7 @@ retry:
{
var inv = await BitPay.GetInvoiceAsync(invoiceId);
var net = parent.ExplorerNode.Network;
this.parent.ExplorerNode.SendToAddress(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
await parent.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(inv.BitcoinAddress, net), inv.BtcDue);
await TestUtils.EventuallyAsync(async () =>
{
var localInvoice = await BitPay.GetInvoiceAsync(invoiceId, Facade.Merchant);
@ -553,5 +554,94 @@ retry:
var repo = this.parent.PayTester.GetService<StoreRepository>();
await repo.AddStoreUser(StoreId, userId, StoreRoleId.Owner);
}
public async Task<uint256> PayOnChain(string invoiceId)
{
var cryptoCode = "BTC";
var client = await CreateClient();
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
var method = methods.First(m => m.PaymentMethod == cryptoCode);
var address = method.Destination;
var tx = await client.CreateOnChainTransaction(StoreId, cryptoCode, new CreateOnChainTransactionRequest()
{
Destinations = new List<CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination>()
{
new ()
{
Destination = address,
Amount = method.Due
}
},
FeeRate = new FeeRate(1.0m)
});
await WaitInvoicePaid(invoiceId);
return tx.TransactionHash;
}
public async Task PayOnBOLT11(string invoiceId)
{
var cryptoCode = "BTC";
var client = await CreateClient();
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LightningNetwork");
var bolt11 = method.Destination;
TestLogs.LogInformation("PAYING");
await parent.CustomerLightningD.Pay(bolt11);
TestLogs.LogInformation("PAID");
await WaitInvoicePaid(invoiceId);
}
public async Task PayOnLNUrl(string invoiceId)
{
var cryptoCode = "BTC";
var network = SupportedNetwork.NBitcoinNetwork;
var client = await CreateClient();
var methods = await client.GetInvoicePaymentMethods(StoreId, invoiceId);
var method = methods.First(m => m.PaymentMethod == $"{cryptoCode}-LNURLPAY");
var lnurL = LNURL.LNURL.Parse(method.PaymentLink, out var tag);
var http = new HttpClient();
var payreq = (LNURL.LNURLPayRequest)await LNURL.LNURL.FetchInformation(lnurL, tag, http);
var resp = await payreq.SendRequest(payreq.MinSendable, network, http);
var bolt11 = resp.Pr;
await parent.CustomerLightningD.Pay(bolt11);
await WaitInvoicePaid(invoiceId);
}
public Task WaitInvoicePaid(string invoiceId)
{
return TestUtils.EventuallyAsync(async () =>
{
var client = await CreateClient();
var invoice = await client.GetInvoice(StoreId, invoiceId);
if (invoice.Status == InvoiceStatus.Settled)
return;
Assert.Equal(InvoiceStatus.Processing, invoice.Status);
});
}
public async Task PayOnLNAddress(string lnAddrUser = null)
{
lnAddrUser ??= LNAddress;
var network = SupportedNetwork.NBitcoinNetwork;
var payReqStr = await (await parent.PayTester.HttpClient.GetAsync($".well-known/lnurlp/{lnAddrUser}")).Content.ReadAsStringAsync();
var payreq = JsonConvert.DeserializeObject<LNURL.LNURLPayRequest>(payReqStr);
var resp = await payreq.SendRequest(payreq.MinSendable, network, parent.PayTester.HttpClient);
var bolt11 = resp.Pr;
await parent.CustomerLightningD.Pay(bolt11);
}
public async Task<string> CreateLNAddress()
{
var lnAddrUser = Guid.NewGuid().ToString();
var ctx = parent.PayTester.GetService<ApplicationDbContextFactory>().CreateContext();
ctx.LightningAddresses.Add(new()
{
StoreDataId = StoreId,
Username = lnAddrUser
});
await ctx.SaveChangesAsync();
LNAddress = lnAddrUser;
return lnAddrUser;
}
}
}

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

@ -11,17 +11,22 @@ using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Models.StoreViewModels;
using BTCPayServer.Rating;
using BTCPayServer.Services.Fees;
using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileSystemGlobbing;
using NBitcoin;
using NBitpayClient;
using Newtonsoft.Json;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using static BTCPayServer.HostedServices.PullPaymentHostedService.PayoutApproval;
namespace BTCPayServer.Tests
{
@ -71,6 +76,25 @@ namespace BTCPayServer.Tests
await UnitTest1.CanUploadRemoveFiles(controller);
}
[Fact]
public async Task CanQueryMempoolFeeProvider()
{
IServiceCollection collection = new ServiceCollection();
collection.AddMemoryCache();
collection.AddHttpClient();
var prov = collection.BuildServiceProvider();
foreach (var isTestnet in new[] { true, false })
{
var mempoolSpaceFeeProvider = new MempoolSpaceFeeProvider(
prov.GetService<IMemoryCache>(),
"test" + isTestnet,
prov.GetService<IHttpClientFactory>(),
isTestnet);
var rates = await mempoolSpaceFeeProvider.GetFeeRatesAsync();
Assert.NotEmpty(rates);
await mempoolSpaceFeeProvider.GetFeeRateAsync(20);
}
}
[Fact]
public async Task CanQueryDirectProviders()
{
@ -177,7 +201,7 @@ namespace BTCPayServer.Tests
Assert.Contains(rates, e => e.CurrencyPair == new CurrencyPair("XMR", "BTC") && e.BidAsk.Bid < 1.0m);
// Check we didn't skip too many exchanges
Assert.InRange(skipped, 0, 3);
Assert.InRange(skipped, 0, 5);
}
[Fact]
@ -266,7 +290,7 @@ retry:
}
catch (Exception ex)
{
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
var details = ex.Message;
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
throw;
@ -276,7 +300,7 @@ retry:
[Fact()]
public void CanSolveTheDogesRatesOnKraken()
{
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = CreateNetworkProvider(ChainName.Mainnet);
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
@ -289,11 +313,45 @@ retry:
}
}
[Fact]
public async Task CanGetRateFromRecommendedExchanges()
{
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var provider = CreateNetworkProvider(ChainName.Mainnet);
var b = new StoreBlob();
string[] temporarilyBroken = { "COP", "UGX" };
foreach (var k in StoreBlob.RecommendedExchanges)
{
b.DefaultCurrency = k.Key;
var rules = b.GetDefaultRateRules(provider);
var pairs = new[] { CurrencyPair.Parse($"BTC_{k.Key}") }.ToHashSet();
var result = fetcher.FetchRates(pairs, rules, default);
foreach ((CurrencyPair key, Task<RateResult> value) in result)
{
TestLogs.LogInformation($"Testing {key} when default currency is {k.Key}");
var rateResult = await value;
var hasRate = rateResult.BidAsk != null;
if (temporarilyBroken.Contains(k.Key))
{
if (!hasRate)
{
TestLogs.LogInformation($"Skipping {key} because it is marked as temporarily broken");
continue;
}
TestLogs.LogInformation($"Note: {key} is marked as temporarily broken, but the rate is available");
}
Assert.True(hasRate, $"Impossible to get the rate {rateResult.EvaluatedRule}");
}
}
}
[Fact]
public async Task CanGetRateCryptoCurrenciesByDefault()
{
string[] brokenShitcoins = { };
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
using var cts = new CancellationTokenSource(60_000);
var provider = CreateNetworkProvider(ChainName.Mainnet);
var factory = FastTests.CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var pairs =
@ -301,37 +359,25 @@ retry:
.Select(c => new CurrencyPair(c.CryptoCode, "USD"))
.ToHashSet();
string[] brokenShitcoins = { "BTG", "BTX" };
bool IsBrokenShitcoin(CurrencyPair p) => brokenShitcoins.Contains(p.Left) || brokenShitcoins.Contains(p.Right);
foreach (var _ in brokenShitcoins)
{
foreach (var p in pairs.Where(IsBrokenShitcoin).ToArray())
{
TestLogs.LogInformation($"Skipping {p} because it is marked as broken");
pairs.Remove(p);
}
}
var rules = new StoreBlob().GetDefaultRateRules(provider);
var result = fetcher.FetchRates(pairs, rules, default);
var result = fetcher.FetchRates(pairs, rules, cts.Token);
foreach ((CurrencyPair key, Task<RateResult> value) in result)
{
var rateResult = await value;
TestLogs.LogInformation($"Testing {key}");
if (brokenShitcoins.Contains(key.ToString()))
continue;
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
}
var b = new StoreBlob();
foreach (var k in StoreBlob.RecommendedExchanges)
{
b.DefaultCurrency = k.Key;
rules = b.GetDefaultRateRules(provider);
pairs =
provider.GetAll()
.Select(c => new CurrencyPair(c.CryptoCode, k.Key))
.ToHashSet();
result = fetcher.FetchRates(pairs, rules, default);
foreach ((CurrencyPair key, Task<RateResult> value) in result)
{
var rateResult = await value;
TestLogs.LogInformation($"Testing {key} when default currency is {k.Key}");
if (brokenShitcoins.Contains(key.ToString()))
continue;
Assert.True(rateResult.BidAsk != null, $"Impossible to get the rate {rateResult.EvaluatedRule}");
}
}
}
[Fact]
@ -373,7 +419,8 @@ retry:
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "tom-select", "tom-select.complete.min.js").Trim();
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/tom-select@2.2.2/dist/js/tom-select.complete.min.js")).Content.ReadAsStringAsync()).Trim();
version = Regex.Match(actual, "Tom Select v([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/tom-select@{version}/dist/js/tom-select.complete.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "dom-confetti", "dom-confetti.min.js").Trim();
@ -391,10 +438,23 @@ retry:
expected = (await (await client.GetAsync($"https://cdnjs.cloudflare.com/ajax/libs/bootstrap-vue/{version}/bootstrap-vue.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "FileSaver", "FileSaver.min.js").Trim();
expected = (await (await client.GetAsync($"https://raw.githubusercontent.com/eligrey/FileSaver.js/43bbd2f0ae6794f8d452cd360e9d33aef6071234/dist/FileSaver.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "papaparse", "papaparse.min.js").Trim();
expected = (await (await client.GetAsync($"https://raw.githubusercontent.com/mholt/PapaParse/5.4.1/papaparse.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "vue-sanitize-directive", "vue-sanitize-directive.umd.min.js").Trim();
version = Regex.Match(actual, "Original file: /npm/vue-sanitize-directive@([0-9]+.[0-9]+.[0-9]+)").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/vue-sanitize-directive@{version}/dist/vue-sanitize-directive.umd.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
actual = GetFileContent("BTCPayServer", "wwwroot", "vendor", "decimal.js", "decimal.min.js").Trim();
version = Regex.Match(actual, "Original file: /npm/decimal\\.js@([0-9]+.[0-9]+.[0-9]+)/decimal\\.js").Groups[1].Value;
expected = (await (await client.GetAsync($"https://cdn.jsdelivr.net/npm/decimal.js@{version}/decimal.min.js")).Content.ReadAsStringAsync()).Trim();
EqualJsContent(expected, actual);
}
private void EqualJsContent(string expected, string actual)

@ -15,6 +15,7 @@ using BTCPayServer.Abstractions.Extensions;
using BTCPayServer.Abstractions.Models;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Controllers;
using BTCPayServer.Data;
using BTCPayServer.Events;
@ -23,6 +24,7 @@ using BTCPayServer.Fido2.Models;
using BTCPayServer.HostedServices;
using BTCPayServer.Hosting;
using BTCPayServer.Lightning;
using BTCPayServer.Lightning.Charge;
using BTCPayServer.Models;
using BTCPayServer.Models.AccountViewModels;
using BTCPayServer.Models.AppViewModels;
@ -45,6 +47,7 @@ using BTCPayServer.Services.Apps;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Mails;
using BTCPayServer.Services.Notifications;
using BTCPayServer.Services.Rates;
using BTCPayServer.Storage.Models;
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
@ -56,6 +59,7 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
@ -68,6 +72,7 @@ using Newtonsoft.Json.Schema;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
namespace BTCPayServer.Tests
@ -175,7 +180,7 @@ namespace BTCPayServer.Tests
// If this test fail, run `UpdateSwagger` once.
if (description != json["components"]["securitySchemes"]["API_Key"]["description"].Value<string>())
{
Assert.False(true, "Please run manually the test `UpdateSwagger` once");
Assert.Fail("Please run manually the test `UpdateSwagger` once");
}
}
@ -269,7 +274,7 @@ namespace BTCPayServer.Tests
}
catch (Exception ex)
{
var details = ex is EqualException ? (ex as EqualException).Actual : ex.Message;
var details = ex.Message;
TestLogs.LogInformation($"FAILED: {url} ({file}) {details}");
throw;
@ -386,11 +391,11 @@ namespace BTCPayServer.Tests
var newBolt11 = newInvoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
var oldBolt11 = invoice.CryptoInfo.First(o => o.PaymentUrls.BOLT11 != null).PaymentUrls.BOLT11;
Assert.NotEqual(newBolt11, oldBolt11);
Assert.Equal(newInvoice.BtcDue.GetValue(),
Assert.Equal(newInvoice.BtcDue.ToDecimal(MoneyUnit.BTC),
BOLT11PaymentRequest.Parse(newBolt11, Network.RegTest).MinimumAmount.ToDecimal(LightMoneyUnit.BTC));
}, 40000);
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue()} via lightning");
TestLogs.LogInformation($"Paying invoice {newInvoice.Id} remaining due amount {newInvoice.BtcDue.GetValue((BTCPayNetwork) tester.DefaultNetwork)} via lightning");
var evt = await tester.WaitForEvent<InvoiceDataChangedEvent>(async () =>
{
await tester.SendLightningPaymentAsync(newInvoice);
@ -475,7 +480,7 @@ namespace BTCPayServer.Tests
await ProcessLightningPayment(LightningConnectionType.LndREST);
}
async Task ProcessLightningPayment(LightningConnectionType type)
async Task ProcessLightningPayment(string type)
{
// For easier debugging and testing
// LightningLikePaymentHandler.LIGHTNING_TIMEOUT = int.MaxValue;
@ -539,7 +544,7 @@ namespace BTCPayServer.Tests
var pairingCode = (string)token.RouteValues["pairingCode"];
acc.BitPay.AuthorizeClient(new PairingCode(pairingCode)).GetAwaiter().GetResult();
await acc.BitPay.AuthorizeClient(new PairingCode(pairingCode));
Assert.True(acc.BitPay.TestAccess(Facade.Merchant));
}
@ -603,7 +608,7 @@ namespace BTCPayServer.Tests
completed = true;
break;
default:
Assert.False(true, $"{evtName} was not expected");
Assert.Fail($"{evtName} was not expected");
break;
}
}
@ -1294,7 +1299,7 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
var rng = new Random();
@ -1328,8 +1333,6 @@ namespace BTCPayServer.Tests
var btcmethod = (await client.GetInvoicePaymentMethods(user.StoreId, invoice.Id))[0];
var paid = btcSent;
var invoiceAddress = BitcoinAddress.Create(btcmethod.Destination, cashCow.Network);
var btc = new PaymentMethodId("BTC", PaymentTypes.BTCLike);
var networkFee = (await tester.PayTester.InvoiceRepository.GetInvoice(invoice.Id))
.GetPaymentMethods()[btc]
@ -1341,9 +1344,7 @@ namespace BTCPayServer.Tests
networkFee = 0.0m;
}
cashCow.SendToAddress(invoiceAddress, paid);
await cashCow.SendToAddressAsync(invoiceAddress, paid);
await TestUtils.EventuallyAsync(async () =>
{
try
@ -1363,7 +1364,7 @@ namespace BTCPayServer.Tests
}
catch (JsonSerializationException)
{
Assert.False(true, "The bitpay's amount is not set");
Assert.Fail("The bitpay's amount is not set");
}
});
}
@ -1700,109 +1701,6 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanExportInvoicesJson()
{
decimal GetFieldValue(string input, string fieldName)
{
var match = Regex.Match(input, $"\"{fieldName}\":([^,]*)");
Assert.True(match.Success);
return decimal.Parse(match.Groups[1].Value.Trim(), CultureInfo.InvariantCulture);
}
async Task<object[]> GetExport(TestAccount account, string storeId = null)
{
var content = await account.GetController<UIInvoiceController>(false)
.Export("json", storeId);
var result = Assert.IsType<ContentResult>(content);
Assert.Equal("application/json", result.ContentType);
return JsonConvert.DeserializeObject<object[]>(result.Content ?? "[]");
}
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 10,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some \", description",
FullNotifications = true
}, Facade.Merchant);
var networkFee = new FeeRate(invoice.MinerFees["BTC"].SatoshiPerBytes).GetFee(100);
var result = await GetExport(user);
Assert.Single(result);
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = invoice.CryptoInfo[0].TotalDue - 3 * networkFee;
cashCow.SendToAddress(invoiceAddress, firstPayment);
Thread.Sleep(1000); // prevent race conditions, ordering payments
// look if you can reduce thread sleep, this was min value for me
// should reduce invoice due by 0 USD because payment = network fee
cashCow.SendToAddress(invoiceAddress, networkFee);
Thread.Sleep(1000);
// pay remaining amount
cashCow.SendToAddress(invoiceAddress, 4 * networkFee);
Thread.Sleep(1000);
await TestUtils.EventuallyAsync(async () =>
{
var parsedJson = await GetExport(user);
Assert.Equal(3, parsedJson.Length);
var invoiceDueAfterFirstPayment = 3 * networkFee.ToDecimal(MoneyUnit.BTC) * invoice.Rate;
var pay1str = parsedJson[0].ToString();
Assert.Contains("\"InvoiceItemDesc\": \"Some \\\", description\"", pay1str);
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay1str, "InvoiceDue"));
Assert.Contains("\"InvoicePrice\": 10.0", pay1str);
Assert.Contains("\"ConversionRate\": 5000.0", pay1str);
Assert.Contains($"\"InvoiceId\": \"{invoice.Id}\",", pay1str);
var pay2str = parsedJson[1].ToString();
Assert.Equal(invoiceDueAfterFirstPayment, GetFieldValue(pay2str, "InvoiceDue"));
var pay3str = parsedJson[2].ToString();
Assert.Contains("\"InvoiceDue\": 0", pay3str);
});
// create an invoice for a new store and check responses with and without store id
var otherUser = tester.NewAccount();
await otherUser.GrantAccessAsync();
otherUser.RegisterDerivationScheme("BTC");
await otherUser.SetNetworkFeeMode(NetworkFeeMode.Always);
var newInvoice = await otherUser.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 21,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some \", description",
FullNotifications = true
}, Facade.Merchant);
await otherUser.PayInvoice(newInvoice.Id);
Assert.Single(await GetExport(otherUser));
Assert.Single(await GetExport(otherUser, otherUser.StoreId));
Assert.Equal(3, (await GetExport(user, user.StoreId)).Length);
Assert.Equal(3, (await GetExport(user)).Length);
await otherUser.AddOwner(user.UserId);
Assert.Equal(4, (await GetExport(user)).Length);
Assert.Single(await GetExport(user, otherUser.StoreId));
}
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanChangeNetworkFeeMode()
@ -1892,45 +1790,6 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanExportInvoicesCsv()
{
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetNetworkFeeMode(NetworkFeeMode.Always);
var invoice = user.BitPay.CreateInvoice(
new Invoice
{
Price = 500,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some \", description",
FullNotifications = true
}, Facade.Merchant);
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = invoice.CryptoInfo[0].TotalDue - Money.Coins(0.001m);
cashCow.SendToAddress(invoiceAddress, firstPayment);
TestUtils.Eventually(() =>
{
var exportResultPaid =
user.GetController<UIInvoiceController>().Export("csv").GetAwaiter().GetResult();
var paidresult = Assert.IsType<ContentResult>(exportResultPaid);
Assert.Equal("application/csv", paidresult.ContentType);
Assert.Contains($",orderId,{invoice.Id},", paidresult.Content);
Assert.Contains($",On-Chain,BTC,0.0991,0.0001,5000.0", paidresult.Content);
Assert.Contains($",USD,5.00", paidresult.Content); // Seems hacky but some plateform does not render this decimal the same
Assert.Contains("0,,\"Some \"\", description\",New (paidPartial),new,paidPartial",
paidresult.Content);
});
}
[Fact(Timeout = LongRunningTestTimeout)]
[Trait("Integration", "Integration")]
public async Task CanCreateAndDeleteApps()
@ -2089,11 +1948,11 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
user.RegisterDerivationScheme("BTC");
await user.SetupWebhook();
var invoice = user.BitPay.CreateInvoice(
new Invoice()
var invoice = await user.BitPay.CreateInvoiceAsync(
new Invoice
{
Price = 5000.0m,
TaxIncluded = 1000.0m,
@ -2125,7 +1984,7 @@ namespace BTCPayServer.Tests
Assert.Single(textSearchResult);
});
invoice = user.BitPay.GetInvoice(invoice.Id, Facade.Merchant);
invoice = await user.BitPay.GetInvoiceAsync(invoice.Id, Facade.Merchant);
Assert.Equal(1000.0m, invoice.TaxIncluded);
Assert.Equal(5000.0m, invoice.Price);
Assert.Equal(Money.Coins(0), invoice.BtcPaid);
@ -2140,11 +1999,8 @@ namespace BTCPayServer.Tests
Assert.Empty(user.BitPay.GetInvoices(invoice.InvoiceTime.UtcDateTime - TimeSpan.FromDays(5),
invoice.InvoiceTime.DateTime - TimeSpan.FromDays(1)));
var firstPayment = Money.Coins(0.04m);
var txFee = Money.Zero;
var cashCow = tester.ExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
@ -2172,7 +2028,7 @@ namespace BTCPayServer.Tests
secondPayment = localInvoice.BtcDue;
});
cashCow.SendToAddress(invoiceAddress, secondPayment);
await cashCow.SendToAddressAsync(invoiceAddress, secondPayment);
TestUtils.Eventually(() =>
{
@ -2186,7 +2042,7 @@ namespace BTCPayServer.Tests
Assert.False((bool)((JValue)localInvoice.ExceptionStatus).Value);
});
cashCow.Generate(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
await cashCow.GenerateAsync(1); //The user has medium speed settings, so 1 conf is enough to be confirmed
TestUtils.Eventually(() =>
{
@ -2194,7 +2050,7 @@ namespace BTCPayServer.Tests
Assert.Equal("confirmed", localInvoice.Status);
});
cashCow.Generate(5); //Now should be complete
await cashCow.GenerateAsync(5); //Now should be complete
TestUtils.Eventually(() =>
{
@ -2203,7 +2059,7 @@ namespace BTCPayServer.Tests
Assert.NotEqual(0.0m, localInvoice.Rate);
});
invoice = user.BitPay.CreateInvoice(new Invoice()
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice
{
Price = 5000.0m,
Currency = "USD",
@ -2216,7 +2072,7 @@ namespace BTCPayServer.Tests
}, Facade.Merchant);
invoiceAddress = BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network);
var txId = cashCow.SendToAddress(invoiceAddress, invoice.BtcDue + Money.Coins(1));
var txId = await cashCow.SendToAddressAsync(invoiceAddress, invoice.BtcDue + Money.Coins(1));
TestUtils.Eventually(() =>
{
@ -2233,7 +2089,7 @@ namespace BTCPayServer.Tests
Assert.Single(textSearchResult);
});
cashCow.Generate(1);
await cashCow.GenerateAsync(2);
TestUtils.Eventually(() =>
{
@ -2248,6 +2104,7 @@ namespace BTCPayServer.Tests
c =>
{
Assert.False(c.ManuallyMarked);
Assert.True(c.OverPaid);
});
user.AssertHasWebhookEvent<WebhookInvoiceProcessingEvent>(WebhookEventType.InvoiceProcessing,
c =>
@ -2398,13 +2255,17 @@ namespace BTCPayServer.Tests
}
class MockVersionFetcher : IVersionFetcher
class MockVersionFetcher : GithubVersionFetcher
{
public const string MOCK_NEW_VERSION = "9.9.9.9";
public Task<string> Fetch(CancellationToken cancellation)
public override Task<string> Fetch(CancellationToken cancellation)
{
return Task.FromResult(MOCK_NEW_VERSION);
}
public MockVersionFetcher(IHttpClientFactory httpClientFactory, BTCPayServerOptions options, ILogger<GithubVersionFetcher> logger, SettingsRepository settingsRepository, BTCPayServerEnvironment environment, NotificationSender notificationSender) : base(httpClientFactory, options, logger, settingsRepository, environment, notificationSender)
{
}
}
[Fact(Timeout = LongRunningTestTimeout)]
@ -2423,8 +2284,13 @@ namespace BTCPayServer.Tests
var mockEnv = tester.PayTester.GetService<BTCPayServerEnvironment>();
var mockSender = tester.PayTester.GetService<Services.Notifications.NotificationSender>();
var svc = new NewVersionCheckerHostedService(settings, mockEnv, mockSender, new MockVersionFetcher(), BTCPayLogs);
await svc.ProcessVersionCheck();
var svc = new MockVersionFetcher(tester.PayTester.GetService<IHttpClientFactory>(),
tester.PayTester.GetService<BTCPayServerOptions>(),
tester.PayTester.GetService<ILogger<GithubVersionFetcher>>(),
settings,
mockEnv,
mockSender);
await svc.Do(CancellationToken.None);
// since last version present in database was null, it should've been updated with version mock returned
var lastVersion = await settings.GetSettingAsync<NewVersionCheckerDataHolder>();
@ -2532,9 +2398,14 @@ namespace BTCPayServer.Tests
Assert.NotNull(lnMethod.GetExternalLightningUrl());
var url = lnMethod.GetExternalLightningUrl();
Assert.Equal(LightningConnectionType.Charge, url.ConnectionType);
Assert.Equal("pass", url.Password);
Assert.Equal("usr", url.Username);
var kv = LightningConnectionStringHelper.ExtractValues(url, out var connType);
Assert.Equal(LightningConnectionType.Charge,connType);
var client = Assert.IsType<ChargeClient>(tester.PayTester.GetService<LightningClientFactoryService>()
.Create(url, tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")));
var auth = Assert.IsType<ChargeAuthentication.UserPasswordAuthentication>(client.ChargeAuthentication);
Assert.Equal("pass", auth.NetworkCredential.Password);
Assert.Equal("usr", auth.NetworkCredential.UserName);
// Test if lightning connection strings get migrated to internal
store.DerivationStrategies = new JObject()
@ -2859,7 +2730,7 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess();
await user.GrantAccessAsync();
var controller = tester.PayTester.GetController<UIServerController>(user.UserId, user.StoreId);
var fileSystemStorageConfiguration = Assert.IsType<FileSystemStorageConfiguration>(Assert
@ -2874,7 +2745,6 @@ namespace BTCPayServer.Tests
Assert.Equal(StorageProvider.FileSystem,
shouldBeRedirectingToLocalStorageConfigPage.RouteValues["provider"]);
await CanUploadRemoveFiles(controller);
}
@ -2906,7 +2776,7 @@ namespace BTCPayServer.Tests
//create a temporary link to file
var tmpLinkGenerate = Assert.IsType<RedirectToActionResult>(await controller.CreateTemporaryFileUrl(fileId,
new UIServerController.CreateTemporaryFileUrlViewModel()
new UIServerController.CreateTemporaryFileUrlViewModel
{
IsDownload = true,
TimeAmount = 1,
@ -2936,5 +2806,124 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewFilesViewModel>(Assert.IsType<ViewResult>(await controller.Files(new string[] { fileId })).Model);
Assert.Null(viewFilesViewModel.DirectUrlByFiles);
}
[Fact]
[Trait("Selenium", "Selenium")]
public async Task CanCreateReports()
{
using var tester = CreateServerTester();
tester.ActivateLightning();
tester.DeleteStore = false;
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var acc = tester.NewAccount();
await acc.GrantAccessAsync();
await acc.MakeAdmin();
acc.RegisterDerivationScheme("BTC", importKeysToNBX: true);
acc.RegisterLightningNode("BTC");
await acc.ReceiveUTXO(Money.Coins(1.0m));
var client = await acc.CreateClient();
var posController = acc.GetController<UIPointOfSaleController>();
var app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
{
AppName = "Static",
DefaultView = Client.Models.PosViewType.Static,
Template = new PointOfSaleSettings().Template
});
var resp = await posController.ViewPointOfSale(app.Id, choiceKey: "green-tea");
var invoiceId = GetInvoiceId(resp);
await acc.PayOnChain(invoiceId);
app = await client.CreatePointOfSaleApp(acc.StoreId, new CreatePointOfSaleAppRequest()
{
AppName = "Cart",
DefaultView = Client.Models.PosViewType.Cart,
Template = new PointOfSaleSettings().Template
});
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()
{
["cart"] = new JArray()
{
new JObject()
{
["id"] = "green-tea",
["count"] = 2
},
new JObject()
{
["id"] = "black-tea",
["count"] = 1
},
}
}.ToString());
invoiceId = GetInvoiceId(resp);
await acc.PayOnBOLT11(invoiceId);
resp = await posController.ViewPointOfSale(app.Id, posData: new JObject()
{
["cart"] = new JArray()
{
new JObject()
{
["id"] = "green-tea",
["count"] = 5
}
}
}.ToString());
invoiceId = GetInvoiceId(resp);
await acc.PayOnLNUrl(invoiceId);
await acc.CreateLNAddress();
await acc.PayOnLNAddress();
var report = await GetReport(acc, new() { ViewName = "Payments" });
// 1 payment on LN Address
// 1 payment on LNURL
// 1 payment on BOLT11
// 1 payment on chain
Assert.Equal(4, report.Data.Count);
var lnAddressIndex = report.GetIndex("LightningAddress");
var paymentTypeIndex = report.GetIndex("PaymentType");
Assert.Contains(report.Data, d => d[lnAddressIndex]?.Value<string>()?.Contains(acc.LNAddress) is true);
var paymentTypes = report.Data
.GroupBy(d => d[paymentTypeIndex].Value<string>())
.ToDictionary(d => d.Key);
Assert.Equal(3, paymentTypes["Lightning"].Count());
Assert.Single(paymentTypes["On-Chain"]);
// 2 on-chain transactions: It received from the cashcow, then paid its own invoice
report = await GetReport(acc, new() { ViewName = "Wallets" });
var txIdIndex = report.GetIndex("TransactionId");
var balanceIndex = report.GetIndex("BalanceChange");
Assert.Equal(2, report.Data.Count);
Assert.Equal(64, report.Data[0][txIdIndex].Value<string>().Length);
Assert.Contains(report.Data, d => d[balanceIndex]["v"].Value<decimal>() == 1.0m);
// Items sold
report = await GetReport(acc, new() { ViewName = "Sales" });
var itemIndex = report.GetIndex("Product");
var countIndex = report.GetIndex("Quantity");
var itemsCount = report.Data.GroupBy(d => d[itemIndex].Value<string>())
.ToDictionary(d => d.Key, r => r.Sum(d => d[countIndex].Value<int>()));
Assert.Equal(8, itemsCount["green-tea"]);
Assert.Equal(1, itemsCount["black-tea"]);
}
private async Task<StoreReportResponse> GetReport(TestAccount acc, StoreReportRequest req)
{
var controller = acc.GetController<UIReportsController>();
return (await controller.StoreReportsJson(acc.StoreId, req)).AssertType<JsonResult>()
.Value
.AssertType<StoreReportResponse>();
}
private static string GetInvoiceId(IActionResult resp)
{
var redirect = resp.AssertType<RedirectToActionResult>();
Assert.Equal("Checkout", redirect.ActionName);
return (string)redirect.RouteValues["invoiceId"];
}
}
}

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

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

@ -24,7 +24,7 @@ services:
TESTS_AzureBlobStorageConnectionString: ${TESTS_AzureBlobStorageConnectionString:-none}
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "http://merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@ -73,7 +73,7 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:25.1
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -99,7 +99,7 @@ services:
custom:
nbxplorer:
image: nicolasdorier/nbxplorer:2.3.63
image: nicolasdorier/nbxplorer:2.4.0
restart: unless-stopped
ports:
- "32838:32838"
@ -135,7 +135,7 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:25.0
image: btcpayserver/bitcoin:25.1
environment:
BITCOIN_NETWORK: regtest
BITCOIN_WALLETDIR: "/data/wallets"
@ -163,7 +163,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v23.05-dev
image: btcpayserver/lightning:v23.08-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -190,7 +190,7 @@ services:
- bitcoind
merchant_lightningd:
image: btcpayserver/lightning:v23.05-dev
image: btcpayserver/lightning:v23.08-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@ -224,7 +224,7 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.16.4-beta
image: btcpayserver/lnd:v0.17.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -259,7 +259,7 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.16.4-beta
image: btcpayserver/lnd:v0.17.2-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
@ -307,7 +307,7 @@ services:
- "torrcdir:/usr/local/etc/tor"
- "tor_servicesdir:/var/lib/tor/hidden_services"
monerod:
image: btcpayserver/monero:0.17.0.0-amd64
image: btcpayserver/monero:0.18.2.2-5
restart: unless-stopped
container_name: xmr_monerod
entrypoint: sleep 999999
@ -317,7 +317,7 @@ services:
ports:
- "18081:18081"
monero_wallet:
image: btcpayserver/monero:0.17.0.0-amd64
image: btcpayserver/monero:0.18.2.2-5
restart: unless-stopped
container_name: xmr_wallet_rpc
entrypoint: monero-wallet-rpc --testnet --rpc-bind-ip=0.0.0.0 --disable-rpc-login --confirm-external-bind --rpc-bind-port=18082 --non-interactive --trusted-daemon --daemon-address=monerod:18081 --wallet-file=/wallet/wallet.keys --password-file=/wallet/password --tx-notify="/bin/sh ./scripts/notifier.sh -k -X GET https://host.docker.internal:14142/monerolikedaemoncallback/tx?cryptoCode=xmr&hash=%s"
@ -349,7 +349,7 @@ services:
elementsd-liquid:
restart: always
container_name: btcpayserver_elementsd_liquid
image: btcpayserver/elements:0.21.0.1
image: btcpayserver/elements:0.21.0.2-4
environment:
ELEMENTS_CHAIN: elementsregtest
ELEMENTS_EXTRA_ARGS: |
@ -364,11 +364,9 @@ services:
whitelist=0.0.0.0/0
rpcallowip=0.0.0.0/0
validatepegin=0
initialfreecoins=210000000000000
initialfreecoins=2100000000000000
con_dyna_deploy_signal=1
con_dyna_deploy_start=0
con_nminerconfirmationwindow=1
con_nrulechangeactivationthreshold=1
con_dyna_deploy_start=10
expose:
- "19332"
- "19444"

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