ListSinceBlockTransactionCategoryModel GetListSinceBlockTransactionCategoryModel( GetTransactionModel transaction) { if (transaction.Isgenerated ?? false) { return(transaction.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? ListSinceBlockTransactionCategoryModel.Generate : ListSinceBlockTransactionCategoryModel.Immature); } return(transaction.Amount > 0 ? ListSinceBlockTransactionCategoryModel.Receive : ListSinceBlockTransactionCategoryModel.Send); }
public async Task GetTransactionOnGeneratedTransactionAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StraxRegTest150Miner).Start(); ChainedHeader header = sendingNode.FullNode.ChainIndexer.GetHeader(3); Block fetchBlock = sendingNode.FullNode.NodeService <IBlockStore>().GetBlock(header.HashBlock); string blockHash = header.HashBlock.ToString(); // Transaction included in block at height 3. string txId = fetchBlock.Transactions[0].GetHash().ToString(); // Check transaction exists in block #3. BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = blockHash, outputJson = true }) .GetJsonAsync <BlockModel>(); block.Transactions.Should().ContainSingle(t => (string)t == txId); // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId); // Assert. GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>(); result.Amount.Should().Be(this.network.Consensus.ProofOfWorkReward.ToDecimal(MoneyUnit.BTC)); result.Fee.Should().BeNull(); result.Confirmations.Should().Be(148); result.Isgenerated.Should().BeTrue(); result.TransactionId.Should().Be(uint256.Parse(txId)); result.BlockHash.ToString().Should().Be(blockHash); result.BlockIndex.Should().Be(0); result.BlockTime.Should().Be(header.Header.Time); result.TimeReceived.Should().Be(fetchBlock.Header.Time); result.TransactionTime.Should().Be(fetchBlock.Header.Time); result.Details.Should().ContainSingle(); GetTransactionDetailsModel details = result.Details.Single(); details.Address.Should().Be(fetchBlock.Transactions[0].Outputs[0].ScriptPubKey.GetDestinationAddress(this.network).ToString()); details.Amount.Should().Be(this.network.Consensus.ProofOfWorkReward.ToDecimal(MoneyUnit.BTC)); details.Fee.Should().BeNull(); details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate); details.OutputIndex.Should().Be(0); } }
public GetTransactionModel GetTransaction(string txid) { uint256 trxid; if (!uint256.TryParse(txid, out trxid)) { throw new ArgumentException(nameof(txid)); } var accountReference = this.GetAccount(); var account = this.walletManager.GetAccounts(accountReference.WalletName) .Where(i => i.Name.Equals(accountReference.AccountName)) .Single(); var transaction = account.GetTransactionsById(trxid)?.Single(); if (transaction == null) { return(null); } var model = new GetTransactionModel { Amount = transaction.Amount, BlockHash = transaction.BlockHash, TransactionId = transaction.Id, TransactionTime = transaction.CreationTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = transaction.Hex == null ? string.Empty : transaction.Hex }; if (transaction.SpendingDetails?.Payments != null) { foreach (var paymentDetail in transaction.SpendingDetails.Payments) { model.Details.Add(new GetTransactionDetailsModel { Address = paymentDetail.DestinationAddress, Category = "send", Amount = paymentDetail.Amount }); } } return(model); }
public async Task GetTransactionOnGeneratedTransactionAsync() { // Transaction included in block at height 3. string txId = "5369de2c6b7b62902d303995e90406d82de48f8c5ed0e847618dcf4e5cde84a1"; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); // Check transaction exists in block #3. BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = "57546d732a7cdf38135377fd3eddebbb928ec760c1e6a4b1b36e775b4ac2a3c7", outputJson = true }) .GetJsonAsync <BlockModel>(); block.Transactions.Should().ContainSingle(t => (string)t == txId); // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId); // Assert. GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>(); result.Amount.Should().Be((decimal)4.00000000); result.Fee.Should().BeNull(); result.Confirmations.Should().Be(148); result.Isgenerated.Should().BeTrue(); result.TransactionId.Should().Be(uint256.Parse(txId)); result.BlockHash.ToString().Should().Be("57546d732a7cdf38135377fd3eddebbb928ec760c1e6a4b1b36e775b4ac2a3c7"); result.BlockIndex.Should().Be(0); result.BlockTime.Should().Be(1579271980); result.TimeReceived.Should().Be(1579271980); result.TransactionTime.Should().Be(1579271980); result.Details.Should().ContainSingle(); GetTransactionDetailsModel details = result.Details.Single(); details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb"); details.Amount.Should().Be((decimal)4.00000000); details.Fee.Should().BeNull(); details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate); details.OutputIndex.Should().Be(0); } }
public async Task GetTransactionOnGeneratedTransactionAsync() { // Transaction included in block at height 3. string txId = "21f3b1fd361f50992db217ea16728acc6b3dbf49c8531e60f8a28d7f3bdf4674"; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); // Check transaction exists in block #3. BlockModel block = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = "b1209de1c0347be83bb02a3bf9b70e33b06c82b91e68bc6392e6fb813cd5e4bd", outputJson = true }) .GetJsonAsync <BlockModel>(); block.Transactions.Should().ContainSingle(t => (string)t == txId); // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId); // Assert. GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>(); result.Amount.Should().Be((decimal)4.00000000); result.Fee.Should().BeNull(); result.Confirmations.Should().Be(148); result.Isgenerated.Should().BeTrue(); result.TransactionId.Should().Be(uint256.Parse(txId)); result.BlockHash.ToString().Should().Be("b1209de1c0347be83bb02a3bf9b70e33b06c82b91e68bc6392e6fb813cd5e4bd"); result.BlockIndex.Should().Be(0); result.BlockTime.Should().Be(1547206662); result.TimeReceived.Should().Be(1547206661); result.TransactionTime.Should().Be(1547206661); result.Details.Should().ContainSingle(); GetTransactionDetailsModel details = result.Details.Single(); details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb"); details.Amount.Should().Be((decimal)4.00000000); details.Fee.Should().BeNull(); details.Category.Should().Be(GetTransactionDetailsCategoryModel.Generate); details.OutputIndex.Should().Be(0); } }
public async Task GetTransactionOnImmatureTransactionAsync() { // Transaction included in block at height 145. string txId = "d65542742643891e17e7527101cee774e7b561f3f6499302c653aa93ace0d303"; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); // Check transaction exists in block #145. BlockTransactionDetailsModel blockTransactionDetailsModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = "a5f03782979d83120d446b2eee764c2e976697c9bf6baf5f5e3145b58ad9e72a", outputJson = true, showtransactiondetails = true }) .GetJsonAsync <BlockTransactionDetailsModel>(); blockTransactionDetailsModel.Transactions.Should().ContainSingle(t => t.TxId == txId); // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId); // Assert. GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>(); result.Amount.Should().Be((decimal)4.00000000); result.Fee.Should().BeNull(); result.Confirmations.Should().Be(6); result.Isgenerated.Should().BeTrue(); result.TransactionId.Should().Be(uint256.Parse(txId)); result.BlockHash.ToString().Should().Be("a5f03782979d83120d446b2eee764c2e976697c9bf6baf5f5e3145b58ad9e72a"); result.BlockIndex.Should().Be(0); result.BlockTime.Should().Be(blockTransactionDetailsModel.Time); result.TimeReceived.Should().BeLessOrEqualTo(blockTransactionDetailsModel.Time); result.Details.Should().ContainSingle(); GetTransactionDetailsModel details = result.Details.Single(); details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb"); details.Amount.Should().Be((decimal)4.00000000); details.Fee.Should().BeNull(); details.Category.Should().Be(GetTransactionDetailsCategoryModel.Immature); details.OutputIndex.Should().Be(0); } }
public async Task GetTransactionOnImmatureTransactionAsync() { // Transaction included in block at height 145. string txId = "3a96e0ffd83526b24fc035920fec89f689a778f64cd692b5eda2861cccb1ddba"; using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); // Check transaction exists in block #145. BlockTransactionDetailsModel block = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = "b8ab1d66febfde4c26d0b4755f7def50a63e06267cefc6d6836651fa8910babc", outputJson = true, showtransactiondetails = true }) .GetJsonAsync <BlockTransactionDetailsModel>(); block.Transactions.Should().ContainSingle(t => t.TxId == txId); // Act. RPCClient rpc = sendingNode.CreateRPCClient(); RPCResponse walletTx = rpc.SendCommand(RPCOperations.gettransaction, txId); // Assert. GetTransactionModel result = walletTx.Result.ToObject <GetTransactionModel>(); result.Amount.Should().Be((decimal)4.00000000); result.Fee.Should().BeNull(); result.Confirmations.Should().Be(6); result.Isgenerated.Should().BeTrue(); result.TransactionId.Should().Be(uint256.Parse(txId)); result.BlockHash.ToString().Should().Be("b8ab1d66febfde4c26d0b4755f7def50a63e06267cefc6d6836651fa8910babc"); result.BlockIndex.Should().Be(0); result.BlockTime.Should().Be(block.Time.ToUnixTimeSeconds()); result.TimeReceived.Should().BeLessOrEqualTo(block.Time.ToUnixTimeSeconds()); result.Details.Should().ContainSingle(); GetTransactionDetailsModel details = result.Details.Single(); details.Address.Should().Be("TXYdgqNVbHTfW5FsdxqSX6vejBByNEk2Yb"); details.Amount.Should().Be((decimal)4.00000000); details.Fee.Should().BeNull(); details.Category.Should().Be(GetTransactionDetailsCategoryModel.Immature); details.OutputIndex.Should().Be(0); } }
public GetTransactionModel GetTransaction(string txid) { uint256 trxid; if (!uint256.TryParse(txid, out trxid)) { throw new ArgumentException(nameof(txid)); } var accountReference = this.GetAccount(); var account = this.walletManager.GetAccounts(accountReference.WalletName) .Where(i => i.Name.Equals(accountReference.AccountName)) .Single(); // 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.GetHistoryById(trxid, accountReference.WalletName, accountReference.AccountName).ToList(); if (!accountsHistory.Any()) { return(null); // This transaction is not relevant for our wallet. } var accountHistory = accountsHistory.First(); var accountHistoryHistory = accountHistory.History.FirstOrDefault(); var transaction = accountHistoryHistory.Transaction; var isChangeAddress = accountHistoryHistory.Address.IsChangeAddress(); var confirmations = GetConformationCount(accountHistoryHistory.Transaction); var details = new List <GetTransactionDetailsModel>(); // Calculate the actual effect on the wallet that this transaction had. Money amount = 0; if (isChangeAddress) { // The actual amount of sent amount is accessible on the Payments that exists on the TransactionData that is the source of this // input. So we need to query the history to get that other transaction, so we can parse its Payments, and not those that exists // on the this current transaction. var paymentTransactions = account.GetTransactionsByPaymentTransactionId(trxid); foreach (var paymentTransaction in paymentTransactions) { // For change address history, the Amount property on the transaction will display the amount that was sent into the change address. // What we need for this RPC call, is the amount that was "taken out" of the wallet, and that is available in the spending details. foreach (var payment in paymentTransaction.SpendingDetails.Payments) { // Increase the total amount of change in the wallet if there are multiple payments. amount += -payment.Amount; details.Add(new GetTransactionDetailsModel { Account = string.Empty, // Can be empty string for default account. Address = payment.DestinationAddress, Category = "send", Amount = -payment.Amount }); } } } else { if (transaction.IsCoinStake.GetValueOrDefault(false)) { // Since this is a coin stake output, we'll hard-code to the ProofOfStakeReward. If this is implemented with a variable // output, then this will likely result in invalid responses. amount = this.FullNode.Network.Consensus.ProofOfStakeReward; details.Add(new GetTransactionDetailsModel { Account = string.Empty, // Can be empty string for default account. Address = accountHistoryHistory.Address.Address, Category = "stake", Amount = amount }); } else { amount = transaction.Amount; // For "receive" payments, we can read the actual Amount on the TransactionData instance. This will display the correct change on the wallet. details.Add(new GetTransactionDetailsModel { Account = string.Empty, // Can be empty string for default account. Address = accountHistoryHistory.Address.Address, Category = "receive", Amount = amount }); } } var model = new GetTransactionModel { Amount = amount, BlockHash = transaction.BlockHash, TransactionId = transaction.Id, TransactionTime = transaction.CreationTime.ToUnixTimeSeconds(), Confirmations = confirmations, Details = details, Hex = transaction.Hex == null ? string.Empty : transaction.Hex, }; return(model); }
public async Task <GetTransactionModel> GetTransactionAsync(string txid) { if (!uint256.TryParse(txid, out uint256 trxid)) { throw new ArgumentException(nameof(txid)); } WalletAccountReference accountReference = this.GetAccount(); HdAccount account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName); // Get the transaction from the wallet by looking into received and send transactions. List <HdAddress> addresses = account.GetCombinedAddresses().ToList(); List <TransactionData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress() && r.Transactions != null).SelectMany(a => a.Transactions.Where(t => t.Id == trxid)).ToList(); List <TransactionData> sendTransactions = addresses.Where(r => r.Transactions != null).SelectMany(a => a.Transactions.Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList(); if (!receivedTransactions.Any() && !sendTransactions.Any()) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id."); } // Get the block hash from the transaction in the wallet. TransactionData transactionFromWallet = null; uint256 blockHash = null; int? blockHeight; if (receivedTransactions.Any()) { blockHeight = receivedTransactions.First().BlockHeight; blockHash = receivedTransactions.First().BlockHash; transactionFromWallet = receivedTransactions.First(); } else { blockHeight = sendTransactions.First().SpendingDetails.BlockHeight; blockHash = blockHeight != null?this.Chain.GetBlock(blockHeight.Value).HashBlock : null; } // Get the block containing the transaction (if it has been confirmed). ChainedHeaderBlock chainedHeaderBlock = null; if (blockHash != null) { await this.ConsensusManager.GetOrDownloadBlocksAsync(new List <uint256> { blockHash }, b => { chainedHeaderBlock = b; }); } Block block = null; Transaction transactionFromStore = null; if (chainedHeaderBlock != null) { block = chainedHeaderBlock.Block; transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid); } DateTimeOffset transactionTime; bool isGenerated; string hex; if (transactionFromStore != null) { transactionTime = Utils.UnixTimeToDateTime(transactionFromStore.Time); isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake; hex = transactionFromStore.ToHex(); } else if (transactionFromWallet != null) { transactionTime = transactionFromWallet.CreationTime; isGenerated = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true; hex = transactionFromWallet.Hex; } else { transactionTime = sendTransactions.First().SpendingDetails.CreationTime; isGenerated = false; hex = null; // TODO get from mempool } Money amountSent = sendTransactions.Select(s => s.SpendingDetails).SelectMany(sds => sds.Payments).GroupBy(p => p.DestinationAddress).Select(g => g.First()).Sum(p => p.Amount); Money totalAmount = receivedTransactions.Sum(t => t.Amount) - amountSent; var model = new GetTransactionModel { Amount = totalAmount.ToDecimal(MoneyUnit.BTC), Fee = null,// TODO this still needs to be worked on. Confirmations = blockHeight != null ? this.ConsensusManager.Tip.Height - blockHeight.Value + 1 : 0, Isgenerated = isGenerated ? true : (bool?)null, BlockHash = blockHash, BlockIndex = block?.Transactions.FindIndex(t => t.GetHash() == trxid), BlockTime = block?.Header.BlockTime.ToUnixTimeSeconds(), TransactionId = uint256.Parse(txid), TransactionTime = transactionTime.ToUnixTimeSeconds(), TimeReceived = transactionTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = hex }; // Send transactions details. foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments)) { // Only a single item should appear per destination address. if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null) { model.Details.Add(new GetTransactionDetailsModel { Address = paymentDetail.DestinationAddress, Category = GetTransactionDetailsCategoryModel.Send, Amount = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC), Fee = null, // TODO this still needs to be worked on. OutputIndex = paymentDetail.OutputIndex }); } } // Receive transactions details. foreach (TransactionData trxInWallet in receivedTransactions) { GetTransactionDetailsCategoryModel category; if (isGenerated) { category = model.Confirmations > this.FullNode.Network.Consensus.CoinbaseMaturity ? GetTransactionDetailsCategoryModel.Generate : GetTransactionDetailsCategoryModel.Immature; } else { category = GetTransactionDetailsCategoryModel.Receive; } model.Details.Add(new GetTransactionDetailsModel { Address = addresses.First(a => a.Transactions.Contains(trxInWallet)).Address, Category = category, Amount = trxInWallet.Amount.ToDecimal(MoneyUnit.BTC), OutputIndex = trxInWallet.Index }); } return(model); }
public GetTransactionModel GetTransaction(string txid) { if (!uint256.TryParse(txid, out uint256 trxid)) { throw new ArgumentException(nameof(txid)); } WalletAccountReference accountReference = this.GetWalletAccountReference(); Types.Wallet wallet = this.walletManager.GetWalletByName(accountReference.WalletName); HdAccount account = this.walletManager.GetAccounts(accountReference.WalletName).Single(a => a.Name == accountReference.AccountName); // Get the transaction from the wallet by looking into received and send transactions. List <HdAddress> addresses = account.GetCombinedAddresses().ToList(); List <TransactionOutputData> receivedTransactions = addresses.Where(r => !r.IsChangeAddress()).SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.Id == trxid)).ToList(); List <TransactionOutputData> sendTransactions = addresses.SelectMany(a => wallet.walletStore.GetForAddress(a.Address).Where(t => t.SpendingDetails != null && t.SpendingDetails.TransactionId == trxid)).ToList(); if (!receivedTransactions.Any() && !sendTransactions.Any()) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id."); } // Get the block hash from the transaction in the wallet. TransactionOutputData transactionFromWallet = null; uint256 blockHash = null; int? blockHeight, blockIndex; if (receivedTransactions.Any()) { blockHeight = receivedTransactions.First().BlockHeight; blockIndex = receivedTransactions.First().BlockIndex; blockHash = receivedTransactions.First().BlockHash; transactionFromWallet = receivedTransactions.First(); } else { blockHeight = sendTransactions.First().SpendingDetails.BlockHeight; blockIndex = sendTransactions.First().SpendingDetails.BlockIndex; blockHash = blockHeight != null?this.ChainIndexer.GetHeader(blockHeight.Value).HashBlock : null; } // 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; transactionFromStore = block.Transactions.Single(t => t.GetHash() == trxid); } DateTimeOffset transactionTime; bool isGenerated; string hex; if (transactionFromStore != null) { // TODO: Use block header time only. The transaction times will need to be uniformly set to a fixed value when an anti-malleability softfork activates if (transactionFromStore is IPosTransactionWithTime posTrx) { transactionTime = Utils.UnixTimeToDateTime(posTrx.Time); } else { transactionTime = Utils.UnixTimeToDateTime(chainedHeaderBlock.ChainedHeader.Header.Time); } isGenerated = transactionFromStore.IsCoinBase || transactionFromStore.IsCoinStake; hex = transactionFromStore.ToHex(); } else if (transactionFromWallet != null) { transactionTime = transactionFromWallet.CreationTime; isGenerated = transactionFromWallet.IsCoinBase == true || transactionFromWallet.IsCoinStake == true; hex = transactionFromWallet.Hex; } else { transactionTime = sendTransactions.First().SpendingDetails.CreationTime; 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 ?? block?.Transactions.FindIndex(t => t.GetHash() == trxid), BlockTime = block?.Header.BlockTime.ToUnixTimeSeconds(), TransactionId = uint256.Parse(txid), TransactionTime = transactionTime.ToUnixTimeSeconds(), TimeReceived = transactionTime.ToUnixTimeSeconds(), Details = new List <GetTransactionDetailsModel>(), Hex = hex }; Money feeSent = Money.Zero; if (sendTransactions.Any()) { feeSent = wallet.GetSentTransactionFee(trxid); } // Send transactions details. foreach (PaymentDetails paymentDetail in sendTransactions.Select(s => s.SpendingDetails).SelectMany(sd => sd.Payments)) { // Only a single item should appear per destination address. if (model.Details.SingleOrDefault(d => d.Address == paymentDetail.DestinationAddress) == null) { model.Details.Add(new GetTransactionDetailsModel { Address = paymentDetail.DestinationAddress, Category = GetTransactionDetailsCategoryModel.Send, Amount = -paymentDetail.Amount.ToDecimal(MoneyUnit.BTC), Fee = -feeSent.ToDecimal(MoneyUnit.BTC), OutputIndex = paymentDetail.OutputIndex }); } } // 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. foreach (TransactionOutputData 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 recognice 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; } model.Details.Add(new GetTransactionDetailsModel { Address = trxInWallet.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 Task <ListSinceBlockModel> ListSinceBlockAsync(string blockHash, int targetConfirmations = 1) { ChainedHeader headerBlock = null; if (!string.IsNullOrEmpty(blockHash) && uint256.TryParse(blockHash, out uint256 hashBlock)) { headerBlock = this.ChainIndexer.GetHeader(hashBlock); } if (!string.IsNullOrEmpty(blockHash) && headerBlock == null) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); } if (targetConfirmations < 1) { throw new RPCServerException(RPCErrorCode.RPC_INVALID_PARAMETER, "Invalid parameter"); } WalletAccountReference accountReference = this.GetWalletAccountReference(); Types.Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName); IEnumerable <TransactionOutputData> transactions = wallet.GetAllTransactions(); var model = new ListSinceBlockModel(); foreach (TransactionOutputData transactionData in transactions) { GetTransactionModel transaction = this.GetTransaction(transactionData.Id.ToString()); int blockHeight = transactionData.BlockHeight ?? 0; if (headerBlock != null && blockHeight < headerBlock.Height) { continue; } if (transaction.Confirmations < targetConfirmations) { continue; } ListSinceBlockTransactionCategoryModel category = GetListSinceBlockTransactionCategoryModel(transaction); model.Transactions.Add(new ListSinceBlockTransactionModel { Confirmations = transaction.Confirmations, BlockHash = transaction.BlockHash, BlockIndex = transaction.BlockIndex, BlockTime = transaction.BlockTime, TransactionId = transaction.TransactionId, TransactionTime = transaction.TransactionTime, TimeReceived = transaction.TimeReceived, Account = accountReference.AccountName, Address = transactionData.ScriptPubKey?.GetDestinationAddress(this.Network)?.ToString(), Amount = transaction.Amount, Category = category, Fee = transaction.Fee }); } model.LastBlock = this.ChainIndexer.Tip.HashBlock; return(Task.FromResult(model)); }
public async Task GetTransactionOnTransactionSentFromMultipleOutputsAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "98000002" }).ToList(), }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; // Mine so that we make sure the node is up to date. TestHelper.MineBlocks(sendingNode, 1); // Get the block that was mined. string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("consensus/getbestblockhash") .GetJsonAsync <string>(); BlockModel blockModelAtTip = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = lastBlockHash, outputJson = true }) .GetJsonAsync <BlockModel>(); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000); resultSendingWallet.Fee.Should().Be((decimal) - 0.0001); resultSendingWallet.Confirmations.Should().Be(1); resultSendingWallet.Isgenerated.Should().BeNull(); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().Be(uint256.Parse(blockModelAtTip.Hash)); resultSendingWallet.BlockIndex.Should().Be(1); resultSendingWallet.BlockTime.Should().Be(blockModelAtTip.Time); resultSendingWallet.TimeReceived.Should().BeLessOrEqualTo(blockModelAtTip.Time); resultSendingWallet.TransactionTime.Should().Be(((PosTransaction)trx).Time); resultSendingWallet.Details.Count.Should().Be(1); GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single(); detailsSendingWallet.Address.Should().Be(unusedaddresses.Single()); detailsSendingWallet.Amount.Should().Be((decimal) - 98000002.00000000); detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001); detailsSendingWallet.OutputIndex.Should().Be(1); } }
public async Task GetTransactionOnUnconfirmedTransactionAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 1 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList(), }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; TestBase.WaitLoop(() => { WalletHistoryModel history = $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/history") .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" }) .GetAsync() .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult(); return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId)); }); TestBase.WaitLoop(() => { WalletHistoryModel history = $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/history") .SetQueryParams(new { walletName = "mywallet", AccountName = "account 0" }) .GetAsync() .ReceiveJson <WalletHistoryModel>().GetAwaiter().GetResult(); return(history.AccountsHistoryModel.First().TransactionsHistory.Any(h => h.Id == txId)); }); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcReceivingNode = receivingNode.CreateRPCClient(); RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 1.00000000); resultSendingWallet.Fee.Should().Be((decimal) - 0.0001); resultSendingWallet.Confirmations.Should().Be(0); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().BeNull(); resultSendingWallet.BlockIndex.Should().BeNull(); resultSendingWallet.BlockTime.Should().BeNull(); resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultSendingWallet.Details.Count.Should().Be(1); GetTransactionDetailsModel detailsSendingWallet = resultSendingWallet.Details.Single(); detailsSendingWallet.Address.Should().Be(unusedaddresses.Single()); detailsSendingWallet.Amount.Should().Be((decimal) - 1.00000000); detailsSendingWallet.Fee.Should().Be((decimal) - 0.0001); detailsSendingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWallet.OutputIndex.Should().Be(1); // The output at index 0 is the change. GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>(); resultReceivingWallet.Amount.Should().Be((decimal)1.00000000); resultReceivingWallet.Fee.Should().BeNull(); resultReceivingWallet.Confirmations.Should().Be(0); resultReceivingWallet.TransactionId.Should().Be(txId); resultReceivingWallet.BlockHash.Should().BeNull(); resultReceivingWallet.BlockIndex.Should().BeNull(); resultReceivingWallet.BlockTime.Should().BeNull(); resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.Details.Should().ContainSingle(); GetTransactionDetailsModel detailsReceivingWallet = resultReceivingWallet.Details.Single(); detailsReceivingWallet.Address.Should().Be(unusedaddresses.Single()); detailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); detailsReceivingWallet.Fee.Should().BeNull(); detailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); detailsReceivingWallet.OutputIndex.Should().Be(1); } }
public async Task GetTransactionOnTransactionReceivedToMultipleAddressesAsync() { using (NodeBuilder builder = NodeBuilder.Create(this)) { // Arrange. // Create a sending and a receiving node. CoreNode sendingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Miner).Start(); CoreNode receivingNode = builder.CreateStratisPosNode(this.network).WithReadyBlockchainData(ReadyBlockchain.StratisRegTest150Listener).Start(); TestHelper.ConnectAndSync(sendingNode, receivingNode); // Get an address to send to. IEnumerable <string> unusedaddresses = await $"http://localhost:{receivingNode.ApiPort}/api" .AppendPathSegment("wallet/unusedAddresses") .SetQueryParams(new { walletName = "mywallet", accountName = "account 0", count = 2 }) .GetJsonAsync <IEnumerable <string> >(); // Build and send the transaction with an Op_Return. WalletBuildTransactionModel buildTransactionModel = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/build-transaction") .PostJsonAsync(new BuildTransactionRequest { WalletName = "mywallet", AccountName = "account 0", FeeType = "low", Password = "******", ShuffleOutputs = false, AllowUnconfirmed = true, Recipients = unusedaddresses.Select(address => new RecipientModel { DestinationAddress = address, Amount = "1" }).ToList() }) .ReceiveJson <WalletBuildTransactionModel>(); await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("wallet/send-transaction") .PostJsonAsync(new SendTransactionRequest { Hex = buildTransactionModel.Hex }) .ReceiveJson <WalletSendTransactionModel>(); uint256 txId = buildTransactionModel.TransactionId; // Mine and sync so that we make sure the receiving node is up to date. TestHelper.MineBlocks(sendingNode, 1); TestHelper.WaitForNodeToSync(sendingNode, receivingNode); // Get the block that was mined. string lastBlockHash = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("consensus/getbestblockhash") .GetJsonAsync <string>(); BlockModel tip = await $"http://localhost:{sendingNode.ApiPort}/api" .AppendPathSegment("blockstore/block") .SetQueryParams(new { hash = lastBlockHash, outputJson = true }) .GetJsonAsync <BlockModel>(); Transaction trx = this.network.Consensus.ConsensusFactory.CreateTransaction(buildTransactionModel.Hex); RPCClient rpcReceivingNode = receivingNode.CreateRPCClient(); RPCResponse txReceivingWallet = rpcReceivingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); RPCClient rpcSendingNode = sendingNode.CreateRPCClient(); RPCResponse txSendingWallet = rpcSendingNode.SendCommand(RPCOperations.gettransaction, txId.ToString()); // Assert. GetTransactionModel resultSendingWallet = txSendingWallet.Result.ToObject <GetTransactionModel>(); resultSendingWallet.Amount.Should().Be((decimal) - 2.00000000); //resultSendingWallet.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. resultSendingWallet.Confirmations.Should().Be(1); resultSendingWallet.Isgenerated.Should().BeNull(); resultSendingWallet.TransactionId.Should().Be(txId); resultSendingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash)); resultSendingWallet.BlockIndex.Should().Be(1); resultSendingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds()); resultSendingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultSendingWallet.TransactionTime.Should().Be(trx.Time); resultSendingWallet.Details.Count.Should().Be(2); GetTransactionDetailsModel detailsSendingWalletFirstRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.First()); detailsSendingWalletFirstRecipient.Address.Should().Be(unusedaddresses.First()); detailsSendingWalletFirstRecipient.Amount.Should().Be((decimal) - 1.00000000); //detailsSendingWalletFirstRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. detailsSendingWalletFirstRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWalletFirstRecipient.OutputIndex.Should().Be(1); // Output at index 0 contains the change. GetTransactionDetailsModel detailsSendingWalletSecondRecipient = resultSendingWallet.Details.Single(d => d.Address == unusedaddresses.Last()); detailsSendingWalletSecondRecipient.Address.Should().Be(unusedaddresses.Last()); detailsSendingWalletSecondRecipient.Amount.Should().Be((decimal) - 1.00000000); //detailsSendingWalletSecondRecipient.Fee.Should().Be(new Money(-100000000)); // TODO Uncomment when is available. detailsSendingWalletSecondRecipient.Category.Should().Be(GetTransactionDetailsCategoryModel.Send); detailsSendingWalletSecondRecipient.OutputIndex.Should().Be(2); // Checking receiver. GetTransactionModel resultReceivingWallet = txReceivingWallet.Result.ToObject <GetTransactionModel>(); resultReceivingWallet.Amount.Should().Be((decimal)2.00000000); resultReceivingWallet.Fee.Should().BeNull(); resultReceivingWallet.Confirmations.Should().Be(1); resultReceivingWallet.Isgenerated.Should().BeNull(); resultReceivingWallet.TransactionId.Should().Be(txId); resultReceivingWallet.BlockHash.Should().Be(uint256.Parse(tip.Hash)); resultReceivingWallet.BlockIndex.Should().Be(1); resultReceivingWallet.BlockTime.Should().Be(tip.Time.ToUnixTimeSeconds()); resultReceivingWallet.TimeReceived.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.TransactionTime.Should().BeGreaterThan((DateTimeOffset.Now - TimeSpan.FromMinutes(1)).ToUnixTimeSeconds()); resultReceivingWallet.Details.Count.Should().Be(2); GetTransactionDetailsModel firstDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.First()); firstDetailsReceivingWallet.Address.Should().Be(unusedaddresses.First()); firstDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); firstDetailsReceivingWallet.Fee.Should().BeNull(); firstDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); firstDetailsReceivingWallet.OutputIndex.Should().Be(1); // Output at index 0 contains the change. GetTransactionDetailsModel secondDetailsReceivingWallet = resultReceivingWallet.Details.Single(d => d.Address == unusedaddresses.Last()); secondDetailsReceivingWallet.Address.Should().Be(unusedaddresses.Last()); secondDetailsReceivingWallet.Amount.Should().Be((decimal)1.00000000); secondDetailsReceivingWallet.Fee.Should().BeNull(); secondDetailsReceivingWallet.Category.Should().Be(GetTransactionDetailsCategoryModel.Receive); secondDetailsReceivingWallet.OutputIndex.Should().Be(2); } }
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); }