コード例 #1
0
        /// <inheritdoc />
        public Mnemonic CreateWallet(string password, string name, string passphrase = null, string mnemonicList = null)
        {
            Guard.NotEmpty(password, nameof(password));
            Guard.NotEmpty(name, nameof(name));

            // for now the passphrase is set to be the password by default.
            if (passphrase == null)
            {
                passphrase = password;
            }

            // generate the root seed used to generate keys from a mnemonic picked at random
            // and a passphrase optionally provided by the user
            Mnemonic mnemonic = string.IsNullOrEmpty(mnemonicList)
                ? new Mnemonic(Wordlist.English, WordCount.Twelve)
                : new Mnemonic(mnemonicList);
            ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase);

            // create a wallet file
            Wallet wallet = this.GenerateWalletFile(password, name, extendedKey);

            // generate multiple accounts and addresses from the get-go
            for (int i = 0; i < WalletCreationAccountsCount; i++)
            {
                HdAccount account = wallet.AddNewAccount(password, this.coinType);
                account.CreateAddresses(this.network, UnusedAddressesBuffer);
                account.CreateAddresses(this.network, UnusedAddressesBuffer, true);
            }

            // update the height of the we start syncing from
            this.UpdateLastBlockSyncedHeight(wallet, this.chain.Tip);

            // save the changes to the file and add addresses to be tracked
            this.SaveToFile(wallet);
            this.Load(wallet);
            this.LoadKeysLookup();

            return(mnemonic);
        }
コード例 #2
0
        /// <inheritdoc />
        public HdAccount CreateNewAccount(Wallet wallet, string password)
        {
            Guard.NotNull(wallet, nameof(wallet));
            Guard.NotEmpty(password, nameof(password));

            // get the accounts for this type of coin
            var accounts = wallet.AccountsRoot.Single(a => a.CoinType == this.coinType).Accounts.ToList();

            int newAccountIndex = 0;

            if (accounts.Any())
            {
                newAccountIndex = accounts.Max(a => a.Index) + 1;
            }

            // get the extended pub key used to generate addresses for this account
            var       privateKey       = Key.Parse(wallet.EncryptedSeed, password, wallet.Network);
            var       seedExtKey       = new ExtKey(privateKey, wallet.ChainCode);
            var       accountHdPath    = $"m/44'/{(int)this.coinType}'/{newAccountIndex}'";
            KeyPath   keyPath          = new KeyPath(accountHdPath);
            ExtKey    accountExtKey    = seedExtKey.Derive(keyPath);
            ExtPubKey accountExtPubKey = accountExtKey.Neuter();

            var newAccount = new HdAccount
            {
                Index             = newAccountIndex,
                ExtendedPubKey    = accountExtPubKey.ToString(wallet.Network),
                ExternalAddresses = new List <HdAddress>(),
                InternalAddresses = new List <HdAddress>(),
                Name         = $"account {newAccountIndex}",
                HdPath       = accountHdPath,
                CreationTime = DateTimeOffset.Now
            };

            accounts.Add(newAccount);
            wallet.AccountsRoot.Single(a => a.CoinType == this.coinType).Accounts = accounts;

            return(newAccount);
        }
コード例 #3
0
        /// <summary>
        /// Adds an account to the current account root using encrypted seed and password.
        /// </summary>
        /// <remarks>The name given to the account is of the form "account (i)" by default, where (i) is an incremental index starting at 0.
        /// According to BIP44, an account at index (i) can only be created when the account at index (i - 1) contains transactions.
        /// <seealso cref="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki"/></remarks>
        /// <param name="password">The password used to decrypt the wallet's encrypted seed.</param>
        /// <param name="encryptedSeed">The encrypted private key for this wallet.</param>
        /// <param name="chainCode">The chain code for this wallet.</param>
        /// <param name="network">The network for which this account will be created.</param>
        /// <param name="accountCreationTime">Creation time of the account to be created.</param>
        /// <returns>A new HD account.</returns>
        public HdAccount AddNewAccount(string password, string encryptedSeed, byte[] chainCode, Network network, DateTimeOffset accountCreationTime)
        {
            Guard.NotEmpty(password, nameof(password));
            Guard.NotEmpty(encryptedSeed, nameof(encryptedSeed));
            Guard.NotNull(chainCode, nameof(chainCode));

            int newAccountIndex = 0;
            ICollection <HdAccount> hdAccounts = this.Accounts.ToList();

            if (hdAccounts.Any())
            {
                // Hide account indexes used for cold staking from the "Max" calculation.
                newAccountIndex = hdAccounts.Where(Wallet.NormalAccounts).Max(a => a.Index) + 1;
            }

            HdAccount newAccount = this.CreateAccount(password, encryptedSeed, chainCode, network, accountCreationTime, newAccountIndex);

            hdAccounts.Add(newAccount);
            this.Accounts = hdAccounts;

            return(newAccount);
        }
