Compare commits

...

20 Commits

Author SHA1 Message Date
6b4eeff3f1 add tests, do not returns bitcoinAddress field if not supported by the invoice 2018-01-13 22:01:09 +09:00
e3cc589ebb fix nbxplorer 2018-01-13 21:07:04 +09:00
4a152e8ffc fix build 2018-01-13 17:32:08 +09:00
d54a9474d1 Fixing exception thrown when invoice is paid and supporting only LTC 2018-01-13 17:23:09 +09:00
98472211fc bump 2018-01-13 12:55:05 +09:00
099c9fa1f9 Fix balance calculation when there is unconfirmed tx 2018-01-13 12:53:56 +09:00
5226b77ffc Fix bug happening when removing data of nbxplorer 2018-01-13 02:28:23 +09:00
290779ee39 bump 2018-01-13 01:48:43 +09:00
4f39a8060c Fixing bug of uncorrectly unassigned addresses 2018-01-13 01:48:19 +09:00
92caa98dfb bump 2018-01-13 01:06:18 +09:00
df7bb9e2f8 Add info about nbxplorer synching 2018-01-13 01:05:38 +09:00
02a039d695 Fixing bug when targetting testnet 2018-01-12 22:36:13 +09:00
b5e4c803aa fix bad network throwing nullreferenceexception 2018-01-12 22:08:20 +09:00
2b7c70622f bump 2018-01-12 21:55:13 +09:00
88779e7129 Make sure websockets does not throw, fix annying warning of emails 2018-01-12 18:32:46 +09:00
6beb7abfd2 fix test 2018-01-12 17:04:47 +09:00
a1ebedc0d1 Fix unit test 2018-01-12 16:54:57 +09:00
d5ad0cdb39 Fix a edge case "The instance of entity type 'HistoricalAddressInvoiceData'" 2018-01-12 16:42:10 +09:00
39fb8dbb6a better handle case when BTC is not supported by a store 2018-01-12 16:30:34 +09:00
58194cb060 Fix tests, clean code of Options 2018-01-12 16:00:31 +09:00
18 changed files with 317 additions and 97 deletions

@ -67,9 +67,14 @@ namespace BTCPayServer.Tests
{
if (!Directory.Exists(_Directory))
Directory.CreateDirectory(_Directory);
string chain = ChainType.Regtest.ToNetwork().Name;
string chainDirectory = Path.Combine(_Directory, chain);
if (!Directory.Exists(chainDirectory))
Directory.CreateDirectory(chainDirectory);
StringBuilder config = new StringBuilder();
config.AppendLine($"regtest=1");
config.AppendLine($"{chain.ToLowerInvariant()}=1");
config.AppendLine($"port={Port}");
config.AppendLine($"chains=btc,ltc");
@ -81,11 +86,12 @@ namespace BTCPayServer.Tests
if (Postgres != null)
config.AppendLine($"postgres=" + Postgres);
File.WriteAllText(Path.Combine(_Directory, "settings.config"), config.ToString());
var confPath = Path.Combine(chainDirectory, "settings.config");
File.WriteAllText(confPath, config.ToString());
ServerUri = new Uri("http://" + HostName + ":" + Port + "/");
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory });
var conf = new DefaultConfiguration() { Logger = Logs.LogProvider.CreateLogger("Console") }.CreateConfiguration(new[] { "--datadir", _Directory, "--conf", confPath });
_Host = new WebHostBuilder()
.UseConfiguration(conf)

