Skip to content

Instantly share code, notes, and snippets.

@rony4d
Last active April 13, 2018 05:44
Show Gist options
  • Select an option

  • Save rony4d/652081c12fa5603844f4efdbfa3fcfb7 to your computer and use it in GitHub Desktop.

Select an option

Save rony4d/652081c12fa5603844f4efdbfa3fcfb7 to your computer and use it in GitHub Desktop.
Dotnet Wallet Console App
using System;
using System.IO;
using Newtonsoft.Json;
using NBitcoin;
namespace DotNetWallet
{
public enum ConnectionType
{
FullNode,
Http
}
public static class Config
{
// Initialized with default attributes
public static string DefaultWalletFileName = @"Wallet.json";
public static Network Network = Network.TestNet;
public static ConnectionType ConnectionType = ConnectionType.Http;
public static bool CanSpendUnconfirmed = false;
static Config()
{
if (!File.Exists(ConfigFileSerializer.ConfigFilePath))
{
Save();
Console.WriteLine($"{ConfigFileSerializer.ConfigFilePath} was missing. It has been created created with default settings.");
}
Load();
}
public static void Load()
{
var rawContent = ConfigFileSerializer.Deserialize();
DefaultWalletFileName = rawContent.DefaultWalletFileName;
if (rawContent.Network == Network.Main.ToString())
Network = Network.Main;
else if (rawContent.Network == Network.TestNet.ToString())
Network = Network.TestNet;
else if (rawContent.Network == null)
throw new Exception($"Network is missing from {ConfigFileSerializer.ConfigFilePath}");
else
throw new Exception($"Wrong Network is specified in {ConfigFileSerializer.ConfigFilePath}");
if (rawContent.ConnectionType == ConnectionType.FullNode.ToString())
ConnectionType = ConnectionType.FullNode;
else if (rawContent.ConnectionType == ConnectionType.Http.ToString())
ConnectionType = ConnectionType.Http;
else if (rawContent.ConnectionType == null)
throw new Exception($"ConnectionType is missing from {ConfigFileSerializer.ConfigFilePath}");
else
throw new Exception($"Wrong ConnectionType is specified in {ConfigFileSerializer.ConfigFilePath}");
if (rawContent.CanSpendUnconfirmed == "True")
CanSpendUnconfirmed = true;
else if (rawContent.CanSpendUnconfirmed == "False")
CanSpendUnconfirmed = false;
else if (rawContent.CanSpendUnconfirmed == null)
throw new Exception($"CanSpendUnconfirmed is missing from {ConfigFileSerializer.ConfigFilePath}");
else
throw new Exception($"Wrong CanSpendUnconfirmed is specified in {ConfigFileSerializer.ConfigFilePath}");
}
public static void Save()
{
ConfigFileSerializer.Serialize(DefaultWalletFileName, Network.ToString(), ConnectionType.ToString(), CanSpendUnconfirmed.ToString());
Load();
}
}
public class ConfigFileSerializer
{
public static string ConfigFilePath = "Config.json";
// KEEP THEM PUBLIC OTHERWISE IT WILL NOT SERIALIZE!
public string DefaultWalletFileName { get; set; }
public string Network { get; set; }
public string ConnectionType { get; set; }
public string CanSpendUnconfirmed { get; set; }
[JsonConstructor]
private ConfigFileSerializer(string walletFileName, string network, string connectionType, string canSpendUnconfirmed)
{
DefaultWalletFileName = walletFileName;
Network = network;
ConnectionType = connectionType;
CanSpendUnconfirmed = canSpendUnconfirmed;
}
internal static void Serialize(string walletFileName, string network, string connectionType, string canSpendUnconfirmed)
{
var content =
JsonConvert.SerializeObject(new ConfigFileSerializer(walletFileName, network, connectionType, canSpendUnconfirmed), Formatting.Indented);
File.WriteAllText(ConfigFilePath, content);
}
internal static ConfigFileSerializer Deserialize()
{
if (!File.Exists(ConfigFilePath))
throw new Exception($"Config file does not exist. Create {ConfigFilePath} before reading it.");
var contentString = File.ReadAllText(ConfigFilePath);
var configFileSerializer = JsonConvert.DeserializeObject<ConfigFileSerializer>(contentString);
return new ConfigFileSerializer(configFileSerializer.DefaultWalletFileName, configFileSerializer.Network, configFileSerializer.ConnectionType, configFileSerializer.CanSpendUnconfirmed);
}
}
}
{
"DefaultWalletFileName": "Wallet.json",
"Network": "TestNet",
"ConnectionType": "Http",
"CanSpendUnconfirmed": "False"
}
{
"EncryptedSeed": "6PYPgX1oewCbhSWCcXnMk6mPtM2dxLDMFr7iYhscjSiUR8T33MAbvgnE32",
"ChainCode": "VLI7xXQWqprBZABuK/mPO3KxcGQzUAJoW4wzXcNN8ew=",
"Network": "TestNet",
"CreationTime": "2017-12-08"
}
using DotNetWallet.Helpers;
using NBitcoin;
using Newtonsoft.Json.Linq;
using QBitNinja.Client;
using QBitNinja.Client.Models;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Threading;
using static DotNetWallet.QBitNinjaJutsus.QBitNinjaJutsus;
using static System.Console;
using DotNetWallet.QBitNinjaJutsus;
using HBitcoin.KeyManagement;
namespace DotNetWallet
{
public class Program
{
#region Commands
public static HashSet<string> Commands = new HashSet<string>()
{
"help",
"generate-wallet",
"recover-wallet",
"show-balances",
"show-history",
"receive",
"send"
};
#endregion
public static void Main(string[] args)
{
//args = new string[] { "help" };
//args = new string[] { "generate-wallet" };
//args = new string[] { "generate-wallet", "wallet-file=EmptyPasswordWallet.json" };
////math super cool donate beach mobile sunny web board kingdom bacon crisp
////no password
//args = new string[] { "recover-wallet", "wallet-file=ugowallet.json" };
args = new string[] { "show-balances", "wallet-file=EmptyPasswordWallet.json" };
//args = new string[] { "receive", "wallet-file=EmptyPasswordWallet.json" };
//args = new string[] { "show-history", "wallet-file=ugowallet.json" };
//args = new string[] { "send", "btc=0.2", "address=msmyUTSznD7SsDQPJ6q5ZQocpvqeeunFPP", "wallet-file=DarkWallet2.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:");
pw = PasswordConsole.ReadPassword();
// 2. Get password confirmation from user
WriteLine("Confirm password:");
pwConf = PasswordConsole.ReadPassword();
if (pw != pwConf) WriteLine("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.ToString()}\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, 10, HdPathType.Receive);
WriteLine("---------------------------------------------------------------------------");
WriteLine("Unused Receive Addresses");
WriteLine("---------------------------------------------------------------------------");
foreach (var elem in operationsPerReceiveAddresses)
if (elem.Value.Count == 0)
WriteLine($"{elem.Key.ToString()}");
}
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;
// 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);
}
#region Assertions
public static void AssertWalletNotExists(string walletFilePath)
{
if (File.Exists(walletFilePath))
{
Exit($"A wallet, named {walletFilePath} already exists.");
}
}
public static void AssertCorrectNetwork(Network network)
{
if (network != Config.Network)
{
WriteLine($"The wallet you want to load is on the {network} Bitcoin network.");
WriteLine($"But your config file specifies {Config.Network} Bitcoin network.");
Exit();
}
}
public static void AssertCorrectMnemonicFormat(string mnemonic)
{
try
{
if (new Mnemonic(mnemonic).IsValidChecksum)
return;
}
catch (FormatException) { }
catch (NotSupportedException) { }
Exit("Incorrect mnemonic format.");
}
// Inclusive
public static void AssertArgumentsLenght(int length, int min, int max)
{
if (length < min)
{
Exit($"Not enough arguments are specified, minimum: {min}");
}
if (length > max)
{
Exit($"Too many arguments are specified, maximum: {max}");
}
}
#endregion
#region CommandLineArgumentStuff
private static string GetArgumentValue(string[] args, string argName, bool required = true)
{
string argValue = "";
foreach (var arg in args)
{
if (arg.StartsWith($"{argName}=", StringComparison.OrdinalIgnoreCase))
{
argValue = arg.Substring(arg.IndexOf("=") + 1);
break;
}
}
if (required && argValue == "")
{
Exit($@"'{argName}=' is not specified.");
}
return argValue;
}
private static string GetWalletFilePath(string[] args)
{
string walletFileName = GetArgumentValue(args, "wallet-file", required: false);
if (walletFileName == "") walletFileName = Config.DefaultWalletFileName;
var walletDirName = "Wallets";
Directory.CreateDirectory(walletDirName);
return Path.Combine(walletDirName, walletFileName);
}
#endregion
#region CommandLineInterface
private static Safe DecryptWalletByAskingForPassword(string walletFilePath)
{
Safe safe = null;
string pw;
bool correctPw = false;
WriteLine("Type your password:");
do
{
pw = PasswordConsole.ReadPassword();
try
{
safe = Safe.Load(pw, walletFilePath);
AssertCorrectNetwork(safe.Network);
correctPw = true;
}
catch (System.Security.SecurityException)
{
WriteLine("Invalid password, try again, (or press ctrl+c to exit):");
correctPw = false;
}
} while (!correctPw);
if (safe == null)
throw new Exception("Wallet could not be decrypted.");
WriteLine($"{walletFilePath} wallet is decrypted.");
return safe;
}
private static ConsoleKey GetYesNoAnswerFromUser()
{
ConsoleKey response;
do
{
WriteLine($"Are you sure you want to proceed? (y/n)");
response = ReadKey(false).Key; // true is intercept key (dont show), false is show
if (response != ConsoleKey.Enter)
WriteLine();
} while (response != ConsoleKey.Y && response != ConsoleKey.N);
return response;
}
public static void DisplayHelp()
{
WriteLine("Possible commands are:");
foreach (var cmd in Commands) WriteLine($"\t{cmd}");
}
public static void Exit(string reason = "", ConsoleColor color = ConsoleColor.Red)
{
ForegroundColor = color;
WriteLine();
if (reason != "")
{
WriteLine(reason);
}
WriteLine("Press any key to exit...");
ResetColor();
ReadKey();
Environment.Exit(0);
}
#endregion
#region Helpers
private static Money ParseBtcString(string value)
{
decimal amount;
if (!decimal.TryParse(
value.Replace(',', '.'),
NumberStyles.Any,
CultureInfo.InvariantCulture,
out amount))
{
Exit("Wrong btc amount format.");
}
return new Money(amount, MoneyUnit.BTC);
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment