Compare commits

..

1 Commits

Author SHA1 Message Date
715ba4cca5 Attempt to solve webhooks disappearing 2021-01-03 14:48:48 +09:00
351 changed files with 20681 additions and 28222 deletions

View File

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

View File

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

View File

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

View File

@ -1,4 +1,3 @@
#nullable enable
using System.Threading;
using System.Threading.Tasks;
@ -6,8 +5,8 @@ namespace BTCPayServer.Abstractions.Contracts
{
public interface ISettingsRepository
{
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;
Task<T> GetSettingAsync<T>(string name = null);
Task UpdateSetting<T>(T obj, string name = null);
Task<T> WaitSettingsChanged<T>(CancellationToken cancellationToken = default);
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@
<RepositoryType>git</RepositoryType>
</PropertyGroup>
<PropertyGroup>
<Version Condition=" '$(Version)' == '' ">1.3.0</Version>
<Version Condition=" '$(Version)' == '' ">1.2.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.73" />
<PackageReference Include="NBitcoin" Version="5.0.60" />
<PackageReference Include="BTCPayServer.Lightning.Common" Version="1.2.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
</ItemGroup>

View File

@ -85,13 +85,5 @@ 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);
}
}
}

View File

@ -1,63 +0,0 @@
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Client.Models;
namespace BTCPayServer.Client
{
public partial class BTCPayServerClient
{
public virtual async Task<IEnumerable<LightningNetworkPaymentMethodData>>
GetStoreLightningNetworkPaymentMethods(string storeId,
CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork"), token);
return await HandleResponse<IEnumerable<LightningNetworkPaymentMethodData>>(response);
}
public virtual async Task<LightningNetworkPaymentMethodData> GetStoreLightningNetworkPaymentMethod(
string storeId,
string cryptoCode, CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}"), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
public virtual async Task RemoveStoreLightningNetworkPaymentMethod(string storeId,
string cryptoCode, CancellationToken token = default)
{
var response =
await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
method: HttpMethod.Delete), token);
await HandleResponse(response);
}
public virtual async Task<LightningNetworkPaymentMethodData> UpdateStoreLightningNetworkPaymentMethod(
string storeId,
string cryptoCode, LightningNetworkPaymentMethodData paymentMethod,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}",
bodyPayload: paymentMethod, method: HttpMethod.Put), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
public virtual async Task<LightningNetworkPaymentMethodData>
UpdateStoreLightningNetworkPaymentMethodToInternalNode(string storeId,
string cryptoCode, LightningNetworkPaymentMethodData paymentMethod,
CancellationToken token = default)
{
var response = await _httpClient.SendAsync(
CreateHttpRequest($"api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}/internal",
method: HttpMethod.Put), token);
return await HandleResponse<LightningNetworkPaymentMethodData>(response);
}
}
}

View File

@ -1,122 +0,0 @@
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);
}
}
}

View File

@ -9,7 +9,7 @@ namespace BTCPayServer.Client.Models
{
public class CreateInvoiceRequest
{
[JsonConverter(typeof(NumericStringJsonConverter))]
[JsonProperty(ItemConverterType = typeof(NumericStringJsonConverter))]
public decimal Amount { get; set; }
public string Currency { get; set; }
public JObject Metadata { get; set; }
@ -33,8 +33,6 @@ 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; }
}
}

View File

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

View File

@ -1,30 +0,0 @@
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;
}
}

View File

@ -8,7 +8,6 @@ namespace BTCPayServer.Client.Models
{
public class InvoicePaymentMethodDataModel
{
public bool Activated { get; set; }
public string Destination { get; set; }
public string PaymentLink { get; set; }

View File

@ -1,14 +0,0 @@
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; }
}
}

View File

@ -1,28 +0,0 @@
namespace BTCPayServer.Client.Models
{
public class LightningNetworkPaymentMethodData
{
/// <summary>
/// Whether the payment method is enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Crypto code of the payment method
/// </summary>
public string CryptoCode { get; set; }
public string ConnectionString { get; set; }
public LightningNetworkPaymentMethodData()
{
}
public LightningNetworkPaymentMethodData(string cryptoCode, string connectionString, bool enabled)
{
Enabled = enabled;
CryptoCode = cryptoCode;
ConnectionString = connectionString;
}
}
}

View File

@ -1,15 +0,0 @@
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; }
}
}

View File

@ -1,12 +0,0 @@
using NBitcoin;
using NBitcoin.JsonConverters;
using Newtonsoft.Json;
namespace BTCPayServer.Client.Models
{
public class OnChainWalletFeeRateData
{
[JsonConverter(typeof(FeeRateJsonConverter))]
public FeeRate FeeRate { get; set; }
}
}

View File

@ -1,17 +0,0 @@
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; }
}
}

View File

@ -1,34 +0,0 @@
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; }
}
}

View File

@ -1,25 +0,0 @@
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; }
}
}

View File

@ -35,7 +35,6 @@ 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)]
@ -54,6 +53,8 @@ namespace BTCPayServer.Client.Models
public string HtmlTitle { get; set; }
[JsonConverter(typeof(StringEnumConverter))]
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public NetworkFeeMode NetworkFeeMode { get; set; } = NetworkFeeMode.Never;

View File

@ -1,9 +0,0 @@
namespace BTCPayServer.Client.Models
{
public enum TransactionStatus
{
Unconfirmed,
Confirmed,
Replaced
}
}

View File

@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Argoneum",
BlockExplorerLink = NetworkType == ChainName.Mainnet
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("421'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("421'")
: new KeyPath("1'")
});
}

View File

@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BGold",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://btgexplorer.com/tx/{0}" : "https://testnet.btgexplorer.com/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("156'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "BPlus",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/xbc/tx.dws?{0}" : "https://chainz.cryptoid.info/xbc/tx.dws?{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("65'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcore",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://insight.bitcore.cc/tx/{0}" : "https://insight.bitcore.cc/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("160'") : new KeyPath("1'")
});
}
}

View File

@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Chaincoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("711'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("711'")
: new KeyPath("1'")
});
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dash",
BlockExplorerLink = NetworkType == ChainName.Mainnet
BlockExplorerLink = NetworkType == NetworkType.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 = bitfinex(DSH_BTC)"
"DASH_BTC = bittrex(DASH_BTC)"
},
CryptoImagePath = "imlegacy/dash.png",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
//https://github.com/satoshilabs/slips/blob/master/slip-0044.md
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("5'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("5'")
: new KeyPath("1'")
});
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Dogecoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://dogechain.info/tx/{0}" : "https://dogechain.info/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("3'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Feathercoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.feathercoin.com/tx/{0}" : "https://explorer.feathercoin.com/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("8'") : new KeyPath("1'")
});
}
}

View File

@ -11,7 +11,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Groestlcoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
BlockExplorerLink = NetworkType == NetworkType.Mainnet
? "https://chainz.cryptoid.info/grs/tx.dws?{0}.htm"
: "https://chainz.cryptoid.info/grs-test/tx.dws?{0}.htm",
NBXplorerNetwork = nbxplorerNetwork,
@ -24,10 +24,9 @@ namespace BTCPayServer
CryptoImagePath = "imlegacy/groestlcoin.png",
LightningImagePath = "imlegacy/groestlcoin-lightning.svg",
DefaultSettings = BTCPayDefaultSettings.GetDefaultSettings(NetworkType),
CoinType = NetworkType == ChainName.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("17'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
VaultSupported = true
SupportPayJoin = true
});
}
}

View File

@ -13,7 +13,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Litecoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("2'") : new KeyPath("1'"),
//https://github.com/pooler/electrum-ltc/blob/0d6989a9d2fb2edbea421c116e49d1015c7c5a91/electrum_ltc/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy },

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Monacoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://mona.insight.monaco-ex.org/insight/tx/{0}" : "https://testnet-mona.insight.monaco-ex.org/insight/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("22'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "MonetaryUnit",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}" : "https://explorer.monetaryunit.org/#/MUE/mainnet/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("31'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Polis",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockbook.polispay.org/tx/{0}" : "https://blockbook.polispay.org/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1997'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Ufo",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://chainz.cryptoid.info/ufo/tx.dws?{0}" : "https://chainz.cryptoid.info/ufo/tx.dws?{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("202'") : new KeyPath("1'")
});
}
}

View File

@ -12,7 +12,7 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Viacoin",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://explorer.viacoin.org/tx/{0}" : "https://explorer.viacoin.org/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("14'") : new KeyPath("1'")
});
}
}

View File

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

View File

@ -13,7 +13,7 @@ namespace BTCPayServer
var nbxplorerNetwork = NBXplorerNetworkProvider.GetFromCryptoCode("LBTC");
Add(new ElementsBTCPayNetwork()
{
AssetId = NetworkType == ChainName.Mainnet ? ElementsParams<Liquid>.PeggedAssetId : ElementsParams<Liquid.LiquidRegtest>.PeggedAssetId,
AssetId = NetworkType == NetworkType.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 == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true
});
}

View File

@ -21,12 +21,12 @@ namespace BTCPayServer
},
AssetId = new uint256("ce091c998b83c78bb71a632313ba3760f1763d9cfcffae02258ffa9865a37bd2"),
DisplayName = "Liquid Tether",
BlockExplorerLink = NetworkType == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.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 == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.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 == ChainName.Mainnet ? "https://blockstream.info/liquid/tx/{0}" : "https://blockstream.info/testnet/liquid/tx/{0}",
BlockExplorerLink = NetworkType == NetworkType.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 == ChainName.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("1776'") : new KeyPath("1'"),
SupportRBF = true,
SupportLightning = false
});

View File

@ -24,22 +24,38 @@ namespace BTCPayServer
});
}
public override List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
public override GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
{
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));
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)
};
}
public override string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
//precision 0: 10 = 0.00000010
//precision 2: 10 = 0.00001000
//precision 8: 10 = 10
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}";
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}";
}
}
}

View File

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

View File

@ -18,29 +18,25 @@ namespace BTCPayServer
{
static BTCPayDefaultSettings()
{
_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)
_Settings = new Dictionary<NetworkType, BTCPayDefaultSettings>();
foreach (var chainType in new[] { NetworkType.Mainnet, NetworkType.Testnet, NetworkType.Regtest })
{
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 == ChainName.Mainnet ? 23000 :
chainType == ChainName.Regtest ? 23002
: 23001);
settings.DefaultPort = (chainType == NetworkType.Mainnet ? 23000 :
chainType == NetworkType.Regtest ? 23002 :
chainType == NetworkType.Testnet ? 23001 : throw new NotSupportedException(chainType.ToString()));
}
}
static readonly Dictionary<NetworkType, BTCPayDefaultSettings> _Settings;
public static BTCPayDefaultSettings GetDefaultSettings(NetworkType chainType)
{
return _Settings[chainType];
}
@ -54,18 +50,18 @@ namespace BTCPayServer
{
public Network NBitcoinNetwork { get { return NBXplorerNetwork?.NBitcoinNetwork; } }
public NBXplorer.NBXplorerNetwork NBXplorerNetwork { get; set; }
public bool SupportRBF { get; set; }
public bool SupportRBF { get; internal set; }
public string LightningImagePath { get; set; }
public BTCPayDefaultSettings DefaultSettings { get; set; }
public KeyPath CoinType { get; set; }
public KeyPath CoinType { get; internal set; }
public Dictionary<uint, DerivationType> ElectrumMapping = new Dictionary<uint, DerivationType>();
public virtual bool WalletSupported { get; set; } = true;
public virtual bool ReadonlyWallet { get; set; } = false;
public virtual bool VaultSupported { get; set; } = false;
public int MaxTrackedConfirmation { get; set; } = 6;
public string UriScheme { get; set; }
public int MaxTrackedConfirmation { get; internal set; } = 6;
public string UriScheme { get; internal set; }
public bool SupportPayJoin { get; set; } = false;
public bool SupportLightning { get; set; } = true;
@ -123,12 +119,12 @@ namespace BTCPayServer
public virtual string GenerateBIP21(string cryptoInfoAddress, Money cryptoInfoDue)
{
return $"{UriScheme}:{cryptoInfoAddress}{(cryptoInfoDue is null? string.Empty: $"?amount={cryptoInfoDue.ToString(false, true)}")}";
return $"{UriScheme}:{cryptoInfoAddress}?amount={cryptoInfoDue.ToString(false, true)}";
}
public virtual List<TransactionInformation> FilterValidTransactions(List<TransactionInformation> transactionInformationSet)
public virtual GetTransactionsResponse FilterValidTransactions(GetTransactionsResponse response)
{
return transactionInformationSet;
return response;
}
}
@ -136,7 +132,7 @@ namespace BTCPayServer
{
private string _blockExplorerLink;
public bool ShowSyncSummary { get; set; } = true;
public string CryptoCode { get; set; }
public string CryptoCode { get; internal set; }
public string BlockExplorerLink
{
@ -152,7 +148,7 @@ namespace BTCPayServer
}
}
public string BlockExplorerLinkDefault { get; set; }
public string BlockExplorerLinkDefault { get; internal set; }
public string DisplayName { get; set; }
public int Divisibility { get; set; } = 8;
[Obsolete("Should not be needed")]
@ -165,7 +161,7 @@ namespace BTCPayServer
}
public string CryptoImagePath { get; set; }
public string[] DefaultRateRules { get; set; } = Array.Empty<string>();
public string[] DefaultRateRules { get; internal set; } = Array.Empty<string>();
public override string ToString()
{
return CryptoCode;

View File

@ -13,20 +13,17 @@ namespace BTCPayServer
{
CryptoCode = nbxplorerNetwork.CryptoCode,
DisplayName = "Bitcoin",
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}",
BlockExplorerLink = NetworkType == NetworkType.Mainnet ? "https://blockstream.info/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 == ChainName.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
CoinType = NetworkType == NetworkType.Mainnet ? new KeyPath("0'") : new KeyPath("1'"),
SupportRBF = true,
SupportPayJoin = true,
VaultSupported = true,
//https://github.com/spesmilo/electrum/blob/11733d6bc271646a00b69ff07657119598874da4/electrum/constants.py
ElectrumMapping = NetworkType == ChainName.Mainnet
ElectrumMapping = NetworkType == NetworkType.Mainnet
? new Dictionary<uint, DerivationType>()
{
{0x0488b21eU, DerivationType.Legacy }, // xpub

View File

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

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="NBXplorer.Client" Version="3.0.20" />
<PackageReference Include="NBXplorer.Client" Version="3.0.19" />
</ItemGroup>
<ItemGroup Condition="'$(Altcoins)' != 'true'">
<Compile Remove="Altcoins\**\*.cs"></Compile>

View File

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

View File

@ -11,16 +11,13 @@ 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,
maxLength: maxLength);
nullable: true);
migrationBuilder.CreateTable(
name: "Notifications",
@ -76,7 +73,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>(maxLength: maxLength, nullable: true),
Destination = table.Column<string>(nullable: true),
Blob = table.Column<byte[]>(nullable: true),
Proof = table.Column<byte[]>(nullable: true)
},
@ -95,8 +92,8 @@ namespace BTCPayServer.Migrations
name: "Refunds",
columns: table => new
{
InvoiceDataId = table.Column<string>(maxLength: maxLength, nullable: false),
PullPaymentDataId = table.Column<string>(maxLength: maxLength, nullable: false)
InvoiceDataId = table.Column<string>(nullable: false),
PullPaymentDataId = table.Column<string>(nullable: false)
},
constraints: table =>
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.6.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="NBitcoin" Version="5.0.73" />
<PackageReference Include="NBitcoin" Version="5.0.60" />
<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

View File

@ -1,31 +1,60 @@
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.Scripting.Parser;
using NBitcoin.DataEncoders;
using NBitcoin.Payment;
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
{
@ -42,7 +71,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
[Trait("Altcoins", "Altcoins")]
[Trait("Lightning", "Lightning")]
public async Task CanSetupWallet()
public async Task CanAddDerivationSchemes()
{
using (var tester = ServerTester.Create())
{
@ -50,7 +79,7 @@ namespace BTCPayServer.Tests
tester.ActivateLightning();
await tester.StartAsync();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("LTC");
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
@ -69,37 +98,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);
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);
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);
// Only Enabling/Disabling the payment method must redirect to store page
setupVm.Enabled = false;
response = controller.UpdateWallet(setupVm).GetAwaiter().GetResult();
Assert.IsType<RedirectToActionResult>(response);
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);
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;
// 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);
invoice = user.BitPay.CreateInvoice(
new Invoice
new Invoice()
{
Price = 1.5m,
Currency = "USD",
@ -113,57 +142,76 @@ namespace BTCPayServer.Tests
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
// Removing the derivation scheme, should redirect to store page
response = controller.ConfirmDeleteWallet(user.StoreId, "BTC").GetAwaiter().GetResult();
Assert.IsType<RedirectToActionResult>(response);
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());
// 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);
// 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);
// 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)\"}";
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);
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());
// 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)
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
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
// 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}";
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);
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());
// Now let's check that no data has been lost in the process
var store = tester.PayTester.StoreRepository.FindStore(storeId).GetAwaiter().GetResult();
var store = tester.PayTester.StoreRepository.FindStore(user.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);
@ -238,7 +286,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
user.RegisterDerivationScheme("BTC");
user.RegisterDerivationScheme("LTC");
@ -246,6 +294,7 @@ 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>()
@ -429,21 +478,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.FindElement(By.ClassName("payment__currencies"));
var currencyDropdownButton = s.Driver.WaitForElement(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.FindElement(By.ClassName("payment__currencies"));
currencyDropdownButton = s.Driver.WaitForElement(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.FindElement(By.ClassName("payment__currencies"));
currencyDropdownButton = s.Driver.WaitForElement(By.ClassName("payment__currencies"));
Assert.Contains("Lightning", currencyDropdownButton.Text);
s.Driver.Quit();
@ -822,11 +871,11 @@ normal:
{
#pragma warning disable CS0618
var dummy = new Key().PubKey.GetAddress(ScriptPubKeyType.Legacy, Network.RegTest).ToString();
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
});
var networkBTC = networkProvider.GetNetwork("BTC");
var networkLTC = networkProvider.GetNetwork("LTC");
@ -902,9 +951,9 @@ normal:
[Trait("Altcoins", "Altcoins")]
public void CanParseDerivationScheme()
{
var testnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(ChainName.Mainnet);
var testnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Testnet);
var regtestNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var mainnetNetworkProvider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var testnetParser = new DerivationSchemeParser(testnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
var mainnetParser = new DerivationSchemeParser(mainnetNetworkProvider.GetNetwork<BTCPayNetwork>("BTC"));
NBXplorer.DerivationStrategy.DerivationStrategyBase result;
@ -991,99 +1040,6 @@ normal:
Assert.Equal(
"tpubDDdeNbNDRgqestPX5XEJM8ELAq6eR5cne5RPbBHHvWSSiLHNHehsrn1kGCijMnHFSsFFQMqHcdMfGzDL3pWHRasPMhcGRqZ4tFankQ3i4ok-[legacy]",
parsed.ToString());
//let's test output descriptor parsing support
//we don't support every descriptor, only the ones which represent an HD wallet with stndard derivation paths
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pk(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("wpkh(02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wpkh(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("combo(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(wsh(pkh(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13)))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("multi(1,022f8bde4d1a07209355b4a7250a5c5128e88b84bddc619ab7cba8d569b240efe4,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(multi(2,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe))"));
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("sh(sortedmulti(2,03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01))"));
//let's see what we actually support now
//standard legacy hd wallet
var parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh([d34db33f/44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal(KeyPath.Parse("44'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//masterfingerprint and key path are optional
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh([d34db33f]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//a master fingerprint must always be present if youre providing rooted path
Assert.Throws<ParsingException>(() => mainnetParser.ParseOutputDescriptor("pkh([44'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
//but a different deriv path from standard (0/*) is not supported
Assert.Throws<FormatException>(() => mainnetParser.ParseOutputDescriptor("pkh(xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/1/*)"));
//p2sh-segwit hd wallet
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(wpkh([d34db33f/49'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(KeyPath.Parse("49'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[p2sh]",parsedDescriptor.Item1.ToString() );
//segwit hd wallet
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"wpkh([d34db33f/84'/0'/0']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)");
Assert.Equal(KeyPath.Parse("84'/0'/0'"),Assert.Single(parsedDescriptor.Item2).KeyPath);
Assert.Equal( HDFingerprint.Parse("d34db33f"),Assert.Single(parsedDescriptor.Item2).MasterFingerprint);
Assert.Equal("xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",parsedDescriptor.Item1.ToString() );
//multisig tests
//legacy
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(multi(1,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/45'/0]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
var strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner);
Assert.True(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]-[keeporder]",parsedDescriptor.Item1.ToString() );
//segwit
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(parsedDescriptor.Item1).Inner);
Assert.False(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]",parsedDescriptor.Item1.ToString() );
//segwit-p2sh
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(wsh(multi(1,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/2']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*)))");
Assert.Equal(2, parsedDescriptor.Item2.Length);
strat = Assert.IsType<MultisigDerivationStrategy>(Assert.IsType<P2WSHDerivationStrategy>(Assert.IsType<P2SHDerivationStrategy>(parsedDescriptor.Item1).Inner).Inner);
Assert.False(strat.IsLegacy);
Assert.Equal(1,strat.RequiredSignatures);
Assert.Equal(2,strat.Keys.Count());
Assert.False(strat.LexicographicOrder);
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[keeporder]-[p2sh]",parsedDescriptor.Item1.ToString() );
//sorted
parsedDescriptor = mainnetParser.ParseOutputDescriptor(
"sh(sortedmulti(1,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*,[d34db33f/48'/0'/0'/1']xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL/0/*))");
Assert.Equal("1-of-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL-[legacy]",parsedDescriptor.Item1.ToString() );
}
}
}

View File

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

View File

@ -61,28 +61,28 @@ namespace BTCPayServer.Tests
Assert.Contains("btcpay.server.canmodifyserversettings", s.Driver.PageSource);
//server management should show now
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.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.SetCheckbox(s, "btcpay.user.canviewprofile", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var superApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var superApiKey = s.AssertHappyMessage().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.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), true);
s.SetCheckbox(s, "btcpay.server.canmodifyserversettings", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var serverOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var serverOnlyApiKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
await TestApiAgainstAccessToken(serverOnlyApiKey, tester, user,
Policies.CanModifyServerSettings);
s.Driver.FindElement(By.Id("AddApiKey")).Click();
s.Driver.SetCheckbox(By.Id("btcpay.store.canmodifystoresettings"), true);
s.SetCheckbox(s, "btcpay.store.canmodifystoresettings", true);
s.Driver.FindElement(By.Id("Generate")).Click();
var allStoreOnlyApiKey = s.FindAlertMessage().FindElement(By.TagName("code")).Text;
var allStoreOnlyApiKey = s.AssertHappyMessage().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.FindAlertMessage().FindElement(By.TagName("code")).Text;
var selectiveStoreApiKey = s.AssertHappyMessage().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.FindAlertMessage().FindElement(By.TagName("code")).Text;
var noPermissionsApiKey = s.AssertHappyMessage().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.Driver.SetCheckbox(By.Id("btcpay.server.canmodifyserversettings"), false);
s.SetCheckbox(s, "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.FindAlertMessage().FindElement(By.TagName("code")).Text;
var allAPIKey = s.AssertHappyMessage().FindElement(By.TagName("code")).Text;
var apikeydata = await TestApiAgainstAccessToken<ApiKeyData>(allAPIKey, $"api/v1/api-keys/current", tester.PayTester.HttpClient);
Assert.Equal(checkedPermissionCount, apikeydata.Permissions.Length);
}

View File

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

View File

@ -91,7 +91,7 @@ namespace BTCPayServer.Tests
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
string chain = NBXplorerDefaultSettings.GetFolderName(ChainName.Regtest);
string chain = NBXplorerDefaultSettings.GetFolderName(NetworkType.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}");
config.AppendLine($"btc.lightning={IntegratedLightning.AbsoluteUri}");
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 string IntegratedLightning { get; internal set; }
public Uri IntegratedLightning { get; internal set; }
public bool InContainer { get; internal set; }
public T GetService<T>()

View File

@ -1,10 +1,13 @@
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;
@ -33,7 +36,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")).Click();
s.Driver.FindElement(By.Name("command")).ForceClick();
var emailAlreadyThereInvoiceId = s.CreateInvoice(store.storeName, 100, "USD", "a@g.com");
s.GoToInvoiceCheckout(emailAlreadyThereInvoiceId);
@ -109,14 +112,14 @@ namespace BTCPayServer.Tests
s.Server.ActivateLightning();
await s.StartAsync();
s.GoToRegister();
s.RegisterNewUser(true);
s.RegisterNewUser();
var store = s.CreateNewStore();
s.AddLightningNode();
s.AddInternalLightningNode("BTC");
s.GoToStore(store.storeId, StoreNavPages.Checkout);
s.Driver.SetCheckbox(By.Id("LightningAmountInSatoshi"), true);
s.SetCheckbox(s, "LightningAmountInSatoshi", true);
var command = s.Driver.FindElement(By.Name("command"));
command.Click();
command.ForceClick();
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);
@ -163,4 +166,30 @@ namespace BTCPayServer.Tests
}
}
}
public static class SeleniumExtensions
{
/// <summary>
/// Utility method to wait until timeout for element to be present (optionally displayed)
/// </summary>
/// <param name="context">Wait context</param>
/// <param name="by">How we search for element</param>
/// <param name="displayed">Flag to wait for element to be displayed or just present</param>
/// <param name="timeout">How long to wait for element to be present/displayed</param>
/// <returns>Element we were waiting for</returns>
public static IWebElement WaitForElement(this IWebDriver context, By by, bool displayed = true, uint timeout = 3)
{
var wait = new DefaultWait<IWebDriver>(context);
wait.Timeout = TimeSpan.FromSeconds(timeout);
wait.IgnoreExceptionTypes(typeof(NoSuchElementException));
return wait.Until(ctx =>
{
var elem = ctx.FindElement(by);
if (displayed && !elem.Displayed)
return null;
return elem;
});
}
}
}

View File

@ -7,24 +7,30 @@ 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) => JsonConvert.SerializeObject(o, Formatting.None, JsonSettings);
public static void LogIn(this SeleniumTester s, string email)
private static readonly JsonSerializerSettings jsonSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
public static string ToJson(this object o)
{
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();
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);
}
public static void AssertNoError(this IWebDriver driver)
{
try
@ -51,18 +57,14 @@ namespace BTCPayServer.Tests
builder.AppendLine($"[{entry.Level}]: {entry.Message}");
}
}
catch
{
// ignored
}
builder.AppendLine("---------");
catch { }
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;
}
@ -106,49 +108,5 @@ 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);
}
}
}
}

View File

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
@ -20,7 +19,6 @@ using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using NBitcoin;
using NBitcoin.OpenAsset;
using NBitcoin.Payment;
using NBitpayClient;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -542,7 +540,7 @@ namespace BTCPayServer.Tests
}
}
private async Task<GreenFieldValidationException> AssertValidationError(string[] fields, Func<Task> act)
private async Task AssertValidationError(string[] fields, Func<Task> act)
{
var remainingFields = fields.ToHashSet();
var ex = await Assert.ThrowsAsync<GreenFieldValidationException>(act);
@ -552,7 +550,6 @@ namespace BTCPayServer.Tests
remainingFields.Remove(field);
}
Assert.Empty(remainingFields);
return ex;
}
private async Task AssertHttpError(int code, Func<Task> act)
@ -951,12 +948,8 @@ 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\"}"), Checkout = new CreateInvoiceRequest.CheckoutOptions()
{
RedirectAutomatically = true
}});
Assert.True(newInvoice.Checkout.RedirectAutomatically);
new CreateInvoiceRequest() { Currency = "USD", Amount = 1, Metadata = JObject.Parse("{\"itemCode\": \"testitem\"}") });
//list
var invoices = await viewOnly.GetInvoices(user.StoreId);
@ -975,28 +968,25 @@ namespace BTCPayServer.Tests
//update
newInvoice = await client.CreateInvoice(user.StoreId,
new CreateInvoiceRequest() { Currency = "USD", Amount = 1 });
await client.MarkInvoiceStatus(user.StoreId, newInvoice.Id, new MarkInvoiceStatusRequest()
invoice = await viewOnly.GetInvoice(user.StoreId, newInvoice.Id);
await AssertValidationError(new[] { nameof(MarkInvoiceStatusRequest.Status) }, async () =>
{
Status = InvoiceStatus.Settled
await client.MarkInvoiceStatus(user.StoreId, invoice.Id, new MarkInvoiceStatusRequest()
{
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, invoice.Id,
await viewOnly.UpdateInvoice(user.StoreId, newInvoice.Id,
new UpdateInvoiceRequest()
{
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
});
});
invoice = await client.UpdateInvoice(user.StoreId, invoice.Id,
invoice = await client.UpdateInvoice(user.StoreId, newInvoice.Id,
new UpdateInvoiceRequest()
{
Metadata = JObject.Parse("{\"itemCode\": \"updated\", newstuff: [1,2,3,4,5]}")
@ -1004,12 +994,6 @@ 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 () =>
{
@ -1093,28 +1077,10 @@ 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 * 20 * 1000)]
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Integration", "Integration")]
[Trait("Lightning", "Lightning")]
public async Task CanUseLightningAPI()
@ -1132,7 +1098,8 @@ 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(LightMoney.Satoshis(1_000), "hey", TimeSpan.FromSeconds(60)));
var merchantInvoice = await merchantClient.CreateLightningInvoice(merchant.StoreId, "BTC", new CreateLightningInvoiceRequest(new LightMoney(1_000), "hey", TimeSpan.FromSeconds(60)));
tester.PayTester.GetService<BTCPayServerEnvironment>().DevelopmentOverride = false;
// The default client is using charge, so we should not be able to query channels
var client = await user.CreateClient(Policies.CanUseInternalLightningNode);
@ -1203,6 +1170,7 @@ namespace BTCPayServer.Tests
}
}
[Fact(Timeout = TestTimeout)]
[Trait("Integration", "Integration")]
public async Task NotificationAPITests()
@ -1298,340 +1266,9 @@ namespace BTCPayServer.Tests
await client.GetStoreOnChainPaymentMethod(store.Id, "BTC");
});
}
[Fact(Timeout = 60 * 2 * 1000)]
[Trait("Lightning", "Lightning")]
[Trait("Integration", "Integration")]
public async Task LightningNetworkPaymentMethodAPITests()
{
using var tester = ServerTester.Create();
tester.ActivateLightning();
await tester.StartAsync();
await tester.EnsureChannelsSetup();
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 adminClient.GetStoreLightningNetworkPaymentMethods(store.Id));
await AssertHttpError(403, async () =>
{
await viewOnlyClient.UpdateStoreLightningNetworkPaymentMethod(store.Id, "BTC", new LightningNetworkPaymentMethodData() { });
});
await AssertHttpError(404, async () =>
{
await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
});
await admin.RegisterLightningNodeAsync("BTC", false);
var method = await adminClient.GetStoreLightningNetworkPaymentMethod(store.Id, "BTC");
await AssertHttpError(403, async () =>
{
await viewOnlyClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
});
await adminClient.RemoveStoreOnChainPaymentMethod(store.Id, "BTC");
await AssertHttpError(404, async () =>
{
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();
settings.AllowLightningInternalNodeForAll = false;
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
var nonAdminUser = tester.NewAccount();
await nonAdminUser.GrantAccessAsync(false);
var nonAdminUserClient= await nonAdminUser.CreateClient(Policies.CanModifyStoreSettings);
await AssertHttpError(404, async () =>
{
await nonAdminUserClient.GetStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC");
});
await Assert.ThrowsAsync<GreenFieldValidationException>(async () =>
{
await nonAdminUserClient.UpdateStoreLightningNetworkPaymentMethod(nonAdminUser.StoreId, "BTC", method);
});
settings = await tester.PayTester.GetService<SettingsRepository>().GetSettingAsync<PoliciesSettings>();
settings.AllowLightningInternalNodeForAll = true;
await tester.PayTester.GetService<SettingsRepository>().UpdateSetting(settings);
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")]
@ -1648,6 +1285,7 @@ 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";

View File

@ -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,14 +19,12 @@ 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;
@ -87,19 +85,6 @@ 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 }));
}
}
@ -179,7 +164,9 @@ namespace BTCPayServer.Tests
var cashCow = tester.ExplorerNode;
cashCow.Generate(2); // get some money in case
var unsupportedFormats = new[] {ScriptPubKeyType.Legacy};
var unsupportedFormats = Enum.GetValues(typeof(ScriptPubKeyType))
.AssertType<ScriptPubKeyType[]>()
.Where(type => !PayjoinClient.SupportedFormats.Contains(type));
foreach (ScriptPubKeyType senderAddressType in Enum.GetValues(typeof(ScriptPubKeyType)))
@ -232,7 +219,7 @@ namespace BTCPayServer.Tests
var invoiceRepository = s.Server.PayTester.GetService<InvoiceRepository>();
s.RegisterNewUser(true);
foreach (var format in new []{ScriptPubKeyType.Segwit, ScriptPubKeyType.SegwitP2SH})
foreach (var format in PayjoinClient.SupportedFormats)
{
var receiver = s.CreateNewStore();
var receiverSeed = s.GenerateWallet("BTC", "", true, true, format);
@ -249,10 +236,9 @@ namespace BTCPayServer.Tests
s.GoToStore(receiver.storeId);
//payjoin is not enabled by default.
Assert.False(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
s.Driver.SetCheckbox(By.Id("PayJoinEnabled"), true);
s.SetCheckbox(s, "PayJoinEnabled", true);
s.Driver.FindElement(By.Id("Save")).Click();
Assert.True(s.Driver.FindElement(By.Id("PayJoinEnabled")).Selected);
var sender = s.CreateNewStore();
var senderSeed = s.GenerateWallet("BTC", "", true, true, format);
var senderWalletId = new WalletId(sender.storeId, "BTC");
@ -271,17 +257,16 @@ namespace BTCPayServer.Tests
s.Driver.SwitchTo().Alert().Accept();
Assert.False(string.IsNullOrEmpty(s.Driver.FindElement(By.Id("PayJoinBIP21"))
.GetAttribute("value")));
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();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
await s.Server.WaitForEvent<NewOnChainTransactionEvent>(() =>
{
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
return Task.CompletedTask;
});
//no funds in receiver wallet to do payjoin
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Warning);
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Warning);
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await s.Server.PayTester.GetService<InvoiceRepository>().GetInvoice(invoiceId);
@ -309,14 +294,15 @@ namespace BTCPayServer.Tests
.GetAttribute("value")));
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).Clear();
s.Driver.FindElement(By.Id("FeeSatoshiPerByte")).SendKeys("2");
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
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]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=payjoin]")).ForceClick();
return Task.CompletedTask;
});
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Success);
s.AssertHappyMessage(StatusMessageModel.StatusSeverity.Success);
await TestUtils.EventuallyAsync(async () =>
{
var invoice = await invoiceRepository.GetInvoice(invoiceId);
@ -377,15 +363,10 @@ namespace BTCPayServer.Tests
var alice = tester.NewAccount();
await alice.RegisterDerivationSchemeAsync("BTC", ScriptPubKeyType.Segwit, true);
await notifications.ListenDerivationSchemesAsync(new[] { alice.DerivationScheme });
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 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()
@ -427,7 +408,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
var requesting = pjClient.RequestPayjoin(bip21, 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]);
@ -443,7 +424,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -452,8 +433,9 @@ 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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -464,7 +446,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -476,7 +458,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -488,7 +470,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -499,9 +481,10 @@ 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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
requesting = pjClient.RequestPayjoin(bip21, derivationSchemeSettings, psbt, default);
request = await fakeServer.GetNextRequest();
originalPSBT = await ParsePSBT(request);
proposalTx = originalPSBT.GetGlobalTransaction();
@ -516,9 +499,6 @@ 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));
@ -528,7 +508,6 @@ 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 =
@ -547,7 +526,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, new PayjoinWallet(derivationSchemeSettings), psbt, default);
var proposal = await pjClient.RequestPayjoin(endpoint, 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());
@ -578,7 +557,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, new PayjoinWallet(derivationSchemeSettings), psbt, default));
var ex2 = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, derivationSchemeSettings, psbt, default));
Assert.Equal(PayjoinReceiverWellknownErrors.NotEnoughMoney, ex2.WellknownError);
}
}
@ -820,32 +799,16 @@ retry:
//give the cow some cash
await cashCow.GenerateAsync(1);
//let's get some more utxos first
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);
}
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);
var senderChange = await senderUser.GetNewAddress(btcPayNetwork);
//Let's start the harassment
@ -876,24 +839,17 @@ 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 =
@ -937,6 +893,10 @@ 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.

View File

