Compare commits
260 Commits
v1.0.6.6
...
pj/no-invo
Author | SHA1 | Date | |
---|---|---|---|
c1fe473cc7 | |||
d3923da8e4 | |||
eac4d54466 | |||
717836e9e0 | |||
8d1ec4b5c0 | |||
1ee5c82b8b | |||
c775c3cc78 | |||
6473da7114 | |||
bb78ae59d4 | |||
459f3c4a93 | |||
e014b30915 | |||
cc5a388106 | |||
5de93f8cc4 | |||
ad1b708da5 | |||
c7ff36b314 | |||
f367480857 | |||
475809b1a0 | |||
fce7fdb3b7 | |||
b2c72f1d75 | |||
f0ac7d2c16 | |||
7099a3b394 | |||
3e985d8554 | |||
f94e8c9719 | |||
bbe1442c28 | |||
3a248d7707 | |||
0bb1f16d2a | |||
0837756152 | |||
23236c96cb | |||
6ead5c3800 | |||
64db865e1e | |||
1b2399745d | |||
94acf60100 | |||
a4298e8c19 | |||
76985838c4 | |||
d24964e900 | |||
f4fde8f5f7 | |||
3461dd6464 | |||
560671b57f | |||
abf3962d91 | |||
f53a85fcd1 | |||
73730355b8 | |||
8827721605 | |||
2497413c60 | |||
a6537ef282 | |||
c92adc36c6 | |||
a64f71f186 | |||
0c766a2714 | |||
6b7c49431a | |||
4b37121b75 | |||
120c7b9730 | |||
1aa233ca47 | |||
14e6e492dd | |||
17bdf55bcc | |||
1ae6508a43 | |||
b7b6cef880 | |||
52b5c56da9 | |||
85ba9e96a0 | |||
b4e15cb27f | |||
068cfe5e3e | |||
c0449a633d | |||
7dfce5e306 | |||
fd53f7476e | |||
ceb541ad8a | |||
d92ced6c6b | |||
0847088391 | |||
a128685b83 | |||
749d26fafa | |||
485faf0141 | |||
af9d896510 | |||
1fc114fec7 | |||
e6938cef6f | |||
ffafd291ee | |||
f532759543 | |||
db3ba6db3c | |||
0a333f8476 | |||
738aaeed12 | |||
ce6c9c91fc | |||
7035b71ccd | |||
923a567822 | |||
9b24e9378f | |||
779f21a1ca | |||
73d70aa5e5 | |||
fc78eacf8f | |||
006af636e6 | |||
4575fda10a | |||
6c960628c2 | |||
00aa7deaae | |||
f722956864 | |||
8f520bff12 | |||
8398534fa0 | |||
e1fed90b71 | |||
5ba6e53379 | |||
d33bdfd50c | |||
4f5392eb74 | |||
064087a7c0 | |||
e13821ba49 | |||
c2b85779c3 | |||
cdfdad3e3d | |||
1f7992e5da | |||
4bad7d7c52 | |||
1fcf39d4ab | |||
a64e304d16 | |||
30c7cbba96 | |||
a7d324901d | |||
15c87dc0b6 | |||
0d2de4c421 | |||
da95bd6127 | |||
e31b5529b0 | |||
0836df6974 | |||
de4cd55adf | |||
6b156f2144 | |||
2b1efd9347 | |||
97dd10edc0 | |||
cc4e46d212 | |||
dc0671942d | |||
f79c8ab641 | |||
314fda7877 | |||
aaf77515fc | |||
b5f7b1aad4 | |||
d36974a47e | |||
5bd16f990c | |||
28d7924077 | |||
89ecba961c | |||
3481a5fd19 | |||
70a21c5136 | |||
2e2c9764f3 | |||
bd447b6c79 | |||
4f6ec3aa32 | |||
e37b3179ba | |||
808214f973 | |||
7e714f1ef8 | |||
e65e46f664 | |||
5e7eb6635f | |||
49ae62b02e | |||
c9cfe5cc6e | |||
e8df010449 | |||
949136b161 | |||
1e00c63146 | |||
6d9b93a407 | |||
37c39ad587 | |||
e3e65878aa | |||
6843b0eaab | |||
e25f76753a | |||
75c2fabd7f | |||
35aeb19fcd | |||
5a00f6a4fc | |||
3a9fc52b8c | |||
64a8de938b | |||
605cf407a8 | |||
9ed5297e91 | |||
07da404a23 | |||
21467ef65d | |||
4d5b2c4033 | |||
5e7836b293 | |||
a6fe61d508 | |||
c0aa320f0a | |||
4bcc18fb41 | |||
32370545cb | |||
2fd8c831c0 | |||
5b4877c402 | |||
71c11b34f4 | |||
3123718166 | |||
dfbec71906 | |||
757c087afd | |||
b30aa968b0 | |||
1e902c8dee | |||
db5f64432e | |||
6c9c463da9 | |||
c73878ccca | |||
41e453306d | |||
626c6007fd | |||
deb88032cb | |||
eca317b3c4 | |||
9300326483 | |||
ebc46eb7d2 | |||
cb83669802 | |||
f6f616a21d | |||
62d50c0189 | |||
dd5b143c13 | |||
2e864c32fa | |||
f4fa7c927c | |||
3736cbc107 | |||
776825cc66 | |||
5cb647e57f | |||
9c5f826bb4 | |||
6b1803629d | |||
5cea0571e3 | |||
95c8afd5ba | |||
ecc9a34359 | |||
1fed7fb5af | |||
93d1ded4c2 | |||
f199775437 | |||
40271f420d | |||
9e71c02eb9 | |||
c9cbfe630d | |||
bf9331b147 | |||
f223d2e00c | |||
5246e7f035 | |||
42de0803c9 | |||
242f9c6197 | |||
5288198474 | |||
b21353c4f9 | |||
65a0c6a4b3 | |||
af5bd89f34 | |||
73aeffd13c | |||
03d2f6c017 | |||
7e1481c43f | |||
8a1069bf70 | |||
6097ab5d12 | |||
d951575f80 | |||
d887546e58 | |||
ec3b80cec9 | |||
2ef442cf83 | |||
6e5a4a7546 | |||
e0d46002cb | |||
739f13b7a3 | |||
34af74b288 | |||
4ee0cc8145 | |||
4984674b1d | |||
f5ee67aafb | |||
98a1b0da71 | |||
074ff76d49 | |||
b75409a6bf | |||
94abda6e3e | |||
68419a9510 | |||
db0854f203 | |||
994301ea4c | |||
5e344b9be7 | |||
66d3da8ac9 | |||
71bb876c9e | |||
1e029f3290 | |||
7926b689fd | |||
4638f781f9 | |||
8bea1505dd | |||
e567f7a80c | |||
3774e8dc51 | |||
dc27ffa6ba | |||
ca25eedfbc | |||
1627c05224 | |||
f65ca04507 | |||
a662b6ef6a | |||
bd6d38b3b0 | |||
a890d5300b | |||
eb8411611a | |||
9360ddf294 | |||
e3c138fa98 | |||
7147dadb2a | |||
8d03738a50 | |||
e5540ee79f | |||
2ccbb6d6a7 | |||
3dce7e7e9f | |||
79e8ce9226 | |||
89857ca77c | |||
5ce60be78a | |||
600cc20423 | |||
987d09041d | |||
df52d01a1d | |||
07de4af581 | |||
9acb8c2ba2 | |||
307f7b6bd7 |
BTCPayServer.Abstractions/Contracts
BTCPayServer.Client
BTCPayServer.Client.csprojBTCPayServerClient.Invoices.csBTCPayServerClient.OnChainWallet.cs
Models
CreateInvoiceRequest.csCreateLightningInvoiceRequest.csCreateOnChainTransactionRequest.csInvoicePaymentMethodDataModel.csLabelData.csOnChainWalletAddressData.csOnChainWalletFeeRateData.csOnChainWalletOverviewData.csOnChainWalletTransactionData.csOnChainWalletUTXOData.csStoreBaseData.csTransactionStatus.cs
BTCPayServer.Common
Altcoins
BTCPayNetworkProvider.Argoneum.csBTCPayNetworkProvider.BGold.csBTCPayNetworkProvider.BPlus.csBTCPayNetworkProvider.Bitcore.csBTCPayNetworkProvider.Chaincoin.csBTCPayNetworkProvider.Dash.csBTCPayNetworkProvider.Dogecoin.csBTCPayNetworkProvider.Feathercoin.csBTCPayNetworkProvider.Groestlcoin.csBTCPayNetworkProvider.Litecoin.csBTCPayNetworkProvider.Monacoin.csBTCPayNetworkProvider.MonetaryUnit.csBTCPayNetworkProvider.Polis.csBTCPayNetworkProvider.Ufo.csBTCPayNetworkProvider.Viacoin.cs
BTCPayNetwork.csBTCPayNetworkProvider.Bitcoin.csBTCPayNetworkProvider.csBTCPayServer.Common.csprojEthereum
Liquid
Monero
BTCPayServer.Data/Migrations
20200625064111_refundnotificationpullpayments.cs20201108054749_webhooks.cs20201208054211_invoicesorderindex.cs20201228225040_AddingInvoiceSearchesTable.cs
BTCPayServer.Rating
BTCPayServer.Tests
AltcoinTests
ApiKeysTests.csBTCPayServer.Tests.csprojBTCPayServerTester.csCheckoutUITests.csExtensions.csGreenfieldAPITests.csPayJoinTests.csREADME.mdSeleniumTester.csSeleniumTests.csServerTester.csStorageTests.csTestAccount.csUnitTest1.csUtils.csdocker-compose.altcoins.ymldocker-compose.ymlBTCPayServer
BTCPayServer.csprojZoneLimits.csbundleconfig.json
Components/NotificationsDropdown
Configuration
Controllers
AccountController.csAppsController.Crowdfund.csAppsController.PointOfSale.csAppsPublicController.csErrorController.cs
GreenField
InvoiceController.csLightningNodeApiController.Internal.csLightningNodeApiController.Store.csLightningNodeApiController.csStoreLightningNetworkPaymentMethodsController.csStoreOnChainWalletsController.csStoresController.cs
HomeController.csInvoiceController.UI.csInvoiceController.csManageController.csNotificationsController.csPullPaymentController.csServerController.Plugins.csServerController.Storage.csServerController.Users.csStoresController.BTCLike.csStoresController.Integrations.csStoresController.LightningLike.csStoresController.Onchain.csStoresController.csWalletsController.PSBT.csWalletsController.csData
DerivationSchemeSettings.csExtensions.csExtensions
AuthorizationExtensions.csEmailSenderExtensions.csMoneyExtensions.csPSBTExtensions.csStoreExtensions.cs
Filters
HostedServices
InvoiceNotificationManager.csNBXplorerWaiter.csWalletReceiveCacheUpdater.csWebhookNotificationManager.cs
Hosting
Models
AppViewModels
InvoicingModels
ManageViewModels
StoreViewModels
CheckoutExperienceViewModel.csDerivationSchemeViewModel.csIntegrationsViewModel.csLightningNodeViewModel.csWalletSetupViewModel.cs
ViewPullPaymentModel.csPayments
Bitcoin
IPaymentMethodDetails.csLightning
LightningLikePaymentHandler.csLightningLikePaymentMethodDetails.csLightningListener.csLightningSupportedPaymentMethod.cs
PayJoin
PaymentTypes.Bitcoin.csPaymentTypes.Lightning.csPaymentTypes.csPlugins
PluginManager.csPluginService.cs
Program.csShopify
ApiModels
CreateScriptResponse.csCreateWebhookResponse.cs
DataHolders
OrdersCountResp.csShopifyOrder.csShopifyTransaction.csTransactionsCreateReq.csTransactionsCreateResp.csTransactionsListResp.csModels
OrderTransactionRegisterLogic.csShopifyApiClient.csShopifyApiException.csShopifyController.csShopifyExtensions.csShopifyOrderMarkerHostedService.csShopifyPlugin.csSecurity/Bitpay
Services
Altcoins
Ethereum
Payments
Services
Monero
Apps
BTCPayServerEnvironment.csInvoices
Labels
MigrationSettings.csPayjoinClient.csPoliciesSettings.csSettingsRepository.csShopify
Stores
WalletRepository.csWallets
Storage
Views
Account
Apps
AppsNavPages.csCreateApp.cshtmlTemplateEditor.cshtmlUpdateCrowdfund.cshtmlUpdatePointOfSale.cshtml_ViewStart.cshtml
AppsPublic
Error
Home
Invoice
Checkout-Body.cshtmlCreateInvoice.cshtmlInvoice.cshtmlInvoiceNavPages.csListInvoices.cshtml_ViewStart.cshtml
Manage
Notifications
PaymentRequest
PullPayment
Server
Shared
Bitcoin
CameraScanner.cshtmlConfirm.cshtmlHeader.cshtmlShopify
VaultElements.cshtml_BTCPaySupporters.cshtml_Layout.cshtml_LayoutSimple.cshtmlShopify
Stores
AddDerivationScheme.cshtmlAddDerivationSchemes_HardwareWalletDialogs.cshtmlAddDerivationSchemes_NBXWalletGenerate.cshtmlAddLightningNode.cshtmlCheckoutExperience.cshtmlGenerateWallet.cshtmlGenerateWalletOptions.cshtml
ImportWallet
ImportWalletOptions.cshtmlIntegrations.cshtmlModifyWallet.cshtmlSetupWallet.cshtmlUpdateStore.cshtml_GenerateWalletForm.cshtml_LayoutWalletSetup.cshtml_ViewStart.cshtmlUserStores
ViewsRazor.csWallets
wwwroot
_bootstrap_kitchensink.html
cart/js
checkout/css
img
js
main
modal
paybutton
swagger/v1
swagger.template.invoices.jsonswagger.template.jsonswagger.template.lightning.common.jsonswagger.template.notifications.jsonswagger.template.payment-requests.jsonswagger.template.pull-payments.jsonswagger.template.stores-payment-methods.lightning-network.jsonswagger.template.stores-payment-methods.on-chain.jsonswagger.template.stores-wallet.on-chain.jsonswagger.template.stores.jsonswagger.template.users.jsonswagger.template.webhooks.json
vendor/jquery
Build
Changelog.mdREADME.mdbtcpayserver.slnpublish-docker.ps1@ -1,3 +1,4 @@
|
||||
#nullable enable
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@ -5,8 +6,8 @@ namespace BTCPayServer.Abstractions.Contracts
|
||||
{
|
||||
public interface ISettingsRepository
|
||||
{
|
||||
Task<T> GetSettingAsync<T>(string name = null);
|
||||
Task UpdateSetting<T>(T obj, string name = null);
|
||||
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default);
|
||||
Task<T?> GetSettingAsync<T>(string? name = null) where T : class;
|
||||
Task UpdateSetting<T>(T obj, string? name = null) where T : class;
|
||||
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default) where T : class;
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@
|
||||
<RepositoryType>git</RepositoryType>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<Version Condition=" '$(Version)' == '' ">1.2.0</Version>
|
||||
<Version Condition=" '$(Version)' == '' ">1.3.0</Version>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
|
||||
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||
@ -27,7 +27,7 @@
|
||||
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NBitcoin" Version="5.0.68" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.73" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
</ItemGroup>
|
||||
|
@ -85,5 +85,13 @@ namespace BTCPayServer.Client
|
||||
method: HttpMethod.Post), token);
|
||||
return await HandleResponse<InvoiceData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod, CancellationToken token = default)
|
||||
{
|
||||
var response = await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate",
|
||||
method: HttpMethod.Post), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
122
BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs
Normal file
122
BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs
Normal file
@ -0,0 +1,122 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client.Models;
|
||||
using NBitcoin;
|
||||
|
||||
namespace BTCPayServer.Client
|
||||
{
|
||||
public partial class BTCPayServerClient
|
||||
{
|
||||
public virtual async Task<OnChainWalletOverviewData> ShowOnChainWalletOverview(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet"), token);
|
||||
return await HandleResponse<OnChainWalletOverviewData>(response);
|
||||
}
|
||||
public virtual async Task<OnChainWalletFeeRateData> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
Dictionary<string, object> queryParams = new Dictionary<string, object>();
|
||||
if (blockTarget != null)
|
||||
{
|
||||
queryParams.Add("blockTarget",blockTarget);
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/feeRate", queryParams), token);
|
||||
return await HandleResponse<OnChainWalletFeeRateData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletAddressData> GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address", new Dictionary<string, object>()
|
||||
{
|
||||
{"forceGenerate", forceGenerate}
|
||||
}), token);
|
||||
return await HandleResponse<OnChainWalletAddressData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/address",method:HttpMethod.Delete), token);
|
||||
await HandleResponse(response);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<OnChainWalletTransactionData>> ShowOnChainWalletTransactions(
|
||||
string storeId, string cryptoCode, TransactionStatus[] statusFilter = null,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var query = new Dictionary<string, object>();
|
||||
if (statusFilter?.Any() is true)
|
||||
{
|
||||
query.Add(nameof(statusFilter), statusFilter);
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", query), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletTransactionData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> GetOnChainWalletTransaction(
|
||||
string storeId, string cryptoCode, string transactionId,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions/{transactionId}"), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<IEnumerable<OnChainWalletUTXOData>> GetOnChainWalletUTXOs(string storeId,
|
||||
string cryptoCode,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/utxos"), token);
|
||||
return await HandleResponse<IEnumerable<OnChainWalletUTXOData>>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<OnChainWalletTransactionData> CreateOnChainTransaction(string storeId,
|
||||
string cryptoCode, CreateOnChainTransactionRequest request,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (!request.ProceedWithBroadcast)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
|
||||
"Please use CreateOnChainTransactionButDoNotBroadcast when wanting to only create the transaction");
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return await HandleResponse<OnChainWalletTransactionData>(response);
|
||||
}
|
||||
|
||||
public virtual async Task<Transaction> CreateOnChainTransactionButDoNotBroadcast(string storeId,
|
||||
string cryptoCode, CreateOnChainTransactionRequest request, Network network,
|
||||
CancellationToken token = default)
|
||||
{
|
||||
if (request.ProceedWithBroadcast)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast),
|
||||
"Please use CreateOnChainTransaction when wanting to also broadcast the transaction");
|
||||
}
|
||||
var response =
|
||||
await _httpClient.SendAsync(
|
||||
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/Onchain/{cryptoCode}/wallet/transactions", null, request, HttpMethod.Post), token);
|
||||
return Transaction.Parse(await HandleResponse<string>(response), network);
|
||||
}
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateInvoiceRequest
|
||||
{
|
||||
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
public string Currency { get; set; }
|
||||
public JObject Metadata { get; set; }
|
||||
@ -33,6 +33,8 @@ namespace BTCPayServer.Client.Models
|
||||
public double? PaymentTolerance { get; set; }
|
||||
[JsonProperty("redirectURL")]
|
||||
public string RedirectURL { get; set; }
|
||||
|
||||
public bool? RedirectAutomatically { get; set; }
|
||||
public string DefaultLanguage { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ namespace BTCPayServer.Client.Models
|
||||
Description = description;
|
||||
Expiry = expiry;
|
||||
}
|
||||
[JsonConverter(typeof(LightMoneyJsonConverter))]
|
||||
[JsonConverter(typeof(BTCPayServer.Client.JsonConverters.LightMoneyJsonConverter))]
|
||||
public LightMoney Amount { get; set; }
|
||||
public string Description { get; set; }
|
||||
[JsonConverter(typeof(JsonConverters.TimeSpanJsonConverter.Seconds))]
|
||||
|
@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class CreateOnChainTransactionRequest
|
||||
{
|
||||
|
||||
public class CreateOnChainTransactionRequestDestination
|
||||
{
|
||||
public string Destination { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal? Amount { get; set; }
|
||||
public bool SubtractFromAmount { get; set; }
|
||||
}
|
||||
[JsonConverter(typeof(FeeRateJsonConverter))]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
public bool ProceedWithPayjoin { get; set; }= true;
|
||||
public bool ProceedWithBroadcast { get; set; } = true;
|
||||
public bool NoChange { get; set; } = false;
|
||||
[JsonProperty(ItemConverterType = typeof(OutpointJsonConverter))]
|
||||
public List<OutPoint> SelectedInputs { get; set; } = null;
|
||||
public List<CreateOnChainTransactionRequestDestination> Destinations { get; set; }
|
||||
[JsonProperty("rbf")]
|
||||
public bool? RBF { get; set; } = null;
|
||||
}
|
||||
}
|
@ -8,6 +8,7 @@ namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class InvoicePaymentMethodDataModel
|
||||
{
|
||||
public bool Activated { get; set; }
|
||||
public string Destination { get; set; }
|
||||
public string PaymentLink { get; set; }
|
||||
|
||||
|
14
BTCPayServer.Client/Models/LabelData.cs
Normal file
14
BTCPayServer.Client/Models/LabelData.cs
Normal file
@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class LabelData
|
||||
{
|
||||
public string Type { get; set; }
|
||||
public string Text { get; set; }
|
||||
|
||||
[JsonExtensionData] public Dictionary<string, JToken> AdditionalData { get; set; }
|
||||
}
|
||||
}
|
15
BTCPayServer.Client/Models/OnChainWalletAddressData.cs
Normal file
15
BTCPayServer.Client/Models/OnChainWalletAddressData.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletAddressData
|
||||
{
|
||||
public string Address { get; set; }
|
||||
[JsonConverter(typeof(KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
|
||||
public string PaymentLink { get; set; }
|
||||
}
|
||||
}
|
12
BTCPayServer.Client/Models/OnChainWalletFeeRateData.cs
Normal file
12
BTCPayServer.Client/Models/OnChainWalletFeeRateData.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletFeeRateData
|
||||
{
|
||||
[JsonConverter(typeof(FeeRateJsonConverter))]
|
||||
public FeeRate FeeRate { get; set; }
|
||||
}
|
||||
}
|
17
BTCPayServer.Client/Models/OnChainWalletOverviewData.cs
Normal file
17
BTCPayServer.Client/Models/OnChainWalletOverviewData.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using BTCPayServer.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletOverviewData
|
||||
{
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Balance { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal UnconfirmedBalance { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal ConfirmedBalance { get; set; }
|
||||
|
||||
public string Label { get; set; }
|
||||
}
|
||||
}
|
34
BTCPayServer.Client/Models/OnChainWalletTransactionData.cs
Normal file
34
BTCPayServer.Client/Models/OnChainWalletTransactionData.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletTransactionData
|
||||
{
|
||||
[JsonConverter(typeof(UInt256JsonConverter))]
|
||||
public uint256 TransactionHash { get; set; }
|
||||
|
||||
public string Comment { get; set; }
|
||||
public Dictionary<string, LabelData> Labels { get; set; } = new Dictionary<string, LabelData>();
|
||||
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonConverter(typeof(UInt256JsonConverter))]
|
||||
public uint256 BlockHash { get; set; }
|
||||
|
||||
public int? BlockHeight { get; set; }
|
||||
|
||||
public int Confirmations { get; set; }
|
||||
|
||||
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public TransactionStatus Status { get; set; }
|
||||
}
|
||||
}
|
25
BTCPayServer.Client/Models/OnChainWalletUTXOData.cs
Normal file
25
BTCPayServer.Client/Models/OnChainWalletUTXOData.cs
Normal file
@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BTCPayServer.JsonConverters;
|
||||
using NBitcoin;
|
||||
using NBitcoin.JsonConverters;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public class OnChainWalletUTXOData
|
||||
{
|
||||
public string Comment { get; set; }
|
||||
[JsonConverter(typeof(NumericStringJsonConverter))]
|
||||
public decimal Amount { get; set; }
|
||||
[JsonConverter(typeof(OutpointJsonConverter))]
|
||||
public OutPoint Outpoint { get; set; }
|
||||
public string Link { get; set; }
|
||||
public Dictionary<string, LabelData> Labels { get; set; }
|
||||
[JsonConverter(typeof(DateTimeToUnixTimeConverter))]
|
||||
public DateTimeOffset Timestamp { get; set; }
|
||||
[JsonConverter(typeof(KeyPathJsonConverter))]
|
||||
public KeyPath KeyPath { get; set; }
|
||||
public string Address { get; set; }
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ namespace BTCPayServer.Client.Models
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
public bool LazyPaymentMethods { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
@ -53,8 +54,6 @@ namespace BTCPayServer.Client.Models
|
||||
|
||||
public string HtmlTitle { get; set; }
|
||||
|
||||
|
||||
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;
|
||||
|
9
BTCPayServer.Client/Models/TransactionStatus.cs
Normal file
9
BTCPayServer.Client/Models/TransactionStatus.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace BTCPayServer.Client.Models
|
||||
{
|
||||
public enum TransactionStatus
|
||||
{
|
||||
Unconfirmed,
|
||||
Confirmed,
|
||||
Replaced
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Argoneum",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://chainz.cryptoid.info/agm/tx.dws?{0}"
|
||||
: "https://chainz.cryptoid.info/agm-test/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
@ -23,7 +23,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/argoneum.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("421'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("421'")
|
||||
: new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "BGold",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoingold",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/btg.svg",
|
||||
LightningImagePath = "imlegacy/btg-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "BPlus",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bplus-fix-it",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/xbc.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcore",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcore",
|
||||
DefaultRateRules = new[]
|
||||
@ -23,7 +23,7 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/bitcore.svg",
|
||||
LightningImagePath = "imlegacy/bitcore-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Chaincoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://explorer.chaincoin.org/Explorer/Transaction/{0}"
|
||||
: "https://test.explorer.chaincoin.org/Explorer/Transaction/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
@ -24,7 +24,7 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/chaincoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("711'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("711'")
|
||||
: new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Dash",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://insight.dash.org/insight/tx/{0}"
|
||||
: "https://testnet-insight.dashevo.org/insight/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
@ -20,12 +20,12 @@ namespace BTCPayServer
|
||||
DefaultRateRules = new[]
|
||||
{
|
||||
"DASH_X = DASH_BTC * BTC_X",
|
||||
"DASH_BTC = bittrex(DASH_BTC)"
|
||||
"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 == NetworkType.Mainnet ? new KeyPath("5'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("5'")
|
||||
: new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Dogecoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "dogecoin",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/dogecoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Feathercoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "feathercoin",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/feathercoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Groestlcoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
|
||||
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
@ -24,9 +24,10 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/groestlcoin.png",
|
||||
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportPayJoin = true
|
||||
SupportPayJoin = true,
|
||||
VaultSupported = true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Litecoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet
|
||||
? "https://live.blockcypher.com/ltc/tx/{0}/"
|
||||
: "http://explorer.litecointools.com/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
@ -26,9 +26,9 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/litecoin.svg",
|
||||
LightningImagePath = "imlegacy/litecoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
|
||||
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
ElectrumMapping = NetworkType == ChainName.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x0488b21eU, DerivationType.Legacy },
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Monacoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monacoin",
|
||||
DefaultRateRules = new[]
|
||||
@ -23,7 +23,7 @@ namespace BTCPayServer
|
||||
CryptoImagePath = "imlegacy/monacoin.png",
|
||||
LightningImagePath = "imlegacy/mona-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "MonetaryUnit",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "monetaryunit",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/monetaryunit.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Polis",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "polis",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/polis.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Ufo",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "ufo",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/ufo.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Viacoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "viacoin",
|
||||
DefaultRateRules = new[]
|
||||
@ -22,7 +22,7 @@ namespace BTCPayServer
|
||||
},
|
||||
CryptoImagePath = "imlegacy/viacoin.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -13,20 +13,20 @@ namespace BTCPayServer
|
||||
DisplayName = "Ethereum",
|
||||
DefaultRateRules = new[] {"ETH_X = ETH_BTC * BTC_X", "ETH_BTC = kraken(ETH_BTC)"},
|
||||
BlockExplorerLink =
|
||||
NetworkType == NetworkType.Mainnet
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://etherscan.io/address/{0}"
|
||||
: "https://ropsten.etherscan.io/address/{0}",
|
||||
CryptoImagePath = "/imlegacy/eth.png",
|
||||
ShowSyncSummary = true,
|
||||
CoinType = NetworkType == NetworkType.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == NetworkType.Mainnet ? 1 : 3,
|
||||
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
|
||||
Divisibility = 18,
|
||||
});
|
||||
}
|
||||
|
||||
public void InitERC20()
|
||||
{
|
||||
if (NetworkType != NetworkType.Mainnet)
|
||||
if (NetworkType != ChainName.Mainnet)
|
||||
{
|
||||
Add(new ERC20BTCPayNetwork()
|
||||
{
|
||||
@ -60,13 +60,13 @@ namespace BTCPayServer
|
||||
"USDT20_BTC = bitfinex(UST_BTC)",
|
||||
},
|
||||
BlockExplorerLink =
|
||||
NetworkType == NetworkType.Mainnet
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://etherscan.io/address/{0}#tokentxns"
|
||||
: "https://ropsten.etherscan.io/address/{0}#tokentxns",
|
||||
CryptoImagePath = "/imlegacy/liquid-tether.svg",
|
||||
ShowSyncSummary = false,
|
||||
CoinType = NetworkType == NetworkType.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == NetworkType.Mainnet ? 1 : 3,
|
||||
CoinType = NetworkType == ChainName.Mainnet? 60 : 1,
|
||||
ChainId = NetworkType == ChainName.Mainnet ? 1 : 3,
|
||||
SmartContractAddress = "0xdAC17F958D2ee523a2206206994597C13D831ec7",
|
||||
Divisibility = 6
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ namespace BTCPayServer
|
||||
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
|
||||
Add(new ElementsBTCPayNetwork()
|
||||
{
|
||||
AssetId = NetworkType == NetworkType.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
|
||||
AssetId = NetworkType == ChainName.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
|
||||
CryptoCode = "LBTC",
|
||||
NetworkCryptoCode = "LBTC",
|
||||
DisplayName = "Liquid Bitcoin",
|
||||
@ -22,12 +22,12 @@ namespace BTCPayServer
|
||||
"LBTC_X = LBTC_BTC * BTC_X",
|
||||
"LBTC_BTC = 1",
|
||||
},
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/liquid.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true
|
||||
});
|
||||
}
|
||||
|
@ -21,12 +21,12 @@ namespace BTCPayServer
|
||||
},
|
||||
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
|
||||
DisplayName = "Liquid Tether",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/liquid-tether.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
@ -45,12 +45,12 @@ namespace BTCPayServer
|
||||
Divisibility = 2,
|
||||
AssetId = new uint256("aa775044c32a7df391902b3659f46dfe004ccb2644ce2ddc7dba31e889391caf"),
|
||||
DisplayName = "Ethiopian Birr",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/etb.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
@ -68,12 +68,12 @@ namespace BTCPayServer
|
||||
},
|
||||
AssetId = new uint256("0e99c1a6da379d1f4151fb9df90449d40d0608f6cb33a5bcbfc8c265f42bab0a"),
|
||||
DisplayName = "Liquid CAD",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "liquidnetwork",
|
||||
CryptoImagePath = "imlegacy/lcad.png",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportLightning = false
|
||||
});
|
||||
|
@ -24,38 +24,22 @@ namespace BTCPayServer
|
||||
});
|
||||
}
|
||||
|
||||
public override GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
|
||||
public override List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
|
||||
{
|
||||
TransactionInformationSet Filter(TransactionInformationSet transactionInformationSet)
|
||||
{
|
||||
return new TransactionInformationSet()
|
||||
{
|
||||
Transactions =
|
||||
transactionInformationSet.Transactions.FindAll(information =>
|
||||
information.Outputs.Any(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId) ||
|
||||
information.Inputs.Any(output =>
|
||||
output.Value is AssetMoney assetMoney && assetMoney.AssetId == AssetId))
|
||||
};
|
||||
}
|
||||
|
||||
return new GetTransactionsResponse()
|
||||
{
|
||||
Height = response.Height,
|
||||
ConfirmedTransactions = Filter(response.ConfirmedTransactions),
|
||||
ReplacedTransactions = Filter(response.ReplacedTransactions),
|
||||
UnconfirmedTransactions = Filter(response.UnconfirmedTransactions)
|
||||
};
|
||||
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 string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
//precision 0: 10 = 0.00000010
|
||||
//precision 2: 10 = 0.00001000
|
||||
//precision 8: 10 = 10
|
||||
var money = new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
|
||||
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}&assetid={AssetId}";
|
||||
var money = cryptoInfoDue is null? null: new Money(cryptoInfoDue.ToDecimal(MoneyUnit.BTC) / decimal.Parse("1".PadRight(1 + 8 - Divisibility, '0')), MoneyUnit.BTC);
|
||||
return $"{base.GenerateBIP21(cryptoInfoAddress, money)}{(money is null? "?": "&")}assetid={AssetId}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer
|
||||
DisplayName = "Monero",
|
||||
Divisibility = 12,
|
||||
BlockExplorerLink =
|
||||
NetworkType == NetworkType.Mainnet
|
||||
NetworkType == ChainName.Mainnet
|
||||
? "https://www.exploremonero.com/transaction/{0}"
|
||||
: "https://testnet.xmrchain.net/tx/{0}",
|
||||
DefaultRateRules = new[]
|
||||
|
@ -18,25 +18,29 @@ namespace BTCPayServer
|
||||
{
|
||||
static BTCPayDefaultSettings()
|
||||
{
|
||||
_Settings = new Dictionary<NetworkType, BTCPayDefaultSettings>();
|
||||
foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
|
||||
_Settings = new Dictionary<ChainName, BTCPayDefaultSettings>();
|
||||
}
|
||||
|
||||
static readonly Dictionary<ChainName, BTCPayDefaultSettings> _Settings;
|
||||
|
||||
public static BTCPayDefaultSettings GetDefaultSettings(ChainName chainType)
|
||||
{
|
||||
if (_Settings.TryGetValue(chainType, out var v))
|
||||
return v;
|
||||
lock (_Settings)
|
||||
{
|
||||
if (_Settings.TryGetValue(chainType, out v))
|
||||
return v;
|
||||
var settings = new BTCPayDefaultSettings();
|
||||
_Settings.Add(chainType, settings);
|
||||
settings.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", NBXplorerDefaultSettings.GetFolderName(chainType));
|
||||
settings.DefaultPluginDirectory =
|
||||
StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", "Plugins");
|
||||
settings.DefaultConfigurationFile = Path.Combine(settings.DefaultDataDirectory, "settings.config");
|
||||
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
|
||||
chainType == NetworkType.Regtest ? 23002 :
|
||||
chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString()));
|
||||
settings.DefaultPort = (chainType == ChainName.Mainnet ? 23000 :
|
||||
chainType == ChainName.Regtest ? 23002
|
||||
: 23001);
|
||||
}
|
||||
}
|
||||
|
||||
static readonly Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
|
||||
|
||||
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
|
||||
{
|
||||
return _Settings[chainType];
|
||||
}
|
||||
|
||||
@ -50,7 +54,7 @@ namespace BTCPayServer
|
||||
{
|
||||
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
|
||||
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
|
||||
public bool SupportRBF { get; internal set; }
|
||||
public bool SupportRBF { get; set; }
|
||||
public string LightningImagePath { get; set; }
|
||||
public BTCPayDefaultSettings DefaultSettings { get; set; }
|
||||
public KeyPath CoinType { get; set; }
|
||||
@ -59,9 +63,9 @@ namespace BTCPayServer
|
||||
|
||||
public virtual bool WalletSupported { get; set; } = true;
|
||||
public virtual bool ReadonlyWallet { get; set; } = false;
|
||||
|
||||
public int MaxTrackedConfirmation { get; internal set; } = 6;
|
||||
public string UriScheme { get; internal set; }
|
||||
public virtual bool VaultSupported { get; set; } = false;
|
||||
public int MaxTrackedConfirmation { get; set; } = 6;
|
||||
public string UriScheme { get; set; }
|
||||
public bool SupportPayJoin { get; set; } = false;
|
||||
public bool SupportLightning { get; set; } = true;
|
||||
|
||||
@ -119,12 +123,12 @@ namespace BTCPayServer
|
||||
|
||||
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
|
||||
{
|
||||
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
|
||||
return $"{UriScheme}:{cryptoInfoAddress}{(cryptoInfoDue is null? string.Empty: $"?amount={cryptoInfoDue.ToString(false, true)}")}";
|
||||
}
|
||||
|
||||
public virtual GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
|
||||
public virtual List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
|
||||
{
|
||||
return response;
|
||||
return transactionInformationSet;
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +152,7 @@ namespace BTCPayServer
|
||||
}
|
||||
}
|
||||
|
||||
public string BlockExplorerLinkDefault { get; internal set; }
|
||||
public string BlockExplorerLinkDefault { get; set; }
|
||||
public string DisplayName { get; set; }
|
||||
public int Divisibility { get; set; } = 8;
|
||||
[Obsolete("Should not be needed")]
|
||||
|
@ -13,17 +13,20 @@ namespace BTCPayServer
|
||||
{
|
||||
CryptoCode = nbxplorerNetwork.CryptoCode,
|
||||
DisplayName = "Bitcoin",
|
||||
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/tx/{0}" : "https://blockstream.info/testnet/tx/{0}",
|
||||
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/tx/{0}" :
|
||||
NetworkType == Bitcoin.Instance.Signet.ChainName ? "https://explorer.bc-2.jp/tx/{0}"
|
||||
: "https://blockstream.info/testnet/tx/{0}",
|
||||
NBXplorerNetwork = nbxplorerNetwork,
|
||||
UriScheme = "bitcoin",
|
||||
CryptoImagePath = "imlegacy/bitcoin.svg",
|
||||
LightningImagePath = "imlegacy/bitcoin-lightning.svg",
|
||||
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
|
||||
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
|
||||
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
|
||||
SupportRBF = true,
|
||||
SupportPayJoin = true,
|
||||
VaultSupported = true,
|
||||
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
|
||||
ElectrumMapping = NetworkType == NetworkType.Mainnet
|
||||
ElectrumMapping = NetworkType == ChainName.Mainnet
|
||||
? new Dictionary<uint, DerivationType>()
|
||||
{
|
||||
{0x0488b21eU, DerivationType.Legacy }, // xpub
|
||||
|
@ -35,8 +35,8 @@ namespace BTCPayServer
|
||||
}
|
||||
|
||||
|
||||
public NetworkType NetworkType { get; private set; }
|
||||
public BTCPayNetworkProvider(NetworkType networkType)
|
||||
public ChainName NetworkType { get; private set; }
|
||||
public BTCPayNetworkProvider(ChainName networkType)
|
||||
{
|
||||
_NBXplorerNetworkProvider = new NBXplorerNetworkProvider(networkType);
|
||||
NetworkType = networkType;
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
<ItemGroup>
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.19" />
|
||||
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
|
||||
</ItemGroup>
|
||||
<ItemGroup Condition="'$(Altcoins)' != 'true'">
|
||||
<Compile Remove="Altcoins\**\*.cs"></Compile>
|
||||
|
@ -11,13 +11,16 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
int? maxLength = this.IsMySql(migrationBuilder.ActiveProvider) ? (int?)255 : null;
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RefundAddresses");
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "CurrentRefundId",
|
||||
table: "Invoices",
|
||||
nullable: true);
|
||||
nullable: true,
|
||||
maxLength: maxLength);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Notifications",
|
||||
@ -73,7 +76,7 @@ namespace BTCPayServer.Migrations
|
||||
PullPaymentDataId = table.Column<string>(maxLength: 30, nullable: true),
|
||||
State = table.Column<string>(maxLength: 20, nullable: false),
|
||||
PaymentMethodId = table.Column<string>(maxLength: 20, nullable: false),
|
||||
Destination = table.Column<string>(nullable: true),
|
||||
Destination = table.Column<string>(maxLength: maxLength, nullable: true),
|
||||
Blob = table.Column<byte[]>(nullable: true),
|
||||
Proof = table.Column<byte[]>(nullable: true)
|
||||
},
|
||||
@ -92,8 +95,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "Refunds",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceDataId = table.Column<string>(nullable: false),
|
||||
PullPaymentDataId = table.Column<string>(nullable: false)
|
||||
InvoiceDataId = table.Column<string>(maxLength: maxLength, nullable: false),
|
||||
PullPaymentDataId = table.Column<string>(maxLength: maxLength, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -27,8 +27,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "StoreWebhooks",
|
||||
columns: table => new
|
||||
{
|
||||
StoreId = table.Column<string>(nullable: false),
|
||||
WebhookId = table.Column<string>(nullable: false)
|
||||
StoreId = table.Column<string>(maxLength: 50, nullable: false),
|
||||
WebhookId = table.Column<string>(maxLength: 25, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
@ -71,8 +71,8 @@ namespace BTCPayServer.Migrations
|
||||
name: "InvoiceWebhookDeliveries",
|
||||
columns: table => new
|
||||
{
|
||||
InvoiceId = table.Column<string>(nullable: false),
|
||||
DeliveryId = table.Column<string>(nullable: false)
|
||||
InvoiceId = table.Column<string>(maxLength: 255, nullable: false),
|
||||
DeliveryId = table.Column<string>(maxLength: 100, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -10,7 +10,17 @@ namespace BTCPayServer.Migrations
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateIndex(
|
||||
if (!migrationBuilder.IsSqlite())
|
||||
{
|
||||
migrationBuilder.AlterColumn<string>(
|
||||
name: "OrderId",
|
||||
table: "Invoices",
|
||||
maxLength: 100,
|
||||
nullable: true,
|
||||
oldClrType: typeof(string));
|
||||
}
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Invoices_OrderId",
|
||||
table: "Invoices",
|
||||
column: "OrderId");
|
||||
|
@ -21,8 +21,8 @@ namespace BTCPayServer.Migrations
|
||||
.Annotation("MySql:ValueGeneratedOnAdd", true)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
// eof manually added
|
||||
InvoiceDataId = table.Column<string>(nullable: true),
|
||||
Value = table.Column<string>(nullable: true)
|
||||
InvoiceDataId = table.Column<string>(maxLength: 255, nullable: true),
|
||||
Value = table.Column<string>(maxLength: 512, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
|
@ -6,7 +6,7 @@
|
||||
<FrameworkReference Include="Microsoft.AspNetCore.App" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
|
||||
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.68" />
|
||||
<PackageReference Include="NBitcoin" Version="5.0.73" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="DigitalRuby.ExchangeSharp" Version="0.6.3" />
|
||||
</ItemGroup>
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,61 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Security;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Configuration;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.AppViewModels;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
using BTCPayServer.Models.ServerViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using NBitcoin.Scripting.Parser;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Newtonsoft.Json.Schema;
|
||||
using OpenQA.Selenium;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
using RatesViewModel = BTCPayServer.Models.StoreViewModels.RatesViewModel;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
@ -72,7 +42,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanAddDerivationSchemes()
|
||||
public async Task CanSetupWallet()
|
||||
{
|
||||
using (var tester = ServerTester.Create())
|
||||
{
|
||||
@ -80,7 +50,7 @@ namespace BTCPayServer.Tests
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
@ -99,37 +69,37 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(3, invoice.CryptoInfo.Length);
|
||||
|
||||
var controller = user.GetController<StoresController>();
|
||||
var lightningVM =
|
||||
(LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC"))
|
||||
.Model;
|
||||
Assert.True(lightningVM.Enabled);
|
||||
lightningVM.Enabled = false;
|
||||
controller.AddLightningNode(user.StoreId, lightningVM, "save", "BTC").GetAwaiter().GetResult();
|
||||
lightningVM =
|
||||
(LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC"))
|
||||
.Model;
|
||||
Assert.False(lightningVM.Enabled);
|
||||
var lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.True(lightningVm.Enabled);
|
||||
lightningVm.Enabled = false;
|
||||
controller.AddLightningNode(user.StoreId, lightningVm, "save", "BTC").GetAwaiter().GetResult();
|
||||
lightningVm = (LightningNodeViewModel)Assert.IsType<ViewResult>(controller.AddLightningNode(user.StoreId, "BTC")).Model;
|
||||
Assert.False(lightningVm.Enabled);
|
||||
|
||||
WalletSetupViewModel setupVm;
|
||||
var storeId = user.StoreId;
|
||||
var cryptoCode = "BTC";
|
||||
var response = await controller.GenerateWallet(storeId, cryptoCode, WalletSetupMethod.GenerateOptions, new GenerateWalletRequest());
|
||||
Assert.IsType<ViewResult>(response);
|
||||
|
||||
// Get setup view model from modify action
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Enabled);
|
||||
|
||||
// Only Enabling/Disabling the payment method must redirect to store page
|
||||
var derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.True(derivationVM.Enabled);
|
||||
derivationVM.Enabled = false;
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
Assert.False(derivationVM.Enabled);
|
||||
setupVm.Enabled = false;
|
||||
response = controller.UpdateWallet(setupVm).GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Clicking next without changing anything should send to the confirmation screen
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.False(setupVm.Enabled);
|
||||
|
||||
var oldScheme = setupVm.DerivationScheme;
|
||||
|
||||
invoice = user.BitPay.CreateInvoice(
|
||||
new Invoice()
|
||||
new Invoice
|
||||
{
|
||||
Price = 1.5m,
|
||||
Currency = "USD",
|
||||
@ -143,76 +113,57 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
|
||||
|
||||
// Removing the derivation scheme, should redirect to store page
|
||||
var oldScheme = derivationVM.DerivationScheme;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.DerivationScheme = null;
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
response = controller.ConfirmDeleteWallet(user.StoreId, "BTC").GetAwaiter().GetResult();
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
|
||||
// Setting it again should redirect to the confirmation page
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.DerivationScheme = oldScheme;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
// Setting it again should show the confirmation page
|
||||
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, DerivationScheme = oldScheme });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Confirmation);
|
||||
|
||||
// The following part posts a wallet update, confirms it and checks the result
|
||||
|
||||
//cobo vault file
|
||||
// cobo vault file
|
||||
var content = "{\"ExtPubKey\":\"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\"MasterFingerprint\":\"7a7563b5\",\"DerivationPath\":\"M\\/84'\\/0'\\/0'\",\"CoboVaultFirmwareVersion\":\"1.2.0(BTC-Only)\"}";
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet3.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
|
||||
//wasabi wallet file
|
||||
content =
|
||||
"{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
|
||||
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet4.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("cobovault.json", content)});
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("CoboVault", setupVm.Source);
|
||||
|
||||
// wasabi wallet file
|
||||
content = "{\r\n \"EncryptedSecret\": \"6PYWBQ1zsukowsnTNA57UUx791aBuJusm7E4egXUmF5WGw3tcdG3cmTL57\",\r\n \"ChainCode\": \"waSIVbn8HaoovoQg/0t8IS1+ZCxGsJRGFT21i06nWnc=\",\r\n \"MasterFingerprint\": \"7a7563b5\",\r\n \"ExtPubKey\": \"xpub6CEqRFZ7yZxCFXuEWZBAdnC8bdvu9SRHevaoU2SsW9ZmKhrCShmbpGZWwaR15hdLURf8hg47g4TpPGaqEU8hw5LEJCE35AUhne67XNyFGBk\",\r\n \"PasswordVerified\": false,\r\n \"MinGapLimit\": 21,\r\n \"AccountKeyPath\": \"84'/0'/0'\",\r\n \"BlockchainState\": {\r\n \"Network\": \"RegTest\",\r\n \"Height\": \"0\"\r\n },\r\n \"HdPubKeys\": []\r\n}";
|
||||
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("wasabi.json", content)});
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("WasabiFile", setupVm.Source);
|
||||
|
||||
// Can we upload coldcard settings? (Should fail, we are giving a mainnet file to a testnet network)
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
content =
|
||||
"{\"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}";
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet.json", content);
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.False(derivationVM
|
||||
.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
|
||||
content = "{\"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}";
|
||||
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-ypub.json", content)});
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.False(setupVm.Confirmation); // Should fail, we are giving a mainnet file to a testnet network
|
||||
|
||||
// And with a good file? (upub)
|
||||
content =
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"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}";
|
||||
derivationVM = (DerivationSchemeViewModel)Assert
|
||||
.IsType<ViewResult>(await controller.AddDerivationScheme(user.StoreId, "BTC")).Model;
|
||||
derivationVM.WalletFile = TestUtils.GetFormFile("wallet2.json", content);
|
||||
derivationVM.Enabled = true;
|
||||
derivationVM = (DerivationSchemeViewModel)Assert.IsType<ViewResult>(controller
|
||||
.AddDerivationScheme(user.StoreId, derivationVM, "BTC").GetAwaiter().GetResult()).Model;
|
||||
Assert.True(derivationVM.Confirmation);
|
||||
Assert.IsType<RedirectToActionResult>(controller.AddDerivationScheme(user.StoreId, derivationVM, "BTC")
|
||||
.GetAwaiter().GetResult());
|
||||
|
||||
content = "{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"upub5DBYp1qGgsTrkzCptMGZc2x18pquLwGrBw6nS59T4NViZ4cni1mGowQzziy85K8vzkp1jVtWrSkLhqk9KDfvrGeB369wGNYf39kX8rQfiLn\", \"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}";
|
||||
response = await controller.UpdateWallet(new WalletSetupViewModel {StoreId = storeId, CryptoCode = cryptoCode, WalletFile = TestUtils.GetFormFile("coldcard-upub.json", content)});
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.True(setupVm.Confirmation);
|
||||
response = await controller.UpdateWallet(setupVm);
|
||||
Assert.IsType<RedirectToActionResult>(response);
|
||||
response = await controller.ModifyWallet(new WalletSetupViewModel { StoreId = storeId, CryptoCode = cryptoCode });
|
||||
setupVm = (WalletSetupViewModel)Assert.IsType<ViewResult>(response).Model;
|
||||
Assert.Equal("ElectrumFile", setupVm.Source);
|
||||
|
||||
// Now let's check that no data has been lost in the process
|
||||
var store = tester.PayTester.StoreRepository.FindStore(user.StoreId).GetAwaiter().GetResult();
|
||||
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
|
||||
var onchainBTC = store.GetSupportedPaymentMethods(tester.PayTester.Networks)
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
.OfType<DerivationSchemeSettings>().First(o => o.PaymentId.IsBTCOnChain);
|
||||
@ -287,7 +238,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
user.RegisterDerivationScheme("LTC");
|
||||
@ -295,7 +246,6 @@ namespace BTCPayServer.Tests
|
||||
var invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC"));
|
||||
Assert.Equal(2, invoice.SupportedTransactionCurrencies.Count);
|
||||
|
||||
|
||||
invoice = await user.BitPay.CreateInvoiceAsync(new Invoice(100, "BTC")
|
||||
{
|
||||
SupportedTransactionCurrencies = new Dictionary<string, InvoiceSupportedTransactionCurrency>()
|
||||
@ -479,21 +429,21 @@ namespace BTCPayServer.Tests
|
||||
//there should be three now
|
||||
invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
|
||||
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("BTC", currencyDropdownButton.Text);
|
||||
currencyDropdownButton.Click();
|
||||
|
||||
var elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
|
||||
Assert.Equal(3, elements.Count);
|
||||
elements.Single(element => element.Text.Contains("LTC")).Click();
|
||||
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
|
||||
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("LTC", currencyDropdownButton.Text);
|
||||
currencyDropdownButton.Click();
|
||||
|
||||
elements = s.Driver.FindElement(By.ClassName("vex-content")).FindElements(By.ClassName("vexmenuitem"));
|
||||
elements.Single(element => element.Text.Contains("Lightning")).Click();
|
||||
|
||||
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
|
||||
currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("Lightning", currencyDropdownButton.Text);
|
||||
|
||||
s.Driver.Quit();
|
||||
@ -872,11 +822,11 @@ normal:
|
||||
{
|
||||
#pragma warning disable CS0618
|
||||
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
|
||||
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
||||
});
|
||||
var networkBTC = networkProvider.GetNetwork("BTC");
|
||||
var networkLTC = networkProvider.GetNetwork("LTC");
|
||||
@ -952,9 +902,9 @@ normal:
|
||||
[Trait("Altcoins", "Altcoins")]
|
||||
public void CanParseDerivationScheme()
|
||||
{
|
||||
var testnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Testnet);
|
||||
var regtestNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
var mainnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Mainnet);
|
||||
var testnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Testnet);
|
||||
var regtestNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var mainnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
|
||||
var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
|
||||
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
|
||||
|
@ -35,7 +35,7 @@ namespace BTCPayServer.Tests
|
||||
InitialData = new[] {new KeyValuePair<string, string>("chains", "usdt20"),}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
var networkProvider = config.ConfigureNetworkProvider();
|
||||
Assert.NotNull(networkProvider.GetNetwork("ETH"));
|
||||
Assert.NotNull(networkProvider.GetNetwork("USDT20"));
|
||||
@ -60,7 +60,7 @@ namespace BTCPayServer.Tests
|
||||
web3Link.Click();
|
||||
s.Driver.FindElement(By.Id("Web3ProviderUrl")).SendKeys("https://ropsten-rpc.linkpool.io");
|
||||
s.Driver.FindElement(By.Id("saveButton")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -73,20 +73,20 @@ namespace BTCPayServer.Tests
|
||||
var seed = new Mnemonic(Wordlist.English);
|
||||
s.Driver.FindElement(By.Id("ModifyETH")).Click();
|
||||
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
|
||||
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
|
||||
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
|
||||
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
|
||||
s.Driver.SetCheckbox(By.Id("Enabled"), true);
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.Id("ModifyUSDT20")).Click();
|
||||
s.Driver.FindElement(By.Id("Seed")).SendKeys(seed.ToString());
|
||||
s.SetCheckbox(s.Driver.FindElement(By.Id("StoreSeed")), true);
|
||||
s.SetCheckbox(s.Driver.FindElement(By.Id("Enabled")), true);
|
||||
s.Driver.SetCheckbox(By.Id("StoreSeed"), true);
|
||||
s.Driver.SetCheckbox(By.Id("Enabled"), true);
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10);
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
var currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
|
||||
var currencyDropdownButton = s.Driver.FindElement(By.ClassName("payment__currencies"));
|
||||
Assert.Contains("ETH", currencyDropdownButton.Text);
|
||||
s.Driver.FindElement(By.Id("copy-tab")).Click();
|
||||
|
||||
|
@ -61,28 +61,28 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
|
||||
|
||||
//server management should show now
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
|
||||
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
|
||||
s.SetCheckbox(s, "btcpay.user.canviewprofile", true);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.user.canviewprofile"), true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var superApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
|
||||
//this api key has access to everything
|
||||
await TestApiAgainstAccessToken(superApiKey, tester, user, Policies.CanModifyServerSettings, Policies.CanModifyStoreSettings, Policies.CanViewProfile);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
|
||||
Policies.CanModifyServerSettings);
|
||||
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var allStoreOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(allStoreOnlyApiKey, tester, user,
|
||||
Policies.CanModifyStoreSettings);
|
||||
|
||||
@ -94,13 +94,13 @@ namespace BTCPayServer.Tests
|
||||
var storeId = option.GetAttribute("value");
|
||||
option.Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var selectiveStoreApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var selectiveStoreApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(selectiveStoreApiKey, tester, user,
|
||||
Permission.Create(Policies.CanModifyStoreSettings, storeId).ToString());
|
||||
|
||||
s.Driver.FindElement(By.Id("AddApiKey")).Click();
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var noPermissionsApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var noPermissionsApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
await TestApiAgainstAccessToken(noPermissionsApiKey, tester, user);
|
||||
|
||||
await Assert.ThrowsAnyAsync<HttpRequestException>(async () =>
|
||||
@ -149,7 +149,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal("checkbox", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("type").ToLowerInvariant());
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Id("btcpay.server.canmodifyserversettings")).GetAttribute("value").ToLowerInvariant());
|
||||
|
||||
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", false);
|
||||
s.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
|
||||
Assert.Contains("change-store-mode", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("consent-yes")).Click();
|
||||
Assert.Equal(callbackUrl, s.Driver.Url);
|
||||
@ -188,7 +188,7 @@ namespace BTCPayServer.Tests
|
||||
checkbox.Click();
|
||||
}
|
||||
s.Driver.FindElement(By.Id("Generate")).Click();
|
||||
var allAPIKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
|
||||
var allAPIKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
|
||||
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
|
||||
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.13" />
|
||||
<PackageReference Include="Selenium.Support" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver" Version="3.141.0" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="87.0.4280.8800" />
|
||||
<PackageReference Include="Selenium.WebDriver.ChromeDriver" Version="88.0.4324.9600" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
|
@ -91,7 +91,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
string chain = NBXplorerDefaultSettings.GetFolderName(NetworkType.Regtest);
|
||||
string chain = NBXplorerDefaultSettings.GetFolderName(ChainName.Regtest);
|
||||
string chainDirectory = Path.Combine(_Directory, chain);
|
||||
if (!Directory.Exists(chainDirectory))
|
||||
Directory.CreateDirectory(chainDirectory);
|
||||
@ -112,7 +112,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
if (UseLightning)
|
||||
{
|
||||
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
|
||||
config.AppendLine($"btc.lightning={IntegratedLightning}");
|
||||
var localLndBackupFile = Path.Combine(_Directory, "walletunlock.json");
|
||||
File.Copy(TestUtils.GetTestDataFullPath("LndSeedBackup/walletunlock.json"), localLndBackupFile, true);
|
||||
config.AppendLine($"btc.external.lndseedbackup={localLndBackupFile}");
|
||||
@ -269,7 +269,7 @@ namespace BTCPayServer.Tests
|
||||
public InvoiceRepository InvoiceRepository { get; private set; }
|
||||
public StoreRepository StoreRepository { get; private set; }
|
||||
public BTCPayNetworkProvider Networks { get; private set; }
|
||||
public Uri IntegratedLightning { get; internal set; }
|
||||
public string IntegratedLightning { get; internal set; }
|
||||
public bool InContainer { get; internal set; }
|
||||
|
||||
public T GetService<T>()
|
||||
|
@ -1,13 +1,10 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using NBitcoin;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@ -36,7 +33,7 @@ namespace BTCPayServer.Tests
|
||||
s.AddDerivationScheme("BTC");
|
||||
s.GoToStore(store.storeId, StoreNavPages.Checkout);
|
||||
s.Driver.FindElement(By.Id("RequiresRefundEmail")).Click();
|
||||
s.Driver.FindElement(By.Name("command")).ForceClick();
|
||||
s.Driver.FindElement(By.Name("command")).Click();
|
||||
|
||||
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
|
||||
@ -112,14 +109,14 @@ namespace BTCPayServer.Tests
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
s.GoToRegister();
|
||||
s.RegisterNewUser();
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.AddInternalLightningNode("BTC");
|
||||
s.AddLightningNode();
|
||||
s.GoToStore(store.storeId, StoreNavPages.Checkout);
|
||||
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
|
||||
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
|
||||
var command = s.Driver.FindElement(By.Name("command"));
|
||||
|
||||
command.ForceClick();
|
||||
command.Click();
|
||||
var invoiceId = s.CreateInvoice(store.storeName, 10, "USD", "a@g.com");
|
||||
s.GoToInvoiceCheckout(invoiceId);
|
||||
Assert.Contains("Sats", s.Driver.FindElement(By.ClassName("payment__currencies_noborder")).Text);
|
||||
@ -166,30 +163,4 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class SeleniumExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Utility method to wait until timeout for element to be present (optionally displayed)
|
||||
/// </summary>
|
||||
/// <param name="context">Wait context</param>
|
||||
/// <param name="by">How we search for element</param>
|
||||
/// <param name="displayed">Flag to wait for element to be displayed or just present</param>
|
||||
/// <param name="timeout">How long to wait for element to be present/displayed</param>
|
||||
/// <returns>Element we were waiting for</returns>
|
||||
public static IWebElement WaitForElement(this IWebDriver context, By by, bool displayed = true, uint timeout = 3)
|
||||
{
|
||||
var wait = new DefaultWait<IWebDriver>(context);
|
||||
wait.Timeout = TimeSpan.FromSeconds(timeout);
|
||||
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
|
||||
return wait.Until(ctx =>
|
||||
{
|
||||
var elem = ctx.FindElement(by);
|
||||
if (displayed && !elem.Displayed)
|
||||
return null;
|
||||
|
||||
return elem;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,30 +7,24 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
public static string ToJson(this object o)
|
||||
private static readonly JsonSerializerSettings JsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
|
||||
public static string ToJson(this object o) => JsonConvert.SerializeObject(o, Formatting.None, JsonSettings);
|
||||
|
||||
public static void LogIn(this SeleniumTester s, string email)
|
||||
{
|
||||
var res = JsonConvert.SerializeObject(o, Formatting.None, jsonSettings);
|
||||
return res;
|
||||
}
|
||||
public static void ScrollTo(this IWebDriver driver, By by)
|
||||
{
|
||||
var element = driver.FindElement(by);
|
||||
}
|
||||
/// <summary>
|
||||
/// Sometimes the chrome driver is fucked up and we need some magic to click on the element.
|
||||
/// </summary>
|
||||
/// <param name="element"></param>
|
||||
public static void ForceClick(this IWebElement element)
|
||||
{
|
||||
element.SendKeys(Keys.Return);
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
}
|
||||
|
||||
public static void AssertNoError(this IWebDriver driver)
|
||||
{
|
||||
try
|
||||
@ -57,14 +51,18 @@ namespace BTCPayServer.Tests
|
||||
builder.AppendLine($"[{entry.Level}]: {entry.Message}");
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
builder.AppendLine($"---------");
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
builder.AppendLine("---------");
|
||||
}
|
||||
Logs.Tester.LogInformation(builder.ToString());
|
||||
builder = new StringBuilder();
|
||||
builder.AppendLine($"Selenium [Sources]:");
|
||||
builder.AppendLine("Selenium [Sources]:");
|
||||
builder.AppendLine(driver.PageSource);
|
||||
builder.AppendLine($"---------");
|
||||
builder.AppendLine("---------");
|
||||
Logs.Tester.LogInformation(builder.ToString());
|
||||
throw;
|
||||
}
|
||||
@ -108,5 +106,49 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
Assert.False(true, "Elements was found");
|
||||
}
|
||||
|
||||
public static void UntilJsIsReady(this WebDriverWait wait)
|
||||
{
|
||||
wait.Until(d=>((IJavaScriptExecutor)d).ExecuteScript("return document.readyState").Equals("complete"));
|
||||
wait.Until(d=>((IJavaScriptExecutor)d).ExecuteScript("return typeof(jQuery) === 'undefined' || jQuery.active === 0").Equals(true));
|
||||
}
|
||||
|
||||
public static IWebElement WaitForElement(this IWebDriver driver, By selector)
|
||||
{
|
||||
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
|
||||
wait.UntilJsIsReady();
|
||||
|
||||
var el = driver.FindElement(selector);
|
||||
wait.Until(d => el.Displayed);
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public static void WaitForAndClick(this IWebDriver driver, By selector)
|
||||
{
|
||||
var wait = new WebDriverWait(driver, SeleniumTester.ImplicitWait);
|
||||
wait.UntilJsIsReady();
|
||||
|
||||
var el = driver.FindElement(selector);
|
||||
wait.Until(d => el.Displayed && el.Enabled);
|
||||
el.Click();
|
||||
|
||||
wait.UntilJsIsReady();
|
||||
}
|
||||
|
||||
public static void SetCheckbox(this IWebDriver driver, By selector, bool value)
|
||||
{
|
||||
var element = driver.FindElement(selector);
|
||||
if ((value && !element.Selected) || (!value && element.Selected))
|
||||
{
|
||||
driver.WaitForAndClick(selector);
|
||||
}
|
||||
|
||||
if (value != element.Selected)
|
||||
{
|
||||
Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again");
|
||||
driver.SetCheckbox(selector, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@ -19,6 +20,7 @@ using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.OpenAsset;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -540,7 +542,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private async Task AssertValidationError(string[] fields, Func<Task> act)
|
||||
private async Task<GreenFieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
|
||||
{
|
||||
var remainingFields = fields.ToHashSet();
|
||||
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
|
||||
@ -550,6 +552,7 @@ namespace BTCPayServer.Tests
|
||||
remainingFields.Remove(field);
|
||||
}
|
||||
Assert.Empty(remainingFields);
|
||||
return ex;
|
||||
}
|
||||
|
||||
private async Task AssertHttpError(int code, Func<Task> act)
|
||||
@ -948,8 +951,12 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
var newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}") });
|
||||
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}"), Checkout = new CreateInvoiceRequest.CheckoutOptions()
|
||||
{
|
||||
RedirectAutomatically = true
|
||||
}});
|
||||
Assert.True(newInvoice.Checkout.RedirectAutomatically);
|
||||
|
||||
//list
|
||||
var invoices = await viewOnly.GetInvoices(user.StoreId);
|
||||
|
||||
@ -968,25 +975,28 @@ namespace BTCPayServer.Tests
|
||||
|
||||
|
||||
//update
|
||||
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
|
||||
|
||||
await AssertValidationError(new[] { nameof(MarkInvoiceStatusRequest.Status) }, async () =>
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
|
||||
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
Status = InvoiceStatus.Settled
|
||||
});
|
||||
|
||||
newInvoice = await client.CreateInvoice(user.StoreId,
|
||||
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
|
||||
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
|
||||
{
|
||||
Status = InvoiceStatus.Invalid
|
||||
});
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnly.UpdateInvoice(user.StoreId, newInvoice.Id,
|
||||
await viewOnly.UpdateInvoice(user.StoreId, invoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
});
|
||||
});
|
||||
invoice = await client.UpdateInvoice(user.StoreId, newInvoice.Id,
|
||||
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
|
||||
new UpdateInvoiceRequest()
|
||||
{
|
||||
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
|
||||
@ -994,6 +1004,12 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
|
||||
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
|
||||
|
||||
//also test the the metadata actually got saved
|
||||
invoice = await client.GetInvoice(user.StoreId, invoice.Id);
|
||||
Assert.Equal("updated",invoice.Metadata["itemCode"].Value<string>());
|
||||
Assert.Equal(15,((JArray) invoice.Metadata["newstuff"]).Values<int>().Sum());
|
||||
|
||||
//archive
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
@ -1077,10 +1093,28 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Assert.Equal("pt-PT", langs.FindBestMatch(match).Code);
|
||||
}
|
||||
|
||||
//payment method activation tests
|
||||
var store = await client.GetStore(user.StoreId);
|
||||
Assert.False(store.LazyPaymentMethods);
|
||||
store.LazyPaymentMethods = true;
|
||||
store = await client.UpdateStore(store.Id,
|
||||
JObject.FromObject(store).ToObject<UpdateStoreRequest>());
|
||||
Assert.True(store.LazyPaymentMethods);
|
||||
|
||||
invoice = await client.CreateInvoice(user.StoreId, new CreateInvoiceRequest() {Amount = 1, Currency = "USD"});
|
||||
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
Assert.False(paymentMethods.First().Activated);
|
||||
await client.ActivateInvoicePaymentMethod(user.StoreId, invoice.Id,
|
||||
paymentMethods.First().PaymentMethod);
|
||||
paymentMethods = await client.GetInvoicePaymentMethods(store.Id, invoice.Id);
|
||||
Assert.Single(paymentMethods);
|
||||
Assert.True(paymentMethods.First().Activated);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Fact(Timeout = 60 * 20 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanUseLightningAPI()
|
||||
@ -1098,8 +1132,7 @@ namespace BTCPayServer.Tests
|
||||
merchant.GrantAccess(true);
|
||||
merchant.RegisterLightningNode("BTC", LightningConnectionType.LndREST);
|
||||
var merchantClient = await merchant.CreateClient($"{Policies.CanUseLightningNodeInStore}:{merchant.StoreId}");
|
||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
|
||||
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
|
||||
// The default client is using charge, so we should not be able to query channels
|
||||
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
|
||||
|
||||
@ -1170,7 +1203,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task NotificationAPITests()
|
||||
@ -1267,8 +1299,6 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
[Trait("Integration", "Integration")]
|
||||
@ -1278,33 +1308,86 @@ namespace BTCPayServer.Tests
|
||||
tester.ActivateLightning();
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings);
|
||||
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
|
||||
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
|
||||
var store = await client.GetStore(user.StoreId);
|
||||
var admin = tester.NewAccount();
|
||||
await admin.GrantAccessAsync(true);
|
||||
var admin2 = tester.NewAccount();
|
||||
await admin2.GrantAccessAsync(true);
|
||||
var adminClient = await admin.CreateClient(Policies.CanModifyStoreSettings);
|
||||
var admin2Client = await admin2.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
|
||||
var viewOnlyClient = await admin.CreateClient(Policies.CanViewStoreSettings);
|
||||
var store = await adminClient.GetStore(admin.StoreId);
|
||||
|
||||
Assert.Empty(await client.GetStoreLightningNetworkPaymentMethods(store.Id));
|
||||
Assert.Empty(await adminClient.GetStoreLightningNetworkPaymentMethods(store.Id));
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() { });
|
||||
});
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
});
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, false);
|
||||
await admin.RegisterLightningNodeAsync("BTC", false);
|
||||
|
||||
var method = await client.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
var method = await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
});
|
||||
await client.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await AssertHttpError(404, async () =>
|
||||
{
|
||||
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
await adminClient.GetStoreOnChainPaymentMethod(store.Id, "BTC");
|
||||
});
|
||||
|
||||
|
||||
// Let's verify that the admin client can't change LN to unsafe connection strings without modify server settings rights
|
||||
foreach (var forbidden in new string[]
|
||||
{
|
||||
"type=clightning;server=tcp://127.0.0.1",
|
||||
"type=clightning;server=tcp://test",
|
||||
"type=clightning;server=tcp://test.lan",
|
||||
"type=clightning;server=tcp://test.local",
|
||||
"type=clightning;server=tcp://192.168.1.2",
|
||||
"type=clightning;server=unix://8.8.8.8",
|
||||
"type=clightning;server=unix://[::1]",
|
||||
"type=clightning;server=unix://[0:0:0:0:0:0:0:1]",
|
||||
})
|
||||
{
|
||||
var ex = await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
Assert.Contains("btcpay.server.canmodifyserversettings", ex.Message);
|
||||
// However, the other client should work because he has `btcpay.server.canmodifyserversettings`
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData()
|
||||
{
|
||||
ConnectionString = forbidden,
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
}
|
||||
// Allowed ip should be ok
|
||||
await adminClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://8.8.8.8",
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
// If we strip the admin's right, he should not be able to set unsafe anymore, even if the API key is still valid
|
||||
await admin2.MakeAdmin(false);
|
||||
await AssertValidationError(new[] { "ConnectionString" }, async () =>
|
||||
{
|
||||
await admin2Client.UpdateStoreLightningNetworkPaymentMethod(admin2.StoreId, "BTC", new LightningNetworkPaymentMethodData()
|
||||
{
|
||||
ConnectionString = "type=clightning;server=tcp://127.0.0.1",
|
||||
CryptoCode = "BTC",
|
||||
Enabled = true
|
||||
});
|
||||
});
|
||||
|
||||
var settings = (await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>())?? new PoliciesSettings();
|
||||
@ -1329,7 +1412,227 @@ namespace BTCPayServer.Tests
|
||||
|
||||
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", method);
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = 60 * 2 * 1000)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task WalletAPITests()
|
||||
{
|
||||
using var tester = ServerTester.Create();
|
||||
await tester.StartAsync();
|
||||
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync(true);
|
||||
|
||||
var client = await user.CreateClient(Policies.CanModifyStoreSettings, Policies.CanModifyServerSettings);
|
||||
var viewOnlyClient = await user.CreateClient(Policies.CanViewStoreSettings);
|
||||
var walletId = await user.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
|
||||
|
||||
//view only clients can't do jack shit with this API
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode );
|
||||
});
|
||||
var overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode );
|
||||
Assert.Equal(0m, overview.Balance);
|
||||
|
||||
|
||||
var fee = await client.GetOnChainFeeRate(walletId.StoreId, walletId.CryptoCode );
|
||||
Assert.NotNull( fee.FeeRate);
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode );
|
||||
});
|
||||
var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode );
|
||||
var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode );
|
||||
var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true );
|
||||
Assert.Equal(address.Address, address2.Address);
|
||||
Assert.NotEqual(address.Address, address3.Address);
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode);
|
||||
});
|
||||
Assert.Empty(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode));
|
||||
uint256 txhash = null;
|
||||
await tester.WaitForEvent<NewOnChainTransactionEvent>(async () =>
|
||||
{
|
||||
txhash = await tester.ExplorerNode.SendToAddressAsync(
|
||||
BitcoinAddress.Create(address3.Address, tester.ExplorerClient.Network.NBitcoinNetwork),
|
||||
new Money(0.01m, MoneyUnit.BTC));
|
||||
});
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
var address4 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, false );
|
||||
Assert.NotEqual(address3.Address, address4.Address);
|
||||
await client.UnReserveOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode);
|
||||
var address5 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true );
|
||||
Assert.Equal(address5.Address, address4.Address);
|
||||
|
||||
|
||||
var utxo = Assert.Single(await client.GetOnChainWalletUTXOs(walletId.StoreId, walletId.CryptoCode));
|
||||
Assert.Equal(0.01m, utxo.Amount);
|
||||
Assert.Equal(txhash, utxo.Outpoint.Hash);
|
||||
overview = await client.ShowOnChainWalletOverview(walletId.StoreId, walletId.CryptoCode );
|
||||
Assert.Equal(0.01m, overview.Balance);
|
||||
|
||||
//the simplest request:
|
||||
var nodeAddress = await tester.ExplorerNode.GetNewAddressAsync();
|
||||
var createTxRequest = new CreateOnChainTransactionRequest()
|
||||
{
|
||||
Destinations =
|
||||
new List<CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination>()
|
||||
{
|
||||
new CreateOnChainTransactionRequest.CreateOnChainTransactionRequestDestination()
|
||||
{
|
||||
Destination = nodeAddress.ToString(), Amount = 0.001m
|
||||
}
|
||||
},
|
||||
FeeRate = new FeeRate(5m) //only because regtest may fail but not required
|
||||
};
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode, createTxRequest );
|
||||
});
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
|
||||
{
|
||||
await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () =>
|
||||
{
|
||||
createTxRequest.ProceedWithBroadcast = false;
|
||||
await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest);
|
||||
});
|
||||
Transaction tx;
|
||||
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
|
||||
Assert.NotNull(tx);
|
||||
Assert.Contains(tx.Outputs, txout => txout.IsTo(nodeAddress) && txout.Value.ToDecimal(MoneyUnit.BTC) == 0.001m);
|
||||
Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed);
|
||||
|
||||
// no change test
|
||||
createTxRequest.NoChange = true;
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
Assert.NotNull(tx);
|
||||
Assert.True(Assert.Single(tx.Outputs).IsTo(nodeAddress) );
|
||||
Assert.True((await tester.ExplorerNode.TestMempoolAcceptAsync(tx)).IsAllowed);
|
||||
|
||||
createTxRequest.NoChange = false;
|
||||
//coin selection
|
||||
await AssertValidationError(new []{nameof(createTxRequest.SelectedInputs)}, async () =>
|
||||
{
|
||||
createTxRequest.SelectedInputs = new List<OutPoint>();
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
createTxRequest.SelectedInputs = new List<OutPoint>()
|
||||
{
|
||||
utxo.Outpoint
|
||||
};
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
createTxRequest.SelectedInputs = null;
|
||||
|
||||
//destination testing
|
||||
await AssertValidationError(new []{ "Destinations"}, async () =>
|
||||
{
|
||||
createTxRequest.Destinations[0].Amount = utxo.Amount;
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
|
||||
createTxRequest.Destinations[0].SubtractFromAmount = true;
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
|
||||
await AssertValidationError(new []{ "Destinations[0]"}, async () =>
|
||||
{
|
||||
createTxRequest.Destinations[0].Amount = 0m;
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
|
||||
//dest can be a bip21
|
||||
|
||||
//cant use bip with subtractfromamount
|
||||
createTxRequest.Destinations[0].Amount = null;
|
||||
createTxRequest.Destinations[0].Destination = $"bitcoin:{nodeAddress}?amount=0.001";
|
||||
await AssertValidationError(new []{ "Destinations[0]"}, async () =>
|
||||
{
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
//if amt specified, it overrides bip21 amount
|
||||
createTxRequest.Destinations[0].Amount = 0.0001m;
|
||||
createTxRequest.Destinations[0].SubtractFromAmount = false;
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
Assert.Contains(tx.Outputs, txout => txout.Value.GetValue(tester.NetworkProvider.GetNetwork<BTCPayNetwork>("BTC")) ==0.0001m );
|
||||
|
||||
//fee rate test
|
||||
createTxRequest.FeeRate = FeeRate.Zero;
|
||||
await AssertValidationError(new []{ "FeeRate"}, async () =>
|
||||
{
|
||||
tx = await client.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
|
||||
|
||||
createTxRequest.FeeRate = new FeeRate(5.0m);
|
||||
|
||||
createTxRequest.Destinations[0].Amount = 0.001m;
|
||||
createTxRequest.Destinations[0].Destination = nodeAddress.ToString();
|
||||
createTxRequest.Destinations[0].SubtractFromAmount = false;
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.CreateOnChainTransactionButDoNotBroadcast(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest, tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
});
|
||||
createTxRequest.ProceedWithBroadcast = true;
|
||||
var txdata=
|
||||
await client.CreateOnChainTransaction(walletId.StoreId, walletId.CryptoCode,
|
||||
createTxRequest);
|
||||
Assert.Equal(TransactionStatus.Unconfirmed, txdata.Status);
|
||||
Assert.Null(txdata.BlockHeight);
|
||||
Assert.Null(txdata.BlockHash);
|
||||
Assert.NotNull(await tester.ExplorerClient.GetTransactionAsync(txdata.TransactionHash));
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
});
|
||||
await client.GetOnChainWalletTransaction(walletId.StoreId, walletId.CryptoCode, txdata.TransactionHash.ToString());
|
||||
|
||||
await AssertHttpError(403, async () =>
|
||||
{
|
||||
await viewOnlyClient.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode);
|
||||
});
|
||||
Assert.True(Assert.Single(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
|
||||
new[] {TransactionStatus.Confirmed})).TransactionHash == utxo.Outpoint.Hash);
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
|
||||
new[] {TransactionStatus.Unconfirmed}), data => data.TransactionHash == txdata.TransactionHash);
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode), data => data.TransactionHash == txdata.TransactionHash);
|
||||
await tester.WaitForEvent<NewBlockEvent>(async () =>
|
||||
{
|
||||
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
}, bevent => bevent.CryptoCode.Equals("BTC", StringComparison.Ordinal));
|
||||
|
||||
Assert.Contains(
|
||||
await client.ShowOnChainWalletTransactions(walletId.StoreId, walletId.CryptoCode,
|
||||
new[] {TransactionStatus.Confirmed}), data => data.TransactionHash == txdata.TransactionHash);
|
||||
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void NumericJsonConverterTests()
|
||||
@ -1345,7 +1648,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(jsonConverter.CanConvert(typeof(double)));
|
||||
Assert.True(jsonConverter.CanConvert(typeof(double?)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(float)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(int)));
|
||||
Assert.False(jsonConverter.CanConvert(typeof(string)));
|
||||
|
||||
var numberJson = "1";
|
||||
|
@ -8,10 +8,10 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Controllers;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
@ -19,12 +19,14 @@ using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
|
||||
@ -85,6 +87,19 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(await repo.TryLock(outpoint));
|
||||
Assert.True(await repo.TryUnlock(outpoint));
|
||||
Assert.False(await repo.TryUnlock(outpoint));
|
||||
|
||||
// Make sure that if any can't be locked, all are not locked
|
||||
var outpoint1 = RandomOutpoint();
|
||||
var outpoint2 = RandomOutpoint();
|
||||
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
|
||||
Assert.False(await repo.TryLockInputs(new[] { outpoint1, outpoint2 }));
|
||||
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
|
||||
|
||||
outpoint1 = RandomOutpoint();
|
||||
outpoint2 = RandomOutpoint();
|
||||
Assert.True(await repo.TryLockInputs(new[] { outpoint1 }));
|
||||
Assert.False(await repo.TryLockInputs(new[] { outpoint2, outpoint1 }));
|
||||
Assert.True(await repo.TryLockInputs(new[] { outpoint2 }));
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,9 +179,7 @@ namespace BTCPayServer.Tests
|
||||
var cashCow = tester.ExplorerNode;
|
||||
cashCow.Generate(2); // get some money in case
|
||||
|
||||
var unsupportedFormats = Enum.GetValues(typeof(ScriptPubKeyType))
|
||||
.AssertType<ScriptPubKeyType[]>()
|
||||
.Where(type => !PayjoinClient.SupportedFormats.Contains(type));
|
||||
var unsupportedFormats = new[] {ScriptPubKeyType.Legacy};
|
||||
|
||||
|
||||
foreach (ScriptPubKeyType senderAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
|
||||
@ -219,7 +232,7 @@ namespace BTCPayServer.Tests
|
||||
var invoiceRepository = s.Server.PayTester.GetService<InvoiceRepository>();
|
||||
s.RegisterNewUser(true);
|
||||
|
||||
foreach (var format in PayjoinClient.SupportedFormats)
|
||||
foreach (var format in new []{ScriptPubKeyType.Segwit, ScriptPubKeyType.SegwitP2SH})
|
||||
{
|
||||
var receiver = s.CreateNewStore();
|
||||
var receiverSeed = s.GenerateWallet("BTC", "", true, true, format);
|
||||
@ -236,9 +249,10 @@ namespace BTCPayServer.Tests
|
||||
s.GoToStore(receiver.storeId);
|
||||
//payjoin is not enabled by default.
|
||||
Assert.False(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
||||
s.SetCheckbox(s, "PayJoinEnabled", true);
|
||||
s.Driver.SetCheckbox(By.Id("PayJoinEnabled"), true);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
|
||||
|
||||
var sender = s.CreateNewStore();
|
||||
var senderSeed = s.GenerateWallet("BTC", "", true, true, format);
|
||||
var senderWalletId = new WalletId(sender.storeId, "BTC");
|
||||
@ -257,16 +271,17 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinBIP21"))
|
||||
.GetAttribute("value")));
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
var nbxSeedButton = s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]"));
|
||||
new WebDriverWait(s.Driver, SeleniumTester.ImplicitWait).Until(d=> nbxSeedButton.Enabled);
|
||||
nbxSeedButton.Click();
|
||||
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
|
||||
{
|
||||
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
//no funds in receiver wallet to do payjoin
|
||||
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Warning);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Warning);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
|
||||
@ -294,15 +309,14 @@ namespace BTCPayServer.Tests
|
||||
.GetAttribute("value")));
|
||||
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear();
|
||||
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2");
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
var txId = await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
|
||||
{
|
||||
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
var invoice = await invoiceRepository.GetInvoice(invoiceId);
|
||||
@ -363,10 +377,15 @@ namespace BTCPayServer.Tests
|
||||
var alice = tester.NewAccount();
|
||||
await alice.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
|
||||
await notifications.ListenDerivationSchemesAsync(new[] { alice.DerivationScheme });
|
||||
var address = (await nbx.GetUnusedAsync(alice.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.0m));
|
||||
await notifications.NextEventAsync();
|
||||
|
||||
BitcoinAddress address = null;
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
address = (await nbx.GetUnusedAsync(alice.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
await tester.ExplorerNode.GenerateAsync(1);
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.0m));
|
||||
await notifications.NextEventAsync();
|
||||
}
|
||||
var paymentAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest);
|
||||
var otherAddress = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest);
|
||||
var psbt = (await nbx.CreatePSBTAsync(alice.DerivationScheme, new CreatePSBTRequest()
|
||||
@ -408,7 +427,7 @@ namespace BTCPayServer.Tests
|
||||
using var fakeServer = new FakeServer();
|
||||
await fakeServer.Start();
|
||||
var bip21 = new BitcoinUrlBuilder($"bitcoin:{paymentAddress}?pj={fakeServer.ServerUri}", Network.RegTest);
|
||||
var requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
var requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
var request = await fakeServer.GetNextRequest();
|
||||
Assert.Equal("1", request.Request.Query["v"][0]);
|
||||
Assert.Equal(changeIndex.ToString(), request.Request.Query["additionalfeeoutputindex"][0]);
|
||||
@ -424,7 +443,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("contribution is more than maxadditionalfeecontribution", ex.Message);
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver tries to change one of our output");
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -433,9 +452,8 @@ namespace BTCPayServer.Tests
|
||||
fakeServer.Done();
|
||||
ex = await Assert.ThrowsAsync<PayjoinSenderException>(async () => await requesting);
|
||||
Assert.Contains("The receiver decreased the value of one", ex.Message);
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver tries to pocket the fee");
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -446,7 +464,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("The receiver decreased absolute fee", ex.Message);
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver tries to remove one of our output");
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -458,7 +476,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains("Some of our outputs are not included in the proposal", ex.Message);
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver tries to change their own output");
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -470,7 +488,7 @@ namespace BTCPayServer.Tests
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver tries to send money to himself");
|
||||
pjClient.MaxFeeBumpContribution = Money.Satoshis(1);
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -481,10 +499,9 @@ namespace BTCPayServer.Tests
|
||||
ex = await Assert.ThrowsAsync<PayjoinSenderException>(async () => await requesting);
|
||||
Assert.Contains("is not only paying fee", ex.Message);
|
||||
pjClient.MaxFeeBumpContribution = null;
|
||||
|
||||
Logs.Tester.LogInformation("The payjoin receiver can't use additional fee without adding inputs");
|
||||
pjClient.MinimumFeeRate = new FeeRate(50m);
|
||||
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
|
||||
requesting = pjClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
request = await fakeServer.GetNextRequest();
|
||||
originalPSBT = await ParsePSBT(request);
|
||||
proposalTx = originalPSBT.GetGlobalTransaction();
|
||||
@ -499,6 +516,9 @@ namespace BTCPayServer.Tests
|
||||
var bob = tester.NewAccount();
|
||||
await bob.GrantAccessAsync();
|
||||
await bob.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
|
||||
|
||||
await notifications.DisposeAsync();
|
||||
notifications = await nbx.CreateWebsocketNotificationSessionAsync();
|
||||
await notifications.ListenDerivationSchemesAsync(new[] { bob.DerivationScheme });
|
||||
address = (await nbx.GetUnusedAsync(bob.DerivationScheme, DerivationFeature.Deposit)).Address;
|
||||
tester.ExplorerNode.SendToAddress(address, Money.Coins(1.1m));
|
||||
@ -508,6 +528,7 @@ namespace BTCPayServer.Tests
|
||||
new Invoice() { Price = 0.1m, Currency = "BTC", FullNotifications = true });
|
||||
var invoiceBIP21 = new BitcoinUrlBuilder(invoice.CryptoInfo.First().PaymentUrls.BIP21,
|
||||
tester.ExplorerClient.Network.NBitcoinNetwork);
|
||||
|
||||
psbt = (await nbx.CreatePSBTAsync(alice.DerivationScheme, new CreatePSBTRequest()
|
||||
{
|
||||
Destinations =
|
||||
@ -526,7 +547,7 @@ namespace BTCPayServer.Tests
|
||||
psbt.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
|
||||
var endpoint = TestAccount.GetPayjoinBitcoinUrl(invoice, Network.RegTest);
|
||||
pjClient.MaxFeeBumpContribution = Money.Satoshis(50);
|
||||
var proposal = await pjClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, default);
|
||||
var proposal = await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(derivationSchemeSettings), psbt, default);
|
||||
Assert.True(proposal.TryGetFee(out var newFee));
|
||||
Assert.Equal(Money.Satoshis(3001 + 50), newFee);
|
||||
proposal = proposal.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
|
||||
@ -557,7 +578,7 @@ namespace BTCPayServer.Tests
|
||||
psbt.SignAll(derivationSchemeSettings.AccountDerivation, alice.GenerateWalletResponseV.AccountHDKey, signingAccount.GetRootedKeyPath());
|
||||
endpoint = TestAccount.GetPayjoinBitcoinUrl(invoice, Network.RegTest);
|
||||
pjClient.MinimumFeeRate = new FeeRate(100_000_000.2m);
|
||||
var ex2 = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, default));
|
||||
var ex2 = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(derivationSchemeSettings), psbt, default));
|
||||
Assert.Equal(PayjoinReceiverWellknownErrors.NotEnoughMoney, ex2.WellknownError);
|
||||
}
|
||||
}
|
||||
@ -799,16 +820,32 @@ retry:
|
||||
//give the cow some cash
|
||||
await cashCow.GenerateAsync(1);
|
||||
//let's get some more utxos first
|
||||
await receiverUser.ReceiveUTXO(Money.Coins(0.011m), btcPayNetwork);
|
||||
await receiverUser.ReceiveUTXO(Money.Coins(0.012m), btcPayNetwork);
|
||||
await receiverUser.ReceiveUTXO(Money.Coins(0.013m), btcPayNetwork);
|
||||
await receiverUser.ReceiveUTXO(Money.Coins(0.014m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.021m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.022m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.023m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.024m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.025m), btcPayNetwork);
|
||||
await senderUser.ReceiveUTXO(Money.Coins(0.026m), btcPayNetwork);
|
||||
foreach (var m in new []
|
||||
{
|
||||
Money.Coins(0.011m),
|
||||
Money.Coins(0.012m),
|
||||
Money.Coins(0.013m),
|
||||
Money.Coins(0.014m),
|
||||
Money.Coins(0.015m),
|
||||
Money.Coins(0.016m)
|
||||
})
|
||||
{
|
||||
await receiverUser.ReceiveUTXO(m, btcPayNetwork);
|
||||
}
|
||||
|
||||
foreach (var m in new[]
|
||||
{
|
||||
Money.Coins(0.021m),
|
||||
Money.Coins(0.022m),
|
||||
Money.Coins(0.023m),
|
||||
Money.Coins(0.024m),
|
||||
Money.Coins(0.025m),
|
||||
Money.Coins(0.026m)
|
||||
})
|
||||
{
|
||||
await senderUser.ReceiveUTXO(m, btcPayNetwork);
|
||||
}
|
||||
|
||||
var senderChange = await senderUser.GetNewAddress(btcPayNetwork);
|
||||
|
||||
//Let's start the harassment
|
||||
@ -839,17 +876,24 @@ retry:
|
||||
settings.PaymentId == paymentMethodId);
|
||||
|
||||
ReceivedCoin[] senderCoins = null;
|
||||
ReceivedCoin coin = null;
|
||||
ReceivedCoin coin2 = null;
|
||||
ReceivedCoin coin3 = null;
|
||||
ReceivedCoin coin4 = null;
|
||||
ReceivedCoin coin5 = null;
|
||||
ReceivedCoin coin6 = null;
|
||||
await TestUtils.EventuallyAsync(async () =>
|
||||
{
|
||||
senderCoins = await btcPayWallet.GetUnspentCoins(senderUser.DerivationScheme);
|
||||
Assert.Contains(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
|
||||
coin = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
|
||||
coin2 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
|
||||
coin3 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
|
||||
coin4 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
|
||||
coin5 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
|
||||
coin6 = Assert.Single(senderCoins, coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
|
||||
});
|
||||
var coin = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.021m);
|
||||
var coin2 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.022m);
|
||||
var coin3 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.023m);
|
||||
var coin4 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.024m);
|
||||
var coin5 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.025m);
|
||||
var coin6 = senderCoins.Single(coin => coin.Value.GetValue(btcPayNetwork) == 0.026m);
|
||||
|
||||
|
||||
var signingKeySettings = derivationSchemeSettings.GetSigningAccountKeySettings();
|
||||
signingKeySettings.RootFingerprint =
|
||||
@ -893,10 +937,6 @@ retry:
|
||||
.SendEstimatedFees(new FeeRate(100m))
|
||||
.BuildTransaction(true);
|
||||
|
||||
//Attempt 1: Send a signed tx to invoice 1 that does not pay the invoice at all
|
||||
//Result: reject
|
||||
// Assert.False((await tester.PayTester.HttpClient.PostAsync(endpoint,
|
||||
// new StringContent(Invoice2Coin1.ToHex(), Encoding.UTF8, "text/plain"))).IsSuccessStatusCode);
|
||||
|
||||
//Attempt 2: Create two transactions using different inputs and send them to the same invoice.
|
||||
//Result: Second Tx should be rejected.
|
||||
|
@ -26,6 +26,21 @@ You can also generate blocks:
|
||||
.\docker-bitcoin-generate.ps1 3
|
||||
```
|
||||
|
||||
### Using Polar to test Lightning payments
|
||||
|
||||
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
|
||||
- Go to your store's General Settings and enable Lightning.
|
||||
- Build your connection string using the Connect infomation in the Polar app.
|
||||
|
||||
LND Connection string example:
|
||||
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
|
||||
|
||||
Now you can create a Lightning invoice on BTCPay Server regtest and make a payment through Polar.
|
||||
|
||||
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
|
||||
Or, uncheck the box that says, "Break when this exceptiontype is thrown".
|
||||
|
||||
|
||||
### Using the test litecoin-cli
|
||||
|
||||
Same as bitcoin-cli, but with `.\docker-litecoin-cli.ps1` and `.\docker-litecoin-cli.sh` instead.
|
||||
@ -55,16 +70,45 @@ Please, run the test `CanSetLightningServer`, this will establish a channel betw
|
||||
Alternatively you can run the `./docker-lightning-channel-setup.sh` script to establish the channel connection.
|
||||
The `./docker-lightning-channel-teardown.sh` script closes any existing lightning channels.
|
||||
|
||||
### Alternative Lightning testing: Using Polar to test Lightning payments
|
||||
|
||||
- Install and run [Polar](https://lightningpolar.com/). Setup a small network of nodes.
|
||||
- Go to your store's General Settings and enable Lightning.
|
||||
- Build your connection string using the Connect information in the Polar app.
|
||||
|
||||
LND Connection string example:
|
||||
type=lnd-rest;server=https://127.0.0.1:8084/;macaroonfilepath="local path to admin.macaroon on your computer, without these quotes";allowinsecure=true
|
||||
|
||||
Now you can create a lightning invoice on BTCPay Server regtest and make a payment through Polar.
|
||||
|
||||
PLEASE NOTE: You may get an exception break in Visual Studio. You must quickly click "Continue" in VS so the invoice is generated.
|
||||
Or, uncheck the box that says, "Break when this exception type is thrown".
|
||||
|
||||
## FAQ
|
||||
|
||||
`docker-compose up dev` failed or tests are not passing, what should I do?
|
||||
### `docker-compose up dev` failed or tests are not passing, what should I do?
|
||||
|
||||
1. Run `docker-compose down --v` (this will reset your test environment)
|
||||
2. Run `docker-compose pull` (this will ensure you have the lastest images)
|
||||
3. Run again with `docker-compose up dev`
|
||||
|
||||
How to run the Altcoin environment?
|
||||
### How to run the Altcoin environment?
|
||||
|
||||
`docker-compose -f docker-compose.altcoins.yml up dev`
|
||||
|
||||
If you still have issues, try to restart docker.
|
||||
|
||||
### How to run the Selenium test with a browser?
|
||||
|
||||
Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser.
|
||||
|
||||
To switch back to headless mode (recommended) you can run `dotnet user-secrets remove RunSeleniumInBrowser`.
|
||||
|
||||
### Session not created: This version of ChromeDriver only supports Chrome version 88
|
||||
|
||||
When you run tests for selenium, you may end up with this error.
|
||||
This happen when we update the selenium packages on BTCPay Server while you did not update your chrome version.
|
||||
|
||||
If you want to use a older chrome driver on [this page](https://chromedriver.chromium.org/downloads) then point to it with
|
||||
|
||||
`dotnet user-secrets set ChromeDriverDirectory "path/to/the/driver/directory"`
|
||||
|
@ -3,23 +3,22 @@ using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Chrome;
|
||||
using OpenQA.Selenium.Interactions;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using Xunit;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
@ -28,64 +27,69 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
public IWebDriver Driver { get; set; }
|
||||
public ServerTester Server { get; set; }
|
||||
public WalletId WalletId { get; set; }
|
||||
|
||||
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false)
|
||||
{
|
||||
var server = ServerTester.Create(scope, newDb);
|
||||
return new SeleniumTester()
|
||||
{
|
||||
Server = server
|
||||
};
|
||||
}
|
||||
public string StoreId { get; set; }
|
||||
|
||||
public static SeleniumTester Create([CallerMemberNameAttribute] string scope = null, bool newDb = false) =>
|
||||
new SeleniumTester { Server = ServerTester.Create(scope, newDb) };
|
||||
|
||||
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(5);
|
||||
|
||||
public async Task StartAsync()
|
||||
{
|
||||
await Server.StartAsync();
|
||||
ChromeOptions options = new ChromeOptions();
|
||||
|
||||
var windowSize = (Width: 1200, Height: 1000);
|
||||
var builder = new ConfigurationBuilder();
|
||||
builder.AddUserSecrets("AB0AC1DD-9D26-485B-9416-56A33F268117");
|
||||
var config = builder.Build();
|
||||
|
||||
// Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser
|
||||
var runInBrowser = config["RunSeleniumInBrowser"] == "true";
|
||||
// Reset this using `dotnet user-secrets remove RunSeleniumInBrowser`
|
||||
|
||||
var chromeDriverPath = config["ChromeDriverDirectory"] ?? (Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory());
|
||||
|
||||
var options = new ChromeOptions();
|
||||
if (Server.PayTester.InContainer)
|
||||
{
|
||||
// this must be first option https://stackoverflow.com/questions/53073411/selenium-webdriverexceptionchrome-failed-to-start-crashed-as-google-chrome-is#comment102570662_53073789
|
||||
options.AddArgument("no-sandbox");
|
||||
}
|
||||
|
||||
var isDebug = !Server.PayTester.InContainer;
|
||||
if (!isDebug)
|
||||
if (!runInBrowser)
|
||||
{
|
||||
options.AddArguments("headless"); // Comment to view browser
|
||||
options.AddArguments("window-size=1200x1000"); // Comment to view browser
|
||||
options.AddArguments("headless");
|
||||
}
|
||||
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
|
||||
options.AddArgument("shm-size=2g");
|
||||
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
|
||||
if (isDebug)
|
||||
Driver = new ChromeDriver(chromeDriverPath, options,
|
||||
// A bit less than test timeout
|
||||
TimeSpan.FromSeconds(50));
|
||||
|
||||
if (runInBrowser)
|
||||
{
|
||||
//when running locally, depending on your resolution, the website may go into mobile responsive mode and screw with navigation of tests
|
||||
// ensure maximized window size
|
||||
// otherwise TESTS WILL FAIL because of different hierarchy in navigation menu
|
||||
Driver.Manage().Window.Maximize();
|
||||
}
|
||||
Logs.Tester.LogInformation("Selenium: Using chrome driver");
|
||||
Logs.Tester.LogInformation("Selenium: Browsing to " + Server.PayTester.ServerUri);
|
||||
|
||||
Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}");
|
||||
Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}");
|
||||
Logs.Tester.LogInformation($"Selenium: Resolution {Driver.Manage().Window.Size}");
|
||||
Driver.Manage().Timeouts().ImplicitWait = ImplicitWait;
|
||||
GoToRegister();
|
||||
Driver.AssertNoError();
|
||||
}
|
||||
|
||||
internal IWebElement AssertHappyMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
internal IWebElement FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
|
||||
{
|
||||
using var cts = new CancellationTokenSource(20_000);
|
||||
while (!cts.IsCancellationRequested)
|
||||
{
|
||||
var result = Driver.FindElements(By.ClassName($"alert-{StatusMessageModel.ToString(severity)}")).Where(el => el.Displayed);
|
||||
if (result.Any())
|
||||
return result.First();
|
||||
Thread.Sleep(100);
|
||||
}
|
||||
Logs.Tester.LogInformation(this.Driver.PageSource);
|
||||
Assert.True(false, $"Should have shown {severity} message");
|
||||
return null;
|
||||
var className = $"alert-{StatusMessageModel.ToString(severity)}";
|
||||
var el = Driver.FindElement(By.ClassName(className)) ?? Driver.WaitForElement(By.ClassName(className));
|
||||
if (el is null)
|
||||
throw new NoSuchElementException($"Unable to find {className}");
|
||||
return el;
|
||||
}
|
||||
|
||||
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
|
||||
public string Link(string relativeLink)
|
||||
{
|
||||
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
|
||||
@ -93,8 +97,9 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToRegister()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
|
||||
Driver.Navigate().GoToUrl(Link("/register"));
|
||||
}
|
||||
|
||||
public string RegisterNewUser(bool isAdmin = false)
|
||||
{
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
@ -111,34 +116,59 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public (string storeName, string storeId) CreateNewStore()
|
||||
{
|
||||
var usr = "Store" + RandomUtils.GetUInt64().ToString();
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
Driver.FindElement(By.Id("CreateStore")).Click();
|
||||
Driver.FindElement(By.Id("Name")).SendKeys(usr);
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
StoreId = Driver.FindElement(By.Id("Id")).GetAttribute("value");
|
||||
return (usr, StoreId);
|
||||
Driver.WaitForElement(By.Id("Stores")).Click();
|
||||
Driver.WaitForElement(By.Id("CreateStore")).Click();
|
||||
var name = "Store" + RandomUtils.GetUInt64();
|
||||
Driver.WaitForElement(By.Id("Name")).SendKeys(name);
|
||||
Driver.WaitForElement(By.Id("Create")).Click();
|
||||
StoreId = Driver.WaitForElement(By.Id("Id")).GetAttribute("value");
|
||||
return (name, StoreId);
|
||||
}
|
||||
public string StoreId { get; set; }
|
||||
|
||||
public Mnemonic GenerateWallet(string cryptoCode = "BTC", string seed = "", bool importkeys = false, bool privkeys = false, ScriptPubKeyType format = ScriptPubKeyType.Segwit)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Id("import-from-btn")).ForceClick();
|
||||
Driver.FindElement(By.Id("nbxplorergeneratewalletbtn")).ForceClick();
|
||||
Driver.WaitForElement(By.Id("ExistingMnemonic")).SendKeys(seed);
|
||||
SetCheckbox(Driver.WaitForElement(By.Id("SavePrivateKeys")), privkeys);
|
||||
SetCheckbox(Driver.WaitForElement(By.Id("ImportKeysToRPC")), importkeys);
|
||||
Driver.WaitForElement(By.Id("ScriptPubKeyType")).Click();
|
||||
Driver.WaitForElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
|
||||
Logs.Tester.LogInformation("Trying to click btn-generate");
|
||||
Driver.WaitForElement(By.Id("btn-generate")).ForceClick();
|
||||
// Seed backup page
|
||||
AssertHappyMessage();
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
|
||||
|
||||
// Replace previous wallet case
|
||||
if (Driver.PageSource.Contains("id=\"ChangeWalletLink\""))
|
||||
{
|
||||
Driver.FindElement(By.Id("ChangeWalletLink")).Click();
|
||||
Driver.FindElement(By.Id("continue")).Click();
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
|
||||
var option = privkeys ? "Hotwallet" : "Watchonly";
|
||||
Logs.Tester.LogInformation($"Generating new seed ({option})");
|
||||
Driver.FindElement(By.Id("GenerateWalletLink")).Click();
|
||||
Driver.FindElement(By.Id($"Generate{option}Link")).Click();
|
||||
}
|
||||
else
|
||||
{
|
||||
Logs.Tester.LogInformation("Progressing with existing seed");
|
||||
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
|
||||
Driver.FindElement(By.Id("ImportSeedLink")).Click();
|
||||
Driver.FindElement(By.Id("ExistingMnemonic")).SendKeys(seed);
|
||||
Driver.SetCheckbox(By.Id("SavePrivateKeys"), privkeys);
|
||||
}
|
||||
|
||||
Driver.FindElement(By.Id("ScriptPubKeyType")).Click();
|
||||
Driver.FindElement(By.CssSelector($"#ScriptPubKeyType option[value={format}]")).Click();
|
||||
|
||||
// Open advanced settings via JS, because if we click the link it triggers the toggle animation.
|
||||
// This leads to Selenium trying to click the button while it is moving resulting in an error.
|
||||
Driver.ExecuteJavaScript("document.getElementById('AdvancedSettings').classList.add('show')");
|
||||
|
||||
Driver.SetCheckbox(By.Id("ImportKeysToRPC"), importkeys);
|
||||
Driver.FindElement(By.Id("Continue")).Click();
|
||||
|
||||
// Seed backup page
|
||||
FindAlertMessage();
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
{
|
||||
seed = Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
|
||||
}
|
||||
|
||||
// Confirm seed backup
|
||||
Driver.FindElement(By.Id("confirm")).Click();
|
||||
Driver.FindElement(By.Id("submit")).Click();
|
||||
@ -146,38 +176,51 @@ namespace BTCPayServer.Tests
|
||||
WalletId = new WalletId(StoreId, cryptoCode);
|
||||
return new Mnemonic(seed);
|
||||
}
|
||||
public WalletId WalletId { get; set; }
|
||||
|
||||
public void AddDerivationScheme(string cryptoCode = "BTC", string derivationScheme = "xpub661MyMwAqRbcGABgHMUXDzPzH1tU7eZaAaJQXhDXsSxsqyQzQeU6kznNfSuAyqAK9UaWSaZaMFdNiY5BCF4zBPAzSnwfUAwUhwttuAKwfRX-[legacy]")
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.ClassName("store-derivation-scheme")).SendKeys(derivationScheme);
|
||||
Driver.FindElement(By.Id("Continue")).ForceClick();
|
||||
Driver.FindElement(By.Id("Confirm")).ForceClick();
|
||||
AssertHappyMessage();
|
||||
Driver.FindElement(By.Id($"Modify{cryptoCode}")).Click();
|
||||
Driver.FindElement(By.Id("ImportWalletOptionsLink")).Click();
|
||||
Driver.FindElement(By.Id("ImportXpubLink")).Click();
|
||||
Driver.FindElement(By.Id("DerivationScheme")).SendKeys(derivationScheme);
|
||||
Driver.FindElement(By.Id("Continue")).Click();
|
||||
Driver.FindElement(By.Id("Confirm")).Click();
|
||||
FindAlertMessage();
|
||||
}
|
||||
|
||||
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
|
||||
public void AddLightningNode(string cryptoCode = "BTC", LightningConnectionType? connectionType = null)
|
||||
{
|
||||
string connectionString = null;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
connectionString = $"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
connectionString = "type=clightning;server=" + ((CLightningClient)Server.MerchantLightningD).Address.AbsoluteUri;
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
connectionString = $"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).Click();
|
||||
|
||||
var connectionString = connectionType switch
|
||||
{
|
||||
LightningConnectionType.Charge =>
|
||||
$"type=charge;server={Server.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true",
|
||||
LightningConnectionType.CLightning =>
|
||||
$"type=clightning;server={((CLightningClient) Server.MerchantLightningD).Address.AbsoluteUri}",
|
||||
LightningConnectionType.LndREST =>
|
||||
$"type=lnd-rest;server={Server.MerchantLnd.Swagger.BaseUrl};allowinsecure=true",
|
||||
_ => null
|
||||
};
|
||||
|
||||
if (connectionString == null)
|
||||
{
|
||||
Assert.True(Driver.FindElement(By.Id("LightningNodeType-Internal")).Enabled, "Usage of the internal Lightning node is disabled.");
|
||||
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Internal\"]")).Click();
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
{
|
||||
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
|
||||
Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
|
||||
}
|
||||
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
|
||||
Driver.FindElement(By.Id($"save")).ForceClick();
|
||||
}
|
||||
var enabled = Driver.FindElement(By.Id("Enabled"));
|
||||
if (!enabled.Selected) enabled.Click();
|
||||
|
||||
public void AddInternalLightningNode(string cryptoCode)
|
||||
{
|
||||
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
|
||||
Driver.FindElement(By.Id($"internal-ln-node-setter")).ForceClick();
|
||||
Driver.FindElement(By.Id($"save")).ForceClick();
|
||||
Driver.FindElement(By.Id("test")).Click();
|
||||
Assert.Contains("Connection to the Lightning node succeeded.", FindAlertMessage().Text);
|
||||
|
||||
Driver.FindElement(By.Id("save")).Click();
|
||||
}
|
||||
|
||||
public void ClickOnAllSideMenus()
|
||||
@ -193,21 +236,23 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Driver != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
Driver.Close();
|
||||
Driver.Quit();
|
||||
}
|
||||
catch { }
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
Driver.Dispose();
|
||||
}
|
||||
if (Server != null)
|
||||
Server.Dispose();
|
||||
|
||||
Server?.Dispose();
|
||||
}
|
||||
|
||||
internal void AssertNotFound()
|
||||
@ -241,6 +286,7 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Driver.FindElement(By.Id("Stores")).Click();
|
||||
Driver.FindElement(By.Id($"update-store-{storeId}")).Click();
|
||||
|
||||
if (storeNavPage != StoreNavPages.Index)
|
||||
{
|
||||
Driver.FindElement(By.Id(storeNavPage.ToString())).Click();
|
||||
@ -254,33 +300,6 @@ namespace BTCPayServer.Tests
|
||||
CheckForJSErrors();
|
||||
}
|
||||
|
||||
|
||||
public void SetCheckbox(IWebElement element, bool value)
|
||||
{
|
||||
if ((value && !element.Selected) || (!value && element.Selected))
|
||||
{
|
||||
element.Click();
|
||||
}
|
||||
|
||||
if (value != element.Selected)
|
||||
{
|
||||
Logs.Tester.LogInformation("SetCheckbox recursion, trying to click again");
|
||||
SetCheckbox(element, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetCheckbox(SeleniumTester s, string checkboxId, bool value)
|
||||
{
|
||||
SetCheckbox(s.Driver.WaitForElement(By.Id(checkboxId)), value);
|
||||
}
|
||||
|
||||
public void ScrollToElement(IWebElement element)
|
||||
{
|
||||
Actions actions = new Actions(Driver);
|
||||
actions.MoveToElement(element);
|
||||
actions.Perform();
|
||||
}
|
||||
|
||||
public void GoToInvoices()
|
||||
{
|
||||
Driver.FindElement(By.Id("Invoices")).Click();
|
||||
@ -297,19 +316,13 @@ namespace BTCPayServer.Tests
|
||||
|
||||
public void GoToLogin()
|
||||
{
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "Account/Login"));
|
||||
}
|
||||
|
||||
public void GoToCreateInvoicePage()
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "/login"));
|
||||
}
|
||||
|
||||
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
|
||||
{
|
||||
GoToInvoices();
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element
|
||||
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
|
||||
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
|
||||
var currencyEl = Driver.FindElement(By.Id("Currency"));
|
||||
currencyEl.Clear();
|
||||
@ -318,8 +331,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
|
||||
Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
AssertHappyMessage();
|
||||
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
|
||||
var statusElement = FindAlertMessage();
|
||||
var id = statusElement.Text.Split(" ")[1];
|
||||
return id;
|
||||
}
|
||||
@ -331,7 +343,7 @@ namespace BTCPayServer.Tests
|
||||
Driver.FindElement(By.Id("generateButton")).Click();
|
||||
var addressStr = Driver.FindElement(By.Id("address")).GetProperty("value");
|
||||
var address = BitcoinAddress.Create(addressStr, ((BTCPayNetwork)Server.NetworkProvider.GetNetwork(walletId.CryptoCode)).NBitcoinNetwork);
|
||||
for (int i = 0; i < coins; i++)
|
||||
for (var i = 0; i < coins; i++)
|
||||
{
|
||||
await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination));
|
||||
}
|
||||
@ -344,19 +356,15 @@ namespace BTCPayServer.Tests
|
||||
.GetAttribute("href");
|
||||
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
|
||||
|
||||
GoToWallet(walletId, WalletsNavPages.Send);
|
||||
GoToWallet(walletId);
|
||||
Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
Driver.SwitchTo().Alert().Accept();
|
||||
Driver.ScrollTo(By.Id("SendMenu"));
|
||||
Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private void CheckForJSErrors()
|
||||
{
|
||||
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
|
||||
@ -402,7 +410,6 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
Driver.FindElement(By.Id($"Server-{navPages}")).Click();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void GoToInvoice(string id)
|
||||
|
@ -3,37 +3,36 @@ using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.Views.Manage;
|
||||
using BTCPayServer.Views.Server;
|
||||
using BTCPayServer.Views.Stores;
|
||||
using BTCPayServer.Views.Wallets;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OpenQA.Selenium;
|
||||
using OpenQA.Selenium.Support.Extensions;
|
||||
using OpenQA.Selenium.Support.UI;
|
||||
using Org.BouncyCastle.Ocsp;
|
||||
using Renci.SshNet.Security.Cryptography;
|
||||
using Xunit;
|
||||
using Xunit.Abstractions;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace BTCPayServer.Tests
|
||||
{
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public class ChromeTests
|
||||
{
|
||||
public const int TestTimeout = TestUtils.TestTimeout;
|
||||
private const int TestTimeout = TestUtils.TestTimeout;
|
||||
|
||||
public ChromeTests(ITestOutputHelper helper)
|
||||
{
|
||||
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
|
||||
@ -85,12 +84,51 @@ namespace BTCPayServer.Tests
|
||||
Assert.Contains(passEl.Text, "hellorockstar", StringComparison.OrdinalIgnoreCase);
|
||||
s.Driver.FindElement(By.Id("delete")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
seedEl = s.Driver.FindElement(By.Id("SeedTextArea"));
|
||||
Assert.Contains("Seed removed", seedEl.Text, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
public async Task CanChangeUserMail()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
|
||||
var tester = s.Server;
|
||||
var u1 = tester.NewAccount();
|
||||
u1.GrantAccess();
|
||||
await u1.MakeAdmin(false);
|
||||
|
||||
var u2 = tester.NewAccount();
|
||||
u2.GrantAccess();
|
||||
await u2.MakeAdmin(false);
|
||||
|
||||
s.GoToLogin();
|
||||
s.Login(u1.RegisterDetails.Email, u1.RegisterDetails.Password);
|
||||
s.GoToProfile(ManageNavPages.Index);
|
||||
s.Driver.FindElement(By.Id("Email")).Clear();
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(u2.RegisterDetails.Email);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
|
||||
|
||||
s.GoToProfile(ManageNavPages.Index);
|
||||
s.Driver.FindElement(By.Id("Email")).Clear();
|
||||
var changedEmail = Guid.NewGuid() + "@lol.com";
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(changedEmail);
|
||||
s.Driver.FindElement(By.Id("save")).Click();
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
|
||||
|
||||
var manager = tester.PayTester.GetService<UserManager<ApplicationUser>>();
|
||||
Assert.NotNull(await manager.FindByNameAsync(changedEmail));
|
||||
Assert.NotNull(await manager.FindByEmailAsync(changedEmail));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task NewUserLogin()
|
||||
{
|
||||
@ -101,9 +139,7 @@ namespace BTCPayServer.Tests
|
||||
var email = s.RegisterNewUser();
|
||||
s.Logout();
|
||||
s.Driver.AssertNoError();
|
||||
Assert.Contains("Account/Login", s.Driver.Url);
|
||||
// Should show the Tor address
|
||||
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
|
||||
Assert.Contains("/login", s.Driver.Url);
|
||||
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
|
||||
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
|
||||
@ -143,15 +179,15 @@ namespace BTCPayServer.Tests
|
||||
//let's test invite link
|
||||
s.Logout();
|
||||
s.GoToRegister();
|
||||
var newAdminUser = s.RegisterNewUser(true);
|
||||
s.RegisterNewUser(true);
|
||||
s.GoToServer(ServerNavPages.Users);
|
||||
s.Driver.FindElement(By.Id("CreateUser")).Click();
|
||||
|
||||
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.Driver.FindElement(By.Id("Save")).Click();
|
||||
var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text;
|
||||
;
|
||||
var url = s.FindAlertMessage().FindElement(By.TagName("a")).Text;
|
||||
|
||||
s.Logout();
|
||||
s.Driver.Navigate().GoToUrl(url);
|
||||
Assert.Equal("hidden", s.Driver.FindElement(By.Id("Email")).GetAttribute("type"));
|
||||
@ -160,7 +196,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("ConfirmPassword")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("SetPassword")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
@ -170,23 +206,16 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
static void LogIn(SeleniumTester s, string email)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(email);
|
||||
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
|
||||
s.Driver.FindElement(By.Id("LoginButton")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
}
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseSSHService()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("server/services/ssh", s.Driver.PageSource);
|
||||
using (var client = await s.Server.PayTester.GetService<BTCPayServer.Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
|
||||
using (var client = await s.Server.PayTester.GetService<Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
|
||||
{
|
||||
var result = await client.RunBash("echo hello");
|
||||
Assert.Equal(string.Empty, result.Error);
|
||||
@ -197,7 +226,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.AssertNoError();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).SendKeys("tes't\r\ntest2");
|
||||
s.Driver.FindElement(By.Id("submit")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("submit")).Click();
|
||||
s.Driver.AssertNoError();
|
||||
|
||||
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
@ -207,7 +236,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(s.Driver.PageSource.Contains("authorized_keys has been updated", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
s.Driver.FindElement(By.Id("SSHKeyFileContent")).Clear();
|
||||
s.Driver.FindElement(By.Id("submit")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("submit")).Click();
|
||||
|
||||
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
|
||||
Assert.DoesNotContain("test2", text);
|
||||
@ -220,12 +249,12 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/emails"));
|
||||
if (s.Driver.PageSource.Contains("Configured"))
|
||||
{
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
}
|
||||
CanSetupEmailCore(s);
|
||||
s.CreateNewStore();
|
||||
@ -234,33 +263,13 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanSetupEmailCore(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
|
||||
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.AssertHappyMessage();
|
||||
Assert.DoesNotContain("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanUseDynamicDns()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser(isAdmin: true);
|
||||
s.RegisterNewUser(isAdmin: true);
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
|
||||
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
|
||||
|
||||
@ -305,13 +314,15 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Lightning", "Lightning")]
|
||||
public async Task CanCreateStores()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
s.Server.ActivateLightning();
|
||||
await s.StartAsync();
|
||||
var alice = s.RegisterNewUser();
|
||||
var storeData = s.CreateNewStore();
|
||||
var alice = s.RegisterNewUser(true);
|
||||
var (storeName, storeId) = s.CreateNewStore();
|
||||
var onchainHint = "Set up your wallet to receive payments at your store.";
|
||||
var offchainHint = "A connection to a Lightning node is required to receive Lightning payments.";
|
||||
|
||||
@ -320,39 +331,47 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint not present");
|
||||
|
||||
s.GoToStores();
|
||||
Assert.True(s.Driver.PageSource.Contains("warninghint_" + storeData.storeId),
|
||||
"Warning hint on list not present");
|
||||
Assert.True(s.Driver.PageSource.Contains($"warninghint_{storeId}"), "Warning hint on list not present");
|
||||
|
||||
s.GoToStore(storeData.storeId);
|
||||
s.GoToStore(storeId);
|
||||
Assert.Contains(storeName, s.Driver.PageSource);
|
||||
Assert.True(s.Driver.PageSource.Contains(onchainHint), "Wallet hint should be present at this point");
|
||||
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be present at this point");
|
||||
|
||||
s.AddDerivationScheme(); // wallet hint should be dismissed
|
||||
// setup onchain wallet
|
||||
s.GoToStore(storeId);
|
||||
s.AddDerivationScheme();
|
||||
s.Driver.AssertNoError();
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint),
|
||||
"Wallet hint not dismissed on derivation scheme add");// dismiss lightning hint
|
||||
Assert.False(s.Driver.PageSource.Contains(onchainHint), "Wallet hint not dismissed on derivation scheme add");
|
||||
|
||||
// setup offchain wallet
|
||||
s.GoToStore(storeId);
|
||||
s.AddLightningNode();
|
||||
s.Driver.AssertNoError();
|
||||
var successAlert = s.FindAlertMessage();
|
||||
Assert.Contains("BTC Lightning node modified.", successAlert.Text);
|
||||
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
|
||||
|
||||
Assert.Contains(storeData.storeName, s.Driver.PageSource);
|
||||
var storeUrl = s.Driver.Url;
|
||||
s.ClickOnAllSideMenus();
|
||||
s.GoToInvoices();
|
||||
var invoiceId = s.CreateInvoice(storeData.storeName);
|
||||
s.AssertHappyMessage();
|
||||
var invoiceId = s.CreateInvoice(storeName);
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.ClassName("invoice-details-link")).Click();
|
||||
var invoiceUrl = s.Driver.Url;
|
||||
|
||||
//let's test archiving an invoice
|
||||
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
s.AssertHappyMessage();
|
||||
Assert.Contains("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
//check that it no longer appears in list
|
||||
s.GoToInvoices();
|
||||
|
||||
Assert.DoesNotContain(invoiceId, s.Driver.PageSource);
|
||||
//ok, let's unarchive and see that it shows again
|
||||
s.Driver.Navigate().GoToUrl(invoiceUrl);
|
||||
s.Driver.FindElement(By.Id("btn-archive-toggle")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
|
||||
s.GoToInvoices();
|
||||
Assert.Contains(invoiceId, s.Driver.PageSource);
|
||||
@ -374,14 +393,14 @@ namespace BTCPayServer.Tests
|
||||
s.Logout();
|
||||
|
||||
// Let's add Bob as a guest to alice's store
|
||||
LogIn(s, alice);
|
||||
s.LogIn(alice);
|
||||
s.Driver.Navigate().GoToUrl(storeUrl + "/users");
|
||||
s.Driver.FindElement(By.Id("Email")).SendKeys(bob + Keys.Enter);
|
||||
Assert.Contains("User added successfully", s.Driver.PageSource);
|
||||
s.Logout();
|
||||
|
||||
// Bob should not have access to store, but should have access to invoice
|
||||
LogIn(s, bob);
|
||||
s.LogIn(bob);
|
||||
s.Driver.Navigate().GoToUrl(storeUrl);
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
s.Driver.Navigate().GoToUrl(invoiceUrl);
|
||||
@ -389,12 +408,8 @@ namespace BTCPayServer.Tests
|
||||
|
||||
// Alice should be able to delete the store
|
||||
s.Logout();
|
||||
LogIn(s, alice);
|
||||
s.LogIn(alice);
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
|
||||
// there shouldn't be any hints now
|
||||
Assert.False(s.Driver.PageSource.Contains(offchainHint), "Lightning hint should be dismissed at this point");
|
||||
|
||||
s.Driver.FindElement(By.LinkText("Remove")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.Driver.FindElement(By.Id("Stores")).Click();
|
||||
@ -412,17 +427,17 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
|
||||
Assert.Contains("ReturnUrl", s.Driver.Url);
|
||||
s.GoToRegister();
|
||||
var alice = s.RegisterNewUser();
|
||||
var store = s.CreateNewStore().storeName;
|
||||
s.RegisterNewUser();
|
||||
s.CreateNewStore();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("Tokens")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
|
||||
s.Driver.FindElement(By.Id("RequestPairing")).Click();
|
||||
string pairingCode = AssertUrlHasPairingCode(s);
|
||||
var pairingCode = AssertUrlHasPairingCode(s);
|
||||
|
||||
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
Assert.Contains(pairingCode, s.Driver.PageSource);
|
||||
|
||||
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
|
||||
@ -454,15 +469,6 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static string AssertUrlHasPairingCode(SeleniumTester s)
|
||||
{
|
||||
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
|
||||
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
|
||||
var pairingCode = regex.Groups[1].Value;
|
||||
return pairingCode;
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanCreateAppPoS()
|
||||
{
|
||||
@ -470,57 +476,62 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
var (storeName, _) = s.CreateNewStore();
|
||||
|
||||
s.Driver.FindElement(By.Id("Apps")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("PoS" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("PointOfSale");
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart");
|
||||
s.Driver.FindElement(By.CssSelector(".template-item:nth-of-type(1) .btn-primary")).Click();
|
||||
s.Driver.FindElement(By.Id("BuyButtonText")).SendKeys("Take my money");
|
||||
s.Driver.FindElement(By.Id("SaveItemChanges")).Click();
|
||||
s.Driver.FindElement(By.Id("ToggleRawEditor")).Click();
|
||||
|
||||
var template = s.Driver.FindElement(By.Id("Template")).GetAttribute("value");
|
||||
Assert.Contains("buyButtonText: Take my money", template);
|
||||
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
|
||||
var posBaseUrl = s.Driver.Url.Replace("/Cart", "");
|
||||
Assert.True(s.Driver.PageSource.Contains("Tea shop"), "Unable to create PoS");
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "PoS not showing correct default view");
|
||||
Assert.True(s.Driver.PageSource.Contains("Take my money"), "PoS not showing correct default view");
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/static";
|
||||
Assert.False(s.Driver.PageSource.Contains("Cart"), "Static PoS not showing correct view");
|
||||
|
||||
s.Driver.Url = posBaseUrl + "/cart";
|
||||
Assert.True(s.Driver.PageSource.Contains("Cart"), "Cart PoS not showing correct view");
|
||||
|
||||
s.Driver.Quit();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanCreateAppCF()
|
||||
public async Task CanCreateCrowdfundingApp()
|
||||
{
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser();
|
||||
var store = s.CreateNewStore();
|
||||
var (storeName, _) = s.CreateNewStore();
|
||||
s.AddDerivationScheme();
|
||||
|
||||
s.Driver.FindElement(By.Id("Apps")).Click();
|
||||
s.Driver.FindElement(By.Id("CreateNewApp")).Click();
|
||||
s.Driver.FindElement(By.Name("Name")).SendKeys("CF" + Guid.NewGuid());
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(store + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SelectedAppType")).SendKeys("Crowdfund");
|
||||
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Kukkstarter");
|
||||
s.Driver.FindElement(By.CssSelector("div.note-editable.card-block")).SendKeys("1BTC = 1BTC");
|
||||
s.Driver.FindElement(By.Id("TargetCurrency")).SendKeys("JPY");
|
||||
s.Driver.FindElement(By.Id("TargetAmount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.True(s.Driver.PageSource.Contains("Currently Active!"), "Unable to create CF");
|
||||
s.Driver.Quit();
|
||||
s.Driver.FindElement(By.Id("SaveSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("ViewApp")).Click();
|
||||
Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,11 +550,10 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("Title")).SendKeys("Pay123");
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("700");
|
||||
s.Driver.FindElement(By.Id("Currency")).SendKeys("BTC");
|
||||
s.Driver.FindElement(By.Id("SaveButton")).ForceClick();
|
||||
s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return);
|
||||
s.Driver.FindElement(By.Id("SaveButton")).Click();
|
||||
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
|
||||
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
|
||||
Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request");
|
||||
s.Driver.Quit();
|
||||
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
|
||||
}
|
||||
}
|
||||
|
||||
@ -554,8 +564,8 @@ namespace BTCPayServer.Tests
|
||||
using (var s = SeleniumTester.Create())
|
||||
{
|
||||
await s.StartAsync();
|
||||
var userId = s.RegisterNewUser(true);
|
||||
var storeId = s.CreateNewStore().storeId;
|
||||
s.RegisterNewUser(true);
|
||||
var (_, storeId) = s.CreateNewStore();
|
||||
s.GenerateWallet("BTC", "", false, true);
|
||||
var walletId = new WalletId(storeId, "BTC");
|
||||
s.GoToWallet(walletId, WalletsNavPages.Receive);
|
||||
@ -576,16 +586,17 @@ namespace BTCPayServer.Tests
|
||||
var x = store.GetSupportedPaymentMethods(s.Server.NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.Single(settings => settings.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
var wallet = s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode);
|
||||
wallet.InvalidateCache(x.AccountDerivation);
|
||||
Assert.Contains(
|
||||
await s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
|
||||
.GetUnspentCoins(x.AccountDerivation),
|
||||
await wallet.GetUnspentCoins(x.AccountDerivation),
|
||||
coin => coin.OutPoint == spentOutpoint);
|
||||
});
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
s.GoToWallet(walletId, WalletsNavPages.Send);
|
||||
s.GoToWallet(walletId);
|
||||
s.Driver.FindElement(By.Id("advancedSettings")).Click();
|
||||
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
|
||||
s.Driver.WaitForElement(By.Id(spentOutpoint.ToString()));
|
||||
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
|
||||
s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
||||
Assert.Equal("true", s.Driver.FindElement(By.Name("InputSelection")).GetAttribute("value").ToLowerInvariant());
|
||||
var el = s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
|
||||
s.Driver.FindElement(By.Id(spentOutpoint.ToString())).Click();
|
||||
@ -596,8 +607,8 @@ namespace BTCPayServer.Tests
|
||||
SetTransactionOutput(s, 0, bob, 0.3m);
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.Id("spendWithNBxplorer")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
var happyElement = s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
var happyElement = s.FindAlertMessage();
|
||||
var happyText = happyElement.Text;
|
||||
var txid = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;
|
||||
|
||||
@ -614,11 +625,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var store = s.CreateNewStore();
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
var (storeName, storeId) = s.CreateNewStore();
|
||||
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
|
||||
Logs.Tester.LogInformation("Let's create two webhooks");
|
||||
for (int i = 0; i < 2; i++)
|
||||
for (var i = 0; i < 2; i++)
|
||||
{
|
||||
s.Driver.FindElement(By.Id("CreateWebhook")).Click();
|
||||
s.Driver.FindElement(By.Name("PayloadUrl")).SendKeys($"http://127.0.0.1/callback{i}");
|
||||
@ -636,7 +647,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
deletes = s.Driver.FindElements(By.LinkText("Delete"));
|
||||
Assert.Single(deletes);
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
|
||||
Logs.Tester.LogInformation("Let's try to update one of them");
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
@ -648,7 +659,7 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Name("Secret")).Clear();
|
||||
s.Driver.FindElement(By.Name("Secret")).SendKeys("HelloWorld");
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
|
||||
{
|
||||
@ -664,13 +675,13 @@ namespace BTCPayServer.Tests
|
||||
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Name("update")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can generate an event");
|
||||
s.GoToStore(store.storeId);
|
||||
s.GoToStore(storeId);
|
||||
s.AddDerivationScheme();
|
||||
s.CreateInvoice(store.storeName);
|
||||
s.CreateInvoice(storeName);
|
||||
var request = await server.GetNextRequest();
|
||||
var headers = request.Request.Headers;
|
||||
var actualSig = headers["BTCPay-Sig"].First();
|
||||
@ -681,21 +692,22 @@ namespace BTCPayServer.Tests
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's make a failed event");
|
||||
s.CreateInvoice(store.storeName);
|
||||
s.CreateInvoice(storeName);
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
// The delivery is done asynchronously, so small wait here
|
||||
await Task.Delay(500);
|
||||
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
|
||||
s.Driver.FindElement(By.LinkText("Modify")).Click();
|
||||
var elements = s.Driver.FindElements(By.ClassName("redeliver"));
|
||||
// One worked, one failed
|
||||
s.Driver.FindElement(By.ClassName("fa-times"));
|
||||
s.Driver.FindElement(By.ClassName("fa-check"));
|
||||
elements[0].Click();
|
||||
s.AssertHappyMessage();
|
||||
|
||||
s.FindAlertMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
@ -708,32 +720,23 @@ namespace BTCPayServer.Tests
|
||||
CanBrowseContent(s);
|
||||
var element = s.Driver.FindElement(By.ClassName("redeliver"));
|
||||
element.Click();
|
||||
s.AssertHappyMessage();
|
||||
|
||||
s.FindAlertMessage();
|
||||
request = await server.GetNextRequest();
|
||||
request.Response.StatusCode = 404;
|
||||
server.Done();
|
||||
|
||||
Logs.Tester.LogInformation("Let's see if we can delete store with some webhooks inside");
|
||||
s.GoToStore(store.storeId);
|
||||
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
|
||||
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
|
||||
s.GoToStore(storeId);
|
||||
// Open danger zone via JS, because if we click the link it triggers the toggle animation.
|
||||
// This leads to Selenium trying to click the button while it is moving resulting in an error.
|
||||
s.Driver.ExecuteJavaScript("document.getElementById('danger-zone').classList.add('show')");
|
||||
s.Driver.FindElement(By.Id("delete-store")).Click();
|
||||
s.Driver.FindElement(By.Id("continue")).Click();
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(windows[0]);
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
public async Task CanManageWallet()
|
||||
{
|
||||
@ -741,20 +744,19 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var storeId = s.CreateNewStore();
|
||||
var (storeName, storeId) = s.CreateNewStore();
|
||||
|
||||
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0', then try to use the seed
|
||||
// to sign the transaction
|
||||
s.GenerateWallet("BTC", "", true, false);
|
||||
// In this test, we try to spend from a manual seed. We import the xpub 49'/0'/0',
|
||||
// then try to use the seed to sign the transaction
|
||||
s.GenerateWallet("BTC", "", true);
|
||||
|
||||
//let's test quickly the receive wallet page
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
//you cant use the Sign with NBX option without saving private keys when generating the wallet.
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
|
||||
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
|
||||
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
|
||||
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
@ -764,17 +766,16 @@ namespace BTCPayServer.Tests
|
||||
var receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
|
||||
//unreserve
|
||||
s.Driver.FindElement(By.CssSelector("button[value=unreserve-current-address]")).Click();
|
||||
//generate it again, should be the same one as before as nothign got used in the meantime
|
||||
//generate it again, should be the same one as before as nothing got used in the meantime
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
Assert.True(s.Driver.FindElement(By.ClassName("qr-container")).Displayed);
|
||||
Assert.Equal(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
|
||||
//send money to addr and ensure it changed
|
||||
var sess = await s.Server.ExplorerClient.CreateWebsocketNotificationSessionAsync();
|
||||
sess.ListenAllTrackedSource();
|
||||
await sess.ListenAllTrackedSourceAsync();
|
||||
var nextEvent = sess.NextEventAsync();
|
||||
s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(receiveAddr, Network.RegTest),
|
||||
Money.Parse("0.1"));
|
||||
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest), Money.Parse("0.1"));
|
||||
await nextEvent;
|
||||
await Task.Delay(200);
|
||||
s.Driver.Navigate().Refresh();
|
||||
@ -783,34 +784,35 @@ namespace BTCPayServer.Tests
|
||||
receiveAddr = s.Driver.FindElement(By.Id("address")).GetAttribute("value");
|
||||
|
||||
//change the wallet and ensure old address is not there and generating a new one does not result in the prev one
|
||||
s.GoToStore(storeId.storeId);
|
||||
s.GenerateWallet("BTC", "", true, false);
|
||||
s.GoToStore(storeId);
|
||||
s.GenerateWallet("BTC", "", true);
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
s.Driver.FindElement(By.Id("WalletReceive")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=generate-new-address]")).Click();
|
||||
|
||||
Assert.NotEqual(receiveAddr, s.Driver.FindElement(By.Id("address")).GetAttribute("value"));
|
||||
|
||||
var invoiceId = s.CreateInvoice(storeId.storeName);
|
||||
var invoiceId = s.CreateInvoice(storeName);
|
||||
var invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
var address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
|
||||
//wallet should have been imported to bitcoin core wallet in watch only mode.
|
||||
var result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
Assert.True(result.IsWatchOnly);
|
||||
s.GoToStore(storeId.storeId);
|
||||
s.GoToStore(storeId);
|
||||
var mnemonic = s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
//lets import and save private keys
|
||||
var root = mnemonic.DeriveExtKey();
|
||||
invoiceId = s.CreateInvoice(storeId.storeName);
|
||||
invoiceId = s.CreateInvoice(storeName);
|
||||
invoice = await s.Server.PayTester.InvoiceRepository.GetInvoice(invoiceId);
|
||||
address = invoice.EntityToDTO().Addresses["BTC"];
|
||||
result = await s.Server.ExplorerNode.GetAddressInfoAsync(BitcoinAddress.Create(address, Network.RegTest));
|
||||
//spendable from bitcoin core wallet!
|
||||
Assert.False(result.IsWatchOnly);
|
||||
var tx = s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(address, Network.RegTest), Money.Coins(3.0m));
|
||||
s.Server.ExplorerNode.Generate(1);
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
|
||||
s.Driver.FindElement(By.Id("Wallets")).Click();
|
||||
s.Driver.FindElement(By.LinkText("Manage")).Click();
|
||||
@ -818,16 +820,17 @@ namespace BTCPayServer.Tests
|
||||
s.ClickOnAllSideMenus();
|
||||
|
||||
// Make sure we can rescan, because we are admin!
|
||||
s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("WalletRescan")).Click();
|
||||
Assert.Contains("The batch size make sure", s.Driver.PageSource);
|
||||
|
||||
// We setup the fingerprint and the account key path
|
||||
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("WalletSettings")).Click();
|
||||
// s.Driver.FindElement(By.Id("AccountKeys_0__MasterFingerprint")).SendKeys("8bafd160");
|
||||
// s.Driver.FindElement(By.Id("AccountKeys_0__AccountKeyPath")).SendKeys("m/49'/0'/0'" + Keys.Enter);
|
||||
|
||||
// Check the tx sent earlier arrived
|
||||
s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("WalletTransactions")).Click();
|
||||
|
||||
var walletTransactionLink = s.Driver.Url;
|
||||
Assert.Contains(tx.ToString(), s.Driver.PageSource);
|
||||
|
||||
@ -838,17 +841,16 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("WalletSend")).Click();
|
||||
var bob = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(s, 0, bob, 1);
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
|
||||
|
||||
// Input the seed
|
||||
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
|
||||
|
||||
// Broadcast
|
||||
Assert.Contains(bob.ToString(), s.Driver.PageSource);
|
||||
Assert.Contains("1.00000000", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||
}
|
||||
|
||||
@ -860,18 +862,17 @@ namespace BTCPayServer.Tests
|
||||
|
||||
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
|
||||
SetTransactionOutput(s, 0, jack, 0.01m);
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
Assert.Contains(jack.ToString(), s.Driver.PageSource);
|
||||
Assert.Contains("0.01000000", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).Click();
|
||||
Assert.EndsWith("psbt", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("#OtherActions")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.EndsWith("psbt/ready", s.Driver.Url);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
Assert.Equal(walletTransactionLink, s.Driver.Url);
|
||||
|
||||
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
|
||||
@ -884,18 +885,18 @@ namespace BTCPayServer.Tests
|
||||
s.Driver.FindElement(By.Id("bip21parse")).Click();
|
||||
s.Driver.SwitchTo().Alert().SendKeys(bip21);
|
||||
s.Driver.SwitchTo().Alert().Accept();
|
||||
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Info);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
|
||||
Assert.Equal(parsedBip21.Amount.ToString(false), s.Driver.FindElement(By.Id($"Outputs_0__Amount")).GetAttribute("value"));
|
||||
Assert.Equal(parsedBip21.Address.ToString(), s.Driver.FindElement(By.Id($"Outputs_0__DestinationAddress")).GetAttribute("value"));
|
||||
|
||||
s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
|
||||
s.GoToWallet(new WalletId(storeId, "BTC"), WalletsNavPages.Settings);
|
||||
var walletUrl = s.Driver.Url;
|
||||
|
||||
s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("SettingsMenu")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
|
||||
|
||||
// Seed backup page
|
||||
var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
|
||||
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
|
||||
Assert.Equal(mnemonic.ToString(), recoveryPhrase);
|
||||
Assert.Contains("The recovery phrase will also be stored on the server as a hot wallet.", s.Driver.PageSource);
|
||||
|
||||
@ -905,18 +906,6 @@ namespace BTCPayServer.Tests
|
||||
Assert.Equal(walletUrl, s.Driver.Url);
|
||||
}
|
||||
}
|
||||
void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
|
||||
{
|
||||
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
|
||||
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
|
||||
amountElement.Clear();
|
||||
amountElement.SendKeys(amount.ToString());
|
||||
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
|
||||
if (checkboxElement.Selected != subtract)
|
||||
{
|
||||
checkboxElement.Click();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Selenium", "Selenium")]
|
||||
@ -926,45 +915,47 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
await s.StartAsync();
|
||||
s.RegisterNewUser(true);
|
||||
var receiver = s.CreateNewStore();
|
||||
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
|
||||
s.CreateNewStore();
|
||||
s.GenerateWallet("BTC", "", true, true);
|
||||
|
||||
await s.Server.ExplorerNode.GenerateAsync(1);
|
||||
await s.FundStoreWallet(denomination: 50.0m);
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP1");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0");;
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
Thread.Sleep(1000);
|
||||
s.GoToWallet(navPages: WalletsNavPages.PullPayments);
|
||||
|
||||
s.Driver.FindElement(By.Id("NewPullPayment")).Click();
|
||||
s.Driver.FindElement(By.Id("Name")).SendKeys("PP2");
|
||||
s.Driver.FindElement(By.Id("Amount")).Clear();
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0" + Keys.Enter);
|
||||
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0");
|
||||
s.Driver.FindElement(By.Id("Create")).Click();
|
||||
|
||||
// This should select the first View, ie, the last one PP2
|
||||
s.Driver.FindElement(By.LinkText("View")).Click();
|
||||
|
||||
Thread.Sleep(1000);
|
||||
var address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("15" + Keys.Enter);
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
|
||||
// We should not be able to use an address already used
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Error);
|
||||
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
|
||||
|
||||
address = await s.Server.ExplorerNode.GetNewAddressAsync();
|
||||
s.Driver.FindElement(By.Id("Destination")).Clear();
|
||||
s.Driver.FindElement(By.Id("Destination")).SendKeys(address.ToString());
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).Clear();
|
||||
s.Driver.FindElement(By.Id("ClaimedAmount")).SendKeys("20" + Keys.Enter);
|
||||
s.AssertHappyMessage();
|
||||
s.FindAlertMessage();
|
||||
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
|
||||
|
||||
var viewPullPaymentUrl = s.Driver.Url;
|
||||
@ -982,11 +973,11 @@ namespace BTCPayServer.Tests
|
||||
Assert.DoesNotContain("No payout waiting for approval", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("selectAllCheckbox")).Click();
|
||||
s.Driver.FindElement(By.Id("payCommand")).Click();
|
||||
s.Driver.ScrollTo(By.Id("SendMenu"));
|
||||
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
|
||||
s.Driver.FindElement(By.Id("SendMenu")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
|
||||
s.AssertHappyMessage();
|
||||
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
|
||||
|
||||
s.FindAlertMessage();
|
||||
|
||||
TestUtils.Eventually(() =>
|
||||
{
|
||||
@ -1027,5 +1018,57 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private static void CanBrowseContent(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
|
||||
var windows = s.Driver.WindowHandles;
|
||||
Assert.Equal(2, windows.Count);
|
||||
s.Driver.SwitchTo().Window(windows[1]);
|
||||
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
|
||||
s.Driver.Close();
|
||||
s.Driver.SwitchTo().Window(windows[0]);
|
||||
}
|
||||
|
||||
private static void CanSetupEmailCore(SeleniumTester s)
|
||||
{
|
||||
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
|
||||
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
s.FindAlertMessage();
|
||||
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
|
||||
Assert.Contains("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
|
||||
s.FindAlertMessage();
|
||||
Assert.DoesNotContain("Configured", s.Driver.PageSource);
|
||||
Assert.Contains("test_fix", s.Driver.PageSource);
|
||||
}
|
||||
|
||||
private static string AssertUrlHasPairingCode(SeleniumTester s)
|
||||
{
|
||||
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
|
||||
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
|
||||
var pairingCode = regex.Groups[1].Value;
|
||||
return pairingCode;
|
||||
}
|
||||
|
||||
private void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
|
||||
{
|
||||
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
|
||||
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
|
||||
amountElement.Clear();
|
||||
amountElement.SendKeys(amount.ToString(CultureInfo.InvariantCulture));
|
||||
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
|
||||
if (checkboxElement.Selected != subtract)
|
||||
{
|
||||
checkboxElement.Click();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,8 @@ using System.Net.Http;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Tests.Lnd;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using NBitcoin;
|
||||
@ -33,7 +35,7 @@ namespace BTCPayServer.Tests
|
||||
if (!Directory.Exists(_Directory))
|
||||
Directory.CreateDirectory(_Directory);
|
||||
|
||||
NetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
NetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
ExplorerNode = new RPCClient(RPCCredentialString.Parse(GetEnvironment("TESTS_BTCRPCCONNECTION", "server=http://127.0.0.1:43782;ceiwHEbqWI83:DwubwWsoo3")), NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork);
|
||||
ExplorerNode.ScanRPCCapabilities();
|
||||
|
||||
@ -86,14 +88,50 @@ namespace BTCPayServer.Tests
|
||||
|
||||
#endif
|
||||
public void ActivateLightning()
|
||||
{
|
||||
ActivateLightning(LightningConnectionType.Charge);
|
||||
}
|
||||
public void ActivateLightning(LightningConnectionType internalNode)
|
||||
{
|
||||
var btc = NetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
|
||||
CustomerLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_CUSTOMERLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30992/"), btc);
|
||||
MerchantLightningD = LightningClientFactory.CreateClient(GetEnvironment("TEST_MERCHANTLIGHTNINGD", "type=clightning;server=tcp://127.0.0.1:30993/"), btc);
|
||||
MerchantCharge = new ChargeTester(this, "TEST_MERCHANTCHARGE", "type=charge;server=http://127.0.0.1:54938/;api-token=foiewnccewuify;allowinsecure=true", "merchant_lightningd", btc);
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
|
||||
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
|
||||
PayTester.UseLightning = true;
|
||||
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
|
||||
PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true);
|
||||
}
|
||||
public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant)
|
||||
{
|
||||
string connectionString = null;
|
||||
if (connectionType is null)
|
||||
return LightningSupportedPaymentMethod.InternalNode;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=charge;server={MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)MerchantLightningD).Address.AbsoluteUri;
|
||||
else
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)CustomerLightningD).Address.AbsoluteUri;
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=lnd-rest;server={MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
public bool Dockerized
|
||||
|
@ -10,7 +10,6 @@ using BTCPayServer.Storage.Services.Providers.AzureBlobStorage.Configuration;
|
||||
using BTCPayServer.Storage.Services.Providers.FileSystemStorage.Configuration;
|
||||
using BTCPayServer.Storage.ViewModels;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using DBriize.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Xunit;
|
||||
@ -212,10 +211,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.NotNull(statusMessageModel);
|
||||
Assert.Equal(StatusMessageModel.StatusSeverity.Success, statusMessageModel.Severity);
|
||||
var index = statusMessageModel.Html.IndexOf("target='_blank'>");
|
||||
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
|
||||
{
|
||||
{"</a>", string.Empty}, {"target='_blank'>", string.Empty}
|
||||
});
|
||||
var url = statusMessageModel.Html.Substring(index)
|
||||
.Replace("</a>", string.Empty)
|
||||
.Replace("target='_blank'>", string.Empty);
|
||||
//verify tmpfile is available and the same
|
||||
data = await net.DownloadStringTaskAsync(new Uri(url));
|
||||
Assert.Equal(fileContent, data);
|
||||
|
@ -14,6 +14,7 @@ using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Lightning.CLightning;
|
||||
using BTCPayServer.Models.AccountViewModels;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
@ -22,6 +23,8 @@ using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.CodeAnalysis.Operations;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using NBitcoin.Payment;
|
||||
using NBitpayClient;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
@ -171,7 +174,7 @@ namespace BTCPayServer.Tests
|
||||
}
|
||||
|
||||
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy,
|
||||
bool importKeysToNBX = false)
|
||||
bool importKeysToNBX = false, bool importsKeysToBitcoinCore = false)
|
||||
{
|
||||
if (StoreId is null)
|
||||
await CreateStoreAsync();
|
||||
@ -181,10 +184,13 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
ScriptPubKeyType = segwit,
|
||||
SavePrivateKeys = importKeysToNBX,
|
||||
ImportKeysToRPC = importsKeysToBitcoinCore
|
||||
});
|
||||
await store.AddDerivationScheme(StoreId,
|
||||
new DerivationSchemeViewModel()
|
||||
await store.UpdateWallet(
|
||||
new WalletSetupViewModel
|
||||
{
|
||||
StoreId = StoreId,
|
||||
Method = importKeysToNBX ? WalletSetupMethod.HotWallet : WalletSetupMethod.WatchOnly,
|
||||
Enabled = true,
|
||||
CryptoCode = cryptoCode,
|
||||
Network = SupportedNetwork,
|
||||
@ -196,7 +202,7 @@ namespace BTCPayServer.Tests
|
||||
KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(),
|
||||
DerivationScheme = DerivationScheme.ToString(),
|
||||
Confirmation = true
|
||||
}, cryptoCode);
|
||||
});
|
||||
return new WalletId(StoreId, cryptoCode);
|
||||
}
|
||||
|
||||
@ -256,40 +262,19 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true, string storeId = null)
|
||||
public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null)
|
||||
{
|
||||
var storeController = this.GetController<StoresController>();
|
||||
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
|
||||
}
|
||||
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
|
||||
{
|
||||
var storeController = GetController<StoresController>();
|
||||
|
||||
string connectionString = null;
|
||||
if (connectionType == LightningConnectionType.Charge)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=charge;server={parent.MerchantCharge.Client.Uri.AbsoluteUri};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.CLightning)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.MerchantLightningD).Address.AbsoluteUri;
|
||||
else
|
||||
connectionString = "type=clightning;server=" +
|
||||
((CLightningClient)parent.CustomerLightningD).Address.AbsoluteUri;
|
||||
}
|
||||
else if (connectionType == LightningConnectionType.LndREST)
|
||||
{
|
||||
if (isMerchant)
|
||||
connectionString = $"type=lnd-rest;server={parent.MerchantLnd.Swagger.BaseUrl};allowinsecure=true";
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException(connectionType.ToString());
|
||||
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
|
||||
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
|
||||
await storeController.AddLightningNode(storeId ?? StoreId,
|
||||
new LightningNodeViewModel() {ConnectionString = connectionString, SkipPortTest = true}, "save", "BTC");
|
||||
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", "BTC");
|
||||
if (storeController.ModelState.ErrorCount != 0)
|
||||
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
|
||||
}
|
||||
@ -358,7 +343,7 @@ namespace BTCPayServer.Tests
|
||||
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
|
||||
if (expectedError is null && !senderError)
|
||||
{
|
||||
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
|
||||
var proposed = await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default);
|
||||
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
|
||||
Assert.NotNull(proposed);
|
||||
return proposed;
|
||||
@ -367,11 +352,11 @@ namespace BTCPayServer.Tests
|
||||
{
|
||||
if (senderError)
|
||||
{
|
||||
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
|
||||
}
|
||||
else
|
||||
{
|
||||
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
|
||||
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
|
||||
var split = expectedError.Split('|');
|
||||
Assert.Equal(split[0], ex.ErrorCode);
|
||||
if (split.Length > 1)
|
||||
|
@ -32,6 +32,7 @@ using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Bitcoin;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Security.Bitpay;
|
||||
using BTCPayServer.Services;
|
||||
@ -43,7 +44,6 @@ using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Tests.Logging;
|
||||
using BTCPayServer.U2F.Models;
|
||||
using BTCPayServer.Validation;
|
||||
using DBriize.Utils;
|
||||
using ExchangeSharp;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@ -263,21 +263,12 @@ namespace BTCPayServer.Tests
|
||||
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
|
||||
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
|
||||
|
||||
description = description.ReplaceMultiple(new Dictionary<string, string>()
|
||||
{
|
||||
{
|
||||
"#OTHERPERMISSIONS#",
|
||||
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#SERVERPERMISSIONS#",
|
||||
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
},
|
||||
{
|
||||
"#STOREPERMISSIONS#",
|
||||
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}"))
|
||||
}
|
||||
});
|
||||
description = description.Replace("#OTHERPERMISSIONS#",
|
||||
string.Join("\n", otherPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
|
||||
.Replace("#SERVERPERMISSIONS#",
|
||||
string.Join("\n", serverPolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")))
|
||||
.Replace("#STOREPERMISSIONS#",
|
||||
string.Join("\n", storePolicies.Select(pair => $"* `{pair.Key}`: {pair.Value.Title}")));
|
||||
Logs.Tester.LogInformation(description);
|
||||
|
||||
var sresp = Assert
|
||||
@ -417,11 +408,11 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanCalculateCryptoDue()
|
||||
{
|
||||
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -705,11 +696,11 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanAcceptInvoiceWithTolerance()
|
||||
{
|
||||
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
|
||||
{
|
||||
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
|
||||
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
|
||||
});
|
||||
var entity = new InvoiceEntity();
|
||||
entity.Networks = networkProvider;
|
||||
@ -883,7 +874,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanEnumerateTorServices()
|
||||
{
|
||||
var tor = new TorServices(new BTCPayNetworkProvider(NetworkType.Regtest),
|
||||
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
|
||||
new BTCPayServerOptions() { TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc") });
|
||||
await tor.Refresh();
|
||||
|
||||
@ -904,7 +895,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
await user.GrantAccessAsync();
|
||||
await user.GrantAccessAsync(true);
|
||||
await user.RegisterDerivationSchemeAsync("BTC");
|
||||
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
|
||||
user.SetNetworkFeeMode(NetworkFeeMode.Never);
|
||||
@ -954,7 +945,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
var storeController = user.GetController<StoresController>();
|
||||
Assert.IsType<ViewResult>(storeController.UpdateStore());
|
||||
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId, "BTC"));
|
||||
@ -1021,7 +1012,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", type);
|
||||
user.RegisterDerivationScheme("BTC");
|
||||
|
||||
@ -1180,7 +1171,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanSolveTheDogesRatesOnKraken()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
|
||||
@ -1211,7 +1202,7 @@ namespace BTCPayServer.Tests
|
||||
});
|
||||
}
|
||||
var httpFactory = tester.PayTester.GetService<IHttpClientFactory>();
|
||||
var client = httpFactory.CreateClient(PayjoinClient.PayjoinOnionNamedClient);
|
||||
var client = httpFactory.CreateClient(PayjoinServerCommunicator.PayjoinOnionNamedClient);
|
||||
Assert.NotNull(client);
|
||||
var response = await client.GetAsync("https://check.torproject.org/");
|
||||
response.EnsureSuccessStatusCode();
|
||||
@ -1230,7 +1221,7 @@ namespace BTCPayServer.Tests
|
||||
AssertConnectionDropped();
|
||||
client.Dispose();
|
||||
AssertConnectionDropped();
|
||||
client = httpFactory.CreateClient(PayjoinClient.PayjoinOnionNamedClient);
|
||||
client = httpFactory.CreateClient(PayjoinServerCommunicator.PayjoinOnionNamedClient);
|
||||
response = await client.GetAsync("http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/");
|
||||
response.EnsureSuccessStatusCode();
|
||||
AssertConnectionDropped();
|
||||
@ -2058,7 +2049,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Fast", "Fast")]
|
||||
public void HasCurrencyDataForNetworks()
|
||||
{
|
||||
var btcPayNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
|
||||
var btcPayNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
|
||||
foreach (var network in btcPayNetworkProvider.GetAll())
|
||||
{
|
||||
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
|
||||
@ -2135,7 +2126,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
|
||||
|
||||
@ -2175,10 +2166,9 @@ namespace BTCPayServer.Tests
|
||||
Assert.StartsWith("bitcoin:", paymentMethodSecond.InvoiceBitcoinUrlQR);
|
||||
var split = paymentMethodSecond.InvoiceBitcoinUrlQR.Split('?')[0];
|
||||
|
||||
// Standard for uppercase Bech32 addresses in QR codes is still not implemented in all wallets
|
||||
// When it is widely propagated consider uncommenting these lines
|
||||
//Assert.True($"BITCOIN:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress}" == split);
|
||||
// Standard for all uppercase characters in QR codes is still not implemented in all wallets
|
||||
// But we're proceeding with BECH32 being uppercase
|
||||
Assert.True($"bitcoin:{paymentMethodSecond.BtcAddress.ToUpperInvariant()}" == split);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2193,7 +2183,7 @@ namespace BTCPayServer.Tests
|
||||
await tester.StartAsync();
|
||||
await tester.EnsureChannelsSetup();
|
||||
var user = tester.NewAccount();
|
||||
user.GrantAccess();
|
||||
user.GrantAccess(true);
|
||||
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
|
||||
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
|
||||
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
|
||||
@ -2996,7 +2986,7 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Integration", "Integration")]
|
||||
public void CanGetRateCryptoCurrenciesByDefault()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var factory = CreateBTCPayRateFactory();
|
||||
var fetcher = new RateFetcher(factory);
|
||||
var pairs =
|
||||
@ -3068,35 +3058,35 @@ namespace BTCPayServer.Tests
|
||||
var unusedUri = new Uri("https://toto.com");
|
||||
Assert.True(ExternalConnectionString.TryParse("server=/test", out var connStr, out var error));
|
||||
var expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge,
|
||||
NetworkType.Mainnet);
|
||||
ChainName.Mainnet);
|
||||
Assert.Equal(new Uri("https://toto.com/test"), expanded.Server);
|
||||
expanded = await connStr.Expand(new Uri("http://toto.onion"), ExternalServiceTypes.Charge,
|
||||
NetworkType.Mainnet);
|
||||
ChainName.Mainnet);
|
||||
Assert.Equal(new Uri("http://toto.onion/test"), expanded.Server);
|
||||
await Assert.ThrowsAsync<SecurityException>(() =>
|
||||
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet));
|
||||
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Testnet);
|
||||
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Mainnet));
|
||||
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Testnet);
|
||||
|
||||
// Make sure absolute paths are not expanded
|
||||
Assert.True(ExternalConnectionString.TryParse("server=https://tow/test", out connStr, out error));
|
||||
expanded = await connStr.Expand(new Uri("https://toto.com"), ExternalServiceTypes.Charge,
|
||||
NetworkType.Mainnet);
|
||||
ChainName.Mainnet);
|
||||
Assert.Equal(new Uri("https://tow/test"), expanded.Server);
|
||||
|
||||
// Error if directory not exists
|
||||
Assert.True(ExternalConnectionString.TryParse($"server={unusedUri};macaroondirectorypath=pouet",
|
||||
out connStr, out error));
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() =>
|
||||
connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet));
|
||||
connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, ChainName.Mainnet));
|
||||
await Assert.ThrowsAsync<DirectoryNotFoundException>(() =>
|
||||
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, ChainName.Mainnet);
|
||||
|
||||
var macaroonDirectory = CreateDirectory();
|
||||
Assert.True(ExternalConnectionString.TryParse(
|
||||
$"server={unusedUri};macaroondirectorypath={macaroonDirectory}", out connStr, out error));
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
|
||||
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, ChainName.Mainnet);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet);
|
||||
Assert.NotNull(expanded.Macaroons);
|
||||
Assert.Null(expanded.MacaroonFilePath);
|
||||
Assert.Null(expanded.Macaroons.AdminMacaroon);
|
||||
@ -3106,7 +3096,7 @@ namespace BTCPayServer.Tests
|
||||
File.WriteAllBytes($"{macaroonDirectory}/admin.macaroon", new byte[] { 0xaa });
|
||||
File.WriteAllBytes($"{macaroonDirectory}/invoice.macaroon", new byte[] { 0xab });
|
||||
File.WriteAllBytes($"{macaroonDirectory}/readonly.macaroon", new byte[] { 0xac });
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet);
|
||||
Assert.NotNull(expanded.Macaroons.AdminMacaroon);
|
||||
Assert.NotNull(expanded.Macaroons.InvoiceMacaroon);
|
||||
Assert.Equal("ab", expanded.Macaroons.InvoiceMacaroon.Hex);
|
||||
@ -3116,7 +3106,7 @@ namespace BTCPayServer.Tests
|
||||
Assert.True(ExternalConnectionString.TryParse(
|
||||
$"server={unusedUri};cookiefilepath={macaroonDirectory}/charge.cookie", out connStr, out error));
|
||||
File.WriteAllText($"{macaroonDirectory}/charge.cookie", "apitoken");
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
|
||||
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, ChainName.Mainnet);
|
||||
Assert.Equal("apitoken", expanded.APIToken);
|
||||
}
|
||||
|
||||
@ -3127,6 +3117,24 @@ namespace BTCPayServer.Tests
|
||||
return name;
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public void CanCheckFileNameValid()
|
||||
{
|
||||
var tests = new[]
|
||||
{
|
||||
("test.com", true),
|
||||
("/test.com", false),
|
||||
("te/st.com", false),
|
||||
("\\test.com", false),
|
||||
("te\\st.com", false)
|
||||
};
|
||||
foreach(var t in tests)
|
||||
{
|
||||
Assert.Equal(t.Item2, t.Item1.IsValidFileName());
|
||||
}
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Fast", "Fast")]
|
||||
public async Task CanCreateSqlitedb()
|
||||
@ -3201,10 +3209,12 @@ namespace BTCPayServer.Tests
|
||||
[Trait("Fast", "Fast")]
|
||||
public void ParseDerivationSchemeSettings()
|
||||
{
|
||||
var mainnet = new BTCPayNetworkProvider(NetworkType.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
var mainnet = new BTCPayNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
var root = new Mnemonic(
|
||||
"usage fever hen zero slide mammal silent heavy donate budget pulse say brain thank sausage brand craft about save attract muffin advance illegal cabbage")
|
||||
.DeriveExtKey();
|
||||
|
||||
// ColdCard
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw\", \"xpub\": \"ypub6WWc2gWwHbdnAAyJDnR4SPL1phRh7REqrPBfZeizaQ1EmTshieRXJC3Z5YoU4wkcdKHEjQGkh6AYEzCQC1Kz3DNaWSwdc1pc8416hAjzqyD\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/49'/0'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
mainnet, out var settings));
|
||||
@ -3218,8 +3228,7 @@ namespace BTCPayServer.Tests
|
||||
settings.AccountOriginal);
|
||||
Assert.Equal(root.Derive(new KeyPath("m/49'/0'/0'")).Neuter().PubKey.WitHash.ScriptPubKey.Hash.ScriptPubKey,
|
||||
settings.AccountDerivation.GetDerivation().ScriptPubKey);
|
||||
|
||||
var testnet = new BTCPayNetworkProvider(NetworkType.Testnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
var testnet = new BTCPayNetworkProvider(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
|
||||
|
||||
// Should be legacy
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
@ -3239,6 +3248,15 @@ namespace BTCPayServer.Tests
|
||||
"{\"keystore\": {\"ckcc_xpub\": \"tpubD6NzVbkrYhZ4YHNiuTdTmHRmbcPRLfqgyneZFCL1mkzkUBjXriQShxTh9HL34FK2mhieasJVk9EzJrUfkFqRNQBjiXgx3n5BhPkxKBoFmaS\", \"xpub\": \"vpub5YjYxTemJ39tFRnuAhwduyxG2tKGjoEpmvqVQRPqdYrqa6YGoeSzBtHXaJUYB19zDbXs3JjbEcVWERjQBPf9bEfUUMZNMv1QnMyHV8JPqyf\", \"label\": \"Coldcard Import 0x60d1af8b\", \"ckcc_xfp\": 1624354699, \"type\": \"hardware\", \"hw_type\": \"coldcard\", \"derivation\": \"m/84'/1'/0'\"}, \"wallet_type\": \"standard\", \"use_encryption\": false, \"seed_version\": 17}",
|
||||
testnet, out settings));
|
||||
Assert.True(settings.AccountDerivation is DirectDerivationStrategy s3 && s3.Segwit);
|
||||
|
||||
// Specter
|
||||
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
|
||||
"{\"label\": \"Specter\", \"blockheight\": 123456, \"descriptor\": \"wpkh([8bafd160/49h/0h/0h]xpub661MyMwAqRbcGVBsTGeNZN6QGVHmMHLdSA4FteGsRrEriu4pnVZMZWnruFFFXkMnyoBjyHndD3Qwcfz4MPzBUxjSevweNFQx7SAYZATtcDw/0/*)#9x4vkw48\"}",
|
||||
mainnet, out var specter));
|
||||
Assert.Equal(root.GetPublicKey().GetHDFingerPrint(), specter.AccountKeySettings[0].RootFingerprint);
|
||||
Assert.Equal(specter.AccountKeySettings[0].RootFingerprint, hd);
|
||||
Assert.Equal("49'/0'/0'", specter.AccountKeySettings[0].AccountKeyPath.ToString());
|
||||
Assert.Equal("Specter", specter.Label);
|
||||
}
|
||||
|
||||
|
||||
@ -3409,7 +3427,86 @@ namespace BTCPayServer.Tests
|
||||
Assert.False(fn.Seen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoLightningInternalNodeMigration()
|
||||
{
|
||||
using (var tester = ServerTester.Create(newDb: true))
|
||||
{
|
||||
tester.ActivateLightning(LightningConnectionType.CLightning);
|
||||
await tester.StartAsync();
|
||||
var acc = tester.NewAccount();
|
||||
await acc.GrantAccessAsync(true);
|
||||
await acc.CreateStoreAsync();
|
||||
|
||||
// Test if legacy DerivationStrategy column is converted to DerivationStrategies
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
var xpub = "tpubDDmH1briYfZcTDMEc7uMEA5hinzjUTzR9yMC1drxTMeiWyw1VyCqTuzBke6df2sqbfw9QG6wbgTLF5yLjcXsZNaXvJMZLwNEwyvmiFWcLav";
|
||||
var derivation = $"{xpub}-[legacy]";
|
||||
store.DerivationStrategy = derivation;
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
Assert.True(string.IsNullOrEmpty(store.DerivationStrategy));
|
||||
var v = (DerivationSchemeSettings)store.GetSupportedPaymentMethods(tester.NetworkProvider).First();
|
||||
Assert.Equal(derivation, v.AccountDerivation.ToString());
|
||||
Assert.Equal(derivation, v.AccountOriginal.ToString());
|
||||
Assert.Equal(xpub, v.SigningKey.ToString());
|
||||
Assert.Equal(xpub, v.GetSigningAccountKeySettings().AccountKey.ToString());
|
||||
|
||||
await acc.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning, true);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
var lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
Assert.Null(lnMethod.GetExternalLightningUrl());
|
||||
|
||||
// Test if legacy lightning charge settings are converted to LightningConnectionString
|
||||
store.DerivationStrategies = new JObject()
|
||||
{
|
||||
new JProperty("BTC_LightningLike", new JObject()
|
||||
{
|
||||
new JProperty("LightningChargeUrl", "http://mycharge.com/"),
|
||||
new JProperty("Username", "usr"),
|
||||
new JProperty("Password", "pass"),
|
||||
new JProperty("CryptoCode", "BTC"),
|
||||
new JProperty("PaymentId", "someshit"),
|
||||
})
|
||||
}.ToString();
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
Assert.NotNull(lnMethod.GetExternalLightningUrl());
|
||||
|
||||
var url = lnMethod.GetExternalLightningUrl();
|
||||
Assert.Equal(LightningConnectionType.Charge, url.ConnectionType);
|
||||
Assert.Equal("pass", url.Password);
|
||||
Assert.Equal("usr", url.Username);
|
||||
|
||||
// Test if lightning connection strings get migrated to internal
|
||||
store.DerivationStrategies = new JObject()
|
||||
{
|
||||
new JProperty("BTC_LightningLike", new JObject()
|
||||
{
|
||||
new JProperty("CryptoCode", "BTC"),
|
||||
new JProperty("LightningConnectionString", tester.PayTester.IntegratedLightning),
|
||||
})
|
||||
}.ToString();
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
await RestartMigration(tester);
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
lnMethod = store.GetSupportedPaymentMethods(tester.NetworkProvider).OfType<LightningSupportedPaymentMethod>().First();
|
||||
Assert.True(lnMethod.IsInternalNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task CanDoInvoiceMigrations()
|
||||
@ -3423,7 +3520,7 @@ namespace BTCPayServer.Tests
|
||||
await acc.CreateStoreAsync();
|
||||
await acc.RegisterDerivationSchemeAsync("BTC");
|
||||
var store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
|
||||
|
||||
var blob = store.GetStoreBlob();
|
||||
var serializer = new Serializer(null);
|
||||
|
||||
@ -3444,10 +3541,10 @@ namespace BTCPayServer.Tests
|
||||
new KeyPath("44'/0'/0'").ToString()
|
||||
}
|
||||
})));
|
||||
|
||||
|
||||
blob.AdditionalData.Add("networkFeeDisabled", JToken.Parse(
|
||||
serializer.ToString((bool?)true)));
|
||||
|
||||
|
||||
blob.AdditionalData.Add("onChainMinValue", JToken.Parse(
|
||||
serializer.ToString(new CurrencyValue()
|
||||
{
|
||||
@ -3460,18 +3557,13 @@ namespace BTCPayServer.Tests
|
||||
Currency = "USD",
|
||||
Value = 5m
|
||||
}.ToString())));
|
||||
|
||||
|
||||
store.SetStoreBlob(blob);
|
||||
await tester.PayTester.StoreRepository.UpdateStore(store);
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
await settings.UpdateSetting<MigrationSettings>(new MigrationSettings());
|
||||
var migrationStartupTask = tester.PayTester.GetService<IServiceProvider>().GetServices<IStartupTask>()
|
||||
.Single(task => task is MigrationStartupTask);
|
||||
await migrationStartupTask.ExecuteAsync();
|
||||
|
||||
|
||||
await RestartMigration(tester);
|
||||
|
||||
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
|
||||
|
||||
|
||||
blob = store.GetStoreBlob();
|
||||
Assert.Empty(blob.AdditionalData);
|
||||
Assert.Single(blob.PaymentMethodCriteria);
|
||||
@ -3486,7 +3578,16 @@ namespace BTCPayServer.Tests
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static async Task RestartMigration(ServerTester tester)
|
||||
{
|
||||
var settings = tester.PayTester.GetService<SettingsRepository>();
|
||||
await settings.UpdateSetting<MigrationSettings>(new MigrationSettings());
|
||||
var migrationStartupTask = tester.PayTester.GetService<IServiceProvider>().GetServices<IStartupTask>()
|
||||
.Single(task => task is MigrationStartupTask);
|
||||
await migrationStartupTask.ExecuteAsync();
|
||||
}
|
||||
|
||||
[Fact(Timeout = TestTimeout)]
|
||||
[Trait("Integration", "Integration")]
|
||||
public async Task EmailSenderTests()
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
@ -24,7 +24,7 @@ services:
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
||||
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
|
||||
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
TESTS_SSHCONNECTION: "root@sshd:22"
|
||||
TESTS_SSHPASSWORD: ""
|
||||
@ -69,20 +69,22 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:0.20.1
|
||||
image: btcpayserver/bitcoin:0.21.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
fallbackfee=0.0002
|
||||
rpcallowip=0.0.0.0/0
|
||||
links:
|
||||
- nbxplorer
|
||||
- postgres
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.1.45
|
||||
image: nicolasdorier/nbxplorer:2.1.49
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -116,14 +118,16 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:0.20.1
|
||||
image: btcpayserver/bitcoin:0.21.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
@ -142,7 +146,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v0.9.0-1-dev
|
||||
image: btcpayserver/lightning:v0.9.3-1-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -169,12 +173,14 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.19-standalone
|
||||
image: shesek/lightning-charge:0.4.23-1-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
BITCOIND_RPCCONNECT: bitcoind
|
||||
LN_NET_PATH: /etc/lightning
|
||||
LN_NET: /etc/lightning
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "lightning_charge_datadir:/data"
|
||||
@ -189,7 +195,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v0.9.0-1-dev
|
||||
image: btcpayserver/lightning:v0.9.3-1-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -221,18 +227,21 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.11.0-beta
|
||||
image: btcpayserver/lnd:v0.12.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
@ -240,6 +249,7 @@ services:
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
expose:
|
||||
@ -251,18 +261,21 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.11.0-beta
|
||||
image: btcpayserver/lnd:v0.12.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:10009
|
||||
@ -270,6 +283,7 @@ services:
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
expose:
|
||||
|
@ -22,7 +22,7 @@ services:
|
||||
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
|
||||
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
|
||||
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
|
||||
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
|
||||
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
|
||||
TESTS_INCONTAINER: "true"
|
||||
TESTS_SSHCONNECTION: "root@sshd:22"
|
||||
TESTS_SSHPASSWORD: ""
|
||||
@ -66,12 +66,14 @@ services:
|
||||
- "sshd_datadir:/root/.ssh"
|
||||
|
||||
devlnd:
|
||||
image: btcpayserver/bitcoin:0.20.1
|
||||
image: btcpayserver/bitcoin:0.21.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |
|
||||
deprecatedrpc=signrawtransaction
|
||||
connect=bitcoind:39388
|
||||
rpcallowip=0.0.0.0/0
|
||||
fallbackfee=0.0002
|
||||
links:
|
||||
- nbxplorer
|
||||
@ -79,7 +81,7 @@ services:
|
||||
- customer_lnd
|
||||
- merchant_lnd
|
||||
nbxplorer:
|
||||
image: nicolasdorier/nbxplorer:2.1.45
|
||||
image: nicolasdorier/nbxplorer:2.1.49
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "32838:32838"
|
||||
@ -103,14 +105,16 @@ services:
|
||||
|
||||
bitcoind:
|
||||
restart: unless-stopped
|
||||
image: btcpayserver/bitcoin:0.20.1
|
||||
image: btcpayserver/bitcoin:0.21.0
|
||||
environment:
|
||||
BITCOIN_NETWORK: regtest
|
||||
BITCOIN_WALLETDIR: "/data/wallets"
|
||||
BITCOIN_EXTRA_ARGS: |-
|
||||
rpcuser=ceiwHEbqWI83
|
||||
rpcpassword=DwubwWsoo3
|
||||
rpcport=43782
|
||||
rpcbind=0.0.0.0:43782
|
||||
rpcallowip=0.0.0.0/0
|
||||
port=39388
|
||||
whitelist=0.0.0.0/0
|
||||
zmqpubrawblock=tcp://0.0.0.0:28332
|
||||
@ -129,7 +133,7 @@ services:
|
||||
- "bitcoin_datadir:/data"
|
||||
|
||||
customer_lightningd:
|
||||
image: btcpayserver/lightning:v0.9.0-1-dev
|
||||
image: btcpayserver/lightning:v0.9.3-1-dev
|
||||
stop_signal: SIGKILL
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
@ -156,12 +160,14 @@ services:
|
||||
- bitcoind
|
||||
|
||||
lightning-charged:
|
||||
image: shesek/lightning-charge:0.4.19-standalone
|
||||
image: shesek/lightning-charge:0.4.23-1-standalone
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
NETWORK: regtest
|
||||
API_TOKEN: foiewnccewuify
|
||||
BITCOIND_RPCCONNECT: bitcoind
|
||||
LN_NET_PATH: /etc/lightning
|
||||
LN_NET: /etc/lightning
|
||||
volumes:
|
||||
- "bitcoin_datadir:/etc/bitcoin"
|
||||
- "lightning_charge_datadir:/data"
|
||||
@ -176,7 +182,7 @@ services:
|
||||
- merchant_lightningd
|
||||
|
||||
merchant_lightningd:
|
||||
image: btcpayserver/lightning:v0.9.0-1-dev
|
||||
image: btcpayserver/lightning:v0.9.3-1-dev
|
||||
stop_signal: SIGKILL
|
||||
environment:
|
||||
EXPOSE_TCP: "true"
|
||||
@ -209,18 +215,21 @@ services:
|
||||
- "5432"
|
||||
|
||||
merchant_lnd:
|
||||
image: btcpayserver/lnd:v0.11.0-beta
|
||||
image: btcpayserver/lnd:v0.12.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
restlisten=merchant_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
rpclisten=merchant_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=merchant_lnd:9735
|
||||
@ -228,6 +237,7 @@ services:
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35531:8080"
|
||||
expose:
|
||||
@ -239,18 +249,21 @@ services:
|
||||
- bitcoind
|
||||
|
||||
customer_lnd:
|
||||
image: btcpayserver/lnd:v0.11.0-beta
|
||||
image: btcpayserver/lnd:v0.12.1-beta
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
LND_CHAIN: "btc"
|
||||
LND_ENVIRONMENT: "regtest"
|
||||
LND_EXPLORERURL: "http://nbxplorer:32838/"
|
||||
LND_REST_LISTEN_HOST: http://customer_lnd:8080
|
||||
LND_EXTRA_ARGS: |
|
||||
restlisten=0.0.0.0:8080
|
||||
restlisten=customer_lnd:8080
|
||||
rpclisten=127.0.0.1:10008
|
||||
rpclisten=0.0.0.0:10009
|
||||
rpclisten=customer_lnd:10009
|
||||
bitcoin.node=bitcoind
|
||||
bitcoind.rpchost=bitcoind:43782
|
||||
bitcoind.rpcuser=ceiwHEbqWI83
|
||||
bitcoind.rpcpass=DwubwWsoo3
|
||||
bitcoind.zmqpubrawblock=tcp://bitcoind:28332
|
||||
bitcoind.zmqpubrawtx=tcp://bitcoind:28333
|
||||
externalip=customer_lnd:10009
|
||||
@ -258,6 +271,7 @@ services:
|
||||
no-macaroons=1
|
||||
debuglevel=debug
|
||||
trickledelay=1000
|
||||
no-rest-tls=1
|
||||
ports:
|
||||
- "35532:8080"
|
||||
expose:
|
||||
|
@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
|
||||
<Import Project="../Build/Common.csproj" />
|
||||
|
||||
@ -44,8 +44,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BIP78.Sender" Version="0.2.0" />
|
||||
<PackageReference Include="BTCPayServer.Hwi" Version="1.1.3" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.4" />
|
||||
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.7" />
|
||||
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
|
||||
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
|
||||
@ -60,7 +61,6 @@
|
||||
<PackageReference Include="QRCoder" Version="1.4.1" />
|
||||
<PackageReference Include="System.IO.Pipelines" Version="4.7.2" />
|
||||
<PackageReference Include="NBitpayClient" Version="1.0.0.39" />
|
||||
<PackageReference Include="DBriize" Version="1.0.1.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.2" />
|
||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.3" />
|
||||
@ -134,6 +134,7 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Views\Stores\Integrations" />
|
||||
<Folder Include="wwwroot\vendor\clipboard.js\" />
|
||||
<Folder Include="wwwroot\vendor\highlightjs\" />
|
||||
<Folder Include="wwwroot\vendor\summernote" />
|
||||
|
@ -2,6 +2,7 @@
|
||||
@inject UserManager<ApplicationUser> UserManager
|
||||
@inject CssThemeManager CssThemeManager
|
||||
@using BTCPayServer.HostedServices
|
||||
@using BTCPayServer.Views.Notifications
|
||||
@using Microsoft.AspNetCore.Http.Extensions
|
||||
@model BTCPayServer.Components.NotificationsDropdown.NotificationSummaryViewModel
|
||||
@addTagHelper *, BundlerMinifier.TagHelpers
|
||||
@ -9,15 +10,15 @@
|
||||
@if (Model.UnseenCount > 0)
|
||||
{
|
||||
<li class="nav-item dropdown" id="notifications-nav-item">
|
||||
<a class="nav-link js-scroll-trigger border-bottom-0" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
|
||||
<i class="fa fa-bell"></i>
|
||||
<a class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(NotificationsNavPages))" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
|
||||
<span class="d-lg-none d-sm-block">Notifications</span><i class="fa fa-bell d-lg-inline-block d-none"></i>
|
||||
</a>
|
||||
<span class="alerts-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
|
||||
<div class="dropdown-menu dropdown-menu-right text-center notification-dropdown" aria-labelledby="navbarDropdown">
|
||||
<div class="d-flex align-items-center justify-content-between py-3 px-4 border-bottom border-light">
|
||||
<h5 class="m-0">Notifications</h5>
|
||||
<form asp-controller="Notifications" asp-action="MarkAllAsSeen" asp-route-returnUrl="@Context.Request.GetCurrentPathWithQueryString()" method="post">
|
||||
<button class="btn btn-link p-0 font-weight-semibold" type="submit">Mark all as seen</button>
|
||||
<button class="btn btn-link p-0" type="submit">Mark all as seen</button>
|
||||
</form>
|
||||
</div>
|
||||
@foreach (var notif in Model.Last5)
|
||||
@ -28,7 +29,7 @@
|
||||
</div>
|
||||
|
||||
<div class="notification-item__content">
|
||||
<div class="text-left text-wrap font-weight-semibold">
|
||||
<div class="text-left text-wrap">
|
||||
@notif.Body
|
||||
</div>
|
||||
<div class="text-left d-flex">
|
||||
@ -38,7 +39,7 @@
|
||||
</a>
|
||||
}
|
||||
<div class="p-3">
|
||||
<a class="font-weight-semibold" asp-controller="Notifications" asp-action="Index">View all</a>
|
||||
<a asp-controller="Notifications" asp-action="Index">View all</a>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
@ -46,8 +47,8 @@
|
||||
else
|
||||
{
|
||||
<li class="nav-item" id="notifications-nav-item">
|
||||
<a asp-controller="Notifications" asp-action="Index" title="Notifications" class="nav-link js-scroll-trigger" id="Notifications">
|
||||
<i class="fa fa-bell"></i>
|
||||
<a asp-controller="Notifications" asp-action="Index" title="Notifications" class="nav-link js-scroll-trigger @ViewData.IsActiveCategory(typeof(NotificationsNavPages))" id="Notifications">
|
||||
<span class="d-lg-none d-sm-block">Notifications</span><i class="fa fa-bell d-lg-inline-block d-none"></i>
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
public class BTCPayServerOptions
|
||||
{
|
||||
public NetworkType NetworkType
|
||||
public ChainName NetworkType
|
||||
{
|
||||
get; set;
|
||||
}
|
||||
@ -61,7 +61,7 @@ namespace BTCPayServer.Configuration
|
||||
|
||||
Logs.Configuration.LogInformation("Network: " + NetworkType.ToString());
|
||||
|
||||
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != NetworkType.Regtest)
|
||||
if (conf.GetOrDefault<bool>("launchsettings", false) && NetworkType != ChainName.Regtest)
|
||||
throw new ConfigException($"You need to run BTCPayServer with the run.sh or run.ps1 script");
|
||||
|
||||
|
||||
|
@ -12,7 +12,7 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
protected override CommandLineApplication CreateCommandLineApplicationCore()
|
||||
{
|
||||
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
|
||||
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
|
||||
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
|
||||
CommandLineApplication app = new CommandLineApplication(true)
|
||||
{
|
||||
@ -23,6 +23,7 @@ namespace BTCPayServer.Configuration
|
||||
app.Option("-n | --network", $"Set the network among (mainnet,testnet,regtest) (default: mainnet)", CommandOptionType.SingleValue);
|
||||
app.Option("--testnet | -testnet", $"Use testnet (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--regtest | -regtest", $"Use regtest (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--signet | -signet", $"Use signet (deprecated, use --network instead)", CommandOptionType.BoolValue);
|
||||
app.Option("--allow-admin-registration", $"For debug only, will show a checkbox when a new user register to add himself as admin. (default: false)", CommandOptionType.BoolValue);
|
||||
app.Option("--chains | -c", $"Chains to support as a comma separated (default: btc; available: {chains})", CommandOptionType.SingleValue);
|
||||
app.Option("--postgres", $"Connection string to a PostgreSQL database", CommandOptionType.SingleValue);
|
||||
@ -89,7 +90,7 @@ namespace BTCPayServer.Configuration
|
||||
return Path.Combine(chainDir, fileName);
|
||||
}
|
||||
|
||||
public static NetworkType GetNetworkType(IConfiguration conf)
|
||||
public static ChainName GetNetworkType(IConfiguration conf)
|
||||
{
|
||||
var network = conf.GetOrDefault<string>("network", null);
|
||||
if (network != null)
|
||||
@ -99,10 +100,12 @@ namespace BTCPayServer.Configuration
|
||||
{
|
||||
throw new ConfigException($"Invalid network parameter '{network}'");
|
||||
}
|
||||
return n.NetworkType;
|
||||
return n.ChainName;
|
||||
}
|
||||
var net = conf.GetOrDefault<bool>("regtest", false) ? NetworkType.Regtest :
|
||||
conf.GetOrDefault<bool>("testnet", false) ? NetworkType.Testnet : NetworkType.Mainnet;
|
||||
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainName.Regtest :
|
||||
conf.GetOrDefault<bool>("testnet", false) ? ChainName.Testnet :
|
||||
conf.GetOrDefault<bool>("signet", false) ? Bitcoin.Instance.Signet.ChainName :
|
||||
ChainName.Mainnet;
|
||||
|
||||
return net;
|
||||
}
|
||||
|
@ -30,12 +30,12 @@ namespace BTCPayServer.Configuration
|
||||
/// Return a connectionString which does not depends on external resources or information like relative path or file path
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, NetworkType network)
|
||||
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, ChainName network)
|
||||
{
|
||||
var connectionString = this.Clone();
|
||||
// Transform relative URI into absolute URI
|
||||
var serviceUri = connectionString.Server.IsAbsoluteUri ? connectionString.Server : ToRelative(absoluteUrlBase, connectionString.Server.ToString());
|
||||
var isSecure = network != NetworkType.Mainnet ||
|
||||
var isSecure = network != ChainName.Mainnet ||
|
||||
serviceUri.Scheme == "https" ||
|
||||
serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase) ||
|
||||
Extensions.IsLocalNetwork(serviceUri.DnsSafeHost);
|
||||
|
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Security.Policy;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
@ -34,6 +35,7 @@ namespace BTCPayServer.Controllers
|
||||
readonly Configuration.BTCPayServerOptions _Options;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
public U2FService _u2FService;
|
||||
private readonly RateLimitService _rateLimitService;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
readonly ILogger _logger;
|
||||
|
||||
@ -45,6 +47,7 @@ namespace BTCPayServer.Controllers
|
||||
Configuration.BTCPayServerOptions options,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
U2FService u2FService,
|
||||
RateLimitService rateLimitService,
|
||||
EventAggregator eventAggregator)
|
||||
{
|
||||
_userManager = userManager;
|
||||
@ -54,6 +57,7 @@ namespace BTCPayServer.Controllers
|
||||
_Options = options;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_u2FService = u2FService;
|
||||
_rateLimitService = rateLimitService;
|
||||
_eventAggregator = eventAggregator;
|
||||
_logger = Logs.PayServer;
|
||||
}
|
||||
@ -66,6 +70,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[Route("~/login", Order = 1)]
|
||||
[Route("~/Account/Login", Order = 2)]
|
||||
public async Task<IActionResult> Login(string returnUrl = null, string email = null)
|
||||
{
|
||||
|
||||
@ -89,6 +95,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[Route("~/login", Order = 1)]
|
||||
[Route("~/Account/Login", Order = 2)]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RateLimitsFilter(ZoneLimits.Login, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
|
||||
@ -396,6 +404,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpGet]
|
||||
[AllowAnonymous]
|
||||
[Route("~/register", Order = 1)]
|
||||
[Route("~/Account/Register", Order = 2)]
|
||||
[RateLimitsFilter(ZoneLimits.Register, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<IActionResult> Register(string returnUrl = null, bool logon = true)
|
||||
{
|
||||
@ -413,6 +423,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[Route("~/register", Order = 1)]
|
||||
[Route("~/Account/Register", Order = 2)]
|
||||
[ValidateAntiForgeryToken]
|
||||
public async Task<IActionResult> Register(RegisterViewModel model, string returnUrl = null, bool logon = true)
|
||||
{
|
||||
@ -535,6 +547,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpPost]
|
||||
[AllowAnonymous]
|
||||
[ValidateAntiForgeryToken]
|
||||
[RateLimitsFilter(ZoneLimits.ForgotPassword, Scope = RateLimitsScope.RemoteAddress)]
|
||||
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
|
||||
{
|
||||
if (ModelState.IsValid)
|
||||
|
@ -71,7 +71,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
try
|
||||
{
|
||||
_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency).ToString();
|
||||
vm.PerksTemplate = _AppService.SerializeTemplate(_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -161,11 +161,16 @@ namespace BTCPayServer.Controllers
|
||||
[Route("{appId}/settings/pos")]
|
||||
public async Task<IActionResult> UpdatePointOfSale(string appId, UpdatePointOfSaleViewModel vm)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (_currencies.GetCurrencyData(vm.Currency, false) == null)
|
||||
ModelState.AddModelError(nameof(vm.Currency), "Invalid currency");
|
||||
try
|
||||
{
|
||||
_AppService.Parse(vm.Template, vm.Currency);
|
||||
vm.Template = _AppService.SerializeTemplate(_AppService.Parse(vm.Template, vm.Currency));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
@ -57,8 +57,10 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/")]
|
||||
[Route("/apps/{appId}/pos/{viewType?}")]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId, PosViewType? viewType = null)
|
||||
{
|
||||
var app = await _AppService.GetApp(appId, AppType.PointOfSale);
|
||||
@ -104,10 +106,12 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/")]
|
||||
[Route("/apps/{appId}/pos/{viewType?}")]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
[DomainMappingConstraint(AppType.PointOfSale)]
|
||||
public async Task<IActionResult> ViewPointOfSale(string appId,
|
||||
PosViewType viewType,
|
||||
[ModelBinder(typeof(InvariantDecimalModelBinder))] decimal amount,
|
||||
@ -232,8 +236,10 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("/")]
|
||||
[Route("/apps/{appId}/crowdfund")]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
||||
public async Task<IActionResult> ViewCrowdfund(string appId, string statusMessage)
|
||||
{
|
||||
var app = await _AppService.GetApp(appId, AppType.Crowdfund, true);
|
||||
@ -263,10 +269,12 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("/")]
|
||||
[Route("/apps/{appId}/crowdfund")]
|
||||
[XFrameOptionsAttribute(XFrameOptionsAttribute.XFrameOptions.AllowAll)]
|
||||
[IgnoreAntiforgeryToken]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
[DomainMappingConstraintAttribute(AppType.Crowdfund)]
|
||||
public async Task<IActionResult> ContributeToCrowdfund(string appId, ContributeToCrowdfund request, CancellationToken cancellationToken)
|
||||
{
|
||||
if (request.Amount <= 0)
|
||||
|
@ -13,7 +13,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (statusCode.HasValue)
|
||||
{
|
||||
var specialPages = new[] { 404, 429, 500 };
|
||||
var specialPages = new[] { 404, 406, 417, 429, 500, 502 };
|
||||
if (specialPages.Any(a => a == statusCode.Value))
|
||||
{
|
||||
var viewName = statusCode.ToString();
|
||||
|
@ -5,14 +5,12 @@ using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Routing;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Conventions;
|
||||
using NBitcoin;
|
||||
using CreateInvoiceRequest = BTCPayServer.Client.Models.CreateInvoiceRequest;
|
||||
using InvoiceData = BTCPayServer.Client.Models.InvoiceData;
|
||||
@ -27,14 +25,22 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly InvoiceController _invoiceController;
|
||||
private readonly InvoiceRepository _invoiceRepository;
|
||||
private readonly LinkGenerator _linkGenerator;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
|
||||
public LanguageService LanguageService { get; }
|
||||
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository, LinkGenerator linkGenerator, LanguageService languageService)
|
||||
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository,
|
||||
LinkGenerator linkGenerator, LanguageService languageService, BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
EventAggregator eventAggregator, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
|
||||
{
|
||||
_invoiceController = invoiceController;
|
||||
_invoiceRepository = invoiceRepository;
|
||||
_linkGenerator = linkGenerator;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_eventAggregator = eventAggregator;
|
||||
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
|
||||
LanguageService = languageService;
|
||||
}
|
||||
|
||||
@ -72,7 +78,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice.StoreId != store.Id)
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
@ -212,7 +218,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
if (!await _invoiceRepository.MarkInvoiceStatus(invoice.Id, request.Status))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.Status),
|
||||
"Status can only be marked to invalid or complete within certain conditions.");
|
||||
"Status can only be marked to invalid or settled within certain conditions.");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
@ -270,6 +276,32 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
return Ok(ToPaymentMethodModels(invoice));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewInvoices,
|
||||
AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/invoices/{invoiceId}/payment-methods/{paymentMethod}/activate")]
|
||||
public async Task<IActionResult> ActivateInvoicePaymentMethod(string storeId, string invoiceId, string paymentMethod)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
|
||||
if (invoice?.StoreId != store.Id)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (PaymentMethodId.TryParse(paymentMethod, out var paymentMethodId))
|
||||
{
|
||||
await _invoiceRepository.ActivateInvoicePaymentMethod(_eventAggregator, _btcPayNetworkProvider,
|
||||
_paymentMethodHandlerDictionary, store, invoice, paymentMethodId);
|
||||
return Ok();
|
||||
}
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
private InvoicePaymentMethodDataModel[] ToPaymentMethodModels(InvoiceEntity entity)
|
||||
{
|
||||
@ -283,6 +315,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
|
||||
return new InvoicePaymentMethodDataModel()
|
||||
{
|
||||
Activated = details.Activated,
|
||||
PaymentMethod = method.GetId().ToStringNormalized(),
|
||||
Destination = details.GetPaymentDestination(),
|
||||
Rate = method.Rate,
|
||||
@ -337,7 +370,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
PaymentMethods =
|
||||
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
|
||||
SpeedPolicy = entity.SpeedPolicy,
|
||||
DefaultLanguage = entity.DefaultLanguage
|
||||
DefaultLanguage = entity.DefaultLanguage,
|
||||
RedirectAutomatically = entity.RedirectAutomatically,
|
||||
RedirectURL = entity.RedirectURLTemplate
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -28,8 +28,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public InternalLightningNodeApiController(
|
||||
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayServerEnvironment btcPayServerEnvironment,
|
||||
CssThemeManager cssThemeManager, LightningClientFactoryService lightningClientFactory,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions ) : base(
|
||||
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager)
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
IAuthorizationService authorizationService) : base(
|
||||
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
@ -100,17 +101,17 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return base.CreateInvoice(cryptoCode, request);
|
||||
}
|
||||
|
||||
protected override Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
|
||||
{
|
||||
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode,
|
||||
out var internalLightningNode);
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network == null || !CanUseInternalLightning(doingAdminThings) || internalLightningNode == null)
|
||||
if (network == null ||
|
||||
!_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
|
||||
out var internalLightningNode) ||
|
||||
!await CanUseInternalLightning(doingAdminThings))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network));
|
||||
return _lightningClientFactory.Create(internalLightningNode, network);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,9 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public StoreLightningNodeApiController(
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager) : base(
|
||||
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager)
|
||||
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager,
|
||||
IAuthorizationService authorizationService) : base(
|
||||
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService)
|
||||
{
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_lightningClientFactory = lightningClientFactory;
|
||||
@ -100,11 +101,10 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return base.CreateInvoice(cryptoCode, request);
|
||||
}
|
||||
|
||||
protected override Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode,
|
||||
bool doingAdminThings)
|
||||
{
|
||||
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode,
|
||||
out var internalLightningNode);
|
||||
|
||||
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
@ -117,13 +117,20 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
if (existing == null || (existing.GetLightningUrl().IsInternalNode(internalLightningNode) &&
|
||||
!CanUseInternalLightning(doingAdminThings)))
|
||||
{
|
||||
if (existing == null)
|
||||
return null;
|
||||
if (existing.GetExternalLightningUrl() is LightningConnectionString connectionString)
|
||||
{
|
||||
return _lightningClientFactory.Create(connectionString, network);
|
||||
}
|
||||
|
||||
return Task.FromResult(_lightningClientFactory.Create(existing.GetLightningUrl(), network));
|
||||
else if (
|
||||
await CanUseInternalLightning(doingAdminThings) &&
|
||||
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
|
||||
out var internalLightningNode))
|
||||
{
|
||||
return _lightningClientFactory.Create(internalLightningNode, network);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,10 +2,13 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Newtonsoft.Json.Linq;
|
||||
@ -32,13 +35,16 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
private readonly CssThemeManager _cssThemeManager;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
|
||||
protected LightningNodeApiController(BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager)
|
||||
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager,
|
||||
IAuthorizationService authorizationService)
|
||||
{
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
_cssThemeManager = cssThemeManager;
|
||||
_authorizationService = authorizationService;
|
||||
}
|
||||
|
||||
public virtual async Task<IActionResult> GetInfo(string cryptoCode)
|
||||
@ -294,10 +300,11 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
};
|
||||
}
|
||||
|
||||
protected bool CanUseInternalLightning(bool doingAdminThings)
|
||||
protected async Task<bool> CanUseInternalLightning(bool doingAdminThings)
|
||||
{
|
||||
return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) ||
|
||||
(_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings));
|
||||
return (!doingAdminThings && _cssThemeManager.AllowLightningInternalNodeForAll) ||
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
|
||||
}
|
||||
|
||||
protected abstract Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings);
|
||||
|
@ -10,6 +10,7 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Lightning;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Payments.Lightning;
|
||||
using BTCPayServer.Security;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
@ -27,24 +28,24 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
private readonly StoreRepository _storeRepository;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly CssThemeManager _cssThemeManager;
|
||||
private readonly BTCPayServerEnvironment _btcPayServerEnvironment;
|
||||
|
||||
public StoreLightningNetworkPaymentMethodsController(
|
||||
StoreRepository storeRepository,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
IOptions<LightningNetworkOptions> lightningNetworkOptions,
|
||||
CssThemeManager cssThemeManager,
|
||||
BTCPayServerEnvironment btcPayServerEnvironment)
|
||||
IAuthorizationService authorizationService,
|
||||
CssThemeManager cssThemeManager)
|
||||
{
|
||||
_storeRepository = storeRepository;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_lightningNetworkOptions = lightningNetworkOptions;
|
||||
_authorizationService = authorizationService;
|
||||
_cssThemeManager = cssThemeManager;
|
||||
_btcPayServerEnvironment = btcPayServerEnvironment;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork")]
|
||||
public ActionResult<IEnumerable<LightningNetworkPaymentMethodData>> GetLightningPaymentMethods(
|
||||
[FromQuery] bool enabledOnly = false)
|
||||
@ -56,13 +57,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.Select(paymentMethod =>
|
||||
new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetLightningUrl().ToString(), !excludedPaymentMethods.Match(paymentMethod.PaymentId)))
|
||||
paymentMethod.GetExternalLightningUrl().ToString(), !excludedPaymentMethods.Match(paymentMethod.PaymentId)))
|
||||
.Where((result) => !enabledOnly || result.Enabled)
|
||||
.ToList()
|
||||
);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
|
||||
public ActionResult<LightningNetworkPaymentMethodData> GetLightningNetworkPaymentMethod(string cryptoCode)
|
||||
{
|
||||
@ -102,15 +103,13 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
public async Task<IActionResult> UpdateLightningNetworkPaymentMethod(string cryptoCode,
|
||||
[FromBody] LightningNetworkPaymentMethodData paymentMethodData)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
|
||||
if (!GetNetwork(cryptoCode, out var network))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var internalLightning = GetInternalLightningNode(network.CryptoCode);
|
||||
|
||||
if (string.IsNullOrEmpty(paymentMethodData?.ConnectionString))
|
||||
{
|
||||
ModelState.AddModelError(nameof(LightningNetworkPaymentMethodData.ConnectionString),
|
||||
@ -120,91 +119,58 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
if (!ModelState.IsValid)
|
||||
return this.CreateValidationError(ModelState);
|
||||
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null;
|
||||
LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (!string.IsNullOrEmpty(paymentMethodData.ConnectionString))
|
||||
{
|
||||
if (!LightningConnectionString.TryParse(paymentMethodData.ConnectionString, false,
|
||||
out var connectionString, out var error))
|
||||
if (paymentMethodData.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"Invalid URL ({error})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
$"BTCPay does not support gRPC connections");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
bool isInternalNode = connectionString.IsInternalNode(internalLightning);
|
||||
|
||||
if (connectionString.BaseUri.Scheme == "http")
|
||||
{
|
||||
if (!isInternalNode && !connectionString.AllowInsecure)
|
||||
if (!await CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), "The url must be HTTPS");
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"You are not authorized to use the internal lightning node");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetInternalNode();
|
||||
}
|
||||
|
||||
if (connectionString.MacaroonFilePath != null)
|
||||
else
|
||||
{
|
||||
if (!CanUseInternalLightning())
|
||||
if (!LightningConnectionString.TryParse(paymentMethodData.ConnectionString, false,
|
||||
out var connectionString, out var error))
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"Invalid URL ({error})");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
"You are not authorized to use macaroonfilepath");
|
||||
$"BTCPay does not support gRPC connections");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (!System.IO.File.Exists(connectionString.MacaroonFilePath))
|
||||
if (!await CanManageServer() && !connectionString.IsSafe())
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
"The macaroonfilepath file does not exist");
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), $"You do not have 'btcpay.server.canmodifyserversettings' rights, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
if (!System.IO.Path.IsPathRooted(connectionString.MacaroonFilePath))
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString),
|
||||
"The macaroonfilepath should be fully rooted");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningUrl(connectionString);
|
||||
}
|
||||
|
||||
if (isInternalNode && !CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(paymentMethodData.ConnectionString), "Unauthorized url");
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetLightningUrl(connectionString);
|
||||
}
|
||||
|
||||
var store = Store;
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
store.SetSupportedPaymentMethod(id, paymentMethod);
|
||||
storeBlob.SetExcluded(id, !paymentMethodData.Enabled);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
|
||||
store.SetStoreBlob(storeBlob);
|
||||
await _storeRepository.UpdateStore(store);
|
||||
return Ok(GetExistingLightningLikePaymentMethod(cryptoCode, store));
|
||||
}
|
||||
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}/internal")]
|
||||
public Task<IActionResult> UpdateLightningNetworkPaymentMethodToInternalNode(string cryptoCode)
|
||||
{
|
||||
return UpdateLightningNetworkPaymentMethod(cryptoCode,
|
||||
new LightningNetworkPaymentMethodData(cryptoCode, GetInternalLightningNode(cryptoCode).ToString(), true));
|
||||
}
|
||||
|
||||
private LightningNetworkPaymentMethodData GetExistingLightningLikePaymentMethod(string cryptoCode, StoreData store = null)
|
||||
{
|
||||
store ??= Store;
|
||||
@ -219,7 +185,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return paymentMethod == null
|
||||
? null
|
||||
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
|
||||
paymentMethod.GetLightningUrl().ToString(), !excluded);
|
||||
paymentMethod.GetDisplayableConnectionString(), !excluded);
|
||||
}
|
||||
|
||||
private bool GetNetwork(string cryptoCode, out BTCPayNetwork network)
|
||||
@ -229,18 +195,17 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
return network != null;
|
||||
}
|
||||
|
||||
private LightningConnectionString GetInternalLightningNode(string cryptoCode)
|
||||
private async Task<bool> CanUseInternalLightning()
|
||||
{
|
||||
if (_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var connectionString))
|
||||
{
|
||||
return CanUseInternalLightning() ? connectionString : null;
|
||||
}
|
||||
return null;
|
||||
return _cssThemeManager.AllowLightningInternalNodeForAll ||
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
|
||||
}
|
||||
|
||||
private bool CanUseInternalLightning()
|
||||
private async Task<bool> CanManageServer()
|
||||
{
|
||||
return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || _cssThemeManager.AllowLightningInternalNodeForAll);
|
||||
return
|
||||
(await _authorizationService.AuthorizeAsync(User, null,
|
||||
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,563 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBitcoin.Payment;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers.GreenField
|
||||
{
|
||||
[ApiController]
|
||||
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
public class StoreOnChainWalletsController : Controller
|
||||
{
|
||||
private StoreData Store => HttpContext.GetStoreData();
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly BTCPayWalletProvider _btcPayWalletProvider;
|
||||
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
|
||||
private readonly WalletRepository _walletRepository;
|
||||
private readonly ExplorerClientProvider _explorerClientProvider;
|
||||
private readonly CssThemeManager _cssThemeManager;
|
||||
private readonly NBXplorerDashboard _nbXplorerDashboard;
|
||||
private readonly WalletsController _walletsController;
|
||||
private readonly PayjoinClient _payjoinClient;
|
||||
private readonly DelayedTransactionBroadcaster _delayedTransactionBroadcaster;
|
||||
private readonly EventAggregator _eventAggregator;
|
||||
private readonly WalletReceiveService _walletReceiveService;
|
||||
private readonly IFeeProviderFactory _feeProviderFactory;
|
||||
|
||||
public StoreOnChainWalletsController(
|
||||
IAuthorizationService authorizationService,
|
||||
BTCPayWalletProvider btcPayWalletProvider,
|
||||
BTCPayNetworkProvider btcPayNetworkProvider,
|
||||
WalletRepository walletRepository,
|
||||
ExplorerClientProvider explorerClientProvider,
|
||||
CssThemeManager cssThemeManager,
|
||||
NBXplorerDashboard nbXplorerDashboard,
|
||||
WalletsController walletsController,
|
||||
PayjoinClient payjoinClient,
|
||||
DelayedTransactionBroadcaster delayedTransactionBroadcaster,
|
||||
EventAggregator eventAggregator,
|
||||
WalletReceiveService walletReceiveService,
|
||||
IFeeProviderFactory feeProviderFactory)
|
||||
{
|
||||
_authorizationService = authorizationService;
|
||||
_btcPayWalletProvider = btcPayWalletProvider;
|
||||
_btcPayNetworkProvider = btcPayNetworkProvider;
|
||||
_walletRepository = walletRepository;
|
||||
_explorerClientProvider = explorerClientProvider;
|
||||
_cssThemeManager = cssThemeManager;
|
||||
_nbXplorerDashboard = nbXplorerDashboard;
|
||||
_walletsController = walletsController;
|
||||
_payjoinClient = payjoinClient;
|
||||
_delayedTransactionBroadcaster = delayedTransactionBroadcaster;
|
||||
_eventAggregator = eventAggregator;
|
||||
_walletReceiveService = walletReceiveService;
|
||||
_feeProviderFactory = feeProviderFactory;
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet")]
|
||||
public async Task<IActionResult> ShowOnChainWalletOverview(string storeId, string cryptoCode)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var balance = await wallet.GetBalance(derivationScheme.AccountDerivation);
|
||||
|
||||
return Ok(new OnChainWalletOverviewData()
|
||||
{
|
||||
Label = derivationScheme.ToPrettyString(),
|
||||
Balance = balance.Total.GetValue(network),
|
||||
UnconfirmedBalance= balance.Unconfirmed.GetValue(network),
|
||||
ConfirmedBalance= balance.Confirmed.GetValue(network),
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feerate")]
|
||||
public async Task<IActionResult> GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var feeRateTarget = blockTarget?? Store.GetStoreBlob().RecommendedFeeBlockTarget;
|
||||
return Ok(new OnChainWalletFeeRateData()
|
||||
{
|
||||
FeeRate = await _feeProviderFactory.CreateFeeProvider(network)
|
||||
.GetFeeRateAsync(feeRateTarget),
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address")]
|
||||
public async Task<IActionResult> GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var kpi = await _walletReceiveService.GetOrGenerate(new WalletId(storeId, cryptoCode), forceGenerate);
|
||||
if (kpi is null)
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var bip21 = network.GenerateBIP21(kpi.Address.ToString(), null);
|
||||
var allowedPayjoin = derivationScheme.IsHotWallet && Store.GetStoreBlob().PayJoinEnabled;
|
||||
if (allowedPayjoin)
|
||||
{
|
||||
bip21 +=
|
||||
$"?{PayjoinClient.BIP21EndpointKey}={Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new {cryptoCode}))}";
|
||||
}
|
||||
return Ok(new OnChainWalletAddressData()
|
||||
{
|
||||
Address = kpi.Address.ToString(),
|
||||
PaymentLink = bip21,
|
||||
KeyPath = kpi.KeyPath
|
||||
});
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address")]
|
||||
public async Task<IActionResult> UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var addr = await _walletReceiveService.UnReserveAddress(new WalletId(storeId, cryptoCode));
|
||||
if (addr is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
return Ok();
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions")]
|
||||
public async Task<IActionResult> ShowOnChainWalletTransactions(string storeId, string cryptoCode,
|
||||
[FromQuery]TransactionStatus[] statusFilter = null)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||
|
||||
var txs = await wallet.FetchTransactions(derivationScheme.AccountDerivation);
|
||||
var filteredFlatList = new List<TransactionInformation>();
|
||||
if (statusFilter is null || !statusFilter.Any() || statusFilter.Contains(TransactionStatus.Confirmed))
|
||||
{
|
||||
filteredFlatList.AddRange(txs.ConfirmedTransactions.Transactions);
|
||||
}
|
||||
|
||||
if (statusFilter is null || !statusFilter.Any() || statusFilter.Contains(TransactionStatus.Unconfirmed))
|
||||
{
|
||||
filteredFlatList.AddRange(txs.UnconfirmedTransactions.Transactions);
|
||||
}
|
||||
|
||||
if (statusFilter is null || !statusFilter.Any() ||statusFilter.Contains(TransactionStatus.Replaced))
|
||||
{
|
||||
filteredFlatList.AddRange(txs.ReplacedTransactions.Transactions);
|
||||
}
|
||||
|
||||
var result = filteredFlatList.Select(information =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(information.TransactionId.ToString(), out var transactionInfo);
|
||||
return ToModel(transactionInfo, information, wallet);
|
||||
}).ToList();
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}")]
|
||||
public async Task<IActionResult> GetOnChainWalletTransaction(string storeId, string cryptoCode,
|
||||
string transactionId)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
var tx = await wallet.FetchTransaction(derivationScheme.AccountDerivation, uint256.Parse(transactionId));
|
||||
if (tx is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync =
|
||||
(await _walletRepository.GetWalletTransactionsInfo(walletId, new[] {transactionId})).Values
|
||||
.FirstOrDefault();
|
||||
|
||||
return Ok(ToModel(walletTransactionsInfoAsync, tx, wallet));
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")]
|
||||
public async Task<IActionResult> GetOnChainWalletUTXOs(string storeId, string cryptoCode)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
|
||||
var walletId = new WalletId(storeId, cryptoCode);
|
||||
var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId);
|
||||
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
||||
return Ok(utxos.Select(coin =>
|
||||
{
|
||||
walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info);
|
||||
return new OnChainWalletUTXOData()
|
||||
{
|
||||
Outpoint = coin.OutPoint,
|
||||
Amount = coin.Value.GetValue(network),
|
||||
Comment = info?.Comment,
|
||||
Labels = info?.Labels,
|
||||
Link = string.Format(CultureInfo.InvariantCulture, network.BlockExplorerLink,
|
||||
coin.OutPoint.Hash.ToString()),
|
||||
Timestamp = coin.Timestamp,
|
||||
KeyPath = coin.KeyPath,
|
||||
Address = network.NBXplorerNetwork.CreateAddress(derivationScheme.AccountDerivation, coin.KeyPath, coin.ScriptPubKey).ToString()
|
||||
};
|
||||
}).ToList()
|
||||
);
|
||||
}
|
||||
|
||||
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
|
||||
[HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions")]
|
||||
public async Task<IActionResult> CreateOnChainTransaction(string storeId, string cryptoCode,
|
||||
[FromBody] CreateOnChainTransactionRequest request)
|
||||
{
|
||||
if (IsInvalidWalletRequest(cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)) return actionResult;
|
||||
if (network.ReadonlyWallet)
|
||||
{
|
||||
return this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} sending services are not currently available");
|
||||
}
|
||||
|
||||
//This API is only meant for hot wallet usage for now. We can expand later when we allow PSBT manipulation.
|
||||
if (!(await CanUseHotWallet()).HotWallet)
|
||||
{
|
||||
return Unauthorized();
|
||||
}
|
||||
|
||||
var explorerClient = _explorerClientProvider.GetExplorerClient(cryptoCode);
|
||||
var wallet = _btcPayWalletProvider.GetWallet(network);
|
||||
|
||||
var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation);
|
||||
if (request.SelectedInputs != null || !utxos.Any())
|
||||
{
|
||||
utxos = utxos.Where(coin => request.SelectedInputs?.Contains(coin.OutPoint) ?? true)
|
||||
.ToArray();
|
||||
if (utxos.Any() is false)
|
||||
{
|
||||
//no valid utxos selected
|
||||
request.AddModelError(transactionRequest => transactionRequest.SelectedInputs,
|
||||
"There are no available utxos based on your request", this);
|
||||
}
|
||||
}
|
||||
|
||||
var balanceAvailable = utxos.Sum(coin => coin.Value.GetValue(network));
|
||||
|
||||
var subtractFeesOutputsCount = new List<int>();
|
||||
var subtractFees = request.Destinations.Any(o => o.SubtractFromAmount);
|
||||
int? payjoinOutputIndex = null;
|
||||
var sum = 0m;
|
||||
var outputs = new List<WalletSendModel.TransactionOutput>();
|
||||
for (var index = 0; index < request.Destinations.Count; index++)
|
||||
{
|
||||
var destination = request.Destinations[index];
|
||||
|
||||
if (destination.SubtractFromAmount)
|
||||
{
|
||||
subtractFeesOutputsCount.Add(index);
|
||||
}
|
||||
|
||||
BitcoinUrlBuilder bip21 = null;
|
||||
var amount = destination.Amount;
|
||||
if (amount.GetValueOrDefault(0) <= 0)
|
||||
{
|
||||
amount = null;
|
||||
}
|
||||
var address = string.Empty;
|
||||
try
|
||||
{
|
||||
destination.Destination = destination.Destination.Replace(network.UriScheme+":", "bitcoin:", StringComparison.InvariantCultureIgnoreCase);
|
||||
bip21 = new BitcoinUrlBuilder(destination.Destination, network.NBitcoinNetwork);
|
||||
amount ??= bip21.Amount.GetValue(network);
|
||||
address = bip21.Address.ToString();
|
||||
if (destination.SubtractFromAmount)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
|
||||
"You cannot use a BIP21 destination along with SubtractFromAmount", this);
|
||||
}
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
try
|
||||
{
|
||||
address = BitcoinAddress.Create(destination.Destination, network.NBitcoinNetwork).ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
|
||||
"Destination must be a BIP21 payment link or an address", this);
|
||||
}
|
||||
}
|
||||
|
||||
if (amount is null || amount <= 0)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations[index],
|
||||
"Amount must be specified or destination must be a BIP21 payment link, and greater than 0", this);
|
||||
}
|
||||
if (request.ProceedWithPayjoin && bip21?.UnknowParameters?.ContainsKey(PayjoinClient.BIP21EndpointKey) is true)
|
||||
{
|
||||
payjoinOutputIndex = index;
|
||||
}
|
||||
|
||||
outputs.Add(new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
DestinationAddress = address,
|
||||
Amount = amount,
|
||||
SubtractFeesFromOutput = destination.SubtractFromAmount
|
||||
});
|
||||
sum += destination.Amount ?? 0;
|
||||
}
|
||||
|
||||
if (subtractFeesOutputsCount.Count > 1)
|
||||
{
|
||||
foreach (var subtractFeesOutput in subtractFeesOutputsCount)
|
||||
{
|
||||
request.AddModelError(model => model.Destinations[subtractFeesOutput].SubtractFromAmount,
|
||||
"You can only subtract fees from one destination", this);
|
||||
}
|
||||
}
|
||||
|
||||
if (balanceAvailable < sum)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations,
|
||||
"You are attempting to send more than is available", this);
|
||||
}
|
||||
else if (balanceAvailable == sum && !subtractFees)
|
||||
{
|
||||
request.AddModelError(transactionRequest => transactionRequest.Destinations,
|
||||
"You are sending your entire balance, you should subtract the fees from a destination", this);
|
||||
}
|
||||
|
||||
var minRelayFee = _nbXplorerDashboard.Get(network.CryptoCode).Status.BitcoinStatus?.MinRelayTxFee ??
|
||||
new FeeRate(1.0m);
|
||||
if (request.FeeRate != null && request.FeeRate < minRelayFee)
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.FeeRate),
|
||||
"The fee rate specified is lower than the current minimum relay fee");
|
||||
}
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
{
|
||||
return this.CreateValidationError(ModelState);
|
||||
}
|
||||
|
||||
CreatePSBTResponse psbt;
|
||||
try
|
||||
{
|
||||
psbt = await _walletsController.CreatePSBT(network, derivationScheme,
|
||||
new WalletSendModel()
|
||||
{
|
||||
SelectedInputs = request.SelectedInputs?.Select(point => point.ToString()),
|
||||
Outputs = outputs,
|
||||
AlwaysIncludeNonWitnessUTXO = true,
|
||||
InputSelection = request.SelectedInputs?.Any() is true,
|
||||
AllowFeeBump =
|
||||
!request.RBF.HasValue ? WalletSendModel.ThreeStateBool.Maybe :
|
||||
request.RBF.Value ? WalletSendModel.ThreeStateBool.Yes :
|
||||
WalletSendModel.ThreeStateBool.No,
|
||||
FeeSatoshiPerByte = request.FeeRate?.SatoshiPerByte,
|
||||
NoChange = request.NoChange
|
||||
},
|
||||
CancellationToken.None);
|
||||
}
|
||||
catch (NBXplorerException ex)
|
||||
{
|
||||
return this.CreateAPIError(ex.Error.Code, ex.Error.Message);
|
||||
}
|
||||
catch (NotSupportedException)
|
||||
{
|
||||
return this.CreateAPIError("not-available", "You need to update your version of NBXplorer");
|
||||
}
|
||||
|
||||
derivationScheme.RebaseKeyPaths(psbt.PSBT);
|
||||
|
||||
var signingContext = new SigningContextModel()
|
||||
{
|
||||
PayJoinBIP21 =
|
||||
payjoinOutputIndex is null
|
||||
? null
|
||||
: request.Destinations.ElementAt(payjoinOutputIndex.Value).Destination,
|
||||
EnforceLowR = psbt.Suggestions?.ShouldEnforceLowR,
|
||||
ChangeAddress = psbt.ChangeAddress?.ToString()
|
||||
};
|
||||
|
||||
var signingKeyStr = await explorerClient
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey);
|
||||
if (!derivationScheme.IsHotWallet || signingKeyStr is null)
|
||||
{
|
||||
return this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} sending services are not currently available");
|
||||
}
|
||||
|
||||
var signingKey = ExtKey.Parse(signingKeyStr, network.NBitcoinNetwork);
|
||||
|
||||
var signingKeySettings = derivationScheme.GetSigningAccountKeySettings();
|
||||
signingKeySettings.RootFingerprint ??= signingKey.GetPublicKey().GetHDFingerPrint();
|
||||
RootedKeyPath rootedKeyPath = signingKeySettings.GetRootedKeyPath();
|
||||
psbt.PSBT.RebaseKeyPaths(signingKeySettings.AccountKey, rootedKeyPath);
|
||||
var accountKey = signingKey.Derive(rootedKeyPath.KeyPath);
|
||||
|
||||
var changed = psbt.PSBT.PSBTChanged(() => psbt.PSBT.SignAll(derivationScheme.AccountDerivation, accountKey,
|
||||
rootedKeyPath, new SigningOptions() {EnforceLowR = signingContext?.EnforceLowR is bool v ? v : psbt.Suggestions.ShouldEnforceLowR }));
|
||||
|
||||
if (!changed)
|
||||
{
|
||||
return this.CreateAPIError("psbt-signing-error",
|
||||
"Impossible to sign the transaction. Probable cause: Incorrect account key path in wallet settings, PSBT already signed.");
|
||||
}
|
||||
|
||||
psbt.PSBT.Finalize();
|
||||
var transaction = psbt.PSBT.ExtractTransaction();
|
||||
var transactionHash = transaction.GetHash();
|
||||
BroadcastResult broadcastResult;
|
||||
if (!string.IsNullOrEmpty(signingContext.PayJoinBIP21))
|
||||
{
|
||||
signingContext.OriginalPSBT = psbt.PSBT.ToBase64();
|
||||
try
|
||||
{
|
||||
await _delayedTransactionBroadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0),
|
||||
transaction, network);
|
||||
var payjoinPSBT = await _payjoinClient.RequestPayjoin(
|
||||
new BitcoinUrlBuilder(signingContext.PayJoinBIP21, network.NBitcoinNetwork), new PayjoinWallet(derivationScheme),
|
||||
psbt.PSBT, CancellationToken.None);
|
||||
payjoinPSBT = psbt.PSBT.SignAll(derivationScheme.AccountDerivation, accountKey, rootedKeyPath,
|
||||
new SigningOptions() {EnforceLowR = !(signingContext?.EnforceLowR is false)});
|
||||
payjoinPSBT.Finalize();
|
||||
var payjoinTransaction = payjoinPSBT.ExtractTransaction();
|
||||
var hash = payjoinTransaction.GetHash();
|
||||
_eventAggregator.Publish(new UpdateTransactionLabel(new WalletId(Store.Id, cryptoCode), hash,
|
||||
UpdateTransactionLabel.PayjoinLabelTemplate()));
|
||||
broadcastResult = await explorerClient.BroadcastAsync(payjoinTransaction);
|
||||
if (broadcastResult.Success)
|
||||
{
|
||||
return await GetOnChainWalletTransaction(storeId, cryptoCode, hash.ToString());
|
||||
}
|
||||
}
|
||||
catch (PayjoinException)
|
||||
{
|
||||
//not a critical thing, payjoin is great if possible, fine if not
|
||||
}
|
||||
}
|
||||
|
||||
if (!request.ProceedWithBroadcast)
|
||||
{
|
||||
return Ok(new JValue(transaction.ToHex()));
|
||||
}
|
||||
|
||||
broadcastResult = await explorerClient.BroadcastAsync(transaction);
|
||||
if (broadcastResult.Success)
|
||||
{
|
||||
return await GetOnChainWalletTransaction(storeId, cryptoCode, transactionHash.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
return this.CreateAPIError("broadcast-error", broadcastResult.RPCMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
return await _authorizationService.CanUseHotWallet(_cssThemeManager.Policies, User);
|
||||
}
|
||||
|
||||
private bool IsInvalidWalletRequest(string cryptoCode, out BTCPayNetwork network,
|
||||
out DerivationSchemeSettings derivationScheme, out IActionResult actionResult)
|
||||
{
|
||||
derivationScheme = null;
|
||||
actionResult = null;
|
||||
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
if (network is null)
|
||||
{
|
||||
actionResult = NotFound();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!network.WalletSupported || !_btcPayWalletProvider.IsAvailable(network))
|
||||
{
|
||||
actionResult = this.CreateAPIError("not-available",
|
||||
$"{cryptoCode} services are not currently available");
|
||||
return true;
|
||||
}
|
||||
|
||||
derivationScheme = GetDerivationSchemeSettings(cryptoCode);
|
||||
if (derivationScheme?.AccountDerivation is null)
|
||||
{
|
||||
actionResult = NotFound();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings GetDerivationSchemeSettings(string cryptoCode)
|
||||
{
|
||||
var paymentMethod = Store
|
||||
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(p =>
|
||||
p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike &&
|
||||
p.PaymentId.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCultureIgnoreCase));
|
||||
return paymentMethod;
|
||||
}
|
||||
|
||||
private OnChainWalletTransactionData ToModel(WalletTransactionInfo walletTransactionsInfoAsync,
|
||||
TransactionInformation tx,
|
||||
BTCPayWallet wallet)
|
||||
{
|
||||
return new OnChainWalletTransactionData()
|
||||
{
|
||||
TransactionHash = tx.TransactionId,
|
||||
Comment = walletTransactionsInfoAsync?.Comment?? string.Empty,
|
||||
Labels = walletTransactionsInfoAsync?.Labels?? new Dictionary<string, LabelData>(),
|
||||
Amount = tx.BalanceChange.GetValue(wallet.Network),
|
||||
BlockHash = tx.BlockHash,
|
||||
BlockHeight = tx.Height,
|
||||
Confirmations = tx.Confirmations,
|
||||
Timestamp = tx.Timestamp,
|
||||
Status = tx.Confirmations > 0 ? TransactionStatus.Confirmed :
|
||||
tx.ReplacedBy != null ? TransactionStatus.Replaced : TransactionStatus.Unconfirmed
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -131,6 +131,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints,
|
||||
OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback,
|
||||
RedirectAutomatically = storeBlob.RedirectAutomatically,
|
||||
LazyPaymentMethods = storeBlob.LazyPaymentMethods,
|
||||
ShowRecommendedFee = storeBlob.ShowRecommendedFee,
|
||||
RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget,
|
||||
DefaultLang = storeBlob.DefaultLang,
|
||||
@ -167,6 +168,7 @@ namespace BTCPayServer.Controllers.GreenField
|
||||
blob.LightningAmountInSatoshi = restModel.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = restModel.LightningPrivateRouteHints;
|
||||
blob.OnChainWithLnInvoiceFallback = restModel.OnChainWithLnInvoiceFallback;
|
||||
blob.LazyPaymentMethods = restModel.LazyPaymentMethods;
|
||||
blob.RedirectAutomatically = restModel.RedirectAutomatically;
|
||||
blob.ShowRecommendedFee = restModel.ShowRecommendedFee;
|
||||
blob.RecommendedFeeBlockTarget = restModel.RecommendedFeeBlockTarget;
|
||||
|
@ -7,6 +7,7 @@ using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
@ -48,71 +49,18 @@ namespace BTCPayServer.Controllers
|
||||
SignInManager = signInManager;
|
||||
}
|
||||
|
||||
private async Task<ViewResult> GoToApp(string appId, AppType? appType)
|
||||
{
|
||||
if (appType.HasValue && !string.IsNullOrEmpty(appId))
|
||||
{
|
||||
this.HttpContext.Response.Headers.Remove("Onion-Location");
|
||||
switch (appType.Value)
|
||||
{
|
||||
case AppType.Crowdfund:
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewCrowdfund(appId, null) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = $"/Views/AppsPublic/ViewCrowdfund.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case AppType.PointOfSale:
|
||||
{
|
||||
var serviceProvider = HttpContext.RequestServices;
|
||||
var controller = (AppsPublicController)serviceProvider.GetService(typeof(AppsPublicController));
|
||||
controller.Url = Url;
|
||||
controller.ControllerContext = ControllerContext;
|
||||
var res = await controller.ViewPointOfSale(appId) as ViewResult;
|
||||
if (res != null)
|
||||
{
|
||||
res.ViewName = $"/Views/AppsPublic/{res.ViewName}.cshtml";
|
||||
return res; // return
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<IActionResult> Index()
|
||||
[Route("")]
|
||||
[DomainMappingConstraint()]
|
||||
public IActionResult Index()
|
||||
{
|
||||
if (_cachedServerSettings.FirstRun)
|
||||
{
|
||||
return RedirectToAction(nameof(AccountController.Register), "Account");
|
||||
}
|
||||
var matchedDomainMapping = _cachedServerSettings.DomainToAppMapping.FirstOrDefault(item =>
|
||||
item.Domain.Equals(Request.Host.Host, StringComparison.InvariantCultureIgnoreCase));
|
||||
if (matchedDomainMapping != null)
|
||||
{
|
||||
return await GoToApp(matchedDomainMapping.AppId, matchedDomainMapping.AppType) ?? GoToHome();
|
||||
}
|
||||
|
||||
return await GoToApp(_cachedServerSettings.RootAppId, _cachedServerSettings.RootAppType) ?? GoToHome();
|
||||
}
|
||||
|
||||
private IActionResult GoToHome()
|
||||
{
|
||||
if (SignInManager.IsSignedIn(User))
|
||||
return View("Home");
|
||||
else
|
||||
return RedirectToAction(nameof(AccountController.Login), "Account");
|
||||
return Challenge();
|
||||
}
|
||||
|
||||
[Route("misc/lang")]
|
||||
|
@ -14,6 +14,7 @@ using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Filters;
|
||||
using BTCPayServer.Logging;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.InvoicingModels;
|
||||
@ -25,7 +26,6 @@ using BTCPayServer.Security;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Invoices.Export;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using DBriize.Utils;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||
@ -504,9 +504,9 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
if (!isDefaultPaymentId)
|
||||
return null;
|
||||
var paymentMethodTemp = invoice.GetPaymentMethods()
|
||||
.Where(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode)
|
||||
.FirstOrDefault();
|
||||
var paymentMethodTemp = invoice
|
||||
.GetPaymentMethods()
|
||||
.FirstOrDefault(c => paymentMethodId.CryptoCode == c.GetId().CryptoCode);
|
||||
if (paymentMethodTemp == null)
|
||||
paymentMethodTemp = invoice.GetPaymentMethods().First();
|
||||
network = paymentMethodTemp.Network;
|
||||
@ -515,6 +515,12 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
var paymentMethod = invoice.GetPaymentMethod(paymentMethodId);
|
||||
var paymentMethodDetails = paymentMethod.GetPaymentMethodDetails();
|
||||
if (!paymentMethodDetails.Activated)
|
||||
{
|
||||
await _InvoiceRepository.ActivateInvoicePaymentMethod(_EventAggregator, _NetworkProvider,
|
||||
_paymentMethodHandlerDictionary, store, invoice, paymentMethod.GetId());
|
||||
return await GetInvoiceModel(invoiceId, paymentMethodId, lang);
|
||||
}
|
||||
var dto = invoice.EntityToDTO();
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var accounting = paymentMethod.Calculate();
|
||||
@ -530,6 +536,7 @@ namespace BTCPayServer.Controllers
|
||||
var divisibility = _CurrencyNameTable.GetNumberFormatInfo(paymentMethod.GetId().CryptoCode, false)?.CurrencyDecimalDigits;
|
||||
var model = new PaymentModel()
|
||||
{
|
||||
Activated = paymentMethodDetails.Activated,
|
||||
CryptoCode = network.CryptoCode,
|
||||
RootPath = this.Request.PathBase.Value.WithTrailingSlash(),
|
||||
OrderId = invoice.Metadata.OrderId,
|
||||
|
@ -44,12 +44,10 @@ namespace BTCPayServer.Controllers
|
||||
private readonly PaymentMethodHandlerDictionary _paymentMethodHandlerDictionary;
|
||||
private readonly ApplicationDbContextFactory _dbContextFactory;
|
||||
private readonly PullPaymentHostedService _paymentHostedService;
|
||||
readonly IServiceProvider _ServiceProvider;
|
||||
|
||||
public WebhookNotificationManager WebhookNotificationManager { get; }
|
||||
|
||||
public InvoiceController(
|
||||
IServiceProvider serviceProvider,
|
||||
InvoiceRepository invoiceRepository,
|
||||
CurrencyNameTable currencyNameTable,
|
||||
UserManager<ApplicationUser> userManager,
|
||||
@ -63,7 +61,6 @@ namespace BTCPayServer.Controllers
|
||||
PullPaymentHostedService paymentHostedService,
|
||||
WebhookNotificationManager webhookNotificationManager)
|
||||
{
|
||||
_ServiceProvider = serviceProvider;
|
||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||
@ -173,6 +170,7 @@ namespace BTCPayServer.Controllers
|
||||
entity.Price = invoice.Amount;
|
||||
entity.SpeedPolicy = invoice.Checkout.SpeedPolicy ?? store.SpeedPolicy;
|
||||
entity.DefaultLanguage = invoice.Checkout.DefaultLanguage;
|
||||
entity.RedirectAutomatically = invoice.Checkout.RedirectAutomatically ?? storeBlob.RedirectAutomatically;
|
||||
IPaymentFilter excludeFilter = null;
|
||||
if (invoice.Checkout.PaymentMethods != null)
|
||||
{
|
||||
@ -320,7 +318,16 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
var logPrefix = $"{supportedPaymentMethod.PaymentId.ToPrettyString()}:";
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||
|
||||
object preparePayment;
|
||||
if (storeBlob.LazyPaymentMethods)
|
||||
{
|
||||
preparePayment = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
preparePayment = handler.PreparePayment(supportedPaymentMethod, store, network);
|
||||
}
|
||||
var rate = await fetchingByCurrencyPair[new CurrencyPair(network.CryptoCode, entity.Currency)];
|
||||
if (rate.BidAsk == null)
|
||||
{
|
||||
|
@ -82,7 +82,6 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
Username = user.UserName,
|
||||
Email = user.Email,
|
||||
PhoneNumber = user.PhoneNumber,
|
||||
IsEmailConfirmed = user.EmailConfirmed
|
||||
};
|
||||
return View(model);
|
||||
@ -97,8 +96,6 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
bool needUpdate = false;
|
||||
|
||||
var user = await _userManager.GetUserAsync(User);
|
||||
if (user == null)
|
||||
{
|
||||
@ -108,33 +105,22 @@ namespace BTCPayServer.Controllers
|
||||
var email = user.Email;
|
||||
if (model.Email != email)
|
||||
{
|
||||
if (!(await _userManager.FindByEmailAsync(model.Email) is null))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "The email address is already in use with an other account.";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
var setUserResult = await _userManager.SetUserNameAsync(user, model.Email);
|
||||
if (!setUserResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
|
||||
}
|
||||
var setEmailResult = await _userManager.SetEmailAsync(user, model.Email);
|
||||
if (!setEmailResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred setting email for user with ID '{user.Id}'.");
|
||||
}
|
||||
await _userManager.SetUserNameAsync(user, model.Username);
|
||||
}
|
||||
|
||||
var phoneNumber = user.PhoneNumber;
|
||||
if (model.PhoneNumber != phoneNumber)
|
||||
{
|
||||
var setPhoneResult = await _userManager.SetPhoneNumberAsync(user, model.PhoneNumber);
|
||||
if (!setPhoneResult.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred setting phone number for user with ID '{user.Id}'.");
|
||||
}
|
||||
}
|
||||
|
||||
if (needUpdate)
|
||||
{
|
||||
var result = await _userManager.UpdateAsync(user);
|
||||
if (!result.Succeeded)
|
||||
{
|
||||
throw new ApplicationException($"Unexpected error occurred updating user with ID '{user.Id}'.");
|
||||
}
|
||||
}
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Your profile has been updated";
|
||||
return RedirectToAction(nameof(Index));
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ namespace BTCPayServer.Controllers
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Generate(string version)
|
||||
{
|
||||
if (_env.NetworkType != NBitcoin.NetworkType.Regtest)
|
||||
if (_env.NetworkType != NBitcoin.ChainName.Regtest)
|
||||
return NotFound();
|
||||
await _notificationSender.SendNotification(new AdminScope(), new NewVersionNotification(version));
|
||||
return RedirectToAction(nameof(Index));
|
||||
|
@ -70,7 +70,8 @@ namespace BTCPayServer.Controllers
|
||||
ClaimedAmount = amountDue,
|
||||
AmountDueFormatted = _currencyNameTable.FormatCurrency(amountDue, blob.Currency),
|
||||
CurrencyData = cd,
|
||||
LastUpdated = DateTime.Now,
|
||||
StartDate = pp.StartDate,
|
||||
LastRefreshed = DateTime.Now,
|
||||
Payouts = payouts
|
||||
.Select(entity => new ViewPullPaymentModel.PayoutLine
|
||||
{
|
||||
|
@ -39,6 +39,7 @@ namespace BTCPayServer.Controllers
|
||||
Installed = pluginService.LoadedPlugins,
|
||||
Available = availablePlugins,
|
||||
Commands = pluginService.GetPendingCommands(),
|
||||
Disabled = pluginService.GetDisabledPlugins(),
|
||||
CanShowRestart = btcPayServerOptions.DockerDeployment
|
||||
};
|
||||
return View(res);
|
||||
@ -50,6 +51,7 @@ namespace BTCPayServer.Controllers
|
||||
public IEnumerable<PluginService.AvailablePlugin> Available { get; set; }
|
||||
public (string command, string plugin)[] Commands { get; set; }
|
||||
public bool CanShowRestart { get; set; }
|
||||
public string[] Disabled { get; set; }
|
||||
}
|
||||
|
||||
[HttpPost("server/plugins/uninstall")]
|
||||
@ -117,7 +119,7 @@ namespace BTCPayServer.Controllers
|
||||
public async Task<IActionResult> UploadPlugin([FromServices] PluginService pluginService,
|
||||
List<IFormFile> files)
|
||||
{
|
||||
foreach (var formFile in files.Where(file => file.Length > 0))
|
||||
foreach (var formFile in files.Where(file => file.Length > 0).Where(file => file.FileName.IsValidFileName()))
|
||||
{
|
||||
await pluginService.UploadPlugin(formFile);
|
||||
pluginService.InstallPlugin(formFile.FileName.TrimEnd(PluginManager.BTCPayPluginSuffix,
|
||||
|
@ -146,6 +146,15 @@ namespace BTCPayServer.Controllers
|
||||
[HttpPost("server/files/upload")]
|
||||
public async Task<IActionResult> CreateFile(IFormFile file)
|
||||
{
|
||||
if (!file.FileName.IsValidFileName())
|
||||
{
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Message = "Invalid file name",
|
||||
Severity = StatusMessageModel.StatusSeverity.Error
|
||||
});
|
||||
return RedirectToAction(nameof(Files));
|
||||
}
|
||||
var newFile = await _FileService.AddFile(file, GetUserId());
|
||||
return RedirectToAction(nameof(Files), new
|
||||
{
|
||||
|
@ -19,13 +19,37 @@ namespace BTCPayServer.Controllers
|
||||
public partial class ServerController
|
||||
{
|
||||
[Route("server/users")]
|
||||
public async Task<IActionResult> ListUsers(UsersViewModel model)
|
||||
public async Task<IActionResult> ListUsers(
|
||||
UsersViewModel model,
|
||||
string sortOrder = null
|
||||
)
|
||||
{
|
||||
model = this.ParseListQuery(model ?? new UsersViewModel());
|
||||
var users = _UserManager.Users;
|
||||
model.Total = await users.CountAsync();
|
||||
model.Users = await users
|
||||
.Skip(model.Skip).Take(model.Count)
|
||||
|
||||
var usersQuery = _UserManager.Users;
|
||||
if (!string.IsNullOrWhiteSpace(model.SearchTerm))
|
||||
{
|
||||
usersQuery = usersQuery.Where(u => u.Email.Contains(model.SearchTerm));
|
||||
}
|
||||
|
||||
if (sortOrder != null)
|
||||
{
|
||||
switch (sortOrder)
|
||||
{
|
||||
case "desc":
|
||||
ViewData["NextUserEmailSortOrder"] = "asc";
|
||||
usersQuery = usersQuery.OrderByDescending(user => user.Email);
|
||||
break;
|
||||
case "asc":
|
||||
usersQuery = usersQuery.OrderBy(user => user.Email);
|
||||
ViewData["NextUserEmailSortOrder"] = "desc";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
model.Users = await usersQuery
|
||||
.Skip(model.Skip)
|
||||
.Take(model.Count)
|
||||
.Select(u => new UsersViewModel.UserViewModel
|
||||
{
|
||||
Name = u.UserName,
|
||||
@ -33,7 +57,9 @@ namespace BTCPayServer.Controllers
|
||||
Id = u.Id,
|
||||
Verified = u.EmailConfirmed || !u.RequiresEmailConfirmation,
|
||||
Created = u.Created
|
||||
}).ToListAsync();
|
||||
})
|
||||
.ToListAsync();
|
||||
model.Total = await usersQuery.CountAsync();
|
||||
|
||||
return View(model);
|
||||
}
|
||||
|
@ -1,400 +0,0 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class StoresController
|
||||
{
|
||||
[HttpGet]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
DerivationSchemeViewModel vm = new DerivationSchemeViewModel();
|
||||
vm.CryptoCode = cryptoCode;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
vm.Network = network;
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
if (derivation != null)
|
||||
{
|
||||
vm.DerivationScheme = derivation.AccountDerivation.ToString();
|
||||
vm.Config = derivation.ToJson();
|
||||
}
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
var hotWallet = await CanUseHotWallet();
|
||||
vm.CanUseHotWallet = hotWallet.HotWallet;
|
||||
vm.CanUseRPCImport = hotWallet.RPCImport;
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations/{cryptoCode}")]
|
||||
[ApiExplorerSettings(IgnoreApi = true)]
|
||||
public async Task<IActionResult> AddDerivationScheme(string storeId, [FromForm] DerivationSchemeViewModel vm,
|
||||
string cryptoCode)
|
||||
{
|
||||
vm.CryptoCode = cryptoCode;
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
|
||||
var network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
if (network == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
vm.Network = network;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
DerivationSchemeSettings strategy = null;
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vm.Config))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Config file was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.WalletFile != null)
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network,
|
||||
out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "Wallet file was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.WalletFileContent))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Message = "QR import was not in the correct format"
|
||||
});
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
|
||||
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
|
||||
{
|
||||
var accountKey = string.IsNullOrEmpty(vm.AccountKey)
|
||||
? null
|
||||
: new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
|
||||
if (accountKey != null)
|
||||
{
|
||||
var accountSettings =
|
||||
newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
|
||||
if (accountSettings != null)
|
||||
{
|
||||
accountSettings.AccountKeyPath =
|
||||
vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
|
||||
? (HDFingerprint?)null
|
||||
: new HDFingerprint(
|
||||
NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
}
|
||||
}
|
||||
|
||||
strategy = newStrategy;
|
||||
strategy.Source = vm.Source;
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
strategy = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
vm.Confirmation = false;
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
|
||||
var oldConfig = vm.Config;
|
||||
vm.Config = strategy == null ? null : strategy.ToJson();
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var exisingStrategy = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.Where(c => c.PaymentId == paymentMethodId)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault();
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
|
||||
var willBeExcluded = !vm.Enabled;
|
||||
|
||||
var showAddress = // Show addresses if:
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
||||
// - The user is clicking on continue after changing the config
|
||||
(!vm.Confirmation && oldConfig != vm.Config) ||
|
||||
// - The user is clicking on continue without changing config nor enabling/disabling
|
||||
(!vm.Confirmation && oldConfig == vm.Config && willBeExcluded == wasExcluded);
|
||||
|
||||
showAddress = showAddress && strategy != null;
|
||||
if (!showAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
|
||||
storeBlob.Hints.Wallet = false;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid Derivation Scheme");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent() {WalletId = new WalletId(storeId, cryptoCode)});
|
||||
|
||||
if (willBeExcluded != wasExcluded)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"On-Chain payments for {network.CryptoCode} has been {label}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} has been modified.";
|
||||
}
|
||||
|
||||
// This is success case when derivation scheme is added to the store
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = storeId});
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||
{
|
||||
BitcoinAddress address = null;
|
||||
try
|
||||
{
|
||||
address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address");
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network);
|
||||
if (newStrategy.AccountDerivation != strategy.AccountDerivation)
|
||||
{
|
||||
strategy.AccountDerivation = newStrategy.AccountDerivation;
|
||||
strategy.AccountOriginal = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address");
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
vm.HintAddress = "";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
||||
ModelState.Remove(nameof(vm.HintAddress));
|
||||
ModelState.Remove(nameof(vm.DerivationScheme));
|
||||
}
|
||||
|
||||
return ShowAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/derivations/{cryptoCode}/generatenbxwallet")]
|
||||
public async Task<IActionResult> GenerateNBXWallet(string storeId, string cryptoCode,
|
||||
GenerateWalletRequest request)
|
||||
{
|
||||
var hotWallet = await CanUseHotWallet();
|
||||
if (!hotWallet.HotWallet || (!hotWallet.RPCImport && request.ImportKeysToRPC))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var network = _NetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
|
||||
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
||||
GenerateWalletResponse response;
|
||||
try
|
||||
{
|
||||
response = await client.GenerateWalletAsync(request);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"There was an error generating your wallet: {e.Message}"
|
||||
});
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
|
||||
}
|
||||
|
||||
if (response == null)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = "There was an error generating your wallet. Is your node available?"
|
||||
});
|
||||
return RedirectToAction(nameof(AddDerivationScheme), new {storeId, cryptoCode});
|
||||
}
|
||||
|
||||
var store = HttpContext.GetStoreData();
|
||||
var result = await AddDerivationScheme(storeId,
|
||||
new DerivationSchemeViewModel()
|
||||
{
|
||||
Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic),
|
||||
Network = network,
|
||||
RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString(),
|
||||
RootKeyPath = network.GetRootKeyPath(),
|
||||
CryptoCode = cryptoCode,
|
||||
DerivationScheme = response.DerivationScheme.ToString(),
|
||||
Source = "NBXplorer",
|
||||
AccountKey = response.AccountHDKey.Neuter().ToWif(),
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
KeyPath = response.AccountKeyPath.KeyPath.ToString(),
|
||||
Enabled = !store.GetStoreBlob()
|
||||
.IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike))
|
||||
}, cryptoCode);
|
||||
if (!ModelState.IsValid || !(result is RedirectToActionResult))
|
||||
return result;
|
||||
TempData.Clear();
|
||||
if (string.IsNullOrEmpty(request.ExistingMnemonic))
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = $"<span class='text-centered'>Your wallet has been generated.</span>"
|
||||
});
|
||||
var vm = new RecoverySeedBackupViewModel()
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
Mnemonic = response.Mnemonic,
|
||||
Passphrase = response.Passphrase,
|
||||
IsStored = request.SavePrivateKeys,
|
||||
ReturnUrl = Url.Action(nameof(UpdateStore), new {storeId})
|
||||
};
|
||||
return this.RedirectToRecoverySeedBackup(vm);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = "Please check your addresses and confirm"
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings))
|
||||
.Succeeded;
|
||||
if (isAdmin)
|
||||
return (true, true);
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
var hotWallet = policies?.AllowHotWalletForAll is true;
|
||||
return (hotWallet, hotWallet && policies?.AllowHotWalletRPCImportForAll is true);
|
||||
}
|
||||
|
||||
private async Task<string> ReadAllText(IFormFile file)
|
||||
{
|
||||
using (var stream = new StreamReader(file.OpenReadStream()))
|
||||
{
|
||||
return await stream.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private IActionResult
|
||||
ShowAddresses(DerivationSchemeViewModel vm, DerivationSchemeSettings strategy)
|
||||
{
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
var deposit = new NBXplorer.KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
var keyPath = deposit.GetKeyPath((uint)i);
|
||||
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
||||
var derivation = line.Derive((uint)i);
|
||||
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath((uint)i),
|
||||
derivation.ScriptPubKey).ToString();
|
||||
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
||||
}
|
||||
}
|
||||
vm.Confirmation = true;
|
||||
ModelState.Remove(nameof(vm.Config)); // Remove the cached value
|
||||
return View(nameof(AddDerivationScheme), vm);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,189 +1,26 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Shopify;
|
||||
using BTCPayServer.Services.Shopify.ApiModels;
|
||||
using BTCPayServer.Services.Shopify.Models;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Cors;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NBitcoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NicolasDorier.RateLimits;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class StoresController
|
||||
{
|
||||
private static string _cachedShopifyJavascript;
|
||||
|
||||
private async Task<string> GetJavascript()
|
||||
{
|
||||
if (!string.IsNullOrEmpty(_cachedShopifyJavascript) && !_BTCPayEnv.IsDeveloping)
|
||||
{
|
||||
return _cachedShopifyJavascript;
|
||||
}
|
||||
|
||||
string[] fileList = _BtcpayServerOptions.BundleJsCss
|
||||
? new[] {"bundles/shopify-bundle.min.js"}
|
||||
: new[] {"modal/btcpay.js", "shopify/btcpay-shopify.js"};
|
||||
|
||||
|
||||
foreach (var file in fileList)
|
||||
{
|
||||
await using var stream = _webHostEnvironment.WebRootFileProvider
|
||||
.GetFileInfo(file).CreateReadStream();
|
||||
using var reader = new StreamReader(stream);
|
||||
_cachedShopifyJavascript += Environment.NewLine + await reader.ReadToEndAsync();
|
||||
}
|
||||
|
||||
return _cachedShopifyJavascript;
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
[HttpGet("{storeId}/integrations/shopify/shopify.js")]
|
||||
public async Task<IActionResult> ShopifyJavascript(string storeId)
|
||||
{
|
||||
var jsFile =
|
||||
$"var BTCPAYSERVER_URL = \"{Request.GetAbsoluteRoot()}\"; var STORE_ID = \"{storeId}\"; {await GetJavascript()}";
|
||||
return Content(jsFile, "text/javascript");
|
||||
}
|
||||
|
||||
[RateLimitsFilter(ZoneLimits.Shopify, Scope = RateLimitsScope.RemoteAddress)]
|
||||
[AllowAnonymous]
|
||||
[EnableCors(CorsPolicies.All)]
|
||||
[HttpGet("{storeId}/integrations/shopify/{orderId}")]
|
||||
public async Task<IActionResult> ShopifyInvoiceEndpoint(
|
||||
[FromServices] InvoiceRepository invoiceRepository,
|
||||
[FromServices] InvoiceController invoiceController,
|
||||
[FromServices] IHttpClientFactory httpClientFactory,
|
||||
string storeId, string orderId, decimal amount, bool checkOnly = false)
|
||||
{
|
||||
var invoiceOrderId = $"{ShopifyOrderMarkerHostedService.SHOPIFY_ORDER_ID_PREFIX}{orderId}";
|
||||
var matchedExistingInvoices = await invoiceRepository.GetInvoices(new InvoiceQuery()
|
||||
{
|
||||
OrderId = new[] {invoiceOrderId}, StoreId = new[] {storeId}
|
||||
});
|
||||
matchedExistingInvoices = matchedExistingInvoices.Where(entity =>
|
||||
entity.GetInternalTags(ShopifyOrderMarkerHostedService.SHOPIFY_ORDER_ID_PREFIX)
|
||||
.Any(s => s == orderId))
|
||||
.ToArray();
|
||||
|
||||
var firstInvoiceStillPending =
|
||||
matchedExistingInvoices.FirstOrDefault(entity => entity.GetInvoiceState().Status == InvoiceStatusLegacy.New);
|
||||
if (firstInvoiceStillPending != null)
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
invoiceId = firstInvoiceStillPending.Id,
|
||||
status = firstInvoiceStillPending.Status.ToString().ToLowerInvariant()
|
||||
});
|
||||
}
|
||||
|
||||
var firstInvoiceSettled =
|
||||
matchedExistingInvoices.LastOrDefault(entity =>
|
||||
new[] {InvoiceStatusLegacy.Paid, InvoiceStatusLegacy.Complete, InvoiceStatusLegacy.Confirmed}.Contains(
|
||||
entity.GetInvoiceState().Status));
|
||||
|
||||
var store = await _Repo.FindStore(storeId);
|
||||
var shopify = store?.GetStoreBlob()?.Shopify;
|
||||
ShopifyApiClient client = null;
|
||||
ShopifyOrder order = null;
|
||||
if (shopify?.IntegratedAt.HasValue is true)
|
||||
{
|
||||
client = new ShopifyApiClient(httpClientFactory, shopify.CreateShopifyApiCredentials());
|
||||
order = await client.GetOrder(orderId);
|
||||
if (string.IsNullOrEmpty(order?.Id))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
}
|
||||
|
||||
if (firstInvoiceSettled != null)
|
||||
{
|
||||
//if BTCPay was shut down before the tx managed to get registered on shopify, this will fix it on the next UI load in shopify
|
||||
if (client != null && order?.FinancialStatus == "pending" &&
|
||||
firstInvoiceSettled.Status != InvoiceStatusLegacy.Paid)
|
||||
{
|
||||
await new OrderTransactionRegisterLogic(client).Process(orderId, firstInvoiceSettled.Id,
|
||||
firstInvoiceSettled.Currency,
|
||||
firstInvoiceSettled.Price.ToString(CultureInfo.InvariantCulture), true);
|
||||
order = await client.GetOrder(orderId);
|
||||
}
|
||||
|
||||
if (order?.FinancialStatus != "pending" && order?.FinancialStatus != "partially_paid")
|
||||
{
|
||||
return Ok(new
|
||||
{
|
||||
invoiceId = firstInvoiceSettled.Id,
|
||||
status = firstInvoiceSettled.Status.ToString().ToLowerInvariant()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (checkOnly)
|
||||
{
|
||||
return Ok();
|
||||
}
|
||||
|
||||
if (shopify?.IntegratedAt.HasValue is true)
|
||||
{
|
||||
if (string.IsNullOrEmpty(order?.Id) ||
|
||||
!new[] {"pending", "partially_paid"}.Contains(order.FinancialStatus))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
//we create the invoice at due amount provided from order page or full amount if due amount is bigger than order amount
|
||||
var invoice = await invoiceController.CreateInvoiceCoreRaw(
|
||||
new CreateInvoiceRequest()
|
||||
{
|
||||
Amount = amount < order.TotalPrice ? amount : order.TotalPrice,
|
||||
Currency = order.Currency,
|
||||
Metadata = new JObject {["orderId"] = invoiceOrderId}
|
||||
}, store,
|
||||
Request.GetAbsoluteUri(""), new List<string>() {invoiceOrderId});
|
||||
|
||||
return Ok(new {invoiceId = invoice.Id, status = invoice.Status.ToString().ToLowerInvariant()});
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/integrations")]
|
||||
[Route("{storeId}/integrations/shopify")]
|
||||
[HttpGet("{storeId}/integrations")]
|
||||
public IActionResult Integrations()
|
||||
{
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
|
||||
var vm = new IntegrationsViewModel {Shopify = blob.Shopify};
|
||||
|
||||
return View("Integrations", vm);
|
||||
{
|
||||
return View("Integrations",new IntegrationsViewModel());
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/webhooks")]
|
||||
[HttpGet("{storeId}/webhooks")]
|
||||
public async Task<IActionResult> Webhooks()
|
||||
{
|
||||
var webhooks = await this._Repo.GetWebhooks(CurrentStore.Id);
|
||||
var webhooks = await _Repo.GetWebhooks(CurrentStore.Id);
|
||||
return View(nameof(Webhooks), new WebhooksViewModel()
|
||||
{
|
||||
Webhooks = webhooks.Select(w => new WebhooksViewModel.WebhookViewModel()
|
||||
@ -193,11 +30,11 @@ namespace BTCPayServer.Controllers
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}/webhooks/new")]
|
||||
|
||||
[HttpGet("{storeId}/webhooks/new")]
|
||||
public IActionResult NewWebhook()
|
||||
{
|
||||
return View(nameof(ModifyWebhook), new EditWebhookViewModel()
|
||||
return View(nameof(ModifyWebhook), new EditWebhookViewModel
|
||||
{
|
||||
Active = true,
|
||||
Everything = true,
|
||||
@ -206,14 +43,14 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/webhooks/{webhookId}/remove")]
|
||||
[HttpGet("{storeId}/webhooks/{webhookId}/remove")]
|
||||
public async Task<IActionResult> DeleteWebhook(string webhookId)
|
||||
{
|
||||
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
|
||||
if (webhook is null)
|
||||
return NotFound();
|
||||
return View("Confirm", new ConfirmModel()
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Delete a webhook",
|
||||
Description = "This webhook will be removed from this store, do you wish to continue?",
|
||||
@ -221,36 +58,36 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/webhooks/{webhookId}/remove")]
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}/remove")]
|
||||
public async Task<IActionResult> DeleteWebhookPost(string webhookId)
|
||||
{
|
||||
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
|
||||
if (webhook is null)
|
||||
return NotFound();
|
||||
|
||||
await _Repo.DeleteWebhook(CurrentStore.Id, webhookId);
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Webhook successfully deleted";
|
||||
return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/webhooks/new")]
|
||||
[HttpPost("{storeId}/webhooks/new")]
|
||||
public async Task<IActionResult> NewWebhook(string storeId, EditWebhookViewModel viewModel)
|
||||
{
|
||||
if (!ModelState.IsValid)
|
||||
return View(viewModel);
|
||||
return View(nameof(ModifyWebhook), viewModel);
|
||||
|
||||
var webhookId = await _Repo.CreateWebhook(CurrentStore.Id, viewModel.CreateBlob());
|
||||
await _Repo.CreateWebhook(CurrentStore.Id, viewModel.CreateBlob());
|
||||
TempData[WellKnownTempData.SuccessMessage] = "The webhook has been created";
|
||||
return RedirectToAction(nameof(Webhooks), new { storeId });
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}/webhooks/{webhookId}")]
|
||||
|
||||
[HttpGet("{storeId}/webhooks/{webhookId}")]
|
||||
public async Task<IActionResult> ModifyWebhook(string webhookId)
|
||||
{
|
||||
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
|
||||
if (webhook is null)
|
||||
return NotFound();
|
||||
|
||||
var blob = webhook.GetBlob();
|
||||
var deliveries = await _Repo.GetWebhookDeliveries(CurrentStore.Id, webhookId, 20);
|
||||
return View(nameof(ModifyWebhook), new EditWebhookViewModel(blob)
|
||||
@ -259,8 +96,8 @@ namespace BTCPayServer.Controllers
|
||||
.Select(s => new DeliveryViewModel(s)).ToList()
|
||||
});
|
||||
}
|
||||
[HttpPost]
|
||||
[Route("{storeId}/webhooks/{webhookId}")]
|
||||
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}")]
|
||||
public async Task<IActionResult> ModifyWebhook(string webhookId, EditWebhookViewModel viewModel)
|
||||
{
|
||||
var webhook = await _Repo.GetWebhook(CurrentStore.Id, webhookId);
|
||||
@ -272,16 +109,17 @@ namespace BTCPayServer.Controllers
|
||||
return RedirectToAction(nameof(Webhooks), new { storeId = CurrentStore.Id });
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
|
||||
[HttpPost("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/redeliver")]
|
||||
public async Task<IActionResult> RedeliverWebhook(string webhookId, string deliveryId)
|
||||
{
|
||||
var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
|
||||
var newDeliveryId = await WebhookNotificationManager.Redeliver(deliveryId);
|
||||
if (newDeliveryId is null)
|
||||
return NotFound();
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Successfully planned a redelivery";
|
||||
return RedirectToAction(nameof(ModifyWebhook),
|
||||
new
|
||||
@ -290,104 +128,15 @@ namespace BTCPayServer.Controllers
|
||||
webhookId
|
||||
});
|
||||
}
|
||||
[HttpGet]
|
||||
[Route("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
|
||||
|
||||
[HttpGet("{storeId}/webhooks/{webhookId}/deliveries/{deliveryId}/request")]
|
||||
public async Task<IActionResult> WebhookDelivery(string webhookId, string deliveryId)
|
||||
{
|
||||
var delivery = await _Repo.GetWebhookDelivery(CurrentStore.Id, webhookId, deliveryId);
|
||||
if (delivery is null)
|
||||
return NotFound();
|
||||
return this.File(delivery.GetBlob().Request, "application/json");
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/integrations/shopify")]
|
||||
public async Task<IActionResult> Integrations([FromServices] IHttpClientFactory clientFactory,
|
||||
IntegrationsViewModel vm, string command = "", string exampleUrl = "")
|
||||
{
|
||||
if (!string.IsNullOrEmpty(exampleUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
//https://{apikey}:{password}@{hostname}/admin/api/{version}/{resource}.json
|
||||
var parsedUrl = new Uri(exampleUrl);
|
||||
var userInfo = parsedUrl.UserInfo.Split(":");
|
||||
vm.Shopify = new ShopifySettings()
|
||||
{
|
||||
ApiKey = userInfo[0],
|
||||
Password = userInfo[1],
|
||||
ShopName = parsedUrl.Host.Replace(".myshopify.com", "",
|
||||
StringComparison.InvariantCultureIgnoreCase)
|
||||
};
|
||||
command = "ShopifySaveCredentials";
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "The provided Example Url was invalid.";
|
||||
return View("Integrations", vm);
|
||||
}
|
||||
}
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "ShopifySaveCredentials":
|
||||
{
|
||||
var shopify = vm.Shopify;
|
||||
var validCreds = shopify != null && shopify?.CredentialsPopulated() == true;
|
||||
if (!validCreds)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] = "Please provide valid Shopify credentials";
|
||||
return View("Integrations", vm);
|
||||
}
|
||||
|
||||
var apiClient = new ShopifyApiClient(clientFactory, shopify.CreateShopifyApiCredentials());
|
||||
try
|
||||
{
|
||||
await apiClient.OrdersCount();
|
||||
}
|
||||
catch (ShopifyApiException)
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] =
|
||||
"Shopify rejected provided credentials, please correct values and try again";
|
||||
return View("Integrations", vm);
|
||||
}
|
||||
|
||||
var scopesGranted = await apiClient.CheckScopes();
|
||||
if (!scopesGranted.Contains("read_orders") || !scopesGranted.Contains("write_orders"))
|
||||
{
|
||||
TempData[WellKnownTempData.ErrorMessage] =
|
||||
"Please grant the private app permissions for read_orders, write_orders";
|
||||
return View("Integrations", vm);
|
||||
}
|
||||
|
||||
// everything ready, proceed with saving Shopify integration credentials
|
||||
shopify.IntegratedAt = DateTimeOffset.Now;
|
||||
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
blob.Shopify = shopify;
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
}
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Shopify integration successfully updated";
|
||||
break;
|
||||
}
|
||||
case "ShopifyClearCredentials":
|
||||
{
|
||||
var blob = CurrentStore.GetStoreBlob();
|
||||
blob.Shopify = null;
|
||||
if (CurrentStore.SetStoreBlob(blob))
|
||||
{
|
||||
await _Repo.UpdateStore(CurrentStore);
|
||||
}
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] = "Shopify integration credentials cleared";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(Integrations), new {storeId = CurrentStore.Id});
|
||||
return File(delivery.GetBlob().Request, "application/json");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,118 +14,78 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class StoresController
|
||||
{
|
||||
|
||||
[HttpGet]
|
||||
[Route("{storeId}/lightning/{cryptoCode}")]
|
||||
[HttpGet("{storeId}/lightning/{cryptoCode}")]
|
||||
public IActionResult AddLightningNode(string storeId, string cryptoCode)
|
||||
{
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
LightningNodeViewModel vm = new LightningNodeViewModel
|
||||
|
||||
var vm = new LightningNodeViewModel
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
InternalLightningNode = GetInternalLighningNode(cryptoCode)?.ToString(),
|
||||
StoreId = storeId
|
||||
};
|
||||
SetExistingValues(store, vm);
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||
{
|
||||
vm.ConnectionString = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store)?.GetLightningUrl()?.ToString();
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.LightningLike));
|
||||
}
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
private LightningConnectionString GetInternalLighningNode(string cryptoCode)
|
||||
{
|
||||
if (_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode, out var connectionString))
|
||||
{
|
||||
return CanUseInternalLightning() ? connectionString : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{storeId}/lightning/{cryptoCode}")]
|
||||
[HttpPost("{storeId}/lightning/{cryptoCode}")]
|
||||
public async Task<IActionResult> AddLightningNode(string storeId, LightningNodeViewModel vm, string command, string cryptoCode)
|
||||
{
|
||||
vm.CryptoCode = cryptoCode;
|
||||
var store = HttpContext.GetStoreData();
|
||||
if (store == null)
|
||||
return NotFound();
|
||||
var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||
|
||||
var internalLightning = GetInternalLighningNode(network.CryptoCode);
|
||||
vm.InternalLightningNode = internalLightning?.ToString();
|
||||
vm.CanUseInternalNode = CanUseInternalLightning();
|
||||
|
||||
var network = vm.CryptoCode == null ? null : _ExplorerProvider.GetNetwork(vm.CryptoCode);
|
||||
if (network == null)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.CryptoCode), "Invalid network");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
Payments.Lightning.LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (!string.IsNullOrEmpty(vm.ConnectionString))
|
||||
var paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.LightningLike);
|
||||
LightningSupportedPaymentMethod paymentMethod = null;
|
||||
if (vm.LightningNodeType == LightningNodeType.Internal)
|
||||
{
|
||||
if (!CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use the internal lightning node");
|
||||
return View(vm);
|
||||
}
|
||||
paymentMethod = new LightningSupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
paymentMethod.SetInternalNode();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (string.IsNullOrEmpty(vm.ConnectionString))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Please provide a connection string");
|
||||
return View(vm);
|
||||
}
|
||||
if (!LightningConnectionString.TryParse(vm.ConnectionString, false, out var connectionString, out var error))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), $"Invalid URL ({error})");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
if (connectionString.ConnectionType == LightningConnectionType.LndGRPC)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), $"BTCPay does not support gRPC connections");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
bool isInternalNode = connectionString.IsInternalNode(internalLightning);
|
||||
|
||||
if (connectionString.BaseUri.Scheme == "http")
|
||||
if (!User.IsInRole(Roles.ServerAdmin) && !connectionString.IsSafe())
|
||||
{
|
||||
if (!isInternalNode && !connectionString.AllowInsecure)
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "The url must be HTTPS");
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
if (connectionString.MacaroonFilePath != null)
|
||||
{
|
||||
if (!CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not authorized to use macaroonfilepath");
|
||||
return View(vm);
|
||||
}
|
||||
if (!System.IO.File.Exists(connectionString.MacaroonFilePath))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath file does not exist");
|
||||
return View(vm);
|
||||
}
|
||||
if (!System.IO.Path.IsPathRooted(connectionString.MacaroonFilePath))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "The macaroonfilepath should be fully rooted");
|
||||
return View(vm);
|
||||
}
|
||||
}
|
||||
|
||||
if (isInternalNode && !CanUseInternalLightning())
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Unauthorized url");
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "You are not a server admin, so the connection string should not contain 'cookiefilepath', 'macaroondirectorypath', 'macaroonfilepath', and should not point to a local ip or to a dns name ending with '.internal', '.local', '.lan' or '.'.");
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
|
||||
paymentMethod = new LightningSupportedPaymentMethod
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode
|
||||
};
|
||||
@ -141,24 +101,20 @@ namespace BTCPayServer.Controllers
|
||||
store.SetStoreBlob(storeBlob);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
|
||||
await _Repo.UpdateStore(store);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Lightning node modified ({network.CryptoCode})";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId = storeId });
|
||||
case "test" when paymentMethod == null:
|
||||
ModelState.AddModelError(nameof(vm.ConnectionString), "Missing url parameter");
|
||||
return View(vm);
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"{network.CryptoCode} Lightning node modified.";
|
||||
return RedirectToAction(nameof(UpdateStore), new { storeId });
|
||||
|
||||
case "test":
|
||||
var handler = _ServiceProvider.GetRequiredService<LightningLikePaymentHandler>();
|
||||
try
|
||||
{
|
||||
var info = await handler.GetNodeInfo(this.Request.IsOnion(), paymentMethod, network);
|
||||
var info = await handler.GetNodeInfo(Request.IsOnion(), paymentMethod, network);
|
||||
if (!vm.SkipPortTest)
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(20)))
|
||||
{
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(20));
|
||||
await handler.TestConnection(info, cts.Token);
|
||||
}
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the lightning node succeeded. Your node address: {info}";
|
||||
TempData[WellKnownTempData.SuccessMessage] = $"Connection to the Lightning node succeeded. Your node address: {info}";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -166,6 +122,7 @@ namespace BTCPayServer.Controllers
|
||||
return View(vm);
|
||||
}
|
||||
return View(vm);
|
||||
|
||||
default:
|
||||
return View(vm);
|
||||
}
|
||||
@ -173,7 +130,28 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private bool CanUseInternalLightning()
|
||||
{
|
||||
return (_BTCPayEnv.IsDeveloping || User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll);
|
||||
return User.IsInRole(Roles.ServerAdmin) || _CssThemeManager.AllowLightningInternalNodeForAll;
|
||||
}
|
||||
|
||||
private void SetExistingValues(StoreData store, LightningNodeViewModel vm)
|
||||
{
|
||||
var lightning = GetExistingLightningSupportedPaymentMethod(vm.CryptoCode, store);
|
||||
if (lightning != null)
|
||||
{
|
||||
vm.LightningNodeType = lightning.IsInternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
|
||||
vm.ConnectionString = lightning.GetDisplayableConnectionString();
|
||||
}
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.LightningLike)) && lightning != null;
|
||||
vm.CanUseInternalNode = CanUseInternalLightning();
|
||||
}
|
||||
|
||||
private LightningSupportedPaymentMethod GetExistingLightningSupportedPaymentMethod(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<LightningSupportedPaymentMethod>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
600
BTCPayServer/Controllers/StoresController.Onchain.cs
Normal file
600
BTCPayServer/Controllers/StoresController.Onchain.cs
Normal file
@ -0,0 +1,600 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.Events;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.StoreViewModels;
|
||||
using BTCPayServer.Payments;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
public partial class StoresController
|
||||
{
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}")]
|
||||
public ActionResult SetupWallet(WalletSetupViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out _);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
vm.DerivationScheme = derivation?.AccountDerivation.ToString();
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/import/{method?}")]
|
||||
public async Task<IActionResult> ImportWallet(WalletSetupViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out _, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
vm.Network = network;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
vm.CanUseHotWallet = hotWallet;
|
||||
vm.CanUseRPCImport = rpcImport;
|
||||
|
||||
if (vm.Method == null)
|
||||
{
|
||||
vm.Method = WalletSetupMethod.ImportOptions;
|
||||
}
|
||||
else if (vm.Method == WalletSetupMethod.Seed)
|
||||
{
|
||||
vm.SetupRequest = new GenerateWalletRequest();
|
||||
}
|
||||
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/modify")]
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/import/{method}")]
|
||||
public async Task<IActionResult> UpdateWallet(WalletSetupViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
vm.Network = network;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
DerivationSchemeSettings strategy = null;
|
||||
|
||||
var wallet = _WalletProvider.GetWallet(network);
|
||||
if (wallet == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vm.Config))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromJson(vm.Config, network, out strategy))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.Config), "Config file was not in the correct format");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
}
|
||||
|
||||
if (vm.WalletFile != null)
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(await ReadAllText(vm.WalletFile), network, out strategy))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.WalletFile), "Wallet file was not in the correct format");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.WalletFileContent))
|
||||
{
|
||||
if (!DerivationSchemeSettings.TryParseFromWalletFile(vm.WalletFileContent, network, out strategy))
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.WalletFileContent), "QR import was not in the correct format");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
}
|
||||
else if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
try
|
||||
{
|
||||
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, null, network);
|
||||
if (newStrategy.AccountDerivation != strategy?.AccountDerivation)
|
||||
{
|
||||
var accountKey = string.IsNullOrEmpty(vm.AccountKey)
|
||||
? null
|
||||
: new BitcoinExtPubKey(vm.AccountKey, network.NBitcoinNetwork);
|
||||
if (accountKey != null)
|
||||
{
|
||||
var accountSettings =
|
||||
newStrategy.AccountKeySettings.FirstOrDefault(a => a.AccountKey == accountKey);
|
||||
if (accountSettings != null)
|
||||
{
|
||||
accountSettings.AccountKeyPath =
|
||||
vm.KeyPath == null ? null : KeyPath.Parse(vm.KeyPath);
|
||||
accountSettings.RootFingerprint = string.IsNullOrEmpty(vm.RootFingerprint)
|
||||
? (HDFingerprint?)null
|
||||
: new HDFingerprint(
|
||||
NBitcoin.DataEncoders.Encoders.Hex.DecodeData(vm.RootFingerprint));
|
||||
}
|
||||
}
|
||||
|
||||
strategy = newStrategy;
|
||||
strategy.Source = vm.Source;
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid wallet format");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Please provide your extended public key");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
var oldConfig = vm.Config;
|
||||
vm.Config = strategy?.ToJson();
|
||||
var configChanged = oldConfig != vm.Config;
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
var storeBlob = store.GetStoreBlob();
|
||||
var wasExcluded = storeBlob.GetExcludedPaymentMethods().Match(paymentMethodId);
|
||||
var willBeExcluded = !vm.Enabled;
|
||||
var excludedChanged = willBeExcluded != wasExcluded;
|
||||
|
||||
var showAddress = // Show addresses if:
|
||||
// - If the user is testing the hint address in confirmation screen
|
||||
(vm.Confirmation && !string.IsNullOrWhiteSpace(vm.HintAddress)) ||
|
||||
// - The user is clicking on continue after changing the config
|
||||
(!vm.Confirmation && configChanged);
|
||||
|
||||
showAddress = showAddress && strategy != null;
|
||||
if (!showAddress)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (strategy != null)
|
||||
await wallet.TrackAsync(strategy.AccountDerivation);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, strategy);
|
||||
storeBlob.SetExcluded(paymentMethodId, willBeExcluded);
|
||||
storeBlob.Hints.Wallet = false;
|
||||
store.SetStoreBlob(storeBlob);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.DerivationScheme), "Invalid derivation scheme");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(vm.StoreId, vm.CryptoCode)});
|
||||
|
||||
if (excludedChanged)
|
||||
{
|
||||
var label = willBeExcluded ? "disabled" : "enabled";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"On-Chain payments for {network.CryptoCode} have been {label}.";
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} have been modified.";
|
||||
}
|
||||
|
||||
// This is success case when derivation scheme is added to the store
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId = vm.StoreId});
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(vm.HintAddress))
|
||||
{
|
||||
BitcoinAddress address;
|
||||
try
|
||||
{
|
||||
address = BitcoinAddress.Create(vm.HintAddress, network.NBitcoinNetwork);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.HintAddress), "Invalid hint address");
|
||||
return ConfirmAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var newStrategy = ParseDerivationStrategy(vm.DerivationScheme, address.ScriptPubKey, network);
|
||||
if (newStrategy.AccountDerivation != strategy.AccountDerivation)
|
||||
{
|
||||
strategy.AccountDerivation = newStrategy.AccountDerivation;
|
||||
strategy.AccountOriginal = null;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ModelState.AddModelError(nameof(vm.HintAddress), "Impossible to find a match with this address. Are you sure the wallet and address provided are correct and from the same source?");
|
||||
return ConfirmAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
vm.HintAddress = "";
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
"Address successfully found, please verify that the rest is correct and click on \"Confirm\"";
|
||||
ModelState.Remove(nameof(vm.HintAddress));
|
||||
ModelState.Remove(nameof(vm.DerivationScheme));
|
||||
}
|
||||
|
||||
return ConfirmAddresses(vm, strategy);
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/{method?}")]
|
||||
public async Task<IActionResult> GenerateWallet(WalletSetupViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var isHotWallet = vm.Method == WalletSetupMethod.HotWallet;
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
if (isHotWallet && !hotWallet)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
if (derivation != null)
|
||||
{
|
||||
vm.DerivationScheme = derivation.AccountDerivation.ToString();
|
||||
vm.Config = derivation.ToJson();
|
||||
}
|
||||
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
vm.CanUseHotWallet = hotWallet;
|
||||
vm.CanUseRPCImport = rpcImport;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
vm.Network = network;
|
||||
|
||||
if (vm.Method == null)
|
||||
{
|
||||
vm.Method = WalletSetupMethod.GenerateOptions;
|
||||
}
|
||||
else
|
||||
{
|
||||
vm.SetupRequest = new GenerateWalletRequest { SavePrivateKeys = isHotWallet };
|
||||
}
|
||||
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/generate/{method}")]
|
||||
public async Task<IActionResult> GenerateWallet(string storeId, string cryptoCode, WalletSetupMethod method, GenerateWalletRequest request)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
if (!hotWallet && request.SavePrivateKeys || !rpcImport && request.ImportKeysToRPC)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var client = _ExplorerProvider.GetExplorerClient(cryptoCode);
|
||||
var isImport = method == WalletSetupMethod.Seed;
|
||||
var vm = new WalletSetupViewModel
|
||||
{
|
||||
StoreId = storeId,
|
||||
CryptoCode = cryptoCode,
|
||||
Method = method,
|
||||
SetupRequest = request,
|
||||
Confirmation = string.IsNullOrEmpty(request.ExistingMnemonic),
|
||||
Network = network,
|
||||
RootKeyPath = network.GetRootKeyPath(),
|
||||
Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike)),
|
||||
Source = "NBXplorer",
|
||||
DerivationSchemeFormat = "BTCPay",
|
||||
CanUseHotWallet = true,
|
||||
CanUseRPCImport = rpcImport
|
||||
};
|
||||
|
||||
if (isImport && string.IsNullOrEmpty(request.ExistingMnemonic))
|
||||
{
|
||||
ModelState.AddModelError(nameof(request.ExistingMnemonic), "Please provide your existing seed");
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
GenerateWalletResponse response;
|
||||
try
|
||||
{
|
||||
response = await client.GenerateWalletAsync(request);
|
||||
if (response == null)
|
||||
{
|
||||
throw new Exception("Node unavailable");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Error,
|
||||
Html = $"There was an error generating your wallet: {e.Message}"
|
||||
});
|
||||
return View(vm.ViewName, vm);
|
||||
}
|
||||
|
||||
// Set wallet properties from generate response
|
||||
vm.RootFingerprint = response.AccountKeyPath.MasterFingerprint.ToString();
|
||||
vm.DerivationScheme = response.DerivationScheme.ToString();
|
||||
vm.AccountKey = response.AccountHDKey.Neuter().ToWif();
|
||||
vm.KeyPath = response.AccountKeyPath.KeyPath.ToString();
|
||||
|
||||
var result = await UpdateWallet(vm);
|
||||
|
||||
if (!ModelState.IsValid || !(result is RedirectToActionResult))
|
||||
return result;
|
||||
|
||||
if (!isImport)
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
Html = "<span class='text-centered'>Your wallet has been generated.</span>"
|
||||
});
|
||||
var seedVm = new RecoverySeedBackupViewModel
|
||||
{
|
||||
CryptoCode = cryptoCode,
|
||||
Mnemonic = response.Mnemonic,
|
||||
Passphrase = response.Passphrase,
|
||||
IsStored = request.SavePrivateKeys,
|
||||
ReturnUrl = Url.Action(nameof(GenerateWalletConfirm), new {storeId, cryptoCode})
|
||||
};
|
||||
return this.RedirectToRecoverySeedBackup(seedVm);
|
||||
}
|
||||
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel
|
||||
{
|
||||
Severity = StatusMessageModel.StatusSeverity.Warning,
|
||||
Html = "Please check your addresses and confirm."
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
// The purpose of this action is to show the user a success message, which confirms
|
||||
// that the store settings have been updated after generating a new wallet.
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/generate/confirm")]
|
||||
public ActionResult GenerateWalletConfirm(string storeId, string cryptoCode)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out _, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"Derivation settings for {network.CryptoCode} have been modified.";
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId});
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/modify")]
|
||||
public async Task<IActionResult> ModifyWallet(WalletSetupViewModel vm)
|
||||
{
|
||||
var checkResult = IsAvailable(vm.CryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(vm.CryptoCode, store);
|
||||
if (derivation == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var (hotWallet, rpcImport) = await CanUseHotWallet();
|
||||
var isHotWallet = await IsHotWallet(vm.CryptoCode, derivation);
|
||||
|
||||
vm.CanUseHotWallet = hotWallet;
|
||||
vm.CanUseRPCImport = rpcImport;
|
||||
vm.RootKeyPath = network.GetRootKeyPath();
|
||||
vm.Network = network;
|
||||
vm.Source = derivation.Source;
|
||||
vm.RootFingerprint = derivation.GetSigningAccountKeySettings().RootFingerprint.ToString();
|
||||
vm.DerivationScheme = derivation.AccountDerivation.ToString();
|
||||
vm.KeyPath = derivation.GetSigningAccountKeySettings().AccountKeyPath?.ToString();
|
||||
vm.Config = derivation.ToJson();
|
||||
vm.Enabled = !store.GetStoreBlob().IsExcluded(new PaymentMethodId(vm.CryptoCode, PaymentTypes.BTCLike));
|
||||
vm.IsHotWallet = isHotWallet;
|
||||
|
||||
return View(vm);
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/replace")]
|
||||
public async Task<IActionResult> ReplaceWallet(string storeId, string cryptoCode)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
var isHotWallet = await IsHotWallet(cryptoCode, derivation);
|
||||
var walletType = isHotWallet ? "hot" : "watch-only";
|
||||
var additionalText = isHotWallet
|
||||
? ""
|
||||
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
|
||||
var description =
|
||||
$"<p class=\"text-danger font-weight-bold\">Please note that this is a {walletType} wallet!</p>" +
|
||||
$"<p class=\"text-danger font-weight-bold\">Do not replace the wallet if you have not backed it up{additionalText}.</p>" +
|
||||
"<p class=\"text-left mb-0\">Replacing the wallet will erase the current wallet data from the server. " +
|
||||
"The current wallet will be replaced once you finish the setup of the new wallet. If you cancel the setup, the current wallet will stay active .</p>";
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Replace {network.CryptoCode} wallet",
|
||||
Description = description,
|
||||
DescriptionHtml = true,
|
||||
Action = "Setup new wallet"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/replace")]
|
||||
public IActionResult ConfirmReplaceWallet(string storeId, string cryptoCode)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out _);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
if (derivation == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(SetupWallet), new {storeId, cryptoCode});
|
||||
}
|
||||
|
||||
[HttpGet("{storeId}/onchain/{cryptoCode}/delete")]
|
||||
public async Task<IActionResult> DeleteWallet(string storeId, string cryptoCode)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
var isHotWallet = await IsHotWallet(cryptoCode, derivation);
|
||||
var walletType = isHotWallet ? "hot" : "watch-only";
|
||||
var additionalText = isHotWallet
|
||||
? ""
|
||||
: " or imported into an external wallet. If you no longer have access to your private key (recovery seed), immediately replace the wallet";
|
||||
var description =
|
||||
$"<p class=\"text-danger font-weight-bold\">Please note that this is a {walletType} wallet!</p>" +
|
||||
$"<p class=\"text-danger font-weight-bold\">Do not remove the wallet if you have not backed it up{additionalText}.</p>" +
|
||||
"<p class=\"text-left mb-0\">Removing the wallet will erase the wallet data from the server. " +
|
||||
$"The store won't be able to receive {network.CryptoCode} onchain payments until a new wallet is set up.</p>";
|
||||
|
||||
return View("Confirm", new ConfirmModel
|
||||
{
|
||||
Title = $"Remove {network.CryptoCode} wallet",
|
||||
Description = description,
|
||||
DescriptionHtml = true,
|
||||
Action = "Remove"
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPost("{storeId}/onchain/{cryptoCode}/delete")]
|
||||
public async Task<IActionResult> ConfirmDeleteWallet(string storeId, string cryptoCode)
|
||||
{
|
||||
var checkResult = IsAvailable(cryptoCode, out var store, out var network);
|
||||
if (checkResult != null)
|
||||
{
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
var derivation = GetExistingDerivationStrategy(cryptoCode, store);
|
||||
if (derivation == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
PaymentMethodId paymentMethodId = new PaymentMethodId(network.CryptoCode, PaymentTypes.BTCLike);
|
||||
store.SetSupportedPaymentMethod(paymentMethodId, null);
|
||||
|
||||
await _Repo.UpdateStore(store);
|
||||
_EventAggregator.Publish(new WalletChangedEvent {WalletId = new WalletId(storeId, cryptoCode)});
|
||||
|
||||
TempData[WellKnownTempData.SuccessMessage] =
|
||||
$"On-Chain payment for {network.CryptoCode} has been removed.";
|
||||
|
||||
return RedirectToAction(nameof(UpdateStore), new {storeId});
|
||||
}
|
||||
|
||||
private IActionResult ConfirmAddresses(WalletSetupViewModel vm, DerivationSchemeSettings strategy)
|
||||
{
|
||||
vm.DerivationScheme = strategy.AccountDerivation.ToString();
|
||||
var deposit = new KeyPathTemplates(null).GetKeyPathTemplate(DerivationFeature.Deposit);
|
||||
|
||||
if (!string.IsNullOrEmpty(vm.DerivationScheme))
|
||||
{
|
||||
var line = strategy.AccountDerivation.GetLineFor(deposit);
|
||||
|
||||
for (uint i = 0; i < 10; i++)
|
||||
{
|
||||
var keyPath = deposit.GetKeyPath(i);
|
||||
var rootedKeyPath = vm.GetAccountKeypath()?.Derive(keyPath);
|
||||
var derivation = line.Derive(i);
|
||||
var address = strategy.Network.NBXplorerNetwork.CreateAddress(strategy.AccountDerivation,
|
||||
line.KeyPathTemplate.GetKeyPath(i),
|
||||
derivation.ScriptPubKey).ToString();
|
||||
vm.AddressSamples.Add((keyPath.ToString(), address, rootedKeyPath));
|
||||
}
|
||||
}
|
||||
|
||||
vm.Confirmation = true;
|
||||
ModelState.Remove(nameof(vm.Config)); // Remove the cached value
|
||||
|
||||
return View("ImportWallet/ConfirmAddresses", vm);
|
||||
}
|
||||
|
||||
private ActionResult IsAvailable(string cryptoCode, out StoreData store, out BTCPayNetwork network)
|
||||
{
|
||||
store = HttpContext.GetStoreData();
|
||||
network = cryptoCode == null ? null : _ExplorerProvider.GetNetwork(cryptoCode);
|
||||
|
||||
return store == null || network == null ? NotFound() : null;
|
||||
}
|
||||
|
||||
private DerivationSchemeSettings GetExistingDerivationStrategy(string cryptoCode, StoreData store)
|
||||
{
|
||||
var id = new PaymentMethodId(cryptoCode, PaymentTypes.BTCLike);
|
||||
var existing = store.GetSupportedPaymentMethods(_NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(d => d.PaymentId == id);
|
||||
return existing;
|
||||
}
|
||||
|
||||
private async Task<(bool HotWallet, bool RPCImport)> CanUseHotWallet()
|
||||
{
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return await _authorizationService.CanUseHotWallet(policies, User);
|
||||
}
|
||||
|
||||
private async Task<string> ReadAllText(IFormFile file)
|
||||
{
|
||||
using (var stream = new StreamReader(file.OpenReadStream()))
|
||||
{
|
||||
return await stream.ReadToEndAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> IsHotWallet(string cryptoCode, DerivationSchemeSettings derivation)
|
||||
{
|
||||
return derivation.IsHotWallet && await _ExplorerProvider.GetExplorerClient(cryptoCode)
|
||||
.GetMetadataAsync<string>(derivation.AccountDerivation, WellknownMetadataKeys.MasterHDKey) != null;
|
||||
}
|
||||
}
|
||||
}
|
@ -23,7 +23,6 @@ using BTCPayServer.Services;
|
||||
using BTCPayServer.Services.Apps;
|
||||
using BTCPayServer.Services.Invoices;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Shopify;
|
||||
using BTCPayServer.Services.Stores;
|
||||
using BTCPayServer.Services.Wallets;
|
||||
using BundlerMinifier.TagHelpers;
|
||||
@ -406,6 +405,7 @@ namespace BTCPayServer.Controllers
|
||||
vm.LightningAmountInSatoshi = storeBlob.LightningAmountInSatoshi;
|
||||
vm.LightningPrivateRouteHints = storeBlob.LightningPrivateRouteHints;
|
||||
vm.OnChainWithLnInvoiceFallback = storeBlob.OnChainWithLnInvoiceFallback;
|
||||
vm.LazyPaymentMethods = storeBlob.LazyPaymentMethods;
|
||||
vm.RedirectAutomatically = storeBlob.RedirectAutomatically;
|
||||
vm.ShowRecommendedFee = storeBlob.ShowRecommendedFee;
|
||||
vm.RecommendedFeeBlockTarget = storeBlob.RecommendedFeeBlockTarget;
|
||||
@ -477,6 +477,7 @@ namespace BTCPayServer.Controllers
|
||||
}).ToList();
|
||||
|
||||
blob.RequiresRefundEmail = model.RequiresRefundEmail;
|
||||
blob.LazyPaymentMethods = model.LazyPaymentMethods;
|
||||
blob.LightningAmountInSatoshi = model.LightningAmountInSatoshi;
|
||||
blob.LightningPrivateRouteHints = model.LightningPrivateRouteHints;
|
||||
blob.OnChainWithLnInvoiceFallback = model.OnChainWithLnInvoiceFallback;
|
||||
@ -546,8 +547,8 @@ namespace BTCPayServer.Controllers
|
||||
vm.LightningNodes.Add(new StoreViewModel.LightningNode()
|
||||
{
|
||||
CryptoCode = paymentMethodId.CryptoCode,
|
||||
Address = lightning?.GetLightningUrl()?.BaseUri.AbsoluteUri ?? string.Empty,
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && lightning?.GetLightningUrl() != null
|
||||
Address = lightning?.GetDisplayableConnectionString(),
|
||||
Enabled = !excludeFilters.Match(paymentMethodId) && lightning != null
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
using BTCPayServer.Models;
|
||||
using BTCPayServer.Models.WalletViewModels;
|
||||
using BTCPayServer.Payments.PayJoin.Sender;
|
||||
using BTCPayServer.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using NBitcoin.Payment;
|
||||
using NBXplorer;
|
||||
using NBXplorer.Models;
|
||||
@ -171,7 +173,7 @@ namespace BTCPayServer.Controllers
|
||||
var cloned = psbt.Clone();
|
||||
cloned = cloned.Finalize();
|
||||
await _broadcaster.Schedule(DateTimeOffset.UtcNow + TimeSpan.FromMinutes(2.0), cloned.ExtractTransaction(), btcPayNetwork);
|
||||
return await _payjoinClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, cancellationToken);
|
||||
return await _payjoinClient.RequestPayjoin(bip21, new PayjoinWallet(derivationSchemeSettings), psbt, cancellationToken);
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
|
@ -8,6 +8,7 @@ using BTCPayServer.Abstractions.Constants;
|
||||
using BTCPayServer.Abstractions.Extensions;
|
||||
using BTCPayServer.Abstractions.Models;
|
||||
using BTCPayServer.Client;
|
||||
using BTCPayServer.Client.Models;
|
||||
using BTCPayServer.Data;
|
||||
using BTCPayServer.HostedServices;
|
||||
using BTCPayServer.ModelBinders;
|
||||
@ -25,11 +26,14 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using NBitcoin;
|
||||
using BTCPayServer.BIP78.Sender;
|
||||
using BTCPayServer.Payments.PayJoin;
|
||||
using NBitcoin.DataEncoders;
|
||||
using NBXplorer;
|
||||
using NBXplorer.DerivationStrategy;
|
||||
using NBXplorer.Models;
|
||||
using Newtonsoft.Json;
|
||||
using StoreData = BTCPayServer.Data.StoreData;
|
||||
|
||||
namespace BTCPayServer.Controllers
|
||||
{
|
||||
@ -49,7 +53,7 @@ namespace BTCPayServer.Controllers
|
||||
private readonly IAuthorizationService _authorizationService;
|
||||
private readonly IFeeProviderFactory _feeRateProvider;
|
||||
private readonly BTCPayWalletProvider _walletProvider;
|
||||
private readonly WalletReceiveStateService _WalletReceiveStateService;
|
||||
private readonly WalletReceiveService _walletReceiveService;
|
||||
private readonly EventAggregator _EventAggregator;
|
||||
private readonly SettingsRepository _settingsRepository;
|
||||
private readonly DelayedTransactionBroadcaster _broadcaster;
|
||||
@ -74,7 +78,7 @@ namespace BTCPayServer.Controllers
|
||||
ExplorerClientProvider explorerProvider,
|
||||
IFeeProviderFactory feeRateProvider,
|
||||
BTCPayWalletProvider walletProvider,
|
||||
WalletReceiveStateService walletReceiveStateService,
|
||||
WalletReceiveService walletReceiveService,
|
||||
EventAggregator eventAggregator,
|
||||
SettingsRepository settingsRepository,
|
||||
DelayedTransactionBroadcaster broadcaster,
|
||||
@ -96,7 +100,7 @@ namespace BTCPayServer.Controllers
|
||||
ExplorerClientProvider = explorerProvider;
|
||||
_feeRateProvider = feeRateProvider;
|
||||
_walletProvider = walletProvider;
|
||||
_WalletReceiveStateService = walletReceiveStateService;
|
||||
_walletReceiveService = walletReceiveService;
|
||||
_EventAggregator = eventAggregator;
|
||||
_settingsRepository = settingsRepository;
|
||||
_broadcaster = broadcaster;
|
||||
@ -359,13 +363,20 @@ namespace BTCPayServer.Controllers
|
||||
var network = NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
|
||||
var address = _WalletReceiveStateService.Get(walletId)?.Address;
|
||||
var address = _walletReceiveService.Get(walletId)?.Address;
|
||||
var allowedPayjoin = paymentMethod.IsHotWallet && CurrentStore.GetStoreBlob().PayJoinEnabled;
|
||||
var bip21 = address is null ? null : network.GenerateBIP21(address.ToString(), null);
|
||||
if (allowedPayjoin)
|
||||
{
|
||||
bip21 +=
|
||||
$"?{PayjoinClient.BIP21EndpointKey}={Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", new {walletId.CryptoCode}))}";
|
||||
}
|
||||
return View(new WalletReceiveViewModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
Address = address?.ToString(),
|
||||
CryptoImage = GetImage(paymentMethod.PaymentId, network)
|
||||
CryptoImage = GetImage(paymentMethod.PaymentId, network),
|
||||
PaymentLink = bip21
|
||||
});
|
||||
}
|
||||
|
||||
@ -382,29 +393,22 @@ namespace BTCPayServer.Controllers
|
||||
var network = this.NetworkProvider.GetNetwork<BTCPayNetwork>(walletId?.CryptoCode);
|
||||
if (network == null)
|
||||
return NotFound();
|
||||
var wallet = _walletProvider.GetWallet(network);
|
||||
switch (command)
|
||||
{
|
||||
case "unreserve-current-address":
|
||||
KeyPathInformation cachedAddress = _WalletReceiveStateService.Get(walletId);
|
||||
if (cachedAddress == null)
|
||||
var address = await _walletReceiveService.UnReserveAddress(walletId);
|
||||
if (!string.IsNullOrEmpty(address))
|
||||
{
|
||||
break;
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
AllowDismiss = true,
|
||||
Message = $"Address {address} was unreserved.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
});
|
||||
}
|
||||
var address = cachedAddress.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork);
|
||||
ExplorerClientProvider.GetExplorerClient(network)
|
||||
.CancelReservation(cachedAddress.DerivationStrategy, new[] { cachedAddress.KeyPath });
|
||||
this.TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
AllowDismiss = true,
|
||||
Message = $"Address {address} was unreserved.",
|
||||
Severity = StatusMessageModel.StatusSeverity.Success,
|
||||
});
|
||||
_WalletReceiveStateService.Remove(walletId);
|
||||
break;
|
||||
case "generate-new-address":
|
||||
var reserve = (await wallet.ReserveAddressAsync(paymentMethod.AccountDerivation));
|
||||
_WalletReceiveStateService.Set(walletId, reserve);
|
||||
await _walletReceiveService.GetOrGenerate(walletId, true);
|
||||
break;
|
||||
}
|
||||
return RedirectToAction(nameof(WalletReceive), new { walletId });
|
||||
@ -412,11 +416,8 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
private async Task<bool> CanUseHotWallet()
|
||||
{
|
||||
var isAdmin = (await _authorizationService.AuthorizeAsync(User, Policies.CanModifyServerSettings)).Succeeded;
|
||||
if (isAdmin)
|
||||
return true;
|
||||
var policies = await _settingsRepository.GetSettingAsync<PoliciesSettings>();
|
||||
return policies?.AllowHotWalletForAll is true;
|
||||
return (await _authorizationService.CanUseHotWallet(policies, User)).HotWallet;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
@ -476,10 +477,8 @@ namespace BTCPayServer.Controllers
|
||||
})
|
||||
.ToArray();
|
||||
var balance = _walletProvider.GetWallet(network).GetBalance(paymentMethod.AccountDerivation);
|
||||
model.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey));
|
||||
model.CurrentBalance = await balance;
|
||||
model.NBXSeedAvailable = await GetSeed(walletId, network) != null;
|
||||
model.CurrentBalance = (await balance).Total.GetValue(network);
|
||||
|
||||
await Task.WhenAll(recommendedFees);
|
||||
model.RecommendedSatoshiPerByte =
|
||||
@ -511,6 +510,15 @@ namespace BTCPayServer.Controllers
|
||||
return View(model);
|
||||
}
|
||||
|
||||
private async Task<string> GetSeed(WalletId walletId, BTCPayNetwork network)
|
||||
{
|
||||
return await CanUseHotWallet() &&
|
||||
GetDerivationSchemeSettings(walletId) is DerivationSchemeSettings s &&
|
||||
s.IsHotWallet &&
|
||||
ExplorerClientProvider.GetExplorerClient(network) is ExplorerClient client &&
|
||||
await client.GetMetadataAsync<string>(s.AccountDerivation, WellknownMetadataKeys.MasterHDKey) is string seed &&
|
||||
!string.IsNullOrEmpty(seed) ? seed : null;
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[Route("{walletId}/send")]
|
||||
@ -527,9 +535,7 @@ namespace BTCPayServer.Controllers
|
||||
if (network == null || network.ReadonlyWallet)
|
||||
return NotFound();
|
||||
vm.SupportRBF = network.SupportRBF;
|
||||
vm.NBXSeedAvailable = await CanUseHotWallet() && !string.IsNullOrEmpty(await ExplorerClientProvider.GetExplorerClient(network)
|
||||
.GetMetadataAsync<string>(GetDerivationSchemeSettings(walletId).AccountDerivation,
|
||||
WellknownMetadataKeys.MasterHDKey, cancellation));
|
||||
vm.NBXSeedAvailable = await GetSeed(walletId, network) != null;
|
||||
if (!string.IsNullOrEmpty(bip21))
|
||||
{
|
||||
LoadFromBIP21(vm, bip21, network);
|
||||
@ -661,7 +667,7 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
if (!ModelState.IsValid)
|
||||
return View(vm);
|
||||
|
||||
|
||||
DerivationSchemeSettings derivationScheme = GetDerivationSchemeSettings(walletId);
|
||||
|
||||
CreatePSBTResponse psbt = null;
|
||||
@ -724,7 +730,7 @@ namespace BTCPayServer.Controllers
|
||||
{
|
||||
new WalletSendModel.TransactionOutput()
|
||||
{
|
||||
Amount = uriBuilder.Amount.ToDecimal(MoneyUnit.BTC),
|
||||
Amount = uriBuilder.Amount?.ToDecimal(MoneyUnit.BTC),
|
||||
DestinationAddress = uriBuilder.Address.ToString(),
|
||||
SubtractFeesFromOutput = false
|
||||
}
|
||||
@ -897,17 +903,17 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint does not match the one set in your wallet settings. Probable cause are: wrong seed, wrong passphrase or wrong fingerprint in your wallet settings.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "The master fingerprint does not match the one set in your wallet settings. Probable causes are: wrong seed, wrong passphrase or wrong fingerprint in your wallet settings.");
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
|
||||
var changed = PSBTChanged(psbt, () => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath, new SigningOptions()
|
||||
var changed = psbt.PSBTChanged( () => psbt.SignAll(settings.AccountDerivation, signingKey, rootedKeyPath, new SigningOptions()
|
||||
{
|
||||
EnforceLowR = !(viewModel.SigningContext?.EnforceLowR is false)
|
||||
}));
|
||||
if (!changed)
|
||||
{
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Impossible to sign the transaction. Probable cause: Incorrect account key path in wallet settings, PSBT already signed.");
|
||||
ModelState.AddModelError(nameof(viewModel.SeedOrKey), "Impossible to sign the transaction. Probable causes: Incorrect account key path in wallet settings or PSBT already signed.");
|
||||
return View(nameof(SignWithSeed), viewModel);
|
||||
}
|
||||
ModelState.Remove(nameof(viewModel.SigningContext.PSBT));
|
||||
@ -920,15 +926,6 @@ namespace BTCPayServer.Controllers
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private bool PSBTChanged(PSBT psbt, Action act)
|
||||
{
|
||||
var before = psbt.ToBase64();
|
||||
act();
|
||||
var after = psbt.ToBase64();
|
||||
return before != after;
|
||||
}
|
||||
|
||||
private string ValueToString(Money v, BTCPayNetworkBase network)
|
||||
{
|
||||
return v.ToString() + " " + network.CryptoCode;
|
||||
@ -1035,26 +1032,19 @@ namespace BTCPayServer.Controllers
|
||||
|
||||
internal DerivationSchemeSettings GetDerivationSchemeSettings(WalletId walletId)
|
||||
{
|
||||
var paymentMethod = CurrentStore
|
||||
.GetSupportedPaymentMethods(NetworkProvider)
|
||||
.OfType<DerivationSchemeSettings>()
|
||||
.FirstOrDefault(p => p.PaymentId.PaymentType == Payments.PaymentTypes.BTCLike && p.PaymentId.CryptoCode == walletId.CryptoCode);
|
||||
return paymentMethod;
|
||||
return CurrentStore.GetDerivationSchemeSettings(NetworkProvider, walletId.CryptoCode);
|
||||
}
|
||||
|
||||
private static async Task<string> GetBalanceString(BTCPayWallet wallet, DerivationStrategyBase derivationStrategy)
|
||||
{
|
||||
using (CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)))
|
||||
using CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
return (await wallet.GetBalance(derivationStrategy, cts.Token)).ShowMoney(wallet.Network
|
||||
.Divisibility);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
return (await wallet.GetBalance(derivationStrategy, cts.Token)).Total.ShowMoney(wallet.Network);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return "--";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1149,10 +1139,22 @@ namespace BTCPayServer.Controllers
|
||||
}
|
||||
else if (command == "view-seed" && await CanUseHotWallet())
|
||||
{
|
||||
var seed = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
if (await GetSeed(walletId, derivationScheme.Network) != null)
|
||||
{
|
||||
var mnemonic = await ExplorerClientProvider.GetExplorerClient(walletId.CryptoCode)
|
||||
.GetMetadataAsync<string>(derivationScheme.AccountDerivation,
|
||||
WellknownMetadataKeys.Mnemonic, cancellationToken);
|
||||
if (string.IsNullOrEmpty(seed))
|
||||
var recoveryVm = new RecoverySeedBackupViewModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
Mnemonic = mnemonic,
|
||||
IsStored = true,
|
||||
RequireConfirm = false,
|
||||
ReturnUrl = Url.Action(nameof(WalletSettings), new { walletId })
|
||||
};
|
||||
return this.RedirectToRecoverySeedBackup(recoveryVm);
|
||||
}
|
||||
else
|
||||
{
|
||||
TempData.SetStatusMessageModel(new StatusMessageModel()
|
||||
{
|
||||
@ -1160,18 +1162,6 @@ namespace BTCPayServer.Controllers
|
||||
Message = "The seed was not found"
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
var recoveryVm = new RecoverySeedBackupViewModel()
|
||||
{
|
||||
CryptoCode = walletId.CryptoCode,
|
||||
Mnemonic = seed,
|
||||
IsStored = true,
|
||||
RequireConfirm = false,
|
||||
ReturnUrl = Url.Action(nameof(WalletSettings), new { walletId })
|
||||
};
|
||||
return this.RedirectToRecoverySeedBackup(recoveryVm);
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(WalletSettings));
|
||||
}
|
||||
@ -1195,6 +1185,7 @@ namespace BTCPayServer.Controllers
|
||||
public string CryptoImage { get; set; }
|
||||
public string CryptoCode { get; set; }
|
||||
public string Address { get; set; }
|
||||
public string PaymentLink { get; set; }
|
||||
}
|
||||
|
||||
|
||||
|
@ -11,7 +11,6 @@ using BTCPayServer.Payments.CoinSwitch;
|
||||
using BTCPayServer.Rating;
|
||||
using BTCPayServer.Services.Mails;
|
||||
using BTCPayServer.Services.Rates;
|
||||
using BTCPayServer.Services.Shopify.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
@ -29,7 +28,6 @@ namespace BTCPayServer.Data
|
||||
PaymentMethodCriteria = new List<PaymentMethodCriteria>();
|
||||
}
|
||||
|
||||
public ShopifySettings Shopify { get; set; }
|
||||
|
||||
[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]
|
||||
public NetworkFeeMode NetworkFeeMode { get; set; }
|
||||
@ -38,6 +36,7 @@ namespace BTCPayServer.Data
|
||||
public bool LightningAmountInSatoshi { get; set; }
|
||||
public bool LightningPrivateRouteHints { get; set; }
|
||||
public bool OnChainWithLnInvoiceFallback { get; set; }
|
||||
public bool LazyPaymentMethods { get; set; }
|
||||
public bool RedirectAutomatically { get; set; }
|
||||
public bool ShowRecommendedFee { get; set; }
|
||||
public int RecommendedFeeBlockTarget { get; set; }
|
||||
|
@ -69,14 +69,6 @@ namespace BTCPayServer.Data
|
||||
#pragma warning disable CS0618
|
||||
bool btcReturned = false;
|
||||
|
||||
// Legacy stuff which should go away
|
||||
if (!string.IsNullOrEmpty(storeData.DerivationStrategy))
|
||||
{
|
||||
btcReturned = true;
|
||||
yield return DerivationSchemeSettings.Parse(storeData.DerivationStrategy, networks.BTC);
|
||||
}
|
||||
|
||||
|
||||
if (!string.IsNullOrEmpty(storeData.DerivationStrategies))
|
||||
{
|
||||
JObject strategies = JObject.Parse(storeData.DerivationStrategies);
|
||||
@ -129,11 +121,9 @@ namespace BTCPayServer.Data
|
||||
bool existing = false;
|
||||
foreach (var strat in strategies.Properties().ToList())
|
||||
{
|
||||
var stratId = PaymentMethodId.Parse(strat.Name);
|
||||
if (stratId.IsBTCOnChain)
|
||||
if (!PaymentMethodId.TryParse(strat.Name, out var stratId))
|
||||
{
|
||||
// Legacy stuff which should go away
|
||||
storeData.DerivationStrategy = null;
|
||||
continue;
|
||||
}
|
||||
if (stratId == paymentMethodId)
|
||||
{
|
||||
@ -149,12 +139,7 @@ namespace BTCPayServer.Data
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!existing && supportedPaymentMethod == null && paymentMethodId.IsBTCOnChain)
|
||||
{
|
||||
storeData.DerivationStrategy = null;
|
||||
}
|
||||
else if (!existing && supportedPaymentMethod != null)
|
||||
if (!existing && supportedPaymentMethod != null)
|
||||
strategies.Add(new JProperty(supportedPaymentMethod.PaymentId.ToString(), PaymentMethodExtensions.Serialize(supportedPaymentMethod)));
|
||||
storeData.DerivationStrategies = strategies.ToString();
|
||||
#pragma warning restore CS0618
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user