This commit is contained in:
Kukks 2024-05-02 15:00:09 +02:00 committed by Dennis Reimann
parent a9dd25e045
commit e0c57b1691
No known key found for this signature in database
GPG key ID: 5009E1797F03F8D0
10 changed files with 495 additions and 243 deletions

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;
namespace BTCPayApp.CommonServer;
@ -8,6 +9,15 @@ public class AppUserInfo
public string? Email { get; set; }
public IEnumerable<string>? Roles { get; set; }
public IEnumerable<AppUserStoreInfo>? Stores { get; set; }
public static bool Equals(AppUserInfo? x, AppUserInfo? y)
{
if (ReferenceEquals(x, y)) return true;
if (ReferenceEquals(x, null)) return false;
if (ReferenceEquals(y, null)) return false;
if (x.GetType() != y.GetType()) return false;
return x.UserId == y.UserId && x.Email == y.Email && Equals(x.Roles, y.Roles) && Equals(x.Stores, y.Stores);
}
}
public class AppUserStoreInfo

View file

@ -0,0 +1,79 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace BTCPayApp.CommonServer;
//methods available on the hub in the client
public interface IBTCPayAppHubClient
{
Task NotifyNetwork(string network);
Task TransactionDetected(string identifier, string txId);
Task NewBlock(string block);
}
//methods available on the hub in the server
public interface IBTCPayAppHubServer
{
Task<Dictionary<string,string>> Pair(PairRequest request);
Task<AppHandshakeResponse> Handshake(AppHandshake request);
Task<bool> BroadcastTransaction(string tx);
Task<decimal> GetFeeRate(int blockTarget);
Task<BestBlockResponse> GetBestBlock();
Task<string> GetBlockHeader(string hash);
Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds);
Task<string> DeriveScript(string identifier);
Task TrackScripts(string identifier, string[] scripts);
Task<string> UpdatePsbt(string[] identifiers, string psbt);
Task<CoinResponse[]> GetUTXOs(string[] identifiers);
}
public class CoinResponse
{
public string Identifier{ get; set; }
public bool Confirmed { get; set; }
public string Script { get; set; }
public string Outpoint { get; set; }
public decimal Value { get; set; }
public string Path { get; set; }
}
public class TxInfoResponse
{
public Dictionary<string,TransactionResponse> Txs { get; set; }
public Dictionary<string,string> Blocks { get; set; }
public Dictionary<string,int> BlockHeghts { get; set; }
}
public class TransactionResponse
{
public string? BlockHash { get; set; }
public int? BlockHeight { get; set; }
public string Transaction { get; set; }
}
public class BestBlockResponse
{
public required string BlockHash { get; set; }
public required int BlockHeight { get; set; }
public string BlockHeader { get; set; }
}
public class AppHandshake
{
public string[] Identifiers { get; set; }
}
public class AppHandshakeResponse
{
//response about identifiers being tracked successfully
public string[] IdentifiersAcknowledged { get; set; }
}
public class PairRequest
{
public Dictionary<string, string?> Derivations { get; set; } = new();
}

View file

@ -1,20 +0,0 @@
using System.Threading.Tasks;
namespace BTCPayApp.CommonServer;
public interface IBTCPayAppServerClient
{
Task TransactionDetected(string txid);
Task NewBlock(string block);
}
public interface IBTCPayAppServerHub
{
Task Handshake(AppHandshake handshake);
Task GetTransactions();
}
public class AppHandshake
{
public string? DerivationScheme { get; set; }
}

View file

@ -1,12 +0,0 @@
namespace BTCPayApp.CommonServer;
public class PairSuccessResult
{
public string? Key { get; set; }
public string? StoreId { get; set; }
public string? UserId { get; set; }
public string? ExistingWallet { get; set; }
public string? ExistingWalletSeed { get; set; }
public string? Network { get; set; }
}

View file

@ -7,7 +7,6 @@ public static class BTCPayAppExtensions
{
public static IServiceCollection AddBTCPayApp(this IServiceCollection serviceCollection)
{
serviceCollection.AddSingleton<BtcPayAppService>();
serviceCollection.AddSingleton<BTCPayAppState>();
serviceCollection.AddHostedService(serviceProvider => serviceProvider.GetRequiredService<BTCPayAppState>());
return serviceCollection;

View file

@ -1,160 +1,265 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayApp.CommonServer;
using BTCPayServer.Abstractions.Constants;
using BTCPayServer.Data;
using BTCPayServer.HostedServices;
using BTCPayServer.Services;
using BTCPayServer.Services.Wallets;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
using NBXplorer.DerivationStrategy;
using NBXplorer.Models;
using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
using Newtonsoft.Json;
namespace BTCPayServer.Controllers;
public class BTCPayAppState : IHostedService
public class GetBlockchainInfoResponse
{
private readonly IHubContext<BTCPayAppHub, IBTCPayAppServerClient> _hubContext;
private readonly ILogger<BTCPayAppState> _logger;
private readonly ExplorerClientProvider _explorerClientProvider;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly EventAggregator _eventAggregator;
private CompositeDisposable? _compositeDisposable;
public ExplorerClient ExplorerClient { get; private set; }
private DerivationSchemeParser _derivationSchemeParser;
private readonly ConcurrentDictionary<string, TrackedSource> _connectionScheme = new();
public BTCPayAppState(
IHubContext<BTCPayAppHub, IBTCPayAppServerClient> hubContext,
ILogger<BTCPayAppState> logger,
ExplorerClientProvider explorerClientProvider,
BTCPayNetworkProvider networkProvider,
EventAggregator eventAggregator)
[JsonProperty("headers")]
public int Headers
{
_hubContext = hubContext;
_logger = logger;
_explorerClientProvider = explorerClientProvider;
_networkProvider = networkProvider;
_eventAggregator = eventAggregator;
get; set;
}
[JsonProperty("blocks")]
public int Blocks
{
get; set;
}
[JsonProperty("verificationprogress")]
public double VerificationProgress
{
get; set;
}
public Task StartAsync(CancellationToken cancellationToken)
[JsonProperty("mediantime")]
public long? MedianTime
{
ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC");
_derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC);
_compositeDisposable = new();
_compositeDisposable.Add(
_eventAggregator.Subscribe<NewBlockEvent>(OnNewBlock));
_compositeDisposable.Add(
_eventAggregator.Subscribe<NewTransactionEvent>(OnNewTransaction));
return Task.CompletedTask;
get; set;
}
private void OnNewTransaction(NewTransactionEvent obj)
[JsonProperty("initialblockdownload")]
public bool? InitialBlockDownload
{
if (obj.CryptoCode != "BTC")
return;
_connectionScheme.Where(pair => pair.Value == obj.TrackedSource)
.Select(pair => pair.Key)
.ToList()
.ForEach(connectionId =>
{
_hubContext.Clients.Client(connectionId)
.TransactionDetected(obj.TransactionData.TransactionHash.ToString());
});
get; set;
}
[JsonProperty("bestblockhash")]
[JsonConverter(typeof(NBitcoin.JsonConverters.UInt256JsonConverter))]
public uint256 BestBlockHash { get; set; }
}
private void OnNewBlock(NewBlockEvent obj)
{
if (obj.CryptoCode != "BTC")
return;
_hubContext.Clients.All.NewBlock(obj.Hash.ToString());
}
public Task StopAsync(CancellationToken cancellationToken)
public record RPCBlockHeader(uint256 Hash, uint256? Previous, int Height, DateTimeOffset Time, uint256 MerkleRoot)
{
public SlimChainedBlock ToSlimChainedBlock() => new(Hash, Previous, Height);
}
public class BlockHeaders : IEnumerable<RPCBlockHeader>
{
public readonly Dictionary<uint256, RPCBlockHeader> ByHashes;
public readonly Dictionary<int, RPCBlockHeader> ByHeight;
public BlockHeaders(IList<RPCBlockHeader> headers)
{
_compositeDisposable?.Dispose();
return Task.CompletedTask;
}
public TrackedSource? GetConnectionState(string connectionId)
{
_connectionScheme.TryGetValue(connectionId, out var res);
return res;
}
public async Task Handshake(string contextConnectionId, AppHandshake handshake)
{
try
ByHashes = new Dictionary<uint256, RPCBlockHeader>(headers.Count);
ByHeight = new Dictionary<int, RPCBlockHeader>(headers.Count);
foreach (var header in headers)
{
var ts =
TrackedSource.Create(_derivationSchemeParser.Parse(handshake.DerivationScheme));
await ExplorerClient.TrackAsync(ts);
_connectionScheme.AddOrReplace(contextConnectionId, ts);
}
catch (Exception e)
{
_logger.LogError(e, "Error during handshake");
throw;
ByHashes.TryAdd(header.Hash, header);
ByHeight.TryAdd(header.Height, header);
}
}
public void RemoveConnection(string contextConnectionId)
public IEnumerator<RPCBlockHeader> GetEnumerator()
{
_connectionScheme.TryRemove(contextConnectionId, out _);
return ByHeight.Values.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Greenfield)]
public class BTCPayAppHub : Hub<IBTCPayAppServerClient>, IBTCPayAppServerHub
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)]
public class BTCPayAppHub : Hub<IBTCPayAppHubClient>, IBTCPayAppHubServer
{
private readonly BTCPayNetworkProvider _btcPayNetworkProvider;
private readonly NBXplorerDashboard _nbXplorerDashboard;
private readonly BTCPayAppState _appState;
private readonly BTCPayWallet _wallet;
private readonly ExplorerClientProvider _explorerClientProvider;
private readonly IFeeProviderFactory _feeProviderFactory;
public BTCPayAppHub(BTCPayAppState appState, BTCPayWalletProvider walletProvider)
public BTCPayAppHub(BTCPayNetworkProvider btcPayNetworkProvider,
NBXplorerDashboard nbXplorerDashboard,
BTCPayAppState appState,
ExplorerClientProvider explorerClientProvider,
IFeeProviderFactory feeProviderFactory)
{
_btcPayNetworkProvider = btcPayNetworkProvider;
_nbXplorerDashboard = nbXplorerDashboard;
_appState = appState;
_wallet = walletProvider.GetWallet("BTC");
_explorerClientProvider = explorerClientProvider;
_feeProviderFactory = feeProviderFactory;
}
public override async Task OnConnectedAsync()
{
}
public override Task OnDisconnectedAsync(Exception? exception)
{
_appState.RemoveConnection(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
public Task Handshake(AppHandshake handshake)
{
return _appState.Handshake(Context.ConnectionId, handshake);
}
public async Task GetTransactions()
{
var deriv = _appState.GetConnectionState(Context.ConnectionId);
if(deriv is null)
throw new InvalidOperationException("Handshake not done");
var txs = await _appState.ExplorerClient.GetTransactionsAsync(deriv);
if (txs is null)
//TODO: this needs to happen BEFORE connection is established
if (!_nbXplorerDashboard.IsFullySynched(_btcPayNetworkProvider.BTC.CryptoCode, out _))
{
throw new InvalidOperationException("NBXplorer failed to get transactions");
Context.Abort();
return;
}
await Clients.Client(Context.ConnectionId).NotifyNetwork(_btcPayNetworkProvider.BTC.NBitcoinNetwork.ToString());
}
public async Task<bool> BroadcastTransaction(string tx)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
Transaction txObj = Transaction.Parse(tx, explorerClient.Network.NBitcoinNetwork);
var result = await explorerClient.BroadcastAsync(txObj);
return result.Success;
}
public async Task<decimal> GetFeeRate(int blockTarget)
{
var feeProvider = _feeProviderFactory.CreateFeeProvider( _btcPayNetworkProvider.BTC);
return (await feeProvider.GetFeeRateAsync(blockTarget)).SatoshiPerByte;
}
public async Task<BestBlockResponse> GetBestBlock()
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var bcInfo = await explorerClient.RPCClient.GetBlockchainInfoAsyncEx();
var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(bcInfo.BestBlockHash);
return new BestBlockResponse()
{
BlockHash = bcInfo.BestBlockHash.ToString(),
BlockHeight = bcInfo.Blocks,
BlockHeader = bh.ToString()
};
}
public async Task<string> GetBlockHeader(string hash)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var bh = await explorerClient.RPCClient.GetBlockHeaderAsync(uint256.Parse(hash));
return bh.ToString();
}
public async Task<TxInfoResponse> FetchTxsAndTheirBlockHeads(string[] txIds)
{
var cancellationToken = Context.ConnectionAborted;
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var uints = txIds.Select(uint256.Parse).ToArray();
var txsFetch = await Task.WhenAll(uints.Select(
uint256 =>
explorerClient.GetTransactionAsync(uint256, cancellationToken)));
var batch = explorerClient.RPCClient.PrepareBatch();
var headersTask = txsFetch.Where(result => result.BlockId is not null && result.BlockId != uint256.Zero)
.Distinct().ToDictionary(result => result.BlockId, result =>
batch.GetBlockHeaderAsync(result.BlockId, cancellationToken));
await batch.SendBatchAsync(cancellationToken);
var headerToHeight = (await Task.WhenAll(headersTask.Values)).ToDictionary(header => header.GetHash(),
header => txsFetch.First(result => result.BlockId == header.GetHash()).Height!);
return new TxInfoResponse()
{
Txs = txsFetch.ToDictionary(tx => tx.TransactionHash.ToString(), tx => new TransactionResponse()
{
BlockHash = tx.BlockId?.ToString(),
BlockHeight = (int?) tx.Height,
Transaction = tx.Transaction.ToString()
}),
Blocks = txsFetch.Where(tx => tx.BlockId is not null).ToDictionary(tx => tx.BlockId.ToString(), tx => tx.BlockId.ToString()),
BlockHeghts = headerToHeight.ToDictionary(kv => kv.Key.ToString(), kv =>(int) kv.Value!)
};
}
public async Task<string> DeriveScript(string identifier)
{
var cancellationToken = Context.ConnectionAborted;
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var ts = TrackedSource.Parse(identifier,explorerClient.Network ) as DerivationSchemeTrackedSource;
var kpi = await explorerClient.GetUnusedAsync(ts.DerivationStrategy, DerivationFeature.Deposit, 0, true, cancellationToken);
return kpi.ScriptPubKey.ToHex();
}
public async Task TrackScripts(string identifier, string[] scripts)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var ts = TrackedSource.Parse(identifier,explorerClient.Network ) as GroupTrackedSource;
var s = scripts.Select(Script.FromHex).Select(script => script.GetDestinationAddress(explorerClient.Network.NBitcoinNetwork)).Select(address => address.ToString()).ToArray();
await explorerClient.AddGroupAddressAsync(explorerClient.CryptoCode,ts.GroupId, s);
}
public async Task<string> UpdatePsbt(string[] identifiers, string psbt)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var resultPsbt = PSBT.Parse(psbt, explorerClient.Network.NBitcoinNetwork);
foreach (string identifier in identifiers)
{
var ts = TrackedSource.Parse(identifier,explorerClient.Network);
if (ts is not DerivationSchemeTrackedSource derivationSchemeTrackedSource)
continue;
var res = await explorerClient.UpdatePSBTAsync(new UpdatePSBTRequest()
{
PSBT = resultPsbt, DerivationScheme = derivationSchemeTrackedSource.DerivationStrategy,
});
resultPsbt = resultPsbt.Combine(res.PSBT);
}
return resultPsbt.ToHex();
}
public async Task<CoinResponse[]> GetUTXOs(string[] identifiers)
{
var explorerClient = _explorerClientProvider.GetExplorerClient( _btcPayNetworkProvider.BTC);
var result = new List<CoinResponse>();
foreach (string identifier in identifiers)
{
var ts = TrackedSource.Parse(identifier,explorerClient.Network);
if (ts is not DerivationSchemeTrackedSource derivationSchemeTrackedSource)
continue;
var utxos = await explorerClient.GetUTXOsAsync(derivationSchemeTrackedSource.DerivationStrategy);
result.AddRange(utxos.GetUnspentUTXOs(0).Select(utxo => new CoinResponse()
{
Identifier = identifier,
Confirmed = utxo.Confirmations >0,
Script = utxo.ScriptPubKey.ToHex(),
Outpoint = utxo.Outpoint.ToString(),
Value = utxo.Value.GetValue(_btcPayNetworkProvider.BTC),
Path = utxo.KeyPath.ToString()
}));
}
return result.ToArray();
}
public async Task<Dictionary<string, string>> Pair(PairRequest request)
{
return await _appState.Pair(Context.ConnectionId, request);
}
public async Task<AppHandshakeResponse> Handshake(AppHandshake request)
{
return await _appState.Handshake(Context.ConnectionId, request);
}
}