@ -26,21 +26,6 @@ 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.
@ -70,45 +55,12 @@ 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?
`docker-compose -f docker-compose.altcoins.yml up dev`
If you still have issues, try to restart docker.
### How to run the Selenium test with a browser?
Run `dotnet user-secrets set RunSeleniumInBrowser true` to run tests in browser.
To switch back to headless mode (recommended) you can run `dotnet user-secrets remove RunSeleniumInBrowser`.
### Session not created: This version of ChromeDriver only supports Chrome version 88
When you run tests for selenium, you may end up with this error.
This happen when we update the selenium packages on BTCPay Server while you did not update your chrome version.
If you want to use a older chrome driver on [this page](https://chromedriver.chromium.org/downloads) then point to it with
`dotnet user-secrets set ChromeDriverDirectory "path/to/the/driver/directory"`

View File

@ -3,22 +3,23 @@ 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.Support.Extensions;
using OpenQA.Selenium.Interactions;
using Xunit;
namespace BTCPayServer.Tests
@ -27,69 +28,64 @@ namespace BTCPayServer.Tests
{
public IWebDriver Driver { get; set; }
public ServerTester Server { get; set; }
public WalletId WalletId { get; set; }
public string StoreId { 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 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();
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();
ChromeOptions 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");
}
if (!runInBrowser)
{
options.AddArguments("headless");
}
options.AddArguments($"window-size={windowSize.Width}x{windowSize.Height}");
options.AddArgument("shm-size=2g");
Driver = new ChromeDriver(chromeDriverPath, options,
// A bit less than test timeout
TimeSpan.FromSeconds(50));
if (runInBrowser)
var isDebug = !Server.PayTester.InContainer;
if (!isDebug)
{
// ensure maximized window size
// otherwise TESTS WILL FAIL because of different hierarchy in navigation menu
options.AddArguments("headless"); // Comment to view browser
options.AddArguments("window-size=1200x1000"); // Comment to view browser
}
options.AddArgument("shm-size=2g");
Driver = new ChromeDriver(Server.PayTester.InContainer ? "/usr/bin" : Directory.GetCurrentDirectory(), options);
if (isDebug)
{
//when running locally, depending on your resolution, the website may go into mobile responsive mode and screw with navigation of tests
Driver.Manage().Window.Maximize();
}
Logs.Tester.LogInformation($"Selenium: Using {Driver.GetType()}");
Logs.Tester.LogInformation($"Selenium: Browsing to {Server.PayTester.ServerUri}");
Logs.Tester.LogInformation("Selenium: Using chrome driver");
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 FindAlertMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
internal IWebElement AssertHappyMessage(StatusMessageModel.StatusSeverity severity = StatusMessageModel.StatusSeverity.Success)
{
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;
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;
}
public static readonly TimeSpan ImplicitWait = TimeSpan.FromSeconds(10);
public string Link(string relativeLink)
{
return Server.PayTester.ServerUri.AbsoluteUri.WithoutEndingSlash() + relativeLink.WithStartingSlash();
@ -97,9 +93,8 @@ namespace BTCPayServer.Tests
public void GoToRegister()
{
Driver.Navigate().GoToUrl(Link("/register"));
Driver.Navigate().GoToUrl(this.Link("/Account/Register"));
}
public string RegisterNewUser(bool isAdmin = false)
{
var usr = RandomUtils.GetUInt256().ToString().Substring(64 - 20) + "@a.com";
@ -116,59 +111,34 @@ namespace BTCPayServer.Tests
public (string storeName, string storeId) CreateNewStore()
{
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);
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);
}
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}")).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))
{
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();
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
FindAlertMessage();
AssertHappyMessage();
if (string.IsNullOrEmpty(seed))
{
seed = Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
seed = Driver.FindElements(By.Id("recovery-phrase")).First().GetAttribute("data-mnemonic");
}
// Confirm seed backup
Driver.FindElement(By.Id("confirm")).Click();
Driver.FindElement(By.Id("submit")).Click();
@ -176,51 +146,38 @@ 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}")).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();
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();
}
public void AddLightningNode(string cryptoCode = "BTC", LightningConnectionType? connectionType = null)
public void AddLightningNode(string cryptoCode, LightningConnectionType connectionType)
{
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();
}
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";
else
{
Driver.FindElement(By.CssSelector("label[for=\"LightningNodeType-Custom\"]")).Click();
Driver.FindElement(By.Id("ConnectionString")).SendKeys(connectionString);
}
throw new NotSupportedException(connectionType.ToString());
var enabled = Driver.FindElement(By.Id("Enabled"));
if (!enabled.Selected) enabled.Click();
Driver.FindElement(By.Id($"Modify-Lightning{cryptoCode}")).ForceClick();
Driver.FindElement(By.Name($"ConnectionString")).SendKeys(connectionString);
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 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();
}
public void ClickOnAllSideMenus()
@ -236,23 +193,21 @@ namespace BTCPayServer.Tests
}
}
public void Dispose()
{
if (Driver != null)
{
try
{
Driver.Quit();
Driver.Close();
}
catch
{
// ignored
}
catch { }
Driver.Dispose();
}
Server?.Dispose();
if (Server != null)
Server.Dispose();
}
internal void AssertNotFound()
@ -286,7 +241,6 @@ 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();
@ -300,6 +254,33 @@ 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();
@ -316,13 +297,19 @@ namespace BTCPayServer.Tests
public void GoToLogin()
{
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "/login"));
Driver.Navigate().GoToUrl(new Uri(Server.PayTester.ServerUri, "Account/Login"));
}
public void GoToCreateInvoicePage()
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
}
public string CreateInvoice(string storeName, decimal amount = 100, string currency = "USD", string refundEmail = "")
{
GoToInvoices();
Driver.FindElement(By.Id("CreateNewInvoice")).Click();
Driver.FindElement(By.Id("CreateNewInvoice")).Click(); // ocassionally gets stuck for some reason, tried force click and wait for element
Driver.FindElement(By.Id("Amount")).SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var currencyEl = Driver.FindElement(By.Id("Currency"));
currencyEl.Clear();
@ -331,7 +318,8 @@ namespace BTCPayServer.Tests
Driver.FindElement(By.Name("StoreId")).SendKeys(storeName);
Driver.FindElement(By.Id("Create")).Click();
var statusElement = FindAlertMessage();
AssertHappyMessage();
var statusElement = Driver.FindElement(By.ClassName("alert-success"));
var id = statusElement.Text.Split(" ")[1];
return id;
}
@ -343,7 +331,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 (var i = 0; i < coins; i++)
for (int i = 0; i < coins; i++)
{
await Server.ExplorerNode.SendToAddressAsync(address, Money.Coins(denomination));
}
@ -356,15 +344,19 @@ namespace BTCPayServer.Tests
.GetAttribute("href");
Assert.Contains($"{PayjoinClient.BIP21EndpointKey}", bip21);
GoToWallet(walletId);
GoToWallet(walletId, WalletsNavPages.Send);
Driver.FindElement(By.Id("bip21parse")).Click();
Driver.SwitchTo().Alert().SendKeys(bip21);
Driver.SwitchTo().Alert().Accept();
Driver.FindElement(By.Id("SendMenu")).Click();
Driver.ScrollTo(By.Id("SendMenu"));
Driver.FindElement(By.Id("SendMenu")).ForceClick();
Driver.FindElement(By.CssSelector("button[value=nbx-seed]")).Click();
Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
}
private void CheckForJSErrors()
{
//wait for seleniun update: https://stackoverflow.com/questions/57520296/selenium-webdriver-3-141-0-driver-manage-logs-availablelogtypes-throwing-syste
@ -410,6 +402,7 @@ namespace BTCPayServer.Tests
{
Driver.FindElement(By.Id($"Server-{navPages}")).Click();
}
}
public void GoToInvoice(string id)

View File

