From 4b514cb597178e8e3773db0aab258d551ddbd0ee Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 6 Dec 2023 18:14:48 +0000 Subject: [PATCH 01/19] Handle unconfirmed trx in wallet --- src/Angor/Client/Pages/Wallet.razor | 12 +++- src/Angor/Shared/IWalletOperations.cs | 2 + src/Angor/Shared/Models/AccountInfo.cs | 12 ++++ src/Angor/Shared/WalletOperations.cs | 97 ++++++++++++++++++++++++-- 4 files changed, 117 insertions(+), 6 deletions(-) diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index acbcd404..2c47677f 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -594,7 +594,16 @@ var wallet = _walletStorage.GetWallet(); - return await _walletOperations.SendAmountToAddress(wallet, _sendInfo); + var res = await _walletOperations.SendAmountToAddress(wallet, _sendInfo); + + if (res.Success) + { + _walletOperations.UpdateAccountInfoWithSpentTransaction(localAccountInfo, res.Data, _sendInfo.ChangeAddress); + localAccountInfo.CalculateBalance(); + storage.SetAccountInfo(network.Name, localAccountInfo); + } + + return res; }); if (operationResult is { Success: true }) @@ -648,7 +657,6 @@ private AccountInfo GetAccountInfoFromStorage() { - var network = _networkConfiguration.GetNetwork(); return storage.GetAccountInfo(network.Name); } diff --git a/src/Angor/Shared/IWalletOperations.cs b/src/Angor/Shared/IWalletOperations.cs index e93154b5..79e05dbf 100644 --- a/src/Angor/Shared/IWalletOperations.cs +++ b/src/Angor/Shared/IWalletOperations.cs @@ -28,4 +28,6 @@ Task> PublishTransactionAsync(Network network, Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transaction, WalletWords walletWords, AccountInfo accountInfo, FeeEstimation feeRate); + + void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction, string changeAddress); } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountInfo.cs b/src/Angor/Shared/Models/AccountInfo.cs index 69b32f3b..a23516a8 100644 --- a/src/Angor/Shared/Models/AccountInfo.cs +++ b/src/Angor/Shared/Models/AccountInfo.cs @@ -11,6 +11,9 @@ public class AccountInfo public List AddressesInfo { get; set; } = new(); public List ChangeAddressesInfo { get; set; } = new(); + public List PendingAdd { get; set; } = new(); + public List PendingRemove { get; set; } = new(); + public int InvestmentsCount { get; set; } //TODO David handle the set logic public string? GetNextReceiveAddress() @@ -22,4 +25,13 @@ public class AccountInfo { return ChangeAddressesInfo.Last()?.Address; } + + public void CalculateBalance() + { + var balance = AddressesInfo.Concat(ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); + var balanceSpent = PendingRemove.Sum(s => s.value); + TotalBalance = balance - balanceSpent; + + TotalUnConfirmedBalance = PendingAdd.Sum(s => s.value); + } } \ No newline at end of file diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index 2e8f6cbc..fe1876d8 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -128,10 +128,92 @@ public async Task> SendAmountToAddress(WalletWords .SendEstimatedFees(new FeeRate(Money.Coins(sendInfo.FeeRate))); var signedTransaction = builder.BuildTransaction(true); - + return await PublishTransactionAsync(network, signedTransaction); } + private void UpdatePendingLists(AccountInfo accountInfo) + { + // remove from the pending remove list if it was removed from the indexer + var pendingRemove = accountInfo.PendingRemove.ToList(); + foreach (var utxoData in pendingRemove) + { + foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + { + if (addressInfo.Address == utxoData.address) + { + if (addressInfo.UtxoData.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) + { + accountInfo.PendingRemove.Remove(utxoData); + } + } + } + } + + // remove from the pending add if it was removed from the indexer + var pendingAdd = accountInfo.PendingAdd.ToList(); + foreach (var utxoData in pendingAdd) + { + foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + { + if (addressInfo.Address == utxoData.address) + { + if (addressInfo.UtxoData.Any(_ => _.outpoint.ToString() == utxoData.outpoint.ToString())) + { + accountInfo.PendingAdd.Remove(utxoData); + } + } + } + } + } + + public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction, string changeAddress) + { + Network network = _networkConfiguration.GetNetwork(); + + var outputs = transaction.Outputs.AsIndexedOutputs(); + var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); + + foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + { + // find all spent inputs to mark them as spent + foreach (var utxoData in addressInfo.UtxoData) + { + foreach (var outPoint in inputs) + { + if (utxoData.outpoint.ToString() == outPoint.ToString()) + { + if (accountInfo.PendingRemove.All(_ => _.outpoint != utxoData.outpoint)) + { + accountInfo.PendingRemove.Add(utxoData); + } + } + } + } + + // find all new outputs to mark them as unspent + foreach (var output in outputs) + { + if (output.TxOut.ScriptPubKey.GetDestinationAddress(network).ToString() == addressInfo.Address) + { + var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; + + if (accountInfo.PendingAdd.All(_ => _.outpoint != outpoint)) + { + accountInfo.PendingAdd.Add(new UtxoData + { + address = addressInfo.Address, + scriptHex = output.TxOut.ScriptPubKey.ToHex(), + outpoint = outpoint, + blockIndex = 0, + value = output.TxOut.Value + }); + } + } + } + } + } + public async Task> PublishTransactionAsync(Network network,Transaction signedTransaction) { var hex = signedTransaction.ToHex(network.Consensus.ConsensusFactory); @@ -305,7 +387,9 @@ public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo accountInfo.ChangeAddressesInfo.Add(changeAddressInfo); } - accountInfo.TotalBalance = accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); + UpdatePendingLists(accountInfo); + + accountInfo.CalculateBalance(); } private async Task<(int,List)> FetchAddressesDataForPubKeyAsync(int scanIndex, string ExtendedPubKey, Network network, bool isChange) @@ -369,12 +453,14 @@ private AddressInfo GenerateAddressFromPubKey(int scanIndex, Network network, bo public async Task<(string address, List data)> FetchUtxoForAddressAsync(string address) { + // cap utxo count to max 1000 items, this is + // mainly to get miner wallets to work fine + var maxutxo = 1000; + var limit = 50; var offset = 0; List allItems = new(); - SettingsUrl indexer = _networkConfiguration.GetIndexerUrl(); - do { // this is inefficient look at headers to know when to stop @@ -388,6 +474,9 @@ private AddressInfo GenerateAddressFromPubKey(int scanIndex, Network network, bo if (utxo.Count < limit) break; + if(allItems.Count >= maxutxo) + break; + offset += limit; } while (true); From db8f6ab381f757b3c6d2e59f37d5ec28686ac220 Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 6 Dec 2023 19:56:07 +0000 Subject: [PATCH 02/19] Ignore spent outputs when looking for utxos --- src/Angor/Shared/WalletOperations.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index fe1876d8..eae1f1b2 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -238,6 +238,11 @@ public List FindOutputsForTransaction(long sendAmountat, Accou .OrderBy(o => o.utxo.blockIndex) .ThenByDescending(o => o.utxo.value)) { + if (accountInfo.PendingRemove.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) + { + continue; + } + utxosToSpend.Add(new UtxoDataWithPath { HdPath = utxoData.path, UtxoData = utxoData.utxo }); total += utxoData.utxo.value; From f137acdccd5d7ef1c9312305b3fe66ed65401a67 Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 6 Dec 2023 22:15:48 +0000 Subject: [PATCH 03/19] Correctly show the days till penalty recovery --- src/Angor/Client/Pages/Recover.razor | 12 +++++++----- src/Angor/Client/Pages/Signatures.razor | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index 8a0e75f4..1d867105 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -61,7 +61,9 @@ { } - + +
+
@@ -343,8 +345,8 @@ else { item.ProjectScriptType = new ProjectScriptType { ScriptType = ProjectScriptTypeEnum.InvestorWithPenalty }; - var days = (penaltyExpieryDate - DateTime.Now).Days; - item.SpentTo = days > 0 ? $"Penalty, released in {days} days" : "Penalty can be released"; + var days = (penaltyExpieryDate - DateTime.Now).TotalDays; + item.SpentTo = days > 0 ? $"Penalty, released in {days.ToString("0.0")} days" : "Penalty can be released"; } } else @@ -367,8 +369,8 @@ } case ProjectScriptTypeEnum.InvestorWithPenalty: { - var days = (penaltyExpieryDate - DateTime.Now).Days; - item.SpentTo = days > 0 ? $"Penalty, released in {days} days" : "Penalty can be released"; + var days = (penaltyExpieryDate - DateTime.Now).TotalDays; + item.SpentTo = days > 0 ? $"Penalty, released in {days.ToString("0.0")} days" : "Penalty can be released"; break; } case ProjectScriptTypeEnum.EndOfProject: diff --git a/src/Angor/Client/Pages/Signatures.razor b/src/Angor/Client/Pages/Signatures.razor index 1fcb8659..1921f63a 100644 --- a/src/Angor/Client/Pages/Signatures.razor +++ b/src/Angor/Client/Pages/Signatures.razor @@ -62,7 +62,7 @@ @foreach (var signature in pendingSignatures.Where(_ => _.TransactionHex != null)) { - + - - @foreach (var addressInfo in localAccountInfo.AddressesInfo.Union(localAccountInfo.ChangeAddressesInfo)) + + @foreach (var addressInfo in accountBalanceInfo.AccountInfo.AddressesInfo.Union(accountBalanceInfo.AccountInfo.ChangeAddressesInfo)) { var total = addressInfo.Balance; var count = addressInfo.UtxoData.Count(); @@ -378,12 +380,12 @@ private bool walletWordsModal; private bool walletWordsCreateModal; - private AccountInfo localAccountInfo = new(); - private int feeRange = 0; private int FeePosition = 1; private SendInfo _sendInfo = new (); + private AccountBalanceInfo accountBalanceInfo = new AccountBalanceInfo(); + private FeeEstimations FeeEstimations = new (); // Max index for the range input @@ -397,8 +399,12 @@ if (hasWallet) { - localAccountInfo = GetAccountInfoFromStorage(); + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + + accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); } + return Task.CompletedTask; } @@ -406,17 +412,18 @@ { var operationResult = await notificationComponent.LongOperation(async () => { - var network = _networkConfiguration.GetNetwork(); - var accountInfo = GetAccountInfoFromStorage(); - + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + await _walletOperations.UpdateDataForExistingAddressesAsync(accountInfo); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, unconfirmedInfo); storage.SetAccountInfo(network.Name, accountInfo); - - localAccountInfo = accountInfo; - + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + + accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); + return new OperationResult { Success = true }; }); @@ -433,8 +440,7 @@ WalletWords data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; var accountInfo = _walletOperations.BuildAccountInfoForWalletWords(data); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); - Network network = _networkConfiguration.GetNetwork(); + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, new UnconfirmedInfo()); _walletStorage.SaveWalletWords(data); storage.SetAccountInfo(network.Name,accountInfo); @@ -450,7 +456,7 @@ { hasWallet = _walletStorage.HasWallet(); ClearWalletWords(); - localAccountInfo = GetAccountInfoFromStorage(); + var accountInfo = storage.GetAccountInfo(network.Name); NavMenuState.NotifyStateChanged(); } @@ -510,7 +516,8 @@ public async Task CopyNextReceiveAddress() { - var address = localAccountInfo.GetNextReceiveAddress(); + var accountInfo = storage.GetAccountInfo(network.Name); + var address = accountInfo.GetNextReceiveAddress(); if (string.IsNullOrEmpty(address)) { @@ -549,10 +556,13 @@ { var operationResult = await notificationComponent.LongOperation(async () => { - var accountInfo = GetAccountInfoFromStorage(); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - localAccountInfo = accountInfo; + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, unconfirmedInfo); + + storage.SetAccountInfo(network.Name, accountInfo); + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); await RefreshFee(); @@ -574,7 +584,7 @@ _sendInfo.ChangeAddress = accountInfo.ChangeAddressesInfo.First(f => f.HasHistory == false).Address; } - _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, estimationsFee.FeeRate); + _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, unconfirmedInfo, estimationsFee.FeeRate); return new OperationResult { Success = true }; }); @@ -593,14 +603,17 @@ sendConfirmModal = false; var wallet = _walletStorage.GetWallet(); + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); var res = await _walletOperations.SendAmountToAddress(wallet, _sendInfo); if (res.Success) { - _walletOperations.UpdateAccountInfoWithSpentTransaction(localAccountInfo, res.Data, _sendInfo.ChangeAddress); - localAccountInfo.CalculateBalance(); - storage.SetAccountInfo(network.Name, localAccountInfo); + _walletOperations.UpdateAccountInfoWithSpentTransaction(accountInfo, unconfirmedInfo, res.Data); + accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); + storage.SetAccountInfo(network.Name, accountInfo); + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); } return res; @@ -646,8 +659,10 @@ _sendInfo.FeeBlockCount = estimationsFee.Confirmations; _sendInfo.FeeRateSat = estimationsFee.FeeRate; - var accountInfo = GetAccountInfoFromStorage(); - _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, estimationsFee.FeeRate); + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + + _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, unconfirmedInfo, estimationsFee.FeeRate); StateHasChanged(); } @@ -655,11 +670,6 @@ } } - private AccountInfo GetAccountInfoFromStorage() - { - return storage.GetAccountInfo(network.Name); - } - private void showCoinControlModal() { coinControlModal = true; diff --git a/src/Angor/Client/Storage/LocalSessionStorage.cs b/src/Angor/Client/Storage/LocalSessionStorage.cs index 7a16bd4b..192a4ee3 100644 --- a/src/Angor/Client/Storage/LocalSessionStorage.cs +++ b/src/Angor/Client/Storage/LocalSessionStorage.cs @@ -40,13 +40,13 @@ public void SetProjectIndexerData(List list) _sessionStorageService.SetItem(BrowseIndexerData,list); } - public List GetPendingSpendUtxo() + public UnconfirmedInfo GetUnconfirmedInfo() { - return _sessionStorageService.GetItem>("pending-utxo") ?? new List(); + return _sessionStorageService.GetItem("unconfirmed-info") ?? new UnconfirmedInfo(); } - public void SetPendingSpentUtxo(List list) + public void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo) { - _sessionStorageService.SetItem("pending-utxo", list); + _sessionStorageService.SetItem("unconfirmed-info", unconfirmedInfo); } } \ No newline at end of file diff --git a/src/Angor/Shared/IWalletOperations.cs b/src/Angor/Shared/IWalletOperations.cs index 79e05dbf..9380c5e8 100644 --- a/src/Angor/Shared/IWalletOperations.cs +++ b/src/Angor/Shared/IWalletOperations.cs @@ -11,23 +11,26 @@ public interface IWalletOperations Task> SendAmountToAddress(WalletWords walletWords, SendInfo sendInfo); AccountInfo BuildAccountInfoForWalletWords(WalletWords walletWords); Task UpdateDataForExistingAddressesAsync(AccountInfo accountInfo); - Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo); + Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo); Task<(string address, List data)> FetchUtxoForAddressAsync(string adddress); - List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo); + List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo); Task> GetFeeEstimationAsync(); - decimal CalculateTransactionFee(SendInfo sendInfo,AccountInfo accountInfo, long feeRate); + decimal CalculateTransactionFee(SendInfo sendInfo,AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, long feeRate); (List? coins, List keys) GetUnspentOutputsForTransaction(WalletWords walletWords, List utxoDataWithPaths); Transaction AddInputsAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, + WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, FeeEstimation feeRate); Task> PublishTransactionAsync(Network network, Transaction signedTransaction); Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, + WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, FeeEstimation feeRate); - void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction, string changeAddress); + void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction); + + void AddInputsAndOutputsAsPending(UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction); + void RemoveInputsAndOutputsFromPending(UnconfirmedInfo unconfirmedInfo, string trxid); } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountBalanceInfo.cs b/src/Angor/Shared/Models/AccountBalanceInfo.cs new file mode 100644 index 00000000..c5f49254 --- /dev/null +++ b/src/Angor/Shared/Models/AccountBalanceInfo.cs @@ -0,0 +1,27 @@ +namespace Angor.Shared.Models; + +public class AccountBalanceInfo +{ + public long TotalBalance { get; set; } + public long TotalUnconfirmedBalance { get; set; } + + + public AccountInfo AccountInfo { get; private set; } = new (); + public UnconfirmedInfo UnconfirmedInfo { get; private set; } = new (); + + public static AccountBalanceInfo GetBalance(AccountInfo account, UnconfirmedInfo unconfirmedInfo) + { + var balance = account.AddressesInfo.Concat(account.ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); + var balanceSpent = unconfirmedInfo.PendingSpent.Sum(s => s.value); + + var balanceUnconfirmed = unconfirmedInfo.PendingReceive.Sum(s => s.value); + + return new AccountBalanceInfo + { + TotalBalance = balance - balanceSpent, + TotalUnconfirmedBalance = balanceUnconfirmed, + AccountInfo = account, + UnconfirmedInfo = unconfirmedInfo + }; + } +} \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountInfo.cs b/src/Angor/Shared/Models/AccountInfo.cs index a23516a8..d85b64b7 100644 --- a/src/Angor/Shared/Models/AccountInfo.cs +++ b/src/Angor/Shared/Models/AccountInfo.cs @@ -6,14 +6,9 @@ public class AccountInfo public string Path { get; set; } public int LastFetchIndex { get; set; } public int LastFetchChangeIndex { get; set; } - public long TotalBalance { get; set; } - public long TotalUnConfirmedBalance { get; set; } public List AddressesInfo { get; set; } = new(); public List ChangeAddressesInfo { get; set; } = new(); - public List PendingAdd { get; set; } = new(); - public List PendingRemove { get; set; } = new(); - public int InvestmentsCount { get; set; } //TODO David handle the set logic public string? GetNextReceiveAddress() @@ -25,13 +20,4 @@ public class AccountInfo { return ChangeAddressesInfo.Last()?.Address; } - - public void CalculateBalance() - { - var balance = AddressesInfo.Concat(ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); - var balanceSpent = PendingRemove.Sum(s => s.value); - TotalBalance = balance - balanceSpent; - - TotalUnConfirmedBalance = PendingAdd.Sum(s => s.value); - } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/UnconfirmedInfo.cs b/src/Angor/Shared/Models/UnconfirmedInfo.cs new file mode 100644 index 00000000..d19813bb --- /dev/null +++ b/src/Angor/Shared/Models/UnconfirmedInfo.cs @@ -0,0 +1,7 @@ +namespace Angor.Shared.Models; + +public class UnconfirmedInfo +{ + public List PendingReceive { get; set; } = new(); + public List PendingSpent { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Angor/Shared/Services/ICacheStorage.cs b/src/Angor/Shared/Services/ICacheStorage.cs index e23e0159..f461fcd3 100644 --- a/src/Angor/Shared/Services/ICacheStorage.cs +++ b/src/Angor/Shared/Services/ICacheStorage.cs @@ -10,6 +10,6 @@ public interface ICacheStorage bool IsProjectInStorageById(string projectId); List? GetProjectIndexerData(); void SetProjectIndexerData(List list); - public List GetPendingSpendUtxo(); - public void SetPendingSpentUtxo(List list); + UnconfirmedInfo GetUnconfirmedInfo(); + void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo); } \ No newline at end of file diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index 026b88f2..99137082 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -39,12 +39,12 @@ public string GenerateWalletWords() } public Transaction AddInputsAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, + WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, FeeEstimation feeRate) { Network network = _networkConfiguration.GetNetwork(); - var utxoDataWithPaths = FindOutputsForTransaction((long)transaction.Outputs.Sum(_ => _.Value), accountInfo); + var utxoDataWithPaths = FindOutputsForTransaction((long)transaction.Outputs.Sum(_ => _.Value), accountInfo, unconfirmedInfo); var coins = GetUnspentOutputsForTransaction(walletWords, utxoDataWithPaths); var builder = new TransactionBuilder(network) @@ -60,9 +60,8 @@ public Transaction AddInputsAndSignTransaction(string changeAddress, Transaction return signTransaction; } - public Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, + WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, FeeEstimation feeRate) { Network network = _networkConfiguration.GetNetwork(); @@ -74,7 +73,7 @@ public Transaction AddFeeAndSignTransaction(string changeAddress, Transaction tr var virtualSize = clonedTransaction.GetVirtualSize(4); var fee = new FeeRate(Money.Satoshis(feeRate.FeeRate)).GetFee(virtualSize); - var utxoDataWithPaths = FindOutputsForTransaction((long)fee, accountInfo); + var utxoDataWithPaths = FindOutputsForTransaction((long)fee, accountInfo, unconfirmedInfo); var coins = GetUnspentOutputsForTransaction(walletWords, utxoDataWithPaths); var totalSats = coins.coins.Sum(s => s.Amount.Satoshi); @@ -132,10 +131,10 @@ public async Task> SendAmountToAddress(WalletWords return await PublishTransactionAsync(network, signedTransaction); } - private void UpdatePendingLists(AccountInfo accountInfo) + private void UpdatePendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) { // remove from the pending remove list if it was removed from the indexer - var pendingRemove = accountInfo.PendingRemove.ToList(); + var pendingRemove = unconfirmedInfo.PendingSpent.ToList(); foreach (var utxoData in pendingRemove) { foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) @@ -144,14 +143,14 @@ private void UpdatePendingLists(AccountInfo accountInfo) { if (addressInfo.UtxoData.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) { - accountInfo.PendingRemove.Remove(utxoData); + unconfirmedInfo.PendingSpent.Remove(utxoData); } } } } // remove from the pending add if it was removed from the indexer - var pendingAdd = accountInfo.PendingAdd.ToList(); + var pendingAdd = unconfirmedInfo.PendingReceive.ToList(); foreach (var utxoData in pendingAdd) { foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) @@ -160,14 +159,65 @@ private void UpdatePendingLists(AccountInfo accountInfo) { if (addressInfo.UtxoData.Any(_ => _.outpoint.ToString() == utxoData.outpoint.ToString())) { - accountInfo.PendingAdd.Remove(utxoData); + unconfirmedInfo.PendingReceive.Remove(utxoData); } } } } } - public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction, string changeAddress) + public void RemoveInputsAndOutputsFromPending(UnconfirmedInfo unconfirmedInfo, string trxid) + { + foreach (var utxoData in unconfirmedInfo.PendingSpent.ToList()) + { + if (utxoData.outpoint.transactionId == trxid) + { + unconfirmedInfo.PendingSpent.Remove(utxoData); + } + } + + foreach (var utxoData in unconfirmedInfo.PendingReceive.ToList()) + { + if (utxoData.outpoint.transactionId == trxid) + { + unconfirmedInfo.PendingReceive.Remove(utxoData); + } + } + } + + public void AddInputsAndOutputsAsPending(UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction) + { + var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); + var outputs = transaction.Outputs.AsIndexedOutputs(); + + foreach (var outPoint in inputs) + { + if (unconfirmedInfo.PendingSpent.All(_ => _.outpoint.ToString() != outPoint.ToString())) + { + unconfirmedInfo.PendingSpent.Add(new UtxoData + { + outpoint = new Outpoint { outputIndex = (int)outPoint.N, transactionId = outPoint.Hash.ToString() }, + }); + } + } + + foreach (var output in outputs) + { + var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; + + if (unconfirmedInfo.PendingReceive.All(_ => _.outpoint != outpoint)) + { + unconfirmedInfo.PendingReceive.Add(new UtxoData + { + scriptHex = output.TxOut.ScriptPubKey.ToHex(), + outpoint = outpoint, + value = output.TxOut.Value + }); + } + } + } + + public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction) { Network network = _networkConfiguration.GetNetwork(); @@ -183,9 +233,9 @@ public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Trans { if (utxoData.outpoint.ToString() == outPoint.ToString()) { - if (accountInfo.PendingRemove.All(_ => _.outpoint != utxoData.outpoint)) + if (unconfirmedInfo.PendingSpent.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) { - accountInfo.PendingRemove.Add(utxoData); + unconfirmedInfo.PendingSpent.Add(utxoData); } } } @@ -198,9 +248,9 @@ public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Trans { var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; - if (accountInfo.PendingAdd.All(_ => _.outpoint != outpoint)) + if (unconfirmedInfo.PendingReceive.All(_ => _.outpoint != outpoint)) { - accountInfo.PendingAdd.Add(new UtxoData + unconfirmedInfo.PendingReceive.Add(new UtxoData { address = addressInfo.Address, scriptHex = output.TxOut.ScriptPubKey.ToHex(), @@ -226,7 +276,7 @@ public async Task> PublishTransactionAsync(Network return new OperationResult { Success = false, Message = res }; } - public List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo) + public List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) { var utxos = accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo); @@ -238,7 +288,7 @@ public List FindOutputsForTransaction(long sendAmountat, Accou .OrderBy(o => o.utxo.blockIndex) .ThenByDescending(o => o.utxo.value)) { - if (accountInfo.PendingRemove.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) + if (unconfirmedInfo.PendingSpent.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) { continue; } @@ -361,7 +411,7 @@ private async Task UpdateAddressInfoUtxoData(AddressInfo addressInfo) } } - public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo) + public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) { ExtKey.UseBCForHMACSHA512 = true; Blockcore.NBitcoin.Crypto.Hashes.UseBCForHMACSHA512 = true; @@ -392,9 +442,7 @@ public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo accountInfo.ChangeAddressesInfo.Add(changeAddressInfo); } - UpdatePendingLists(accountInfo); - - accountInfo.CalculateBalance(); + UpdatePendingLists(accountInfo, unconfirmedInfo); } private async Task<(int,List)> FetchAddressesDataForPubKeyAsync(int scanIndex, string ExtendedPubKey, Network network, bool isChange) @@ -517,13 +565,13 @@ public async Task> GetFeeEstimationAsync() } } - public decimal CalculateTransactionFee(SendInfo sendInfo,AccountInfo accountInfo, long feeRate) + public decimal CalculateTransactionFee(SendInfo sendInfo, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, long feeRate) { var network = _networkConfiguration.GetNetwork(); if (sendInfo.SendUtxos.Count == 0) { - var utxosToSpend = FindOutputsForTransaction(sendInfo.SendAmountSat, accountInfo); + var utxosToSpend = FindOutputsForTransaction(sendInfo.SendAmountSat, accountInfo, unconfirmedInfo); foreach (var data in utxosToSpend) //TODO move this out of the fee calculation { From 3cde90e196a503ebcc8c17c857b9e7995fef22a0 Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 13 Dec 2023 18:03:17 +0000 Subject: [PATCH 09/19] separate the pending account structure from other pending outputs (like founder spend) --- src/Angor/Client/Pages/Index.razor | 4 +- src/Angor/Client/Pages/Spend.razor | 17 +++-- src/Angor/Client/Pages/Wallet.razor | 2 +- src/Angor/Shared/IWalletOperations.cs | 5 +- src/Angor/Shared/Models/AccountBalanceInfo.cs | 4 +- src/Angor/Shared/Models/Outpoint.cs | 5 ++ src/Angor/Shared/Models/UnconfirmedInfo.cs | 32 +++++++- src/Angor/Shared/WalletOperations.cs | 75 +++---------------- 8 files changed, 65 insertions(+), 79 deletions(-) diff --git a/src/Angor/Client/Pages/Index.razor b/src/Angor/Client/Pages/Index.razor index 5053674e..9d5a10af 100644 --- a/src/Angor/Client/Pages/Index.razor +++ b/src/Angor/Client/Pages/Index.razor @@ -4,9 +4,9 @@

Welcome to Angor !

-

+


A decentralized crowdfunding platform built on the Bitcoin network.

Think of it as a trustless, risk-minimized ICO. -
+ diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index 2c095b53..e4c762e4 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -257,6 +257,7 @@ { List trxs = new(); var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + bool modified = false; foreach (StageData stageData in stageDatas) { @@ -280,19 +281,23 @@ { item.IsSepnt = true; - _WalletOperations.RemoveInputsAndOutputsFromPending(unconfirmedInfo, item.Trxid); - _WalletOperations.RemoveInputsAndOutputsFromPending(unconfirmedInfo, output.SpentInTransaction); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + unconfirmedInfo.RemoveInputsFromPending(item.Trxid); + modified = true; continue; } - if (unconfirmedInfo.PendingSpent.Any(a => a.ToString() == new Outpoint { transactionId = item.Trxid, outputIndex = item.Outputindex }.ToString())) + if (unconfirmedInfo.PendingSpent.Any(intput => intput.ToString() == new Outpoint { transactionId = item.Trxid, outputIndex = item.Outputindex }.ToString())) { item.IsSepnt = true; } } } + + if (modified) + { + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + } } private async Task ClaimCoins(int stageId) @@ -377,8 +382,10 @@ return response; // add all outptus to the pending list + var accountInfo = storage.GetAccountInfo(network.Name); var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - _WalletOperations.AddInputsAndOutputsAsPending(unconfirmedInfo, signedTransaction); + _WalletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, unconfirmedInfo, signedTransaction); + unconfirmedInfo.AddInputsAsPending(signedTransaction); _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); // mark stage as spent diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 6321627b..aaa4555b 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -610,7 +610,7 @@ if (res.Success) { - _walletOperations.UpdateAccountInfoWithSpentTransaction(accountInfo, unconfirmedInfo, res.Data); + _walletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, unconfirmedInfo, res.Data); accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); storage.SetAccountInfo(network.Name, accountInfo); _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); diff --git a/src/Angor/Shared/IWalletOperations.cs b/src/Angor/Shared/IWalletOperations.cs index 9380c5e8..dd011531 100644 --- a/src/Angor/Shared/IWalletOperations.cs +++ b/src/Angor/Shared/IWalletOperations.cs @@ -29,8 +29,5 @@ Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transacti WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, FeeEstimation feeRate); - void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction); - - void AddInputsAndOutputsAsPending(UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction); - void RemoveInputsAndOutputsFromPending(UnconfirmedInfo unconfirmedInfo, string trxid); + void UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction); } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountBalanceInfo.cs b/src/Angor/Shared/Models/AccountBalanceInfo.cs index c5f49254..25d2b21e 100644 --- a/src/Angor/Shared/Models/AccountBalanceInfo.cs +++ b/src/Angor/Shared/Models/AccountBalanceInfo.cs @@ -12,9 +12,9 @@ public class AccountBalanceInfo public static AccountBalanceInfo GetBalance(AccountInfo account, UnconfirmedInfo unconfirmedInfo) { var balance = account.AddressesInfo.Concat(account.ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); - var balanceSpent = unconfirmedInfo.PendingSpent.Sum(s => s.value); + var balanceSpent = unconfirmedInfo.AccountPendingSpent.Sum(s => s.value); - var balanceUnconfirmed = unconfirmedInfo.PendingReceive.Sum(s => s.value); + var balanceUnconfirmed = unconfirmedInfo.AccountPendingReceive.Sum(s => s.value); return new AccountBalanceInfo { diff --git a/src/Angor/Shared/Models/Outpoint.cs b/src/Angor/Shared/Models/Outpoint.cs index 7e9b4f6a..fff2045b 100644 --- a/src/Angor/Shared/Models/Outpoint.cs +++ b/src/Angor/Shared/Models/Outpoint.cs @@ -17,4 +17,9 @@ public OutPoint ToOutPoint() { return new OutPoint(uint256.Parse(transactionId), outputIndex); } + + public static Outpoint FromOutPoint(OutPoint outPoint) + { + return new Outpoint { outputIndex = (int)outPoint.N, transactionId = outPoint.Hash.ToString() }; + } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/UnconfirmedInfo.cs b/src/Angor/Shared/Models/UnconfirmedInfo.cs index d19813bb..6de01005 100644 --- a/src/Angor/Shared/Models/UnconfirmedInfo.cs +++ b/src/Angor/Shared/Models/UnconfirmedInfo.cs @@ -1,7 +1,35 @@ +using Blockcore.Networks; + namespace Angor.Shared.Models; public class UnconfirmedInfo { - public List PendingReceive { get; set; } = new(); - public List PendingSpent { get; set; } = new(); + public List AccountPendingReceive { get; set; } = new(); + public List AccountPendingSpent { get; set; } = new(); + + public List PendingSpent { get; set; } = new(); + + public void RemoveInputsFromPending(string trxid) + { + foreach (var input in PendingSpent.ToList()) + { + if (input.transactionId == trxid) + { + PendingSpent.Remove(input); + } + } + } + + public void AddInputsAsPending(Blockcore.Consensus.TransactionInfo.Transaction transaction) + { + var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); + + foreach (var outPoint in inputs) + { + if (PendingSpent.All(input => input.ToString() != outPoint.ToString())) + { + PendingSpent.Add(Outpoint.FromOutPoint(outPoint)); + } + } + } } \ No newline at end of file diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index 99137082..92908ecf 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -131,10 +131,10 @@ public async Task> SendAmountToAddress(WalletWords return await PublishTransactionAsync(network, signedTransaction); } - private void UpdatePendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) + private void UpdateAccountPendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) { // remove from the pending remove list if it was removed from the indexer - var pendingRemove = unconfirmedInfo.PendingSpent.ToList(); + var pendingRemove = unconfirmedInfo.AccountPendingSpent.ToList(); foreach (var utxoData in pendingRemove) { foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) @@ -143,14 +143,14 @@ private void UpdatePendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfi { if (addressInfo.UtxoData.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) { - unconfirmedInfo.PendingSpent.Remove(utxoData); + unconfirmedInfo.AccountPendingSpent.Remove(utxoData); } } } } // remove from the pending add if it was removed from the indexer - var pendingAdd = unconfirmedInfo.PendingReceive.ToList(); + var pendingAdd = unconfirmedInfo.AccountPendingReceive.ToList(); foreach (var utxoData in pendingAdd) { foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) @@ -159,65 +159,14 @@ private void UpdatePendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfi { if (addressInfo.UtxoData.Any(_ => _.outpoint.ToString() == utxoData.outpoint.ToString())) { - unconfirmedInfo.PendingReceive.Remove(utxoData); + unconfirmedInfo.AccountPendingReceive.Remove(utxoData); } } } } } - public void RemoveInputsAndOutputsFromPending(UnconfirmedInfo unconfirmedInfo, string trxid) - { - foreach (var utxoData in unconfirmedInfo.PendingSpent.ToList()) - { - if (utxoData.outpoint.transactionId == trxid) - { - unconfirmedInfo.PendingSpent.Remove(utxoData); - } - } - - foreach (var utxoData in unconfirmedInfo.PendingReceive.ToList()) - { - if (utxoData.outpoint.transactionId == trxid) - { - unconfirmedInfo.PendingReceive.Remove(utxoData); - } - } - } - - public void AddInputsAndOutputsAsPending(UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction) - { - var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); - var outputs = transaction.Outputs.AsIndexedOutputs(); - - foreach (var outPoint in inputs) - { - if (unconfirmedInfo.PendingSpent.All(_ => _.outpoint.ToString() != outPoint.ToString())) - { - unconfirmedInfo.PendingSpent.Add(new UtxoData - { - outpoint = new Outpoint { outputIndex = (int)outPoint.N, transactionId = outPoint.Hash.ToString() }, - }); - } - } - - foreach (var output in outputs) - { - var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; - - if (unconfirmedInfo.PendingReceive.All(_ => _.outpoint != outpoint)) - { - unconfirmedInfo.PendingReceive.Add(new UtxoData - { - scriptHex = output.TxOut.ScriptPubKey.ToHex(), - outpoint = outpoint, - value = output.TxOut.Value - }); - } - } - } - - public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Blockcore.Consensus.TransactionInfo.Transaction transaction) + public void UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction) { Network network = _networkConfiguration.GetNetwork(); @@ -233,9 +182,9 @@ public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Uncon { if (utxoData.outpoint.ToString() == outPoint.ToString()) { - if (unconfirmedInfo.PendingSpent.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) + if (unconfirmedInfo.AccountPendingSpent.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) { - unconfirmedInfo.PendingSpent.Add(utxoData); + unconfirmedInfo.AccountPendingSpent.Add(utxoData); } } } @@ -248,9 +197,9 @@ public void UpdateAccountInfoWithSpentTransaction(AccountInfo accountInfo, Uncon { var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; - if (unconfirmedInfo.PendingReceive.All(_ => _.outpoint != outpoint)) + if (unconfirmedInfo.AccountPendingReceive.All(_ => _.outpoint != outpoint)) { - unconfirmedInfo.PendingReceive.Add(new UtxoData + unconfirmedInfo.AccountPendingReceive.Add(new UtxoData { address = addressInfo.Address, scriptHex = output.TxOut.ScriptPubKey.ToHex(), @@ -288,7 +237,7 @@ public List FindOutputsForTransaction(long sendAmountat, Accou .OrderBy(o => o.utxo.blockIndex) .ThenByDescending(o => o.utxo.value)) { - if (unconfirmedInfo.PendingSpent.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) + if (unconfirmedInfo.AccountPendingSpent.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) { continue; } @@ -442,7 +391,7 @@ public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo accountInfo.ChangeAddressesInfo.Add(changeAddressInfo); } - UpdatePendingLists(accountInfo, unconfirmedInfo); + UpdateAccountPendingLists(accountInfo, unconfirmedInfo); } private async Task<(int,List)> FetchAddressesDataForPubKeyAsync(int scanIndex, string ExtendedPubKey, Network network, bool isChange) From 747f66b3f5c2faa6987ca0899aca55ca10235a7e Mon Sep 17 00:00:00 2001 From: dangershony Date: Wed, 13 Dec 2023 23:02:45 +0000 Subject: [PATCH 10/19] Fix qrcode to be a popup --- src/Angor/Client/Components/ShowQrCode.razor | 100 ++++++++++++++----- src/Angor/Client/Pages/Wallet.razor | 21 ++-- src/Angor/Client/wwwroot/css/app.css | 23 +++++ 3 files changed, 110 insertions(+), 34 deletions(-) diff --git a/src/Angor/Client/Components/ShowQrCode.razor b/src/Angor/Client/Components/ShowQrCode.razor index dd54bf46..eff0a0e9 100644 --- a/src/Angor/Client/Components/ShowQrCode.razor +++ b/src/Angor/Client/Components/ShowQrCode.razor @@ -1,51 +1,71 @@ @using Angor.Shared.Services @using Angor.Client.Models +@using Angor.Client.Services @using Nostr.Client.Messages @using Nostr.Client.Messages.Metadata @using QRCoder +@inject IClipboardService _clipboardService -
-

QR Code

- @if (!string.IsNullOrEmpty(base64qrcode)) - { - QR Code - } + +
+
+ +@if (showModal) +{ + +} + @code { [Parameter] public string Data { get; set; } - private static string lastqrcode; - private static string lastaddress; private string base64qrcode; - protected override async Task OnInitializedAsync() + private bool showModal = false; + + private void ShowModal() { GenerateQRCode(Data); + showModal = true; } - public Task GenerateQRCode(string newData) + private void HideModal() { - Data = newData; - - if (lastaddress == Data) - { - base64qrcode = lastqrcode; - return Task.CompletedTask; - } - - return Task.Run(() => - { - base64qrcode = GenerateQRCodeInternal(Data); - lastqrcode = base64qrcode; - lastaddress = Data; + showModal = false; + } - StateHasChanged(); + public void GenerateQRCode(string newData) + { + Data = newData; + + base64qrcode = GenerateQRCodeInternal(Data); - }); + StateHasChanged(); } public static string GenerateQRCodeInternal(string content) @@ -55,4 +75,32 @@ using PngByteQRCode pngByteQRCode = new PngByteQRCode(qrCodeData); return Convert.ToBase64String(pngByteQRCode.GetGraphic(10)); } -} \ No newline at end of file + + private async Task CopyToClipboard() + { + await _clipboardService.WriteTextAsync(Data); + + } +} + + \ No newline at end of file diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 61ea90e3..8ba022b1 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -162,20 +162,25 @@
-
-

Receive Address

-

@accountBalanceInfo.AccountInfo.GetNextReceiveAddress()

- -
+
+
+
+
+

@accountBalanceInfo.AccountInfo.GetNextReceiveAddress()

+ +
+
+
- - + +
- diff --git a/src/Angor/Client/wwwroot/css/app.css b/src/Angor/Client/wwwroot/css/app.css index 5275d4f6..3e430f98 100644 --- a/src/Angor/Client/wwwroot/css/app.css +++ b/src/Angor/Client/wwwroot/css/app.css @@ -221,6 +221,29 @@ tr[style*="cursor: pointer;"]:hover { /* Add any additional styling as needed */ } +.address-container { + background-color: #f8f9fa; /* Light background */ + border: 1px solid #ddd; /* Light border */ + border-radius: 10px; /* Rounded corners */ + padding: 10px; /* Padding inside the container */ + cursor: pointer; /* Change cursor on hover */ + text-align: center; /* Center-align the text */ + margin-top: 10px; /* Spacing from the QR code */ +} + +.address-container:hover { + background-color: #e9ecef; /* Slightly darker background on hover */ +} + +.address-container-wrapper { + max-width: 450px; /* Adjust as needed */ + margin-right: 30px; /* Space between address and QR code button */ +} + +.address-copy-button { + margin-left: 10px; /* Space between address and button */ + cursor: pointer; +} /*long duration spinner*/ From dcb14f2b3180c8d542afba78bd68f6f6f418c78d Mon Sep 17 00:00:00 2001 From: dangershony Date: Thu, 14 Dec 2023 00:19:40 +0000 Subject: [PATCH 11/19] Add support for pending in the recovery page --- src/Angor/Client/Pages/Recover.razor | 45 +++++++++++++++++++++- src/Angor/Client/Pages/Spend.razor | 22 +++++------ src/Angor/Shared/Models/Outpoint.cs | 5 +++ src/Angor/Shared/Models/UnconfirmedInfo.cs | 9 ++++- 4 files changed, 66 insertions(+), 15 deletions(-) diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index 0bdebc29..23a0e76f 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -44,7 +44,7 @@

Total funds to recover = @StageInfo.TotalSpendable @network.CoinTicker

- @if (StageInfo.Trxid == null) + @if (trxNotFound) {

Transaction was not found it may still be confirming

} @@ -230,7 +230,7 @@ private FeeData feeData = new(); - + private bool trxNotFound = false; public class StageData { public string Trxid; @@ -297,9 +297,12 @@ if (trx == null) { explorerLink = _NetworkConfiguration.GetExplorerUrl().Url + $"/transaction/{recoverySigs.TransactionId}"; + trxNotFound = true; return; } + trxNotFound = false; + if (investmentTransaction == null) { var trxHex = await _IndexerService.GetTransactionHexByIdAsync(recoverySigs.TransactionId); @@ -343,10 +346,36 @@ return; } + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + bool modified = false; + + foreach (var infoOutput in StageInfo.TransactionInfo.Outputs) + { + if (!string.IsNullOrEmpty(infoOutput.SpentInTransaction)) + { + unconfirmedInfo.RemoveInputFromPending(Outpoint.Create(StageInfo.TransactionInfo.TransactionId, infoOutput.Index)); + modified = true; + } + } + + if (modified) + { + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + } + var penaltyExpieryDate = Utils.UnixTimeToDateTime(StageInfo.TransactionInfo.Timestamp).AddDays(project.PenaltyDays); foreach (var item in StageInfo.Items) { + if (unconfirmedInfo.IsInPendingSpent(Outpoint.Create(StageInfo.Trxid, item.Outputindex))) + { + item.IsSpent = true; + item.SpentTo = "pending confirmations"; + item.ProjectScriptType = new ProjectScriptType { ScriptType = ProjectScriptTypeEnum.Unknown }; + + continue; + } + var output = StageInfo.TransactionInfo.Outputs.ElementAt(item.Outputindex); if (!string.IsNullOrEmpty(output.SpentInTransaction)) @@ -500,6 +529,10 @@ if (!response.Success) return response; + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + unconfirmedInfo.AddInputsAsPending(recoveryTransaction); + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + return new OperationResult { Success = response.Success, Message = response.Message }; }); @@ -592,6 +625,10 @@ if (!response.Success) return response; + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + unconfirmedInfo.AddInputsAsPending(releaseRecoveryTransaction); + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + return new OperationResult { Success = response.Success, Message = response.Message }; }); @@ -684,6 +721,10 @@ if (!response.Success) return response; + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + unconfirmedInfo.AddInputsAsPending(endOfProjectTransaction); + _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + return new OperationResult { Success = response.Success, Message = response.Message }; }); diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index e4c762e4..28c48f5d 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -36,8 +36,8 @@ @foreach (var stage in stageDatas) { bool stageisActive = stage.Stage.ReleaseDate < DateTime.UtcNow; - var investedCount = stage.Items.Count(c => c.IsSepnt == false); - var investedAmount = stage.Items.Where(c => c.IsSepnt == false).Sum(c => c.Amount); + var investedCount = stage.Items.Count(c => c.IsSpent == false); + var investedAmount = stage.Items.Where(c => c.IsSpent == false).Sum(c => c.Amount);
@@ -69,13 +69,13 @@
- +

@transaction.Amount @network.CoinTicker - utxo : @transaction.Trxid-@transaction.Outputindex - @if (transaction.IsSepnt) + @if (transaction.IsSpent) {

spent

} @@ -171,7 +171,7 @@ public int Outputindex; public string OutputAddress; public decimal Amount; - public bool IsSepnt; + public bool IsSpent; } List stageDatas = new(); @@ -263,7 +263,7 @@ { foreach (var item in stageData.Items) { - if (item.IsSepnt) + if (item.IsSpent) continue; QueryTransaction? trx = trxs.FirstOrDefault(f => f.TransactionId == item.Trxid); @@ -279,17 +279,17 @@ if (!string.IsNullOrEmpty(output.SpentInTransaction)) { - item.IsSepnt = true; + item.IsSpent = true; - unconfirmedInfo.RemoveInputsFromPending(item.Trxid); + unconfirmedInfo.RemoveInputFromPending(Outpoint.Create(item.Trxid, item.Outputindex)); modified = true; continue; } - if (unconfirmedInfo.PendingSpent.Any(intput => intput.ToString() == new Outpoint { transactionId = item.Trxid, outputIndex = item.Outputindex }.ToString())) + if (unconfirmedInfo.IsInPendingSpent(Outpoint.Create(item.Trxid, item.Outputindex))) { - item.IsSepnt = true; + item.IsSpent = true; } } } @@ -392,7 +392,7 @@ stageDatas.FirstOrDefault(_ => _.StageIndex == selectedStageId)?.Items.ForEach(_ => { if (signedTransaction.Inputs.Any(a => _.Trxid == a.PrevOut.Hash.ToString() && _.Outputindex == a.PrevOut.N)) - _.IsSepnt = true; + _.IsSpent = true; }); return new OperationResult { Success = response.Success, Message = response.Message }; diff --git a/src/Angor/Shared/Models/Outpoint.cs b/src/Angor/Shared/Models/Outpoint.cs index fff2045b..2ee3d1d7 100644 --- a/src/Angor/Shared/Models/Outpoint.cs +++ b/src/Angor/Shared/Models/Outpoint.cs @@ -22,4 +22,9 @@ public static Outpoint FromOutPoint(OutPoint outPoint) { return new Outpoint { outputIndex = (int)outPoint.N, transactionId = outPoint.Hash.ToString() }; } + + public static Outpoint Create(string trxid, int index) + { + return new Outpoint { outputIndex = index, transactionId = trxid }; + } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/UnconfirmedInfo.cs b/src/Angor/Shared/Models/UnconfirmedInfo.cs index 6de01005..e825b450 100644 --- a/src/Angor/Shared/Models/UnconfirmedInfo.cs +++ b/src/Angor/Shared/Models/UnconfirmedInfo.cs @@ -9,11 +9,16 @@ public class UnconfirmedInfo public List PendingSpent { get; set; } = new(); - public void RemoveInputsFromPending(string trxid) + public bool IsInPendingSpent(Outpoint outpoint) + { + return PendingSpent.Any(intput => intput.ToString() == outpoint.ToString()); + } + + public void RemoveInputFromPending(Outpoint outpoint) { foreach (var input in PendingSpent.ToList()) { - if (input.transactionId == trxid) + if (input.ToString() == outpoint.ToString()) { PendingSpent.Remove(input); } From ab72b80dc91d8cbf1bb437ffd5c7e7f3194abdff Mon Sep 17 00:00:00 2001 From: Milad Raeisi Date: Thu, 14 Dec 2023 04:47:20 +0400 Subject: [PATCH 12/19] Making minor changes in the display of the qrcode modal button --- src/Angor/Client/Components/ShowQrCode.razor | 13 +++++-------- src/Angor/Client/Pages/Wallet.razor | 4 ++-- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Angor/Client/Components/ShowQrCode.razor b/src/Angor/Client/Components/ShowQrCode.razor index eff0a0e9..532ee546 100644 --- a/src/Angor/Client/Components/ShowQrCode.razor +++ b/src/Angor/Client/Components/ShowQrCode.razor @@ -8,15 +8,14 @@ @inject IClipboardService _clipboardService -
- -
+ @if (showModal) {
- @foreach (var addressInfo in accountBalanceInfo.AccountInfo.AddressesInfo.Union(accountBalanceInfo.AccountInfo.ChangeAddressesInfo)) + @foreach (var addressInfo in accountBalanceInfo.AccountInfo.AllAddresses()) { var total = addressInfo.Balance; var count = addressInfo.UtxoData.Count(); @@ -404,7 +404,7 @@ var accountInfo = storage.GetAccountInfo(network.Name); var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); + accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); } return Task.CompletedTask; @@ -424,7 +424,7 @@ storage.SetAccountInfo(network.Name, accountInfo); _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); - accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); + accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); showQrCode.GenerateQRCode(accountBalanceInfo.AccountInfo.GetNextReceiveAddress()); @@ -502,7 +502,8 @@ private void DeleteWallet() { walletWordsModal = false; - storage.DeleteAccountInfo(_networkConfiguration.GetNetwork().Name); + storage.DeleteAccountInfo(network.Name); + _cacheStorage.DeleteUnconfirmedInfo(); storage.DeleteWalletPubkey(); _walletStorage.DeleteWallet(); storage.DeleteFounderKeys(); @@ -615,7 +616,7 @@ if (res.Success) { _walletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, unconfirmedInfo, res.Data); - accountBalanceInfo = AccountBalanceInfo.GetBalance(accountInfo, unconfirmedInfo); + accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); storage.SetAccountInfo(network.Name, accountInfo); _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); } diff --git a/src/Angor/Client/Storage/LocalSessionStorage.cs b/src/Angor/Client/Storage/LocalSessionStorage.cs index 192a4ee3..415eed17 100644 --- a/src/Angor/Client/Storage/LocalSessionStorage.cs +++ b/src/Angor/Client/Storage/LocalSessionStorage.cs @@ -49,4 +49,8 @@ public void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo) { _sessionStorageService.SetItem("unconfirmed-info", unconfirmedInfo); } + public void DeleteUnconfirmedInfo() + { + _sessionStorageService.RemoveItem("unconfirmed-info"); + } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountBalanceInfo.cs b/src/Angor/Shared/Models/AccountBalanceInfo.cs index 25d2b21e..377a43e6 100644 --- a/src/Angor/Shared/Models/AccountBalanceInfo.cs +++ b/src/Angor/Shared/Models/AccountBalanceInfo.cs @@ -5,23 +5,20 @@ public class AccountBalanceInfo public long TotalBalance { get; set; } public long TotalUnconfirmedBalance { get; set; } - public AccountInfo AccountInfo { get; private set; } = new (); public UnconfirmedInfo UnconfirmedInfo { get; private set; } = new (); - public static AccountBalanceInfo GetBalance(AccountInfo account, UnconfirmedInfo unconfirmedInfo) + public void CalculateBalance(AccountInfo account, UnconfirmedInfo unconfirmedInfo) { - var balance = account.AddressesInfo.Concat(account.ChangeAddressesInfo).SelectMany(s => s.UtxoData).Sum(s => s.value); - var balanceSpent = unconfirmedInfo.AccountPendingSpent.Sum(s => s.value); + AccountInfo = account; + UnconfirmedInfo = unconfirmedInfo; + + var balance = AccountInfo.AllAddresses().SelectMany(s => s.UtxoData).Sum(s => s.value); + var balanceSpent = UnconfirmedInfo.AccountPendingSpent.Sum(s => s.value); - var balanceUnconfirmed = unconfirmedInfo.AccountPendingReceive.Sum(s => s.value); + var balanceUnconfirmed = UnconfirmedInfo.AccountPendingReceive.Sum(s => s.value); - return new AccountBalanceInfo - { - TotalBalance = balance - balanceSpent, - TotalUnconfirmedBalance = balanceUnconfirmed, - AccountInfo = account, - UnconfirmedInfo = unconfirmedInfo - }; + TotalBalance = balance - balanceSpent; + TotalUnconfirmedBalance = balanceUnconfirmed; } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountInfo.cs b/src/Angor/Shared/Models/AccountInfo.cs index d85b64b7..08fbf54d 100644 --- a/src/Angor/Shared/Models/AccountInfo.cs +++ b/src/Angor/Shared/Models/AccountInfo.cs @@ -10,7 +10,15 @@ public class AccountInfo public List ChangeAddressesInfo { get; set; } = new(); public int InvestmentsCount { get; set; } //TODO David handle the set logic - + + public IEnumerable AllAddresses() + { + foreach (var addressInfo in AddressesInfo.Concat(ChangeAddressesInfo)) + { + yield return addressInfo; + } + } + public string? GetNextReceiveAddress() { return AddressesInfo.Last()?.Address; diff --git a/src/Angor/Shared/Services/ICacheStorage.cs b/src/Angor/Shared/Services/ICacheStorage.cs index f461fcd3..0476abdc 100644 --- a/src/Angor/Shared/Services/ICacheStorage.cs +++ b/src/Angor/Shared/Services/ICacheStorage.cs @@ -12,4 +12,5 @@ public interface ICacheStorage void SetProjectIndexerData(List list); UnconfirmedInfo GetUnconfirmedInfo(); void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo); + void DeleteUnconfirmedInfo(); } \ No newline at end of file diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index 92908ecf..da85ddd6 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -137,7 +137,7 @@ private void UpdateAccountPendingLists(AccountInfo accountInfo, UnconfirmedInfo var pendingRemove = unconfirmedInfo.AccountPendingSpent.ToList(); foreach (var utxoData in pendingRemove) { - foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + foreach (var addressInfo in accountInfo.AllAddresses()) { if (addressInfo.Address == utxoData.address) { @@ -153,7 +153,7 @@ private void UpdateAccountPendingLists(AccountInfo accountInfo, UnconfirmedInfo var pendingAdd = unconfirmedInfo.AccountPendingReceive.ToList(); foreach (var utxoData in pendingAdd) { - foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + foreach (var addressInfo in accountInfo.AllAddresses()) { if (addressInfo.Address == utxoData.address) { @@ -173,7 +173,7 @@ public void UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo account var outputs = transaction.Outputs.AsIndexedOutputs(); var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); - foreach (var addressInfo in accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo)) + foreach (var addressInfo in accountInfo.AllAddresses()) { // find all spent inputs to mark them as spent foreach (var utxoData in addressInfo.UtxoData) @@ -227,21 +227,15 @@ public async Task> PublishTransactionAsync(Network public List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) { - var utxos = accountInfo.AddressesInfo.Concat(accountInfo.ChangeAddressesInfo); - var utxosToSpend = new List(); long total = 0; - foreach (var utxoData in utxos.SelectMany(_ => _.UtxoData + foreach (var utxoData in accountInfo.AllAddresses().SelectMany(_ => _.UtxoData + .Where(utxow => unconfirmedInfo.AccountPendingSpent.All(p => p.outpoint.ToString() != utxow.outpoint.ToString())) .Select(u => new { path = _.HdPath, utxo = u })) .OrderBy(o => o.utxo.blockIndex) .ThenByDescending(o => o.utxo.value)) { - if (unconfirmedInfo.AccountPendingSpent.Any(p => p.outpoint.ToString() == utxoData.utxo.outpoint.ToString())) - { - continue; - } - utxosToSpend.Add(new UtxoDataWithPath { HdPath = utxoData.path, UtxoData = utxoData.utxo }); total += utxoData.utxo.value; From f1dc099989c4dbcbee15f10718abadb38f334065 Mon Sep 17 00:00:00 2001 From: dangershony Date: Thu, 14 Dec 2023 20:01:43 +0000 Subject: [PATCH 14/19] Fix a bug on the recover page and change the spinner --- src/Angor/Client/Pages/Invest.razor | 14 ++---- src/Angor/Client/Pages/Recover.razor | 4 +- src/Angor/Client/wwwroot/css/app.css | 74 ++++++++-------------------- 3 files changed, 26 insertions(+), 66 deletions(-) diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index f5744858..cebcddc6 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -197,20 +197,14 @@ } else { -
+
-

