/// <inheritdoc /> public HdAccount CreateNewAccount(Wallet wallet, CoinType coinType, string password) { // get the accounts for this type of coin var accounts = wallet.AccountsRoot.Single(a => a.CoinType == 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)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 == coinType).Accounts = accounts; return(newAccount); }
/// <inheritdoc /> public Mnemonic CreateWallet(string password, string folderPath, string name, string network, string passphrase = null) { // 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 = new Mnemonic(Wordlist.English, WordCount.Twelve); ExtKey extendedKey = mnemonic.DeriveExtKey(passphrase); Network coinNetwork = WalletHelpers.GetNetwork(network); // create a wallet file Wallet wallet = this.GenerateWalletFile(password, folderPath, name, coinNetwork, extendedKey); // generate multiple accounts and addresses from the get-go for (int i = 0; i < WalletCreationAccountsCount; i++) { HdAccount account = CreateNewAccount(wallet, this.coinType, password); this.CreateAddressesInAccount(account, coinNetwork, UnusedAddressesBuffer); this.CreateAddressesInAccount(account, coinNetwork, UnusedAddressesBuffer, true); } // update the height of the we start syncing from this.UpdateLastBlockSyncedHeight(wallet, this.chain.Tip.Height); // save the changes to the file and add addresses to be tracked this.SaveToFile(wallet); this.Load(wallet); this.LoadKeysLookup(); return(mnemonic); }
/// <inheritdoc /> public (string hex, uint256 transactionId, Money fee) BuildTransaction(string walletName, string accountName, string password, string destinationAddress, Money amount, string 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(walletName); HdAccount account = wallet.AccountsRoot.Single(a => a.CoinType == this.coinType).GetAccountByName(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); // 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(); // get script destination address Script destinationScript = PayToPubkeyHashTemplate.Instance.GenerateScriptPubKey(new BitcoinPubKeyAddress(destinationAddress, wallet.Network)); // 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); }