BTCWallet is single currency, introduce BTCWalletProvider

This commit is contained in:
nicolas.dorier
2018-01-11 14:36:12 +09:00
parent 3ff293ab7f
commit 55d50af39d
8 changed files with 146 additions and 68 deletions

View File

@@ -66,13 +66,17 @@ namespace BTCPayServer.Controllers
public async Task<IActionResult> PostPayment(string invoiceId, string cryptoCode = null) 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())
return NotFound();
if (cryptoCode == null) if (cryptoCode == null)
cryptoCode = "BTC"; cryptoCode = "BTC";
var network = _NetworkProvider.GetNetwork(cryptoCode); var network = _NetworkProvider.GetNetwork(cryptoCode);
if (network == null || invoice == null || invoice.IsExpired() || !invoice.Support(network))
return NotFound();
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
return NotFound();
var payment = PaymentMessage.Load(Request.Body); var payment = PaymentMessage.Load(Request.Body);
var unused = _Wallet.BroadcastTransactionsAsync(network, payment.Transactions); var unused = wallet.BroadcastTransactionsAsync(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..."));
} }

View File

@@ -165,7 +165,7 @@ namespace BTCPayServer.Controllers
}).ToList() }).ToList()
}; };
var isMultiCurrency = invoice.Payments.Select(p=>p.GetCryptoCode()).Concat(new[] { network.CryptoCode }).Distinct().Count() > 1; var isMultiCurrency = invoice.GetPayments().Select(p=>p.GetCryptoCode()).Concat(new[] { network.CryptoCode }).Distinct().Count() > 1;
if (isMultiCurrency) if (isMultiCurrency)
model.NetworkFeeDescription = $"{accounting.NetworkFee} {network.CryptoCode}"; model.NetworkFeeDescription = $"{accounting.NetworkFee} {network.CryptoCode}";

View File

@@ -45,7 +45,7 @@ namespace BTCPayServer.Controllers
public partial class InvoiceController : Controller public partial class InvoiceController : Controller
{ {
InvoiceRepository _InvoiceRepository; InvoiceRepository _InvoiceRepository;
BTCPayWallet _Wallet; BTCPayWalletProvider _WalletProvider;
IRateProviderFactory _RateProviders; IRateProviderFactory _RateProviders;
StoreRepository _StoreRepository; StoreRepository _StoreRepository;
UserManager<ApplicationUser> _UserManager; UserManager<ApplicationUser> _UserManager;
@@ -57,7 +57,7 @@ namespace BTCPayServer.Controllers
public InvoiceController(InvoiceRepository invoiceRepository, public InvoiceController(InvoiceRepository invoiceRepository,
CurrencyNameTable currencyNameTable, CurrencyNameTable currencyNameTable,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
BTCPayWallet wallet, BTCPayWalletProvider walletProvider,
IRateProviderFactory rateProviders, IRateProviderFactory rateProviders,
StoreRepository storeRepository, StoreRepository storeRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
@@ -69,7 +69,7 @@ namespace BTCPayServer.Controllers
_CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable)); _CurrencyNameTable = currencyNameTable ?? throw new ArgumentNullException(nameof(currencyNameTable));
_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)); _WalletProvider = walletProvider ?? throw new ArgumentNullException(nameof(walletProvider));
_RateProviders = rateProviders ?? throw new ArgumentNullException(nameof(rateProviders)); _RateProviders = rateProviders ?? throw new ArgumentNullException(nameof(rateProviders));
_UserManager = userManager; _UserManager = userManager;
_FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory)); _FeeProviderFactory = feeProviderFactory ?? throw new ArgumentNullException(nameof(feeProviderFactory));
@@ -116,14 +116,20 @@ namespace BTCPayServer.Controllers
entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy); entity.SpeedPolicy = ParseSpeedPolicy(invoice.TransactionSpeed, store.SpeedPolicy);
var queries = derivationStrategies var queries = derivationStrategies
.Select(derivationStrategy => .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 && _.RateProvider != null)
.Select(_ =>
{ {
return new return new
{ {
network = derivationStrategy.Network, network = _.Network,
getFeeRate = _FeeProviderFactory.CreateFeeProvider(derivationStrategy.Network).GetFeeRateAsync(), getFeeRate = _.FeeRateProvider.GetFeeRateAsync(),
getRate = _RateProviders.GetRateProvider(derivationStrategy.Network).GetRateAsync(invoice.Currency), getRate = _.RateProvider.GetRateAsync(invoice.Currency),
getAddress = _Wallet.ReserveAddressAsync(derivationStrategy) getAddress = _.Wallet.ReserveAddressAsync(_.DerivationStrategy)
}; };
}); });

