2017-10-06 10:37:38 +09:00
|
|
|
|
using NBitcoin;
|
2017-09-13 15:47:34 +09:00
|
|
|
|
using NBXplorer;
|
|
|
|
|
using NBXplorer.DerivationStrategy;
|
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Text;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
2017-10-06 10:37:38 +09:00
|
|
|
|
using BTCPayServer.Data;
|
2018-01-07 02:16:42 +09:00
|
|
|
|
using System.Threading;
|
|
|
|
|
using NBXplorer.Models;
|
2018-01-11 17:29:48 +09:00
|
|
|
|
using Microsoft.Extensions.Caching.Memory;
|
2017-09-13 15:47:34 +09:00
|
|
|
|
|
2017-09-15 16:06:57 +09:00
|
|
|
|
namespace BTCPayServer.Services.Wallets
|
2017-09-13 15:47:34 +09:00
|
|
|
|
{
|
2018-01-07 02:16:42 +09:00
|
|
|
|
public class KnownState
|
|
|
|
|
{
|
2018-01-17 15:02:53 +09:00
|
|
|
|
public UTXOChanges PreviousCall { get; set; }
|
2018-01-07 02:16:42 +09:00
|
|
|
|
}
|
2018-01-10 15:43:07 +09:00
|
|
|
|
public class NetworkCoins
|
2018-01-07 02:16:42 +09:00
|
|
|
|
{
|
2018-01-10 18:30:45 +09:00
|
|
|
|
public class TimestampedCoin
|
|
|
|
|
{
|
|
|
|
|
public DateTimeOffset DateTime { get; set; }
|
|
|
|
|
public Coin Coin { get; set; }
|
|
|
|
|
}
|
|
|
|
|
public TimestampedCoin[] TimestampedCoins { get; set; }
|
2018-01-07 02:16:42 +09:00
|
|
|
|
public KnownState State { get; set; }
|
2018-01-11 14:36:12 +09:00
|
|
|
|
public DerivationStrategyBase Strategy { get; set; }
|
|
|
|
|
public BTCPayWallet Wallet { get; set; }
|
2018-01-07 02:16:42 +09:00
|
|
|
|
}
|
2017-10-27 17:53:04 +09:00
|
|
|
|
public class BTCPayWallet
|
|
|
|
|
{
|
2018-01-11 14:36:12 +09:00
|
|
|
|
private ExplorerClient _Client;
|
2018-02-15 12:42:48 +09:00
|
|
|
|
public BTCPayWallet(ExplorerClient client, BTCPayNetwork network)
|
2017-10-27 17:53:04 +09:00
|
|
|
|
{
|
|
|
|
|
if (client == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(client));
|
|
|
|
|
_Client = client;
|
2018-01-11 14:36:12 +09:00
|
|
|
|
_Network = network;
|
2017-10-27 17:53:04 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2018-01-11 14:36:12 +09:00
|
|
|
|
private readonly BTCPayNetwork _Network;
|
|
|
|
|
public BTCPayNetwork Network
|
2017-10-27 17:53:04 +09:00
|
|
|
|
{
|
2018-01-11 14:36:12 +09:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _Network;
|
|
|
|
|
}
|
2017-10-27 17:53:04 +09:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 17:29:48 +09:00
|
|
|
|
public TimeSpan CacheSpan { get; private set; } = TimeSpan.FromMinutes(60);
|
|
|
|
|
|
2018-01-11 14:36:12 +09:00
|
|
|
|
public async Task<BitcoinAddress> ReserveAddressAsync(DerivationStrategyBase derivationStrategy)
|
2017-10-27 17:53:04 +09:00
|
|
|
|
{
|
2018-01-13 02:28:23 +09:00
|
|
|
|
if (derivationStrategy == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(derivationStrategy));
|
2018-01-11 14:36:12 +09:00
|
|
|
|
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Deposit, 0, true).ConfigureAwait(false);
|
2018-01-13 02:28:23 +09:00
|
|
|
|
// 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);
|
|
|
|
|
}
|
2018-01-12 11:54:57 +09:00
|
|
|
|
return pathInfo.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork);
|
2018-01-11 14:36:12 +09:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-13 03:27:36 +09:00
|
|
|
|
public async Task<(BitcoinAddress, KeyPath)> GetChangeAddressAsync(DerivationStrategyBase derivationStrategy)
|
|
|
|
|
{
|
|
|
|
|
if (derivationStrategy == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(derivationStrategy));
|
|
|
|
|
var pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Change, 0, false).ConfigureAwait(false);
|
|
|
|
|
// Might happen on some broken install
|
|
|
|
|
if (pathInfo == null)
|
|
|
|
|
{
|
|
|
|
|
await _Client.TrackAsync(derivationStrategy).ConfigureAwait(false);
|
|
|
|
|
pathInfo = await _Client.GetUnusedAsync(derivationStrategy, DerivationFeature.Change, 0, false).ConfigureAwait(false);
|
|
|
|
|
}
|
|
|
|
|
return (pathInfo.ScriptPubKey.GetDestinationAddress(Network.NBitcoinNetwork), pathInfo.KeyPath);
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 14:36:12 +09:00
|
|
|
|
public async Task TrackAsync(DerivationStrategyBase derivationStrategy)
|
|
|
|
|
{
|
|
|
|
|
await _Client.TrackAsync(derivationStrategy);
|
2017-10-27 17:53:04 +09:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 21:01:00 +09:00
|
|
|
|
public async Task<TransactionResult> GetTransactionAsync(uint256 txId, CancellationToken cancellation = default(CancellationToken))
|
2018-01-07 02:16:42 +09:00
|
|
|
|
{
|
2018-01-10 18:30:45 +09:00
|
|
|
|
if (txId == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(txId));
|
2018-02-15 12:42:48 +09:00
|
|
|
|
var tx = await _Client.GetTransactionAsync(txId, cancellation);
|
2018-01-11 21:01:00 +09:00
|
|
|
|
return tx;
|
2018-01-07 02:16:42 +09:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-11 14:36:12 +09:00
|
|
|
|
public async Task<NetworkCoins> GetCoins(DerivationStrategyBase strategy, KnownState state, CancellationToken cancellation = default(CancellationToken))
|
2018-01-07 02:16:42 +09:00
|
|
|
|
{
|
2018-01-17 15:02:53 +09:00
|
|
|
|
var changes = await _Client.GetUTXOsAsync(strategy, state?.PreviousCall, false, cancellation).ConfigureAwait(false);
|
2018-01-10 15:43:07 +09:00
|
|
|
|
return new NetworkCoins()
|
2018-01-07 02:16:42 +09:00
|
|
|
|
{
|
2018-01-10 18:30:45 +09:00
|
|
|
|
TimestampedCoins = changes.Confirmed.UTXOs.Concat(changes.Unconfirmed.UTXOs).Select(c => new NetworkCoins.TimestampedCoin() { Coin = c.AsCoin(), DateTime = c.Timestamp }).ToArray(),
|
2018-01-17 15:02:53 +09:00
|
|
|
|
State = new KnownState() { PreviousCall = changes },
|
2018-01-07 02:16:42 +09:00
|
|
|
|
Strategy = strategy,
|
2018-01-11 14:36:12 +09:00
|
|
|
|
Wallet = this
|
2018-01-07 02:16:42 +09:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-13 03:27:36 +09:00
|
|
|
|
public Task<BroadcastResult[]> BroadcastTransactionsAsync(List<Transaction> transactions)
|
2017-10-27 17:53:04 +09:00
|
|
|
|
{
|
2018-01-11 14:36:12 +09:00
|
|
|
|
var tasks = transactions.Select(t => _Client.BroadcastAsync(t)).ToArray();
|
2017-10-27 17:53:04 +09:00
|
|
|
|
return Task.WhenAll(tasks);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-13 16:57:40 +09:00
|
|
|
|
public async Task<(Coin[], Dictionary<Script, KeyPath>)> GetUnspentCoins(DerivationStrategyBase derivationStrategy, CancellationToken cancellation = default(CancellationToken))
|
2018-02-13 03:27:36 +09:00
|
|
|
|
{
|
|
|
|
|
var changes = await _Client.GetUTXOsAsync(derivationStrategy, null, false, cancellation).ConfigureAwait(false);
|
2018-02-13 16:57:40 +09:00
|
|
|
|
var keyPaths = new Dictionary<Script, KeyPath>();
|
2018-02-13 03:27:36 +09:00
|
|
|
|
foreach (var coin in changes.GetUnspentUTXOs())
|
|
|
|
|
{
|
2018-02-13 16:57:40 +09:00
|
|
|
|
keyPaths.TryAdd(coin.ScriptPubKey, coin.KeyPath);
|
2018-02-13 03:27:36 +09:00
|
|
|
|
}
|
|
|
|
|
return (changes.GetUnspentCoins(), keyPaths);
|
|
|
|
|
}
|
2018-01-10 15:43:07 +09:00
|
|
|
|
|
2018-01-11 14:36:12 +09:00
|
|
|
|
public async Task<Money> GetBalance(DerivationStrategyBase derivationStrategy)
|
2017-10-27 17:53:04 +09:00
|
|
|
|
{
|
2018-01-17 15:02:53 +09:00
|
|
|
|
var result = await _Client.GetUTXOsAsync(derivationStrategy, null, true);
|
2018-02-13 03:27:36 +09:00
|
|
|
|
return result.GetUnspentUTXOs().Select(c => c.Value).Sum();
|
2017-10-27 17:53:04 +09:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-09-13 15:47:34 +09:00
|
|
|
|
}
|