コード例 #4
0
        /// <summary>
        /// Gets the first account from the "default" wallet if it specified, otherwise returns the first available account in the existing wallets.
        /// </summary>
        /// <returns>Reference to the default wallet account, or the first available if no default wallet is specified.</returns>
        private WalletAccountReference GetAccount()
        {
            string walletName;

            if (this.walletSettings.IsDefaultWalletEnabled())
            {
                walletName = this.walletManager.GetWalletsNames().FirstOrDefault(w => w == this.walletSettings.DefaultWalletName);
            }
            else
            {
                //TODO: Support multi wallet like core by mapping passed RPC credentials to a wallet/account
                walletName = this.walletManager.GetWalletsNames().FirstOrDefault();
            }

            if (walletName == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "No wallet found");
            }

            HdAccount account = this.walletManager.GetAccounts(walletName).FirstOrDefault();

            return(new WalletAccountReference(walletName, account.Name));
        }
コード例 #5
0
        /// <inheritdoc />
        public Wallet RecoverWallet(string password, string name, string mnemonic, DateTime creationTime, string passphrase = null)
        {
            Guard.NotEmpty(password, nameof(password));
            Guard.NotEmpty(name, nameof(name));
            Guard.NotEmpty(mnemonic, nameof(mnemonic));

            // for now the passphrase is set to be the password by default.
            if (passphrase == null)
            {
                passphrase = password;
            }

            // generate the root seed used to generate keys
            ExtKey extendedKey = HdOperations.GetHdPrivateKey(mnemonic, passphrase);

            // create a wallet file
            Wallet wallet = this.GenerateWalletFile(password, name, extendedKey, creationTime);

            // generate multiple accounts and addresses from the get-go
            for (int i = 0; i < WalletRecoveryAccountsCount; i++)
            {
                HdAccount account = wallet.AddNewAccount(password, this.coinType);
                account.CreateAddresses(this.network, UnusedAddressesBuffer);
                account.CreateAddresses(this.network, UnusedAddressesBuffer, true);
            }

            int blockSyncStart = this.chain.GetHeightAtTime(creationTime);

            this.UpdateLastBlockSyncedHeight(wallet, this.chain.GetBlock(blockSyncStart));

            // save the changes to the file and add addresses to be tracked
            this.SaveToFile(wallet);
            this.Load(wallet);
            this.LoadKeysLookup();

            return(wallet);
        }
コード例 #6
0
        /// <summary>
        /// Adds an account to the current account root.
        /// </summary>
        /// <remarks>The name given to the account is of the form "account (i)" by default, where (i) is an incremental index starting at 0.
        /// According to BIP44, an account at index (i) can only be created when the account at index (i - 1) contains transactions.
        /// <seealso cref="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki"/></remarks>
        /// <param name="password">The password used to decrypt the wallet's encrypted seed.</param>
        /// <param name="encryptedSeed">The encrypted private key for this wallet.</param>
        /// <param name="chainCode">The chain code for this wallet.</param>
        /// <param name="network">The network for which this account will be created.</param>
        /// <returns>A new HD account.</returns>
        public HdAccount AddNewAccount(string password, string encryptedSeed, byte[] chainCode, Network network)
        {
            Guard.NotEmpty(password, nameof(password));
            Guard.NotEmpty(encryptedSeed, nameof(encryptedSeed));
            Guard.NotNull(chainCode, nameof(chainCode));

            // Get the current collection of accounts.
            var accounts = this.Accounts.ToList();

            int newAccountIndex = 0;

            if (accounts.Any())
            {
                newAccountIndex = accounts.Max(a => a.Index) + 1;
            }

            // Get the extended pub key used to generate addresses for this account.
            string    accountHdPath    = HdOperations.GetAccountHdPath((int)this.CoinType, newAccountIndex);
            Key       privateKey       = HdOperations.DecryptSeed(encryptedSeed, password, network);
            ExtPubKey accountExtPubKey = HdOperations.GetExtendedPublicKey(privateKey, chainCode, accountHdPath);

            var newAccount = new HdAccount
            {
                Index             = newAccountIndex,
                ExtendedPubKey    = accountExtPubKey.ToString(network),
                ExternalAddresses = new List <HdAddress>(),
                InternalAddresses = new List <HdAddress>(),
                Name         = $"account {newAccountIndex}",
                HdPath       = accountHdPath,
                CreationTime = DateTimeOffset.Now
            };

            accounts.Add(newAccount);
            this.Accounts = accounts;

            return(newAccount);
        }
