private static TransactionItemModel FindSimilarReceivedTransactionOutput(List <TransactionItemModel> items, TransactionOutputData transaction)
        {
            TransactionItemModel existingTransaction = items.FirstOrDefault(i => i.Id == transaction.Id &&
                                                                            i.Type == TransactionItemType.Received &&
                                                                            i.ConfirmedInBlock == transaction.BlockHeight);

            return(existingTransaction);
        }
 public TransactionItemListViewModel(ITransactionService transactionService)
 {
     _transactionService = transactionService;
     _allItems           = new List <TransactionItemModel>();
     TransactionItems    = new BindingList <TransactionItemModel>();
     TransactionItem     = new TransactionItemModel
     {
         Type     = BaseModel.TypeEnum.Income,
         Category = new CategoryModel()
     };
 }
Пример #3
0
        public static TransactionItemModel Update(this TransactionItemModel model, TransactionItem transactionItem)
        {
            var dateTime = new DateTime(1970, 1, 1);

            dateTime = dateTime.AddMilliseconds(long.Parse(transactionItem.Timestamp));

            model.Hash           = transactionItem.Hash;
            model.Address        = transactionItem.Address;
            model.Index          = int.Parse(transactionItem.CurrentIndex);
            model.Balance        = Int64.Parse(transactionItem.Value);
            model.IsConfirmed    = transactionItem.Persistence;
            model.DateTime       = dateTime;
            model.TransactionTag = transactionItem.Tag;
            model.Tag            = transactionItem;
            return(model);
        }
        public async Task GetHistoryFromMiningNode()
        {
            using (NodeBuilder builder = NodeBuilder.Create(this))
            {
                // Arrange.
                // Create a mining node.
                CoreNode miningNode = builder.CreateStratisPosNode(this.network).WithWallet().Start();

                TestHelper.MineBlocks(miningNode, 5);

                // Check balances.
                WalletBalanceModel sendingNodeBalances = await $"http://localhost:{miningNode.ApiPort}/api"
                                                         .AppendPathSegment("wallet/balance")
                                                         .SetQueryParams(new { walletName = "mywallet" })
                                                         .GetJsonAsync <WalletBalanceModel>();

                AccountBalanceModel sendingAccountBalance = sendingNodeBalances.AccountsBalances.Single();
                (sendingAccountBalance.AmountConfirmed + sendingAccountBalance.AmountUnconfirmed).Should().Be(new Money(98000000 + (4 * 4), MoneyUnit.BTC));

                // Act.
                WalletHistoryModel firstAccountHistory = await $"http://localhost:{miningNode.ApiPort}/api"
                                                         .AppendPathSegment("wallet/history")
                                                         .SetQueryParams(new { walletName = "mywallet", accountName = "account 0" })
                                                         .GetJsonAsync <WalletHistoryModel>();

                // Assert.
                firstAccountHistory.AccountsHistoryModel.Should().NotBeEmpty();

                ICollection <TransactionItemModel> history = firstAccountHistory.AccountsHistoryModel.First().TransactionsHistory;
                history.Should().NotBeEmpty();
                history.Count.Should().Be(5);

                TransactionItemModel firstItem = history.First(); // First item in the list but last item to have occurred.
                firstItem.Amount.Should().Be(new Money(4, MoneyUnit.BTC));
                firstItem.BlockIndex.Should().Be(0);
                firstItem.ConfirmedInBlock.Should().Be(5);
                firstItem.ToAddress.Should().NotBeNullOrEmpty();
                firstItem.Fee.Should().BeNull();
            }
        }
