Пример #1
0
        private static void Sign()
        {
            foreach (string target in Targets)
            {
                if (target.StartsWith("win", StringComparison.OrdinalIgnoreCase))
                {
                    string publishedFolder = Path.Combine(BinDistDirectory, target);

                    Console.WriteLine("Move created .msi");
                    var msiPath = Path.Combine(WixProjectDirectory, "bin", "Release", "Wasabi.msi");
                    if (!File.Exists(msiPath))
                    {
                        throw new Exception(".msi does not exist. Expected path: Wasabi.msi.");
                    }
                    var msiFileName = Path.GetFileNameWithoutExtension(msiPath);
                    var newMsiPath  = Path.Combine(BinDistDirectory, $"{msiFileName}-{VersionPrefix}.msi");
                    File.Copy(msiPath, newMsiPath);

                    Console.Write("Enter Code Signing Certificate Password: "******"cmd", BinDistDirectory, $"signtool sign /d \"Wasabi Wallet\" /f \"{PfxPath}\" /p {pfxPassword} /t http://timestamp.digicert.com /a \"{newMsiPath}\" && exit");

                    IoHelpers.TryDeleteDirectoryAsync(publishedFolder).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {publishedFolder}");
                }
                else if (target.StartsWith("osx", StringComparison.OrdinalIgnoreCase))
                {
                    string dmgFilePath = Path.Combine(BinDistDirectory, $"Wasabi-{VersionPrefix}.dmg");
                    if (!File.Exists(dmgFilePath))
                    {
                        throw new Exception(".dmg does not exist.");
                    }
                    string zipFilePath = Path.Combine(BinDistDirectory, $"Wasabi-osx-{VersionPrefix}.zip");
                    if (File.Exists(zipFilePath))
                    {
                        File.Delete(zipFilePath);
                    }
                }
            }

            Console.WriteLine("Signing final files...");
            var finalFiles = Directory.GetFiles(BinDistDirectory);

            foreach (var finalFile in finalFiles)
            {
                StartProcessAndWaitForExit("cmd", BinDistDirectory, $"gpg --armor --detach-sign {finalFile} && exit");

                StartProcessAndWaitForExit("cmd", WixProjectDirectory, $"git checkout -- ComponentsGenerated.wxs && exit");
            }

            IoHelpers.OpenFolderInFileExplorer(BinDistDirectory);
        }
Пример #2
0
        internal async Task RunAsync(string walletName, string destinationWalletName, bool keepMixAlive)
        {
            try
            {
                Logger.LogSoftwareStarted("Wasabi Daemon");

                KeyManager keyManager = Global.WalletManager.GetWalletByName(walletName).KeyManager;

                string password = null;
                var    count    = 3;
                string compatibilityPassword = null;
                do
                {
                    if (password is { })
                    {
                        if (count > 0)
                        {
                            Logger.LogError($"Wrong password. {count} attempts left. Try again.");
                        }
                        else
                        {
                            Logger.LogCritical($"Wrong password. {count} attempts left. Exiting...");
                            return;
                        }
                        count--;
                    }
                    Console.Write("Password: ");

                    password = PasswordConsole.ReadPassword();
                    if (PasswordHelper.IsTooLong(password, out password))
                    {
                        Console.WriteLine(PasswordHelper.PasswordTooLongMessage);
                    }
                    if (PasswordHelper.IsTrimable(password, out password))
                    {
                        Console.WriteLine(PasswordHelper.TrimWarnMessage);
                    }
                }while (!PasswordHelper.TryPassword(keyManager, password, out compatibilityPassword));

                if (compatibilityPassword is { })
Пример #3
0
        private static void Sign()
        {
            foreach (string target in Targets)
            {
                if (target.StartsWith("win", StringComparison.OrdinalIgnoreCase))
                {
                    string publishedFolder = Path.Combine(BinDistDirectory, target);

                    Console.WriteLine("Move created .msi");
                    var msiPath = Path.Combine(WixProjectDirectory, @"bin\Release\Wasabi.msi");
                    if (!File.Exists(msiPath))
                    {
                        throw new Exception(".msi does not exist. Expected path: Wasabi.msi.");
                    }
                    var msiFileName = Path.GetFileNameWithoutExtension(msiPath);
                    var newMsiPath  = Path.Combine(BinDistDirectory, $"{msiFileName}-{VersionPrefix}.msi");
                    File.Move(msiPath, newMsiPath);

                    Console.Write("Enter Code Signing Certificate Password: "******"cmd",
                        RedirectStandardInput = true,
                        WorkingDirectory = BinDistDirectory
                    }))
                    {
                        process.StandardInput.WriteLine($"signtool sign /d \"Wasabi Wallet\" /f \"{PfxPath}\" /p {pfxPassword} /t http://timestamp.digicert.com /a \"{newMsiPath}\" && exit");
                        process.WaitForExit();
                    }

                    IoHelpers.DeleteRecursivelyWithMagicDustAsync(publishedFolder).GetAwaiter().GetResult();
                    Console.WriteLine($"Deleted {publishedFolder}");
                }
            }

            Console.WriteLine("Signing final files...");
            var finalFiles = Directory.GetFiles(BinDistDirectory);

            foreach (var finalFile in finalFiles)
            {
                using (var process = Process.Start(new ProcessStartInfo
                {
                    FileName = "cmd",
                    RedirectStandardInput = true,
                    WorkingDirectory = BinDistDirectory
                }))
                {
                    process.StandardInput.WriteLine($"gpg --armor --detach-sign {finalFile} && exit");
                    process.WaitForExit();
                }

                using (var process = Process.Start(new ProcessStartInfo
                {
                    FileName = "cmd",
                    RedirectStandardInput = true,
                    WorkingDirectory = WixProjectDirectory
                }))
                {
                    process.StandardInput.WriteLine($"git checkout -- ComponentsGenerated.wxs && exit");
                    process.WaitForExit();
                }
            }

            IoHelpers.OpenFolderInFileExplorer(BinDistDirectory);
        }