@ -46,12 +46,15 @@ namespace BTCPayServer.Tests
Assert.IsType<ViewResult>(await store.RequestPairing(pairingCode.ToString()));
await store.Pair(pairingCode.ToString(), StoreId);
}
public StoresController CreateStore(string cryptoCode = "BTC")
public StoresController CreateStore(string cryptoCode = null)
{
return CreateStoreAsync(cryptoCode).GetAwaiter().GetResult();
}
public async Task<StoresController> CreateStoreAsync(string cryptoCode = "BTC")
public string CryptoCode { get; set; } = "BTC";
public async Task<StoresController> CreateStoreAsync(string cryptoCode = null)
{
cryptoCode = cryptoCode ?? CryptoCode;
SupportedNetwork = parent.NetworkProvider.GetNetwork(cryptoCode);
ExtKey = new ExtKey().GetWif(SupportedNetwork.NBitcoinNetwork);
var store = parent.PayTester.GetController<StoresController>(UserId);
@ -65,7 +68,7 @@ namespace BTCPayServer.Tests
await store.AddDerivationScheme(StoreId, new DerivationSchemeViewModel()
{
CryptoCurrency = "BTC",
CryptoCurrency = cryptoCode,
DerivationSchemeFormat = "BTCPay",
DerivationScheme = DerivationScheme.ToString(),
}, "Save");

@ -43,11 +43,19 @@ namespace BTCPayServer.Tests
entity.TxFee = Money.Coins(0.1m);
entity.Rate = 5000;
var cryptoData = entity.GetCryptoData("BTC", null);
Assert.NotNull(cryptoData); // Should use legacy data to build itself
entity.Payments = new System.Collections.Generic.List<PaymentEntity>();
entity.ProductInformation = new ProductInformation() { Price = 5000 };
// Some check that handling legacy stuff does not break things
var cryptoData = entity.GetCryptoData("BTC", null, true);
cryptoData.Calculate();
Assert.NotNull(cryptoData);
Assert.Null(entity.GetCryptoData("BTC", null, false));
entity.SetCryptoData(new CryptoData() { ParentEntity = entity, Rate = entity.Rate, CryptoCode = "BTC", TxFee = entity.TxFee });
Assert.NotNull(entity.GetCryptoData("BTC", null, false));
Assert.NotNull(entity.GetCryptoData("BTC", null, true));
////////////////////
var accounting = cryptoData.Calculate();
Assert.Equal(Money.Coins(1.1m), accounting.Due);
Assert.Equal(Money.Coins(1.1m), accounting.TotalDue);
@ -384,6 +392,64 @@ namespace BTCPayServer.Tests
}
}
[Fact]
public void CanHaveLTCOnlyStore()
{
using (var tester = ServerTester.Create())
{
tester.Start();
var user = tester.NewAccount();
user.CryptoCode = "LTC";
user.GrantAccess();
// First we try payment with a merchant having only BTC
var invoice = user.BitPay.CreateInvoice(new Invoice()
{
Price = 500,
Currency = "USD",
PosData = "posData",
OrderId = "orderId",
ItemDesc = "Some description",
FullNotifications = true
}, Facade.Merchant);
Assert.Single(invoice.CryptoInfo);
Assert.Equal("LTC", invoice.CryptoInfo[0].CryptoCode);
var cashCow = tester.LTCExplorerNode;
var invoiceAddress = BitcoinAddress.Create(invoice.CryptoInfo[0].Address, cashCow.Network);
var firstPayment = Money.Coins(0.1m);
cashCow.SendToAddress(invoiceAddress, firstPayment);
Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal(firstPayment, invoice.CryptoInfo[0].Paid);
});
Assert.Single(invoice.CryptoInfo); // Only BTC should be presented
var controller = tester.PayTester.GetController<InvoiceController>(null);
var checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
Assert.Single(checkout.AvailableCryptos);
Assert.Equal("LTC", checkout.CryptoCode);
//////////////////////
// Despite it is called BitcoinAddress it should be LTC because BTC is not available
Assert.Null(invoice.BitcoinAddress);
Assert.NotEqual(invoice.BtcDue, invoice.CryptoInfo[0].Due); // Should be BTC rate
cashCow.SendToAddress(invoiceAddress, invoice.CryptoInfo[0].Due);
Eventually(() =>
{
invoice = user.BitPay.GetInvoice(invoice.Id);
Assert.Equal("paid", invoice.Status);
checkout = (Models.InvoicingModels.PaymentModel)((JsonResult)controller.GetStatus(invoice.Id, null).GetAwaiter().GetResult()).Value;
Assert.Equal("paid", checkout.Status);
});
}
}
[Fact]
public void CanPayWithTwoCurrencies()
{

@ -37,7 +37,7 @@ services:
- postgres
nbxplorer:
image: nicolasdorier/nbxplorer:1.0.0.53
image: nicolasdorier/nbxplorer:1.0.0.67
ports:
- "32838:32838"
expose:

@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<Version>1.0.0.68</Version>
<Version>1.0.0.78</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Build\dockerfiles\**" />
@ -24,7 +24,7 @@
<PackageReference Include="NBitcoin" Version="4.0.0.51" />
<PackageReference Include="NBitpayClient" Version="1.0.0.16" />
<PackageReference Include="DBreeze" Version="1.87.0" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.31" />
<PackageReference Include="NBXplorer.Client" Version="1.0.0.33" />
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />

@ -13,6 +13,13 @@ using NBXplorer;
namespace BTCPayServer.Configuration
{
public class NBXplorerConnectionSetting
{
public string CryptoCode { get; internal set; }
public Uri ExplorerUri { get; internal set; }
public string CookieFile { get; internal set; }
}
public class BTCPayServerOptions
{
public ChainType ChainType
@ -35,6 +42,12 @@ namespace BTCPayServer.Configuration
set;
}
public List<NBXplorerConnectionSetting> NBXplorerConnectionSettings
{
get;
set;
} = new List<NBXplorerConnectionSetting>();
public void LoadArgs(IConfiguration conf)
{
ChainType = DefaultConfiguration.GetChainType(conf);
@ -51,14 +64,11 @@ namespace BTCPayServer.Configuration
if (supportedChains.Contains(net.CryptoCode))
{
validChains.Add(net.CryptoCode);
var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl);
var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile);
if (cookieFile.Trim() == "0" || string.IsNullOrEmpty(cookieFile.Trim()))
cookieFile = null;
if (explorer != null)
{
ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile));
}
NBXplorerConnectionSetting setting = new NBXplorerConnectionSetting();
setting.CryptoCode = net.CryptoCode;
setting.ExplorerUri = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", net.NBXplorerNetwork.DefaultSettings.DefaultUrl);
setting.CookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", net.NBXplorerNetwork.DefaultSettings.DefaultCookieFile);
NBXplorerConnectionSettings.Add(setting);
}
}
var invalidChains = String.Join(',', supportedChains.Where(s => !validChains.Contains(s)).ToArray());
@ -70,15 +80,6 @@ namespace BTCPayServer.Configuration
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
}
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
{
var explorer = new ExplorerClient(n.NBXplorerNetwork, uri);
if (cookieFile == null || !explorer.SetCookieAuth(cookieFile))
explorer.SetNoAuth();
return explorer;
}
public Dictionary<string, Func<BTCPayNetwork, ExplorerClient>> ExplorerFactories = new Dictionary<string, Func<BTCPayNetwork, ExplorerClient>>();
public string PostgresConnectionString
{
get;

@ -19,7 +19,7 @@ namespace BTCPayServer.Configuration
protected override CommandLineApplication CreateCommandLineApplicationCore()
{
var provider = new BTCPayNetworkProvider(ChainType.Main);
var chains = string.Join(",", provider.GetAll().Select(n=>n.CryptoCode.ToLowerInvariant()).ToArray());
var chains = string.Join(",", provider.GetAll().Select(n => n.CryptoCode.ToLowerInvariant()).ToArray());
CommandLineApplication app = new CommandLineApplication(true)
{
FullName = "BTCPay\r\nOpen source, self-hosted payment processor.",
@ -72,9 +72,13 @@ namespace BTCPayServer.Configuration
if (network != null)
{
var n = Network.GetNetwork(network);
if (n == null)
{
throw new ConfigException($"Invalid network parameter '{network}'");
}
return n.ToChainType();
}
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainType.Regtest:
var net = conf.GetOrDefault<bool>("regtest", false) ? ChainType.Regtest :
conf.GetOrDefault<bool>("testnet", false) ? ChainType.Test : ChainType.Main;
return net;

@ -123,11 +123,22 @@ namespace BTCPayServer.Controllers
if (invoice == null)
return null;
var store = await _StoreRepository.FindStore(invoice.StoreId);
bool isDefaultCrypto = false;
if (cryptoCode == null)
{
cryptoCode = store.GetDefaultCrypto();
isDefaultCrypto = true;
}
var network = _NetworkProvider.GetNetwork(cryptoCode);
if (invoice == null || network == null || !invoice.Support(network))
if (invoice == null || network == null)
return null;
if(!invoice.Support(network))
{
if(!isDefaultCrypto)
return null;
network = invoice.GetCryptoData(_NetworkProvider).First().Value.Network;
}
var cryptoData = invoice.GetCryptoData(network, _NetworkProvider);
var dto = invoice.EntityToDTO(_NetworkProvider);
@ -251,7 +262,7 @@ namespace BTCPayServer.Controllers
{
await webSocket.SendAsync(DummyBuffer, WebSocketMessageType.Binary, true, cts.Token);
}
catch { await CloseSocket(webSocket); }
catch { try { webSocket.Dispose(); } catch { } }
}
private static async Task CloseSocket(WebSocket webSocket)
@ -266,7 +277,7 @@ namespace BTCPayServer.Controllers
}
}
catch { }
finally { webSocket.Dispose(); }
finally { try { webSocket.Dispose(); } catch { } }
}
[HttpPost]