View file

@ -0,0 +1,132 @@
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BTCPayApp.CommonServer;
using BTCPayServer.Events;
using Microsoft.AspNetCore.SignalR;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using NBitcoin;
using NBXplorer;
using NBXplorer.Models;
using NewBlockEvent = BTCPayServer.Events.NewBlockEvent;
namespace BTCPayServer.Controllers;
public class BTCPayAppState : IHostedService
{
private readonly IHubContext<BTCPayAppHub, IBTCPayAppHubClient> _hubContext;
private readonly ILogger<BTCPayAppState> _logger;
private readonly ExplorerClientProvider _explorerClientProvider;
private readonly BTCPayNetworkProvider _networkProvider;
private readonly EventAggregator _eventAggregator;
private CompositeDisposable? _compositeDisposable;
public ExplorerClient ExplorerClient { get; private set; }
private DerivationSchemeParser _derivationSchemeParser;
// private readonly ConcurrentDictionary<string, TrackedSource> _connectionScheme = new();
public BTCPayAppState(
IHubContext<BTCPayAppHub, IBTCPayAppHubClient> hubContext,
ILogger<BTCPayAppState> logger,
ExplorerClientProvider explorerClientProvider,
BTCPayNetworkProvider networkProvider,
EventAggregator eventAggregator)
{
_hubContext = hubContext;
_logger = logger;
_explorerClientProvider = explorerClientProvider;
_networkProvider = networkProvider;
_eventAggregator = eventAggregator;
}
public Task StartAsync(CancellationToken cancellationToken)
{
ExplorerClient = _explorerClientProvider.GetExplorerClient("BTC");
_derivationSchemeParser = new DerivationSchemeParser(_networkProvider.BTC);
_compositeDisposable = new();
_compositeDisposable.Add(
_eventAggregator.Subscribe<NewBlockEvent>(OnNewBlock));
_compositeDisposable.Add(
_eventAggregator.Subscribe<NewOnChainTransactionEvent>(OnNewTransaction));
return Task.CompletedTask;
}
private void OnNewTransaction(NewOnChainTransactionEvent obj)
{
if (obj.CryptoCode != "BTC")
return;
var identifier = obj.NewTransactionEvent.TrackedSource.ToString()!;
_hubContext.Clients
.Group(identifier)
.TransactionDetected(identifier, obj.NewTransactionEvent.TransactionData.TransactionHash.ToString());
}
private void OnNewBlock(NewBlockEvent obj)
{
if (obj.CryptoCode != "BTC")
return;
_hubContext.Clients.All.NewBlock(obj.Hash.ToString());
}
public Task StopAsync(CancellationToken cancellationToken)
{
_compositeDisposable?.Dispose();
return Task.CompletedTask;
}
public async Task<AppHandshakeResponse> Handshake(string contextConnectionId, AppHandshake handshake)
{
foreach (var ts in handshake.Identifiers)
{
try
{
await _hubContext.Groups.AddToGroupAsync(contextConnectionId, ts);
}
catch (Exception e)
{
_logger.LogError(e, "Error during handshake");
throw;
}
}
//TODO: Check if the provided identifiers are already tracked on the server
//TODO: Maybe also introduce a checkpoint to make sure nothing is missed, but this may be somethign to handle alongside VSS
return new AppHandshakeResponse()
{
IdentifiersAcknowledged = handshake.Identifiers
};
}
public async Task<Dictionary<string, string>> Pair(string contextConnectionId, PairRequest request)
{
var result = new Dictionary<string, string>();
foreach (var derivation in request.Derivations)
{
if(derivation.Value is null)
{
var id =await ExplorerClient.CreateGroupAsync();
result.Add(derivation.Key, id.TrackedSource);
}
else
{
var strategy = _derivationSchemeParser.ParseOutputDescriptor(derivation.Value);
result.Add(derivation.Key, TrackedSource.Create(strategy.Item1).ToString());
}
}
await Handshake(contextConnectionId, new AppHandshake()
{
Identifiers = result.Values.ToArray()
});
return result;
}
}

