Compare commits
20 Commits
Author | SHA1 | Date | |
---|---|---|---|
6b4eeff3f1 | |||
e3cc589ebb | |||
4a152e8ffc | |||
d54a9474d1 | |||
98472211fc | |||
099c9fa1f9 | |||
5226b77ffc | |||
290779ee39 | |||
4f39a8060c | |||
92caa98dfb | |||
df7bb9e2f8 | |||
02a039d695 | |||
b5e4c803aa | |||
2b7c70622f | |||
88779e7129 | |||
6beb7abfd2 | |||
a1ebedc0d1 | |||
d5ad0cdb39 | |||
39fb8dbb6a | |||
58194cb060 |
BTCPayServer.Tests
BTCPayServer
BTCPayServer.csproj
Configuration
Controllers
Events
ExplorerClientProvider.csHostedServices
Properties
Services
Views/Shared
@ -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
|
||||
{
|
||||
|
Reference in New Issue
Block a user