mirror of
https://github.com/aljazceru/btcpayserver.git
synced 2025-12-17 22:14:26 +01:00
Use Websocket for blockchain notifications
This commit is contained in:
@@ -107,12 +107,6 @@ namespace BTCPayServer.Tests
|
|||||||
.Build();
|
.Build();
|
||||||
_Host.Start();
|
_Host.Start();
|
||||||
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
InvoiceRepository = (InvoiceRepository)_Host.Services.GetService(typeof(InvoiceRepository));
|
||||||
|
|
||||||
var waiter = ((NBXplorerWaiterAccessor)_Host.Services.GetService(typeof(NBXplorerWaiterAccessor))).Instance;
|
|
||||||
while(waiter.State != NBXplorerState.Ready)
|
|
||||||
{
|
|
||||||
Thread.Sleep(10);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public string HostName
|
public string HostName
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ namespace BTCPayServer.Tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
public void TestAccessBitpayAPI()
|
||||||
{
|
{
|
||||||
using (var tester = ServerTester.Create())
|
using (var tester = ServerTester.Create())
|
||||||
{
|
{
|
||||||
@@ -298,6 +298,17 @@ namespace BTCPayServer.Tests
|
|||||||
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.False(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
user.GrantAccess();
|
user.GrantAccess();
|
||||||
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
Assert.True(user.BitPay.TestAccess(Facade.Merchant));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void InvoiceFlowThroughDifferentStatesCorrectly()
|
||||||
|
{
|
||||||
|
using (var tester = ServerTester.Create())
|
||||||
|
{
|
||||||
|
tester.Start();
|
||||||
|
var user = tester.NewAccount();
|
||||||
|
user.GrantAccess();
|
||||||
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
var invoice = user.BitPay.CreateInvoice(new Invoice()
|
||||||
{
|
{
|
||||||
Price = 5000.0,
|
Price = 5000.0,
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ services:
|
|||||||
# - eclair2
|
# - eclair2
|
||||||
|
|
||||||
nbxplorer:
|
nbxplorer:
|
||||||
image: nicolasdorier/nbxplorer:1.0.0.35
|
image: nicolasdorier/nbxplorer:1.0.0.36
|
||||||
ports:
|
ports:
|
||||||
- "32838:32838"
|
- "32838:32838"
|
||||||
expose:
|
expose:
|
||||||
|
|||||||
@@ -22,6 +22,5 @@ namespace BTCPayServer
|
|||||||
return CryptoCode == "BTC";
|
return CryptoCode == "BTC";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace BTCPayServer
|
|||||||
CryptoCode = "BTC",
|
CryptoCode = "BTC",
|
||||||
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
|
BlockExplorerLink = "https://www.smartbit.com.au/tx/{0}",
|
||||||
NBitcoinNetwork = Network.Main,
|
NBitcoinNetwork = Network.Main,
|
||||||
UriScheme = "bitcoin"
|
UriScheme = "bitcoin",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ namespace BTCPayServer
|
|||||||
CryptoCode = "BTC",
|
CryptoCode = "BTC",
|
||||||
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
BlockExplorerLink = "https://testnet.smartbit.com.au/tx/{0}",
|
||||||
NBitcoinNetwork = Network.TestNet,
|
NBitcoinNetwork = Network.TestNet,
|
||||||
UriScheme = "bitcoin"
|
UriScheme = "bitcoin",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,6 +59,11 @@ namespace BTCPayServer
|
|||||||
_Networks.Add(network.CryptoCode, network);
|
_Networks.Add(network.CryptoCode, network);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<BTCPayNetwork> GetAll()
|
||||||
|
{
|
||||||
|
return _Networks.Values.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
public BTCPayNetwork GetNetwork(string cryptoCode)
|
public BTCPayNetwork GetNetwork(string cryptoCode)
|
||||||
{
|
{
|
||||||
_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetwork network);
|
_Networks.TryGetValue(cryptoCode.ToUpperInvariant(), out BTCPayNetwork network);
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<PackageReference Include="NBitcoin" Version="4.0.0.51" />
|
<PackageReference Include="NBitcoin" Version="4.0.0.51" />
|
||||||
<PackageReference Include="NBitpayClient" Version="1.0.0.13" />
|
<PackageReference Include="NBitpayClient" Version="1.0.0.13" />
|
||||||
<PackageReference Include="DBreeze" Version="1.87.0" />
|
<PackageReference Include="DBreeze" Version="1.87.0" />
|
||||||
<PackageReference Include="NBXplorer.Client" Version="1.0.0.23" />
|
<PackageReference Include="NBXplorer.Client" Version="1.0.0.24" />
|
||||||
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
<PackageReference Include="NicolasDorier.CommandLine" Version="1.0.0.1" />
|
||||||
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
|
<PackageReference Include="NicolasDorier.CommandLine.Configuration" Version="1.0.0.2" />
|
||||||
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
|
<PackageReference Include="NicolasDorier.StandardConfiguration" Version="1.0.0.13" />
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Net;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using StandardConfiguration;
|
using StandardConfiguration;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using NBXplorer;
|
||||||
|
|
||||||
namespace BTCPayServer.Configuration
|
namespace BTCPayServer.Configuration
|
||||||
{
|
{
|
||||||
@@ -18,15 +19,6 @@ namespace BTCPayServer.Configuration
|
|||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
public Uri Explorer
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public string CookieFile
|
|
||||||
{
|
|
||||||
get; set;
|
|
||||||
}
|
|
||||||
public string ConfigurationFile
|
public string ConfigurationFile
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
@@ -53,11 +45,39 @@ namespace BTCPayServer.Configuration
|
|||||||
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
DataDir = conf.GetOrDefault<string>("datadir", networkInfo.DefaultDataDirectory);
|
||||||
Logs.Configuration.LogInformation("Network: " + Network);
|
Logs.Configuration.LogInformation("Network: " + Network);
|
||||||
|
|
||||||
Explorer = conf.GetOrDefault<Uri>("explorer.url", networkInfo.DefaultExplorerUrl);
|
foreach (var net in new BTCPayNetworkProvider(Network).GetAll())
|
||||||
CookieFile = conf.GetOrDefault<string>("explorer.cookiefile", networkInfo.DefaultExplorerCookieFile);
|
{
|
||||||
|
var explorer = conf.GetOrDefault<Uri>($"{net.CryptoCode}.explorer.url", null);
|
||||||
|
var cookieFile = conf.GetOrDefault<string>($"{net.CryptoCode}.explorer.cookiefile", null);
|
||||||
|
if (explorer != null && cookieFile != null)
|
||||||
|
{
|
||||||
|
ExplorerFactories.Add(net.CryptoCode, (n) => CreateExplorerClient(n, explorer, cookieFile));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle legacy explorer.url and explorer.cookiefile
|
||||||
|
if (ExplorerFactories.Count == 0)
|
||||||
|
{
|
||||||
|
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(Network.Name);
|
||||||
|
var explorer = conf.GetOrDefault<Uri>($"explorer.url", new Uri(nbxplorer.GetDefaultExplorerUrl(), UriKind.Absolute));
|
||||||
|
var cookieFile = conf.GetOrDefault<string>($"explorer.cookiefile", nbxplorer.GetDefaultCookieFile());
|
||||||
|
ExplorerFactories.Add("BTC", (n) => CreateExplorerClient(n, explorer, cookieFile));
|
||||||
|
}
|
||||||
|
//////
|
||||||
|
|
||||||
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
PostgresConnectionString = conf.GetOrDefault<string>("postgres", null);
|
||||||
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
ExternalUrl = conf.GetOrDefault<Uri>("externalurl", null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ExplorerClient CreateExplorerClient(BTCPayNetwork n, Uri uri, string cookieFile)
|
||||||
|
{
|
||||||
|
var explorer = new ExplorerClient(n.NBitcoinNetwork, uri);
|
||||||
|
if (!explorer.SetCookieAuth(cookieFile))
|
||||||
|
explorer.SetNoAuth();
|
||||||
|
return explorer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Dictionary<string, Func<BTCPayNetwork, ExplorerClient>> ExplorerFactories = new Dictionary<string, Func<BTCPayNetwork, ExplorerClient>>();
|
||||||
public string PostgresConnectionString
|
public string PostgresConnectionString
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ namespace BTCPayServer.Configuration
|
|||||||
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
|
app.Option("--testnet | -testnet", $"Use testnet", CommandOptionType.BoolValue);
|
||||||
app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue);
|
app.Option("--regtest | -regtest", $"Use regtest", CommandOptionType.BoolValue);
|
||||||
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
|
app.Option("--postgres", $"Connection string to postgres database (default: sqlite is used)", CommandOptionType.SingleValue);
|
||||||
app.Option("--explorerurl", $"Url of the NBxplorer (default: : Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
foreach (var network in new BTCPayNetworkProvider(Network.Main).GetAll())
|
||||||
app.Option("--explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
{
|
||||||
|
app.Option($"--{network.CryptoCode}explorerurl", $"Url of the NBxplorer for {network.CryptoCode} (default: If no explorer is specified, the default for Bitcoin will be selected)", CommandOptionType.SingleValue);
|
||||||
|
app.Option($"--{network.CryptoCode}explorercookiefile", $"Path to the cookie file (default: Default setting of NBXplorer for the network)", CommandOptionType.SingleValue);
|
||||||
|
}
|
||||||
app.Option("--externalurl", $"The expected external url of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
app.Option("--externalurl", $"The expected external url of this service, to use if BTCPay is behind a reverse proxy (default: empty, use the incoming HTTP request to figure out)", CommandOptionType.SingleValue);
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
@@ -83,8 +86,12 @@ namespace BTCPayServer.Configuration
|
|||||||
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
builder.AppendLine("#postgres=User ID=root;Password=myPassword;Host=localhost;Port=5432;Database=myDataBase;");
|
||||||
builder.AppendLine();
|
builder.AppendLine();
|
||||||
builder.AppendLine("### NBXplorer settings ###");
|
builder.AppendLine("### NBXplorer settings ###");
|
||||||
builder.AppendLine("#explorer.url=" + network.DefaultExplorerUrl.AbsoluteUri);
|
foreach (var n in new BTCPayNetworkProvider(network.Network).GetAll())
|
||||||
builder.AppendLine("#explorer.cookiefile=" + network.DefaultExplorerCookieFile);
|
{
|
||||||
|
var nbxplorer = NBXplorer.Configuration.NetworkInformation.GetNetworkByName(n.NBitcoinNetwork.ToString());
|
||||||
|
builder.AppendLine($"#{n.CryptoCode}.explorer.url={nbxplorer.GetDefaultExplorerUrl()}");
|
||||||
|
builder.AppendLine($"#{n.CryptoCode}.explorer.cookiefile={ nbxplorer.GetDefaultCookieFile()}");
|
||||||
|
}
|
||||||
return builder.ToString();
|
return builder.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,25 +13,20 @@ namespace BTCPayServer.Configuration
|
|||||||
static NetworkInformation()
|
static NetworkInformation()
|
||||||
{
|
{
|
||||||
_Networks = new Dictionary<string, NetworkInformation>();
|
_Networks = new Dictionary<string, NetworkInformation>();
|
||||||
foreach (var network in Network.GetNetworks())
|
foreach (var network in new[] { Network.Main, Network.TestNet, Network.RegTest })
|
||||||
{
|
{
|
||||||
NetworkInformation info = new NetworkInformation();
|
NetworkInformation info = new NetworkInformation();
|
||||||
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
info.DefaultDataDirectory = StandardConfiguration.DefaultDataDirectory.GetDirectory("BTCPayServer", network.Name);
|
||||||
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
info.DefaultConfigurationFile = Path.Combine(info.DefaultDataDirectory, "settings.config");
|
||||||
info.DefaultExplorerCookieFile = Path.Combine(StandardConfiguration.DefaultDataDirectory.GetDirectory("NBXplorer", network.Name, false), ".cookie");
|
|
||||||
info.Network = network;
|
info.Network = network;
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24446", UriKind.Absolute);
|
|
||||||
info.DefaultPort = 23002;
|
info.DefaultPort = 23002;
|
||||||
_Networks.Add(network.Name, info);
|
_Networks.Add(network.Name, info);
|
||||||
if (network == Network.Main)
|
if (network == Network.Main)
|
||||||
{
|
{
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24444", UriKind.Absolute);
|
|
||||||
Main = info;
|
|
||||||
info.DefaultPort = 23000;
|
info.DefaultPort = 23000;
|
||||||
}
|
}
|
||||||
if (network == Network.TestNet)
|
if (network == Network.TestNet)
|
||||||
{
|
{
|
||||||
info.DefaultExplorerUrl = new Uri("http://127.0.0.1:24445", UriKind.Absolute);
|
|
||||||
info.DefaultPort = 23001;
|
info.DefaultPort = 23001;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,12 +49,7 @@ namespace BTCPayServer.Configuration
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static NetworkInformation Main
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
set;
|
|
||||||
}
|
|
||||||
public Network Network
|
public Network Network
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
@@ -74,21 +64,11 @@ namespace BTCPayServer.Configuration
|
|||||||
get;
|
get;
|
||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
public Uri DefaultExplorerUrl
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
public int DefaultPort
|
public int DefaultPort
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
public string DefaultExplorerCookieFile
|
|
||||||
{
|
|
||||||
get;
|
|
||||||
internal set;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -273,7 +273,6 @@ namespace BTCPayServer.Controllers
|
|||||||
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
|
||||||
RegisteredUserId = user.Id;
|
RegisteredUserId = user.Id;
|
||||||
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
|
||||||
_logger.LogInformation("User created a new account with password.");
|
|
||||||
if (!policies.RequiresConfirmedEmail)
|
if (!policies.RequiresConfirmedEmail)
|
||||||
{
|
{
|
||||||
await _signInManager.SignInAsync(user, isPersistent: false);
|
await _signInManager.SignInAsync(user, isPersistent: false);
|
||||||
|
|||||||
@@ -61,14 +61,18 @@ namespace BTCPayServer.Controllers
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Route("i/{invoiceId}", Order = 99)]
|
[Route("i/{invoiceId}", Order = 99)]
|
||||||
|
[Route("i/{invoiceId}/{cryptoCode}", Order = 99)]
|
||||||
[MediaTypeConstraint("application/bitcoin-payment")]
|
[MediaTypeConstraint("application/bitcoin-payment")]
|
||||||
public async Task<IActionResult> PostPayment(string invoiceId)
|
public async Task<IActionResult> PostPayment(string invoiceId, string cryptoCode = null)
|
||||||
{
|
{
|
||||||
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||||
if (invoice == null || invoice.IsExpired())
|
if (invoice == null || invoice.IsExpired())
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
if (cryptoCode == null)
|
||||||
|
cryptoCode = "BTC";
|
||||||
|
var network = _NetworkProvider.GetNetwork(cryptoCode);
|
||||||
var payment = PaymentMessage.Load(Request.Body);
|
var payment = PaymentMessage.Load(Request.Body);
|
||||||
var unused = _Wallet.BroadcastTransactionsAsync(payment.Transactions);
|
var unused = _Wallet.BroadcastTransactionsAsync(network, payment.Transactions);
|
||||||
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
|
await _InvoiceRepository.AddRefundsAsync(invoiceId, payment.RefundTo.Select(p => new TxOut(p.Amount, p.Script)).ToArray());
|
||||||
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
return new PaymentAckActionResult(payment.CreateACK(invoiceId + " is currently processing, thanks for your purchase..."));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
if (command == "refresh")
|
if (command == "refresh")
|
||||||
{
|
{
|
||||||
_Watcher.Watch(invoiceId);
|
_EventAggregator.Publish(new Events.InvoiceCreatedEvent(invoiceId));
|
||||||
}
|
}
|
||||||
StatusMessage = "Invoice is state is being refreshed, please refresh the page soon...";
|
StatusMessage = "Invoice is state is being refreshed, please refresh the page soon...";
|
||||||
return RedirectToAction(nameof(Invoice), new
|
return RedirectToAction(nameof(Invoice), new
|
||||||
@@ -94,7 +94,7 @@ namespace BTCPayServer.Controllers
|
|||||||
{
|
{
|
||||||
var m = new InvoiceDetailsModel.Payment();
|
var m = new InvoiceDetailsModel.Payment();
|
||||||
m.DepositAddress = payment.GetScriptPubKey().GetDestinationAddress(network.NBitcoinNetwork);
|
m.DepositAddress = payment.GetScriptPubKey().GetDestinationAddress(network.NBitcoinNetwork);
|
||||||
m.Confirmations = (await _Explorer.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
m.Confirmations = (await _ExplorerClients.GetExplorerClient(payment.GetCryptoCode())?.GetTransactionAsync(payment.Outpoint.Hash))?.Confirmations ?? 0;
|
||||||
m.TransactionId = payment.Outpoint.Hash.ToString();
|
m.TransactionId = payment.Outpoint.Hash.ToString();
|
||||||
m.ReceivedTime = payment.ReceivedTime;
|
m.ReceivedTime = payment.ReceivedTime;
|
||||||
m.TransactionLink = string.Format(network.BlockExplorerLink, m.TransactionId);
|
m.TransactionLink = string.Format(network.BlockExplorerLink, m.TransactionId);
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ using Microsoft.EntityFrameworkCore;
|
|||||||
using Microsoft.AspNetCore.Mvc.Routing;
|
using Microsoft.AspNetCore.Mvc.Routing;
|
||||||
using NBXplorer.DerivationStrategy;
|
using NBXplorer.DerivationStrategy;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
|
using BTCPayServer.HostedServices;
|
||||||
|
|
||||||
namespace BTCPayServer.Controllers
|
namespace BTCPayServer.Controllers
|
||||||
{
|
{
|
||||||
@@ -46,14 +47,13 @@ namespace BTCPayServer.Controllers
|
|||||||
InvoiceRepository _InvoiceRepository;
|
InvoiceRepository _InvoiceRepository;
|
||||||
BTCPayWallet _Wallet;
|
BTCPayWallet _Wallet;
|
||||||
IRateProvider _RateProvider;
|
IRateProvider _RateProvider;
|
||||||
private InvoiceWatcher _Watcher;
|
|
||||||
StoreRepository _StoreRepository;
|
StoreRepository _StoreRepository;
|
||||||
UserManager<ApplicationUser> _UserManager;
|
UserManager<ApplicationUser> _UserManager;
|
||||||
IFeeProviderFactory _FeeProviderFactory;
|
IFeeProviderFactory _FeeProviderFactory;
|
||||||
private CurrencyNameTable _CurrencyNameTable;
|
private CurrencyNameTable _CurrencyNameTable;
|
||||||
ExplorerClient _Explorer;
|
|
||||||
EventAggregator _EventAggregator;
|
EventAggregator _EventAggregator;
|
||||||
BTCPayNetworkProvider _NetworkProvider;
|
BTCPayNetworkProvider _NetworkProvider;
|
||||||
|
ExplorerClientProvider _ExplorerClients;
|
||||||
public InvoiceController(InvoiceRepository invoiceRepository,
|
public InvoiceController(InvoiceRepository invoiceRepository,
|
||||||
CurrencyNameTable currencyNameTable,
|
CurrencyNameTable currencyNameTable,
|
||||||
UserManager<ApplicationUser> userManager,
|
UserManager<ApplicationUser> userManager,
|
||||||
@@ -61,18 +61,16 @@ namespace BTCPayServer.Controllers
|
|||||||
IRateProvider rateProvider,
|
IRateProvider rateProvider,
|
||||||
StoreRepository storeRepository,
|
StoreRepository storeRepository,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
InvoiceWatcherAccessor watcher,
|
|
||||||
ExplorerClient explorerClient,
|
|
||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
|
ExplorerClientProvider explorerClientProviders,
|
||||||
IFeeProviderFactory feeProviderFactory)
|
IFeeProviderFactory feeProviderFactory)
|
||||||
{
|
{
|
||||||
|
_ExplorerClients = explorerClientProviders;
|
||||||
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
|
||||||
_Explorer = explorerClient ?? throw new ArgumentNullException(nameof(explorerClient));
|
|
||||||
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
_StoreRepository = storeRepository ?? throw new ArgumentNullException(nameof(storeRepository));
|
||||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||||
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
_RateProvider = rateProvider ?? throw new ArgumentNullException(nameof(rateProvider));
|
||||||
_Watcher = (watcher ?? throw new ArgumentNullException(nameof(watcher))).Instance;
|
|
||||||
_UserManager = userManager;
|
_UserManager = userManager;
|
||||||
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
|
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
|
||||||
_EventAggregator = eventAggregator;
|
_EventAggregator = eventAggregator;
|
||||||
@@ -151,7 +149,7 @@ namespace BTCPayServer.Controllers
|
|||||||
entity.SetCryptoData(cryptoDatas);
|
entity.SetCryptoData(cryptoDatas);
|
||||||
entity.PosData = invoice.PosData;
|
entity.PosData = invoice.PosData;
|
||||||
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
entity = await _InvoiceRepository.CreateInvoiceAsync(store.Id, entity, _NetworkProvider);
|
||||||
_Watcher.Watch(entity.Id);
|
_EventAggregator.Publish(new Events.InvoiceCreatedEvent(entity.Id));
|
||||||
var resp = entity.EntityToDTO(_NetworkProvider);
|
var resp = entity.EntityToDTO(_NetworkProvider);
|
||||||
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
return new DataWrapper<InvoiceResponse>(resp) { Facade = "pos/invoice" };
|
||||||
}
|
}
|
||||||
|
|||||||
22
BTCPayServer/Events/InvoiceCreatedEvent.cs
Normal file
22
BTCPayServer/Events/InvoiceCreatedEvent.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BTCPayServer.Events
|
||||||
|
{
|
||||||
|
public class InvoiceCreatedEvent
|
||||||
|
{
|
||||||
|
public InvoiceCreatedEvent(string id)
|
||||||
|
{
|
||||||
|
InvoiceId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string InvoiceId { get; set; }
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"Invoice {InvoiceId} created";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,23 +2,26 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.HostedServices;
|
||||||
|
|
||||||
namespace BTCPayServer.Events
|
namespace BTCPayServer.Events
|
||||||
{
|
{
|
||||||
public class NBXplorerStateChangedEvent
|
public class NBXplorerStateChangedEvent
|
||||||
{
|
{
|
||||||
public NBXplorerStateChangedEvent(NBXplorerState old, NBXplorerState newState)
|
public NBXplorerStateChangedEvent(BTCPayNetwork network, NBXplorerState old, NBXplorerState newState)
|
||||||
{
|
{
|
||||||
|
Network = network;
|
||||||
NewState = newState;
|
NewState = newState;
|
||||||
OldState = old;
|
OldState = old;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BTCPayNetwork Network { get; set; }
|
||||||
public NBXplorerState NewState { get; set; }
|
public NBXplorerState NewState { get; set; }
|
||||||
public NBXplorerState OldState { get; set; }
|
public NBXplorerState OldState { get; set; }
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"NBXplorer: {OldState} => {NewState}";
|
return $"NBXplorer {Network.CryptoCode}: {OldState} => {NewState}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@ namespace BTCPayServer.Events
|
|||||||
{
|
{
|
||||||
public class TxOutReceivedEvent
|
public class TxOutReceivedEvent
|
||||||
{
|
{
|
||||||
|
public BTCPayNetwork Network { get; set; }
|
||||||
public Script ScriptPubKey { get; set; }
|
public Script ScriptPubKey { get; set; }
|
||||||
public BitcoinAddress Address { get; set; }
|
|
||||||
|
|
||||||
public override string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
String address = Address?.ToString() ?? ScriptPubKey.ToHex();
|
String address = ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork)?.ToString() ?? ScriptPubKey.ToString();
|
||||||
return $"{address} received a transaction";
|
return $"{address} received a transaction";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
56
BTCPayServer/ExplorerClientProvider.cs
Normal file
56
BTCPayServer/ExplorerClientProvider.cs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Configuration;
|
||||||
|
using NBXplorer;
|
||||||
|
|
||||||
|
namespace BTCPayServer
|
||||||
|
{
|
||||||
|
public class ExplorerClientProvider
|
||||||
|
{
|
||||||
|
BTCPayNetworkProvider _NetworkProviders;
|
||||||
|
BTCPayServerOptions _Options;
|
||||||
|
|
||||||
|
public BTCPayNetworkProvider NetworkProviders => _NetworkProviders;
|
||||||
|
|
||||||
|
public ExplorerClientProvider(BTCPayNetworkProvider networkProviders, BTCPayServerOptions options)
|
||||||
|
{
|
||||||
|
_NetworkProviders = networkProviders;
|
||||||
|
_Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object GetExplorerClient(object network)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ExplorerClient GetExplorerClient(BTCPayNetwork network)
|
||||||
|
{
|
||||||
|
return GetExplorerClient(network.CryptoCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<(BTCPayNetwork, ExplorerClient)> GetAll()
|
||||||
|
{
|
||||||
|
foreach(var net in _NetworkProviders.GetAll())
|
||||||
|
{
|
||||||
|
if(_Options.ExplorerFactories.TryGetValue(net.CryptoCode, out Func<BTCPayNetwork, ExplorerClient> factory))
|
||||||
|
{
|
||||||
|
yield return (net, factory(net));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,21 +18,30 @@ using NBXplorer.Models;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using BTCPayServer.Services.Wallets;
|
using BTCPayServer.Services.Wallets;
|
||||||
|
using System.IO;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer
|
||||||
{
|
{
|
||||||
public static class Extensions
|
public static class Extensions
|
||||||
{
|
{
|
||||||
|
public static string GetDefaultExplorerUrl(this NBXplorer.Configuration.NetworkInformation networkInfo)
|
||||||
|
{
|
||||||
|
return $"http://127.0.0.1:{networkInfo.DefaultExplorerPort}/";
|
||||||
|
}
|
||||||
|
public static string GetDefaultCookieFile(this NBXplorer.Configuration.NetworkInformation networkInfo)
|
||||||
|
{
|
||||||
|
return Path.Combine(networkInfo.DefaultDataDirectory, ".cookie");
|
||||||
|
}
|
||||||
public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
|
public static bool SupportDropColumn(this Microsoft.EntityFrameworkCore.Migrations.Migration migration, string activeProvider)
|
||||||
{
|
{
|
||||||
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
return activeProvider != "Microsoft.EntityFrameworkCore.Sqlite";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, uint256[] hashes, CancellationToken cts = default(CancellationToken))
|
public static async Task<Dictionary<uint256, TransactionResult>> GetTransactions(this BTCPayWallet client, BTCPayNetwork network, uint256[] hashes, CancellationToken cts = default(CancellationToken))
|
||||||
{
|
{
|
||||||
hashes = hashes.Distinct().ToArray();
|
hashes = hashes.Distinct().ToArray();
|
||||||
var transactions = hashes
|
var transactions = hashes
|
||||||
.Select(async o => await client.GetTransactionAsync(o, cts))
|
.Select(async o => await client.GetTransactionAsync(network, o, cts))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
await Task.WhenAll(transactions).ConfigureAwait(false);
|
await Task.WhenAll(transactions).ConfigureAwait(false);
|
||||||
return transactions.Select(t => t.Result).Where(t => t != null).ToDictionary(o => o.Transaction.GetHash());
|
return transactions.Select(t => t.Result).Where(t => t != null).ToDictionary(o => o.Transaction.GetHash());
|
||||||
|
|||||||
@@ -18,8 +18,9 @@ using System.Collections.Concurrent;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using NBXplorer;
|
using NBXplorer;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
public class InvoiceNotificationManager : IHostedService
|
public class InvoiceNotificationManager : IHostedService
|
||||||
{
|
{
|
||||||
@@ -16,13 +16,10 @@ using BTCPayServer.Services.Wallets;
|
|||||||
using BTCPayServer.Controllers;
|
using BTCPayServer.Controllers;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
|
||||||
namespace BTCPayServer.Services.Invoices
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
public class InvoiceWatcherAccessor
|
|
||||||
{
|
|
||||||
public InvoiceWatcher Instance { get; set; }
|
|
||||||
}
|
|
||||||
public class InvoiceWatcher : IHostedService
|
public class InvoiceWatcher : IHostedService
|
||||||
{
|
{
|
||||||
class UpdateInvoiceContext
|
class UpdateInvoiceContext
|
||||||
@@ -56,15 +53,13 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
BTCPayNetworkProvider networkProvider,
|
BTCPayNetworkProvider networkProvider,
|
||||||
InvoiceRepository invoiceRepository,
|
InvoiceRepository invoiceRepository,
|
||||||
EventAggregator eventAggregator,
|
EventAggregator eventAggregator,
|
||||||
BTCPayWallet wallet,
|
BTCPayWallet wallet)
|
||||||
InvoiceWatcherAccessor accessor)
|
|
||||||
{
|
{
|
||||||
PollInterval = TimeSpan.FromMinutes(1.0);
|
PollInterval = TimeSpan.FromMinutes(1.0);
|
||||||
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet));
|
||||||
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
_InvoiceRepository = invoiceRepository ?? throw new ArgumentNullException(nameof(invoiceRepository));
|
||||||
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
_EventAggregator = eventAggregator ?? throw new ArgumentNullException(nameof(eventAggregator));
|
||||||
_NetworkProvider = networkProvider;
|
_NetworkProvider = networkProvider;
|
||||||
accessor.Instance = this;
|
|
||||||
}
|
}
|
||||||
CompositeDisposable leases = new CompositeDisposable();
|
CompositeDisposable leases = new CompositeDisposable();
|
||||||
|
|
||||||
@@ -157,7 +152,6 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString())).ToArray();
|
response.Coins = response.Coins.Where(c => invoice.AvailableAddressHashes.Contains(c.ScriptPubKey.Hash.ToString())).ToArray();
|
||||||
}
|
}
|
||||||
var coins = getCoinsResponses.Where(s => s.Coins.Length != 0).FirstOrDefault();
|
var coins = getCoinsResponses.Where(s => s.Coins.Length != 0).FirstOrDefault();
|
||||||
|
|
||||||
bool dirtyAddress = false;
|
bool dirtyAddress = false;
|
||||||
if (coins != null)
|
if (coins != null)
|
||||||
{
|
{
|
||||||
@@ -172,7 +166,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//////
|
//////
|
||||||
var network = _NetworkProvider.GetNetwork("BTC");
|
var network = coins?.Strategy?.Network ?? _NetworkProvider.GetNetwork(invoice.GetCryptoData().First().Key);
|
||||||
var cryptoData = invoice.GetCryptoData(network);
|
var cryptoData = invoice.GetCryptoData(network);
|
||||||
var cryptoDataAll = invoice.GetCryptoData();
|
var cryptoDataAll = invoice.GetCryptoData();
|
||||||
var accounting = cryptoData.Calculate();
|
var accounting = cryptoData.Calculate();
|
||||||
@@ -187,7 +181,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
if (invoice.Status == "new" || invoice.Status == "expired")
|
if (invoice.Status == "new" || invoice.Status == "expired")
|
||||||
{
|
{
|
||||||
var totalPaid = (await GetPaymentsWithTransaction(invoice)).Select(p => p.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
var totalPaid = (await GetPaymentsWithTransaction(network, invoice)).Select(p => p.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||||
if (totalPaid >= accounting.TotalDue)
|
if (totalPaid >= accounting.TotalDue)
|
||||||
{
|
{
|
||||||
if (invoice.Status == "new")
|
if (invoice.Status == "new")
|
||||||
@@ -228,7 +222,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
if (invoice.Status == "paid")
|
if (invoice.Status == "paid")
|
||||||
{
|
{
|
||||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
var transactions = await GetPaymentsWithTransaction(network, invoice);
|
||||||
var chainConfirmedTransactions = transactions.Where(t => t.Confirmations >= 1);
|
var chainConfirmedTransactions = transactions.Where(t => t.Confirmations >= 1);
|
||||||
if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
|
||||||
{
|
{
|
||||||
@@ -271,7 +265,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
|
|
||||||
if (invoice.Status == "confirmed")
|
if (invoice.Status == "confirmed")
|
||||||
{
|
{
|
||||||
var transactions = await GetPaymentsWithTransaction(invoice);
|
var transactions = await GetPaymentsWithTransaction(network, invoice);
|
||||||
transactions = transactions.Where(t => t.Confirmations >= 6);
|
transactions = transactions.Where(t => t.Confirmations >= 6);
|
||||||
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
var totalConfirmed = transactions.Select(t => t.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum();
|
||||||
if (totalConfirmed >= accounting.TotalDue)
|
if (totalConfirmed >= accounting.TotalDue)
|
||||||
@@ -283,9 +277,9 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(InvoiceEntity invoice)
|
private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(BTCPayNetwork network, InvoiceEntity invoice)
|
||||||
{
|
{
|
||||||
var transactions = await _Wallet.GetTransactions(invoice.Payments.Select(t => t.Outpoint.Hash).ToArray());
|
var transactions = await _Wallet.GetTransactions(network, invoice.Payments.Select(t => t.Outpoint.Hash).ToArray());
|
||||||
|
|
||||||
var spentTxIn = new Dictionary<OutPoint, AccountedPaymentEntity>();
|
var spentTxIn = new Dictionary<OutPoint, AccountedPaymentEntity>();
|
||||||
var result = invoice.Payments.Select(p => p.Outpoint).ToHashSet();
|
var result = invoice.Payments.Select(p => p.Outpoint).ToHashSet();
|
||||||
@@ -355,7 +349,7 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Watch(string invoiceId)
|
private void Watch(string invoiceId)
|
||||||
{
|
{
|
||||||
if (invoiceId == null)
|
if (invoiceId == null)
|
||||||
throw new ArgumentNullException(nameof(invoiceId));
|
throw new ArgumentNullException(nameof(invoiceId));
|
||||||
@@ -390,7 +384,8 @@ namespace BTCPayServer.Services.Invoices
|
|||||||
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
}, null, 0, (int)PollInterval.TotalMilliseconds);
|
||||||
|
|
||||||
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
|
leases.Add(_EventAggregator.Subscribe<Events.NewBlockEvent>(async b => { await NotifyBlock(); }));
|
||||||
leases.Add(_EventAggregator.Subscribe<TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey); }));
|
leases.Add(_EventAggregator.Subscribe<Events.TxOutReceivedEvent>(async b => { await NotifyReceived(b.ScriptPubKey); }));
|
||||||
|
leases.Add(_EventAggregator.Subscribe<Events.InvoiceCreatedEvent>(b => { Watch(b.InvoiceId); }));
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
153
BTCPayServer/HostedServices/NBXplorerListener.cs
Normal file
153
BTCPayServer/HostedServices/NBXplorerListener.cs
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using BTCPayServer.Logging;
|
||||||
|
using BTCPayServer.Services.Invoices;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using NBXplorer;
|
||||||
|
using System.Collections.Concurrent;
|
||||||
|
using NBXplorer.DerivationStrategy;
|
||||||
|
using BTCPayServer.Events;
|
||||||
|
|
||||||
|
namespace BTCPayServer.HostedServices
|
||||||
|
{
|
||||||
|
public class NBXplorerListener : IHostedService
|
||||||
|
{
|
||||||
|
EventAggregator _Aggregator;
|
||||||
|
ExplorerClientProvider _ExplorerClients;
|
||||||
|
IApplicationLifetime _Lifetime;
|
||||||
|
InvoiceRepository _InvoiceRepository;
|
||||||
|
private TaskCompletionSource<bool> _RunningTask;
|
||||||
|
private CancellationTokenSource _Cts;
|
||||||
|
|
||||||
|
public NBXplorerListener(ExplorerClientProvider explorerClients,
|
||||||
|
InvoiceRepository invoiceRepository,
|
||||||
|
EventAggregator aggregator, IApplicationLifetime lifetime)
|
||||||
|
{
|
||||||
|
_InvoiceRepository = invoiceRepository;
|
||||||
|
_ExplorerClients = explorerClients;
|
||||||
|
_Aggregator = aggregator;
|
||||||
|
_Lifetime = lifetime;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositeDisposable leases = new CompositeDisposable();
|
||||||
|
ConcurrentDictionary<string, NotificationSession> _Sessions = new ConcurrentDictionary<string, NotificationSession>();
|
||||||
|
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
_RunningTask = new TaskCompletionSource<bool>();
|
||||||
|
_Cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
leases.Add(_Aggregator.Subscribe<Events.NBXplorerStateChangedEvent>(async nbxplorerEvent =>
|
||||||
|
{
|
||||||
|
if (nbxplorerEvent.NewState == NBXplorerState.Ready)
|
||||||
|
{
|
||||||
|
if (_Sessions.ContainsKey(nbxplorerEvent.Network.CryptoCode))
|
||||||
|
return;
|
||||||
|
var client = _ExplorerClients.GetExplorerClient(nbxplorerEvent.Network);
|
||||||
|
var session = await client.CreateNotificationSessionAsync(_Cts.Token);
|
||||||
|
if (!_Sessions.TryAdd(nbxplorerEvent.Network.CryptoCode, session))
|
||||||
|
{
|
||||||
|
await session.DisposeAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (session)
|
||||||
|
{
|
||||||
|
await session.ListenNewBlockAsync(_Cts.Token);
|
||||||
|
await session.ListenDerivationSchemesAsync((await GetStrategies(nbxplorerEvent)).ToArray(), _Cts.Token);
|
||||||
|
Logs.PayServer.LogInformation($"Start Listening {nbxplorerEvent.Network.CryptoCode} explorer events");
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
var newEvent = await session.NextEventAsync(_Cts.Token);
|
||||||
|
switch (newEvent)
|
||||||
|
{
|
||||||
|
case NBXplorer.Models.NewBlockEvent evt:
|
||||||
|
_Aggregator.Publish(new Events.NewBlockEvent());
|
||||||
|
break;
|
||||||
|
case NBXplorer.Models.NewTransactionEvent evt:
|
||||||
|
foreach (var txout in evt.Match.Outputs)
|
||||||
|
{
|
||||||
|
_Aggregator.Publish(new Events.TxOutReceivedEvent()
|
||||||
|
{
|
||||||
|
Network = nbxplorerEvent.Network,
|
||||||
|
ScriptPubKey = txout.ScriptPubKey
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Logs.PayServer.LogWarning("Received unknown message from NBXplorer");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch when (_Cts.IsCancellationRequested) { }
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Logs.PayServer.LogInformation($"Stop listening {nbxplorerEvent.Network.CryptoCode} explorer events");
|
||||||
|
_Sessions.TryRemove(nbxplorerEvent.Network.CryptoCode, out NotificationSession unused);
|
||||||
|
if(_Sessions.Count == 0 && _Cts.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
_RunningTask.TrySetResult(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
leases.Add(_Aggregator.Subscribe<Events.InvoiceCreatedEvent>(async inv =>
|
||||||
|
{
|
||||||
|
var invoice = await _InvoiceRepository.GetInvoice(null, inv.InvoiceId);
|
||||||
|
List<Task> listeningDerivations = new List<Task>();
|
||||||
|
foreach (var notificationSessions in _Sessions)
|
||||||
|
{
|
||||||
|
var derivationStrategy = GetStrategy(notificationSessions.Key, invoice);
|
||||||
|
if (derivationStrategy != null)
|
||||||
|
{
|
||||||
|
listeningDerivations.Add(notificationSessions.Value.ListenDerivationSchemesAsync(new[] { derivationStrategy }, _Cts.Token));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Task.WhenAll(listeningDerivations.ToArray()).ConfigureAwait(false);
|
||||||
|
}));
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<List<DerivationStrategyBase>> GetStrategies(NBXplorerStateChangedEvent nbxplorerEvent)
|
||||||
|
{
|
||||||
|
List<DerivationStrategyBase> strategies = new List<DerivationStrategyBase>();
|
||||||
|
foreach (var invoiceId in await _InvoiceRepository.GetPendingInvoices())
|
||||||
|
{
|
||||||
|
var invoice = await _InvoiceRepository.GetInvoice(null, invoiceId);
|
||||||
|
var strategy = GetStrategy(nbxplorerEvent.Network.CryptoCode, invoice);
|
||||||
|
if (strategy != null)
|
||||||
|
strategies.Add(strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return strategies;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DerivationStrategyBase GetStrategy(string cryptoCode, InvoiceEntity invoice)
|
||||||
|
{
|
||||||
|
foreach (var derivationStrategy in invoice.GetDerivationStrategies(_ExplorerClients.NetworkProviders))
|
||||||
|
{
|
||||||
|
if (derivationStrategy.Network.CryptoCode == cryptoCode)
|
||||||
|
{
|
||||||
|
return derivationStrategy.DerivationStrategyBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
leases.Dispose();
|
||||||
|
_Cts.Cancel();
|
||||||
|
return Task.WhenAny(_RunningTask.Task, Task.Delay(-1, cancellationToken));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,12 +11,8 @@ using NBXplorer.Models;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using BTCPayServer.Events;
|
using BTCPayServer.Events;
|
||||||
|
|
||||||
namespace BTCPayServer
|
namespace BTCPayServer.HostedServices
|
||||||
{
|
{
|
||||||
public class NBXplorerWaiterAccessor
|
|
||||||
{
|
|
||||||
public NBXplorerWaiter Instance { get; set; }
|
|
||||||
}
|
|
||||||
public enum NBXplorerState
|
public enum NBXplorerState
|
||||||
{
|
{
|
||||||
NotConnected,
|
NotConnected,
|
||||||
@@ -24,15 +20,38 @@ namespace BTCPayServer
|
|||||||
Ready
|
Ready
|
||||||
}
|
}
|
||||||
|
|
||||||
public class NBXplorerWaiter : IHostedService
|
public class NBXplorerWaiters : IHostedService
|
||||||
{
|
{
|
||||||
public NBXplorerWaiter(ExplorerClient client, EventAggregator aggregator, NBXplorerWaiterAccessor accessor)
|
List<NBXplorerWaiter> _Waiters = new List<NBXplorerWaiter>();
|
||||||
|
public NBXplorerWaiters(ExplorerClientProvider explorerClientProvider, EventAggregator eventAggregator)
|
||||||
{
|
{
|
||||||
_Client = client;
|
foreach(var explorer in explorerClientProvider.GetAll())
|
||||||
_Aggregator = aggregator;
|
{
|
||||||
accessor.Instance = this;
|
_Waiters.Add(new NBXplorerWaiter(explorer.Item1, explorer.Item2, eventAggregator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.WhenAll(_Waiters.Select(w => w.StartAsync(cancellationToken)).ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
return Task.WhenAll(_Waiters.Select(w => w.StopAsync(cancellationToken)).ToArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class NBXplorerWaiter : IHostedService
|
||||||
|
{
|
||||||
|
|
||||||
|
public NBXplorerWaiter(BTCPayNetwork network, ExplorerClient client, EventAggregator aggregator)
|
||||||
|
{
|
||||||
|
_Network = network;
|
||||||
|
_Client = client;
|
||||||
|
_Aggregator = aggregator;
|
||||||
|
}
|
||||||
|
|
||||||
|
BTCPayNetwork _Network;
|
||||||
EventAggregator _Aggregator;
|
EventAggregator _Aggregator;
|
||||||
ExplorerClient _Client;
|
ExplorerClient _Client;
|
||||||
Timer _Timer;
|
Timer _Timer;
|
||||||
@@ -126,7 +145,7 @@ namespace BTCPayServer
|
|||||||
{
|
{
|
||||||
SetInterval(TimeSpan.FromMinutes(1));
|
SetInterval(TimeSpan.FromMinutes(1));
|
||||||
}
|
}
|
||||||
_Aggregator.Publish(new NBXplorerStateChangedEvent(oldState, State));
|
_Aggregator.Publish(new NBXplorerStateChangedEvent(_Network, oldState, State));
|
||||||
}
|
}
|
||||||
return oldState != State;
|
return oldState != State;
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ using BTCPayServer.Services.Wallets;
|
|||||||
using BTCPayServer.Authentication;
|
using BTCPayServer.Authentication;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using BTCPayServer.Logging;
|
using BTCPayServer.Logging;
|
||||||
|
using BTCPayServer.HostedServices;
|
||||||
|
|
||||||
namespace BTCPayServer.Hosting
|
namespace BTCPayServer.Hosting
|
||||||
{
|
{
|
||||||
@@ -134,22 +135,18 @@ namespace BTCPayServer.Hosting
|
|||||||
services.TryAddSingleton<StoreRepository>();
|
services.TryAddSingleton<StoreRepository>();
|
||||||
services.TryAddSingleton<BTCPayWallet>();
|
services.TryAddSingleton<BTCPayWallet>();
|
||||||
services.TryAddSingleton<CurrencyNameTable>();
|
services.TryAddSingleton<CurrencyNameTable>();
|
||||||
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClient>())
|
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>())
|
||||||
{
|
{
|
||||||
Fallback = new FeeRate(100, 1),
|
Fallback = new FeeRate(100, 1),
|
||||||
BlockTarget = 20
|
BlockTarget = 20
|
||||||
});
|
});
|
||||||
|
|
||||||
services.TryAddSingleton<NBXplorerWaiterAccessor>();
|
services.AddSingleton<IHostedService, NBXplorerWaiters>();
|
||||||
services.AddSingleton<IHostedService, NBXplorerWaiter>();
|
services.AddSingleton<IHostedService, NBXplorerListener>();
|
||||||
services.TryAddSingleton<ExplorerClient>(o =>
|
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
||||||
{
|
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
||||||
var opts = o.GetRequiredService<BTCPayServerOptions>();
|
|
||||||
var explorer = new ExplorerClient(opts.Network, opts.Explorer);
|
services.TryAddSingleton<ExplorerClientProvider>();
|
||||||
if (!explorer.SetCookieAuth(opts.CookieFile))
|
|
||||||
explorer.SetNoAuth();
|
|
||||||
return explorer;
|
|
||||||
});
|
|
||||||
services.TryAddSingleton<Bitpay>(o =>
|
services.TryAddSingleton<Bitpay>(o =>
|
||||||
{
|
{
|
||||||
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
if (o.GetRequiredService<BTCPayServerOptions>().Network == Network.Main)
|
||||||
@@ -163,11 +160,7 @@ namespace BTCPayServer.Hosting
|
|||||||
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
|
var bitpay = new BitpayRateProvider(new Bitpay(new Key(), new Uri("https://bitpay.com/")));
|
||||||
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
return new CachedRateProvider(new FallbackRateProvider(new IRateProvider[] { coinaverage, bitpay }), o.GetRequiredService<IMemoryCache>()) { CacheSpan = TimeSpan.FromMinutes(1.0) };
|
||||||
});
|
});
|
||||||
|
|
||||||
services.AddSingleton<IHostedService, InvoiceNotificationManager>();
|
|
||||||
|
|
||||||
services.TryAddSingleton<InvoiceWatcherAccessor>();
|
|
||||||
services.AddSingleton<IHostedService, InvoiceWatcher>();
|
|
||||||
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
services.TryAddScoped<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
services.TryAddSingleton<IAuthorizationHandler, OwnStoreHandler>();
|
||||||
services.AddTransient<AccessTokenController>();
|
services.AddTransient<AccessTokenController>();
|
||||||
|
|||||||
@@ -10,43 +10,38 @@ namespace BTCPayServer.Services.Fees
|
|||||||
{
|
{
|
||||||
public class NBXplorerFeeProviderFactory : IFeeProviderFactory
|
public class NBXplorerFeeProviderFactory : IFeeProviderFactory
|
||||||
{
|
{
|
||||||
public NBXplorerFeeProviderFactory(ExplorerClient explorerClient)
|
public NBXplorerFeeProviderFactory(ExplorerClientProvider explorerClients)
|
||||||
{
|
{
|
||||||
if (explorerClient == null)
|
if (explorerClients == null)
|
||||||
throw new ArgumentNullException(nameof(explorerClient));
|
throw new ArgumentNullException(nameof(explorerClients));
|
||||||
_ExplorerClient = explorerClient;
|
_ExplorerClients = explorerClients;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly ExplorerClient _ExplorerClient;
|
private readonly ExplorerClientProvider _ExplorerClients;
|
||||||
public ExplorerClient ExplorerClient
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return _ExplorerClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public FeeRate Fallback { get; set; }
|
public FeeRate Fallback { get; set; }
|
||||||
public int BlockTarget { get; set; }
|
public int BlockTarget { get; set; }
|
||||||
public IFeeProvider CreateFeeProvider(BTCPayNetwork network)
|
public IFeeProvider CreateFeeProvider(BTCPayNetwork network)
|
||||||
{
|
{
|
||||||
return new NBXplorerFeeProvider(this);
|
return new NBXplorerFeeProvider(this, _ExplorerClients.GetExplorerClient(network));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class NBXplorerFeeProvider : IFeeProvider
|
public class NBXplorerFeeProvider : IFeeProvider
|
||||||
{
|
{
|
||||||
public NBXplorerFeeProvider(NBXplorerFeeProviderFactory factory)
|
public NBXplorerFeeProvider(NBXplorerFeeProviderFactory parent, ExplorerClient explorerClient)
|
||||||
{
|
{
|
||||||
if (factory == null)
|
if (explorerClient == null)
|
||||||
throw new ArgumentNullException(nameof(factory));
|
throw new ArgumentNullException(nameof(explorerClient));
|
||||||
_Factory = factory;
|
_Factory = parent;
|
||||||
|
_ExplorerClient = explorerClient;
|
||||||
}
|
}
|
||||||
private readonly NBXplorerFeeProviderFactory _Factory;
|
NBXplorerFeeProviderFactory _Factory;
|
||||||
|
ExplorerClient _ExplorerClient;
|
||||||
public async Task<FeeRate> GetFeeRateAsync()
|
public async Task<FeeRate> GetFeeRateAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return (await _Factory.ExplorerClient.GetFeeRateAsync(_Factory.BlockTarget).ConfigureAwait(false)).FeeRate;
|
return (await _ExplorerClient.GetFeeRateAsync(_Factory.BlockTarget).ConfigureAwait(false)).FeeRate;
|
||||||
}
|
}
|
||||||
catch (NBXplorerException ex) when (ex.Error.HttpCode == 400 && ex.Error.Code == "fee-estimation-unavailable")
|
catch (NBXplorerException ex) when (ex.Error.HttpCode == 400 && ex.Error.Code == "fee-estimation-unavailable")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,11 +25,10 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
}
|
}
|
||||||
public class BTCPayWallet
|
public class BTCPayWallet
|
||||||
{
|
{
|
||||||
private ExplorerClient _Client;
|
private ExplorerClientProvider _Client;
|
||||||
private Serializer _Serializer;
|
|
||||||
ApplicationDbContextFactory _DBFactory;
|
ApplicationDbContextFactory _DBFactory;
|
||||||
|
|
||||||
public BTCPayWallet(ExplorerClient client, ApplicationDbContextFactory factory)
|
public BTCPayWallet(ExplorerClientProvider client, ApplicationDbContextFactory factory)
|
||||||
{
|
{
|
||||||
if (client == null)
|
if (client == null)
|
||||||
throw new ArgumentNullException(nameof(client));
|
throw new ArgumentNullException(nameof(client));
|
||||||
@@ -37,31 +36,32 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
throw new ArgumentNullException(nameof(factory));
|
throw new ArgumentNullException(nameof(factory));
|
||||||
_Client = client;
|
_Client = client;
|
||||||
_DBFactory = factory;
|
_DBFactory = factory;
|
||||||
_Serializer = new NBXplorer.Serializer(_Client.Network);
|
|
||||||
LongPollingMode = client.Network == Network.RegTest;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategy derivationStrategy)
|
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategy derivationStrategy)
|
||||||
{
|
{
|
||||||
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy.DerivationStrategyBase, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
var client = _Client.GetExplorerClient(derivationStrategy.Network);
|
||||||
return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network);
|
var pathInfo = await client.GetUnusedAsync(derivationStrategy.DerivationStrategyBase, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
||||||
|
return pathInfo.ScriptPubKey.GetDestinationAddress(client.Network);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task TrackAsync(DerivationStrategy derivationStrategy)
|
public async Task TrackAsync(DerivationStrategy derivationStrategy)
|
||||||
{
|
{
|
||||||
await _Client.TrackAsync(derivationStrategy.DerivationStrategyBase);
|
var client = _Client.GetExplorerClient(derivationStrategy.Network);
|
||||||
|
await client.TrackAsync(derivationStrategy.DerivationStrategyBase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
|
public Task<TransactionResult> GetTransactionAsync(BTCPayNetwork network, uint256 txId, CancellationToken cancellation = default(CancellationToken))
|
||||||
{
|
{
|
||||||
return _Client.GetTransactionAsync(txId, cancellation);
|
var client = _Client.GetExplorerClient(network);
|
||||||
|
return client.GetTransactionAsync(txId, cancellation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool LongPollingMode { get; set; }
|
|
||||||
public async Task<GetCoinsResult> GetCoins(DerivationStrategy strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
|
public async Task<GetCoinsResult> GetCoins(DerivationStrategy strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
|
||||||
{
|
{
|
||||||
var changes = await _Client.SyncAsync(strategy.DerivationStrategyBase, state?.ConfirmedHash, state?.UnconfirmedHash, !LongPollingMode, cancellation).ConfigureAwait(false);
|
var client = _Client.GetExplorerClient(strategy.Network);
|
||||||
|
var changes = await client.SyncAsync(strategy.DerivationStrategyBase, state?.ConfirmedHash, state?.UnconfirmedHash, true, cancellation).ConfigureAwait(false);
|
||||||
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => c.AsCoin()).ToArray();
|
var utxos = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => c.AsCoin()).ToArray();
|
||||||
return new GetCoinsResult()
|
return new GetCoinsResult()
|
||||||
{
|
{
|
||||||
@@ -71,20 +71,17 @@ namespace BTCPayServer.Services.Wallets
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] ToBytes<T>(T obj)
|
public Task BroadcastTransactionsAsync(BTCPayNetwork network, List<Transaction> transactions)
|
||||||
{
|
{
|
||||||
return ZipUtils.Zip(_Serializer.ToString(obj));
|
var client = _Client.GetExplorerClient(network);
|
||||||
}
|
var tasks = transactions.Select(t => client.BroadcastAsync(t)).ToArray();
|
||||||
|
|
||||||
public Task BroadcastTransactionsAsync(List<Transaction> transactions)
|
|
||||||
{
|
|
||||||
var tasks = transactions.Select(t => _Client.BroadcastAsync(t)).ToArray();
|
|
||||||
return Task.WhenAll(tasks);
|
return Task.WhenAll(tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Money> GetBalance(DerivationStrategy derivationStrategy)
|
public async Task<Money> GetBalance(DerivationStrategy derivationStrategy)
|
||||||
{
|
{
|
||||||
var result = await _Client.SyncAsync(derivationStrategy.DerivationStrategyBase, null, true);
|
var client = _Client.GetExplorerClient(derivationStrategy.Network);
|
||||||
|
var result = await client.SyncAsync(derivationStrategy.DerivationStrategyBase, null, true);
|
||||||
return result.Confirmed.UTXOs.Select(u => u.Value)
|
return result.Confirmed.UTXOs.Select(u => u.Value)
|
||||||
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Value))
|
.Concat(result.Unconfirmed.UTXOs.Select(u => u.Value))
|
||||||
.Sum();
|
.Sum();
|
||||||
|
|||||||
Reference in New Issue
Block a user