View file

@ -34,7 +34,6 @@ namespace BTCPayServer.App;
[Authorize(AuthenticationSchemes = AuthenticationSchemes.Bearer)]
[Route("btcpayapp")]
public class BtcPayAppController(
BtcPayAppService appService,
APIKeyRepository apiKeyRepository,
StoreRepository storeRepository,
BTCPayNetworkProvider btcPayNetworkProvider,
@ -254,63 +253,5 @@ public class BtcPayAppController(
}
return result.Succeeded ? TypedResults.Ok() : TypedResults.Problem(result.ToString().Split(": ").Last(), statusCode: 401);
}
[HttpGet("pair/{code}")]
public async Task<IActionResult> StartPair(string code)
{
var res = appService.ConsumePairingCode(code);
if (res is null)
{
return Unauthorized();
}
StoreData? store = null;
if (res.StoreId is not null)
{
store = await storeRepository.FindStore(res.StoreId, res.UserId);
if (store is null)
{
return NotFound();
}
}
var key = new APIKeyData
{
Id = Encoders.Hex.EncodeData(RandomUtils.GetBytes(20)),
Type = APIKeyType.Permanent,
UserId = res.UserId,
Label = "BTCPay App Pairing"
};
key.SetBlob(new APIKeyBlob {Permissions = [Policies.Unrestricted] });
await apiKeyRepository.CreateKey(key);
var onchain = store?.GetDerivationSchemeSettings(handlers, "BTC");
string? onchainSeed = null;
if (onchain is not null)
{
var explorerClient = explorerClientProvider.GetExplorerClient("BTC");
onchainSeed = await GetSeed(explorerClient, onchain);
}
var nBitcoinNetwork = btcPayNetworkProvider.GetNetwork<BTCPayNetwork>("BTC").NBitcoinNetwork;
return Ok(new PairSuccessResult
{
Key = key.Id,
StoreId = store?.Id,
UserId = res.UserId,
ExistingWallet = onchain?.AccountDerivation?.GetExtPubKeys()?.FirstOrDefault()?.ToString(nBitcoinNetwork),
ExistingWalletSeed = onchainSeed,
Network = nBitcoinNetwork.Name
});
}
private async Task<string?> GetSeed(ExplorerClient client, DerivationSchemeSettings derivation)
{
return derivation.IsHotWallet &&
await client.GetMetadataAsync<string>(derivation.AccountDerivation, WellknownMetadataKeys.Mnemonic) is
{ } seed &&
!string.IsNullOrEmpty(seed)
? seed
: null;
}
}