Пример #5
0
        public IActionResult GetHistory([FromQuery] WalletHistoryRequest request)
        {
            Guard.NotNull(request, nameof(request));

            if (!this.ModelState.IsValid)
            {
                return(ModelStateErrors.BuildErrorResponse(this.ModelState));
            }

            try
            {
                var model = new WalletHistoryModel();

                // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them.
                IEnumerable <AccountHistory> accountsHistory = this.walletManager.GetHistory(request.WalletName, request.AccountName);

                foreach (AccountHistory accountHistory in accountsHistory)
                {
                    var transactionItems = new List <TransactionItemModel>();

                    List <FlatHistory> items = accountHistory.History.OrderByDescending(o => o.Transaction.CreationTime).Take(200).ToList();

                    // Represents a sublist containing only the transactions that have already been spent.
                    List <FlatHistory> spendingDetails = items.Where(t => t.Transaction.SpendingDetails != null).ToList();

                    // Represents a sublist of transactions associated with receive addresses + a sublist of already spent transactions associated with change addresses.
                    // In effect, we filter out 'change' transactions that are not spent, as we don't want to show these in the history.
                    List <FlatHistory> history = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && !t.Transaction.IsSpendable())).ToList();

                    // Represents a sublist of 'change' transactions.
                    List <FlatHistory> allchange = items.Where(t => t.Address.IsChangeAddress()).ToList();

                    foreach (FlatHistory item in history)
                    {
                        TransactionData transaction = item.Transaction;
                        HdAddress       address     = item.Address;

                        // We don't show in history transactions that are outputs of staking transactions.
                        if (transaction.IsCoinStake != null && transaction.IsCoinStake.Value && transaction.SpendingDetails == null)
                        {
                            continue;
                        }

                        // First we look for staking transaction as they require special attention.
                        // A staking transaction spends one of our inputs into 2 outputs, paid to the same address.
                        if (transaction.SpendingDetails?.IsCoinStake != null && transaction.SpendingDetails.IsCoinStake.Value)
                        {
                            // We look for the 2 outputs related to our spending input.
                            List <FlatHistory> relatedOutputs = items.Where(h => h.Transaction.Id == transaction.SpendingDetails.TransactionId && h.Transaction.IsCoinStake != null && h.Transaction.IsCoinStake.Value).ToList();
                            if (relatedOutputs.Any())
                            {
                                // Add staking transaction details.
                                // The staked amount is calculated as the difference between the sum of the outputs and the input and should normally be equal to 1.
                                var stakingItem = new TransactionItemModel
                                {
                                    Type             = TransactionItemType.Staked,
                                    ToAddress        = address.Address,
                                    Amount           = relatedOutputs.Sum(o => o.Transaction.Amount) - transaction.Amount,
                                    Id               = transaction.SpendingDetails.TransactionId,
                                    Timestamp        = transaction.SpendingDetails.CreationTime,
                                    ConfirmedInBlock = transaction.SpendingDetails.BlockHeight
                                };

                                transactionItems.Add(stakingItem);
                            }

                            // No need for further processing if the transaction itself is the output of a staking transaction.
                            if (transaction.IsCoinStake != null)
                            {
                                continue;
                            }
                        }

                        // Create a record for a 'receive' transaction.
                        if (!address.IsChangeAddress())
                        {
                            // Add incoming fund transaction details.
                            var receivedItem = new TransactionItemModel
                            {
                                Type             = TransactionItemType.Received,
                                ToAddress        = address.Address,
                                Amount           = transaction.Amount,
                                Id               = transaction.Id,
                                Timestamp        = transaction.CreationTime,
                                ConfirmedInBlock = transaction.BlockHeight
                            };

                            transactionItems.Add(receivedItem);
                        }

                        // If this is a normal transaction (not staking) that has been spent, add outgoing fund transaction details.
                        if (transaction.SpendingDetails != null && transaction.SpendingDetails.IsCoinStake == null)
                        {
                            // Create a record for a 'send' transaction.
                            uint256 spendingTransactionId = transaction.SpendingDetails.TransactionId;
                            var     sentItem = new TransactionItemModel
                            {
                                Type             = TransactionItemType.Send,
                                Id               = spendingTransactionId,
                                Timestamp        = transaction.SpendingDetails.CreationTime,
                                ConfirmedInBlock = transaction.SpendingDetails.BlockHeight,
                                Amount           = Money.Zero
                            };

                            // If this 'send' transaction has made some external payments, i.e the funds were not sent to another address in the wallet.
                            if (transaction.SpendingDetails.Payments != null)
                            {
                                sentItem.Payments = new List <PaymentDetailModel>();
                                foreach (PaymentDetails payment in transaction.SpendingDetails.Payments)
                                {
                                    sentItem.Payments.Add(new PaymentDetailModel
                                    {
                                        DestinationAddress = payment.DestinationAddress,
                                        Amount             = payment.Amount
                                    });

                                    sentItem.Amount += payment.Amount;
                                }
                            }

                            // Get the change address for this spending transaction.
                            FlatHistory changeAddress = allchange.FirstOrDefault(a => a.Transaction.Id == spendingTransactionId);

                            // Find all the spending details containing the spending transaction id and aggregate the sums.
                            // This is our best shot at finding the total value of inputs for this transaction.
                            var inputsAmount = new Money(spendingDetails.Where(t => t.Transaction.SpendingDetails.TransactionId == spendingTransactionId).Sum(t => t.Transaction.Amount));

                            // The fee is calculated as follows: funds in utxo - amount spent - amount sent as change.
                            sentItem.Fee = inputsAmount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transaction.Amount);

                            // Mined/staked coins add more coins to the total out.
                            // That makes the fee negative. If that's the case ignore the fee.
                            if (sentItem.Fee < 0)
                            {
                                sentItem.Fee = 0;
                            }

                            if (!transactionItems.Contains(sentItem, new SentTransactionItemModelComparer()))
                            {
                                transactionItems.Add(sentItem);
                            }
                        }
                    }

                    model.AccountsHistoryModel.Add(new AccountHistoryModel
                    {
                        TransactionsHistory = transactionItems.OrderByDescending(t => t.Timestamp).ToList(),
                        Name     = accountHistory.Account.Name,
                        CoinType = this.coinType,
                        HdPath   = accountHistory.Account.HdPath
                    });
                }

                return(this.Json(model));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public IActionResult GetHistory([FromQuery] WalletHistoryRequest request)
        {
            // checks the request is valid
            if (!this.ModelState.IsValid)
            {
                var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors)));
            }

            try
            {
                WalletHistoryModel model = new WalletHistoryModel {
                    TransactionsHistory = new List <TransactionItemModel>()
                };

                // get transactions contained in the wallet
                var addresses = this.walletManager.GetHistoryByCoinType(request.WalletName, this.coinType);
                foreach (var address in addresses.Where(a => !a.IsChangeAddress()))
                {
                    foreach (var transaction in address.Transactions)
                    {
                        // add incoming fund transaction details
                        TransactionItemModel receivedItem = new TransactionItemModel
                        {
                            Type             = TransactionItemType.Received,
                            ToAddress        = address.Address,
                            Amount           = transaction.Amount,
                            Id               = transaction.Id,
                            Timestamp        = transaction.CreationTime,
                            ConfirmedInBlock = transaction.BlockHeight
                        };

                        model.TransactionsHistory.Add(receivedItem);

                        // add outgoing fund transaction details
                        if (transaction.SpendingDetails != null)
                        {
                            TransactionItemModel sentItem = new TransactionItemModel();
                            sentItem.Type             = TransactionItemType.Send;
                            sentItem.Id               = transaction.SpendingDetails.TransactionId;
                            sentItem.Timestamp        = transaction.SpendingDetails.CreationTime;
                            sentItem.ConfirmedInBlock = transaction.SpendingDetails.BlockHeight;

                            sentItem.Amount = Money.Zero;
                            if (transaction.SpendingDetails.Payments != null)
                            {
                                sentItem.Payments = new List <PaymentDetailModel>();
                                foreach (var payment in transaction.SpendingDetails.Payments)
                                {
                                    sentItem.Payments.Add(new PaymentDetailModel
                                    {
                                        DestinationAddress = payment.DestinationAddress,
                                        Amount             = payment.Amount
                                    });

                                    sentItem.Amount += payment.Amount;
                                }
                            }

                            // get the change address for this spending transaction
                            var changeAddress = addresses.SingleOrDefault(a => a.IsChangeAddress() && a.Transactions.Any(t => t.Id == transaction.SpendingDetails.TransactionId));

                            // the fee is calculated as follows: fund in utxo - amount spent - amount sent as change
                            sentItem.Fee = transaction.Amount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transactions.First(t => t.Id == transaction.SpendingDetails.TransactionId).Amount);

                            // mined/staked coins add more coins to the total out
                            // that makes the fee negative if thats the case ignore the fee
                            if (sentItem.Fee < 0)
                            {
                                sentItem.Fee = 0;
                            }

                            model.TransactionsHistory.Add(sentItem);
                        }
                    }
                }

                model.TransactionsHistory = model.TransactionsHistory.OrderByDescending(t => t.Timestamp).ToList();
                return(this.Json(model));
            }
            catch (Exception e)
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        public IActionResult GetHistory([FromQuery] WalletHistoryRequest request)
        {
            Guard.NotNull(request, nameof(request));

            // checks the request is valid
            if (!this.ModelState.IsValid)
            {
                var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors)));
            }

            try
            {
                WalletHistoryModel model = new WalletHistoryModel();

                // get transactions contained in the wallet
                var items = this.walletManager.GetHistory(request.WalletName).ToList().OrderByDescending(o => o.Transaction.CreationTime).Take(100).ToList();
                List <FlatHistory> spendingDetails = items.Where(t => t.Transaction.SpendingDetails != null).ToList();
                List <FlatHistory> filtered        = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && !t.Transaction.IsSpendable())).ToList();
                List <FlatHistory> allchange       = items.Where(t => t.Address.IsChangeAddress()).ToList();

                foreach (var item in filtered)
                {
                    var transaction = item.Transaction;
                    var address     = item.Address;

                    if (!address.IsChangeAddress())
                    {
                        // add incoming fund transaction details
                        TransactionItemModel receivedItem = new TransactionItemModel
                        {
                            Type             = TransactionItemType.Received,
                            ToAddress        = address.Address,
                            Amount           = transaction.Amount,
                            Id               = transaction.Id,
                            Timestamp        = transaction.CreationTime,
                            ConfirmedInBlock = transaction.BlockHeight
                        };

                        model.TransactionsHistory.Add(receivedItem);
                    }

                    // add outgoing fund transaction details
                    if (transaction.SpendingDetails != null)
                    {
                        var spendingTransactionId     = transaction.SpendingDetails.TransactionId;
                        TransactionItemModel sentItem = new TransactionItemModel
                        {
                            Type             = TransactionItemType.Send,
                            Id               = spendingTransactionId,
                            Timestamp        = transaction.SpendingDetails.CreationTime,
                            ConfirmedInBlock = transaction.SpendingDetails.BlockHeight,
                            Amount           = Money.Zero
                        };

                        if (transaction.SpendingDetails.Payments != null)
                        {
                            sentItem.Payments = new List <PaymentDetailModel>();
                            foreach (var payment in transaction.SpendingDetails.Payments)
                            {
                                sentItem.Payments.Add(new PaymentDetailModel
                                {
                                    DestinationAddress = payment.DestinationAddress,
                                    Amount             = payment.Amount
                                });

                                sentItem.Amount += payment.Amount;
                            }
                        }

                        // get the change address for this spending transaction
                        var changeAddress = allchange.FirstOrDefault(a => a.Transaction.Id == spendingTransactionId);

                        // find all the spending details containing the spending transaction id and aggregate the sums.
                        // this is our best shot at finding the total value of inputs for this transaction.
                        var inputsAmount = new Money(spendingDetails.Where(t => t.Transaction.SpendingDetails.TransactionId == spendingTransactionId).Sum(t => t.Transaction.Amount));

                        // the fee is calculated as follows: funds in utxo - amount spent - amount sent as change
                        sentItem.Fee = inputsAmount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transaction.Amount);

                        // mined/staked coins add more coins to the total out
                        // that makes the fee negative if that's the case ignore the fee
                        if (sentItem.Fee < 0)
                        {
                            sentItem.Fee = 0;
                        }

                        if (!model.TransactionsHistory.Contains(sentItem, new SentTransactionItemModelComparer()))
                        {
                            model.TransactionsHistory.Add(sentItem);
                        }
                    }
                }

                model.TransactionsHistory = model.TransactionsHistory.OrderByDescending(t => t.Timestamp).ToList();

                return(this.Json(model));
            }
            catch (Exception e)
            {
                this.logger.LogError("Exception occurred: {0}", e.ToString());
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
        /// <summary>
        /// This method is an attempt to fetch history and its complex calculation directly from disk
        /// and avoid the need to fetch the entire history to memory
        /// </summary>
        public static WalletHistoryModel GetHistorySlim(IWalletManager walletManager, Network network, WalletHistoryRequest request)
        {
            if (!string.IsNullOrEmpty(request.Address))
            {
                throw new NotImplementedException("Search by address not implemented");
            }

            if (!string.IsNullOrEmpty(request.SearchQuery))
            {
                throw new NotImplementedException("Search by SearchQuery not implemented");
            }

            var model = new WalletHistoryModel();

            // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them.
            IEnumerable <AccountHistorySlim> accountsHistory = walletManager.GetHistorySlim(request.WalletName, request.AccountName, skip: request.Skip ?? 0, take: request.Take ?? int.MaxValue);

            foreach (AccountHistorySlim accountHistory in accountsHistory)
            {
                var transactionItems = new List <TransactionItemModel>();

                foreach (FlatHistorySlim item in accountHistory.History)
                {
                    var modelItem = new TransactionItemModel
                    {
                        Type             = item.Transaction.IsSent ? TransactionItemType.Send : TransactionItemType.Received,
                        ToAddress        = item.Transaction.Address,
                        Amount           = item.Transaction.IsSent == false ? item.Transaction.Amount : Money.Zero,
                        Id               = item.Transaction.IsSent ? item.Transaction.SentTo : item.Transaction.OutPoint.Hash,
                        Timestamp        = item.Transaction.CreationTime,
                        ConfirmedInBlock = item.Transaction.BlockHeight,
                        BlockIndex       = item.Transaction.BlockIndex
                    };

                    if (item.Transaction.IsSent == true) // handle send entries
                    {
                        // First we look for staking transaction as they require special attention.
                        // A staking transaction spends one of our inputs into 2 outputs or more, paid to the same address.
                        if ((item.Transaction.IsCoinStake ?? false == true))
                        {
                            if (item.Transaction.IsSent == true)
                            {
                                modelItem.Type = TransactionItemType.Staked;
                                var amount = item.Transaction.SentPayments.Sum(p => p.Amount);
                                modelItem.Amount = amount - item.Transaction.Amount;
                            }
                            else
                            {
                                // We don't show in history transactions that are outputs of staking transactions.
                                continue;
                            }
                        }
                        else
                        {
                            if (item.Transaction.SentPayments.All(a => a.PayToSelf == true))
                            {
                                // if all outputs are to ourself
                                // we don't show that in history
                                continue;
                            }

                            modelItem.Amount = item.Transaction.SentPayments.Where(x => x.PayToSelf == false).Sum(p => p.Amount);
                        }
                    }
                    else // handle receive entries
                    {
                        if (item.Address.IsChangeAddress())
                        {
                            // we don't display transactions sent to self
                            continue;
                        }

                        if (item.Transaction.IsCoinStake.HasValue && item.Transaction.IsCoinStake.Value == true)
                        {
                            // We don't show in history transactions that are outputs of staking transactions.
                            continue;
                        }
                    }

                    transactionItems.Add(modelItem);
                }

                model.AccountsHistoryModel.Add(new AccountHistoryModel
                {
                    TransactionsHistory = transactionItems,
                    Name     = accountHistory.Account.Name,
                    CoinType = network.Consensus.CoinType,
                    HdPath   = accountHistory.Account.HdPath
                });
            }

            return(model);
        }
        public static WalletHistoryModel GetHistory(IWalletManager walletManager, Network network, WalletHistoryRequest request)
        {
            var model = new WalletHistoryModel();

            // Get a list of all the transactions found in an account (or in a wallet if no account is specified), with the addresses associated with them.
            IEnumerable <AccountHistory> accountsHistory = walletManager.GetHistory(request.WalletName, request.AccountName);

            foreach (AccountHistory accountHistory in accountsHistory)
            {
                var transactionItems     = new List <TransactionItemModel>();
                var uniqueProcessedTxIds = new HashSet <uint256>();

                IEnumerable <FlatHistory> query = accountHistory.History;

                if (!string.IsNullOrEmpty(request.Address))
                {
                    query = query.Where(x => x.Address.Address == request.Address);
                }

                // Sorting the history items by descending dates. That includes received and sent dates.
                List <FlatHistory> items = query
                                           .OrderBy(o => o.Transaction.IsConfirmed() ? 1 : 0)
                                           .ThenByDescending(o => o.Transaction.SpendingDetails?.CreationTime ?? o.Transaction.CreationTime)
                                           .ToList();

                // Represents a sublist containing only the transactions that have already been spent.
                List <FlatHistory> spendingDetails = items.Where(t => t.Transaction.SpendingDetails != null).ToList();

                // Represents a sublist of transactions associated with receive addresses + a sublist of already spent transactions associated with change addresses.
                // In effect, we filter out 'change' transactions that are not spent, as we don't want to show these in the history.
                List <FlatHistory> history = items.Where(t => !t.Address.IsChangeAddress() || (t.Address.IsChangeAddress() && t.Transaction.IsSpent())).ToList();

                // Represents a sublist of 'change' transactions.
                List <FlatHistory> allchange = items.Where(t => t.Address.IsChangeAddress()).ToList();

                foreach (FlatHistory item in history)
                {
                    // Count only unique transactions and limit it to MaxHistoryItemsPerAccount.
                    int processedTransactions = uniqueProcessedTxIds.Count;
                    if (processedTransactions >= WalletController.MaxHistoryItemsPerAccount)
                    {
                        break;
                    }

                    TransactionOutputData transaction = item.Transaction;
                    HdAddress             address     = item.Address;

                    // First we look for staking transaction as they require special attention.
                    // A staking transaction spends one of our inputs into 2 outputs or more, paid to the same address.
                    if (transaction.SpendingDetails?.IsCoinStake != null && transaction.SpendingDetails.IsCoinStake.Value)
                    {
                        // We look for the output(s) related to our spending input.
                        List <FlatHistory> relatedOutputs = items.Where(h => h.Transaction.Id == transaction.SpendingDetails.TransactionId && h.Transaction.IsCoinStake != null && h.Transaction.IsCoinStake.Value).ToList();
                        if (relatedOutputs.Any())
                        {
                            // Add staking transaction details.
                            // The staked amount is calculated as the difference between the sum of the outputs and the input and should normally be equal to 1.
                            var stakingItem = new TransactionItemModel
                            {
                                Type             = TransactionItemType.Staked,
                                ToAddress        = address.Address,
                                Amount           = relatedOutputs.Sum(o => o.Transaction.Amount) - transaction.Amount,
                                Id               = transaction.SpendingDetails.TransactionId,
                                Timestamp        = transaction.SpendingDetails.CreationTime,
                                ConfirmedInBlock = transaction.SpendingDetails.BlockHeight,
                                BlockIndex       = transaction.SpendingDetails.BlockIndex
                            };

                            transactionItems.Add(stakingItem);
                            uniqueProcessedTxIds.Add(stakingItem.Id);
                        }

                        // No need for further processing if the transaction itself is the output of a staking transaction.
                        if (transaction.IsCoinStake != null)
                        {
                            continue;
                        }
                    }

                    // If this is a normal transaction (not staking) that has been spent, add outgoing fund transaction details.
                    if (transaction.SpendingDetails != null && transaction.SpendingDetails.IsCoinStake == null)
                    {
                        // Create a record for a 'send' transaction.
                        uint256 spendingTransactionId = transaction.SpendingDetails.TransactionId;
                        var     sentItem = new TransactionItemModel
                        {
                            Type             = TransactionItemType.Send,
                            Id               = spendingTransactionId,
                            Timestamp        = transaction.SpendingDetails.CreationTime,
                            ConfirmedInBlock = transaction.SpendingDetails.BlockHeight,
                            BlockIndex       = transaction.SpendingDetails.BlockIndex,
                            Amount           = Money.Zero
                        };

                        // If this 'send' transaction has made some external payments, i.e the funds were not sent to another address in the wallet.
                        if (transaction.SpendingDetails.Payments != null)
                        {
                            sentItem.Payments = new List <PaymentDetailModel>();
                            foreach (PaymentDetails payment in transaction.SpendingDetails.Payments)
                            {
                                sentItem.Payments.Add(new PaymentDetailModel
                                {
                                    DestinationAddress = payment.DestinationAddress,
                                    Amount             = payment.Amount
                                });

                                sentItem.Amount += payment.Amount;
                            }
                        }

                        // Get the change address for this spending transaction.
                        FlatHistory changeAddress = allchange.FirstOrDefault(a => a.Transaction.Id == spendingTransactionId);

                        // Find all the spending details containing the spending transaction id and aggregate the sums.
                        // This is our best shot at finding the total value of inputs for this transaction.
                        var inputsAmount = new Money(spendingDetails.Where(t => t.Transaction.SpendingDetails.TransactionId == spendingTransactionId).Sum(t => t.Transaction.Amount));

                        // The fee is calculated as follows: funds in utxo - amount spent - amount sent as change.
                        sentItem.Fee = inputsAmount - sentItem.Amount - (changeAddress == null ? 0 : changeAddress.Transaction.Amount);

                        // Mined/staked coins add more coins to the total out.
                        // That makes the fee negative. If that's the case ignore the fee.
                        if (sentItem.Fee < 0)
                        {
                            sentItem.Fee = 0;
                        }

                        transactionItems.Add(sentItem);
                        uniqueProcessedTxIds.Add(sentItem.Id);
                    }

                    // We don't show in history transactions that are outputs of staking transactions.
                    if (transaction.IsCoinStake != null && transaction.IsCoinStake.Value && transaction.SpendingDetails == null)
                    {
                        continue;
                    }

                    // Create a record for a 'receive' transaction.
                    if (transaction.IsCoinStake == null && !address.IsChangeAddress())
                    {
                        // First check if we already have a similar transaction output, in which case we just sum up the amounts
                        TransactionItemModel existingReceivedItem = FindSimilarReceivedTransactionOutput(transactionItems, transaction);

                        if (existingReceivedItem == null)
                        {
                            // Add incoming fund transaction details.
                            var receivedItem = new TransactionItemModel
                            {
                                Type             = TransactionItemType.Received,
                                ToAddress        = address.Address,
                                Amount           = transaction.Amount,
                                Id               = transaction.Id,
                                Timestamp        = transaction.CreationTime,
                                ConfirmedInBlock = transaction.BlockHeight,
                                BlockIndex       = transaction.BlockIndex
                            };

                            transactionItems.Add(receivedItem);
                            uniqueProcessedTxIds.Add(receivedItem.Id);
                        }
                        else
                        {
                            existingReceivedItem.Amount += transaction.Amount;
                        }
                    }
                }

                transactionItems = transactionItems.Distinct(new SentTransactionItemModelComparer()).Select(e => e).ToList();

                // Sort and filter the history items.
                List <TransactionItemModel> itemsToInclude = transactionItems.OrderByDescending(t => t.Timestamp)
                                                             .Where(x => String.IsNullOrEmpty(request.SearchQuery) || (x.Id.ToString() == request.SearchQuery || x.ToAddress == request.SearchQuery || x.Payments.Any(p => p.DestinationAddress == request.SearchQuery)))
                                                             .Skip(request.Skip ?? 0)
                                                             .Take(request.Take ?? transactionItems.Count)
                                                             .ToList();

                model.AccountsHistoryModel.Add(new AccountHistoryModel
                {
                    TransactionsHistory = itemsToInclude,
                    Name     = accountHistory.Account.Name,
                    CoinType = network.Consensus.CoinType,
                    HdPath   = accountHistory.Account.HdPath
                });
            }

            return(model);
        }
Пример #10
0
        public static TransactionItemModel ToModel(this TransactionItem transactionItem)
        {
            var model = new TransactionItemModel();

            return(model.Update(transactionItem));
        }
Пример #11
0
        public IActionResult GetHistory([FromQuery] WalletHistoryRequest request)
        {
            // checks the request is valid
            if (!this.ModelState.IsValid)
            {
                var errors = this.ModelState.Values.SelectMany(e => e.Errors.Select(m => m.ErrorMessage));
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, "Formatting error", string.Join(Environment.NewLine, errors)));
            }

            try
            {
                WalletHistoryModel model = new WalletHistoryModel {
                    TransactionsHistory = new List <TransactionItemModel>()
                };

                // get transactions contained in the wallet
                var addresses = this.walletManager.GetHistoryByCoinType(request.WalletName, this.coinType);
                foreach (var address in addresses.Where(a => !a.IsChangeAddress()))
                {
                    foreach (var transaction in address.Transactions)
                    {
                        TransactionItemModel item = new TransactionItemModel();
                        if (transaction.Amount > Money.Zero)
                        {
                            item.Type      = TransactionItemType.Received;
                            item.ToAddress = address.Address;
                            item.Amount    = transaction.Amount;
                        }
                        else
                        {
                            item.Type   = TransactionItemType.Send;
                            item.Amount = Money.Zero;
                            if (transaction.Payments != null)
                            {
                                item.Payments = new List <PaymentDetailModel>();
                                foreach (var payment in transaction.Payments)
                                {
                                    item.Payments.Add(new PaymentDetailModel
                                    {
                                        DestinationAddress = payment.DestinationAddress,
                                        Amount             = payment.Amount
                                    });

                                    item.Amount += payment.Amount;
                                }
                            }

                            var changeAddress = addresses.Single(a => a.IsChangeAddress() && a.Transactions.Any(t => t.Id == transaction.Id));
                            item.Fee = transaction.Amount.Abs() - item.Amount - changeAddress.Transactions.First(t => t.Id == transaction.Id).Amount;
                        }

                        item.Id               = transaction.Id;
                        item.Timestamp        = transaction.CreationTime;
                        item.ConfirmedInBlock = transaction.BlockHeight;

                        model.TransactionsHistory.Add(item);
                    }
                }

                model.TransactionsHistory = model.TransactionsHistory.OrderByDescending(t => t.Timestamp).ToList();
                return(this.Json(model));
            }
            catch (Exception e)
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }