Files
btcpayserver/BTCPayServer/Services/Wallets/WalletReceiveService.cs
Andrew Camilleri cdfdad3e3d GreenField API: Wallet API (#2246)
* GreenField: Wallet API

* more work

* wip

* rough fiunish of transaction sending api

* Allow to create tx without broadcasting and small fixes

* Refactor Wallet Receive feature ad add greenfield api for address reserve for wallet

* add wallet api client

* add docs

* fix json converter tags

* fixes and add wallet tests

* fix tests

* fix rebase

* fixes

* just pass the tests already

* ugggh

* small cleanup

* revert int support in numeric string converter and make block id as native number in json

* fix LN endpoint

* try fix flaky test

* Revert "try fix flaky test"

This reverts commit 2e0d256325b892f7741325dcbab01196f74d182a.

* try fix other flaky test

* return proepr error if fee rate could not be fetched

* try fix test again

* reduce fee related logic for wallet api

* try reduce code changes for pr scope

* change auth logic for initial release of wallet api
2021-03-11 21:34:52 +09:00

133 lines
4.7 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayServer.Events;
using BTCPayServer.Services.Stores;
using Microsoft.Extensions.Hosting;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
namespace BTCPayServer.Services.Wallets
{
public class WalletReceiveService : IHostedService
{
private readonly CompositeDisposable _leases = new CompositeDisposable();
private readonly EventAggregator _eventAggregator;
private readonly ExplorerClientProvider _explorerClientProvider;
private readonly BTCPayWalletProvider _btcPayWalletProvider;
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly StoreRepository _storeRepository;
private readonly ConcurrentDictionary<WalletId, KeyPathInformation> _walletReceiveState =
new ConcurrentDictionary<WalletId, KeyPathInformation>();
public WalletReceiveService(EventAggregator eventAggregator, ExplorerClientProvider explorerClientProvider,
BTCPayWalletProvider btcPayWalletProvider, BTCPayNetworkProvider btcPayNetworkProvider,
StoreRepository storeRepository)
{
_eventAggregator = eventAggregator;
_explorerClientProvider = explorerClientProvider;
_btcPayWalletProvider = btcPayWalletProvider;
_btcPayNetworkProvider = btcPayNetworkProvider;
_storeRepository = storeRepository;
}
public async Task<string> UnReserveAddress(WalletId walletId)
{
var kpi = Get(walletId);
if (kpi is null)
{
return null;
}
var explorerClient = _explorerClientProvider.GetExplorerClient(walletId.CryptoCode);
if (explorerClient is null)
{
return null;
}
await explorerClient.CancelReservationAsync(kpi.DerivationStrategy, new[] {kpi.KeyPath});
return kpi.Address.ToString();
}
public async Task<KeyPathInformation> GetOrGenerate(WalletId walletId, bool forceGenerate = false)
{
var existing = Get(walletId);
if (existing != null && !forceGenerate)
{
return existing;
}
var wallet = _btcPayWalletProvider.GetWallet(walletId.CryptoCode);
var store = await _storeRepository.FindStore(walletId.StoreId);
var derivationScheme = store?.GetDerivationSchemeSettings(_btcPayNetworkProvider, walletId.CryptoCode);
if (wallet is null || derivationScheme is null)
{
return null;
}
var reserve = (await wallet.ReserveAddressAsync(derivationScheme.AccountDerivation));
Set(walletId, reserve);
return reserve;
}
public void Remove(WalletId walletId)
{
_walletReceiveState.TryRemove(walletId, out _);
}
public KeyPathInformation Get(WalletId walletId)
{
if (_walletReceiveState.ContainsKey(walletId))
{
return _walletReceiveState[walletId];
}
return null;
}
private void Set(WalletId walletId, KeyPathInformation information)
{
_walletReceiveState.AddOrReplace(walletId, information);
}
public IEnumerable<KeyValuePair<WalletId, KeyPathInformation>> GetByDerivation(string cryptoCode,
DerivationStrategyBase derivationStrategyBase)
{
return _walletReceiveState.Where(pair =>
pair.Key.CryptoCode.Equals(cryptoCode, StringComparison.InvariantCulture) &&
pair.Value.DerivationStrategy == derivationStrategyBase);
}
public Task StartAsync(CancellationToken cancellationToken)
{
_leases.Add(_eventAggregator.Subscribe<WalletChangedEvent>(evt =>
Remove(evt.WalletId)));
_leases.Add(_eventAggregator.Subscribe<NewOnChainTransactionEvent>(evt =>
{
var matching = GetByDerivation(evt.CryptoCode, evt.NewTransactionEvent.DerivationStrategy).Where(pair =>
evt.NewTransactionEvent.Outputs.Any(output => output.ScriptPubKey == pair.Value.ScriptPubKey));
foreach (var keyValuePair in matching)
{
Remove(keyValuePair.Key);
}
}));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_leases.Dispose();
return Task.CompletedTask;
}
}
}