Waiting for the founder

+

Waiting for the founder to approve

-

Waiting for the founder to sign the investment request

+ @*

Waiting for the founder to sign the investment request

*@ -
-
-
-
Waiting...
-
- - @* *@ +
} diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index 3caa9fd9..efe9f319 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -46,7 +46,7 @@ @if (trxNotFound) { -

Transaction was not found it may still be confirming

+

Transaction was not found it may still be confirming

} @if (StageInfo.CanRelease) @@ -442,7 +442,7 @@ private void AddTransactionToPending(Transaction transaction) { var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - unconfirmedInfo.AddInputsAsPending(recoveryTransaction); + unconfirmedInfo.AddInputsAsPending(transaction); _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); } diff --git a/src/Angor/Client/wwwroot/css/app.css b/src/Angor/Client/wwwroot/css/app.css index 3e430f98..ab0d6d68 100644 --- a/src/Angor/Client/wwwroot/css/app.css +++ b/src/Angor/Client/wwwroot/css/app.css @@ -193,6 +193,26 @@ tr[style*="cursor: pointer;"]:hover { background-size: 90%; /* Adjust the size of the logo as needed */ } +.loader-slow { + border: 12px solid #eaeaea; /* Light grey for the base circle */ + border-top: 12px solid #f7931a; /* Bitcoin orange for the rotating part */ + border-radius: 50%; + width: 100px; /* Smaller width */ + height: 100px; /* Smaller height */ + animation: spin 20s linear infinite; + background: url('../bitcoin-logo.png') no-repeat center; + background-size: 90%; /* Adjust the size of the logo as needed */ + margin: 0 auto; +} + +.loader-slow-container { + display: flex; /* Enable Flexbox */ + justify-content: center; /* Center children horizontally */ + align-items: center; /* Center children vertically */ + width: 100%; /* Full width of the container */ + height: 100px; /* Adjust the height as needed */ +} + /* Safari */ @-webkit-keyframes spin { 0% { @@ -244,57 +264,3 @@ tr[style*="cursor: pointer;"]:hover { margin-left: 10px; /* Space between address and button */ cursor: pointer; } - -/*long duration spinner*/ - -.long-duration-loader { - width: 100px; - height: 100px; - position: relative; -} - -.spinner { - border: 8px solid #e0e0e0; /* Light color for base */ - border-top: 8px solid #3498db; /* Calming blue for spinner */ - border-radius: 50%; - width: 100%; - height: 100%; - animation: spin 20s linear infinite; -} - -.progress-bar { - position: absolute; - bottom: 0; - background: #2ecc71; /* Green color for progress */ - height: 10%; - animation: fillBar 864000s linear forwards; /* Long duration for filling */ -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - - 100% { - transform: rotate(360deg); - } -} - -@keyframes fillBar { - 0% { - width: 0%; - } - - 100% { - width: 100%; - } -} - -.loader-text { - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-size: 14px; - color: #333; -} From d133ccb9ad62c254c01d407bef4ce3065fb6d784 Mon Sep 17 00:00:00 2001 From: dangershony Date: Fri, 15 Dec 2023 12:46:32 +0000 Subject: [PATCH 15/19] Fix page titles --- src/Angor/Client/Pages/Browse.razor | 15 ++++++++------- src/Angor/Client/Pages/Founder.razor | 21 +++++++++++---------- src/Angor/Client/Pages/Index.razor | 16 +++++++++------- src/Angor/Client/Pages/Invest.razor | 3 +-- src/Angor/Client/Pages/Penalties.razor | 2 +- src/Angor/Client/Pages/Recover.razor | 6 ++---- src/Angor/Client/Pages/Settings.razor | 6 +++--- src/Angor/Client/Pages/Signatures.razor | 4 +--- src/Angor/Client/Pages/Spend.razor | 7 +++---- src/Angor/Client/Pages/View.razor | 2 +- 10 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index b7b847cd..0d7b0812 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -8,21 +8,22 @@ @inject IRelayService _RelayService @inject IIndexerService _IndexerService -
+
+

Browse Projects

+ + +
-

Browse Projects

- -
- +
-

+

@@ -47,7 +48,7 @@
@project.ProjectIdentifier

Nostr ID: @(NostrPublicKey.FromHex(project.NostrPubKey).Bech32)

- @if(searchInProgress) + @if (searchInProgress) {
} diff --git a/src/Angor/Client/Pages/Founder.razor b/src/Angor/Client/Pages/Founder.razor index 23c9b45e..430766fb 100644 --- a/src/Angor/Client/Pages/Founder.razor +++ b/src/Angor/Client/Pages/Founder.razor @@ -19,24 +19,25 @@ return; } -
+
+