@ -116,13 +116,13 @@ namespace BTCPayServer.Controllers
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
var queries = derivationStrategies
.Select(derivationStrategy => ( Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
.Select(derivationStrategy => (Wallet: _WalletProvider.GetWallet(derivationStrategy.Network),
DerivationStrategy: derivationStrategy.DerivationStrategyBase,
Network: derivationStrategy.Network,
RateProvider: _RateProviders.GetRateProvider(derivationStrategy.Network),
FeeRateProvider: _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network)))
.Where(_ => _.Wallet != null &&
_.FeeRateProvider != null &&
.Where(_ => _.Wallet != null &&
_.FeeRateProvider != null &&
_.RateProvider != null)
.Select(_ =>
{
@ -135,19 +135,21 @@ namespace BTCPayServer.Controllers
};
});
bool legacyBTCisSet = false;
var cryptoDatas = new Dictionary<string, CryptoData>();
foreach (var q in queries)
{
CryptoData cryptoData = new CryptoData();
cryptoData.CryptoCode = q.network.CryptoCode;
cryptoData.FeeRate = (await q.getFeeRate);
cryptoData.TxFee = storeBlob.NetworkFeeDisabled ? Money.Zero : cryptoData.FeeRate.GetFee(100); // assume price for 100 bytes
cryptoData.TxFee = GetTxFee(storeBlob, cryptoData.FeeRate); // assume price for 100 bytes
cryptoData.Rate = await q.getRate;
cryptoData.DepositAddress = (await q.getAddress).ToString();
#pragma warning disable CS0618
if (q.network.IsBTC)
{
legacyBTCisSet = true;
entity.TxFee = cryptoData.TxFee;
entity.Rate = cryptoData.Rate;
entity.DepositAddress = cryptoData.DepositAddress;
@ -155,6 +157,24 @@ namespace BTCPayServer.Controllers
#pragma warning restore CS0618
cryptoDatas.Add(cryptoData.CryptoCode, cryptoData);
}
if (!legacyBTCisSet)
{
// Legacy Bitpay clients expect information for BTC information, even if the store do not support it
#pragma warning disable CS0618
var btc = _NetworkProvider.BTC;
var feeProvider = _FeeProviderFactory.CreateFeeProvider(btc);
var rateProvider = _RateProviders.GetRateProvider(btc);
if (feeProvider != null && rateProvider != null)
{
var gettingFee = feeProvider.GetFeeRateAsync();
var gettingRate = rateProvider.GetRateAsync("BTC");
entity.TxFee = GetTxFee(storeBlob, await gettingFee);
entity.Rate = await gettingRate;
}
#pragma warning restore CS0618
}
entity.SetCryptoData(cryptoDatas);
entity.PosData = invoice.PosData;
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
@ -163,6 +183,11 @@ namespace BTCPayServer.Controllers
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
}
private static Money GetTxFee(StoreBlob storeBlob, FeeRate feeRate)
{
return storeBlob.NetworkFeeDisabled ? Money.Zero : feeRate.GetFee(100);
}
private SpeedPolicy ParseSpeedPolicy(string transactionSpeed, SpeedPolicy defaultPolicy)
{
if (transactionSpeed == null)

@ -6,6 +6,21 @@ using BTCPayServer.HostedServices;
namespace BTCPayServer.Events
{
public class NBXplorerErrorEvent
{
public NBXplorerErrorEvent(BTCPayNetwork network, string errorMessage)
{
Message = errorMessage;
Network = network;
}
public string Message { get; set; }
public BTCPayNetwork Network { get; set; }
public override string ToString()
{
return $"{Network.CryptoCode}: NBXplorer error `{Message}`";
}
}
public class NBXplorerStateChangedEvent
{
public NBXplorerStateChangedEvent(BTCPayNetwork network, NBXplorerState old, NBXplorerState newState)

@ -1,8 +1,10 @@
using System;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BTCPayServer.Configuration;
using BTCPayServer.Logging;
using NBXplorer;
namespace BTCPayServer
@ -18,18 +20,41 @@ namespace BTCPayServer
{
_NetworkProviders = networkProviders;
_Options = options;
foreach (var setting in options.NBXplorerConnectionSettings)
{
var cookieFile = setting.CookieFile;
if (cookieFile.Trim() == "0" || string.IsNullOrEmpty(cookieFile.Trim()))
cookieFile = null;
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Explorer url is {(setting.ExplorerUri.AbsoluteUri ?? "not set")}");
Logs.Configuration.LogInformation($"{setting.CryptoCode}: Cookie file is {(setting.CookieFile ?? "not set")}");
if (setting.ExplorerUri != null)
{
_Clients.TryAdd(setting.CryptoCode, CreateExplorerClient(_NetworkProviders.GetNetwork(setting.CryptoCode), setting.ExplorerUri, setting.CookieFile));
}
}
}
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
{
var explorer = new ExplorerClient(n.NBXplorerNetwork, uri);
if (cookieFile == null || !explorer.SetCookieAuth(cookieFile))
{
Logs.Configuration.LogWarning($"{n.CryptoCode}: Not using cookie authentication");
explorer.SetNoAuth();
}
return explorer;
}
Dictionary<string, ExplorerClient> _Clients = new Dictionary<string, ExplorerClient>();
public ExplorerClient GetExplorerClient(string cryptoCode)
{
var network = _NetworkProviders.GetNetwork(cryptoCode);
if (network == null)
return null;
if (_Options.ExplorerFactories.TryGetValue(network.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
{
return factory(network);
}
return null;
_Clients.TryGetValue(network.CryptoCode, out ExplorerClient client);
return client;
}
public ExplorerClient GetExplorerClient(BTCPayNetwork network)
@ -44,18 +69,18 @@ namespace BTCPayServer
var network = _NetworkProviders.GetNetwork(cryptoCode);
if (network == null)
return null;
if (_Options.ExplorerFactories.ContainsKey(network.CryptoCode))
if (_Clients.ContainsKey(network.CryptoCode))
return network;
return null;
}
public IEnumerable<(BTCPayNetwork, ExplorerClient)> GetAll()
{
foreach(var net in _NetworkProviders.GetAll())
foreach (var net in _NetworkProviders.GetAll())
{
if(_Options.ExplorerFactories.TryGetValue(net.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
if (_Clients.TryGetValue(net.CryptoCode, out ExplorerClient explorer))
{
yield return (net, factory(net));
yield return (net, explorer);
}
}
}

@ -27,11 +27,12 @@ namespace BTCPayServer.HostedServices
public BTCPayNetwork Network { get; set; }
public NBXplorerState State { get; set; }
public StatusResult Status { get; set; }
public string Error { get; set; }
}
ConcurrentDictionary<string, NBXplorerSummary> _Summaries = new ConcurrentDictionary<string, NBXplorerSummary>();
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status)
public void Publish(BTCPayNetwork network, NBXplorerState state, StatusResult status, string error)
{
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status };
var summary = new NBXplorerSummary() { Network = network, State = state, Status = status, Error = error };
_Summaries.AddOrUpdate(network.CryptoCode, summary, (k, v) => summary);
}
@ -120,6 +121,7 @@ namespace BTCPayServer.HostedServices
private async Task<bool> StepAsync(CancellationToken cancellation)
{
var oldState = State;
string error = null;
StatusResult status = null;
try
{
@ -164,15 +166,28 @@ namespace BTCPayServer.HostedServices
}
}
catch when (cancellation.IsCancellationRequested)
catch (Exception ex) when (!cancellation.IsCancellationRequested)
{
error = ex.Message;
}
catch (Exception ex)
if(status == null && error == null)
error = $"{_Network.CryptoCode}: NBXplorer does not support this cryptocurrency";
if(status != null && error == null)
{
if(status.ChainType != _Network.NBXplorerNetwork.DefaultSettings.ChainType)
error = $"{_Network.CryptoCode}: NBXplorer is on a different ChainType (actual: {status.ChainType}, expected: {_Network.NBXplorerNetwork.DefaultSettings.ChainType})";
}
if (error != null)
{
State = NBXplorerState.NotConnected;
Logs.PayServer.LogError(ex, $"Error while trying to connect to NBXplorer ({_Network.CryptoCode})");
status = null;
_Aggregator.Publish(new NBXplorerErrorEvent(_Network, error));
}
if (oldState != State)
{
if (State == NBXplorerState.Synching)
@ -185,7 +200,7 @@ namespace BTCPayServer.HostedServices
}
_Aggregator.Publish(new NBXplorerStateChangedEvent(_Network, oldState, State));
}
_Dashboard.Publish(_Network, State, status);
_Dashboard.Publish(_Network, State, status, error);
return oldState != State;
}

@ -1,27 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14139/",
"sslPort": 0
}
},
"profiles": {
"Default": {
"commandName": "Project"
},
"Docker-Regtest": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32839/",
"BTCPAY_NETWORK": "regtest",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
},
"applicationUrl": "http://localhost:14142/"
}
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:14139/",
"sslPort": 0
}
}
},
"profiles": {
"Default": {
"commandName": "Project",
"commandLineArgs": "--network testnet --chains ltc --ltcexplorerurl http://127.0.0.1:2727/"
},
"Docker-Regtest": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"BTCPAY_NETWORK": "regtest",
"BTCPAY_LTCEXPLORERURL": "http://127.0.0.1:32838/",
"BTCPAY_BTCEXPLORERURL": "http://127.0.0.1:32838/",
"ASPNETCORE_ENVIRONMENT": "Development",
"BTCPAY_CHAINS": "btc,ltc",
"BTCPAY_POSTGRES": "User ID=postgres;Host=127.0.0.1;Port=39372;Database=btcpayserver"
},
"applicationUrl": "http://localhost:14142/"
}
}
}

