/// <summary> /// Adds the transaction to the wallet. /// </summary> /// <param name="transactionHash">The transaction hash.</param> /// <param name="time">The time.</param> /// <param name="paidToOutputs">A list of payments made out</param> /// <param name="spendingTransactionId">The id of the transaction containing the output being spent, if this is a spending transaction.</param> /// <param name="spendingTransactionIndex">The index of the output in the transaction being referenced, if this is a spending transaction.</param> /// <param name="blockHeight">Height of the block.</param> /// <param name="block">The block containing the transaction to add.</param> /// <param name="transactionHex">The hexadecimal representation of the transaction.</param> private void AddSpendingTransactionToWallet(string transactionHex, uint256 transactionHash, uint time, IEnumerable <TxOut> paidToOutputs, uint256 spendingTransactionId, int?spendingTransactionIndex, int?blockHeight = null, Block block = null) { // get the transaction being spent TransactionData spentTransaction = this.keysLookup.Values.Distinct().SelectMany(v => v.Transactions) .SingleOrDefault(t => t.Id == spendingTransactionId && t.Index == spendingTransactionIndex); if (spentTransaction == null) { // strange, why would it be null? return; } // if the details of this spending transaction are seen for the first time if (spentTransaction.SpendingDetails == null) { List <PaymentDetails> payments = new List <PaymentDetails>(); foreach (var paidToOutput in paidToOutputs) { payments.Add(new PaymentDetails { DestinationScriptPubKey = paidToOutput.ScriptPubKey, DestinationAddress = paidToOutput.ScriptPubKey.GetDestinationAddress(this.network)?.ToString(), Amount = paidToOutput.Value }); } SpendingDetails spendingDetails = new SpendingDetails { TransactionId = transactionHash, Payments = payments, CreationTime = DateTimeOffset.FromUnixTimeSeconds(block?.Header.Time ?? time), BlockHeight = blockHeight, Hex = transactionHex }; spentTransaction.SpendingDetails = spendingDetails; spentTransaction.MerkleProof = null; } else // if this spending transaction is being confirmed in a block { // update the block height if (spentTransaction.SpendingDetails.BlockHeight == null && blockHeight != null) { spentTransaction.SpendingDetails.BlockHeight = blockHeight; } // update the block time to be that of the block in which the transaction is confirmed if (block != null) { spentTransaction.SpendingDetails.CreationTime = DateTimeOffset.FromUnixTimeSeconds(block.Header.Time); } } }
public GetTransactionModel GetTransaction(string txid) { if (!uint256.TryParse(txid, out uint256 trxid)) { throw new ArgumentException(nameof(txid)); } WalletAccountReference accountReference = this.GetWalletAccountReference(); Wallet hdWallet = this.walletManager.WalletRepository.GetWallet(accountReference.WalletName); HdAccount hdAccount = this.walletManager.WalletRepository.GetAccounts(hdWallet, accountReference.AccountName).First(); IWalletAddressReadOnlyLookup addressLookup = this.walletManager.WalletRepository.GetWalletAddressLookup(accountReference.WalletName); bool IsChangeAddress(Script scriptPubKey) { return(addressLookup.Contains(scriptPubKey, out AddressIdentifier addressIdentifier) && addressIdentifier.AddressType == 1); } // Get the transaction from the wallet by looking into received and send transactions. List <TransactionData> receivedTransactions = this.walletManager.WalletRepository.GetTransactionOutputs(hdAccount, null, trxid, true) .Where(td => !IsChangeAddress(td.ScriptPubKey)).ToList(); List <TransactionData> sentTransactions = this.walletManager.WalletRepository.GetTransactionInputs(hdAccount, null, trxid, true).ToList(); TransactionData firstReceivedTransaction = receivedTransactions.FirstOrDefault(); TransactionData firstSendTransaction = sentTransactions.FirstOrDefault(); if (firstReceivedTransaction == null && firstSendTransaction == null) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id."); } uint256 blockHash = null; int? blockHeight, blockIndex; DateTimeOffset transactionTime; SpendingDetails spendingDetails = firstSendTransaction?.SpendingDetails; if (firstReceivedTransaction != null) { blockHeight = firstReceivedTransaction.BlockHeight; blockIndex = firstReceivedTransaction.BlockIndex; blockHash = firstReceivedTransaction.BlockHash; transactionTime = firstReceivedTransaction.CreationTime; } else { blockHeight = spendingDetails.BlockHeight; blockIndex = spendingDetails.BlockIndex; blockHash = spendingDetails.BlockHash; transactionTime = spendingDetails.CreationTime; } // Get the block containing the transaction (if it has been confirmed). ChainedHeaderBlock chainedHeaderBlock = null; if (blockHash != null) { this.ConsensusManager.GetOrDownloadBlocks(new List <uint256> { blockHash }, b => { chainedHeaderBlock = b; }); } Block block = null; Transaction transactionFromStore = null; if (chainedHeaderBlock != null) { block = chainedHeaderBlock.Block; if (block != null) { if (blockIndex == null) { blockIndex = block.Transactions.FindIndex(t => t.GetHash() == trxid); } transactionFromStore = block.Transactions[(int)blockIndex]; } } bool isGenerated; string hex; if (transactionFromStore != null) { transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time); isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake; hex = transactionFromStore.ToHex(); } else { isGenerated = false; hex = null; // TODO get from mempool } var model = new GetTransactionModel { Confirmations = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0, Isgenerated = isGenerated ? true : (bool?)null, BlockHash = blockHash, BlockIndex = blockIndex, BlockTime = block?.Header.BlockTime.ToUnixTimeSeconds(), TransactionId = uint256.Parse(txid), TransactionTime = transactionTime.ToUnixTimeSeconds(), TimeReceived = transactionTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = hex }; // Send transactions details. if (spendingDetails != null) { Money feeSent = Money.Zero; if (firstSendTransaction != null) { // Get the change. long change = spendingDetails.Change.Sum(o => o.Amount); Money inputsAmount = new Money(sentTransactions.Sum(i => i.Amount)); Money outputsAmount = new Money(spendingDetails.Payments.Sum(p => p.Amount) + change); feeSent = inputsAmount - outputsAmount; } var details = spendingDetails.Payments .GroupBy(detail => detail.DestinationAddress) .Select(p => new GetTransactionDetailsModel() { Address = p.Key, Category = GetTransactionDetailsCategoryModel.Send, OutputIndex = p.First().OutputIndex, Amount = 0 - p.Sum(detail => detail.Amount.ToDecimal(MoneyUnit.BTC)), Fee = -feeSent.ToDecimal(MoneyUnit.BTC) }); model.Details.AddRange(details); } // Get the ColdStaking script template if available. Dictionary <string, ScriptTemplate> templates = this.walletManager.GetValidStakingTemplates(); ScriptTemplate coldStakingTemplate = templates.ContainsKey("ColdStaking") ? templates["ColdStaking"] : null; // Receive transactions details. IScriptAddressReader scriptAddressReader = this.FullNode.NodeService <IScriptAddressReader>(); foreach (TransactionData trxInWallet in receivedTransactions) { // Skip the details if the script pub key is cold staking. // TODO: Verify if we actually need this any longer, after changing the internals to recognize account type if (coldStakingTemplate != null && coldStakingTemplate.CheckScriptPubKey(trxInWallet.ScriptPubKey)) { continue; } GetTransactionDetailsCategoryModel category; if (isGenerated) { category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature; } else { category = GetTransactionDetailsCategoryModel.Receive; } string address = scriptAddressReader.GetAddressFromScriptPubKey(this.FullNode.Network, trxInWallet.ScriptPubKey); model.Details.Add(new GetTransactionDetailsModel { Address = address, Category = category, Amount = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC), OutputIndex = trxInWallet.Index }); } model.Amount = model.Details.Sum(d => d.Amount); model.Fee = model.Details.FirstOrDefault(d => d.Category == GetTransactionDetailsCategoryModel.Send)?.Fee; return(model); }
public PaymentCollection(SpendingDetails spendingDetails, ICollection <PaymentDetails> payments, bool isChange) { this.isChange = isChange; this.spendingDetails = spendingDetails; this.payments = payments?.ToList() ?? new List <PaymentDetails>(); }