コード例 #7
0
        /// <summary>
        /// Gets the first account from the "default" wallet if it specified,
        /// otherwise returns the first available account in the existing wallets.
        /// </summary>
        /// <returns>Reference to the default wallet account, or the first available if no default wallet is specified.</returns>
        private WalletAccountReference GetWalletAccountReference()
        {
            string walletName = null;

            if (string.IsNullOrWhiteSpace(WalletRPCController.CurrentWalletName))
            {
                if (this.walletSettings.IsDefaultWalletEnabled())
                {
                    walletName = this.walletManager.GetWalletsNames().FirstOrDefault(w => w == this.walletSettings.DefaultWalletName);
                }
                else
                {
                    // TODO: Support multi wallet like core by mapping passed RPC credentials to a wallet/account
                    walletName = this.walletManager.GetWalletsNames().FirstOrDefault();
                }
            }
            else
            {
                // Read the wallet name from the class instance.
                walletName = WalletRPCController.CurrentWalletName;
            }

            if (walletName == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "No wallet found");
            }

            HdAccount account = this.walletManager.GetAccounts(walletName).FirstOrDefault();

            if (account == null)
            {
                throw new RPCServerException(RPCErrorCode.RPC_INVALID_REQUEST, "Account not found");
            }

            return(new WalletAccountReference(walletName, account.Name));
        }
コード例 #8
0
        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);
        }
コード例 #9
0
        public GetTransactionModel GetTransaction(string txid)
        {
            if (!uint256.TryParse(txid, out uint256 trxid))
            {
                throw new ArgumentException(nameof(txid));
            }

            WalletAccountReference accountReference = this.GetWalletAccountReference();
            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, 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)
            {
                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
            }

            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())
            {
                Wallet wallet = this.walletManager.GetWallet(accountReference.WalletName);
                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 (TransactionData trxInWallet in receivedTransactions)
            {
                // Skip the details if the script pub key is cold staking.
                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     = addresses.First(a => a.Transactions.Contains(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);
        }
コード例 #10
0
        /// <inheritdoc />
        public (string hex, uint256 transactionId, Money fee) BuildTransaction(WalletAccountReference accountReference, string password, Script destinationScript, Money amount, FeeType feeType, int minConfirmations)
        {
            if (amount == Money.Zero)
            {
                throw new WalletException($"Cannot send transaction with 0 {this.coinType}");
            }

            // get the wallet and the account
            Wallet    wallet  = this.GetWalletByName(accountReference.WalletName);
            HdAccount account = this.GetAccounts(wallet).GetAccountByName(accountReference.AccountName);

            // get a list of transactions outputs that have not been spent
            var spendableTransactions = account.GetSpendableTransactions().ToList();

            // remove whats under min confirmations
            var currentHeight = this.chain.Height;

            spendableTransactions = spendableTransactions.Where(s => currentHeight - s.BlockHeight >= minConfirmations).ToList();

            // get total spendable balance in the account.
            var balance = spendableTransactions.Sum(t => t.Amount);

            // make sure we have enough funds
            if (balance < amount)
            {
                throw new WalletException("Not enough funds.");
            }

            // calculate which addresses needs to be used as well as the fee to be charged
            var calculationResult = this.CalculateFees(spendableTransactions, amount, feeType.ToConfirmations());

            // get extended private key
            var privateKey = Key.Parse(wallet.EncryptedSeed, password, wallet.Network);
            var seedExtKey = new ExtKey(privateKey, wallet.ChainCode);

            var signingKeys = new HashSet <ISecret>();
            var coins       = new List <Coin>();

            foreach (var transactionToUse in calculationResult.transactionsToUse)
            {
                var           address           = account.FindAddressesForTransaction(t => t.Id == transactionToUse.Id && t.Index == transactionToUse.Index && t.Amount > 0).Single();
                ExtKey        addressExtKey     = seedExtKey.Derive(new KeyPath(address.HdPath));
                BitcoinExtKey addressPrivateKey = addressExtKey.GetWif(wallet.Network);
                signingKeys.Add(addressPrivateKey);

                coins.Add(new Coin(transactionToUse.Id, (uint)transactionToUse.Index, transactionToUse.Amount, transactionToUse.ScriptPubKey));
            }

            // get address to send the change to
            var changeAddress = account.GetFirstUnusedChangeAddress();

            // build transaction
            var         builder = new TransactionBuilder();
            Transaction tx      = builder
                                  .AddCoins(coins)
                                  .AddKeys(signingKeys.ToArray())
                                  .Send(destinationScript, amount)
                                  .SetChange(changeAddress.ScriptPubKey)
                                  .SendFees(calculationResult.fee)
                                  .BuildTransaction(true);

            if (!builder.Verify(tx))
            {
                throw new WalletException("Could not build transaction, please make sure you entered the correct data.");
            }

            return(tx.ToHex(), tx.GetHash(), calculationResult.fee);
        }
コード例 #11
0
 public AddressCollection(HdAccount account, int addressType)
     : this(account, addressType, new List <HdAddress>())
 {
 }
コード例 #12
0
        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);
        }