@ -323,7 +323,7 @@ namespace BTCPayServer.Services.Invoices
};
dto.CryptoInfo = new List<NBitpayClient.InvoiceCryptoInfo>();
foreach (var info in this.GetCryptoData(networkProvider).Values)
foreach (var info in this.GetCryptoData(networkProvider, true).Values)
{
var accounting = info.Calculate();
var cryptoInfo = new NBitpayClient.InvoiceCryptoInfo();
@ -369,8 +369,8 @@ namespace BTCPayServer.Services.Invoices
dto.PaymentUrls = cryptoInfo.PaymentUrls;
}
#pragma warning restore CS0618
dto.CryptoInfo.Add(cryptoInfo);
if(!info.IsPhantomBTC)
dto.CryptoInfo.Add(cryptoInfo);
}
Populate(ProductInformation, dto);
@ -395,9 +395,9 @@ namespace BTCPayServer.Services.Invoices
return rates.TryGetValue(network.CryptoCode, out var data);
}
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider)
public CryptoData GetCryptoData(string cryptoCode, BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
{
GetCryptoData(networkProvider).TryGetValue(cryptoCode, out var data);
GetCryptoData(networkProvider, alwaysIncludeBTC).TryGetValue(cryptoCode, out var data);
return data;
}
@ -407,26 +407,30 @@ namespace BTCPayServer.Services.Invoices
return data;
}
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider)
public Dictionary<string, CryptoData> GetCryptoData(BTCPayNetworkProvider networkProvider, bool alwaysIncludeBTC = false)
{
Dictionary<string, CryptoData> rates = new Dictionary<string, CryptoData>();
var serializer = new Serializer(Dummy);
CryptoData phantom = null;
#pragma warning disable CS0618
// Legacy
if (Rate != 0.0m)
if (alwaysIncludeBTC)
{
var btcNetwork = networkProvider?.GetNetwork("BTC");
rates.TryAdd("BTC", new CryptoData() { ParentEntity = this, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork });
phantom = new CryptoData() { ParentEntity = this, IsPhantomBTC = true, Rate = Rate, CryptoCode = "BTC", TxFee = TxFee, FeeRate = new FeeRate(TxFee, 100), DepositAddress = DepositAddress, Network = btcNetwork };
rates.Add("BTC", phantom);
}
if (CryptoData != null)
{
foreach (var prop in CryptoData.Properties())
{
if (prop.Name == "BTC" && phantom != null)
rates.Remove("BTC");
var r = serializer.ToObject<CryptoData>(prop.Value.ToString());
r.CryptoCode = prop.Name;
r.ParentEntity = this;
r.Network = networkProvider?.GetNetwork(r.CryptoCode);
rates.TryAdd(r.CryptoCode, r);
rates.Add(r.CryptoCode, r);
}
}
#pragma warning restore CS0618
@ -507,9 +511,12 @@ namespace BTCPayServer.Services.Invoices
[JsonProperty(PropertyName = "depositAddress")]
public string DepositAddress { get; set; }
[JsonIgnore]
public bool IsPhantomBTC { get; set; }
public CryptoDataAccounting Calculate()
{
var cryptoData = ParentEntity.GetCryptoData(null);
var cryptoData = ParentEntity.GetCryptoData(null, IsPhantomBTC);
var totalDue = Money.Coins(ParentEntity.ProductInformation.Price / Rate);
var paid = Money.Zero;
var cryptoPaid = Money.Zero;

@ -196,7 +196,7 @@ namespace BTCPayServer.Services.Invoices
continue;
var historical = new HistoricalAddressInvoiceData();
historical.InvoiceDataId = invoiceId;
historical.SetAddress(address.Value.DepositAddress, cryptoCode);
historical.SetAddress(address.Value.DepositAddress, address.Value.CryptoCode);
historical.UnAssigned = DateTimeOffset.UtcNow;
context.Attach(historical);
context.Entry(historical).Property(o => o.UnAssigned).IsModified = true;

@ -1,4 +1,6 @@
using Hangfire;
using BTCPayServer.Logging;
using Microsoft.Extensions.Logging;
using Hangfire;
using System;
using System.Collections.Generic;
using System.Linq;
@ -20,10 +22,16 @@ namespace BTCPayServer.Services.Mails
_JobClient = jobClient;
_Repository = repository;
}
public Task SendEmailAsync(string email, string subject, string message)
public async Task SendEmailAsync(string email, string subject, string message)
{
var settings = await _Repository.GetSettingAsync<EmailSettings>();
if (settings == null)
{
Logs.Configuration.LogWarning("Should have sent email, but email settings are not configured");
return;
}
_JobClient.Schedule(() => SendMailCore(email, subject, message), TimeSpan.Zero);
return Task.CompletedTask;
return;
}
public async Task SendMailCore(string email, string subject, string message)

@ -57,7 +57,15 @@ namespace BTCPayServer.Services.Wallets
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
{
if (derivationStrategy == null)
throw new ArgumentNullException(nameof(derivationStrategy));
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
// Might happen on some broken install
if (pathInfo == null)
{
await _Client.TrackAsync(derivationStrategy).ConfigureAwait(false);
pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
}
return pathInfo.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork);
}
@ -100,9 +108,17 @@ namespace BTCPayServer.Services.Wallets
public async Task<Money> GetBalance(DerivationStrategyBase derivationStrategy)
{
var result = await _Client.SyncAsync(derivationStrategy, null, true);
return result.Confirmed.UTXOs.Select(u => u.Value)
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Value))
.Sum();
Dictionary<OutPoint, UTXO> received = new Dictionary<OutPoint, UTXO>();
foreach(var utxo in result.Confirmed.UTXOs.Concat(result.Unconfirmed.UTXOs))
{
received.TryAdd(utxo.Outpoint, utxo);
}
foreach (var utxo in result.Confirmed.SpentOutpoints.Concat(result.Unconfirmed.SpentOutpoints))
{
received.Remove(utxo);
}
return received.Values.Select(c => c.Value).Sum();
}
}
}

@ -103,7 +103,13 @@
<h4>@line.Network.CryptoCode</h4>
@if(line.Status == null)
{
<p>NBXplorer is offline</p>
<ul>
<li>The node is offline</li>
@if(line.Error != null)
{
<li>Last error: @line.Error</li>
}
</ul>
}
else
{
@ -118,11 +124,21 @@
else
{
<li>The node is offline</li>
@if(line.Error != null)
{
<li>Last error: @line.Error</li>
}
}
}
else if(line.Status.BitcoinStatus.IsSynched)
{
<li>The node is synched</li>
<li>The node is synched (Height: @line.Status.BitcoinStatus.Headers)</li>
@if(line.Status.BitcoinStatus.IsSynched &&
line.Status.SyncHeight.HasValue &&
line.Status.SyncHeight.Value < line.Status.BitcoinStatus.Headers)
{
<li>NBXplorer is synching... (Height: @line.Status.SyncHeight.Value)</li>
}
}
else
{