Founder Page

+ + +
-

Founder Page

- -

Welcome to the founder page! Here you can create a new project or view your existing project.

- +

Creating a project requires an on-chain transaction and a nostr did

- + @if (founderProjects.Count == 0) {

No projects found.

- +
@if (scanningForProjects) @@ -45,12 +46,12 @@ } else { - +
}
- + } else { @@ -63,7 +64,7 @@
} } - +
diff --git a/src/Angor/Client/Pages/Index.razor b/src/Angor/Client/Pages/Index.razor index 9d5a10af..65c6d76f 100644 --- a/src/Angor/Client/Pages/Index.razor +++ b/src/Angor/Client/Pages/Index.razor @@ -2,11 +2,13 @@ Index -

Welcome to Angor !

+
+

Welcome to Angor !

-
-
- A decentralized crowdfunding platform built on the Bitcoin network. -

- Think of it as a trustless, risk-minimized ICO. -
+
+
+ A decentralized crowdfunding platform built on the Bitcoin network. +

+ Think of it as a trustless, risk-minimized ICO. +
+
\ No newline at end of file diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index cebcddc6..12bf1055 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -30,7 +30,6 @@ @inject IInvestorTransactionActions _InvestorTransactionActions - @if (!hasWallet) { NavigationManager.NavigateTo($"/wallet"); @@ -56,7 +55,7 @@ }
-