Пример #4
0
        internal async Task RunAsync(string walletName, bool mixAll, bool keepMixAlive)
        {
            try
            {
                Logger.LogSoftwareStarted("Wasabi Daemon");

                KeyManager keyManager = TryGetKeyManagerFromWalletName(walletName);
                if (keyManager is null)
                {
                    return;
                }

                string password = null;
                var    count    = 3;
                string compatibilityPassword = null;
                do
                {
                    if (password != null)
                    {
                        if (count > 0)
                        {
                            Logger.LogError($"Wrong password. {count} attempts left. Try again.");
                        }
                        else
                        {
                            Logger.LogCritical($"Wrong password. {count} attempts left. Exiting...");
                            return;
                        }
                        count--;
                    }
                    Console.Write("Password: "******"Correct password.");

                await Global.InitializeNoWalletAsync();

                if (Global.KillRequested)
                {
                    return;
                }

                await Global.InitializeWalletServiceAsync(keyManager);

                if (Global.KillRequested)
                {
                    return;
                }

                await TryQueueCoinsToMixAsync(mixAll, password);

                bool mixing;
                do
                {
                    if (Global.KillRequested)
                    {
                        break;
                    }

                    await Task.Delay(3000);

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    bool anyCoinsQueued = Global.ChaumianClient.State.AnyCoinsQueued();
                    if (!anyCoinsQueued && keepMixAlive)                     // If no coins queued and mixing is asked to be kept alive then try to queue coins.
                    {
                        await TryQueueCoinsToMixAsync(mixAll, password);
                    }

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    mixing = anyCoinsQueued || keepMixAlive;
                } while (mixing);

                if (!Global.KillRequested)                 // This only has to run if it finishes by itself. Otherwise the Ctrl+c runs it.
                {
                    await Global.ChaumianClient?.DequeueAllCoinsFromMixAsync("Stopping Wasabi.");
                }
            }
            catch
            {
                if (!Global.KillRequested)
                {
                    throw;
                }
            }
            finally
            {
                Logger.LogInfo($"{nameof(Daemon)} stopped.");
            }
        }
Пример #5
0
        internal async Task RunAsync(string walletName, string destinationWalletName, bool keepMixAlive)
        {
            try
            {
                Logger.LogSoftwareStarted("Wasabi Daemon");

                KeyManager keyManager = Global.WalletManager.GetWalletByName(walletName).KeyManager;

                string password = null;
                var    count    = 3;
                string compatibilityPassword = null;
                do
                {
                    if (password != null)
                    {
                        if (count > 0)
                        {
                            Logger.LogError($"Wrong password. {count} attempts left. Try again.");
                        }
                        else
                        {
                            Logger.LogCritical($"Wrong password. {count} attempts left. Exiting...");
                            return;
                        }
                        count--;
                    }
                    Console.Write("Password: "******"Correct password.");

                await Global.InitializeNoWalletAsync();

                if (Global.KillRequested)
                {
                    return;
                }

                Wallet = await Global.WalletManager.StartWalletAsync(keyManager);

                if (Global.KillRequested)
                {
                    return;
                }

                KeyManager destinationKeyManager           = Global.WalletManager.GetWalletByName(destinationWalletName).KeyManager;
                bool       isDifferentDestinationSpecified = keyManager.ExtPubKey != destinationKeyManager.ExtPubKey;
                if (isDifferentDestinationSpecified)
                {
                    await Global.WalletManager.StartWalletAsync(destinationKeyManager);
                }

                do
                {
                    if (Global.KillRequested)
                    {
                        break;
                    }

                    // If no coins enqueued then try to enqueue the large anonset coins and mix to another wallet.
                    if (isDifferentDestinationSpecified && !AnyCoinsQueued())
                    {
                        Wallet.ChaumianClient.DestinationKeyManager = destinationKeyManager;
                        await TryQueueCoinsToMixAsync(password, minAnonset : Wallet.ServiceConfiguration.GetMixUntilAnonymitySetValue());
                    }

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    // If no coins were enqueued then try to enqueue coins those have less anonset and mix into the same wallet.
                    if (!AnyCoinsQueued())
                    {
                        Wallet.ChaumianClient.DestinationKeyManager = Wallet.ChaumianClient.KeyManager;
                        await TryQueueCoinsToMixAsync(password, maxAnonset : Wallet.ServiceConfiguration.GetMixUntilAnonymitySetValue() - 1);
                    }

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    await Task.Delay(3000);
                }
                // Keep this loop alive as long as a coin is enqueued or keepalive was specified.
                while (keepMixAlive || AnyCoinsQueued());

                await Global.DisposeAsync();
            }
            catch
            {
                if (!Global.KillRequested)
                {
                    throw;
                }
            }
            finally
            {
                Logger.LogInfo($"{nameof(Daemon)} stopped.");
            }
        }
Пример #6
0
        internal static async Task RunAsync(string walletName, bool mixAll, bool keepMixAlive)
        {
            try
            {
                Logger.LogStarting("Wasabi Daemon");

                KeyManager keyManager = TryGetKeymanagerFromWalletName(walletName);
                if (keyManager is null)
                {
                    return;
                }

                string password = null;
                var    count    = 3;
                do
                {
                    if (password != null)
                    {
                        if (count > 0)
                        {
                            Logger.LogError($"Wrong password. {count} attempts left. Try again.");
                        }
                        else
                        {
                            Logger.LogCritical($"Wrong password. {count} attempts left. Exiting...");
                            return;
                        }
                        count--;
                    }
                    Console.Write("Password: "******"Correct password.");

                await Global.InitializeNoWalletAsync();

                await Global.InitializeWalletServiceAsync(keyManager);

                await TryQueueCoinsToMixAsync(mixAll, password);

                bool mixing;
                do
                {
                    if (Global.KillRequested)
                    {
                        break;
                    }

                    await Task.Delay(3000);

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    bool anyCoinsQueued = Global.ChaumianClient.State.AnyCoinsQueued();

                    if (!anyCoinsQueued && keepMixAlive)                     // If no coins queued and mixing is asked to be kept alive then try to queue coins.
                    {
                        await TryQueueCoinsToMixAsync(mixAll, password);
                    }

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    mixing = anyCoinsQueued || keepMixAlive;
                } while (mixing);

                await Global.ChaumianClient.DequeueAllCoinsFromMixAsync();
            }
            finally
            {
                Logger.LogInfo($"Wasabi Daemon stopped gracefully.", Logger.InstanceGuid.ToString());
            }
        }
Пример #7
0
        public static void Main(string[] args)
        {
            //args = new string[] { "help" };
            //args = new string[] { "generate-wallet" };
            //args = new string[] { "generate-wallet", "wallet-file=test2.json" };
            ////math super cool donate beach mobile sunny web board kingdom bacon crisp
            ////no password
            //args = new string[] { "recover-wallet", "wallet-file=test5.json" };
            //args = new string[] { "show-balances", "wallet-file=test5.json" };
            //args = new string[] { "receive", "wallet-file=test4.json" };
            //args = new string[] { "show-history", "wallet-file=test.json" };
            //args = new string[] { "send", "btc=0.001", "address=mq6fK8fkFyCy9p53m4Gf4fiX2XCHvcwgi1", "wallet-file=test.json" };
            //args = new string[] { "send", "btc=all", "address=mzz63n3n89KVeHQXRqJEVsQX8MZj5zeqCw", "wallet-file=test4.json" };

            // Load config file
            // It also creates it with default settings if doesn't exist
            Config.Load();

            if (args.Length == 0)
            {
                DisplayHelp();
                Exit(color: ConsoleColor.Green);
            }
            var command = args[0];

            if (!Commands.Contains(command))
            {
                WriteLine("Wrong command is specified.");
                DisplayHelp();
            }
            foreach (var arg in args.Skip(1))
            {
                if (!arg.Contains('='))
                {
                    Exit($"Wrong argument format specified: {arg}");
                }
            }

            #region HelpCommand
            if (command == "help")
            {
                AssertArgumentsLenght(args.Length, 1, 1);
                DisplayHelp();
            }
            #endregion
            #region GenerateWalletCommand
            if (command == "generate-wallet")
            {
                AssertArgumentsLenght(args.Length, 1, 2);
                var walletFilePath = GetWalletFilePath(args);
                AssertWalletNotExists(walletFilePath);

                string pw;
                string pwConf;
                do
                {
                    // 1. Get password from user
                    WriteLine("Choose a password:"******"Confirm password:"******"Passwords do not match. Try again!");
                    }
                } while (pw != pwConf);

                // 3. Create wallet
                Mnemonic mnemonic;
                Safe     safe = Safe.Create(out mnemonic, pw, walletFilePath, Config.Network);
                // If no exception thrown the wallet is successfully created.
                WriteLine();
                WriteLine("Wallet is successfully created.");
                WriteLine($"Wallet file: {walletFilePath}");

                // 4. Display mnemonic
                WriteLine();
                WriteLine("Write down the following mnemonic words.");
                WriteLine("With the mnemonic words AND your password you can recover this wallet by using the recover-wallet command.");
                WriteLine();
                WriteLine("-------");
                WriteLine(mnemonic);
                WriteLine("-------");
            }
            #endregion
            #region RecoverWalletCommand
            if (command == "recover-wallet")
            {
                AssertArgumentsLenght(args.Length, 1, 2);
                var walletFilePath = GetWalletFilePath(args);
                AssertWalletNotExists(walletFilePath);

                WriteLine($"Your software is configured using the Bitcoin {Config.Network} network.");
                WriteLine("Provide your mnemonic words, separated by spaces:");
                var mnemonicString = ReadLine();
                AssertCorrectMnemonicFormat(mnemonicString);
                var mnemonic = new Mnemonic(mnemonicString);

                WriteLine("Provide your password. Please note the wallet cannot check if your password is correct or not. If you provide a wrong password a wallet will be recovered with your provided mnemonic AND password pair:");
                var password = PasswordConsole.ReadPassword();

                Safe safe = Safe.Recover(mnemonic, password, walletFilePath, Config.Network);
                // If no exception thrown the wallet is successfully recovered.
                WriteLine();
                WriteLine("Wallet is successfully recovered.");
                WriteLine($"Wallet file: {walletFilePath}");
            }
            #endregion
            #region ShowBalancesCommand
            if (command == "show-balances")
            {
                AssertArgumentsLenght(args.Length, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    // 0. Query all operations, grouped by addresses
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = QueryOperationsPerSafeAddresses(safe, 7);

                    // 1. Get all address history record with a wrapper class
                    var addressHistoryRecords = new List <AddressHistoryRecord>();
                    foreach (var elem in operationsPerAddresses)
                    {
                        foreach (var op in elem.Value)
                        {
                            addressHistoryRecords.Add(new AddressHistoryRecord(elem.Key, op));
                        }
                    }

                    // 2. Calculate wallet balances
                    Money confirmedWalletBalance;
                    Money unconfirmedWalletBalance;
                    GetBalances(addressHistoryRecords, out confirmedWalletBalance, out unconfirmedWalletBalance);

                    // 3. Group all address history records by addresses
                    var addressHistoryRecordsPerAddresses = new Dictionary <BitcoinAddress, HashSet <AddressHistoryRecord> >();
                    foreach (var address in operationsPerAddresses.Keys)
                    {
                        var recs = new HashSet <AddressHistoryRecord>();
                        foreach (var record in addressHistoryRecords)
                        {
                            if (record.Address == address)
                            {
                                recs.Add(record);
                            }
                        }
                        addressHistoryRecordsPerAddresses.Add(address, recs);
                    }

                    // 4. Calculate address balances
                    WriteLine();
                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine("Address\t\t\t\t\tConfirmed\tUnconfirmed");
                    WriteLine("---------------------------------------------------------------------------");
                    foreach (var elem in addressHistoryRecordsPerAddresses)
                    {
                        Money confirmedBalance;
                        Money unconfirmedBalance;
                        GetBalances(elem.Value, out confirmedBalance, out unconfirmedBalance);
                        if (confirmedBalance != Money.Zero || unconfirmedBalance != Money.Zero)
                        {
                            WriteLine($"{elem.Key.ToWif()}\t{confirmedBalance.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}\t\t{unconfirmedBalance.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}");
                        }
                    }
                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine($"Confirmed Wallet Balance: {confirmedWalletBalance.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                    WriteLine($"Unconfirmed Wallet Balance: {unconfirmedWalletBalance.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                    WriteLine("---------------------------------------------------------------------------");
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }
            #endregion
            #region ShowHistoryCommand
            if (command == "show-history")
            {
                AssertArgumentsLenght(args.Length, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    // 0. Query all operations, grouped our used safe addresses
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = QueryOperationsPerSafeAddresses(safe);

                    WriteLine();
                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine("Date\t\t\tAmount\t\tConfirmed\tTransaction Id");
                    WriteLine("---------------------------------------------------------------------------");

                    Dictionary <uint256, List <BalanceOperation> > operationsPerTransactions = GetOperationsPerTransactions(operationsPerAddresses);

                    // 3. Create history records from the transactions
                    // History records is arbitrary data we want to show to the user
                    var txHistoryRecords = new List <Tuple <DateTimeOffset, Money, int, uint256> >();
                    foreach (var elem in operationsPerTransactions)
                    {
                        var amount = Money.Zero;
                        foreach (var op in elem.Value)
                        {
                            amount += op.Amount;
                        }
                        var firstOp = elem.Value.First();

                        txHistoryRecords
                        .Add(new Tuple <DateTimeOffset, Money, int, uint256>(
                                 firstOp.FirstSeen,
                                 amount,
                                 firstOp.Confirmations,
                                 elem.Key));
                    }

                    // 4. Order the records by confirmations and time (Simply time does not work, because of a QBitNinja bug)
                    var orderedTxHistoryRecords = txHistoryRecords
                                                  .OrderByDescending(x => x.Item3) // Confirmations
                                                  .ThenBy(x => x.Item1);           // FirstSeen
                    foreach (var record in orderedTxHistoryRecords)
                    {
                        // Item2 is the Amount
                        if (record.Item2 > 0)
                        {
                            ForegroundColor = ConsoleColor.Green;
                        }
                        else if (record.Item2 < 0)
                        {
                            ForegroundColor = ConsoleColor.Red;
                        }
                        WriteLine($"{record.Item1.DateTime}\t{record.Item2}\t{record.Item3 > 0}\t\t{record.Item4}");
                        ResetColor();
                    }
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }
            #endregion
            #region ReceiveCommand
            if (command == "receive")
            {
                AssertArgumentsLenght(args.Length, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerReceiveAddresses = QueryOperationsPerSafeAddresses(safe, 7, HdPathType.Receive);

                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine("Unused Receive Addresses");
                    WriteLine("---------------------------------------------------------------------------");
                    foreach (var elem in operationsPerReceiveAddresses)
                    {
                        if (elem.Value.Count == 0)
                        {
                            WriteLine($"{elem.Key.ToWif()}");
                        }
                    }
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }
            #endregion
            #region SendCommand
            if (command == "send")
            {
                AssertArgumentsLenght(args.Length, 3, 4);
                var            walletFilePath = GetWalletFilePath(args);
                BitcoinAddress addressToSend;
                try
                {
                    addressToSend = BitcoinAddress.Create(GetArgumentValue(args, argName: "address", required: true), Config.Network);
                }
                catch (Exception ex)
                {
                    Exit(ex.ToString());
                    throw ex;
                }
                Safe safe = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = QueryOperationsPerSafeAddresses(safe, 7);

                    // 1. Gather all the not empty private keys
                    WriteLine("Finding not empty private keys...");
                    var operationsPerNotEmptyPrivateKeys = new Dictionary <BitcoinExtKey, List <BalanceOperation> >();
                    foreach (var elem in operationsPerAddresses)
                    {
                        var balance = Money.Zero;
                        foreach (var op in elem.Value)
                        {
                            balance += op.Amount;
                        }
                        if (balance > Money.Zero)
                        {
                            var secret = safe.FindPrivateKey(elem.Key);
                            operationsPerNotEmptyPrivateKeys.Add(secret, elem.Value);
                        }
                    }

                    // 2. Get the script pubkey of the change.
                    WriteLine("Select change address...");
                    Script changeScriptPubKey = null;
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerChangeAddresses = QueryOperationsPerSafeAddresses(safe, minUnusedKeys: 1, hdPathType: HdPathType.Change);
                    foreach (var elem in operationsPerChangeAddresses)
                    {
                        if (elem.Value.Count == 0)
                        {
                            changeScriptPubKey = safe.FindPrivateKey(elem.Key).ScriptPubKey;
                        }
                    }
                    if (changeScriptPubKey == null)
                    {
                        throw new ArgumentNullException();
                    }

                    // 3. Gather coins can be spend
                    WriteLine("Gathering unspent coins...");
                    Dictionary <Coin, bool> unspentCoins = GetUnspentCoins(operationsPerNotEmptyPrivateKeys.Keys);

                    // 4. Get the fee
                    WriteLine("Calculating transaction fee...");
                    Money fee;
                    try
                    {
                        var txSizeInBytes = 250;
                        using (var client = new HttpClient())
                        {
                            const string request = @"https://bitcoinfees.21.co/api/v1/fees/recommended";
                            var          result  = client.GetAsync(request, HttpCompletionOption.ResponseContentRead).Result;
                            var          json    = JObject.Parse(result.Content.ReadAsStringAsync().Result);
                            var          fastestSatoshiPerByteFee = json.Value <decimal>("fastestFee");
                            fee = new Money(fastestSatoshiPerByteFee * txSizeInBytes, MoneyUnit.Satoshi);
                        }
                    }
                    catch
                    {
                        Exit("Couldn't calculate transaction fee, try it again later.");
                        throw new Exception("Can't get tx fee");
                    }
                    WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");

                    // 5. How much money we can spend?
                    Money availableAmount            = Money.Zero;
                    Money unconfirmedAvailableAmount = Money.Zero;
                    foreach (var elem in unspentCoins)
                    {
                        // If can spend unconfirmed add all
                        if (Config.CanSpendUnconfirmed)
                        {
                            availableAmount += elem.Key.Amount;
                            if (!elem.Value)
                            {
                                unconfirmedAvailableAmount += elem.Key.Amount;
                            }
                        }
                        // else only add confirmed ones
                        else
                        {
                            if (elem.Value)
                            {
                                availableAmount += elem.Key.Amount;
                            }
                        }
                    }

                    // 6. How much to spend?
                    Money  amountToSend = null;
                    string amountString = GetArgumentValue(args, argName: "btc", required: true);
                    if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase))
                    {
                        amountToSend  = availableAmount;
                        amountToSend -= fee;
                    }
                    else
                    {
                        amountToSend = ParseBtcString(amountString);
                    }

                    // 7. Do some checks
                    if (amountToSend < Money.Zero || availableAmount < amountToSend + fee)
                    {
                        Exit("Not enough coins.");
                    }

                    decimal feePc = Math.Round((100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC));
                    if (feePc > 1)
                    {
                        WriteLine();
                        WriteLine($"The transaction fee is {feePc.ToString("0.#")}% of your transaction amount.");
                        WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                        WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")}btc");
                        ConsoleKey response = GetYesNoAnswerFromUser();
                        if (response == ConsoleKey.N)
                        {
                            Exit("User interruption.");
                        }
                    }

                    var confirmedAvailableAmount = availableAmount - unconfirmedAvailableAmount;
                    var totalOutAmount           = amountToSend + fee;
                    if (confirmedAvailableAmount < totalOutAmount)
                    {
                        var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount;
                        WriteLine();
                        WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC).ToString("0.#############################")} unconfirmed btc.");
                        ConsoleKey response = GetYesNoAnswerFromUser();
                        if (response == ConsoleKey.N)
                        {
                            Exit("User interruption.");
                        }
                    }

                    // 8. Select coins
                    WriteLine("Selecting coins...");
                    var coinsToSpend            = new HashSet <Coin>();
                    var unspentConfirmedCoins   = new List <Coin>();
                    var unspentUnconfirmedCoins = new List <Coin>();
                    foreach (var elem in unspentCoins)
                    {
                        if (elem.Value)
                        {
                            unspentConfirmedCoins.Add(elem.Key);
                        }
                        else
                        {
                            unspentUnconfirmedCoins.Add(elem.Key);
                        }
                    }

                    bool haveEnough = SelectCoins(ref coinsToSpend, totalOutAmount, unspentConfirmedCoins);
                    if (!haveEnough)
                    {
                        haveEnough = SelectCoins(ref coinsToSpend, totalOutAmount, unspentUnconfirmedCoins);
                    }
                    if (!haveEnough)
                    {
                        throw new Exception("Not enough funds.");
                    }

                    // 9. Get signing keys
                    var signingKeys = new HashSet <ISecret>();
                    foreach (var coin in coinsToSpend)
                    {
                        foreach (var elem in operationsPerNotEmptyPrivateKeys)
                        {
                            if (elem.Key.ScriptPubKey == coin.ScriptPubKey)
                            {
                                signingKeys.Add(elem.Key);
                            }
                        }
                    }

                    // 10. Build the transaction
                    WriteLine("Signing transaction...");
                    var builder = new TransactionBuilder();
                    var tx      = builder
                                  .AddCoins(coinsToSpend)
                                  .AddKeys(signingKeys.ToArray())
                                  .Send(addressToSend, amountToSend)
                                  .SetChange(changeScriptPubKey)
                                  .SendFees(fee)
                                  .BuildTransaction(true);

                    if (!builder.Verify(tx))
                    {
                        Exit("Couldn't build the transaction.");
                    }

                    WriteLine($"Transaction Id: {tx.GetHash()}");

                    var qBitClient = new QBitNinjaClient(Config.Network);

                    // QBit's success response is buggy so let's check manually, too
                    BroadcastResponse broadcastResponse;
                    var success = false;
                    var tried   = 0;
                    var maxTry  = 7;
                    do
                    {
                        tried++;
                        WriteLine($"Try broadcasting transaction... ({tried})");
                        broadcastResponse = qBitClient.Broadcast(tx).Result;
                        var getTxResp = qBitClient.GetTransaction(tx.GetHash()).Result;
                        if (getTxResp == null)
                        {
                            Thread.Sleep(3000);
                            continue;
                        }
                        else
                        {
                            success = true;
                            break;
                        }
                    } while (tried <= maxTry);
                    if (!success)
                    {
                        if (broadcastResponse.Error != null)
                        {
                            WriteLine($"Error code: {broadcastResponse.Error.ErrorCode} Reason: {broadcastResponse.Error.Reason}");
                        }
                        Exit($"The transaction might not have been successfully broadcasted. Please check the Transaction ID in a block explorer.", ConsoleColor.Blue);
                    }
                    Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green);
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }
            #endregion

            Exit(color: ConsoleColor.Green);
        }
        public static async Task <bool> RunAsyncReturnTrueIfContinueWithGuiAsync(string[] args)
        {
            var continueWithGui = true;
            var silent          = false;

            var      showHelp     = false;
            var      showVersion  = false;
            LogLevel?logLevel     = null;
            string   walletName   = null;
            var      doMix        = false;
            var      mixAll       = false;
            var      keepMixAlive = false;

            try
            {
                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
                {
                    Native.AttachParentConsole();
                    Console.WriteLine();
                }

                var options = new OptionSet()
                {
                    { "v|version", "Displays Wasabi version and exit.", x => showVersion = x != null },
                    { "h|help", "Displays help page and exit.", x => showHelp = x != null },
                    { "s|silent", "Do not log to the standard outputs.", x => silent = x != null },
                    { "l|loglevel=", "Sets the level of verbosity for the log TRACE|INFO|WARNING|DEBUG|ERROR.", x => {
                          var normalized = x?.ToLower()?.Trim();
                          if (normalized == "info")
                          {
                              logLevel = LogLevel.Info;
                          }
                          else if (normalized == "warning")
                          {
                              logLevel = LogLevel.Warning;
                          }
                          else if (normalized == "error")
                          {
                              logLevel = LogLevel.Error;
                          }
                          else if (normalized == "trace")
                          {
                              logLevel = LogLevel.Trace;
                          }
                          else if (normalized == "debug")
                          {
                              logLevel = LogLevel.Debug;
                          }
                          else
                          {
                              Console.WriteLine("ERROR: Log level not recognized.");
                              showHelp = true;
                          }
                      } },
                    { "m|mix", "Start mixing without the GUI with the specified wallet.", x => doMix = x != null },
                    { "w|wallet=", "The specified wallet file.", x => {
                          walletName = x?.Trim();
                      } },
                    { "mixall", "Mix once even if the coin reached the target anonymity set specified in the config file.", x => mixAll = x != null },
                    { "keepalive", "Don't exit the software after mixing has been finished, rather keep mixing when new money arrives.", x => keepMixAlive = x != null },
                };
                try
                {
                    var extras = options.Parse(args);
                    if (extras.Count > 0)
                    {
                        showHelp = true;
                    }
                }
                catch (OptionException)
                {
                    continueWithGui = false;
                    Console.WriteLine("Option not recognized.");
                    Console.WriteLine();
                    ShowHelp(options);
                    return(continueWithGui);
                }
                if (showHelp)
                {
                    continueWithGui = false;
                    ShowHelp(options);
                    return(continueWithGui);
                }
                else if (showVersion)
                {
                    continueWithGui = false;
                    ShowVersion();
                    return(continueWithGui);
                }
            }
            finally
            {
                if (silent)
                {
                    Native.DettachParentConsole();
                }
            }

            Logger.InitializeDefaults(Path.Combine(Global.DataDir, "Logs.txt"));

            if (logLevel.HasValue)
            {
                Logger.SetMinimumLevel(logLevel.Value);
            }
            if (silent)
            {
                Logger.Modes.Remove(LogMode.Console);
                Logger.Modes.Remove(LogMode.Debug);
            }
            else
            {
                Logger.Modes.Add(LogMode.Console);
                Logger.Modes.Add(LogMode.Debug);
            }
            Logger.LogStarting("Wasabi");

            KeyManager keyManager = null;

            if (walletName != null)
            {
                continueWithGui = false;

                var walletFullPath       = Global.GetWalletFullPath(walletName);
                var walletBackupFullPath = Global.GetWalletBackupFullPath(walletName);
                if (!File.Exists(walletFullPath) && !File.Exists(walletBackupFullPath))
                {
                    // The selected wallet is not available any more (someone deleted it?).
                    Logger.LogCritical("The selected wallet doesn't exsist, did you delete it?", nameof(Daemon));
                    return(continueWithGui);
                }

                try
                {
                    keyManager = Global.LoadKeyManager(walletFullPath, walletBackupFullPath);
                }
                catch (Exception ex)
                {
                    Logger.LogCritical(ex, nameof(Daemon));
                    return(continueWithGui);
                }
            }

            if (doMix)
            {
                continueWithGui = false;

                if (keyManager is null)
                {
                    Logger.LogCritical("Wallet was not supplied. Add --wallet {WalletName}", nameof(Daemon));
                    return(continueWithGui);
                }

                string password = null;
                var    count    = 3;
                do
                {
                    if (password != null)
                    {
                        if (count > 0)
                        {
                            Logger.LogError($"Wrong password. {count} attempts left. Try again.");
                        }
                        else
                        {
                            Logger.LogCritical($"Wrong password. {count} attempts left. Exiting...");
                            return(continueWithGui);
                        }
                        count--;
                    }
                    Console.Write("Password: "******"Correct password.");

                await Global.InitializeNoWalletAsync();

                await Global.InitializeWalletServiceAsync(keyManager);

                await TryQueueCoinsToMixAsync(mixAll, password);

                var mixing = true;
                do
                {
                    if (Global.KillRequested)
                    {
                        break;
                    }
                    await Task.Delay(3000);

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    bool anyCoinsQueued = Global.ChaumianClient.State.AnyCoinsQueued();

                    if (!anyCoinsQueued && keepMixAlive)                     // If no coins queued and mixing is asked to be kept alive then try to queue coins.
                    {
                        await TryQueueCoinsToMixAsync(mixAll, password);
                    }

                    if (Global.KillRequested)
                    {
                        break;
                    }

                    mixing = anyCoinsQueued || keepMixAlive;
                } while (mixing);

                await Global.ChaumianClient.DequeueAllCoinsFromMixAsync();
            }

            return(continueWithGui);
        }
Пример #9
0
        private static async Task MainAsync(IReadOnlyList <string> args)
        {
            //args = new string[] { "help" };
            //args = new string[] { "generate-wallet" };
            //args = new string[] { "generate-wallet", "wallet-file=test2.json" };
            ////math super cool donate beach mobile sunny web board kingdom bacon crisp
            ////no password
            //args = new string[] { "recover-wallet", "wallet-file=test5.json" };
            //args = new string[] { "show-balances"};
            //args = new string[] { "receive" };
            //args = new string[] { "send","btc=1", "address=mqjVoPiXtLdBdxdqQzWvFSMSBv93swPUUH", "wallet-file=MoliWallet.json" };
            //args = new string[] { "send", "btc=0.1", "address=mkpC5HFC8QHbJbuwajYLDkwPoqcftMU1ga" };
            //args = new string[] { "send", "btc=all", "address=mzz63n3n89KVeHQXRqJEVsQX8MZj5zeqCw", "wallet-file=test4.json" };

            // Load config file
            // It also creates it with default settings if doesn't exist
            Config.Load();

            // Configure QBitNinjaClient
            _qBitClient = new QBitNinjaClient(Config.Network);
            _httpClient = new HttpClient();
            if (Config.UseTor)
            {
                var torHandler = new SocksPortHandler(Config.TorHost, Config.TorSocksPort, ignoreSslCertification: true);                 // ignoreSslCertification needed for linux, until QBit or DotNetTor fixes its issues
                _qBitClient.SetHttpMessageHandler(torHandler);
                _httpClient = new HttpClient(torHandler);
            }

            if (args.Count == 0)
            {
                DisplayHelp();
                Exit(color: ConsoleColor.Green);
            }
            var command = args[0];

            if (!Commands.Contains(command))
            {
                WriteLine("Wrong command is specified.");
                DisplayHelp();
            }
            foreach (var arg in args.Skip(1).Where(arg => !arg.Contains('=')))
            {
                Exit($"Wrong argument format specified: {arg}");
            }

            #region HelpCommand

            if (command == "help")
            {
                AssertArgumentsLenght(args.Count, 1, 1);
                DisplayHelp();
            }

            #endregion HelpCommand

            #region GenerateWalletCommand

            if (command == "generate-wallet")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var walletFilePath = GetWalletFilePath(args);
                AssertWalletNotExists(walletFilePath);

                string pw;
                string pwConf;
                do
                {
                    // 1. Get password from user
                    WriteLine("Choose a password:"******"Confirm password:"******"Passwords do not match. Try again!");
                    }
                } while (pw != pwConf);

                // 3. Create wallet
                string mnemonic;
                Safe.Create(out mnemonic, pw, walletFilePath, Config.Network);
                // If no exception thrown the wallet is successfully created.
                WriteLine();
                WriteLine("Wallet is successfully created.");
                WriteLine($"Wallet file: {walletFilePath}");

                // 4. Display mnemonic
                WriteLine();
                WriteLine("Write down the following mnemonic words.");
                WriteLine("With the mnemonic words AND your password you can recover this wallet by using the recover-wallet command.");
                WriteLine();
                WriteLine("-------");
                WriteLine(mnemonic);
                WriteLine("-------");
            }

            #endregion GenerateWalletCommand

            #region RecoverWalletCommand

            if (command == "recover-wallet")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var walletFilePath = GetWalletFilePath(args);
                AssertWalletNotExists(walletFilePath);

                WriteLine($"Your software is configured using the Bitcoin {Config.Network} network.");
                WriteLine("Provide your mnemonic words, separated by spaces:");
                var mnemonic = ReadLine();
                AssertCorrectMnemonicFormat(mnemonic);

                WriteLine("Provide your password. Please note the wallet cannot check if your password is correct or not. If you provide a wrong password a wallet will be recovered with your provided mnemonic AND password pair:");
                var password = PasswordConsole.ReadPassword();

                Safe.Recover(mnemonic, password, walletFilePath, Config.Network);
                // If no exception thrown the wallet is successfully recovered.
                WriteLine();
                WriteLine("Wallet is successfully recovered.");
                WriteLine($"Wallet file: {walletFilePath}");
            }

            #endregion RecoverWalletCommand

            #region ShowBalancesCommand

            if (command == "show-balances")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false);

                    // 0. Query all operations, grouped by addresses
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false);

                    // 1. Get all address history record with a wrapper class
                    var addressHistoryRecords = new List <AddressHistoryRecord>();
                    foreach (var elem in operationsPerAddresses)
                    {
                        foreach (BalanceOperation op in elem.Value)
                        {
                            addressHistoryRecords.Add(new AddressHistoryRecord(elem.Key, op));
                        }
                    }

                    // 2. Calculate wallet balances
                    Money confirmedWalletBalance;
                    Money unconfirmedWalletBalance;
                    GetBalances(addressHistoryRecords, out confirmedWalletBalance, out unconfirmedWalletBalance);

                    // 3. Group all address history records by addresses
                    var addressHistoryRecordsPerAddresses = new Dictionary <BitcoinAddress, HashSet <AddressHistoryRecord> >();
                    foreach (BitcoinAddress address in operationsPerAddresses.Keys)
                    {
                        var recs = new HashSet <AddressHistoryRecord>();
                        foreach (AddressHistoryRecord record in addressHistoryRecords)
                        {
                            if (record.Address == address)
                            {
                                recs.Add(record);
                            }
                        }

                        addressHistoryRecordsPerAddresses.Add(address, recs);
                    }

                    // 4. Calculate address balances
                    WriteLine();
                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine(@"Address					Confirmed	Unconfirmed");
                    WriteLine("---------------------------------------------------------------------------");
                    foreach (var elem in addressHistoryRecordsPerAddresses)
                    {
                        Money confirmedBalance;
                        Money unconfirmedBalance;
                        GetBalances(elem.Value, out confirmedBalance, out unconfirmedBalance);
                        if (confirmedBalance != Money.Zero || unconfirmedBalance != Money.Zero)
                        {
                            WriteLine($@"{elem.Key.ToWif()}	{confirmedBalance.ToDecimal(MoneyUnit.BTC):0.#############################}		{unconfirmedBalance.ToDecimal(MoneyUnit.BTC):0.#############################}");
                        }
                    }

                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine($"Confirmed Wallet Balance: {confirmedWalletBalance.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                    WriteLine($"Unconfirmed Wallet Balance: {unconfirmedWalletBalance.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                    WriteLine("---------------------------------------------------------------------------");
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }

            #endregion ShowBalancesCommand

            #region ShowHistoryCommand

            if (command == "show-history")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false);

                    // 0. Query all operations, grouped our used safe addresses
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false);

                    WriteLine();
                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine(@"Date			Amount		Confirmed	Transaction Id");
                    WriteLine("---------------------------------------------------------------------------");

                    Dictionary <uint256, List <BalanceOperation> > operationsPerTransactions = GetOperationsPerTransactions(operationsPerAddresses);

                    // 3. Create history records from the transactions
                    // History records is arbitrary data we want to show to the user
                    var txHistoryRecords = new List <Tuple <DateTimeOffset, Money, int, uint256> >();
                    foreach (var elem in operationsPerTransactions)
                    {
                        var amount = Money.Zero;
                        foreach (var op in elem.Value)
                        {
                            amount += op.Amount;
                        }

                        var firstOp = elem.Value.First();

                        txHistoryRecords
                        .Add(new Tuple <DateTimeOffset, Money, int, uint256>(
                                 firstOp.FirstSeen,
                                 amount,
                                 firstOp.Confirmations,
                                 elem.Key));
                    }

                    // 4. Order the records by confirmations and time (Simply time does not work, because of a QBitNinja issue)
                    var orderedTxHistoryRecords = txHistoryRecords
                                                  .OrderByDescending(x => x.Item3) // Confirmations
                                                  .ThenBy(x => x.Item1);           // FirstSeen
                    foreach (var record in orderedTxHistoryRecords)
                    {
                        // Item2 is the Amount
                        if (record.Item2 > 0)
                        {
                            ForegroundColor = ConsoleColor.Green;
                        }
                        else if (record.Item2 < 0)
                        {
                            ForegroundColor = ConsoleColor.DarkGreen;
                        }
                        WriteLine($@"{record.Item1.DateTime}	{record.Item2}	{record.Item3 > 0}		{record.Item4}");
                        ResetColor();
                    }
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }

            #endregion ShowHistoryCommand

            #region ShowExtKeys

            if (command == "show-extkey")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                WriteLine($"ExtKey: {safe.BitcoinExtKey}");
                WriteLine($"Network: {safe.Network}");
            }
            if (command == "show-extpubkey")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                WriteLine($"ExtPubKey: {safe.BitcoinExtPubKey}");
                WriteLine($"Network: {safe.Network}");
            }

            #endregion ShowExtKeys

            #region ReceiveCommand

            if (command == "receive")
            {
                AssertArgumentsLenght(args.Count, 1, 2);
                var  walletFilePath = GetWalletFilePath(args);
                Safe safe           = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false);

                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerReceiveAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, 7, Safe.HdPathType.Receive).ConfigureAwait(false);

                    WriteLine("---------------------------------------------------------------------------");
                    WriteLine("Unused Receive Addresses");
                    WriteLine("---------------------------------------------------------------------------");
                    foreach (var elem in operationsPerReceiveAddresses)
                    {
                        if (elem.Value.Count == 0)
                        {
                            WriteLine($"{elem.Key.ToWif()}");
                        }
                    }
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }

            #endregion ReceiveCommand

            #region SendCommand

            if (command == "send")
            {
                await AssertCorrectQBitBlockHeightAsync().ConfigureAwait(false);

                AssertArgumentsLenght(args.Count, 3, 4);
                var            walletFilePath = GetWalletFilePath(args);
                BitcoinAddress addressToSend;
                try
                {
                    addressToSend = BitcoinAddress.Create(GetArgumentValue(args, argName: "address", required: true), Config.Network);
                }
                catch (Exception ex)
                {
                    Exit(ex.ToString());
                    throw;
                }

                Safe safe = DecryptWalletByAskingForPassword(walletFilePath);

                if (Config.ConnectionType == ConnectionType.Http)
                {
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, MinUnusedKeyNum).ConfigureAwait(false);

                    // 1. Gather all the not empty private keys
                    WriteLine("Finding not empty private keys...");
                    var operationsPerNotEmptyPrivateKeys = new Dictionary <BitcoinExtKey, List <BalanceOperation> >();
                    foreach (var elem in operationsPerAddresses)
                    {
                        var balance = Money.Zero;
                        foreach (var op in elem.Value)
                        {
                            balance += op.Amount;
                        }

                        if (balance > Money.Zero)
                        {
                            var secret = safe.FindPrivateKey(elem.Key);
                            operationsPerNotEmptyPrivateKeys.Add(secret, elem.Value);
                        }
                    }

                    // 2. Get the script pubkey of the change.
                    WriteLine("Select change address...");
                    Script changeScriptPubKey = null;
                    Dictionary <BitcoinAddress, List <BalanceOperation> > operationsPerChangeAddresses = await QueryOperationsPerSafeAddressesAsync(_qBitClient, safe, minUnusedKeys : 1, hdPathType : Safe.HdPathType.Change).ConfigureAwait(false);

                    foreach (var elem in operationsPerChangeAddresses)
                    {
                        if (elem.Value.Count == 0)
                        {
                            changeScriptPubKey = safe.FindPrivateKey(elem.Key).ScriptPubKey;
                        }
                    }

                    if (changeScriptPubKey == null)
                    {
                        throw new ArgumentNullException();
                    }

                    // 3. Gather coins can be spend
                    WriteLine("Gathering unspent coins...");
                    Dictionary <Coin, bool> unspentCoins = await GetUnspentCoinsAsync(operationsPerNotEmptyPrivateKeys.Keys, _qBitClient).ConfigureAwait(false);

                    // 4. How much money we can spend?
                    var availableAmount            = Money.Zero;
                    var unconfirmedAvailableAmount = Money.Zero;
                    foreach (var elem in unspentCoins)
                    {
                        // If can spend unconfirmed add all
                        if (Config.CanSpendUnconfirmed)
                        {
                            availableAmount += elem.Key.Amount;
                            if (!elem.Value)
                            {
                                unconfirmedAvailableAmount += elem.Key.Amount;
                            }
                        }
                        // else only add confirmed ones
                        else
                        {
                            if (elem.Value)
                            {
                                availableAmount += elem.Key.Amount;
                            }
                        }
                    }

                    // 5. Get and calculate fee
                    WriteLine("Calculating dynamic transaction fee...");
                    Money feePerBytes = null;
                    try
                    {
                        feePerBytes = await QueryFeePerBytesAsync().ConfigureAwait(false);
                    }
                    catch (Exception ex)
                    {
                        WriteLine(ex.Message);
                        Exit("Couldn't calculate transaction fee, try it again later.");
                    }

                    int inNum;

                    string amountString = GetArgumentValue(args, argName: "btc", required: true);
                    if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase))
                    {
                        inNum = unspentCoins.Count;
                    }
                    else
                    {
                        const int expectedMinTxSize = 1 * 148 + 2 * 34 + 10 - 1;
                        inNum = SelectCoinsToSpend(unspentCoins, ParseBtcString(amountString) + feePerBytes * expectedMinTxSize).Count;
                    }
                    const int outNum          = 2;                                      // 1 address to send + 1 for change
                    var       estimatedTxSize = inNum * 148 + outNum * 34 + 10 + inNum; // http://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
                    WriteLine($"Estimated tx size: {estimatedTxSize} bytes");
                    Money fee = feePerBytes * estimatedTxSize;
                    WriteLine($"Fee: {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc");

                    // 6. How much to spend?
                    Money amountToSend = null;
                    if (string.Equals(amountString, "all", StringComparison.OrdinalIgnoreCase))
                    {
                        amountToSend  = availableAmount;
                        amountToSend -= fee;
                    }
                    else
                    {
                        amountToSend = ParseBtcString(amountString);
                    }

                    // 7. Do some checks
                    if (amountToSend < Money.Zero || availableAmount < amountToSend + fee)
                    {
                        Exit("Not enough coins.");
                    }

                    decimal feePc = Math.Round((100 * fee.ToDecimal(MoneyUnit.BTC)) / amountToSend.ToDecimal(MoneyUnit.BTC));
                    if (feePc > 1)
                    {
                        WriteLine();
                        WriteLine($"The transaction fee is {feePc:0.#}% of your transaction amount.");
                        WriteLine($"Sending:\t {amountToSend.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                        WriteLine($"Fee:\t\t {fee.ToDecimal(MoneyUnit.BTC):0.#############################}btc");
                        ConsoleKey response = GetYesNoAnswerFromUser();
                        if (response == ConsoleKey.N)
                        {
                            Exit("User interruption.");
                        }
                    }

                    var confirmedAvailableAmount = availableAmount - unconfirmedAvailableAmount;
                    var totalOutAmount           = amountToSend + fee;
                    if (confirmedAvailableAmount < totalOutAmount)
                    {
                        var unconfirmedToSend = totalOutAmount - confirmedAvailableAmount;
                        WriteLine();
                        WriteLine($"In order to complete this transaction you have to spend {unconfirmedToSend.ToDecimal(MoneyUnit.BTC):0.#############################} unconfirmed btc.");
                        ConsoleKey response = GetYesNoAnswerFromUser();
                        if (response == ConsoleKey.N)
                        {
                            Exit("User interruption.");
                        }
                    }

                    // 8. Select coins
                    WriteLine("Selecting coins...");
                    HashSet <Coin> coinsToSpend = SelectCoinsToSpend(unspentCoins, totalOutAmount);

                    // 9. Get signing keys
                    var signingKeys = new HashSet <ISecret>();
                    foreach (var coin in coinsToSpend)
                    {
                        foreach (var elem in operationsPerNotEmptyPrivateKeys)
                        {
                            if (elem.Key.ScriptPubKey == coin.ScriptPubKey)
                            {
                                signingKeys.Add(elem.Key);
                            }
                        }
                    }

                    // 10. Build the transaction
                    WriteLine("Signing transaction...");
                    var builder = new TransactionBuilder();
                    var tx      = builder
                                  .AddCoins(coinsToSpend)
                                  .AddKeys(signingKeys.ToArray())
                                  .Send(addressToSend, amountToSend)
                                  .SetChange(changeScriptPubKey)
                                  .SendFees(fee)
                                  .BuildTransaction(true);

                    if (!builder.Verify(tx))
                    {
                        Exit("Couldn't build the transaction.");
                    }

                    WriteLine($"Transaction Id: {tx.GetHash()}");

                    // QBit's success response is buggy so let's check manually, too
                    BroadcastResponse broadcastResponse;
                    var       success = false;
                    var       tried   = 0;
                    const int maxTry  = 7;
                    do
                    {
                        tried++;
                        WriteLine($"Try broadcasting transaction... ({tried})");
                        broadcastResponse = await _qBitClient.Broadcast(tx).ConfigureAwait(false);

                        var getTxResp = await _qBitClient.GetTransaction(tx.GetHash()).ConfigureAwait(false);

                        if (getTxResp != null)
                        {
                            success = true;
                            break;
                        }
                        else
                        {
                            await Task.Delay(3000).ConfigureAwait(false);
                        }
                    } while (tried < maxTry);

                    if (!success)
                    {
                        if (broadcastResponse.Error != null)
                        {
                            // Try broadcasting with smartbit if QBit fails (QBit issue)
                            if (broadcastResponse.Error.ErrorCode == NBitcoin.Protocol.RejectCode.INVALID && broadcastResponse.Error.Reason == "Unknown")
                            {
                                WriteLine("Try broadcasting transaction with smartbit...");

                                var post = "https://testnet-api.smartbit.com.au/v1/blockchain/pushtx";
                                if (Config.Network == Network.Main)
                                {
                                    post = "https://api.smartbit.com.au/v1/blockchain/pushtx";
                                }

                                var content = new StringContent(new JObject(new JProperty("hex", tx.ToHex())).ToString(), Encoding.UTF8, "application/json");
                                var resp    = await _httpClient.PostAsync(post, content).ConfigureAwait(false);

                                var json = JObject.Parse(await resp.Content.ReadAsStringAsync().ConfigureAwait(false));
                                if (json.Value <bool>("success"))
                                {
                                    Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green);
                                }
                                else
                                {
                                    WriteLine($"Error code: {json["error"].Value<string>("code")} Reason: {json["error"].Value<string>("message")}");
                                }
                            }
                            else
                            {
                                WriteLine($"Error code: {broadcastResponse.Error.ErrorCode} Reason: {broadcastResponse.Error.Reason}");
                            }
                        }
                        Exit("The transaction might not have been successfully broadcasted. Please check the Transaction ID in a block explorer.", ConsoleColor.Blue);
                    }
                    Exit("Transaction is successfully propagated on the network.", ConsoleColor.Green);
                }
                else if (Config.ConnectionType == ConnectionType.FullNode)
                {
                    throw new NotImplementedException();
                }
                else
                {
                    Exit("Invalid connection type.");
                }
            }

            #endregion SendCommand

            Exit(color: ConsoleColor.Green);
        }