@ -3,36 +3,37 @@ 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
{
private const int TestTimeout = TestUtils.TestTimeout;
public const int TestTimeout = TestUtils.TestTimeout;
public ChromeTests(ITestOutputHelper helper)
{
Logs.Tester = new XUnitLog(helper) { Name = "Tests" };
@ -84,51 +85,12 @@ 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.FindAlertMessage();
s.AssertHappyMessage();
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()
{
@ -139,7 +101,9 @@ namespace BTCPayServer.Tests
var email = s.RegisterNewUser();
s.Logout();
s.Driver.AssertNoError();
Assert.Contains("/login", s.Driver.Url);
Assert.Contains("Account/Login", s.Driver.Url);
// Should show the Tor address
Assert.Contains("wsaxew3qa5ljfuenfebmaf3m5ykgatct3p6zjrqwoouj3foererde3id.onion", s.Driver.PageSource);
s.Driver.Navigate().GoToUrl(s.Link("/invoices"));
Assert.Contains("ReturnUrl=%2Finvoices", s.Driver.Url);
@ -179,15 +143,15 @@ namespace BTCPayServer.Tests
//let's test invite link
s.Logout();
s.GoToRegister();
s.RegisterNewUser(true);
var newAdminUser = 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.FindAlertMessage().FindElement(By.TagName("a")).Text;
var url = s.AssertHappyMessage().FindElement(By.TagName("a")).Text;
;
s.Logout();
s.Driver.Navigate().GoToUrl(url);
Assert.Equal("hidden", s.Driver.FindElement(By.Id("Email")).GetAttribute("type"));
@ -196,7 +160,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.FindAlertMessage();
s.AssertHappyMessage();
s.Driver.FindElement(By.Id("Email")).SendKeys(usr);
s.Driver.FindElement(By.Id("Password")).SendKeys("123456");
s.Driver.FindElement(By.Id("LoginButton")).Click();
@ -206,16 +170,23 @@ 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();
s.RegisterNewUser(isAdmin: true);
var alice = 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<Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
using (var client = await s.Server.PayTester.GetService<BTCPayServer.Configuration.BTCPayServerOptions>().SSHSettings.ConnectAsync())
{
var result = await client.RunBash("echo hello");
Assert.Equal(string.Empty, result.Error);
@ -226,7 +197,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")).Click();
s.Driver.FindElement(By.Id("submit")).ForceClick();
s.Driver.AssertNoError();
var text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
@ -236,7 +207,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")).Click();
s.Driver.FindElement(By.Id("submit")).ForceClick();
text = s.Driver.FindElement(By.Id("SSHKeyFileContent")).Text;
Assert.DoesNotContain("test2", text);
@ -249,12 +220,12 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(isAdmin: true);
var alice = 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.FindAlertMessage();
s.AssertHappyMessage();
}
CanSetupEmailCore(s);
s.CreateNewStore();
@ -263,13 +234,33 @@ 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();
s.RegisterNewUser(isAdmin: true);
var alice = s.RegisterNewUser(isAdmin: true);
s.Driver.Navigate().GoToUrl(s.Link("/server/services"));
Assert.Contains("Dynamic DNS", s.Driver.PageSource);
@ -314,15 +305,13 @@ 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(true);
var (storeName, storeId) = s.CreateNewStore();
var alice = s.RegisterNewUser();
var storeData = 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.";
@ -331,47 +320,39 @@ namespace BTCPayServer.Tests
Assert.True(s.Driver.PageSource.Contains(offchainHint), "Lightning hint not present");
s.GoToStores();
Assert.True(s.Driver.PageSource.Contains($"warninghint_{storeId}"), "Warning hint on list not present");
Assert.True(s.Driver.PageSource.Contains("warninghint_" + storeData.storeId),
"Warning hint on list not present");
s.GoToStore(storeId);
Assert.Contains(storeName, s.Driver.PageSource);
s.GoToStore(storeData.storeId);
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");
// setup onchain wallet
s.GoToStore(storeId);
s.AddDerivationScheme();
s.AddDerivationScheme(); // wallet hint should be dismissed
s.Driver.AssertNoError();
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.False(s.Driver.PageSource.Contains(onchainHint),
"Wallet hint not dismissed on derivation scheme add");// dismiss lightning hint
Assert.Contains(storeData.storeName, s.Driver.PageSource);
var storeUrl = s.Driver.Url;
s.ClickOnAllSideMenus();
s.GoToInvoices();
var invoiceId = s.CreateInvoice(storeName);
s.FindAlertMessage();
var invoiceId = s.CreateInvoice(storeData.storeName);
s.AssertHappyMessage();
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.FindAlertMessage();
s.AssertHappyMessage();
Assert.DoesNotContain("Archived", s.Driver.FindElement(By.Id("btn-archive-toggle")).Text);
s.GoToInvoices();
Assert.Contains(invoiceId, s.Driver.PageSource);
@ -393,14 +374,14 @@ namespace BTCPayServer.Tests
s.Logout();
// Let's add Bob as a guest to alice's store
s.LogIn(alice);
LogIn(s, 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
s.LogIn(bob);
LogIn(s, bob);
s.Driver.Navigate().GoToUrl(storeUrl);
Assert.Contains("ReturnUrl", s.Driver.Url);
s.Driver.Navigate().GoToUrl(invoiceUrl);
@ -408,8 +389,12 @@ namespace BTCPayServer.Tests
// Alice should be able to delete the store
s.Logout();
s.LogIn(alice);
LogIn(s, 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();
@ -427,17 +412,17 @@ namespace BTCPayServer.Tests
s.Driver.Navigate().GoToUrl(s.Link("/api-access-request"));
Assert.Contains("ReturnUrl", s.Driver.Url);
s.GoToRegister();
s.RegisterNewUser();
s.CreateNewStore();
var alice = s.RegisterNewUser();
var store = s.CreateNewStore().storeName;
s.AddDerivationScheme();
s.Driver.FindElement(By.Id("Tokens")).Click();
s.Driver.FindElement(By.Id("CreateNewToken")).Click();
s.Driver.FindElement(By.Id("RequestPairing")).Click();
var pairingCode = AssertUrlHasPairingCode(s);
string pairingCode = AssertUrlHasPairingCode(s);
s.Driver.FindElement(By.Id("ApprovePairing")).Click();
s.FindAlertMessage();
s.AssertHappyMessage();
Assert.Contains(pairingCode, s.Driver.PageSource);
var client = new NBitpayClient.Bitpay(new Key(), s.Server.PayTester.ServerUri);
@ -469,6 +454,15 @@ 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()
{
@ -476,62 +470,57 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser();
var (storeName, _) = s.CreateNewStore();
var store = 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");
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
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("Create")).Click();
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();
s.Driver.FindElement(By.Id("DefaultView")).SendKeys("Cart" + Keys.Enter);
s.Driver.FindElement(By.Id("SaveSettings")).ForceClick();
s.Driver.FindElement(By.Id("ViewApp")).ForceClick();
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 CanCreateCrowdfundingApp()
public async Task CanCreateAppCF()
{
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser();
var (storeName, _) = s.CreateNewStore();
var store = 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");
s.Driver.FindElement(By.Id("SelectedStore")).SendKeys(storeName);
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("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")).Click();
s.Driver.FindElement(By.Id("ViewApp")).Click();
Assert.Equal("Currently Active!", s.Driver.FindElement(By.CssSelector(".h6.text-muted")).Text);
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();
}
}
@ -550,10 +539,11 @@ 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")).Click();
s.Driver.FindElement(By.Name("ViewAppButton")).Click();
s.Driver.FindElement(By.Id("SaveButton")).ForceClick();
s.Driver.FindElement(By.Name("ViewAppButton")).SendKeys(Keys.Return);
s.Driver.SwitchTo().Window(s.Driver.WindowHandles.Last());
Assert.Equal("Amount due", s.Driver.FindElement(By.CssSelector("[data-test='amount-due-title']")).Text);
Assert.True(s.Driver.PageSource.Contains("Amount due"), "Unable to create Payment Request");
s.Driver.Quit();
}
}
@ -564,8 +554,8 @@ namespace BTCPayServer.Tests
using (var s = SeleniumTester.Create())
{
await s.StartAsync();
s.RegisterNewUser(true);
var (_, storeId) = s.CreateNewStore();
var userId = s.RegisterNewUser(true);
var storeId = s.CreateNewStore().storeId;
s.GenerateWallet("BTC", "", false, true);
var walletId = new WalletId(storeId, "BTC");
s.GoToWallet(walletId, WalletsNavPages.Receive);
@ -586,17 +576,16 @@ 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 wallet.GetUnspentCoins(x.AccountDerivation),
await s.Server.PayTester.GetService<BTCPayWalletProvider>().GetWallet(walletId.CryptoCode)
.GetUnspentCoins(x.AccountDerivation),
coin => coin.OutPoint == spentOutpoint);
});
await s.Server.ExplorerNode.GenerateAsync(1);
s.GoToWallet(walletId);
s.GoToWallet(walletId, WalletsNavPages.Send);
s.Driver.FindElement(By.Id("advancedSettings")).Click();
s.Driver.WaitForAndClick(By.Id("toggleInputSelection"));
s.Driver.FindElement(By.Id(spentOutpoint.ToString()));
s.Driver.FindElement(By.Id("toggleInputSelection")).Click();
s.Driver.WaitForElement(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();
@ -607,8 +596,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]")).Click();
var happyElement = s.FindAlertMessage();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
var happyElement = s.AssertHappyMessage();
var happyText = happyElement.Text;
var txid = Regex.Match(happyText, @"\((.*)\)").Groups[1].Value;
@ -625,11 +614,11 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
s.GoToStore(storeId, Views.Stores.StoreNavPages.Webhooks);
var store = s.CreateNewStore();
s.GoToStore(store.storeId, Views.Stores.StoreNavPages.Webhooks);
Logs.Tester.LogInformation("Let's create two webhooks");
for (var i = 0; i < 2; i++)
for (int 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}");
@ -647,7 +636,7 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("continue")).Click();
deletes = s.Driver.FindElements(By.LinkText("Delete"));
Assert.Single(deletes);
s.FindAlertMessage();
s.AssertHappyMessage();
Logs.Tester.LogInformation("Let's try to update one of them");
s.Driver.FindElement(By.LinkText("Modify")).Click();
@ -659,7 +648,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.FindAlertMessage();
s.AssertHappyMessage();
s.Driver.FindElement(By.LinkText("Modify")).Click();
foreach (var value in Enum.GetValues(typeof(WebhookEventType)))
{
@ -675,13 +664,13 @@ namespace BTCPayServer.Tests
Assert.DoesNotContain($"value=\"InvoiceReceivedPayment\" checked", s.Driver.PageSource);
s.Driver.FindElement(By.Name("update")).Click();
s.FindAlertMessage();
s.AssertHappyMessage();
Assert.Contains(server.ServerUri.AbsoluteUri, s.Driver.PageSource);
Logs.Tester.LogInformation("Let's see if we can generate an event");
s.GoToStore(storeId);
s.GoToStore(store.storeId);
s.AddDerivationScheme();
s.CreateInvoice(storeName);
s.CreateInvoice(store.storeName);
var request = await server.GetNextRequest();
var headers = request.Request.Headers;
var actualSig = headers["BTCPay-Sig"].First();
@ -692,22 +681,21 @@ namespace BTCPayServer.Tests
server.Done();
Logs.Tester.LogInformation("Let's make a failed event");
s.CreateInvoice(storeName);
s.CreateInvoice(store.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(storeId, Views.Stores.StoreNavPages.Webhooks);
s.GoToStore(store.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.FindAlertMessage();
s.AssertHappyMessage();
request = await server.GetNextRequest();
request.Response.StatusCode = 404;
server.Done();
@ -720,23 +708,32 @@ namespace BTCPayServer.Tests
CanBrowseContent(s);
var element = s.Driver.FindElement(By.ClassName("redeliver"));
element.Click();
s.FindAlertMessage();
s.AssertHappyMessage();
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(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.GoToStore(store.storeId);
s.Driver.ExecuteJavaScript("window.scrollBy(0,1000);");
s.Driver.FindElement(By.Id("danger-zone-expander")).Click();
s.Driver.FindElement(By.Id("delete-store")).Click();
s.Driver.FindElement(By.Id("continue")).Click();
s.FindAlertMessage();
s.AssertHappyMessage();
}
}
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()
{
@ -744,19 +741,20 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
var (storeName, storeId) = s.CreateNewStore();
var 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);
// 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);
//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.FindElement(By.Id("SendMenu")).Click();
//you cannot use the Sign with NBX option without saving private keys when generating the wallet.
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.
Assert.DoesNotContain("nbx-seed", s.Driver.PageSource);
s.Driver.FindElement(By.Id("WalletReceive")).Click();
@ -766,16 +764,17 @@ 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 nothing got used in the meantime
//generate it again, should be the same one as before as nothign 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();
await sess.ListenAllTrackedSourceAsync();
sess.ListenAllTrackedSource();
var nextEvent = sess.NextEventAsync();
await s.Server.ExplorerNode.SendToAddressAsync(BitcoinAddress.Create(receiveAddr, Network.RegTest), Money.Parse("0.1"));
s.Server.ExplorerNode.SendToAddress(BitcoinAddress.Create(receiveAddr, Network.RegTest),
Money.Parse("0.1"));
await nextEvent;
await Task.Delay(200);
s.Driver.Navigate().Refresh();
@ -784,35 +783,34 @@ 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);
s.GenerateWallet("BTC", "", true);
s.GoToStore(storeId.storeId);
s.GenerateWallet("BTC", "", true, false);
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(storeName);
var invoiceId = s.CreateInvoice(storeId.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);
s.GoToStore(storeId.storeId);
var mnemonic = s.GenerateWallet("BTC", "", true, true);
//lets import and save private keys
var root = mnemonic.DeriveExtKey();
invoiceId = s.CreateInvoice(storeName);
invoiceId = s.CreateInvoice(storeId.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));
await s.Server.ExplorerNode.GenerateAsync(1);
s.Server.ExplorerNode.Generate(1);
s.Driver.FindElement(By.Id("Wallets")).Click();
s.Driver.FindElement(By.LinkText("Manage")).Click();
@ -820,17 +818,16 @@ namespace BTCPayServer.Tests
s.ClickOnAllSideMenus();
// Make sure we can rescan, because we are admin!
s.Driver.FindElement(By.Id("WalletRescan")).Click();
s.Driver.FindElement(By.Id("WalletRescan")).ForceClick();
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")).Click();
s.Driver.FindElement(By.Id("WalletSettings")).ForceClick();
// 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")).Click();
s.Driver.FindElement(By.Id("WalletTransactions")).ForceClick();
var walletTransactionLink = s.Driver.Url;
Assert.Contains(tx.ToString(), s.Driver.PageSource);
@ -841,16 +838,17 @@ 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.FindElement(By.Id("SendMenu")).Click();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=seed]")).Click();
// Input the seed
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource + Keys.Enter);
s.Driver.FindElement(By.Id("SeedOrKey")).SendKeys(signingSource.ToString() + 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]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.Equal(walletTransactionLink, s.Driver.Url);
}
@ -862,17 +860,18 @@ namespace BTCPayServer.Tests
var jack = new Key().PubKey.Hash.GetAddress(Network.RegTest);
SetTransactionOutput(s, 0, jack, 0.01m);
s.Driver.FindElement(By.Id("SendMenu")).Click();
s.Driver.ScrollTo(By.Id("SendMenu"));
s.Driver.FindElement(By.Id("SendMenu")).ForceClick();
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]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=analyze-psbt]")).ForceClick();
Assert.EndsWith("psbt", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("#OtherActions")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
s.Driver.FindElement(By.CssSelector("#OtherActions")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.EndsWith("psbt/ready", s.Driver.Url);
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).Click();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
Assert.Equal(walletTransactionLink, s.Driver.Url);
var bip21 = invoice.EntityToDTO().CryptoInfo.First().PaymentUrls.BIP21;
@ -885,18 +884,18 @@ namespace BTCPayServer.Tests
s.Driver.FindElement(By.Id("bip21parse")).Click();
s.Driver.SwitchTo().Alert().SendKeys(bip21);
s.Driver.SwitchTo().Alert().Accept();
s.FindAlertMessage(StatusMessageModel.StatusSeverity.Info);
s.AssertHappyMessage(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, "BTC"), WalletsNavPages.Settings);
s.GoToWallet(new WalletId(storeId.storeId, "BTC"), WalletsNavPages.Settings);
var walletUrl = s.Driver.Url;
s.Driver.FindElement(By.Id("SettingsMenu")).Click();
s.Driver.FindElement(By.Id("SettingsMenu")).ForceClick();
s.Driver.FindElement(By.CssSelector("button[value=view-seed]")).Click();
// Seed backup page
var recoveryPhrase = s.Driver.FindElements(By.Id("RecoveryPhrase")).First().GetAttribute("data-mnemonic");
var recoveryPhrase = s.Driver.FindElements(By.Id("recovery-phrase")).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);
@ -906,6 +905,18 @@ 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")]
@ -915,47 +926,45 @@ namespace BTCPayServer.Tests
{
await s.StartAsync();
s.RegisterNewUser(true);
s.CreateNewStore();
s.GenerateWallet("BTC", "", true, true);
var receiver = s.CreateNewStore();
var receiverSeed = s.GenerateWallet("BTC", "", true, true, ScriptPubKeyType.Segwit);
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");;
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("Amount")).SendKeys("99.0" + Keys.Enter);
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");
s.Driver.FindElement(By.Id("Create")).Click();
s.Driver.FindElement(By.Id("Amount")).SendKeys("100.0" + Keys.Enter);
// 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.FindAlertMessage();
s.AssertHappyMessage();
// 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.FindAlertMessage(StatusMessageModel.StatusSeverity.Error);
s.AssertHappyMessage(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.FindAlertMessage();
s.AssertHappyMessage();
Assert.Contains("Awaiting Approval", s.Driver.PageSource);
var viewPullPaymentUrl = s.Driver.Url;
@ -973,11 +982,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.FindElement(By.Id("SendMenu")).Click();
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.CssSelector("button[value=broadcast]")).Click();
s.FindAlertMessage();
s.Driver.FindElement(By.CssSelector("button[value=broadcast]")).ForceClick();
s.AssertHappyMessage();
TestUtils.Eventually(() =>
{
@ -1018,57 +1027,5 @@ namespace BTCPayServer.Tests
});
}
}
private static void CanBrowseContent(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("delivery-content")).Click();
var windows = s.Driver.WindowHandles;
Assert.Equal(2, windows.Count);
s.Driver.SwitchTo().Window(windows[1]);
JObject.Parse(s.Driver.FindElement(By.TagName("body")).Text);
s.Driver.Close();
s.Driver.SwitchTo().Window(windows[0]);
}
private static void CanSetupEmailCore(SeleniumTester s)
{
s.Driver.FindElement(By.ClassName("dropdown-toggle")).Click();
s.Driver.FindElement(By.ClassName("dropdown-item")).Click();
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
s.FindAlertMessage();
s.Driver.FindElement(By.Id("Settings_Password")).SendKeys("mypassword");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
s.Driver.FindElement(By.Id("Settings_Login")).SendKeys("test_fix@gmail.com");
s.Driver.FindElement(By.CssSelector("button[value=\"Save\"]")).Submit();
Assert.Contains("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
s.Driver.FindElement(By.CssSelector("button[value=\"ResetPassword\"]")).Submit();
s.FindAlertMessage();
Assert.DoesNotContain("Configured", s.Driver.PageSource);
Assert.Contains("test_fix", s.Driver.PageSource);
}
private static string AssertUrlHasPairingCode(SeleniumTester s)
{
var regex = Regex.Match(new Uri(s.Driver.Url, UriKind.Absolute).Query, "pairingCode=([^&]*)");
Assert.True(regex.Success, $"{s.Driver.Url} does not match expected regex");
var pairingCode = regex.Groups[1].Value;
return pairingCode;
}
private void SetTransactionOutput(SeleniumTester s, int index, BitcoinAddress dest, decimal amount, bool subtract = false)
{
s.Driver.FindElement(By.Id($"Outputs_{index}__DestinationAddress")).SendKeys(dest.ToString());
var amountElement = s.Driver.FindElement(By.Id($"Outputs_{index}__Amount"));
amountElement.Clear();
amountElement.SendKeys(amount.ToString(CultureInfo.InvariantCulture));
var checkboxElement = s.Driver.FindElement(By.Id($"Outputs_{index}__SubtractFeesFromOutput"));
if (checkboxElement.Selected != subtract)
{
checkboxElement.Click();
}
}
}
}

View File

@ -7,8 +7,6 @@ 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;
@ -35,7 +33,7 @@ namespace BTCPayServer.Tests
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
NetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
NetworkProvider = new BTCPayNetworkProvider(NetworkType.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();
@ -88,50 +86,14 @@ 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", "http://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
MerchantLnd = new LndMockTester(this, "TEST_MERCHANTLND", "https://lnd:lnd@127.0.0.1:35531/", "merchant_lnd", btc);
PayTester.UseLightning = true;
PayTester.IntegratedLightning = GetLightningConnectionString(internalNode, true);
}
public string GetLightningConnectionString(LightningConnectionType? connectionType, bool isMerchant)
{
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;
PayTester.IntegratedLightning = MerchantCharge.Client.Uri;
}
public bool Dockerized

View File

@ -10,6 +10,7 @@ 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;
@ -211,9 +212,10 @@ 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)
.Replace("</a>", string.Empty)
.Replace("target='_blank'>", string.Empty);
var url = statusMessageModel.Html.Substring(index).ReplaceMultiple(new Dictionary<string, string>()
{
{"</a>", string.Empty}, {"target='_blank'>", string.Empty}
});
//verify tmpfile is available and the same
data = await net.DownloadStringTaskAsync(new Uri(url));
Assert.Equal(fileContent, data);

View File

@ -14,7 +14,6 @@ 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;
@ -23,8 +22,6 @@ 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;
@ -174,7 +171,7 @@ namespace BTCPayServer.Tests
}
public async Task<WalletId> RegisterDerivationSchemeAsync(string cryptoCode, ScriptPubKeyType segwit = ScriptPubKeyType.Legacy,
bool importKeysToNBX = false, bool importsKeysToBitcoinCore = false)
bool importKeysToNBX = false)
{
if (StoreId is null)
await CreateStoreAsync();
@ -184,13 +181,10 @@ namespace BTCPayServer.Tests
{
ScriptPubKeyType = segwit,
SavePrivateKeys = importKeysToNBX,
ImportKeysToRPC = importsKeysToBitcoinCore
});
await store.UpdateWallet(
new WalletSetupViewModel
await store.AddDerivationScheme(StoreId,
new DerivationSchemeViewModel()
{
StoreId = StoreId,
Method = importKeysToNBX ? WalletSetupMethod.HotWallet : WalletSetupMethod.WatchOnly,
Enabled = true,
CryptoCode = cryptoCode,
Network = SupportedNetwork,
@ -202,7 +196,7 @@ namespace BTCPayServer.Tests
KeyPath = GenerateWalletResponseV.AccountKeyPath.KeyPath.ToString(),
DerivationScheme = DerivationScheme.ToString(),
Confirmation = true
});
}, cryptoCode);
return new WalletId(StoreId, cryptoCode);
}
@ -262,19 +256,40 @@ namespace BTCPayServer.Tests
{
RegisterLightningNodeAsync(cryptoCode, connectionType, isMerchant).GetAwaiter().GetResult();
}
public Task RegisterLightningNodeAsync(string cryptoCode, bool isMerchant = true, string storeId = null)
{
return RegisterLightningNodeAsync(cryptoCode, null, isMerchant, storeId);
}
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType? connectionType, bool isMerchant = true, string storeId = null)
{
var storeController = GetController<StoresController>();
var connectionString = parent.GetLightningConnectionString(connectionType, isMerchant);
var nodeType = connectionString == LightningSupportedPaymentMethod.InternalNode ? LightningNodeType.Internal : LightningNodeType.Custom;
public async Task RegisterLightningNodeAsync(string cryptoCode, LightningConnectionType connectionType, bool isMerchant = true)
{
var storeController = this.GetController<StoresController>();
await storeController.AddLightningNode(storeId ?? StoreId,
new LightningNodeViewModel { ConnectionString = connectionString, LightningNodeType = nodeType, SkipPortTest = true }, "save", "BTC");
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());
await storeController.AddLightningNode(StoreId,
new LightningNodeViewModel() { ConnectionString = connectionString, SkipPortTest = true }, "save", "BTC");
if (storeController.ModelState.ErrorCount != 0)
Assert.False(true, storeController.ModelState.FirstOrDefault().Value.Errors[0].ErrorMessage);
}
@ -343,7 +358,7 @@ namespace BTCPayServer.Tests
Logs.Tester.LogInformation($"Proposing {psbt.GetGlobalTransaction().GetHash()}");
if (expectedError is null && !senderError)
{
var proposed = await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default);
var proposed = await pjClient.RequestPayjoin(endpoint, settings, psbt, default);
Logs.Tester.LogInformation($"Proposed payjoin is {proposed.GetGlobalTransaction().GetHash()}");
Assert.NotNull(proposed);
return proposed;
@ -352,11 +367,11 @@ namespace BTCPayServer.Tests
{
if (senderError)
{
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
await Assert.ThrowsAsync<PayjoinSenderException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
}
else
{
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, new PayjoinWallet(settings), psbt, default));
var ex = await Assert.ThrowsAsync<PayjoinReceiverException>(async () => await pjClient.RequestPayjoin(endpoint, settings, psbt, default));
var split = expectedError.Split('|');
Assert.Equal(split[0], ex.ErrorCode);
if (split.Length > 1)

View File

@ -32,7 +32,6 @@ 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;
@ -44,6 +43,7 @@ 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,12 +263,21 @@ namespace BTCPayServer.Tests
ManageController.AddApiKeyViewModel.PermissionValueItem.PermissionDescriptions.Where(pair =>
!Policies.IsStorePolicy(pair.Key) && !Policies.IsServerPolicy(pair.Key));
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}")));
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}"))
}
});
Logs.Tester.LogInformation(description);
var sresp = Assert
@ -408,11 +417,11 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanCalculateCryptoDue()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
});
var entity = new InvoiceEntity();
entity.Networks = networkProvider;
@ -696,11 +705,11 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void CanAcceptInvoiceWithTolerance()
{
var networkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var networkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
var paymentMethodHandlerDictionary = new PaymentMethodHandlerDictionary(new IPaymentMethodHandler[]
{
new BitcoinLikePaymentHandler(null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null, null),
new LightningLikePaymentHandler(null, null, networkProvider, null, null),
});
var entity = new InvoiceEntity();
entity.Networks = networkProvider;
@ -874,7 +883,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public async Task CanEnumerateTorServices()
{
var tor = new TorServices(new BTCPayNetworkProvider(ChainName.Regtest),
var tor = new TorServices(new BTCPayNetworkProvider(NetworkType.Regtest),
new BTCPayServerOptions() { TorrcFile = TestUtils.GetTestDataFullPath("Tor/torrc") });
await tor.Refresh();
@ -895,7 +904,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
await user.GrantAccessAsync(true);
await user.GrantAccessAsync();
await user.RegisterDerivationSchemeAsync("BTC");
await user.RegisterLightningNodeAsync("BTC", LightningConnectionType.CLightning);
user.SetNetworkFeeMode(NetworkFeeMode.Never);
@ -945,7 +954,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
var storeController = user.GetController<StoresController>();
Assert.IsType<ViewResult>(storeController.UpdateStore());
Assert.IsType<ViewResult>(storeController.AddLightningNode(user.StoreId, "BTC"));
@ -1012,7 +1021,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
user.RegisterLightningNode("BTC", type);
user.RegisterDerivationScheme("BTC");
@ -1171,7 +1180,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
public void CanSolveTheDogesRatesOnKraken()
{
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var factory = CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
@ -1202,7 +1211,7 @@ namespace BTCPayServer.Tests
});
}
var httpFactory = tester.PayTester.GetService<IHttpClientFactory>();
var client = httpFactory.CreateClient(PayjoinServerCommunicator.PayjoinOnionNamedClient);
var client = httpFactory.CreateClient(PayjoinClient.PayjoinOnionNamedClient);
Assert.NotNull(client);
var response = await client.GetAsync("https://check.torproject.org/");
response.EnsureSuccessStatusCode();
@ -1221,7 +1230,7 @@ namespace BTCPayServer.Tests
AssertConnectionDropped();
client.Dispose();
AssertConnectionDropped();
client = httpFactory.CreateClient(PayjoinServerCommunicator.PayjoinOnionNamedClient);
client = httpFactory.CreateClient(PayjoinClient.PayjoinOnionNamedClient);
response = await client.GetAsync("http://explorerzydxu5ecjrkwceayqybizmpjjznk5izmitf2modhcusuqlid.onion/");
response.EnsureSuccessStatusCode();
AssertConnectionDropped();
@ -2049,7 +2058,7 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void HasCurrencyDataForNetworks()
{
var btcPayNetworkProvider = new BTCPayNetworkProvider(ChainName.Regtest);
var btcPayNetworkProvider = new BTCPayNetworkProvider(NetworkType.Regtest);
foreach (var network in btcPayNetworkProvider.GetAll())
{
var cd = CurrencyNameTable.Instance.GetCurrencyData(network.CryptoCode, false);
@ -2126,7 +2135,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
user.RegisterDerivationScheme("BTC", ScriptPubKeyType.Segwit);
user.RegisterLightningNode("BTC", LightningConnectionType.CLightning);
@ -2166,9 +2175,10 @@ namespace BTCPayServer.Tests
Assert.StartsWith("bitcoin:", paymentMethodSecond.InvoiceBitcoinUrlQR);
var split = paymentMethodSecond.InvoiceBitcoinUrlQR.Split('?')[0];
// 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);
// 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);
}
}
@ -2183,7 +2193,7 @@ namespace BTCPayServer.Tests
await tester.StartAsync();
await tester.EnsureChannelsSetup();
var user = tester.NewAccount();
user.GrantAccess(true);
user.GrantAccess();
user.RegisterLightningNode("BTC", LightningConnectionType.Charge);
var vm = Assert.IsType<CheckoutExperienceViewModel>(Assert
.IsType<ViewResult>(user.GetController<StoresController>().CheckoutExperience()).Model);
@ -2986,7 +2996,7 @@ namespace BTCPayServer.Tests
[Trait("Integration", "Integration")]
public void CanGetRateCryptoCurrenciesByDefault()
{
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var factory = CreateBTCPayRateFactory();
var fetcher = new RateFetcher(factory);
var pairs =
@ -3058,35 +3068,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,
ChainName.Mainnet);
NetworkType.Mainnet);
Assert.Equal(new Uri("https://toto.com/test"), expanded.Server);
expanded = await connStr.Expand(new Uri("http://toto.onion"), ExternalServiceTypes.Charge,
ChainName.Mainnet);
NetworkType.Mainnet);
Assert.Equal(new Uri("http://toto.onion/test"), expanded.Server);
await Assert.ThrowsAsync<SecurityException>(() =>
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Mainnet));
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, ChainName.Testnet);
connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.Mainnet));
await connStr.Expand(new Uri("http://toto.com"), ExternalServiceTypes.Charge, NetworkType.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,
ChainName.Mainnet);
NetworkType.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, ChainName.Mainnet));
connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet));
await Assert.ThrowsAsync<DirectoryNotFoundException>(() =>
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet));
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, ChainName.Mainnet);
connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet));
await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
var macaroonDirectory = CreateDirectory();
Assert.True(ExternalConnectionString.TryParse(
$"server={unusedUri};macaroondirectorypath={macaroonDirectory}", out connStr, out error));
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, ChainName.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, ChainName.Mainnet);
await connStr.Expand(unusedUri, ExternalServiceTypes.LNDGRPC, NetworkType.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
Assert.NotNull(expanded.Macaroons);
Assert.Null(expanded.MacaroonFilePath);
Assert.Null(expanded.Macaroons.AdminMacaroon);
@ -3096,7 +3106,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, ChainName.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.LNDRest, NetworkType.Mainnet);
Assert.NotNull(expanded.Macaroons.AdminMacaroon);
Assert.NotNull(expanded.Macaroons.InvoiceMacaroon);
Assert.Equal("ab", expanded.Macaroons.InvoiceMacaroon.Hex);
@ -3106,7 +3116,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, ChainName.Mainnet);
expanded = await connStr.Expand(unusedUri, ExternalServiceTypes.Charge, NetworkType.Mainnet);
Assert.Equal("apitoken", expanded.APIToken);
}
@ -3117,24 +3127,6 @@ 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()
@ -3209,12 +3201,10 @@ namespace BTCPayServer.Tests
[Trait("Fast", "Fast")]
public void ParseDerivationSchemeSettings()
{
var mainnet = new BTCPayNetworkProvider(ChainName.Mainnet).GetNetwork<BTCPayNetwork>("BTC");
var mainnet = new BTCPayNetworkProvider(NetworkType.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));
@ -3228,7 +3218,8 @@ 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(ChainName.Testnet).GetNetwork<BTCPayNetwork>("BTC");
var testnet = new BTCPayNetworkProvider(NetworkType.Testnet).GetNetwork<BTCPayNetwork>("BTC");
// Should be legacy
Assert.True(DerivationSchemeSettings.TryParseFromWalletFile(
@ -3248,15 +3239,6 @@ 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);
}
@ -3427,86 +3409,7 @@ 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()
@ -3520,7 +3423,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);
@ -3541,10 +3444,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()
{
@ -3557,13 +3460,18 @@ namespace BTCPayServer.Tests
Currency = "USD",
Value = 5m
}.ToString())));
store.SetStoreBlob(blob);
await tester.PayTester.StoreRepository.UpdateStore(store);
await RestartMigration(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();
store = await tester.PayTester.StoreRepository.FindStore(acc.StoreId);
blob = store.GetStoreBlob();
Assert.Empty(blob.AdditionalData);
Assert.Single(blob.PaymentMethodCriteria);
@ -3578,16 +3486,7 @@ 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()

View File

@ -1,6 +1,5 @@
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;

View File

@ -24,7 +24,7 @@ services:
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@ -69,22 +69,20 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:0.21.0
image: btcpayserver/bitcoin:0.20.1
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.49
image: nicolasdorier/nbxplorer:2.1.45
restart: unless-stopped
ports:
- "32838:32838"
@ -118,16 +116,14 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:0.21.0
image: btcpayserver/bitcoin:0.20.1
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
@ -146,7 +142,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.9.3-1-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -173,14 +169,12 @@ services:
- bitcoind
lightning-charged:
image: shesek/lightning-charge:0.4.23-1-standalone
image: shesek/lightning-charge:0.4.19-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"
@ -195,7 +189,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.9.3-1-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@ -227,21 +221,18 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.12.1-beta
image: btcpayserver/lnd:v0.11.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
LND_EXTRA_ARGS: |
restlisten=merchant_lnd:8080
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=merchant_lnd:10009
rpclisten=0.0.0.0: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
@ -249,7 +240,6 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35531:8080"
expose:
@ -261,21 +251,18 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.12.1-beta
image: btcpayserver/lnd:v0.11.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://customer_lnd:8080
LND_EXTRA_ARGS: |
restlisten=customer_lnd:8080
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=customer_lnd:10009
rpclisten=0.0.0.0: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
@ -283,7 +270,6 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35532:8080"
expose:

View File

@ -22,7 +22,7 @@ services:
TEST_MERCHANTLIGHTNINGD: "type=clightning;server=unix://etc/merchant_lightningd_datadir/lightning-rpc"
TEST_CUSTOMERLIGHTNINGD: "type=clightning;server=unix://etc/customer_lightningd_datadir/lightning-rpc"
TEST_MERCHANTCHARGE: "type=charge;server=http://lightning-charged:9112/;api-token=foiewnccewuify;allowinsecure=true"
TEST_MERCHANTLND: "http://lnd:lnd@merchant_lnd:8080/"
TEST_MERCHANTLND: "https://lnd:lnd@merchant_lnd:8080/"
TESTS_INCONTAINER: "true"
TESTS_SSHCONNECTION: "root@sshd:22"
TESTS_SSHPASSWORD: ""
@ -66,14 +66,12 @@ services:
- "sshd_datadir:/root/.ssh"
devlnd:
image: btcpayserver/bitcoin:0.21.0
image: btcpayserver/bitcoin:0.20.1
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
@ -81,7 +79,7 @@ services:
- customer_lnd
- merchant_lnd
nbxplorer:
image: nicolasdorier/nbxplorer:2.1.49
image: nicolasdorier/nbxplorer:2.1.45
restart: unless-stopped
ports:
- "32838:32838"
@ -105,16 +103,14 @@ services:
bitcoind:
restart: unless-stopped
image: btcpayserver/bitcoin:0.21.0
image: btcpayserver/bitcoin:0.20.1
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
@ -133,7 +129,7 @@ services:
- "bitcoin_datadir:/data"
customer_lightningd:
image: btcpayserver/lightning:v0.9.3-1-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
restart: unless-stopped
environment:
@ -160,14 +156,12 @@ services:
- bitcoind
lightning-charged:
image: shesek/lightning-charge:0.4.23-1-standalone
image: shesek/lightning-charge:0.4.19-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"
@ -182,7 +176,7 @@ services:
- merchant_lightningd
merchant_lightningd:
image: btcpayserver/lightning:v0.9.3-1-dev
image: btcpayserver/lightning:v0.9.0-1-dev
stop_signal: SIGKILL
environment:
EXPOSE_TCP: "true"
@ -215,21 +209,18 @@ services:
- "5432"
merchant_lnd:
image: btcpayserver/lnd:v0.12.1-beta
image: btcpayserver/lnd:v0.11.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://merchant_lnd:8080
LND_EXTRA_ARGS: |
restlisten=merchant_lnd:8080
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=merchant_lnd:10009
rpclisten=0.0.0.0: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
@ -237,7 +228,6 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35531:8080"
expose:
@ -249,21 +239,18 @@ services:
- bitcoind
customer_lnd:
image: btcpayserver/lnd:v0.12.1-beta
image: btcpayserver/lnd:v0.11.0-beta
restart: unless-stopped
environment:
LND_CHAIN: "btc"
LND_ENVIRONMENT: "regtest"
LND_EXPLORERURL: "http://nbxplorer:32838/"
LND_REST_LISTEN_HOST: http://customer_lnd:8080
LND_EXTRA_ARGS: |
restlisten=customer_lnd:8080
restlisten=0.0.0.0:8080
rpclisten=127.0.0.1:10008
rpclisten=customer_lnd:10009
rpclisten=0.0.0.0: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
@ -271,7 +258,6 @@ services:
no-macaroons=1
debuglevel=debug
trickledelay=1000
no-rest-tls=1
ports:
- "35532:8080"
expose:

View File

@ -1,4 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<Import Project="../Build/Version.csproj" Condition="Exists('../Build/Version.csproj')" />
<Import Project="../Build/Common.csproj" />
@ -44,14 +44,13 @@
</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.7" />
<PackageReference Include="BTCPayServer.Lightning.All" Version="1.2.4" />
<PackageReference Include="BuildBundlerMinifier" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.Core" Version="3.2.435" />
<PackageReference Include="BundlerMinifier.TagHelpers" Version="3.2.435" />
<PackageReference Include="CsvHelper" Version="15.0.5" />
<PackageReference Include="HtmlSanitizer" Version="5.0.372" />
<PackageReference Include="HtmlSanitizer" Version="4.0.217" />
<PackageReference Include="McMaster.NETCore.Plugins.Mvc" Version="1.3.1" />
<PackageReference Include="Microsoft.Extensions.Logging.Filter" Version="1.1.2" />
<PackageReference Include="Microsoft.NetCore.Analyzers" Version="2.9.8">
@ -61,6 +60,7 @@
<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,7 +134,6 @@
</ItemGroup>
<ItemGroup>
<Folder Include="Views\Stores\Integrations" />
<Folder Include="wwwroot\vendor\clipboard.js\" />
<Folder Include="wwwroot\vendor\highlightjs\" />
<Folder Include="wwwroot\vendor\summernote" />

View File

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

View File

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

View File

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

View File

@ -2,53 +2,40 @@
@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
@if (Model.UnseenCount > 0)
{
<li class="nav-item dropdown" id="notifications-nav-item">
<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 class="nav-link js-scroll-trigger" href="#" id="navbarDropdown" role="button" data-toggle="dropdown" id="Notifications">
<i class="fa fa-bell"></i>
</a>
<span class="alerts-badge badge badge-pill badge-danger">@Model.UnseenCount</span>
<div class="dropdown-menu dropdown-menu-right text-center notification-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" type="submit">Mark all as seen</button>
</form>
</div>
<div class="dropdown-menu dropdown-menu-right text-center notification-items" aria-labelledby="navbarDropdown">
@foreach (var notif in Model.Last5)
{
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="notification d-flex align-items-center dropdown-item border-bottom border-light py-3 px-4">
<div class="mr-3">
<vc:icon symbol="note" />
<a asp-action="NotificationPassThrough" asp-controller="Notifications" asp-route-id="@notif.Id" class="dropdown-item border-bottom py-2 px-3">
<div class="text-left" style="width: 200px; white-space:normal;">
@notif.Body
</div>
<div class="notification-item__content">
<div class="text-left text-wrap">
@notif.Body
</div>
<div class="text-left d-flex">
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
</div>
<div class="text-left">
<small class="text-muted" data-timeago-unixms="@notif.Created.ToUnixTimeMilliseconds()">@notif.Created.ToTimeAgo()</small>
</div>
</a>
}
<div class="p-3">
<a asp-controller="Notifications" asp-action="Index">View all</a>
</div>
<a class="dropdown-item text-secondary" asp-controller="Notifications" asp-action="Index">See All</a>
<form asp-controller="Notifications" asp-action="MarkAllAsSeen" asp-route-returnUrl="@Context.Request.GetCurrentPathWithQueryString()" method="post">
<button class="dropdown-item text-secondary" type="submit"><i class="fa fa-eye"></i> Mark all as seen</button>
</form>
</div>
</li>
}
else
{
<li class="nav-item" id="notifications-nav-item">
<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 asp-controller="Notifications" asp-action="Index" title="Notifications" class="nav-link js-scroll-trigger" id="Notifications">
<i class="fa fa-bell"></i>
</a>
</li>
}

View File

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

View File

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

View File

@ -12,7 +12,7 @@ namespace BTCPayServer.Configuration
{
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
var provider = new BTCPayNetworkProvider(ChainName.Mainnet);
var provider = new BTCPayNetworkProvider(NetworkType.Mainnet);
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
CommandLineApplication app = new CommandLineApplication(true)
{
@ -23,7 +23,6 @@ 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);
@ -90,7 +89,7 @@ namespace BTCPayServer.Configuration
return Path.Combine(chainDir, fileName);
}
public static ChainName GetNetworkType(IConfiguration conf)
public static NetworkType GetNetworkType(IConfiguration conf)
{
var network = conf.GetOrDefault<string>("network", null);
if (network != null)
@ -100,12 +99,10 @@ namespace BTCPayServer.Configuration
{
throw new ConfigException($"Invalid network parameter '{network}'");
}
return n.ChainName;
return n.NetworkType;
}
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;
var net = conf.GetOrDefault<bool>("regtest", false) ? NetworkType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? NetworkType.Testnet : NetworkType.Mainnet;
return net;
}

View File

@ -30,12 +30,12 @@ namespace BTCPayServer.Configuration
/// Return a connectionString which does not depends on external resources or information like relative path or file path
/// </summary>
/// <returns></returns>
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, ChainName network)
public async Task<ExternalConnectionString> Expand(Uri absoluteUrlBase, ExternalServiceTypes serviceType, NetworkType 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 != ChainName.Mainnet ||
var isSecure = network != NetworkType.Mainnet ||
serviceUri.Scheme == "https" ||
serviceUri.DnsSafeHost.EndsWith(".onion", StringComparison.OrdinalIgnoreCase) ||
Extensions.IsLocalNetwork(serviceUri.DnsSafeHost);

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
using System;
using System.Globalization;
using System.Security.Policy;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Abstractions.Extensions;
@ -35,7 +34,6 @@ 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;
@ -47,7 +45,6 @@ namespace BTCPayServer.Controllers
Configuration.BTCPayServerOptions options,
BTCPayServerEnvironment btcPayServerEnvironment,
U2FService u2FService,
RateLimitService rateLimitService,
EventAggregator eventAggregator)
{
_userManager = userManager;
@ -57,7 +54,6 @@ namespace BTCPayServer.Controllers
_Options = options;
_btcPayServerEnvironment = btcPayServerEnvironment;
_u2FService = u2FService;
_rateLimitService = rateLimitService;
_eventAggregator = eventAggregator;
_logger = Logs.PayServer;
}
@ -70,8 +66,6 @@ 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)
{
@ -95,8 +89,6 @@ 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)
@ -404,8 +396,6 @@ 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)
{
@ -423,8 +413,6 @@ 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)
{
@ -547,7 +535,6 @@ namespace BTCPayServer.Controllers
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
[RateLimitsFilter(ZoneLimits.ForgotPassword, Scope = RateLimitsScope.RemoteAddress)]
public async Task<IActionResult> ForgotPassword(ForgotPasswordViewModel model)
{
if (ModelState.IsValid)

View File

@ -71,7 +71,7 @@ namespace BTCPayServer.Controllers
try
{
vm.PerksTemplate = _AppService.SerializeTemplate(_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency));
_AppService.Parse(vm.PerksTemplate, vm.TargetCurrency).ToString();
}
catch
{

View File

@ -161,16 +161,11 @@ 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
{
vm.Template = _AppService.SerializeTemplate(_AppService.Parse(vm.Template, vm.Currency));
_AppService.Parse(vm.Template, vm.Currency);
}
catch
{

View File

@ -57,10 +57,8 @@ 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);
@ -106,12 +104,10 @@ 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,
@ -236,10 +232,8 @@ 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);
@ -269,12 +263,10 @@ 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)