Investment Page

+

Investment Page

Here is a small explanation of the project. You can view more details about the project here.

ProjectId: @ProjectId

diff --git a/src/Angor/Client/Pages/Penalties.razor b/src/Angor/Client/Pages/Penalties.razor index b8a1e692..642f192c 100644 --- a/src/Angor/Client/Pages/Penalties.razor +++ b/src/Angor/Client/Pages/Penalties.razor @@ -18,7 +18,7 @@ }
-

View Penalties

+

View Penalties

diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index efe9f319..63bab450 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -27,10 +27,8 @@ return; } -

Recover funds

- -
- +
+

Recover funds

diff --git a/src/Angor/Client/Pages/Settings.razor b/src/Angor/Client/Pages/Settings.razor index bdb762e6..0c53435b 100644 --- a/src/Angor/Client/Pages/Settings.razor +++ b/src/Angor/Client/Pages/Settings.razor @@ -10,13 +10,13 @@ @inject IClientStorage _clientStorage @inject INetworkService _networkService -

- +
+

Settings


+
-

Settings


Network Type: @networkType

Explorer url: @_networkConfiguration.GetDefaultExplorerUrl().First().Url

diff --git a/src/Angor/Client/Pages/Signatures.razor b/src/Angor/Client/Pages/Signatures.razor index a4cf1e89..7f5d8350 100644 --- a/src/Angor/Client/Pages/Signatures.razor +++ b/src/Angor/Client/Pages/Signatures.razor @@ -30,10 +30,8 @@ return; } -

Pending Signatures

-
- +

Pending Signatures

Project ID: @ProjectIdentifier diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index 28c48f5d..b15c9be4 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -26,10 +26,9 @@ return; } -

Founder Stage Claim

-
Project Identifier: @project.ProjectIdentifier
- -
+
+

Founder Stage Claim

+
Project Identifier: @project.ProjectIdentifier
diff --git a/src/Angor/Client/Pages/View.razor b/src/Angor/Client/Pages/View.razor index d9efec68..b187ed11 100644 --- a/src/Angor/Client/Pages/View.razor +++ b/src/Angor/Client/Pages/View.razor @@ -18,7 +18,7 @@ @inherits BaseComponent
-

View Project

+

View Project

