예제 #1
0
        /// <summary>
        /// We get the details via the wallet service's history method.
        /// </summary>
        private async Task <WalletHistoryModel> GetWatchOnlyTransactionAsync(uint256 trxid)
        {
            var accountReference = this.GetWatchOnlyWalletAccountReference();

            var request = new WalletHistoryRequest()
            {
                WalletName  = accountReference.WalletName,
                AccountName = Wallet.WatchOnlyAccountName,
                SearchQuery = trxid.ToString(),
            };

            var history = await this.walletService.GetHistory(request, default);

            return(history);
        }
예제 #2
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 {
                    Transactions = new List <TransactionItem>()
                };

                // get transactions contained in the wallet
                var addresses = this.walletManager.GetHistoryByCoinType(request.WalletName, request.CoinType);
                foreach (var address in addresses)
                {
                    foreach (var transaction in address.Transactions)
                    {
                        model.Transactions.Add(new TransactionItem
                        {
                            Amount        = transaction.Amount,
                            Confirmed     = transaction.Confirmed,
                            Timestamp     = transaction.CreationTime,
                            TransactionId = transaction.Id,
                            Address       = address.Address
                        });
                    }
                }

                model.Transactions = model.Transactions.OrderByDescending(t => t.Timestamp).ToList();
                return(this.Json(model));
            }
            catch (Exception e)
            {
                return(ErrorHelpers.BuildErrorResponse(HttpStatusCode.BadRequest, e.Message, e.ToString()));
            }
        }
예제 #3
0
        public async Task GetWalletHistory()
        {
            const int unixTimeSeconds = 1493942400;

            _fakeHttpMessageHandler.HandleRequestAsync = TestHelper.HandleContent(form =>
            {
                Assert.Equal(unixTimeSeconds.ToString(CultureInfo.InvariantCulture), form["date"]);
            });
            _fakeHttpMessageHandler.ResponseMessage = new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = new StringContent("{\"result\":true,\"error\":\"\",\"begin\":\"1493942400\",\"end\":\"1494028800\",\"history\":[{\"dt\":1461841192,\"type\":\"deposit\",\"curr\":\"RUB\",\"status\":\"processing\",\"provider\":\"Qiwi (LA) [12345]\",\"amount\":\"1\",\"account\":\"\",\"txid\":\"ec46f784ad976fd7f7539089d1a129fe46\"},{\"dt\":1463414785,\"type\":\"withdrawal\",\"curr\":\"USD\",\"status\":\"paid\",\"provider\":\"EXCODE\",\"amount\":\"-1\",\"account\":\"EX-CODE_19371_USDda\",\"txid\":\"\"}]}")
            };

            var request = new WalletHistoryRequest
            {
                Date = DateTimeOffset.FromUnixTimeSeconds(unixTimeSeconds)
            };
            var walletHistory = await _authenticatedApi.GetWalletHistoryAsync(request);

            Assert.Equal(1493942400, walletHistory.Begin.ToUnixTimeSeconds());
            Assert.Equal(1494028800, walletHistory.End.ToUnixTimeSeconds());
            Assert.Equal(2, walletHistory.History.Length);
            Assert.Equal(1461841192, walletHistory.History[0].Date.ToUnixTimeSeconds());
            Assert.Equal("deposit", walletHistory.History[0].Type);
            Assert.Equal("RUB", walletHistory.History[0].Currency);
            Assert.Equal("processing", walletHistory.History[0].Status);
            Assert.Equal("Qiwi (LA) [12345]", walletHistory.History[0].Provider);
            Assert.Equal(1m, walletHistory.History[0].Amount);
            Assert.Equal("", walletHistory.History[0].Account);
            Assert.Equal("ec46f784ad976fd7f7539089d1a129fe46", walletHistory.History[0].TransactionId);
            Assert.Equal(1463414785, walletHistory.History[1].Date.ToUnixTimeSeconds());
            Assert.Equal("withdrawal", walletHistory.History[1].Type);
            Assert.Equal("USD", walletHistory.History[1].Currency);
            Assert.Equal("paid", walletHistory.History[1].Status);
            Assert.Equal("EXCODE", walletHistory.History[1].Provider);
            Assert.Equal(-1m, walletHistory.History[1].Amount);
            Assert.Equal("EX-CODE_19371_USDda", walletHistory.History[1].Account);
            Assert.Equal("", walletHistory.History[1].TransactionId);
        }
예제 #4
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);
        }
예제 #9
0
 public async Task <IActionResult> GetHistory([FromQuery] WalletHistoryRequest request,
                                              CancellationToken cancellationToken = default(CancellationToken))
 {
     return(await this.Execute(request, cancellationToken,
                               async (req, token) => this.Json(await this.walletService.GetHistory(req, token))));
 }
예제 #10
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()));
            }
        }
예제 #11
0
 public Task <WalletHistory> GetWalletHistoryAsync(WalletHistoryRequest request, CancellationToken cancellationToken = default)
 {
     return(_client.PostAsync <WalletHistory>("wallet_history", request, cancellationToken));
 }