Compare commits

...

181 Commits

Author SHA1 Message Date
b03f8db06b Refactor wallet file parsing (Fix: #5690) (#5692) 2024-01-23 21:33:45 +09:00
27e70a169e Do not show warning about browser compatibility to vault on confirm address 2024-01-23 21:30:29 +09:00
6a1d17dda2 Remove ESC as a supporter (#5685)
Closes #5684.
2024-01-22 09:00:10 +09:00
31bc6dd48c More tests on interpolation 2024-01-20 12:21:58 +09:00
6054315d84 Add changelog 1.12.4, bump (#5678)
* Add changelog 1.12.4, bump

* Update Changelog.md

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

* Update Changelog.md

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2024-01-19 23:28:01 +09:00
2a7059ddeb Update languages updates from transifex (#5679)
* Update languages

* Update ChatGPT translator script

* Update translations
2024-01-19 21:45:14 +09:00
e2e7e59722 Fix webhook test for payment requests (#5680)
When testing the webhook for payment requests, we were incorrectly creating a payout webhook instead of a payment request. This would cause an error (but nothing fatal as it is only a test webhook(
2024-01-19 21:30:15 +09:00
8b373bda8e bump NBX 2024-01-18 17:21:15 +09:00
d6806dc1f6 Improve checkout page load time by fetching recommended fee in the background periodically (#5672) 2024-01-18 17:16:57 +09:00
a753698ae7 Various plugin fixes (#5577)
* Fix: Plugin updates do not work

* Offer install on disabled plugins when different version

This will:
* Clear any previous pending actions of a plugin if you click uninstall
* Show the plugin version that was disabled
* Show an update button on disabled plugins instead of install
* if a plugin is scheduled to be installed/updated, it will show which version was scheduled to be updated. If a newer version if available than the scheduled one, it will show an option to switch to that

* Ensure disabled plugins don't get loaded

* View fixes

---------

Co-authored-by: d11n <mail@dennisreimann.de>
2024-01-18 17:15:16 +09:00
3eec9cb0bb Refactor fee provider (#5643)
* Refactor fee provider

The fee provider ended up glued with a hardcoded factory. This PR:
* removes this glue and uses the DI to register fee provider for a network. (allows plugins to add their own fee providers, for any network
* Add a 10 second timeout to mempoolspace fee fetching as they are slow at times

* use linear interpolation for mempool space fee estimation

* fix upper bound

* Add tests, rollback pluginify FeeProvider

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2024-01-18 15:27:19 +09:00
cd8ef0c1ff Fix: Bitpay's API rate route wasn't backward for some queries (#5671) 2024-01-18 14:08:07 +09:00
bd196ad963 fix build 2024-01-18 12:31:59 +09:00
1ad93838c9 Remove reliance on static field 2024-01-18 11:13:32 +09:00
a9252fd741 Fix: Partial Payment shows 'Could not update BTC (LNURL-Pay)' in invoice logs (#5670) 2024-01-18 09:57:25 +09:00
376067324b Remove unused variables (#5669) 2024-01-18 09:47:39 +09:00
dd7ab2f647 Avoid exception storm when currency provider is initialized (#5668) 2024-01-18 09:31:35 +09:00
1d6d146fb2 Revert "Remove unused variables" (#5667)
This reverts commit f070b223552b92b1450a96f893883c488f985cea.
2024-01-18 00:05:50 +09:00
3ae1f13323 Bump libraries 2024-01-17 22:11:30 +09:00
0b0a8f8218 Fix: BTCPay Server fails to start the first time when installing a new plugin (#5595) 2024-01-17 19:26:22 +09:00
f070b22355 Remove unused variables 2024-01-17 18:46:28 +09:00
c5a0e28420 Refactor Wallet import code (#5638)
* Refactor Wallet import code

The code for wallet import was incredibly messy as it evolved over time from various requests.

This PR:
* splits up each supported format into its own file
* Supports taproot descriptors (through a hack until NBitcoin supports it internally) fixes #5518
* Reduces different paths for handling electrum/non-electrum xpubs
* Allows plugins to add their own import support formats for onchain wallets.

* Update NBitcoin to parse tr descriptors

* Fix warnings

* Use dedicated type OnChainWalletParsers

---------

Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2024-01-17 18:08:39 +09:00
70e9ea1d5e POS: Fix missing store branding property on form error case (#5658)
When a POS has a form, which results in an error state, the store branding property was not set. This adds the missing property and also does not render the store branding partial, in case the model property isn't present.

Fixes #5655.
2024-01-16 08:55:38 +01:00
89d294524a Checkout v2: Clicking QR code copies full payment URI (#5627)
* Checkout v2: Clicking QR code copies full payment URI

Before it copied only the destination value (Bitcoin address or Lightning BOLT11). This didn't include the BOLT11 in case of the unified QR code. Now it will copy the full payment URI, which is the same as the QR represents:

- Unified: `bitcoin:ADDRESS?amount=AMOUNT&lightning=BOLT11`
- Bitcoin: `bitcoin:ADDRESS?amount=AMOUNT`
- Lightning: `lightning:BOLT11`

Fixes #5625.

* Test fix
2024-01-16 08:54:59 +01:00
5e25ee2996 Checkout v1: Apply custom style (#5628)
Applies the custom CSS in Checkout v1 and prevents that it interferes with the styling of Checkout v2.

Fixes #5615 and fixes #5616.
2024-01-15 13:30:39 +01:00
5935dbf1d1 Store Emails: Fix test email with multiple recipients (#5649)
Fixes #5648.
2024-01-15 13:30:10 +01:00
f7542c988d Prevent payment request to be created when a wallet is not set up (#5620)
* Prevent payment request to be created when a wallet is not set up

* Created an extension method for store wallet checks

* fix for invoice and payment request selenium test

* refactoring payment request controller

* removing unused variable

* Unify behaviour across controllers

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2024-01-11 16:25:56 +01:00
e90414bded Hide LN Balance when using internal node and not server admin (#5639)
* Hide LN Balance when using internal node and not server admin

* Minor updates

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2024-01-06 08:46:19 +01:00
78882dcff0 Propose linking Greenfield API information within the Legacy API view (#5635)
* Propose linking Greenfield API information within the Legacy API view

* Propose linking Greenfield API information within the Legacy API view

* moved Greenfield API section up

* moved Greenfield API section up

* Fix link

* Wording

* Adjust button alignment

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2024-01-03 11:33:54 +01:00
1ac1443070 README.md: .NET is not called "Core" anymore (#5636)
* README.md: .NET is not called "Core" anymore

Ever since version 5.x, the "Core" part of the name was removed.

* README.md: remove unneeded lang setting from URL
2024-01-02 12:29:40 +01:00
b5405e9313 Make tips and discount properties disabled in POS setting (#5619)
* Make tips and discount properties disabled in POS setting

* Update discount and tips boolean properties in model and swagger json

* update pos tests to cater for default tip and discount state

* Remove custom IDs and unify tests

---------

Co-authored-by: Dennis Reimann <mail@dennisreimann.de>
2023-12-31 09:07:15 +01:00
c7eef01fd5 Removed what's new button and info #5608 (#5618) 2023-12-28 08:57:18 +01:00
26f61d35bb Bumping LND to 0.17.3-beta (#5614) 2023-12-25 00:27:46 -06:00
765776c429 Update .NET version in README.md (#5609)
Update the version of .NET requirement from 6.0 to 8.0 in README.md
2023-12-24 15:40:38 +01:00
9f54074d03 Startup: List configured networks in non-altcoin build warning (#5593) 2023-12-22 17:25:04 +09:00
f23078df1c Use buildx for creating and pushing docker images (#5592) 2023-12-22 14:23:04 +09:00
a35bf54a02 Changelog and bump 2023-12-22 14:21:12 +09:00
4867698ac9 AppService: Update inventory only for known app types (#5590)
There are apps, which do not have a template and hence no inventory. Accessing it via `settings[templatePath]!.Value` causes exceptions in those cases.
2023-12-22 14:21:01 +09:00
e84e575017 bump 2023-12-22 10:53:54 +09:00
c585a0b276 Webhooks: Fix invoice interpolation (#5586)
* Webhooks: Fix invoice interpolation

Fixes #5584.

* Syntax cleanups
2023-12-22 10:50:08 +09:00
ad89139e07 Plugins: Fix missing uninstall button (#5587)
Fixes #5585.
2023-12-22 10:49:40 +09:00
ebc053aca5 Update Changelog (#5583) 2023-12-21 23:46:29 +09:00
96da7f0322 UI: Form validation summary matches alert style (#5576)
Fixes #5564.
2023-12-21 23:43:12 +09:00
8ae9e59d9d Lightning Address: Use lowercased username when resolving (#5579)
* Lightning Address: Use lowercased username when resolving

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

Builds on btcpayserver/BTCPayServer.Lightning#153.

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

Requires btcpayserver/BTCPayServer.Lightning#152.

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

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

* Test fix

---------

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

Fixes #5540.

* Ensure we are not using the symbol for BTC
2023-12-07 20:40:13 +09:00
eda437995f Show Warning is browser safari/brave is incompatible with vault on all pages 2023-12-07 14:00:30 +09:00
379286c366 Webhooks: Remove OverPaid property from invoice payment events
In addition to #5538 and #5496. This aligns it with [what we have in the docs](https://docs.btcpayserver.org/API/Greenfield/v1/#tag/Webhooks).
2023-12-06 14:48:34 +01:00
3f344f2c0c Webhooks: Re-add OverPaid property to WebhookInvoiceSettledEvent (#5538)
Fixes #5496.
2023-12-06 09:21:04 +09:00
d050c8e3b2 Boltcard integration (#5419)
* 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 (#5534)
Fixes #5530. 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 (#5529) 2023-12-02 10:13:28 +01:00
44b7ed0e6e Store Branding: Refactoring and logo as favicon (#5519)
* 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 (#5513) 2023-12-01 10:55:05 +01:00
28265b30d2 Support BIP129 Multisig wallet import (#5389) 2023-12-01 10:54:13 +01:00
a97172cea6 Pluginize Webhooks and support Payouts (#5421)
Co-authored-by: d11n <mail@dennisreimann.de>
2023-12-01 10:50:05 +01:00
605741182d enhance fine grain permissions (#5502)
Co-authored-by: d11n <mail@dennisreimann.de>
2023-12-01 09:12:02 +01:00
2c94a87be4 Support adjusting form invoice amount by multiplier (#5463)
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 (#5404)
Co-authored-by: Nicolas Dorier <nicolas.dorier@gmail.com>
2023-11-30 18:25:09 +09:00
bdf56c0a6f Keypad: List recent transactions (#5478)
* Keypad: List recent transactions

Closes #5379.

* UI updates

* Optional: No border

* Fix class

* Decrease keypad max-width
2023-11-30 18:19:03 +09:00
b9b3860e6b NFC improvements (#5509)
* 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 (#5450) 2023-11-30 18:12:44 +09:00
b31f1812d2 Greenfield: Remove unused checkout type setting from POS (#5512)
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 (#5331) 2023-11-29 18:51:40 +09:00
1081eab9db Fix warnings (#5517)
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 (#5514) 2023-11-28 15:19:47 +01:00
bac9ab08d1 Make wallet object system much more performant (#5441)
Co-authored-by: nicolas.dorier <nicolas.dorier@gmail.com>
2023-11-28 11:38:09 +01:00
75bf8a5086 Use Mempoolspace fees (#5490)
* 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 (#5504)
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 (#5505)
* 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 #5498.

* 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 (#5495) 2023-11-21 13:38:01 +01:00
5ad320ee4b Domain mapping: Redirect root app to canonical URL (#5471)
* 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 (#5466) 2023-11-21 11:53:24 +01:00
2f23bad3bc Support the new LN lib (#5422)
* 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 (#5462)
Based on #5413 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 (#5473)
* 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: #5476
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 (#5479) 2023-11-21 14:11:17 +09:00
3ee4f43eb5 Remove CurrentRefund property in InvoiceData (#5494) 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 (#5460) 2023-11-20 11:18:19 +01:00
773f8a9aea Apps: Filter list lookups by available app types (#5482)
* 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 #5480.

* 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 (#5489)
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 #5488.
2023-11-20 10:46:36 +09:00
2fb72d5aa6 Payment Request: Improve public view (#5413)
* Payment Request: Improve public view

Closes #4450.

* 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 (#5438) 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 (#5449) 2023-11-09 10:27:33 +01:00
c15f02ddbf Reporting: UI improvements (#5432) 2023-11-09 10:26:00 +01:00
7708084331 Pull payment improvements (#5453) 2023-11-09 10:17:52 +01:00
696a414e95 POS Keypad: Add plus and change clear functionality (#5396)
Closes #5299.
2023-11-02 20:03:34 +01:00
c16dfb2dcb POS and Crowdfund: Improve item editor (#5418)
* 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 (#5391) 2023-11-02 08:36:27 +01:00
e82281d273 switch pos to metadata in invoice create view (#5412)
Co-authored-by: d11n <mail@dennisreimann.de>
2023-11-02 08:13:48 +01:00
27c22d5e33 Unify list views (#5399) 2023-11-02 08:12:28 +01:00
6acc545b66 Greenfield: LNURLPay store payment method fixes (#5446) 2023-11-02 08:11:32 +01:00
609ec0989f Do not activate Blazor in Wizard screens (#5435) 2023-10-27 10:16:36 +02:00
b702621a04 Simplify vault logic by introducing a VaultClient (#5434) 2023-10-27 11:54:15 +09:00
89041a6744 Wallet Send: Fill label from BIP21 (#5428)
Fixes #5426.
2023-10-27 09:59:12 +09:00
c485c109e6 Bumping LND to 0.17.0-beta (#5429) 2023-10-26 10:47:03 +02:00
29a49d5f71 Fix: In pull payment page, the amount of claims wasn't displayed (#5427) 2023-10-25 13:51:27 +02:00
a5fafc4864 Update Passport tooltips (#5423) 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 (#5405)
Closes #5401.
2023-10-18 19:21:56 +09:00
097ffbf8a3 Greenfield: Add missing checkout (V2) settings (#5406)
* Greenfield: Add missing checkout (V2) settings

Closes #5403.

* Fix swagger
2023-10-18 19:20:05 +09:00
ec076d1560 Fix: BTCPayServer.HostedServices.BitpayIPNSender fail to send notifications on some locale (Fix #5361) 2023-10-18 19:07:30 +09:00
8dadfa2111 Reporting fixes (#5410) 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 (#5394)
* 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 (#5387)
* 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 (#5397) 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 (#5388)
* 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 (#5392)
Potentially fixes #5390.
2023-10-12 09:35:47 +09:00
2846c38ff5 Invoice: Unify status display and functionality (#5360)
* 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 (#5371)
* 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 (#5363)
* 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 (#5380) 2023-10-10 14:15:07 +09:00
ef03497350 Fix build warning (#5355)
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 (#5358)
* Pull Payment: Add QR scanner for destination and infer payment method

Closes #4754.

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

Clearer description and display, especially for overpayments. Closes #5207.

* Further refinements

* Test fix
2023-10-10 12:28:00 +09:00
f20e6d3768 Greenfield: allow delete user by email too (#5372) 2023-10-10 12:26:23 +09:00
1d210eb6e3 Crowdfund: Improve no perks case (#5378)
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 #5376.
2023-10-06 22:58:02 +09:00
d8422a979f Fix number of rates (#5365)
* 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 (#5373) 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 (#5357) 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 (#5354)
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 (#5350)
* 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 #5347) 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 (#5352) 2023-09-26 16:40:02 +09:00
0a956fdc73 Remove some useless intermediary type from Rate Source (#5351) 2023-09-26 16:37:40 +09:00
75396f491b Fix: Exchangerate.host falsly appear as Yadio in the UI (Fix #5347) 2023-09-26 14:45:46 +09:00
633 changed files with 12181 additions and 7623 deletions

View File

@ -31,79 +31,23 @@ jobs:
- run:
command: |
curl -X POST -H "Authorization: token $GH_PAT" -H "Accept: application/vnd.github.everest-preview+json" -H "Content-Type: application/json" https://api.github.com/repos/btcpayserver/btcpayserver-doc/dispatches --data '{"event_type": "build_docs"}'
# publish jobs require $DOCKERHUB_REPO, $DOCKERHUB_USER, $DOCKERHUB_PASS defined
amd64:
machine:
image: ubuntu-2004:202111-02
docker:
docker:
- image: cimg/base:stable
steps:
- setup_remote_docker
- checkout
- run:
command: |
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
GIT_COMMIT=$(git rev-parse HEAD)
#
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-amd64 -f amd64.Dockerfile .
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 -f amd64.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-amd64
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64
arm32v7:
machine:
image: ubuntu-2004:202111-02
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
GIT_COMMIT=$(git rev-parse HEAD)
#
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 -f arm32v7.Dockerfile .
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull --build-arg CONFIGURATION_NAME=Altcoins-Release -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 -f arm32v7.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm32v7
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7
arm64v8:
machine:
image: ubuntu-2004:202111-02
steps:
- checkout
- run:
command: |
sudo docker run --rm --privileged multiarch/qemu-user-static:register --reset
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
GIT_COMMIT=$(git rev-parse HEAD)
#
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --pull -t $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 -f arm64v8.Dockerfile .
sudo docker build --build-arg GIT_COMMIT=${GIT_COMMIT} --build-arg CONFIGURATION_NAME=Altcoins-Release --pull -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 -f arm64v8.Dockerfile .
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker push $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8
multiarch:
machine:
image: ubuntu-2004:202201-02
steps:
- run:
command: |
sudo docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
#
LATEST_TAG=${CIRCLE_TAG:1} #trim v from tag
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-arm64v8
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG $DOCKERHUB_REPO:$LATEST_TAG-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG -p
sudo docker manifest create --amend $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-amd64 --os linux --arch amd64
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm32v7 --os linux --arch arm --variant v7
sudo docker manifest annotate $DOCKERHUB_REPO:$LATEST_TAG-altcoins $DOCKERHUB_REPO:$LATEST_TAG-altcoins-arm64v8 --os linux --arch arm64 --variant v8
sudo docker manifest push $DOCKERHUB_REPO:$LATEST_TAG-altcoins -p
docker login --username=$DOCKERHUB_USER --password=$DOCKERHUB_PASS
docker buildx create --use
DOCKER_BUILDX_OPTS="--platform linux/amd64,linux/arm64,linux/arm/v7 --build-arg GIT_COMMIT=${GIT_COMMIT} --push"
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG .
docker buildx build $DOCKER_BUILDX_OPTS -t $DOCKERHUB_REPO:$LATEST_TAG-altcoins --build-arg CONFIGURATION_NAME=Altcoins-Release .
workflows:
version: 2
build_and_test:
@ -120,7 +64,7 @@ workflows:
# only act on version tags
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- amd64:
- docker:
filters:
# ignore any commit on any branch by default
branches:
@ -130,25 +74,3 @@ workflows:
# OR features on specific versions like v1.0.0.88-lndseedbackup-1
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- arm32v7:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- arm64v8:
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/
- multiarch:
requires:
- amd64
- arm32v7
- arm64v8
filters:
branches:
ignore: /.*/
tags:
only: /(v[1-9]+(\.[0-9]+)*(-[a-z0-9-]+)?)|(v[a-z0-9-]+)/

3
.gitignore vendored
View File

@ -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

View File

@ -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.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.1" />
<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" />

View File

@ -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);

View File

@ -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
{

View File

@ -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>();
}
}
}
}

View File

@ -1,5 +1,5 @@
using System.Web;
using Ganss.XSS;
using Ganss.Xss;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Rendering;

View File

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

View File

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

View File

@ -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>();

View File

@ -27,8 +27,10 @@ namespace BTCPayServer.Client.Models
[JsonConverter(typeof(StringEnumConverter))]
public PosViewType DefaultView { get; set; }
public bool ShowCustomAmount { get; set; } = false;
public bool ShowDiscount { get; set; } = true;
public bool EnableTips { get; set; } = true;
public bool ShowDiscount { get; set; } = false;
public bool ShowSearch { get; set; } = true;
public bool ShowCategories { get; set; } = true;
public bool EnableTips { get; set; } = false;
public string CustomAmountPayButtonText { get; set; } = null;
public string FixedAmountPayButtonText { get; set; } = null;
public string TipText { get; set; } = null;
@ -40,7 +42,6 @@ namespace BTCPayServer.Client.Models
public bool? Archived { get; set; } = null;
public string FormId { get; set; } = null;
public string EmbeddedCSS { get; set; } = null;
public CheckoutType? CheckoutType { get; set; } = null;
}
public enum CrowdfundResetEvery

View File

@ -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
}

View File

@ -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()

View File

@ -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;
}
}
}

View File

@ -21,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; }

View File

@ -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; }
}
}

View File

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

View File

@ -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;

View File

@ -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]

View File

@ -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);
}

View File

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

View File

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

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

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

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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
});
}
}
}

View File

@ -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 }
}
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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'")
});
}
}
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"
});
}
}
}

View File

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

View File

@ -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"
});
}
}
}

View File

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

View File

@ -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; }
}
}

View File

@ -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
}
});
}
}
}

View File

@ -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,37 @@ 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)
{
_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
// Assume that electrum mappings are same as BTC if not specified
foreach (var network in _Networks.Values.OfType<BTCPayNetwork>())
var networksList = networks.ToList();
#if !ALTCOINS
var onlyBTC = networksList.Count == 1 && networksList.First().IsBTC;
if (!onlyBTC)
throw new ConfigException($"This build of BTCPay Server does not support altcoins. Configured networks: {string.Join(',', networksList.Select(n => n.CryptoCode).ToArray())}");
#endif
_NBXplorerNetworkProvider = nbxplorerNetworkProvider;
NetworkType = nbxplorerNetworkProvider.NetworkType;
foreach (var network in networksList)
{
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: {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();

View File

@ -4,10 +4,13 @@
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="4.2.5" />
<PackageReference Include="NBXplorer.Client" Version="4.3.0" />
<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>

View File

@ -174,7 +174,6 @@ namespace BTCPayServer.Logging
logLevelColors = GetLogLevelConsoleColors(logLevel);
logLevelString = GetLogLevelString(logLevel);
// category and event id
var lenBefore = logBuilder.ToString().Length;
logBuilder.Append(_loglevelPadding);
logBuilder.Append(logName);
logBuilder.Append(": ");

View File

@ -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;
}
}

View File

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

View File

@ -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.1">
<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.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BTCPayServer.Abstractions\BTCPayServer.Abstractions.csproj" />

View File

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

View File

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

View File

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

View File

@ -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>()

View File

@ -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();
}
}
}

View File

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

View File

@ -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");
}
}
}

View File

@ -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)
{
}
}
}

View File

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

View File

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

View File

@ -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>

View File

@ -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())
};
}
}

View File

@ -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.8.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
<PackageReference Include="NBitcoin" Version="7.0.24" />
<PackageReference Include="NBitcoin" Version="7.0.34" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.2" />
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="1.0.4" />
</ItemGroup>
<ItemGroup>

View File

@ -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)
{
@ -64,8 +64,17 @@ namespace BTCPayServer.Services.Rates
{
if (_CurrencyProviders.Count == 0)
{
foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures).Where(c => !c.IsNeutralCulture))
foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
// This avoid storms of exception throwing slowing up
// startup and debugging sessions
if (culture switch
{
{ LCID: 0x007F or 0x0000 or 0x0c00 or 0x1000 } => true,
{ IsNeutralCulture : true } => true,
_ => false
})
continue;
try
{
_CurrencyProviders.TryAdd(new RegionInfo(culture.LCID).ISOCurrencySymbol, culture);

View File

@ -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)

View File

@ -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)) };
}
}
}

View File

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

View File

@ -110,7 +110,7 @@ namespace BTCPayServer.Services.Rates
public void LoadState(BackgroundFetcherState state)
{
if (state.LastRequested is DateTimeOffset lastRequested)
if (state.LastRequested is DateTimeOffset)
this.LastRequested = state.LastRequested;
if (state.LastUpdated is DateTimeOffset updated && state.Rates is List<BackgroundFetcherRate> rates)
{

View File

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

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long

View File

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

View File

@ -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();
}
}

View File

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

View File

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

View File

@ -87,7 +87,6 @@ namespace BTCPayServer.Services.Rates
var normalizedPairsList = symbols.Where(s => !notFoundSymbols.ContainsKey(s)).Select(s => helper.NormalizeMarketSymbol(s)).ToList();
var csvPairsList = string.Join(",", normalizedPairsList);
JToken apiTickers = await MakeJsonRequestAsync<JToken>("/0/public/Ticker", null, new Dictionary<string, object> { { "pair", csvPairsList } }, cancellationToken: cancellationToken);
var tickers = new List<KeyValuePair<string, ExchangeTicker>>();
foreach (string symbol in symbols)
{
var ticker = ConvertToExchangeTicker(symbol, apiTickers[symbol]);
@ -172,7 +171,7 @@ namespace BTCPayServer.Services.Rates
sb.Append(String.Join('&', payload.Select(kv => $"{kv.Key}={kv.Value}").OfType<object>().ToArray()));
}
var request = new HttpRequestMessage(HttpMethod.Get, sb.ToString());
var response = await HttpClient.SendAsync(request, cancellationToken);
using var response = await HttpClient.SendAsync(request, cancellationToken);
string stringResult = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<T>(stringResult);
if (result is JToken json)

View File

@ -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);
using 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))

View File

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

View File

@ -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);

View File

@ -45,7 +45,6 @@ namespace BTCPayServer.Services.Rates
var fetchingRates = new Dictionary<CurrencyPair, Task<RateResult>>();
var fetchingExchanges = new Dictionary<string, Task<QueryRateResult>>();
var consolidatedRates = new ExchangeRates();
foreach (var i in pairs.Select(p => (Pair: p, RateRule: rules.GetRuleFor(p))))
{

View File

@ -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)
{

View File

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

View File

@ -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(() =>
{

View File

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

View File

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

View File

@ -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; }

View File

@ -60,13 +60,13 @@ namespace BTCPayServer.Tests
Assert.Contains("Bitcoin", s.Driver.FindElement(By.CssSelector(".payment-method.active")).Text);
Assert.Contains("LNURL", s.Driver.FindElement(By.CssSelector(".payment-method:nth-child(2)")).Text);
var qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
var address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
var clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
var payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var copyAddress = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
Assert.Equal($"bitcoin:{address}", payUrl);
var address = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
Assert.StartsWith("bcrt", s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text);
Assert.DoesNotContain("lightning=", payUrl);
Assert.Equal(address, copyAddress);
Assert.Equal($"bitcoin:{address}", payUrl);
Assert.Equal($"bitcoin:{address}", clipboard);
Assert.Equal($"bitcoin:{address.ToUpperInvariant()}", qrValue);
s.Driver.ElementDoesNotExist(By.Id("Lightning_BTC"));
@ -97,11 +97,11 @@ namespace BTCPayServer.Tests
Assert.Contains("Lightning", s.Driver.WaitForElement(By.CssSelector(".payment-method.active")).Text);
Assert.Contains("Bitcoin", s.Driver.WaitForElement(By.CssSelector(".payment-method")).Text);
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
copyAddress = s.Driver.FindElement(By.CssSelector("#Lightning_BTC_LightningLike .truncate-center-start")).Text;
address = s.Driver.FindElement(By.CssSelector("#Lightning_BTC_LightningLike .truncate-center-start")).Text;
Assert.Equal($"lightning:{address}", payUrl);
Assert.Equal(address, copyAddress);
Assert.Equal($"lightning:{address}", clipboard);
Assert.Equal($"lightning:{address.ToUpperInvariant()}", qrValue);
s.Driver.ElementDoesNotExist(By.Id("Address_BTC"));
@ -153,7 +153,7 @@ namespace BTCPayServer.Tests
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
await Task.Delay(200);
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
address = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
var amountFraction = "0.00001";
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
Money.Parse(amountFraction));
@ -202,15 +202,14 @@ namespace BTCPayServer.Tests
// Pay partial amount
await Task.Delay(200);
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
amountFraction = "0.00001";
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(address, Network.RegTest),
Money.Parse(amountFraction));
await s.Server.ExplorerNode.GenerateAsync(1);
s.Driver.FindElement(By.Id("test-payment-amount")).Clear();
s.Driver.FindElement(By.Id("test-payment-amount")).SendKeys("0.00001");
// Fake Pay
TestUtils.Eventually(() =>
{
s.Driver.FindElement(By.Id("FakePayment")).Click();
s.Driver.FindElement(By.Id("mine-block")).Click();
paymentInfo = s.Driver.WaitForElement(By.Id("PaymentInfo"));
Assert.Contains("The invoice hasn't been paid in full", paymentInfo.Text);
Assert.Contains("Please send", paymentInfo.Text);
@ -265,18 +264,19 @@ namespace BTCPayServer.Tests
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
Assert.Contains("BTC", s.Driver.FindElement(By.Id("AmountDue")).Text);
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
var copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
var copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
Assert.StartsWith($"bitcoin:{address}?amount=", payUrl);
Assert.StartsWith($"bitcoin:{copyAddressOnchain}?amount=", payUrl);
Assert.Contains("?amount=", payUrl);
Assert.Contains("&lightning=", payUrl);
Assert.StartsWith("bcrt", copyAddressOnchain);
Assert.Equal(address, copyAddressOnchain);
Assert.StartsWith("lnbcrt", copyAddressLightning);
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?amount=", qrValue);
Assert.StartsWith($"bitcoin:{copyAddressOnchain.ToUpperInvariant()}?amount=", qrValue);
Assert.Contains("&lightning=LNBCRT", qrValue);
Assert.Contains("&lightning=lnbcrt", clipboard);
Assert.Equal(clipboard, payUrl);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");
@ -333,17 +333,18 @@ namespace BTCPayServer.Tests
s.Driver.WaitUntilAvailable(By.Id("Checkout-v2"));
Assert.Empty(s.Driver.FindElements(By.CssSelector(".payment-method")));
qrValue = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-qr-value");
address = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
clipboard = s.Driver.FindElement(By.CssSelector(".qr-container")).GetAttribute("data-clipboard");
payUrl = s.Driver.FindElement(By.Id("PayInWallet")).GetAttribute("href");
copyAddressOnchain = s.Driver.FindElement(By.CssSelector("#Address_BTC .truncate-center-start")).Text;
copyAddressLightning = s.Driver.FindElement(By.CssSelector("#Lightning_BTC .truncate-center-start")).Text;
Assert.StartsWith($"bitcoin:{address}", payUrl);
Assert.StartsWith($"bitcoin:{copyAddressOnchain}", payUrl);
Assert.Contains("?lightning=lnurl", payUrl);
Assert.DoesNotContain("amount=", payUrl);
Assert.StartsWith("bcrt", copyAddressOnchain);
Assert.Equal(address, copyAddressOnchain);
Assert.StartsWith("lnurl", copyAddressLightning);
Assert.StartsWith($"bitcoin:{address.ToUpperInvariant()}?lightning=LNURL", qrValue);
Assert.StartsWith($"bitcoin:{copyAddressOnchain.ToUpperInvariant()}?lightning=LNURL", qrValue);
Assert.Contains($"bitcoin:{copyAddressOnchain}?lightning=lnurl", clipboard);
Assert.Equal(clipboard, payUrl);
// Check details
s.Driver.ToggleCollapse("PaymentDetails");

View File

@ -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.101-bookworm-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends chromium-driver \
&& rm -rf /var/lib/apt/lists/*

View File

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

View File

@ -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,12 +19,16 @@ 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.Fees;
using BTCPayServer.Services.Invoices;
using BTCPayServer.Services.Labels;
using BTCPayServer.Services.Rates;
@ -131,7 +136,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)})");
}
}
}
@ -155,6 +160,43 @@ namespace BTCPayServer.Tests
Assert.Equal("Test", data.FromAsset);
}
[Fact]
public void CanInterpolateOrBound()
{
var testData = new ((int Blocks, decimal Fee)[] Data, int Target, decimal Expected) []
{
([(0, 0m), (10, 100m)], 5, 50m),
([(50, 0m), (100, 100m)], 5, 0.0m),
([(50, 0m), (100, 100m)], 101, 100.0m),
([(50, 100m), (50, 100m)], 101, 100.0m),
([(50, 0m), (100, 100m)], 75, 50m),
([(0, 0m), (50, 50m), (100, 100m)], 75, 75m),
([(0, 0m), (500, 50m), (1000, 100m)], 750, 75m),
([(0, 0m), (500, 50m), (1000, 100m)], 100, 10m),
([(0, 0m), (100, 100m)], 80, 80m),
([(0, 0m), (100, 100m)], 25, 25m),
([(0, 0m), (25, 25m), (50, 50m), (100, 100m), (110, 120m)], 75, 75m),
([(0, 0m), (25, 0m), (50, 50m), (100, 100m), (110, 0m)], 75, 75m),
([(0, 0m), (25, 0m), (50, 50m), (100, 100m), (110, 0m)], 50, 50m),
([(0, 0m), (25, 0m), (50, 50m), (100, 100m), (110, 0m)], 100, 100m),
([(0, 0m), (25, 0m), (50, 50m), (100, 100m), (110, 0m)], 102, 80m),
};
foreach (var t in testData)
{
var actual = MempoolSpaceFeeProvider.InterpolateOrBound(t.Data.Select(t => new MempoolSpaceFeeProvider.BlockFeeRate(t.Blocks, new FeeRate(t.Fee))).ToArray(), t.Target);
Assert.Equal(new FeeRate(t.Expected), actual);
}
}
[Fact]
public void CanRandomizeByPercentage()
{
var generated = Enumerable.Range(0, 1000).Select(_ => MempoolSpaceFeeProvider.RandomizeByPercentage(100.0m, 10.0m)).ToArray();
Assert.Empty(generated.Where(g => g < 90m));
Assert.Empty(generated.Where(g => g > 110m));
Assert.NotEmpty(generated.Where(g => g < 91m));
Assert.NotEmpty(generated.Where(g => g > 109m));
}
private void CanParseDecimalsCore(string str, decimal expected)
{
var d = JsonConvert.DeserializeObject<LedgerEntryData>(str);
@ -410,7 +452,7 @@ namespace BTCPayServer.Tests
public void CanCalculateDust()
{
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = new BTCPayNetworkProvider(ChainName.Regtest);
entity.Networks = CreateNetworkProvider(ChainName.Regtest);
#pragma warning disable CS0618
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.SetPaymentMethod(new PaymentMethod()
@ -456,7 +498,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanCalculateCryptoDue()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var entity = new InvoiceEntity() { Currency = "USD" };
entity.Networks = networkProvider;
#pragma warning disable CS0618
@ -644,12 +686,7 @@ namespace BTCPayServer.Tests
[Fact]
public void CanAcceptInvoiceWithTolerance()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
});
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var entity = new InvoiceEntity();
entity.Networks = networkProvider;
#pragma warning disable CS0618
@ -764,7 +801,7 @@ namespace BTCPayServer.Tests
[Fact]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
var tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc")
@ -776,7 +813,7 @@ namespace BTCPayServer.Tests
Assert.Single(tor.Services.Where(t => t.ServiceType == TorServiceType.RPC));
Assert.True(tor.Services.Count(t => t.ServiceType == TorServiceType.Other) > 1);
tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
tor = new TorServices(CreateNetworkProvider(ChainName.Regtest),
new OptionsWrapper<BTCPayServerOptions>(new BTCPayServerOptions()
{
TorrcFile = null,
@ -813,12 +850,13 @@ 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
var xpub = "xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw";
DerivationStrategyBase strategyBase = parser.Parse(xpub);
Assert.Throws<FormatException>(() => parser.Parse(xpub, false, false, true));
DerivationStrategyBase strategyBase = parser.Parse(xpub, false, false, false);
Assert.IsType<DirectDerivationStrategy>(strategyBase);
Assert.True(((DirectDerivationStrategy)strategyBase).Segwit);
Assert.Equal("tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS", strategyBase.ToString());
@ -839,7 +877,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);
@ -878,18 +916,26 @@ namespace BTCPayServer.Tests
Assert.Equal(TradeQuantity.Parse(qty2.ToString()), TradeQuantity.Parse(" 1.3 "));
}
public static WalletFileParsers GetParsers()
{
var service = new ServiceCollection();
BTCPayServerServices.AddOnchainWalletParsers(service);
return service.BuildServiceProvider().GetRequiredService<WalletFileParsers>();
}
[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();
var parsers = GetParsers();
// xpub
var tpub = "tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(tpub, testnet, out var settings, out var error));
Assert.True(parsers.TryParseWalletFile(tpub, testnet, out var settings, out var error));
Assert.Null(error);
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
Assert.Equal($"{tpub}-[legacy]", ((DirectDerivationStrategy)settings.AccountDerivation).ToString());
@ -900,7 +946,7 @@ namespace BTCPayServer.Tests
var fingerprint = "e5746fd9";
var account = "84'/1'/0'";
var str = $"[{fingerprint}/{account}]{vpub}";
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(str, testnet, out settings, out error));
Assert.True(parsers.TryParseWalletFile(str, testnet, out settings, out error));
Assert.Null(error);
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: true });
Assert.Equal(vpub, settings.AccountOriginal);
@ -909,7 +955,7 @@ namespace BTCPayServer.Tests
Assert.Equal(account, settings.AccountKeySettings[0].AccountKeyPath.ToString());
// ColdCard
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
Assert.True(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
mainnet, out settings, out error));
Assert.Null(error);
@ -925,28 +971,28 @@ namespace BTCPayServer.Tests
settings.AccountDerivation.GetDerivation().ScriptPubKey);
// Should be legacy
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
Assert.True(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"tpubDDWYqT3P24znfsaGX7kZcQhNc5LAjnQiKQvUCHF2jS6dsgJBRtymopEU5uGpMaR5YChjuiExZG1X2aTbqXkp82KqH5qnqwWHp6EWis9ZvKr\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/44'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: false });
Assert.Null(error);
// Should be segwit p2sh
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
Assert.True(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DSddA9NoRUyJrQ4p86nsCiTSY7kLHrSxx3joEJXjHd4HPARhdXUATuk585FdWPVC2GdjsMePHb6BMDmf7c6KG4K4RPX6LVqBLtDcWpQJmh\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is P2SHDerivationStrategy { Inner: DirectDerivationStrategy { Segwit: true } });
Assert.Null(error);
// Should be segwit
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
Assert.True(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
testnet, out settings, out error));
Assert.True(settings.AccountDerivation is DirectDerivationStrategy { Segwit: true });
Assert.Null(error);
// Specter
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
Assert.True(parsers.TryParseWalletFile(
"{\"label\": \"Specter\", \"blockheight\": 123456, \"descriptor\": \"wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48\"}",
mainnet, out var specter, out error));
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), specter.AccountKeySettings[0].RootFingerprint);
@ -955,19 +1001,63 @@ namespace BTCPayServer.Tests
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(parsers.TryParseWalletFile(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(
Assert.False(parsers.TryParseWalletFile(
"{\"keystore\": {\"ckcc_xpub\": \"tpubFailure\", \"xpub\": \"tpubFailure\", \"label\": \"Failure\"}, \"wallet_type\": \"standard\"}",
testnet, out settings, out error));
Assert.Null(settings);
Assert.NotNull(error);
//passport
var passportText =
"{\"Source\": \"Passport\", \"Descriptor\": \"tr([5c9e228d/86'/0'/0']xpub6EgGHjcvovyN3nK921zAGPfuB41cJXkYRdt3tLGmiMyvbgHpss4X1eRZwShbEBb1znz2e2bCkCED87QZpin3sSYKbmCzQ9Sc7LaV98ngdeX/0/*)\", \"FirmwareVersion\": \"v1.0.0\"}";
Assert.True(parsers.TryParseWalletFile(passportText, mainnet, out var passport, out error));
Assert.Equal("Passport", passport.Source);
Assert.True(passport.AccountDerivation is TaprootDerivationStrategy);
Assert.Equal("5c9e228d", passport.AccountKeySettings[0].RootFingerprint.ToString());
Assert.Equal("86'/0'/0'", passport.AccountKeySettings[0].AccountKeyPath.ToString());
}
[Fact]
public void CheckRatesProvider()
public async Task CheckRatesProvider()
{
var spy = new SpyRateProvider();
RateRules.TryParse("X_X = bittrex(X_X);", out var rateRules);
RateRules.TryParse("X_X = bitpay(X_X);", out var rateRules);
var factory = CreateBTCPayRateFactory();
factory.Providers.Clear();
@ -975,24 +1065,23 @@ namespace BTCPayServer.Tests
factory.Providers.Clear();
var fetch = new BackgroundFetcherRateProvider(spy);
fetch.DoNotAutoFetchIfExpired = true;
factory.Providers.Add("bittrex", fetch);
var fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter()
.GetResult();
factory.Providers.Add("bitpay", fetch);
var fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertHit();
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertNotHit();
fetch.RefreshRate = TimeSpan.FromSeconds(1.0);
Thread.Sleep(1020);
fetchedRate = fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default).GetAwaiter().GetResult();
fetchedRate = await fetcher.FetchRate(CurrencyPair.Parse("BTC_USD"), rateRules, default);
spy.AssertNotHit();
fetch.ValidatyTime = TimeSpan.FromSeconds(1.0);
fetch.UpdateIfNecessary(default).GetAwaiter().GetResult();
await fetch.UpdateIfNecessary(default);
spy.AssertHit();
fetch.GetRatesAsync(default).GetAwaiter().GetResult();
await fetch.GetRatesAsync(default);
Thread.Sleep(1000);
Assert.Throws<InvalidOperationException>(() => fetch.GetRatesAsync(default).GetAwaiter().GetResult());
await Assert.ThrowsAsync<InvalidOperationException>(() => fetch.GetRatesAsync(default));
}
public static RateProviderFactory CreateBTCPayRateFactory()
@ -1246,7 +1335,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);
@ -1564,7 +1653,7 @@ namespace BTCPayServer.Tests
{
var b = JsonConvert.DeserializeObject<PullPaymentBlob>("{}");
Assert.Equal(TimeSpan.FromDays(30.0), b.BOLT11Expiration);
var aaa = JsonConvert.SerializeObject(b);
JsonConvert.SerializeObject(b);
}
[Fact]
@ -1577,7 +1666,7 @@ namespace BTCPayServer.Tests
StringBuilder builder = new StringBuilder();
builder.AppendLine("// Some cool comments");
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("// Some other cool comments");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X);");
@ -1588,7 +1677,7 @@ namespace BTCPayServer.Tests
Assert.Equal(
"// Some cool comments\n" +
"DOGE_X = DOGE_BTC * BTC_X * 1.1;\n" +
"DOGE_BTC = bittrex(DOGE_BTC);\n" +
"DOGE_BTC = bitpay(DOGE_BTC);\n" +
"// Some other cool comments\n" +
"BTC_USD = kraken(BTC_USD);\n" +
"BTC_X = coinbase(BTC_X);\n" +
@ -1596,10 +1685,10 @@ namespace BTCPayServer.Tests
rules.ToString());
var tests = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
(Pair: "DOGE_USD", Expected: "bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
(Pair: "DOGE_CAD", Expected: "bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02"),
(Pair: "SATS_CAD", Expected: "0.00000001 * coinbase(BTC_CAD)"),
(Pair: "Sats_USD", Expected: "0.00000001 * kraken(BTC_USD)")
@ -1609,13 +1698,13 @@ namespace BTCPayServer.Tests
Assert.Equal(test.Expected, rules.GetRuleFor(CurrencyPair.Parse(test.Pair)).ToString());
}
rules.Spread = 0.2m;
Assert.Equal("(bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
Assert.Equal("(bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1) * (0.8, 1.2)", rules.GetRuleFor(CurrencyPair.Parse("DOGE_USD")).ToString());
////////////////
// Check errors conditions
builder = new StringBuilder();
builder.AppendLine("DOGE_X = LTC_CAD * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("LTC_CHF = LTC_CHF * 1.01");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
@ -1636,7 +1725,7 @@ namespace BTCPayServer.Tests
// Check if we can resolve exchange rates
builder = new StringBuilder();
builder.AppendLine("DOGE_X = DOGE_BTC * BTC_X * 1.1");
builder.AppendLine("DOGE_BTC = Bittrex(DOGE_BTC)");
builder.AppendLine("DOGE_BTC = bitpay(DOGE_BTC)");
builder.AppendLine("BTC_usd = kraken(BTC_USD)");
builder.AppendLine("BTC_X = Coinbase(BTC_X)");
builder.AppendLine("X_X = CoinAverage(X_X) * 1.02");
@ -1644,10 +1733,10 @@ namespace BTCPayServer.Tests
var tests2 = new[]
{
(Pair: "DOGE_USD", Expected: "bittrex(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),kraken(BTC_USD)"),
(Pair: "DOGE_USD", Expected: "bitpay(DOGE_BTC) * kraken(BTC_USD) * 1.1", ExpectedExchangeRates: "bitpay(DOGE_BTC),kraken(BTC_USD)"),
(Pair: "BTC_USD", Expected: "kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
(Pair: "BTC_CAD", Expected: "coinbase(BTC_CAD)", ExpectedExchangeRates: "coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bittrex(DOGE_BTC),coinbase(BTC_CAD)"),
(Pair: "DOGE_CAD", Expected: "bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", ExpectedExchangeRates: "bitpay(DOGE_BTC),coinbase(BTC_CAD)"),
(Pair: "LTC_CAD", Expected: "coinaverage(LTC_CAD) * 1.02", ExpectedExchangeRates: "coinaverage(LTC_CAD)"),
(Pair: "SATS_USD", Expected: "0.00000001 * kraken(BTC_USD)", ExpectedExchangeRates: "kraken(BTC_USD)"),
(Pair: "SATS_EUR", Expected: "0.00000001 * coinbase(BTC_EUR)", ExpectedExchangeRates: "coinbase(BTC_EUR)")
@ -1659,11 +1748,11 @@ namespace BTCPayServer.Tests
Assert.Equal(test.ExpectedExchangeRates, string.Join(',', rule.ExchangeRates.OfType<object>().ToArray()));
}
var rule2 = rules.GetRuleFor(CurrencyPair.Parse("DOGE_CAD"));
rule2.ExchangeRates.SetRate("bittrex", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
rule2.ExchangeRates.SetRate("bitpay", CurrencyPair.Parse("DOGE_BTC"), new BidAsk(5000m));
rule2.Reevaluate();
Assert.True(rule2.HasError);
Assert.Equal("5000 * ERR_RATE_UNAVAILABLE(coinbase, BTC_CAD) * 1.1", rule2.ToString(true));
Assert.Equal("bittrex(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
Assert.Equal("bitpay(DOGE_BTC) * coinbase(BTC_CAD) * 1.1", rule2.ToString(false));
rule2.ExchangeRates.SetRate("coinbase", CurrencyPair.Parse("BTC_CAD"), new BidAsk(2000.4m));
rule2.Reevaluate();
Assert.False(rule2.HasError);
@ -1825,8 +1914,7 @@ namespace BTCPayServer.Tests
new KeyValuePair<string, string>("chains", "usdt")}
})
});
var networkProvider = config.ConfigureNetworkProvider(BTCPayLogs);
var networkProvider = CreateNetworkProvider(config);
Assert.NotNull(networkProvider.GetNetwork("LBTC"));
Assert.NotNull(networkProvider.GetNetwork("USDT"));
}
@ -1834,16 +1922,16 @@ 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;
// Passing electrum stuff
// Passing a native segwit from mainnet to a testnet parser, means the testnet parser will try to convert it into segwit
result = testnetParser.Parse(
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t");
"zpub6nL6PUGurpU3DfPDSZaRS6WshpbNc9ctCFFzrCn54cssnheM31SZJZUcFHKtjJJNhAueMbh6ptFMfy1aeiMQJr3RJ4DDt1hAPx7sMTKV48t", false, false, false);
Assert.Equal(
"tpubD93CJNkmGjLXnsBqE2zGDqfEh1Q8iJ8wueordy3SeWt1RngbbuxXCsqASuVWFywmfoCwUE1rSfNJbaH4cBNcbp8WcyZgPiiRSTazLGL8U9w",
result.ToString());
@ -1867,7 +1955,7 @@ namespace BTCPayServer.Tests
// if prefix not recognize, assume it is segwit
result = testnetParser.Parse(
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X");
"xpub661MyMwAqRbcGeVGU5e5KBcau1HHEUGf9Wr7k4FyLa8yRPNQrrVa7Ndrgg8Afbe2UYXMSL6tJBFd2JewwWASsePPLjkcJFL1tTVEs3UQ23X", false, false, false);
Assert.Equal(
"tpubD6NzVbkrYhZ4YSg7vGdAX6wxE8NwDrmih9SR6cK7gUtsAg37w5LfFpJgviCxC6bGGT4G3uckqH5fiV9ZLN1gm5qgQLVuymzFUR5ed7U7ksu",
result.ToString());
@ -1876,13 +1964,13 @@ namespace BTCPayServer.Tests
var tpub =
"tpubD6NzVbkrYhZ4Wc65tjhmcKdWFauAo7bGLRTxvggygkNyp6SMGutJp7iociwsinU33jyNBp1J9j2hJH5yQsayfiS3LEU2ZqXodAcnaygra8o";
result = testnetParser.Parse(tpub);
result = testnetParser.Parse(tpub, false, true);
Assert.Equal(tpub, result.ToString());
var regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var parsed =
regtestParser.Parse(
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]", false, false, false);
Assert.Equal(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]",
parsed.ToString());
@ -1890,14 +1978,14 @@ namespace BTCPayServer.Tests
// Let's make sure we can't generate segwit with dogecoin
regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("DOGE"));
parsed = regtestParser.Parse(
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]");
"xpub6DG1rMYXiQtCc6CfdLFD9CtxqhzzRh7j6Sq6EdE9abgYy3cfDRrniLLv2AdwqHL1exiLnnKR5XXcaoiiexf3Y9R6J6rxkJtqJHzNzMW9QMZ-[p2sh]", false, false, false);
Assert.Equal(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
parsed.ToString());
regtestParser = new DerivationSchemeParser(regtestNetworkProvider.GetNetwork<BTCPayNetwork>("DOGE"));
parsed = regtestParser.Parse(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]");
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[p2sh]", false, false, false);
Assert.Equal(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
parsed.ToString());
@ -2002,7 +2090,7 @@ namespace BTCPayServer.Tests
{
#pragma warning disable CS0618
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = CreateNetworkProvider(ChainName.Regtest);
var networkBTC = networkProvider.GetNetwork("BTC");
var networkLTC = networkProvider.GetNetwork("LTC");
InvoiceEntity invoiceEntity = new InvoiceEntity();
@ -2074,6 +2162,7 @@ namespace BTCPayServer.Tests
[Fact]
public void AllPoliciesShowInUI()
{
new BitpayRateProvider(new System.Net.Http.HttpClient()).GetRatesAsync(default).GetAwaiter().GetResult();
foreach (var policy in Policies.AllPolicies)
{
Assert.True(UIManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.ContainsKey(policy));
@ -2119,7 +2208,7 @@ namespace BTCPayServer.Tests
{
["derivationStrategy"] = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf"
};
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", new BTCPayNetworkProvider(ChainName.Regtest).BTC);
var scheme = DerivationSchemeSettings.Parse("tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf", CreateNetworkProvider(ChainName.Regtest).BTC);
scheme.Source = "ManualDerivationScheme";
scheme.AccountOriginal = "tpubDDLQZ1WMdy5YJAJWmRNoTJ3uQkavEPXCXnmD4eAuo9BKbzFUBbJmVHys5M3ku4Qw1C165wGpVWH55gZpHjdsCyntwNzhmCAzGejSL6rzbyf";
@ -2139,7 +2228,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();

View File

@ -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)

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Contracts;
@ -28,6 +29,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using Xunit;
using Xunit.Abstractions;
using Xunit.Sdk;
using CreateApplicationUserRequest = BTCPayServer.Client.Models.CreateApplicationUserRequest;
namespace BTCPayServer.Tests
@ -56,8 +58,8 @@ namespace BTCPayServer.Tests
var factory = tester.PayTester.GetService<IBTCPayServerClientFactory>();
Assert.NotNull(factory);
var client = await factory.Create(user.UserId, user.StoreId);
var u = await client.GetCurrentUser();
var s = await client.GetStores();
await client.GetCurrentUser();
await client.GetStores();
var store = await client.GetStore(user.StoreId);
Assert.NotNull(store);
var addr = await client.GetLightningDepositAddress(user.StoreId, "BTC");
@ -1084,7 +1086,39 @@ namespace BTCPayServer.Tests
Assert.IsType<string>(lnrURLs.LNURLUri);
Assert.Equal(12.303228134m, test4.Amount);
Assert.Equal("BTC", test4.Currency);
// Check we can register Boltcard
var uid = new byte[7];
RandomNumberGenerator.Fill(uid);
var card = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(0, card.Version);
var card1keys = new[] { card.K0, card.K1, card.K2, card.K3, card.K4 };
Assert.DoesNotContain(null, card1keys);
var card2 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid
});
Assert.Equal(1, card2.Version);
Assert.StartsWith("lnurlw://", card2.LNURLW);
Assert.EndsWith("/boltcard", card2.LNURLW);
var card2keys = new[] { card2.K0, card2.K1, card2.K2, card2.K3, card2.K4 };
Assert.DoesNotContain(null, card2keys);
for (int i = 0; i < card1keys.Length; i++)
{
if (i == 1)
Assert.Contains(card1keys[i], card2keys);
else
Assert.DoesNotContain(card1keys[i], card2keys);
}
var card3 = await client.RegisterBoltcard(test4.Id, new RegisterBoltcardRequest()
{
UID = uid,
OnExisting = OnExistingBehavior.KeepVersion
});
Assert.Equal(card2.Version, card3.Version);
// Test with SATS denomination values
var testSats = await client.CreatePullPayment(storeId, new Client.Models.CreatePullPaymentRequest()
{
@ -1104,7 +1138,7 @@ namespace BTCPayServer.Tests
var approved = await acc.CreateClient(Policies.CanCreatePullPayments);
await AssertPermissionError(Policies.CanCreatePullPayments, async () =>
{
var pullPayment = await nonApproved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
await nonApproved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
{
Amount = 100,
Currency = "USD",
@ -1115,7 +1149,7 @@ namespace BTCPayServer.Tests
});
await AssertPermissionError(Policies.CanCreatePullPayments, async () =>
{
var pullPayment = await nonApproved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
await nonApproved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 100,
PaymentMethod = "BTC",
@ -1124,7 +1158,7 @@ namespace BTCPayServer.Tests
});
});
var pullPayment = await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
await approved.CreatePullPayment(acc.StoreId, new CreatePullPaymentRequest()
{
Amount = 100,
Currency = "USD",
@ -1133,7 +1167,7 @@ namespace BTCPayServer.Tests
AutoApproveClaims = true
});
var p = await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
await approved.CreatePayout(acc.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 100,
PaymentMethod = "BTC",
@ -1284,15 +1318,18 @@ namespace BTCPayServer.Tests
var client = await user.CreateClient(Policies.Unrestricted);
//create store
var newStore = await client.CreateStore(new CreateStoreRequest() { Name = "A" });
var newStore = await client.CreateStore(new CreateStoreRequest { Name = "A" });
Assert.Equal("A", newStore.Name);
Assert.Equal(CheckoutType.V2, newStore.CheckoutType);
//update store
Assert.Empty(newStore.PaymentMethodCriteria);
await client.GenerateOnChainWallet(newStore.Id, "BTC", new GenerateOnChainWalletRequest());
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest()
var updatedStore = await client.UpdateStore(newStore.Id, new UpdateStoreRequest
{
Name = "B",
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>()
CheckoutType = CheckoutType.V1,
PaymentMethodCriteria = new List<PaymentMethodCriteriaData>
{
new()
{
@ -1304,6 +1341,7 @@ namespace BTCPayServer.Tests
}
});
Assert.Equal("B", updatedStore.Name);
Assert.Equal(CheckoutType.V1, updatedStore.CheckoutType);
var s = (await client.GetStore(newStore.Id));
Assert.Equal("B", s.Name);
var pmc = Assert.Single(s.PaymentMethodCriteria);
@ -1457,7 +1495,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);
@ -2095,18 +2133,18 @@ namespace BTCPayServer.Tests
//validation errors
await AssertValidationError(new[] { nameof(CreateInvoiceRequest.Amount), $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentTolerance)}", $"{nameof(CreateInvoiceRequest.Checkout)}.{nameof(CreateInvoiceRequest.Checkout.PaymentMethods)}[0]" }, async () =>
{
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions() { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest { Amount = -1, Checkout = new CreateInvoiceRequest.CheckoutOptions { PaymentTolerance = -2, PaymentMethods = new[] { "jasaas_sdsad" } } });
});
await AssertHttpError(403, async () =>
{
await viewOnly.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "helloinvalid", Amount = 1 });
new CreateInvoiceRequest { Currency = "helloinvalid", Amount = 1 });
});
await user.RegisterDerivationSchemeAsync("BTC");
string origOrderId = "testOrder";
var newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
@ -2193,7 +2231,7 @@ namespace BTCPayServer.Tests
//list NonExisting Status
var invoicesNonExistingStatus = await viewOnly.GetInvoices(user.StoreId,
status: new[] { BTCPayServer.Client.Models.InvoiceStatus.Invalid });
status: new[] { InvoiceStatus.Invalid });
Assert.NotNull(invoicesNonExistingStatus);
Assert.Empty(invoicesNonExistingStatus);
@ -2211,7 +2249,7 @@ namespace BTCPayServer.Tests
//update
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
Assert.Contains(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
@ -2223,7 +2261,7 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain(InvoiceStatus.Settled, newInvoice.AvailableStatusesForManualMarking);
Assert.Contains(InvoiceStatus.Invalid, newInvoice.AvailableStatusesForManualMarking);
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
new CreateInvoiceRequest { Currency = "USD", Amount = 1 });
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
{
Status = InvoiceStatus.Invalid
@ -2238,13 +2276,13 @@ namespace BTCPayServer.Tests
await AssertHttpError(403, async () =>
{
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
});
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
new UpdateInvoiceRequest()
new UpdateInvoiceRequest
{
Metadata = metadataForUpdate
});
@ -2284,13 +2322,12 @@ namespace BTCPayServer.Tests
await client.UnarchiveInvoice(user.StoreId, invoice.Id);
Assert.NotNull(await client.GetInvoice(user.StoreId, invoice.Id));
foreach (var marked in new[] { InvoiceStatus.Settled, InvoiceStatus.Invalid })
{
var inv = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 100 });
new CreateInvoiceRequest { Currency = "USD", Amount = 100 });
await user.PayInvoice(inv.Id);
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest()
await client.MarkInvoiceStatus(user.StoreId, inv.Id, new MarkInvoiceStatusRequest
{
Status = marked
});
@ -2318,13 +2355,12 @@ namespace BTCPayServer.Tests
}
}
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest()
new CreateInvoiceRequest
{
Currency = "USD",
Amount = 1,
Checkout = new CreateInvoiceRequest.CheckoutOptions()
Checkout = new CreateInvoiceRequest.CheckoutOptions
{
DefaultLanguage = "it-it ",
RedirectURL = "http://toto.com/lol"
@ -2501,7 +2537,6 @@ namespace BTCPayServer.Tests
Expiry = TimeSpan.FromSeconds(400),
PrivateRouteHints = false
});
var chargeInvoice = invoiceData;
Assert.NotNull(await client.GetLightningInvoice("BTC", invoiceData.Id));
// check list for internal node
@ -3643,7 +3678,7 @@ namespace BTCPayServer.Tests
SavePrivateKeys = true
});
var preApprovedPayoutWithoutPullPayment = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 0.0001m,
Approved = true,
@ -3781,9 +3816,19 @@ namespace BTCPayServer.Tests
{
case "before-automated-payout-processing":
beforeHookTcs.TrySetResult();
var bd = (BeforePayoutActionData)tuple.args;
foreach (var p in bd.Payouts)
{
TestLogs.LogInformation("Before Processed: " + p.Id);
}
break;
case "after-automated-payout-processing":
afterHookTcs.TrySetResult();
var ad = (AfterPayoutActionData)tuple.args;
foreach (var p in ad.Payouts)
{
TestLogs.LogInformation("After Processed: " + p.Id);
}
break;
}
};
@ -3798,7 +3843,21 @@ namespace BTCPayServer.Tests
await beforeHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
await afterHookTcs.Task.WaitAsync(TimeSpan.FromSeconds(5));
payouts = await adminClient.GetStorePayouts(admin.StoreId);
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
try
{
Assert.Single(payouts.Where(data => data.State == PayoutState.InProgress && data.Id == payoutThatShouldBeProcessedStraightAway.Id));
}
catch (SingleException)
{
TestLogs.LogInformation("Debugging flaky test...");
TestLogs.LogInformation("payoutThatShouldBeProcessedStraightAway: " + payoutThatShouldBeProcessedStraightAway.Id);
foreach (var p in payouts)
{
TestLogs.LogInformation("Payout Id: " + p.Id);
TestLogs.LogInformation("Payout State: " + p.State);
}
throw;
}
beforeHookTcs = new TaskCompletionSource();
afterHookTcs = new TaskCompletionSource();
@ -3851,7 +3910,7 @@ namespace BTCPayServer.Tests
beforeHookTcs = new TaskCompletionSource();
afterHookTcs = new TaskCompletionSource();
var payoutThatShouldNotBeProcessedStraightAway3 = await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
await adminClient.CreatePayout(admin.StoreId, new CreatePayoutThroughStoreRequest()
{
Amount = 0.3m,
Approved = true,
@ -4271,7 +4330,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await admin.GrantAccessAsync(true);
var unauthClient = new BTCPayServerClient(tester.PayTester.ServerUri);
var authClientNoPermissions = await admin.CreateClient(Policies.CanViewInvoices);
await admin.CreateClient(Policies.CanViewInvoices);
var adminClient = await admin.CreateClient(Policies.Unrestricted);
var managerClient = await admin.CreateClient(Policies.CanManageCustodianAccounts);
var withdrawalClient = await admin.CreateClient(Policies.CanWithdrawFromCustodianAccounts);
@ -4471,7 +4530,7 @@ clientBasic.PreviewUpdateStoreRateConfiguration(user.StoreId, new StoreRateConfi
await AssertHttpError(404, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal(storeId, "WRONG-ACCOUNT-ID", simulateWithdrawalRequest));
// Test: SimulateWithdrawal, wrong store ID
// TODO it is wierd that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
// TODO it is weird that 403 is considered normal, but it is like this for all calls where the store is wrong... I'd have preferred a 404 error, because the store cannot be found.
await AssertHttpError(403, async () => await withdrawalClient.SimulateCustodianAccountWithdrawal("WRONG-STORE-ID", accountId, simulateWithdrawalRequest));
// Test: SimulateWithdrawal, correct payment method, wrong amount
@ -4502,7 +4561,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

View File

@ -29,7 +29,7 @@ namespace BTCPayServer.Tests
using var s = CreateSeleniumTester(newDb: true);
await s.StartAsync();
var u1 = s.RegisterNewUser(true);
s.RegisterNewUser(true);
var hot = s.CreateNewStore();
var seed = s.GenerateWallet(isHotWallet: true);
var cold = s.CreateNewStore();

View File

@ -68,7 +68,7 @@ namespace BTCPayServer.Tests
{
using var tester = CreateServerTester();
await tester.StartAsync();
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var repo = tester.PayTester.GetService<UTXOLocker>();
var outpoint = RandomOutpoint();
@ -189,10 +189,10 @@ namespace BTCPayServer.Tests
using var tester = CreateServerTester();
await tester.StartAsync();
var broadcaster = tester.PayTester.GetService<DelayedTransactionBroadcaster>();
var payjoinRepository = tester.PayTester.GetService<UTXOLocker>();
tester.PayTester.GetService<UTXOLocker>();
broadcaster.Disable();
var network = tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC");
var btcPayWallet = tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
tester.PayTester.GetService<BTCPayWalletProvider>().GetWallet(network);
var cashCow = tester.ExplorerNode;
cashCow.Generate(2); // get some money in case
@ -218,7 +218,7 @@ namespace BTCPayServer.Tests
receiverUser.GrantAccess(true);
receiverUser.RegisterDerivationScheme("BTC", receiverAddressType, true);
await receiverUser.ModifyOnchainPaymentSettings(p => p.PayJoinEnabled = true);
var receiverCoin = await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
await receiverUser.ReceiveUTXO(Money.Satoshis(810), network);
string errorCode = receiverAddressType == senderAddressType ? null : "unavailable|any UTXO available";
var invoice = receiverUser.BitPay.CreateInvoice(new Invoice() { Price = 50000, Currency = "SATS", FullNotifications = true });
@ -236,7 +236,7 @@ namespace BTCPayServer.Tests
txBuilder.SendEstimatedFees(new FeeRate(50m));
var psbt = txBuilder.BuildPSBT(false);
psbt = await senderUser.Sign(psbt);
var pj = await senderUser.SubmitPayjoin(invoice, psbt, errorCode, false);
await senderUser.SubmitPayjoin(invoice, psbt, errorCode, false);
}
}
}
@ -250,11 +250,11 @@ namespace BTCPayServer.Tests
s.RegisterNewUser(true);
var receiver = s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
var receiverWalletId = new WalletId(receiver.storeId, "BTC");
var sender = s.CreateNewStore();
var senderSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
var senderWalletId = new WalletId(sender.storeId, "BTC");
await s.Server.ExplorerNode.GenerateAsync(1);
@ -305,7 +305,7 @@ namespace BTCPayServer.Tests
var cryptoCode = "BTC";
var receiver = s.CreateNewStore();
s.EnableCheckout(CheckoutType.V1);
var receiverSeed = s.GenerateWallet(cryptoCode, "", true, true, format);
s.GenerateWallet(cryptoCode, "", true, true, format);
var receiverWalletId = new WalletId(receiver.storeId, cryptoCode);
//payjoin is enabled by default.
@ -320,7 +320,7 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
var sender = s.CreateNewStore();
var senderSeed = s.GenerateWallet(cryptoCode, "", true, true, format);
s.GenerateWallet(cryptoCode, "", true, true, format);
var senderWalletId = new WalletId(sender.storeId, cryptoCode);
await s.Server.ExplorerNode.GenerateAsync(1);
await s.FundStoreWallet(senderWalletId);
@ -374,7 +374,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear();
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2");
s.Driver.FindElement(By.Id("SignTransaction")).Click();
var txId = await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
return Task.CompletedTask;
@ -406,7 +406,6 @@ namespace BTCPayServer.Tests
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
var dto = invoice.EntityToDTO();
Assert.Equal(InvoiceStatusLegacy.Paid, invoice.Status);
});
s.GoToInvoices(receiver.storeId);
@ -875,7 +874,6 @@ retry:
new Invoice() { Price = 0.02m, Currency = "BTC", FullNotifications = true });
cashCow.SendToAddress(BitcoinAddress.Create(invoice.BitcoinAddress, cashCow.Network),
Money.Coins(0.06m));
var receiverWalletId = new WalletId(receiverUser.StoreId, "BTC");
//give the cow some cash
await cashCow.GenerateAsync(1);
@ -963,8 +961,6 @@ retry:
senderUser.GenerateWalletResponseV.MasterHDKey.Derive(signingKeySettings.GetRootedKeyPath()
.KeyPath);
var n = tester.ExplorerClient.Network.NBitcoinNetwork;
var Invoice1Coin1 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
.SetChange(senderChange)
.Send(parsedBip21.Address, parsedBip21.Amount)
@ -973,7 +969,7 @@ retry:
.SendEstimatedFees(new FeeRate(100m))
.BuildTransaction(true);
var Invoice1Coin2 = tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
tester.ExplorerClient.Network.NBitcoinNetwork.CreateTransactionBuilder()
.SetChange(senderChange)
.Send(parsedBip21.Address, parsedBip21.Amount)
.AddCoins(coin2.Coin)
@ -1133,8 +1129,7 @@ retry:
var invoice7Coin6Response1Tx = await senderUser.SubmitPayjoin(invoice7, invoice7Coin6Tx, btcPayNetwork);
var Invoice7Coin6Response1TxSigned = invoice7Coin6TxBuilder.SignTransaction(invoice7Coin6Response1Tx);
var contributedInputsInvoice7Coin6Response1TxSigned =
Invoice7Coin6Response1TxSigned.Inputs.Single(txin => coin6.OutPoint != txin.PrevOut);
Invoice7Coin6Response1TxSigned.Inputs.Single(txin => coin6.OutPoint != txin.PrevOut);
////var receiverWalletPayJoinState = payJoinStateProvider.Get(receiverWalletId);

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