From 9c1fb6721d8806fa88d67431f05ffd44c2946c49 Mon Sep 17 00:00:00 2001 From: TheDude Date: Fri, 15 Dec 2023 14:57:30 +0000 Subject: [PATCH 16/19] Fixed bug in the flow of loading items in the browse page --- src/Angor/Client/Pages/Browse.razor | 37 +++++++++++++---------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index 0d7b0812..38524a8d 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -27,35 +27,25 @@ - @if (projects.Count == 0) + @if (searchInProgress) { - @if (searchInProgress) - { -
- } - else - { -

No projects found.

- } +
} else { - foreach (var project in projects.OrderByDescending(project => project.CreatedOnBlock)) + @if (projects.Count == 0) { - @if (SessionStorage.IsProjectInStorageById(project.ProjectIdentifier)) //TODO should we show that projects exist but the user needs to add the right relay? +

No projects found.

+ } + else + { + foreach (var project in projects.OrderByDescending(project => project.CreatedOnBlock)) {
@project.ProjectIdentifier

Nostr ID: @(NostrPublicKey.FromHex(project.NostrPubKey).Bech32)

- @if (searchInProgress) - { -
- } - else - { - - } +
} @@ -104,7 +94,14 @@ if (!SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) SessionStorage.StoreProjectInfo(_); }, - OnEndOfStreamAction: StateHasChanged, + OnEndOfStreamAction:() => + { + projects = projects //Remove projects that were not found on the relays + .Where(_ => SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) + .ToList(); + + StateHasChanged(); + }, nostrPubKey: projectsForLookup); StateHasChanged(); From 28fbb0cc37f59aba7ce87b2e7a4861cfc7d7c1d4 Mon Sep 17 00:00:00 2001 From: TheDude Date: Wed, 20 Dec 2023 14:53:09 +0000 Subject: [PATCH 17/19] Changed the pending to be flagged on UTXO on the account info and not as it's own list in session storage --- src/Angor.Test/WalletOperationsTest.cs | 12 +- .../Client/Pages/CheckTransactionCode.razor | 5 +- src/Angor/Client/Pages/Create.razor | 8 +- src/Angor/Client/Pages/Invest.razor | 8 +- src/Angor/Client/Pages/Recover.razor | 33 +++-- src/Angor/Client/Pages/Spend.razor | 24 +-- src/Angor/Client/Pages/Wallet.razor | 34 ++--- .../Client/Storage/LocalSessionStorage.cs | 6 +- src/Angor/Shared/IWalletOperations.cs | 13 +- src/Angor/Shared/Models/AccountBalanceInfo.cs | 17 ++- src/Angor/Shared/Models/AccountInfo.cs | 30 ++++ src/Angor/Shared/Models/Outpoint.cs | 25 +--- src/Angor/Shared/Models/UnconfirmedInfo.cs | 40 ----- src/Angor/Shared/Models/UtxoData.cs | 2 + src/Angor/Shared/Services/ICacheStorage.cs | 4 +- src/Angor/Shared/WalletOperations.cs | 138 +++++++----------- 16 files changed, 169 insertions(+), 230 deletions(-) delete mode 100644 src/Angor/Shared/Models/UnconfirmedInfo.cs diff --git a/src/Angor.Test/WalletOperationsTest.cs b/src/Angor.Test/WalletOperationsTest.cs index b14415f3..b671c162 100644 --- a/src/Angor.Test/WalletOperationsTest.cs +++ b/src/Angor.Test/WalletOperationsTest.cs @@ -75,11 +75,11 @@ private void AddCoins(AccountInfo accountInfo, int utxos, long amount) { var res = new List { - new UtxoData + new () { address =address, value = Money.Satoshis(amount).Satoshi, - outpoint = new Outpoint {outputIndex = outputIndex++, transactionId = uint256.Zero.ToString() }, + outpoint = new Outpoint( uint256.Zero.ToString(),outputIndex++ ), scriptHex = new Blockcore.NBitcoin.BitcoinWitPubKeyAddress(address,network).ScriptPubKey.ToHex() } }; @@ -89,7 +89,7 @@ private void AddCoins(AccountInfo accountInfo, int utxos, long amount) _sut.UpdateDataForExistingAddressesAsync(accountInfo).Wait(); - _sut.UpdateAccountInfoWithNewAddressesAsync(accountInfo, new UnconfirmedInfo()).Wait(); + _sut.UpdateAccountInfoWithNewAddressesAsync(accountInfo).Wait(); } [Fact] @@ -116,7 +116,7 @@ public void AddFeeAndSignTransaction_test() recoveryTransaction.Outputs.RemoveAt(0); recoveryTransaction.Inputs.RemoveAt(0); - var recoveryTransactions = _sut.AddFeeAndSignTransaction(changeAddress, recoveryTransaction, words, accountInfo, new UnconfirmedInfo(), new FeeEstimation { FeeRate = 3000 }); + var recoveryTransactions = _sut.AddFeeAndSignTransaction(changeAddress, recoveryTransaction, words, accountInfo, new FeeEstimation { FeeRate = 3000 }); // add the inputs of the investment trx List coins = new(); @@ -168,7 +168,7 @@ public void AddInputsAndSignTransaction() var investorPrivateKey = _derivationOperations.DeriveInvestorPrivateKey(words, projectInfo.FounderKey); var investmentTransaction = _investorTransactionActions.CreateInvestmentTransaction(projectInfo, investorKey, Money.Coins(investmentAmount).Satoshi); - var signedInvestmentTransaction = _sut.AddInputsAndSignTransaction(accountInfo.GetNextReceiveAddress(), investmentTransaction, words, accountInfo, new UnconfirmedInfo(), new FeeEstimation { FeeRate = 3000 }); + var signedInvestmentTransaction = _sut.AddInputsAndSignTransaction(accountInfo.GetNextReceiveAddress(), investmentTransaction, words, accountInfo, new FeeEstimation { FeeRate = 3000 }); var strippedInvestmentTransaction = network.CreateTransaction(signedInvestmentTransaction.ToHex()); strippedInvestmentTransaction.Inputs.ForEach(f => f.WitScript = Blockcore.Consensus.TransactionInfo.WitScript.Empty); Assert.Equal(signedInvestmentTransaction.GetHash(), strippedInvestmentTransaction.GetHash()); @@ -184,7 +184,7 @@ public void AddInputsAndSignTransaction() recoveryTransaction.Outputs.RemoveAt(0); recoveryTransaction.Inputs.RemoveAt(0); - var signedRecoveryTransaction = _sut.AddFeeAndSignTransaction(accountInfo.GetNextReceiveAddress(), recoveryTransaction, words, accountInfo, new UnconfirmedInfo(), new FeeEstimation { FeeRate = 3000 }); + var signedRecoveryTransaction = _sut.AddFeeAndSignTransaction(accountInfo.GetNextReceiveAddress(), recoveryTransaction, words, accountInfo, new FeeEstimation { FeeRate = 3000 }); // add the inputs of the investment trx List coins = new(); diff --git a/src/Angor/Client/Pages/CheckTransactionCode.razor b/src/Angor/Client/Pages/CheckTransactionCode.razor index 01f3f5f2..a355a87d 100644 --- a/src/Angor/Client/Pages/CheckTransactionCode.razor +++ b/src/Angor/Client/Pages/CheckTransactionCode.razor @@ -183,7 +183,7 @@ else SendToAddress = privateFounderKey.Neuter().PubKey.GetSegwitAddress(network).ToString() }; - _walletOperations.CalculateTransactionFee(sendInfo, localAccountInfo, new UnconfirmedInfo(), 1000); + _walletOperations.CalculateTransactionFee(sendInfo, localAccountInfo, 1000); var fee = await _walletOperations.GetFeeEstimationAsync(); @@ -193,8 +193,7 @@ else founderTransaction = FounderTransactionActions.SpendFounderStage(context.ProjectInfo, new List(){ transaction.ToHex()}, 1, testAddress.ScriptPubKey , privateFounderKey.PrivateKey.ToHex(network.Consensus.ConsensusFactory), fee.First()); - transaction = _walletOperations.AddInputsAndSignTransaction(context.ChangeAddress, transaction, walletWords, localAccountInfo, new UnconfirmedInfo(), - fee.First()); + transaction = _walletOperations.AddInputsAndSignTransaction(context.ChangeAddress, transaction, walletWords, localAccountInfo, fee.First()); ShowTransaction = true; } diff --git a/src/Angor/Client/Pages/Create.razor b/src/Angor/Client/Pages/Create.razor index e389e2b1..2555ac78 100644 --- a/src/Angor/Client/Pages/Create.razor +++ b/src/Angor/Client/Pages/Create.razor @@ -317,7 +317,7 @@ var operationResult = await notificationComponent.LongOperation(async () => { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); var fetchFees = await _WalletOperations.GetFeeEstimationAsync(); feeData.FeeEstimations.Fees.Clear(); @@ -326,7 +326,7 @@ unsignedTransaction = _founderTransactionActions.CreateNewProjectTransaction(project.FounderKey, _derivationOperations.AngorKeyToScript(project.ProjectIdentifier), 10000, project.NostrPubKey); - signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); project.CreationTransactionId = signedTransaction.GetHash().ToString(); @@ -357,9 +357,9 @@ feeData.SelectedFeeEstimation = feeData.FeeEstimations.Fees.OrderBy(fee => fee.Confirmations).ToList()[res - 1]; var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); - signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); StateHasChanged(); } diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index 12bf1055..deb48baa 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -377,7 +377,6 @@ var operationResult = await notificationComponent.LongOperation(async () => { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); var fetchFees = await _WalletOperations.GetFeeEstimationAsync(); feeData.FeeEstimations.Fees.Clear(); @@ -393,7 +392,7 @@ unSignedTransaction = _InvestorTransactionActions.CreateInvestmentTransaction(project, investorKey, Money.Coins(Investment.InvestmentAmount).Satoshi); - signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unSignedTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unSignedTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); storage.AddInvestmentProject(project); //TODO David need to verify that this is the correct place to change state in storage @@ -424,9 +423,8 @@ feeData.SelectedFeeEstimation = feeData.FeeEstimations.Fees.OrderBy(fee => fee.Confirmations).ToList()[res - 1]; var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - - signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unSignedTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + + signedTransaction = _WalletOperations.AddInputsAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unSignedTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); StateHasChanged(); } diff --git a/src/Angor/Client/Pages/Recover.razor b/src/Angor/Client/Pages/Recover.razor index 63bab450..8256a719 100644 --- a/src/Angor/Client/Pages/Recover.razor +++ b/src/Angor/Client/Pages/Recover.razor @@ -344,28 +344,34 @@ return; } - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); bool modified = false; foreach (var infoOutput in StageInfo.TransactionInfo.Outputs) { if (!string.IsNullOrEmpty(infoOutput.SpentInTransaction)) { - unconfirmedInfo.RemoveInputFromPending(Outpoint.Create(StageInfo.TransactionInfo.TransactionId, infoOutput.Index)); - modified = true; + var spent = unconfirmedInfo.FirstOrDefault(x => x.outpoint.transactionId == StageInfo.TransactionInfo.TransactionId && + x.outpoint.outputIndex == infoOutput.Index); + + if (spent != null) + { + unconfirmedInfo.Remove(spent); + modified = true; + } } } if (modified) { - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); } var penaltyExpieryDate = Utils.UnixTimeToDateTime(StageInfo.TransactionInfo.Timestamp).AddDays(project.PenaltyDays); - + var accountInfo = storage.GetAccountInfo(network.Name); foreach (var item in StageInfo.Items) { - if (unconfirmedInfo.IsInPendingSpent(Outpoint.Create(StageInfo.Trxid, item.Outputindex))) + if (accountInfo.IsInPendingSpent(new Outpoint(StageInfo.Trxid, item.Outputindex))) { item.IsSpent = true; item.SpentTo = "pending confirmations"; @@ -439,9 +445,12 @@ private void AddTransactionToPending(Transaction transaction) { - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - unconfirmedInfo.AddInputsAsPending(transaction); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + var accountInfo = storage.GetAccountInfo(network.Name); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); + var pendingInbound = _WalletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, transaction); + unconfirmedInfo.AddRange(pendingInbound); + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); + storage.SetAccountInfo(network.Name,accountInfo); } private async Task PrepareToRecoverCoins() @@ -455,7 +464,6 @@ recoverySigs = storage.GetSignaturess().First(p => p.ProjectIdentifier == ProjectId); var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); var investorPrivateKey = _derivationOperations.DeriveInvestorPrivateKey(_walletStorage.GetWallet(), project.FounderKey); @@ -477,7 +485,7 @@ foreach (var txIn in removeTxin) unsignedRecoveryTransaction.Inputs.Remove(txIn); // add fee to the recovery trx - recoveryTransaction = _WalletOperations.AddFeeAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedRecoveryTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + recoveryTransaction = _WalletOperations.AddFeeAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedRecoveryTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); recoverySigs.RecoveryTransactionId = recoveryTransaction.GetHash().ToString(); @@ -509,9 +517,8 @@ feeData.SelectedFeeEstimation = feeData.FeeEstimations.Fees.OrderBy(fee => fee.Confirmations).ToList()[res - 1]; var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - recoveryTransaction = _WalletOperations.AddFeeAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedRecoveryTransaction, _walletStorage.GetWallet(), accountInfo, unconfirmedInfo, feeData.SelectedFeeEstimation); + recoveryTransaction = _WalletOperations.AddFeeAndSignTransaction(accountInfo.GetNextChangeReceiveAddress(), unsignedRecoveryTransaction, _walletStorage.GetWallet(), accountInfo, feeData.SelectedFeeEstimation); recoverySigs.RecoveryTransactionId = recoveryTransaction.GetHash().ToString(); diff --git a/src/Angor/Client/Pages/Spend.razor b/src/Angor/Client/Pages/Spend.razor index b15c9be4..6f89dbf8 100644 --- a/src/Angor/Client/Pages/Spend.razor +++ b/src/Angor/Client/Pages/Spend.razor @@ -255,8 +255,7 @@ private async Task CheckSpentFund() { List trxs = new(); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - bool modified = false; + var accountInfo = storage.GetAccountInfo(network.Name); foreach (StageData stageData in stageDatas) { @@ -280,23 +279,14 @@ { item.IsSpent = true; - unconfirmedInfo.RemoveInputFromPending(Outpoint.Create(item.Trxid, item.Outputindex)); - modified = true; + accountInfo.RemoveInputFromPending(new Outpoint(item.Trxid, item.Outputindex)); continue; } - if (unconfirmedInfo.IsInPendingSpent(Outpoint.Create(item.Trxid, item.Outputindex))) - { - item.IsSpent = true; - } + item.IsSpent = accountInfo.IsInPendingSpent(new Outpoint(item.Trxid, item.Outputindex)); } } - - if (modified) - { - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); - } } private async Task ClaimCoins(int stageId) @@ -382,10 +372,10 @@ // add all outptus to the pending list var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - _WalletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, unconfirmedInfo, signedTransaction); - unconfirmedInfo.AddInputsAsPending(signedTransaction); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); + var pendingInbound = _WalletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, signedTransaction); + unconfirmedInfo.AddRange(pendingInbound); + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); // mark stage as spent stageDatas.FirstOrDefault(_ => _.StageIndex == selectedStageId)?.Items.ForEach(_ => diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index bfbca130..52a95ea4 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -1,7 +1,6 @@ @page "/wallet" @using Blockcore.NBitcoin @using Angor.Shared -@using Blockcore.Networks @using Angor.Client.Services @using Angor.Client.Storage @using Angor.Shared.Models @@ -12,7 +11,6 @@ @inject ICacheStorage _cacheStorage; @inject IWalletStorage _walletStorage; @inject ILogger Logger; -@inject INetworkConfiguration _networkConfiguration; @inject IWalletOperations _walletOperations @inject IClipboardService _clipboardService @inject IDerivationOperations _derivationOperations @@ -402,9 +400,9 @@ if (hasWallet) { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); - accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); + accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); } return Task.CompletedTask; @@ -415,16 +413,16 @@ var operationResult = await notificationComponent.LongOperation(async () => { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); await _walletOperations.UpdateDataForExistingAddressesAsync(accountInfo); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, unconfirmedInfo); + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); storage.SetAccountInfo(network.Name, accountInfo); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); - accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); + accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); showQrCode.GenerateQRCode(accountBalanceInfo.AccountInfo.GetNextReceiveAddress()); @@ -444,7 +442,7 @@ WalletWords data = new WalletWords { Words = newWalletWords, Passphrase = newWalletWordsPassphrase }; var accountInfo = _walletOperations.BuildAccountInfoForWalletWords(data); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, new UnconfirmedInfo()); + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); _walletStorage.SaveWalletWords(data); storage.SetAccountInfo(network.Name,accountInfo); @@ -562,12 +560,10 @@ var operationResult = await notificationComponent.LongOperation(async () => { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo, unconfirmedInfo); + await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); storage.SetAccountInfo(network.Name, accountInfo); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); await RefreshFee(); @@ -589,7 +585,7 @@ _sendInfo.ChangeAddress = accountInfo.ChangeAddressesInfo.First(f => f.HasHistory == false).Address; } - _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, unconfirmedInfo, estimationsFee.FeeRate); + _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, estimationsFee.FeeRate); return new OperationResult { Success = true }; }); @@ -609,16 +605,17 @@ var wallet = _walletStorage.GetWallet(); var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); + var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); var res = await _walletOperations.SendAmountToAddress(wallet, _sendInfo); if (res.Success) { - _walletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, unconfirmedInfo, res.Data); - accountBalanceInfo.CalculateBalance(accountInfo, unconfirmedInfo); + var pendingInbound = _walletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, res.Data); + unconfirmedInfo.AddRange(pendingInbound); + accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); storage.SetAccountInfo(network.Name, accountInfo); - _cacheStorage.SetUnconfirmedInfo(unconfirmedInfo); + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); } return res; @@ -665,9 +662,8 @@ _sendInfo.FeeBlockCount = estimationsFee.Confirmations; _sendInfo.FeeRateSat = estimationsFee.FeeRate; var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInfo(); - _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, unconfirmedInfo, estimationsFee.FeeRate); + _sendInfo.SendFee = _walletOperations.CalculateTransactionFee(_sendInfo, accountInfo, estimationsFee.FeeRate); StateHasChanged(); } diff --git a/src/Angor/Client/Storage/LocalSessionStorage.cs b/src/Angor/Client/Storage/LocalSessionStorage.cs index 415eed17..0f83bb28 100644 --- a/src/Angor/Client/Storage/LocalSessionStorage.cs +++ b/src/Angor/Client/Storage/LocalSessionStorage.cs @@ -40,12 +40,12 @@ public void SetProjectIndexerData(List list) _sessionStorageService.SetItem(BrowseIndexerData,list); } - public UnconfirmedInfo GetUnconfirmedInfo() + public List GetUnconfirmedInboundFunds() { - return _sessionStorageService.GetItem("unconfirmed-info") ?? new UnconfirmedInfo(); + return _sessionStorageService.GetItem>("unconfirmed-info") ?? new (); } - public void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo) + public void SetUnconfirmedInboundFunds(List unconfirmedInfo) { _sessionStorageService.SetItem("unconfirmed-info", unconfirmedInfo); } diff --git a/src/Angor/Shared/IWalletOperations.cs b/src/Angor/Shared/IWalletOperations.cs index dd011531..2b97fdff 100644 --- a/src/Angor/Shared/IWalletOperations.cs +++ b/src/Angor/Shared/IWalletOperations.cs @@ -11,23 +11,22 @@ public interface IWalletOperations Task> SendAmountToAddress(WalletWords walletWords, SendInfo sendInfo); AccountInfo BuildAccountInfoForWalletWords(WalletWords walletWords); Task UpdateDataForExistingAddressesAsync(AccountInfo accountInfo); - Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo); + Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo); Task<(string address, List data)> FetchUtxoForAddressAsync(string adddress); - List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo); + List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo); Task> GetFeeEstimationAsync(); - decimal CalculateTransactionFee(SendInfo sendInfo,AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, long feeRate); + decimal CalculateTransactionFee(SendInfo sendInfo,AccountInfo accountInfo, long feeRate); (List? coins, List keys) GetUnspentOutputsForTransaction(WalletWords walletWords, List utxoDataWithPaths); Transaction AddInputsAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, + WalletWords walletWords, AccountInfo accountInfo, FeeEstimation feeRate); Task> PublishTransactionAsync(Network network, Transaction signedTransaction); Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, - FeeEstimation feeRate); + WalletWords walletWords, AccountInfo accountInfo, FeeEstimation feeRate); - void UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction); + List UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction); } \ No newline at end of file diff --git a/src/Angor/Shared/Models/AccountBalanceInfo.cs b/src/Angor/Shared/Models/AccountBalanceInfo.cs index 377a43e6..878e6bbb 100644 --- a/src/Angor/Shared/Models/AccountBalanceInfo.cs +++ b/src/Angor/Shared/Models/AccountBalanceInfo.cs @@ -6,17 +6,22 @@ public class AccountBalanceInfo public long TotalUnconfirmedBalance { get; set; } public AccountInfo AccountInfo { get; private set; } = new (); - public UnconfirmedInfo UnconfirmedInfo { get; private set; } = new (); + + public List AccountPendingReceive { get; set; } = new(); - public void CalculateBalance(AccountInfo account, UnconfirmedInfo unconfirmedInfo) + public void UpdateAccountBalanceInfo(AccountInfo account,List accountPendingReceive) { AccountInfo = account; - UnconfirmedInfo = unconfirmedInfo; + AccountPendingReceive = accountPendingReceive; - var balance = AccountInfo.AllAddresses().SelectMany(s => s.UtxoData).Sum(s => s.value); - var balanceSpent = UnconfirmedInfo.AccountPendingSpent.Sum(s => s.value); + var balance = AccountInfo.AllAddresses().SelectMany(s => s.UtxoData) + .Sum(s => s.value); + + var balanceSpent = AccountInfo.AllAddresses().SelectMany(s => s.UtxoData + .Where(u => u.InMempoolTransaction)) + .Sum(s => s.value); - var balanceUnconfirmed = UnconfirmedInfo.AccountPendingReceive.Sum(s => s.value); + var balanceUnconfirmed = AccountPendingReceive.Sum(s => s.value); TotalBalance = balance - balanceSpent; TotalUnconfirmedBalance = balanceUnconfirmed; diff --git a/src/Angor/Shared/Models/AccountInfo.cs b/src/Angor/Shared/Models/AccountInfo.cs index 08fbf54d..a1b73c9d 100644 --- a/src/Angor/Shared/Models/AccountInfo.cs +++ b/src/Angor/Shared/Models/AccountInfo.cs @@ -28,4 +28,34 @@ public IEnumerable AllAddresses() { return ChangeAddressesInfo.Last()?.Address; } + + public bool IsInPendingSpent(Outpoint outpoint) + { + return AllAddresses() + .SelectMany(x => x.UtxoData) + .Any(x => x.outpoint.ToString() == outpoint.ToString()); + } + + public bool RemoveInputFromPending(Outpoint outpoint) + { + foreach (var addressInfo in AddressesInfo) + { + var utxo = addressInfo.UtxoData.FirstOrDefault(x => x.outpoint.ToString() == outpoint.ToString()); + + if (utxo is null) continue; + addressInfo.UtxoData.Remove(utxo); + return true; + } + + foreach (var addressInfo in ChangeAddressesInfo) + { + var utxo = addressInfo.UtxoData.FirstOrDefault(x => x.outpoint.ToString() == outpoint.ToString()); + + if (utxo is null) continue; + addressInfo.UtxoData.Remove(utxo); + return true; + } + + return false; + } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/Outpoint.cs b/src/Angor/Shared/Models/Outpoint.cs index 2ee3d1d7..e20336c0 100644 --- a/src/Angor/Shared/Models/Outpoint.cs +++ b/src/Angor/Shared/Models/Outpoint.cs @@ -1,6 +1,3 @@ -using Blockcore.Consensus.TransactionInfo; -using Blockcore.NBitcoin; - namespace Angor.Shared.Models; public class Outpoint @@ -8,23 +5,15 @@ public class Outpoint public string transactionId { get; set; } public int outputIndex { get; set; } - public override string ToString() - { - return $"{transactionId}-{outputIndex}"; - } - - public OutPoint ToOutPoint() + public Outpoint(){ } //Required for JSON serializer + + public Outpoint(string trxid, int index) { - return new OutPoint(uint256.Parse(transactionId), outputIndex); + outputIndex = index; transactionId = trxid; } - - public static Outpoint FromOutPoint(OutPoint outPoint) - { - return new Outpoint { outputIndex = (int)outPoint.N, transactionId = outPoint.Hash.ToString() }; - } - - public static Outpoint Create(string trxid, int index) + + public override string ToString() { - return new Outpoint { outputIndex = index, transactionId = trxid }; + return $"{transactionId}-{outputIndex}"; } } \ No newline at end of file diff --git a/src/Angor/Shared/Models/UnconfirmedInfo.cs b/src/Angor/Shared/Models/UnconfirmedInfo.cs deleted file mode 100644 index e825b450..00000000 --- a/src/Angor/Shared/Models/UnconfirmedInfo.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Blockcore.Networks; - -namespace Angor.Shared.Models; - -public class UnconfirmedInfo -{ - public List AccountPendingReceive { get; set; } = new(); - public List AccountPendingSpent { get; set; } = new(); - - public List PendingSpent { get; set; } = new(); - - public bool IsInPendingSpent(Outpoint outpoint) - { - return PendingSpent.Any(intput => intput.ToString() == outpoint.ToString()); - } - - public void RemoveInputFromPending(Outpoint outpoint) - { - foreach (var input in PendingSpent.ToList()) - { - if (input.ToString() == outpoint.ToString()) - { - PendingSpent.Remove(input); - } - } - } - - public void AddInputsAsPending(Blockcore.Consensus.TransactionInfo.Transaction transaction) - { - var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); - - foreach (var outPoint in inputs) - { - if (PendingSpent.All(input => input.ToString() != outPoint.ToString())) - { - PendingSpent.Add(Outpoint.FromOutPoint(outPoint)); - } - } - } -} \ No newline at end of file diff --git a/src/Angor/Shared/Models/UtxoData.cs b/src/Angor/Shared/Models/UtxoData.cs index 10cc4fbb..8ac032df 100644 --- a/src/Angor/Shared/Models/UtxoData.cs +++ b/src/Angor/Shared/Models/UtxoData.cs @@ -9,6 +9,8 @@ public class UtxoData public int blockIndex { get; set; } public bool coinBase { get; set; } public bool coinStake { get; set; } + + public bool InMempoolTransaction { get; set; } } public class UtxoDataWithPath diff --git a/src/Angor/Shared/Services/ICacheStorage.cs b/src/Angor/Shared/Services/ICacheStorage.cs index 0476abdc..9b14e5f0 100644 --- a/src/Angor/Shared/Services/ICacheStorage.cs +++ b/src/Angor/Shared/Services/ICacheStorage.cs @@ -10,7 +10,7 @@ public interface ICacheStorage bool IsProjectInStorageById(string projectId); List? GetProjectIndexerData(); void SetProjectIndexerData(List list); - UnconfirmedInfo GetUnconfirmedInfo(); - void SetUnconfirmedInfo(UnconfirmedInfo unconfirmedInfo); + List GetUnconfirmedInboundFunds(); + void SetUnconfirmedInboundFunds(List unconfirmedInfo); void DeleteUnconfirmedInfo(); } \ No newline at end of file diff --git a/src/Angor/Shared/WalletOperations.cs b/src/Angor/Shared/WalletOperations.cs index da85ddd6..c0201278 100644 --- a/src/Angor/Shared/WalletOperations.cs +++ b/src/Angor/Shared/WalletOperations.cs @@ -39,12 +39,11 @@ public string GenerateWalletWords() } public Transaction AddInputsAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, - FeeEstimation feeRate) + WalletWords walletWords, AccountInfo accountInfo, FeeEstimation feeRate) { Network network = _networkConfiguration.GetNetwork(); - var utxoDataWithPaths = FindOutputsForTransaction((long)transaction.Outputs.Sum(_ => _.Value), accountInfo, unconfirmedInfo); + var utxoDataWithPaths = FindOutputsForTransaction((long)transaction.Outputs.Sum(_ => _.Value), accountInfo); var coins = GetUnspentOutputsForTransaction(walletWords, utxoDataWithPaths); var builder = new TransactionBuilder(network) @@ -61,8 +60,7 @@ public Transaction AddInputsAndSignTransaction(string changeAddress, Transaction } public Transaction AddFeeAndSignTransaction(string changeAddress, Transaction transaction, - WalletWords walletWords, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, - FeeEstimation feeRate) + WalletWords walletWords, AccountInfo accountInfo, FeeEstimation feeRate) { Network network = _networkConfiguration.GetNetwork(); @@ -73,7 +71,7 @@ public Transaction AddFeeAndSignTransaction(string changeAddress, Transaction tr var virtualSize = clonedTransaction.GetVirtualSize(4); var fee = new FeeRate(Money.Satoshis(feeRate.FeeRate)).GetFee(virtualSize); - var utxoDataWithPaths = FindOutputsForTransaction((long)fee, accountInfo, unconfirmedInfo); + var utxoDataWithPaths = FindOutputsForTransaction((long)fee, accountInfo); var coins = GetUnspentOutputsForTransaction(walletWords, utxoDataWithPaths); var totalSats = coins.coins.Sum(s => s.Amount.Satoshi); @@ -131,86 +129,36 @@ public async Task> SendAmountToAddress(WalletWords return await PublishTransactionAsync(network, signedTransaction); } - private void UpdateAccountPendingLists(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) - { - // remove from the pending remove list if it was removed from the indexer - var pendingRemove = unconfirmedInfo.AccountPendingSpent.ToList(); - foreach (var utxoData in pendingRemove) - { - foreach (var addressInfo in accountInfo.AllAddresses()) - { - if (addressInfo.Address == utxoData.address) - { - if (addressInfo.UtxoData.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) - { - unconfirmedInfo.AccountPendingSpent.Remove(utxoData); - } - } - } - } - - // remove from the pending add if it was removed from the indexer - var pendingAdd = unconfirmedInfo.AccountPendingReceive.ToList(); - foreach (var utxoData in pendingAdd) - { - foreach (var addressInfo in accountInfo.AllAddresses()) - { - if (addressInfo.Address == utxoData.address) - { - if (addressInfo.UtxoData.Any(_ => _.outpoint.ToString() == utxoData.outpoint.ToString())) - { - unconfirmedInfo.AccountPendingReceive.Remove(utxoData); - } - } - } - } - } - - public void UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, Transaction transaction) + public List UpdateAccountUnconfirmedInfoWithSpentTransaction(AccountInfo accountInfo, Transaction transaction) { Network network = _networkConfiguration.GetNetwork(); + + var inputs = transaction.Inputs.Select(_ => _.PrevOut.ToString()).ToList(); - var outputs = transaction.Outputs.AsIndexedOutputs(); - var inputs = transaction.Inputs.Select(_ => _.PrevOut).ToList(); + var accountChangeAddresses = accountInfo.ChangeAddressesInfo.Select(x => x.Address); - foreach (var addressInfo in accountInfo.AllAddresses()) + var transactionHash = transaction.GetHash().ToString(); + + foreach (var utxoData in accountInfo.AllAddresses().SelectMany(x => x.UtxoData)) { // find all spent inputs to mark them as spent - foreach (var utxoData in addressInfo.UtxoData) - { - foreach (var outPoint in inputs) - { - if (utxoData.outpoint.ToString() == outPoint.ToString()) - { - if (unconfirmedInfo.AccountPendingSpent.All(_ => _.outpoint.ToString() != utxoData.outpoint.ToString())) - { - unconfirmedInfo.AccountPendingSpent.Add(utxoData); - } - } - } - } - - // find all new outputs to mark them as unspent - foreach (var output in outputs) - { - if (output.TxOut.ScriptPubKey.GetDestinationAddress(network).ToString() == addressInfo.Address) - { - var outpoint = new Outpoint { outputIndex = (int)output.N, transactionId = transaction.GetHash().ToString() }; - - if (unconfirmedInfo.AccountPendingReceive.All(_ => _.outpoint != outpoint)) - { - unconfirmedInfo.AccountPendingReceive.Add(new UtxoData - { - address = addressInfo.Address, - scriptHex = output.TxOut.ScriptPubKey.ToHex(), - outpoint = outpoint, - blockIndex = 0, - value = output.TxOut.Value - }); - } - } - } + if (inputs.Contains(utxoData.outpoint.ToString())) + utxoData.InMempoolTransaction = true; } + + return transaction.Outputs.AsIndexedOutputs() + .Where(x => + accountChangeAddresses.Contains(x.TxOut.ScriptPubKey.GetDestinationAddress(network).ToString())) + .Select(x => + new UtxoData + { + address = x.TxOut.ScriptPubKey.GetDestinationAddress(network).ToString(), + scriptHex = x.TxOut.ScriptPubKey.ToHex(), + outpoint = new Outpoint( transactionHash ,(int)x.N), + blockIndex = 0, + value = x.TxOut.Value + }) + .ToList();; } public async Task> PublishTransactionAsync(Network network,Transaction signedTransaction) @@ -225,13 +173,13 @@ public async Task> PublishTransactionAsync(Network return new OperationResult { Success = false, Message = res }; } - public List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) + public List FindOutputsForTransaction(long sendAmountat, AccountInfo accountInfo) { var utxosToSpend = new List(); long total = 0; foreach (var utxoData in accountInfo.AllAddresses().SelectMany(_ => _.UtxoData - .Where(utxow => unconfirmedInfo.AccountPendingSpent.All(p => p.outpoint.ToString() != utxow.outpoint.ToString())) + .Where(utxow => utxow.InMempoolTransaction == false) .Select(u => new { path = _.HdPath, utxo = u })) .OrderBy(o => o.utxo.blockIndex) .ThenByDescending(o => o.utxo.value)) @@ -354,7 +302,7 @@ private async Task UpdateAddressInfoUtxoData(AddressInfo addressInfo) } } - public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo) + public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo) { ExtKey.UseBCForHMACSHA512 = true; Blockcore.NBitcoin.Crypto.Hashes.UseBCForHMACSHA512 = true; @@ -368,7 +316,16 @@ public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo { var addressInfoToDelete = accountInfo.AddressesInfo.SingleOrDefault(_ => _.Address == addressInfo.Address); if (addressInfoToDelete != null) + { + //TODO need to update the indexer response with mempool utxo as well so it is always consistant + foreach (var utxoData in addressInfo.UtxoData.Where(x => x.InMempoolTransaction)) + { + var outpoint = utxoData.outpoint.ToString(); + var newUtxo = addressInfo.UtxoData.FirstOrDefault(x => x.outpoint.ToString() == outpoint); + if (newUtxo != null) newUtxo.InMempoolTransaction = true; + } accountInfo.AddressesInfo.Remove(addressInfoToDelete); + } accountInfo.AddressesInfo.Add(addressInfo); } @@ -379,13 +336,20 @@ public async Task UpdateAccountInfoWithNewAddressesAsync(AccountInfo accountInfo foreach (var changeAddressInfo in changeItems) { var addressInfoToDelete = accountInfo.ChangeAddressesInfo.SingleOrDefault(_ => _.Address == changeAddressInfo.Address); - if (addressInfoToDelete != null) + if (addressInfoToDelete != null) + { + //TODO need to update the indexer response with mempool utxo as well so it is always consistant + foreach (var utxoData in addressInfoToDelete.UtxoData.Where(x => x.InMempoolTransaction)) + { + var outpoint = utxoData.outpoint.ToString(); + var newUtxo = addressInfoToDelete.UtxoData.FirstOrDefault(x => x.outpoint.ToString() == outpoint); + if (newUtxo != null) newUtxo.InMempoolTransaction = true; + } accountInfo.ChangeAddressesInfo.Remove(addressInfoToDelete); + } accountInfo.ChangeAddressesInfo.Add(changeAddressInfo); } - - UpdateAccountPendingLists(accountInfo, unconfirmedInfo); } private async Task<(int,List)> FetchAddressesDataForPubKeyAsync(int scanIndex, string ExtendedPubKey, Network network, bool isChange) @@ -508,13 +472,13 @@ public async Task> GetFeeEstimationAsync() } } - public decimal CalculateTransactionFee(SendInfo sendInfo, AccountInfo accountInfo, UnconfirmedInfo unconfirmedInfo, long feeRate) + public decimal CalculateTransactionFee(SendInfo sendInfo, AccountInfo accountInfo, long feeRate) { var network = _networkConfiguration.GetNetwork(); if (sendInfo.SendUtxos.Count == 0) { - var utxosToSpend = FindOutputsForTransaction(sendInfo.SendAmountSat, accountInfo, unconfirmedInfo); + var utxosToSpend = FindOutputsForTransaction(sendInfo.SendAmountSat, accountInfo); foreach (var data in utxosToSpend) //TODO move this out of the fee calculation { From 87d77bf0ca99c6068e67bb99c61ecd5ce2006bb3 Mon Sep 17 00:00:00 2001 From: TheDude Date: Thu, 21 Dec 2023 12:32:49 +0000 Subject: [PATCH 18/19] Testing pending payment and fixed other minor bugs --- src/Angor/Client/Pages/Browse.razor | 2 ++ src/Angor/Client/Pages/Invest.razor | 8 ++++++++ src/Angor/Client/Pages/Wallet.razor | 15 ++++++++++++--- src/Angor/Shared/Services/RelayService.cs | 2 +- .../Shared/Services/RelaySubscriptionsHanding.cs | 9 ++++++++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index 38524a8d..adc6b10f 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -100,6 +100,8 @@ .Where(_ => SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) .ToList(); + SessionStorage.SetProjectIndexerData(projects); + StateHasChanged(); }, nostrPubKey: projectsForLookup); diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index deb48baa..1d2c6d4f 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -532,6 +532,14 @@ var response = await _WalletOperations.PublishTransactionAsync(network, signedTransaction); + var accountInfo = storage.GetAccountInfo(network.Name); + var unspentInfo = SessionStorage.GetUnconfirmedInboundFunds(); + + var spendUtxos = _WalletOperations.UpdateAccountUnconfirmedInfoWithSpentTransaction(accountInfo, signedTransaction); + + unspentInfo.AddRange(spendUtxos); + SessionStorage.SetUnconfirmedInboundFunds(unspentInfo); + return response.Success ? new SuccessOperationResult() : response; }); diff --git a/src/Angor/Client/Pages/Wallet.razor b/src/Angor/Client/Pages/Wallet.razor index 52a95ea4..9916de06 100644 --- a/src/Angor/Client/Pages/Wallet.razor +++ b/src/Angor/Client/Pages/Wallet.razor @@ -413,16 +413,25 @@ var operationResult = await notificationComponent.LongOperation(async () => { var accountInfo = storage.GetAccountInfo(network.Name); - var unconfirmedInfo = _cacheStorage.GetUnconfirmedInboundFunds(); + var unconfirmedInboundFunds = _cacheStorage.GetUnconfirmedInboundFunds(); await _walletOperations.UpdateDataForExistingAddressesAsync(accountInfo); await _walletOperations.UpdateAccountInfoWithNewAddressesAsync(accountInfo); storage.SetAccountInfo(network.Name, accountInfo); - _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInfo); + + var utxos = accountInfo.AllAddresses() + .SelectMany(x => x.UtxoData) + .Select(x => x.outpoint.ToString()); - accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInfo); + var spentToUpdate = unconfirmedInboundFunds + .RemoveAll(x => utxos.Contains(x.outpoint.ToString())); + + if (spentToUpdate > 0) + _cacheStorage.SetUnconfirmedInboundFunds(unconfirmedInboundFunds); + + accountBalanceInfo.UpdateAccountBalanceInfo(accountInfo, unconfirmedInboundFunds); showQrCode.GenerateQRCode(accountBalanceInfo.AccountInfo.GetNextReceiveAddress()); diff --git a/src/Angor/Shared/Services/RelayService.cs b/src/Angor/Shared/Services/RelayService.cs index c31b38e9..b9e79459 100644 --- a/src/Angor/Shared/Services/RelayService.cs +++ b/src/Angor/Shared/Services/RelayService.cs @@ -71,7 +71,7 @@ public void LookupProjectsInfoByPubKeys(Action responseDataAction, Action? if (OnEndOfStreamAction != null) { - userEoseActions.Add(subscriptionName,new SubscriptionCallCounter(OnEndOfStreamAction)); + userEoseActions.TryAdd(subscriptionName,new SubscriptionCallCounter(OnEndOfStreamAction)); } } diff --git a/src/Angor/Shared/Services/RelaySubscriptionsHanding.cs b/src/Angor/Shared/Services/RelaySubscriptionsHanding.cs index 1403f07c..c2e05e98 100644 --- a/src/Angor/Shared/Services/RelaySubscriptionsHanding.cs +++ b/src/Angor/Shared/Services/RelaySubscriptionsHanding.cs @@ -61,7 +61,14 @@ public void HandleEoseMessages(NostrEoseResponse _) if (userEoseActions[_.Subscription].NumberOfInvocations == _communicationFactory.GetNumberOfRelaysConnected()) { _logger.LogInformation($"Invoking action on EOSE - {_.Subscription}"); - value.Item.Invoke(); + try + { + value.Item.Invoke(); + } + catch (Exception e) + { + _logger.LogError(e,"Failed to invoke end of events event action"); + } userEoseActions.Remove(_.Subscription); _logger.LogInformation($"Removed action on EOSE for subscription - {_.Subscription}"); } From 567599a7e349f943c46a60bef0dd776c99c6dd66 Mon Sep 17 00:00:00 2001 From: TheDude Date: Thu, 21 Dec 2023 15:39:13 +0000 Subject: [PATCH 19/19] Removed commented code --- src/Angor/Client/Pages/Browse.razor | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Angor/Client/Pages/Browse.razor b/src/Angor/Client/Pages/Browse.razor index adc6b10f..e89e7275 100644 --- a/src/Angor/Client/Pages/Browse.razor +++ b/src/Angor/Client/Pages/Browse.razor @@ -84,7 +84,6 @@ var projectsForLookup = projectsNotInList .Where(_ => _.NostrPubKey != null) //For old projects in the indexer - //.Where(_ => !SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) .Select(_ => _.NostrPubKey) .ToArray(); @@ -94,14 +93,14 @@ if (!SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) SessionStorage.StoreProjectInfo(_); }, - OnEndOfStreamAction:() => + OnEndOfStreamAction: () => { projects = projects //Remove projects that were not found on the relays .Where(_ => SessionStorage.IsProjectInStorageById(_.ProjectIdentifier)) .ToList(); - + SessionStorage.SetProjectIndexerData(projects); - + StateHasChanged(); }, nostrPubKey: projectsForLookup);
@signature.AmountToInvest @network.CoinTicker@Money.Satoshis(signature.AmountToInvest).ToUnit(MoneyUnit.BTC) @network.CoinTicker @signature.TimeArrived.ToString("g") From 28ba039008334fdf5d4f96ee85bf93f14eb6ad68 Mon Sep 17 00:00:00 2001 From: dangershony Date: Mon, 11 Dec 2023 12:46:11 +0000 Subject: [PATCH 04/19] hide text when waiting for sigs --- src/Angor/Client/Pages/Invest.razor | 54 ++++++++++++--------- src/Angor/Shared/Services/NetworkService.cs | 17 ++++++- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/src/Angor/Client/Pages/Invest.razor b/src/Angor/Client/Pages/Invest.razor index 48a0e5f3..47d7c3c3 100644 --- a/src/Angor/Client/Pages/Invest.razor +++ b/src/Angor/Client/Pages/Invest.razor @@ -170,25 +170,34 @@ } } - @if(recoverySigs?.Signatures.Any() == true) + @if (recoverySigs != null) { -
- @if (!validatingsignaturs) - { - - - } - else - { -
- } -
- } - else - { -
-

Waiting for the founder to sign the investment request

-
+ @if (recoverySigs?.Signatures.Any() == true) + { +
+ @if (!validatingsignaturs) + { + + + } + else + { +
+ } +
+ } + else + { +
+
+

Waiting for the founder

+
+
+

Waiting for the founder to sign the investment request

+ @* *@ +
+
+ } } @@ -249,16 +258,15 @@ await CheckIfSeederTimeHasPassed(); UpdateStagesBreakdown(new ChangeEventArgs { Value = Investment.InvestmentAmount }); - - if (project != null && recoverySigs?.Signatures.Any() ==false) + if (project != null && recoverySigs?.Signatures.Any() == false) { var accountInfo = storage.GetAccountInfo(_networkConfiguration.GetNetwork().Name); - + var nostrPrivateKey = _derivationOperations.DeriveProjectNostrPrivateKey(_walletStorage.GetWallet(), accountInfo.InvestmentsCount + 1); - var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); - + var nostrPrivateKeyHex = Encoders.Hex.EncodeData(nostrPrivateKey.ToBytes()); + _SignService.LookupSignatureForInvestmentRequest( NostrPrivateKey.FromHex(nostrPrivateKeyHex).DerivePublicKey().Hex , project.NostrPubKey, recoverySigs.TimeOfRequestForSigning, diff --git a/src/Angor/Shared/Services/NetworkService.cs b/src/Angor/Shared/Services/NetworkService.cs index e7089ea0..f8cb035a 100644 --- a/src/Angor/Shared/Services/NetworkService.cs +++ b/src/Angor/Shared/Services/NetworkService.cs @@ -85,7 +85,12 @@ public SettingsUrl GetPrimaryIndexer() { var settings = _networkStorage.GetSettings(); - var ret = settings.Indexers.First(p => p.IsPrimary); + var ret = settings.Indexers.FirstOrDefault(p => p.IsPrimary); + + if (ret == null) + { + throw new ApplicationException("No indexer found go to settings to add an indexer."); + } return ret; } @@ -94,7 +99,15 @@ public SettingsUrl GetPrimaryRelay() { var settings = _networkStorage.GetSettings(); - return settings.Relays.First(p => p.IsPrimary); + var ret = settings.Relays.FirstOrDefault(p => p.IsPrimary); + + if (ret == null) + { + throw new ApplicationException("No relay found go to settings to add a relay."); + } + + return ret; + } public List GetRelays() From 5eda92f94b339d538badc7f25dd91c42a88384dd Mon Sep 17 00:00:00 2001 From: dangershony Date: Mon, 11 Dec 2023 15:14:13 +0000 Subject: [PATCH 05/19] Add scollable to modal and make a hack for the utxo balance from indexer --- src/Angor/Client/Pages/Create.razor | 2 +- src/Angor/Client/Pages/Invest.razor | 11 +++- src/Angor/Client/Pages/Recover.razor | 6 +- src/Angor/Client/Pages/Spend.razor | 2 +- src/Angor/Client/Pages/Wallet.razor | 8 +-- .../Client/Shared/NotificationComponent.razor | 2 +- src/Angor/Client/wwwroot/css/app.css | 60 ++++++++++++++++++- src/Angor/Shared/WalletOperations.cs | 25 +++++++- 8 files changed, 101 insertions(+), 15 deletions(-) diff --git a/src/Angor/Client/Pages/Create.razor b/src/Angor/Client/Pages/Create.razor index c9908796..b3dabe84 100644 --- a/src/Angor/Client/Pages/Create.razor +++ b/src/Angor/Client/Pages/Create.razor @@ -152,7 +152,7 @@ - -