View File

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

View File

@ -5,12 +5,14 @@ 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;
@ -25,22 +27,14 @@ 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, BTCPayNetworkProvider btcPayNetworkProvider,
EventAggregator eventAggregator, PaymentMethodHandlerDictionary paymentMethodHandlerDictionary)
public GreenFieldInvoiceController(InvoiceController invoiceController, InvoiceRepository invoiceRepository, LinkGenerator linkGenerator, LanguageService languageService)
{
_invoiceController = invoiceController;
_invoiceRepository = invoiceRepository;
_linkGenerator = linkGenerator;
_btcPayNetworkProvider = btcPayNetworkProvider;
_eventAggregator = eventAggregator;
_paymentMethodHandlerDictionary = paymentMethodHandlerDictionary;
LanguageService = languageService;
}
@ -78,7 +72,7 @@ namespace BTCPayServer.Controllers.GreenField
}
var invoice = await _invoiceRepository.GetInvoice(invoiceId, true);
if (invoice?.StoreId != store.Id)
if (invoice.StoreId != store.Id)
{
return NotFound();
}
@ -218,7 +212,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 settled within certain conditions.");
"Status can only be marked to invalid or complete within certain conditions.");
}
if (!ModelState.IsValid)
@ -276,32 +270,6 @@ 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)
{
@ -315,7 +283,6 @@ namespace BTCPayServer.Controllers.GreenField
return new InvoicePaymentMethodDataModel()
{
Activated = details.Activated,
PaymentMethod = method.GetId().ToStringNormalized(),
Destination = details.GetPaymentDestination(),
Rate = method.Rate,
@ -370,9 +337,7 @@ namespace BTCPayServer.Controllers.GreenField
PaymentMethods =
entity.GetPaymentMethods().Select(method => method.GetId().ToStringNormalized()).ToArray(),
SpeedPolicy = entity.SpeedPolicy,
DefaultLanguage = entity.DefaultLanguage,
RedirectAutomatically = entity.RedirectAutomatically,
RedirectURL = entity.RedirectURLTemplate
DefaultLanguage = entity.DefaultLanguage
}
};
}

View File

@ -28,9 +28,8 @@ namespace BTCPayServer.Controllers.GreenField
public InternalLightningNodeApiController(
BTCPayNetworkProvider btcPayNetworkProvider, BTCPayServerEnvironment btcPayServerEnvironment,
CssThemeManager cssThemeManager, LightningClientFactoryService lightningClientFactory,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
IAuthorizationService authorizationService) : base(
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService)
IOptions<LightningNetworkOptions> lightningNetworkOptions ) : base(
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
_lightningClientFactory = lightningClientFactory;
@ -101,17 +100,17 @@ namespace BTCPayServer.Controllers.GreenField
return base.CreateInvoice(cryptoCode, request);
}
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
protected override Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings)
{
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(cryptoCode,
out var internalLightningNode);
var network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
if (network == null ||
!_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
out var internalLightningNode) ||
!await CanUseInternalLightning(doingAdminThings))
if (network == null || !CanUseInternalLightning(doingAdminThings) || internalLightningNode == null)
{
return null;
}
return _lightningClientFactory.Create(internalLightningNode, network);
return Task.FromResult(_lightningClientFactory.Create(internalLightningNode, network));
}
}
}

View File

@ -31,9 +31,8 @@ namespace BTCPayServer.Controllers.GreenField
public StoreLightningNodeApiController(
IOptions<LightningNetworkOptions> lightningNetworkOptions,
LightningClientFactoryService lightningClientFactory, BTCPayNetworkProvider btcPayNetworkProvider,
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager,
IAuthorizationService authorizationService) : base(
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager, authorizationService)
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager) : base(
btcPayNetworkProvider, btcPayServerEnvironment, cssThemeManager)
{
_lightningNetworkOptions = lightningNetworkOptions;
_lightningClientFactory = lightningClientFactory;
@ -101,10 +100,11 @@ namespace BTCPayServer.Controllers.GreenField
return base.CreateInvoice(cryptoCode, request);
}
protected override async Task<ILightningClient> GetLightningClient(string cryptoCode,
protected override 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,20 +117,13 @@ namespace BTCPayServer.Controllers.GreenField
var existing = store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(d => d.PaymentId == id);
if (existing == null)
if (existing == null || (existing.GetLightningUrl().IsInternalNode(internalLightningNode) &&
!CanUseInternalLightning(doingAdminThings)))
{
return null;
if (existing.GetExternalLightningUrl() is LightningConnectionString connectionString)
{
return _lightningClientFactory.Create(connectionString, network);
}
else if (
await CanUseInternalLightning(doingAdminThings) &&
_lightningNetworkOptions.Value.InternalLightningByCryptoCode.TryGetValue(network.CryptoCode,
out var internalLightningNode))
{
return _lightningClientFactory.Create(internalLightningNode, network);
}
return null;
return Task.FromResult(_lightningClientFactory.Create(existing.GetLightningUrl(), network));
}
}
}

View File

@ -2,13 +2,10 @@ 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;
@ -35,16 +32,13 @@ 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,
IAuthorizationService authorizationService)
BTCPayServerEnvironment btcPayServerEnvironment, CssThemeManager cssThemeManager)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
_btcPayServerEnvironment = btcPayServerEnvironment;
_cssThemeManager = cssThemeManager;
_authorizationService = authorizationService;
}
public virtual async Task<IActionResult> GetInfo(string cryptoCode)
@ -300,11 +294,10 @@ namespace BTCPayServer.Controllers.GreenField
};
}
protected async Task<bool> CanUseInternalLightning(bool doingAdminThings)
protected bool CanUseInternalLightning(bool doingAdminThings)
{
return (!doingAdminThings && _cssThemeManager.AllowLightningInternalNodeForAll) ||
(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
return (_btcPayServerEnvironment.IsDeveloping || User.IsInRole(Roles.ServerAdmin) ||
(_cssThemeManager.AllowLightningInternalNodeForAll && !doingAdminThings));
}
protected abstract Task<ILightningClient> GetLightningClient(string cryptoCode, bool doingAdminThings);

View File

@ -1,211 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Client;
using BTCPayServer.Client.Models;
using BTCPayServer.Configuration;
using BTCPayServer.Data;
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;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using StoreData = BTCPayServer.Data.StoreData;
namespace BTCPayServer.Controllers.GreenField
{
[ApiController]
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public class StoreLightningNetworkPaymentMethodsController : ControllerBase
{
private StoreData Store => HttpContext.GetStoreData();
private readonly StoreRepository _storeRepository;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly IOptions<LightningNetworkOptions> _lightningNetworkOptions;
private readonly IAuthorizationService _authorizationService;
private readonly CssThemeManager _cssThemeManager;
public StoreLightningNetworkPaymentMethodsController(
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
IOptions<LightningNetworkOptions> lightningNetworkOptions,
IAuthorizationService authorizationService,
CssThemeManager cssThemeManager)
{
_storeRepository = storeRepository;
_btcPayNetworkProvider = btcPayNetworkProvider;
_lightningNetworkOptions = lightningNetworkOptions;
_authorizationService = authorizationService;
_cssThemeManager = cssThemeManager;
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork")]
public ActionResult<IEnumerable<LightningNetworkPaymentMethodData>> GetLightningPaymentMethods(
[FromQuery] bool enabledOnly = false)
{
var blob = Store.GetStoreBlob();
var excludedPaymentMethods = blob.GetExcludedPaymentMethods();
return Ok(Store.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.Where((method) => method.PaymentId.PaymentType == PaymentTypes.LightningLike)
.OfType<LightningSupportedPaymentMethod>()
.Select(paymentMethod =>
new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
paymentMethod.GetExternalLightningUrl().ToString(), !excludedPaymentMethods.Match(paymentMethod.PaymentId)))
.Where((result) => !enabledOnly || result.Enabled)
.ToList()
);
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpGet("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
public ActionResult<LightningNetworkPaymentMethodData> GetLightningNetworkPaymentMethod(string cryptoCode)
{
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
{
return NotFound();
}
var method = GetExistingLightningLikePaymentMethod(cryptoCode);
if (method is null)
{
return NotFound();
}
return Ok(method);
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpDelete("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
public async Task<IActionResult> RemoveLightningNetworkPaymentMethod(
string cryptoCode,
int offset = 0, int amount = 10)
{
if (!GetNetwork(cryptoCode, out BTCPayNetwork _))
{
return NotFound();
}
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var store = Store;
store.SetSupportedPaymentMethod(id, null);
await _storeRepository.UpdateStore(store);
return Ok();
}
[Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
[HttpPut("~/api/v1/stores/{storeId}/payment-methods/LightningNetwork/{cryptoCode}")]
public async Task<IActionResult> UpdateLightningNetworkPaymentMethod(string cryptoCode,
[FromBody] LightningNetworkPaymentMethodData paymentMethodData)
{
var paymentMethodId = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
if (!GetNetwork(cryptoCode, out var network))
{
return NotFound();
}
if (string.IsNullOrEmpty(paymentMethodData?.ConnectionString))
{
ModelState.AddModelError(nameof(LightningNetworkPaymentMethodData.ConnectionString),
"Missing connectionString");
}
if (!ModelState.IsValid)
return this.CreateValidationError(ModelState);
LightningSupportedPaymentMethod paymentMethod = null;
if (!string.IsNullOrEmpty(paymentMethodData.ConnectionString))
{
if (paymentMethodData.ConnectionString == LightningSupportedPaymentMethod.InternalNode)
{
if (!await CanUseInternalLightning())
{
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();
}
else
{
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),
$"BTCPay does not support gRPC connections");
return this.CreateValidationError(ModelState);
}
if (!await CanManageServer() && !connectionString.IsSafe())
{
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);
}
paymentMethod = new Payments.Lightning.LightningSupportedPaymentMethod()
{
CryptoCode = paymentMethodId.CryptoCode
};
paymentMethod.SetLightningUrl(connectionString);
}
}
var store = Store;
var storeBlob = store.GetStoreBlob();
store.SetSupportedPaymentMethod(paymentMethodId, paymentMethod);
storeBlob.SetExcluded(paymentMethodId, !paymentMethodData.Enabled);
store.SetStoreBlob(storeBlob);
await _storeRepository.UpdateStore(store);
return Ok(GetExistingLightningLikePaymentMethod(cryptoCode, store));
}
private LightningNetworkPaymentMethodData GetExistingLightningLikePaymentMethod(string cryptoCode, StoreData store = null)
{
store ??= Store;
var storeBlob = store.GetStoreBlob();
var id = new PaymentMethodId(cryptoCode, PaymentTypes.LightningLike);
var paymentMethod = store
.GetSupportedPaymentMethods(_btcPayNetworkProvider)
.OfType<LightningSupportedPaymentMethod>()
.FirstOrDefault(method => method.PaymentId == id);
var excluded = storeBlob.IsExcluded(id);
return paymentMethod == null
? null
: new LightningNetworkPaymentMethodData(paymentMethod.PaymentId.CryptoCode,
paymentMethod.GetDisplayableConnectionString(), !excluded);
}
private bool GetNetwork(string cryptoCode, out BTCPayNetwork network)
{
network = _btcPayNetworkProvider.GetNetwork<BTCPayNetwork>(cryptoCode);
network = network?.SupportLightning is true ? network : null;
return network != null;
}
private async Task<bool> CanUseInternalLightning()
{
return _cssThemeManager.AllowLightningInternalNodeForAll ||
(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanUseInternalLightningNode))).Succeeded;
}
private async Task<bool> CanManageServer()
{
return
(await _authorizationService.AuthorizeAsync(User, null,
new PolicyRequirement(Policies.CanModifyServerSettings))).Succeeded;
}
}
}

View File

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

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