View file

@ -1,39 +0,0 @@
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Caching.Memory;
namespace BTCPayServer.Controllers;
public class BtcPayAppService
{
private readonly IMemoryCache _memoryCache;
public BtcPayAppService(IMemoryCache memoryCache)
{
_memoryCache = memoryCache;
}
private string CacheKey(string k) => $"BtcPayAppService_{k}";
public async Task<string> GeneratePairingCode(string storeId, string userId)
{
var code = Guid.NewGuid().ToString();
_memoryCache.Set(CacheKey(code), new PairingRequest() {Key = code, StoreId = storeId, UserId = userId},
TimeSpan.FromMinutes(5));
return code;
}
public PairingRequest? ConsumePairingCode(string code)
{
return _memoryCache.TryGetValue(CacheKey(code), out var pairingRequest)
? (PairingRequest?)pairingRequest
: null;
}
public class PairingRequest
{
public string Key { get; set; }
public string StoreId { get; set; }
public string UserId { get; set; }
}
}

57
BTCPayServer/App/Exts.cs Normal file
View file

@ -0,0 +1,57 @@
#nullable enable
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NBitcoin;
using NBitcoin.RPC;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Controllers;
//TODO: this currently requires NBX to be enabled with RPCPROXY enabled, we need to fix the whitelisted rpc commands to remove this dependency
public static class Exts
{
public static async Task<GetBlockchainInfoResponse> GetBlockchainInfoAsyncEx(this RPCClient client, CancellationToken cancellationToken = default)
{
var result = await client.SendCommandAsync("getblockchaininfo", cancellationToken).ConfigureAwait(false);
return JsonConvert.DeserializeObject<GetBlockchainInfoResponse>(result.ResultString);
}
public static async Task<BlockHeaders> GetBlockHeadersAsync(this RPCClient rpc, IList<int> blockHeights, CancellationToken cancellationToken)
{
var batch = rpc.PrepareBatch();
var hashes = blockHeights.Select(h => batch.GetBlockHashAsync(h)).ToArray();
await batch.SendBatchAsync(cancellationToken);
batch = rpc.PrepareBatch();
var headers = hashes.Select(async h => await batch.GetBlockHeaderAsyncEx(await h, cancellationToken)).ToArray();
await batch.SendBatchAsync(cancellationToken);
return new BlockHeaders(headers.Select(h => h.GetAwaiter().GetResult()).Where(h => h is not null).ToList());
}
public static async Task<RPCBlockHeader> GetBlockHeaderAsyncEx(this RPCClient rpc, uint256 blk, CancellationToken cancellationToken)
{
var header = await rpc.SendCommandAsync(new NBitcoin.RPC.RPCRequest("getblockheader", new[] { blk.ToString() })
{
ThrowIfRPCError = false
}, cancellationToken);
if (header.Result is null || header.Error is not null)
return null;
var response = header.Result;
var confs = response["confirmations"].Value<long>();
if (confs == -1)
return null;
var prev = response["previousblockhash"]?.Value<string>();
return new RPCBlockHeader(
blk,
prev is null ? null : new uint256(prev),
response["height"].Value<int>(),
NBitcoin.Utils.UnixTimeToDateTime(response["time"].Value<long>()),
new uint256(response["merkleroot"]?.Value<string>()));
}
}