app/BTCPayApp.UI/Features/TransactionState.cs
2023-11-08 19:46:23 +01:00

119 lines
4.2 KiB
C#

using BTCPayApp.Core;
using BTCPayServer.Client.Models;
using Fluxor;
using Microsoft.AspNetCore.SignalR.Client;
using NBitcoin;
namespace BTCPayApp.UI.Features;
public enum OnChainTransactionStatus
{
Mempool,
Confirmed
}
public record OnChainTransaction(string Id, decimal Balance, DateTimeOffset Timestamp, OnChainTransactionStatus Status);
[FeatureState]
public record OnChainTransactionsState(Dictionary<string, OnChainTransaction>? AllTransactions, bool Error)
{
public record SetError(bool Error);
public record UpdateTransactionAction(OnChainTransaction Transaction);
public record AllTransactionsLoadedAction(IEnumerable<OnChainTransaction> Transactions);
private OnChainTransactionsState() : this(null, false)
{
}
protected class BTCPayConnectionUpdatedActionEffect : Effect<RootState.BTCPayConnectionUpdatedAction>
{
private readonly IState<OnChainTransactionsState> _state;
public BTCPayConnectionUpdatedActionEffect(
IState<OnChainTransactionsState> state)
{
_state = state;
}
public override Task HandleAsync(RootState.BTCPayConnectionUpdatedAction action, IDispatcher dispatcher)
{
if (action.ConnectionState is HubConnectionState.Connected)
{
dispatcher.Dispatch(new LoadTransactionsAction(true));
}
return Task.CompletedTask;
}
}
public record LoadTransactionsAction(bool IgnoreIfNotNull = false);
protected class LoadTransactionsActionEffect : Effect<LoadTransactionsAction>
{
private readonly IState<RootState> _rootState;
private readonly IState<OnChainTransactionsState> _state;
private readonly BTCPayConnection _connection;
public LoadTransactionsActionEffect(IState<RootState> rootState, IState<OnChainTransactionsState> state,
BTCPayConnection connection)
{
_rootState = rootState;
_state = state;
_connection = connection;
}
public override async Task HandleAsync(LoadTransactionsAction action, IDispatcher dispatcher)
{
if (_rootState.Value.Loading.TryGetValue(RootState.LoadingHandles.TransactionState, out _))
return;
if (action.IgnoreIfNotNull && _state.Value.AllTransactions is not null)
return;
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.TransactionState, true));
try
{
var txs = await _connection.Client.ShowOnChainWalletTransactions(
_rootState.Value.PairConfig.PairingResult.StoreId,
"BTC", null, null);
dispatcher.Dispatch(new AllTransactionsLoadedAction(txs.Select(t =>
new OnChainTransaction(t.TransactionHash.ToString()!, t.Amount, t.Timestamp,
t.Status == TransactionStatus.Unconfirmed
? OnChainTransactionStatus.Mempool
: OnChainTransactionStatus.Confirmed))));
}
catch (Exception)
{
dispatcher.Dispatch(new SetError(true));
}
finally
{
dispatcher.Dispatch(new RootState.LoadingAction(RootState.LoadingHandles.TransactionState, false));
}
}
}
protected class AllTransactionsLoadedReducer : Reducer<OnChainTransactionsState, AllTransactionsLoadedAction>
{
public override OnChainTransactionsState Reduce(OnChainTransactionsState state,
AllTransactionsLoadedAction action)
{
return state with {AllTransactions = action.Transactions.ToDictionary(t => t.Id)};
}
}
protected class UpdateTransactionReducer : Reducer<OnChainTransactionsState, UpdateTransactionAction>
{
public override OnChainTransactionsState Reduce(OnChainTransactionsState state, UpdateTransactionAction action)
{
var allTransactions = state.AllTransactions ?? new Dictionary<string, OnChainTransaction>();
allTransactions.AddOrReplace(action.Transaction.Id, action.Transaction);
return state with {AllTransactions = allTransactions};
}
}
}