View File

@@ -32,7 +32,7 @@ namespace BTCPayServer.Controllers
TokenRepository tokenRepo, TokenRepository tokenRepo,
UserManager<ApplicationUser> userManager, UserManager<ApplicationUser> userManager,
AccessTokenController tokenController, AccessTokenController tokenController,
BTCPayWallet wallet, BTCPayWalletProvider walletProvider,
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
ExplorerClientProvider explorerProvider, ExplorerClientProvider explorerProvider,
IHostingEnvironment env) IHostingEnvironment env)
@@ -41,14 +41,14 @@ namespace BTCPayServer.Controllers
_TokenRepository = tokenRepo; _TokenRepository = tokenRepo;
_UserManager = userManager; _UserManager = userManager;
_TokenController = tokenController; _TokenController = tokenController;
_Wallet = wallet; _WalletProvider = walletProvider;
_Env = env; _Env = env;
_NetworkProvider = networkProvider; _NetworkProvider = networkProvider;
_ExplorerProvider = explorerProvider; _ExplorerProvider = explorerProvider;
} }
BTCPayNetworkProvider _NetworkProvider; BTCPayNetworkProvider _NetworkProvider;
private ExplorerClientProvider _ExplorerProvider; private ExplorerClientProvider _ExplorerProvider;
BTCPayWallet _Wallet; BTCPayWalletProvider _WalletProvider;
AccessTokenController _TokenController; AccessTokenController _TokenController;
StoreRepository _Repo; StoreRepository _Repo;
TokenRepository _TokenRepository; TokenRepository _TokenRepository;
@@ -95,7 +95,10 @@ namespace BTCPayServer.Controllers
var stores = await _Repo.GetStoresByUserId(GetUserId()); var stores = await _Repo.GetStoresByUserId(GetUserId());
var balances = stores var balances = stores
.Select(s => s.GetDerivationStrategies(_NetworkProvider) .Select(s => s.GetDerivationStrategies(_NetworkProvider)
.Select(async ss => (await _Wallet.GetBalance(ss)).ToString() + " " + ss.Network.CryptoCode)) .Select(d => (Wallet: _WalletProvider.GetWallet(d.Network),
DerivationStrategy: d.DerivationStrategyBase))
.Where(_ => _.Wallet != null)
.Select(async _ => (await _.Wallet.GetBalance(_.DerivationStrategy)).ToString() + " " + _.Wallet.Network.CryptoCode))
.ToArray(); .ToArray();
await Task.WhenAll(balances.SelectMany(_ => _)); await Task.WhenAll(balances.SelectMany(_ => _));
@@ -210,6 +213,12 @@ namespace BTCPayServer.Controllers
ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network"); ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network");
return View(vm); return View(vm);
} }
var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
{
ModelState.AddModelError(nameof(vm.CryptoCurrency), "Invalid network");
return View(vm);
}
if (command == "Save") if (command == "Save")
{ {
@@ -218,7 +227,7 @@ namespace BTCPayServer.Controllers
if (!string.IsNullOrEmpty(vm.DerivationScheme)) if (!string.IsNullOrEmpty(vm.DerivationScheme))
{ {
var strategy = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network); var strategy = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network);
await _Wallet.TrackAsync(strategy); await wallet.TrackAsync(strategy);
vm.DerivationScheme = strategy.ToString(); vm.DerivationScheme = strategy.ToString();
} }
store.SetDerivationStrategy(network, vm.DerivationScheme); store.SetDerivationStrategy(network, vm.DerivationScheme);
@@ -240,12 +249,12 @@ namespace BTCPayServer.Controllers
try try
{ {
var scheme = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network); var scheme = ParseDerivationStrategy(vm.DerivationScheme, vm.DerivationSchemeFormat, network);
var line = scheme.DerivationStrategyBase.GetLineFor(DerivationFeature.Deposit); var line = scheme.GetLineFor(DerivationFeature.Deposit);
for (int i = 0; i < 10; i++) for (int i = 0; i < 10; i++)
{ {
var address = line.Derive((uint)i); var address = line.Derive((uint)i);
vm.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(scheme.Network.NBitcoinNetwork).ToString())); vm.AddressSamples.Add((line.Path.Derive((uint)i).ToString(), address.ScriptPubKey.GetDestinationAddress(network.NBitcoinNetwork).ToString()));
} }
} }
catch catch
@@ -315,7 +324,7 @@ namespace BTCPayServer.Controllers
}); });
} }
private DerivationStrategy ParseDerivationStrategy(string derivationScheme, string format, BTCPayNetwork network) private DerivationStrategyBase ParseDerivationStrategy(string derivationScheme, string format, BTCPayNetwork network)
{ {
if (format == "Electrum") if (format == "Electrum")
{ {
@@ -349,7 +358,7 @@ namespace BTCPayServer.Controllers
} }
} }
return DerivationStrategy.Parse(new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationScheme).ToString(), network); return new DerivationStrategyFactory(network.NBitcoinNetwork).Parse(derivationScheme);
} }
[HttpGet] [HttpGet]

View File

@@ -45,7 +45,7 @@ namespace BTCPayServer.HostedServices
InvoiceRepository _InvoiceRepository; InvoiceRepository _InvoiceRepository;
EventAggregator _EventAggregator; EventAggregator _EventAggregator;
BTCPayWallet _Wallet; BTCPayWalletProvider _WalletProvider;
BTCPayNetworkProvider _NetworkProvider; BTCPayNetworkProvider _NetworkProvider;
public InvoiceWatcher( public InvoiceWatcher(
@@ -53,10 +53,10 @@ namespace BTCPayServer.HostedServices
BTCPayNetworkProvider networkProvider, BTCPayNetworkProvider networkProvider,
InvoiceRepository invoiceRepository, InvoiceRepository invoiceRepository,
EventAggregator eventAggregator, EventAggregator eventAggregator,
BTCPayWallet wallet) BTCPayWalletProvider walletProvider)
{ {
PollInterval = TimeSpan.FromMinutes(1.0); PollInterval = TimeSpan.FromMinutes(1.0);
_Wallet = wallet ?? throw new ArgumentNullException(nameof(wallet)); _WalletProvider = walletProvider ?? throw new ArgumentNullException(nameof(walletProvider));
_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;
@@ -150,29 +150,39 @@ namespace BTCPayServer.HostedServices
} }
var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray(); var derivationStrategies = invoice.GetDerivationStrategies(_NetworkProvider).ToArray();
foreach (NetworkCoins coins in await GetCoinsPerNetwork(context, invoice, derivationStrategies)) var payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
foreach (Task<NetworkCoins> coinsAsync in GetCoinsPerNetwork(context, invoice, derivationStrategies))
{ {
var coins = await coinsAsync;
if (coins.TimestampedCoins.Length == 0)
continue;
bool dirtyAddress = false; bool dirtyAddress = false;
if (coins.State != null) if (coins.State != null)
context.ModifiedKnownStates.AddOrReplace(coins.Strategy.Network, coins.State); context.ModifiedKnownStates.AddOrReplace(coins.Wallet.Network, coins.State);
var alreadyAccounted = new HashSet<OutPoint>(invoice.GetPayments(coins.Strategy.Network).Select(p => p.Outpoint)); var alreadyAccounted = new HashSet<OutPoint>(invoice.GetPayments(coins.Wallet.Network).Select(p => p.Outpoint));
foreach (var coin in coins.TimestampedCoins.Where(c => !alreadyAccounted.Contains(c.Coin.Outpoint))) foreach (var coin in coins.TimestampedCoins.Where(c => !alreadyAccounted.Contains(c.Coin.Outpoint)))
{ {
var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.DateTime, coin.Coin, coins.Strategy.Network.CryptoCode).ConfigureAwait(false); var payment = await _InvoiceRepository.AddPayment(invoice.Id, coin.DateTime, coin.Coin, coins.Wallet.Network.CryptoCode).ConfigureAwait(false);
#pragma warning disable CS0618 #pragma warning disable CS0618
invoice.Payments.Add(payment); invoice.Payments.Add(payment);
#pragma warning restore CS0618 #pragma warning restore CS0618
alreadyAccounted.Add(coin.Coin.Outpoint);
context.Events.Add(new InvoicePaymentEvent(invoice.Id)); context.Events.Add(new InvoicePaymentEvent(invoice.Id));
dirtyAddress = true; dirtyAddress = true;
} }
var network = coins.Strategy.Network; if (dirtyAddress)
{
payments = await GetPaymentsWithTransaction(derivationStrategies, invoice);
}
var network = coins.Wallet.Network;
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();
if (invoice.Status == "new" || invoice.Status == "expired") if (invoice.Status == "new" || invoice.Status == "expired")
{ {
var totalPaid = (await GetPaymentsWithTransaction(derivationStrategies, invoice)).Select(p => p.Payment.GetValue(cryptoDataAll, cryptoData.CryptoCode)).Sum(); var totalPaid = payments.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")
@@ -203,7 +213,7 @@ namespace BTCPayServer.HostedServices
context.MarkDirty(); context.MarkDirty();
if (dirtyAddress) if (dirtyAddress)
{ {
var address = await _Wallet.ReserveAddressAsync(coins.Strategy); var address = await coins.Wallet.ReserveAddressAsync(coins.Strategy);
Logs.PayServer.LogInformation("Generate new " + address); Logs.PayServer.LogInformation("Generate new " + address);
await _InvoiceRepository.NewAddress(invoice.Id, address, network); await _InvoiceRepository.NewAddress(invoice.Id, address, network);
} }
@@ -212,7 +222,7 @@ namespace BTCPayServer.HostedServices
if (invoice.Status == "paid") if (invoice.Status == "paid")
{ {
var transactions = await GetPaymentsWithTransaction(derivationStrategies, invoice); var transactions = payments;
if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed) if (invoice.SpeedPolicy == SpeedPolicy.HighSpeed)
{ {
transactions = transactions.Where(t => t.Confirmations >= 1 || !t.Transaction.RBF); transactions = transactions.Where(t => t.Confirmations >= 1 || !t.Transaction.RBF);
@@ -250,7 +260,7 @@ namespace BTCPayServer.HostedServices
if (invoice.Status == "confirmed") if (invoice.Status == "confirmed")
{ {
var transactions = await GetPaymentsWithTransaction(derivationStrategies, invoice); var transactions = payments;
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)
@@ -263,18 +273,23 @@ namespace BTCPayServer.HostedServices
} }
} }
private async Task<IEnumerable<NetworkCoins>> GetCoinsPerNetwork(UpdateInvoiceContext context, InvoiceEntity invoice, DerivationStrategy[] strategies) private IEnumerable<Task<NetworkCoins>> GetCoinsPerNetwork(UpdateInvoiceContext context, InvoiceEntity invoice, DerivationStrategy[] strategies)
{ {
var getCoinsResponsesAsync = strategies return strategies
.Select(d => _Wallet.GetCoins(d, context.KnownStates.TryGet(d.Network))) .Select(d => (Wallet: _WalletProvider.GetWallet(d.Network),
Network: d.Network,
Strategy: d.DerivationStrategyBase))
.Where(d => d.Wallet != null)
.Select(d => (Network: d.Network,
Coins: d.Wallet.GetCoins(d.Strategy, context.KnownStates.TryGet(d.Network))))
.Select(async d =>
{
var coins = await d.Coins;
// Keep only coins from the invoice
coins.TimestampedCoins = coins.TimestampedCoins.Where(c => invoice.AvailableAddressHashes.Contains(c.Coin.ScriptPubKey.Hash.ToString() + d.Network.CryptoCode)).ToArray();
return coins;
})
.ToArray(); .ToArray();
await Task.WhenAll(getCoinsResponsesAsync);
var getCoinsResponses = getCoinsResponsesAsync.Select(g => g.Result).ToArray();
foreach (var response in getCoinsResponses)
{
response.TimestampedCoins = response.TimestampedCoins.Where(c => invoice.AvailableAddressHashes.Contains(c.Coin.ScriptPubKey.Hash.ToString() + response.Strategy.Network.CryptoCode)).ToArray();
}
return getCoinsResponses.Where(s => s.TimestampedCoins.Length != 0).ToArray();
} }
private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(DerivationStrategy[] derivations, InvoiceEntity invoice) private async Task<IEnumerable<AccountedPaymentEntity>> GetPaymentsWithTransaction(DerivationStrategy[] derivations, InvoiceEntity invoice)
@@ -283,7 +298,10 @@ namespace BTCPayServer.HostedServices
List<AccountedPaymentEntity> accountedPayments = new List<AccountedPaymentEntity>(); List<AccountedPaymentEntity> accountedPayments = new List<AccountedPaymentEntity>();
foreach (var network in derivations.Select(d => d.Network)) foreach (var network in derivations.Select(d => d.Network))
{ {
var transactions = await _Wallet.GetTransactions(network, invoice.GetPayments(network).Select(t => t.Outpoint.Hash).ToArray()); var wallet = _WalletProvider.GetWallet(network);
if (wallet == null)
continue;
var transactions = await wallet.GetTransactions(network, invoice.GetPayments(network).Select(t => t.Outpoint.Hash).ToArray());
var conflicts = GetConflicts(transactions.Select(t => t.Value)); var conflicts = GetConflicts(transactions.Select(t => t.Value));
foreach (var payment in invoice.GetPayments(network)) foreach (var payment in invoice.GetPayments(network))
{ {

View File

@@ -134,7 +134,7 @@ namespace BTCPayServer.Hosting
services.TryAddSingleton<NBXplorerDashboard>(); services.TryAddSingleton<NBXplorerDashboard>();
services.TryAddSingleton<StoreRepository>(); services.TryAddSingleton<StoreRepository>();
services.TryAddSingleton<BTCPayWallet>(); services.TryAddSingleton<BTCPayWalletProvider>();
services.TryAddSingleton<CurrencyNameTable>(); services.TryAddSingleton<CurrencyNameTable>();
services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>()) services.TryAddSingleton<IFeeProviderFactory>(o => new NBXplorerFeeProviderFactory(o.GetRequiredService<ExplorerClientProvider>())
{ {

View File

@@ -26,31 +26,40 @@ namespace BTCPayServer.Services.Wallets
} }
public TimestampedCoin[] TimestampedCoins { get; set; } public TimestampedCoin[] TimestampedCoins { get; set; }
public KnownState State { get; set; } public KnownState State { get; set; }
public DerivationStrategy Strategy { get; set; } public DerivationStrategyBase Strategy { get; set; }
public BTCPayWallet Wallet { get; set; }
} }
public class BTCPayWallet public class BTCPayWallet
{ {
private ExplorerClientProvider _Client; private ExplorerClient _Client;
public BTCPayWallet(ExplorerClientProvider client) public BTCPayWallet(ExplorerClient client, BTCPayNetwork network)
{ {
if (client == null) if (client == null)
throw new ArgumentNullException(nameof(client)); throw new ArgumentNullException(nameof(client));
_Client = client; _Client = client;
_Network = network;
} }
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategy derivationStrategy) private readonly BTCPayNetwork _Network;
public BTCPayNetwork Network
{ {
var client = _Client.GetExplorerClient(derivationStrategy.Network); get
var pathInfo = await client.GetUnusedAsync(derivationStrategy.DerivationStrategyBase, DerivationFeature.Deposit, 0, true).ConfigureAwait(false); {
return pathInfo.ScriptPubKey.GetDestinationAddress(client.Network); return _Network;
}
} }
public async Task TrackAsync(DerivationStrategy derivationStrategy) public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
{ {
var client = _Client.GetExplorerClient(derivationStrategy.Network); var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
await client.TrackAsync(derivationStrategy.DerivationStrategyBase); return pathInfo.ScriptPubKey.GetDestinationAddress(_Client.Network);
}
public async Task TrackAsync(DerivationStrategyBase derivationStrategy)
{
await _Client.TrackAsync(derivationStrategy);
} }
public Task<TransactionResult> GetTransactionAsync(BTCPayNetwork network, uint256 txId, CancellationToken cancellation = default(CancellationToken)) public Task<TransactionResult> GetTransactionAsync(BTCPayNetwork network, uint256 txId, CancellationToken cancellation = default(CancellationToken))
@@ -59,36 +68,31 @@ namespace BTCPayServer.Services.Wallets
throw new ArgumentNullException(nameof(network)); throw new ArgumentNullException(nameof(network));
if (txId == null) if (txId == null)
throw new ArgumentNullException(nameof(txId)); throw new ArgumentNullException(nameof(txId));
var client = _Client.GetExplorerClient(network); return _Client.GetTransactionAsync(txId, cancellation);
return client.GetTransactionAsync(txId, cancellation);
} }
public async Task<NetworkCoins> GetCoins(DerivationStrategy strategy, KnownState state, CancellationToken cancellation = default(CancellationToken)) public async Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
{ {
var client = _Client.GetExplorerClient(strategy.Network); var changes = await _Client.SyncAsync(strategy, state?.ConfirmedHash, state?.UnconfirmedHash, true, cancellation).ConfigureAwait(false);
if (client == null)
return new NetworkCoins() { TimestampedCoins = new NetworkCoins.TimestampedCoin[0], State = null, Strategy = strategy };
var changes = await client.SyncAsync(strategy.DerivationStrategyBase, state?.ConfirmedHash, state?.UnconfirmedHash, true, cancellation).ConfigureAwait(false);
return new NetworkCoins() return new NetworkCoins()
{ {
TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(), TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(),
State = new KnownState() { ConfirmedHash = changes.Confirmed.Hash, UnconfirmedHash = changes.Unconfirmed.Hash }, State = new KnownState() { ConfirmedHash = changes.Confirmed.Hash, UnconfirmedHash = changes.Unconfirmed.Hash },
Strategy = strategy, Strategy = strategy,
Wallet = this
}; };
} }
public Task BroadcastTransactionsAsync(BTCPayNetwork network, List<Transaction> transactions) public Task BroadcastTransactionsAsync(List<Transaction> transactions)
{ {
var client = _Client.GetExplorerClient(network); var tasks = transactions.Select(t => _Client.BroadcastAsync(t)).ToArray();
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(DerivationStrategyBase derivationStrategy)
{ {
var client = _Client.GetExplorerClient(derivationStrategy.Network); var result = await _Client.SyncAsync(derivationStrategy, null, true);
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();

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace BTCPayServer.Services.Wallets
{
public class BTCPayWalletProvider
{
private ExplorerClientProvider _Client;
BTCPayNetworkProvider _NetworkProvider;
public BTCPayWalletProvider(ExplorerClientProvider client, BTCPayNetworkProvider networkProvider)
{
if (client == null)
throw new ArgumentNullException(nameof(client));
_Client = client;
_NetworkProvider = networkProvider;
}
public BTCPayWallet GetWallet(BTCPayNetwork network)
{
if (network == null)
throw new ArgumentNullException(nameof(network));
return GetWallet(network.CryptoCode);
}
public BTCPayWallet GetWallet(string cryptoCode)
{
if (cryptoCode == null)
throw new ArgumentNullException(nameof(cryptoCode));
var network = _NetworkProvider.GetNetwork(cryptoCode);
var client = _Client.GetExplorerClient(cryptoCode);
if (network == null && client == null)
return null;
return new BTCPayWallet(client, network);
}
}
}