using System.Diagnostics; using System.Net; using System.Net.Security; using System.Runtime.InteropServices; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.RegularExpressions; using DnsClient; using DnsClient.Protocol; using Newtonsoft.Json.Linq; using QRCoder; using Point = System.Drawing.Point; using Size = System.Drawing.Size; namespace FireWallet { public partial class MainForm : Form { #region Variables // Main directory for the application files public string dir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\FireWallet\\"; // Settings and theme key value pairs public Dictionary NodeSettings { get; set; } public Dictionary UserSettings { get; set; } public Dictionary Theme { get; set; } // HSD & Wallet settings public bool WatchOnly { get; set; } public bool HSD { get; set; } public Process HSDProcess { get; set; } public int HSDNetwork { get; set; } public string Account { get; set; } public string Password { get; set; } // Wallet information public decimal Balance { get; set; } public decimal Balance_Locked { get; set; } // Batching variables public bool BatchMode { get; set; } public BatchForm BatchForm { get; set; } public bool multiSig { get; set; } #endregion #region Application public MainForm() { InitializeComponent(); panelaccount.Visible = true; } private async void MainForm_Load(object sender, EventArgs e) { // Set initial values WatchOnly = false; Account = ""; // Stop timers and load settings timerNodeStatus.Stop(); LoadSettings(); // Show splash SplashScreen ss = new SplashScreen(false); bool splash = false; if (UserSettings.ContainsKey("hide-splash")) { if (UserSettings["hide-splash"] == "false") { // Show splash screen ss.Show(); splash = true; } } else { // Show splash screen ss.Show(); splash = true; } // Record time DateTime start = DateTime.Now; // Do form events Application.DoEvents(); // Load theme UpdateTheme(); // Theme drop down foreach (ToolStripItem item in toolStripDropDownButtonHelp.DropDownItems) { if (item is ToolStripDropDownItem dropDownItem) { dropDownItem.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); dropDownItem.BackColor = ColorTranslator.FromHtml(Theme["background"]); } } toolStripDropDownButtonHelp.DropDown.BackColor = ColorTranslator.FromHtml(Theme["background"]); // Load node if (await LoadNode(ss) != true) this.Close(); // If node load caused app to close, exit load function if (this.Disposing || this.IsDisposed) return; // Edit the theme of the navigation panel panelNav.BackColor = ColorTranslator.FromHtml(Theme["background-alt"]); panelNav.ForeColor = ColorTranslator.FromHtml(Theme["foreground-alt"]); foreach (Control c in panelNav.Controls) { c.BackColor = ColorTranslator.FromHtml(Theme["background"]); c.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); } panelNav.Dock = DockStyle.Left; ResizeForm(); panelNav.Visible = false; // Prompt for login GetAccounts(); BatchMode = false; if (splash) { // If not internal node if (!HSD) { // Wait until the splash has been visible for 4 seconds if ((DateTime.Now - start).TotalSeconds > 4) ss.CloseSplash(); else Thread.Sleep(4000 - (int)(DateTime.Now - start).TotalMilliseconds); ss.CloseSplash(); } else { // Wait until the Node is connected before closing the splash while (true) { string status = await APIGet("", false); if (status != "Error") { ss.CloseSplash(); GetAccounts(); break; } else { Thread.Sleep(100); } } } Application.DoEvents(); while (!ss.IsClosed) { Thread.Sleep(100); Application.DoEvents(); } } AddLog("Loaded"); // Pull form to front this.WindowState = FormWindowState.Minimized; this.Show(); this.Opacity = 1; this.WindowState = FormWindowState.Normal; textBoxaccountpassword.Focus(); } private void MainForm_Closing(object sender, FormClosingEventArgs e) { // Close HSD before closing FireWallet AddLog("Closing"); if (HSDProcess != null) { this.Opacity = 0; HSDProcess.Kill(); AddLog("HSD Closed"); Thread.Sleep(1000); try { HSDProcess.Dispose(); } catch { AddLog("Dispose failed"); } } } #endregion #region Settings private async Task LoadNode(SplashScreen? ss) { HSD = false; if (!File.Exists(dir + "node.txt")) { ss.Hide(); NodeForm cf = new NodeForm(); timerNodeStatus.Stop(); cf.ShowDialog(); timerNodeStatus.Start(); ss.Show(); } if (!File.Exists(dir + "node.txt")) { AddLog("Node setup failed"); this.Close(); await Task.Delay(1000); AddLog("Close Failed"); } StreamReader sr = new StreamReader(dir + "node.txt"); NodeSettings = new Dictionary(); while (!sr.EndOfStream) { string line = sr.ReadLine(); string[] split = line.Split(':'); NodeSettings.Add(split[0].Trim(), split[1].Trim()); } sr.Dispose(); if (!NodeSettings.ContainsKey("Network") || !NodeSettings.ContainsKey("Key") || !NodeSettings.ContainsKey("IP")) { AddLog("Node Settings file is missing key"); this.Close(); await Task.Delay(1000); AddLog("Close Failed"); } HSDNetwork = Convert.ToInt32(NodeSettings["Network"]); switch (HSDNetwork) { case 0: toolStripStatusLabelNetwork.Text = "Network: Mainnet"; break; case 1: toolStripStatusLabelNetwork.Text = "Network: Regtest (Not Fully Tested)"; break; case 2: toolStripStatusLabelNetwork.Text = "Network: Testnet (Not Implemented)"; break; } if (NodeSettings.ContainsKey("Timeout")) { int timeout = Convert.ToInt32(NodeSettings["Timeout"]); httpClient.Timeout = TimeSpan.FromSeconds(timeout); } else httpClient.Timeout = TimeSpan.FromSeconds(10); if (NodeSettings.ContainsKey("HSD")) { if (NodeSettings["HSD"].ToLower() == "true") { HSD = true; AddLog("Starting HSD"); toolStripStatusLabelstatus.Text = "Status: HSD Starting"; string hsdPath = dir + "hsd\\bin\\hsd.exe"; if (NodeSettings.ContainsKey("HSD-command")) { if (NodeSettings["HSD-command"].Contains("{default-dir}")) { if (!Directory.Exists(dir + "hsd")) { NotifyForm Notifyinstall = new NotifyForm("Installing hsd\nThis may take a few minutes\nDo not close FireWallet", false); Notifyinstall.Show(); // Wait for the notification to show await Task.Delay(1000); string repositoryUrl = "https://github.com/handshake-org/hsd.git"; string destinationPath = dir + "hsd"; CloneRepository(repositoryUrl, destinationPath); Notifyinstall.CloseNotification(); Notifyinstall.Dispose(); } if (!Directory.Exists(dir + "hsd\\node_modules")) { AddLog("HSD install failed"); this.Close(); return false; } } } else { if (!Directory.Exists(dir + "hsd")) { NotifyForm Notifyinstall = new NotifyForm("Installing hsd\nThis may take a few minutes\nDo not close FireWallet", false); Notifyinstall.Show(); // Wait for the notification to show await Task.Delay(1000); string repositoryUrl = "https://github.com/handshake-org/hsd.git"; string destinationPath = dir + "hsd"; CloneRepository(repositoryUrl, destinationPath); Notifyinstall.CloseNotification(); Notifyinstall.Dispose(); } if (!Directory.Exists(dir + "hsd\\node_modules")) { AddLog("HSD install failed"); this.Close(); return false; } } HSDProcess = new Process(); bool hideScreen = true; if (NodeSettings.ContainsKey("HideScreen")) { if (NodeSettings["HideScreen"].ToLower() == "false") { hideScreen = false; } } try { HSDProcess.StartInfo.CreateNoWindow = hideScreen; if (hideScreen) { HSDProcess.StartInfo.RedirectStandardError = true; // Send errors to log HSDProcess.ErrorDataReceived += (sender, e) => AddLog("HSD Error: " + e.Data); } else { HSDProcess.StartInfo.RedirectStandardError = false; } HSDProcess.StartInfo.RedirectStandardInput = true; HSDProcess.StartInfo.RedirectStandardOutput = false; HSDProcess.StartInfo.UseShellExecute = false; HSDProcess.StartInfo.FileName = "node.exe"; if (NodeSettings.ContainsKey("HSD-command")) { AddLog("Using custom HSD command"); string command = NodeSettings["HSD-command"]; command = command.Replace("{default-dir}", dir + "hsd\\bin\\hsd"); string bobPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Bob\\hsd_data"; if (Directory.Exists(bobPath)) { command = command.Replace("{Bob}", bobPath); } else if (command.Contains("{Bob}")) { AddLog("Bob not found, using default HSD command"); command = dir + "hsd\\bin\\hsd --agent=FireWallet --index-tx --index-address --api-key " + NodeSettings["Key"]; } command = command.Replace("{key}", NodeSettings["Key"]); HSDProcess.StartInfo.Arguments = command; } else { AddLog("Using default HSD command"); HSDProcess.StartInfo.Arguments = dir + "hsd\\bin\\hsd --agent=FireWallet --index-tx --index-address --api-key " + NodeSettings["Key"]; string bobPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Bob\\hsd_data"; if (Directory.Exists(bobPath)) { HSDProcess.StartInfo.Arguments = HSDProcess.StartInfo.Arguments + " --prefix " + bobPath; } } HSDProcess.Start(); // Wait for HSD to start await Task.Delay(2000); // Check if HSD is running if (HSDProcess.HasExited) { AddLog("HSD Failed to start"); AddLog(HSDProcess.StandardError.ReadToEnd()); NotifyForm Notifyinstall = new NotifyForm("HSD Failed to start\nPlease check the logs"); Notifyinstall.ShowDialog(); Notifyinstall.Dispose(); // Wait for the notification to show await Task.Delay(1000); this.Close(); await Task.Delay(1000); return false; } } catch (Exception ex) { AddLog("HSD Failed to start"); AddLog(ex.Message); this.Close(); await Task.Delay(1000); } } } timerNodeStatus.Start(); NodeStatus(); return true; } private void LoadSettings() { if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); if (!File.Exists(dir + "settings.txt")) { AddLog("Creating settings file"); StreamWriter sw = new StreamWriter(dir + "settings.txt"); sw.WriteLine("explorer-tx: https://niami.io/tx/"); sw.WriteLine("explorer-addr: https://niami.io/address/"); sw.WriteLine("explorer-block: https://niami.io/block/"); sw.WriteLine("explorer-domain: https://niami.io/domain/"); sw.WriteLine("confirmations: 1"); sw.WriteLine("portfolio-tx: 20"); sw.WriteLine("hide-splash: false"); sw.Dispose(); } StreamReader sr = new StreamReader(dir + "settings.txt"); UserSettings = new Dictionary(); while (!sr.EndOfStream) { string line = sr.ReadLine(); UserSettings.Add(line.Substring(0, line.IndexOf(":")).Trim(), line.Substring(line.IndexOf(":") + 1).Trim()); } sr.Dispose(); } #endregion #region Logging public void AddLog(string message) { if (message.Contains("Get Error: No connection could be made because the target machine actively refused it")) return; // If file size is over 1MB, rename it to old.log.txt if (File.Exists(dir + "log.txt")) { FileInfo fi = new FileInfo(dir + "log.txt"); if (fi.Length > 1000000) { if (File.Exists(dir + "old.log.txt")) File.Delete(dir + "old.log.txt"); // Delete old log file as it is super old File.Move(dir + "log.txt", dir + "old.log.txt"); } } StreamWriter sw = new StreamWriter(dir + "log.txt", true); sw.WriteLine(DateTime.Now.ToString() + ": " + message); sw.Dispose(); } #endregion #region Theming private void UpdateTheme() { // Check if file exists if (!Directory.Exists(dir)) { CreateConfig(dir); } if (!File.Exists(dir + "theme.txt")) { CreateConfig(dir); } // Read file StreamReader sr = new StreamReader(dir + "theme.txt"); Theme = new Dictionary(); while (!sr.EndOfStream) { string line = sr.ReadLine(); string[] split = line.Split(':'); Theme.Add(split[0].Trim(), split[1].Trim()); } sr.Dispose(); if (!Theme.ContainsKey("background") || !Theme.ContainsKey("background-alt") || !Theme.ContainsKey("foreground") || !Theme.ContainsKey("foreground-alt")) { AddLog("Theme file is missing key"); return; } // Apply theme this.BackColor = ColorTranslator.FromHtml(Theme["background"]); // Foreground this.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); // Need to specify this for each groupbox to override the black text foreach (Control c in Controls) { ThemeControl(c); } this.Width = Screen.PrimaryScreen.Bounds.Width / 5 * 3; this.Height = Screen.PrimaryScreen.Bounds.Height / 5 * 3; applyTransparency(Theme); ResizeForm(); } public void ThemeControl(Control c) { if (c.GetType() == typeof(GroupBox) || c.GetType() == typeof(Panel)) { c.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); foreach (Control sub in c.Controls) { ThemeControl(sub); } } if (c.GetType() == typeof(TextBox) || c.GetType() == typeof(Button) || c.GetType() == typeof(ComboBox) || c.GetType() == typeof(StatusStrip) || c.GetType() == typeof(ToolStrip) || c.GetType() == typeof(NumericUpDown)) { c.ForeColor = ColorTranslator.FromHtml(Theme["foreground-alt"]); c.BackColor = ColorTranslator.FromHtml(Theme["background-alt"]); } if (c.GetType() == typeof(Panel)) c.Dock = DockStyle.Fill; } private void applyTransparency(Dictionary theme) { if (theme.ContainsKey("transparent-mode")) { switch (theme["transparent-mode"]) { case "mica": var accent = new AccentPolicy { AccentState = AccentState.ACCENT_ENABLE_BLURBEHIND }; var accentStructSize = Marshal.SizeOf(accent); var accentPtr = Marshal.AllocHGlobal(accentStructSize); Marshal.StructureToPtr(accent, accentPtr, false); var data = new WindowCompositionAttributeData { Attribute = WindowCompositionAttribute.WCA_ACCENT_POLICY, SizeOfData = accentStructSize, Data = accentPtr }; User32.SetWindowCompositionAttribute(Handle, ref data); Marshal.FreeHGlobal(accentPtr); break; case "key": if (theme.ContainsKey("transparency-key")) { switch (theme["transparency-key"]) { case "alt": this.TransparencyKey = ColorTranslator.FromHtml(theme["background-alt"]); break; case "main": this.TransparencyKey = ColorTranslator.FromHtml(theme["background"]); break; default: this.TransparencyKey = ColorTranslator.FromHtml(theme["transparency-key"]); break; } } else { AddLog("No transparency-key found in theme file"); } break; case "percent": if (theme.ContainsKey("transparency-percent")) { Opacity = Convert.ToDouble(theme["transparency-percent"]) / 100; } else { AddLog("No transparency-percent found in theme file"); } break; } } } private void CreateConfig(string dir) { if (!Directory.Exists(dir)) { Directory.CreateDirectory(dir); } StreamWriter sw = new StreamWriter(dir + "theme.txt"); sw.WriteLine("background: #000000"); sw.WriteLine("foreground: #8e05c2"); sw.WriteLine("background-alt: #3e065f"); sw.WriteLine("foreground-alt: #ffffff"); sw.WriteLine("transparent-mode: off"); sw.WriteLine("transparency-key: main"); sw.WriteLine("transparency-percent: 90"); sw.WriteLine("selected-bg: #000000"); sw.WriteLine("selected-fg: #ffffff"); sw.WriteLine("error: #ff0000"); sw.Dispose(); AddLog("Created theme file"); } // Required for mica effect internal enum AccentState { ACCENT_DISABLED = 0, ACCENT_ENABLE_GRADIENT = 1, ACCENT_ENABLE_TRANSPARENTGRADIENT = 2, ACCENT_ENABLE_BLURBEHIND = 3, ACCENT_INVALID_STATE = 4 } internal enum WindowCompositionAttribute { WCA_ACCENT_POLICY = 19 } [StructLayout(LayoutKind.Sequential)] internal struct AccentPolicy { public AccentState AccentState; public int AccentFlags; public int GradientColor; public int AnimationId; } [StructLayout(LayoutKind.Sequential)] internal struct WindowCompositionAttributeData { public WindowCompositionAttribute Attribute; public IntPtr Data; public int SizeOfData; } internal static class User32 { [DllImport("user32.dll")] internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data); } private void MainForm_ResizeEnd(object sender, EventArgs e) { ResizeForm(); } private void Form1_Resize(object sender, EventArgs e) { ResizeForm(); } private void ResizeForm() { groupBoxaccount.Left = (this.ClientSize.Width - groupBoxaccount.Width) / 2; groupBoxaccount.Top = (this.ClientSize.Height - groupBoxaccount.Height) / 2; groupBoxDomains.Width = panelDomains.Width - 20; groupBoxDomains.Left = 10; groupBoxDomains.Height = panelDomains.Height - groupBoxDomains.Top - 10; buttonNavSettings.Top = panelNav.Height - buttonNavSettings.Height - 10; buttonSettingsSave.Top = panelSettings.Height - buttonSettingsSave.Height - 10; groupBoxTransactions.Height = panelPortfolio.Height - groupBoxbalance.Height - 10; // SEND Page labelSendPrompt.Left = (panelSend.Width - labelSendPrompt.Width) / 2; buttonSendHNS.Left = (panelSend.Width - buttonSendHNS.Width) / 2; labelSendingTo.Left = (panelSend.Width - labelSendingTo.Width - textBoxSendingTo.Width) / 2; labelSendingAmount.Left = labelSendingTo.Left; textBoxSendingTo.Left = labelSendingTo.Left + labelSendingTo.Width + 10; textBoxSendingAmount.Left = textBoxSendingTo.Left; labelSendingMax.Left = labelSendingTo.Left; labelSendingError.Left = textBoxSendingTo.Left + textBoxSendingTo.Width + 10; labelSendingFee.Left = labelSendingTo.Left; buttonSendMax.Left = textBoxSendingAmount.Left + textBoxSendingAmount.Width - buttonSendMax.Width; checkBoxSendSubFee.Left = labelSendingTo.Left; // RECEIVE Page labelReceive1.Left = (panelRecieve.Width - labelReceive1.Width) / 2; labelReceive2.Left = (panelRecieve.Width - labelReceive2.Width) / 2; textBoxReceiveAddress.Left = (panelRecieve.Width - textBoxReceiveAddress.Width) / 2; Size size = TextRenderer.MeasureText(textBoxReceiveAddress.Text, textBoxReceiveAddress.Font); textBoxReceiveAddress.Width = size.Width + 10; textBoxReceiveAddress.Left = (panelRecieve.Width - textBoxReceiveAddress.Width) / 2; pictureBoxReceiveQR.Width = panelRecieve.Width / 3; pictureBoxReceiveQR.Left = (panelRecieve.Width - pictureBoxReceiveQR.Width) / 2; } #endregion #region Accounts private void buttonaccountnew_Click(object sender, EventArgs e) { NewAccountForm newAccount = new NewAccountForm(this); newAccount.ShowDialog(); newAccount.Dispose(); GetAccounts(); } private async void GetAccounts() { try { string APIresponse = await APIGet("wallet", true); comboBoxaccount.Items.Clear(); if (APIresponse != "Error") { comboBoxaccount.Enabled = true; JArray jArray = JArray.Parse(APIresponse); foreach (string account in jArray) { comboBoxaccount.Items.Add(account); } if (comboBoxaccount.Items.Count > 0) { comboBoxaccount.SelectedIndex = 0; } else { comboBoxaccount.Items.Add("No accounts found"); comboBoxaccount.Enabled = false; } } else { comboBoxaccount.Items.Add("No accounts found"); comboBoxaccount.Enabled = false; } } catch { AddLog("Error getting accounts"); } } private async Task Login() { string path = "wallet/" + Account + "/unlock"; string content = "{\"passphrase\": \"" + Password + "\",\"timeout\": 60}"; if (Password == "") { // For some reason, the API doesn't like an empty password, so we'll just use a default one content = "{\"passphrase\": \"password\" ,\"timeout\": 60}"; } string APIresponse = await APIPost(path, true, content); if (!APIresponse.Contains("true")) { AddLog("Login failed"); NotifyForm notifyForm = new NotifyForm("Login Failed\nMake sure your password is correct"); notifyForm.ShowDialog(); notifyForm.Dispose(); return false; } path = ""; content = "{\"method\": \"selectwallet\",\"params\":[ \"" + Account + "\"]}"; APIresponse = await APIPost(path, true, content); if (!APIresponse.Contains("\"error\":null")) { AddLog("Wallet selection failed"); NotifyForm notifyForm = new NotifyForm("Wallet selection failed\n" + APIresponse); notifyForm.ShowDialog(); notifyForm.Dispose(); return false; } UpdateBalance(); path = "wallet/" + Account + ""; APIresponse = await APIGet(path, true); JObject jObject = JObject.Parse(APIresponse); if (jObject["watchOnly"].ToString() == "True") { WatchOnly = true; toolStripStatusLabelLedger.Visible = true; buttonAddressVerify.Visible = true; } else { WatchOnly = false; toolStripStatusLabelLedger.Visible = false; buttonAddressVerify.Visible = false; } path = "wallet/" + Account + "/account/default"; APIresponse = await APIGet(path, true); if (APIresponse.Contains("Error")) { AddLog("Error getting default account"); multiSig = false; } else { jObject = JObject.Parse(APIresponse); if (jObject["n"].ToString() == "1") { multiSig = false; } else { multiSig = true; } } if (multiSig) { toolStripStatusLabelMultisig.Visible = true; buttonMultiSettings.Visible = true; } else { toolStripStatusLabelMultisig.Visible = false; buttonMultiSettings.Visible = false; } if (WatchOnly || multiSig) { buttonRevealAll.Visible = false; buttonRedeemAll.Visible = false; buttonSendAll.Visible = false; } else { buttonRevealAll.Visible = true; buttonRedeemAll.Visible = true; buttonSendAll.Visible = true; } return true; } private async void LoginClick(object sender, EventArgs e) { // If the node isn't connected show a message try { if (await APIGet("", false) == "Error") { NotifyForm notifyForm = new NotifyForm("Node not connected"); notifyForm.ShowDialog(); notifyForm.Dispose(); return; } Account = comboBoxaccount.Text; Password = textBoxaccountpassword.Text; bool loggedin = await Login(); if (loggedin) { toolStripStatusLabelaccount.Text = "Account: " + Account; textBoxaccountpassword.Text = ""; panelaccount.Visible = false; toolStripSplitButtonlogout.Visible = true; panelNav.Visible = true; buttonNavPortfolio.PerformClick(); } } catch (Exception ex) { NotifyForm notifyForm = new NotifyForm("Node not connected\n" + ex.Message); notifyForm.ShowDialog(); notifyForm.Dispose(); return; } } private void PasswordEntered(object sender, KeyEventArgs e) { if (e.KeyValue == 13) { LoginClick(sender, e); } } private void AccountChoose(object sender, EventArgs e) { textBoxaccountpassword.Focus(); } private async void Logout(object sender, EventArgs e) { Password = ""; // Clear password from memory as soon as possible toolStripSplitButtonlogout.Visible = false; toolStripStatusLabelLedger.Visible = false; string path = "wallet/" + Account + "/lock"; string content = ""; string APIresponse = await APIPost(path, true, content); if (!APIresponse.Contains("true")) { AddLog("Logout failed"); NotifyForm notifyForm = new NotifyForm("Logout Failed\n" + APIresponse); notifyForm.ShowDialog(); notifyForm.Dispose(); panelaccount.Visible = true; return; } panelaccount.Visible = true; panelNav.Visible = false; hidePages(); toolStripStatusLabelaccount.Text = "Account: Not Logged In"; textBoxaccountpassword.Focus(); } private async void buttonRevealAll_Click(object sender, EventArgs e) { buttonRevealAll.Enabled = false; string content = "{\"method\": \"sendreveal\"}"; string response = await APIPost("", true, content); if (response == "Error") { AddLog("Error sending reveal"); NotifyForm notifyForm = new NotifyForm("Error sending reveal"); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonRevealAll.Enabled = true; return; } JObject resp = JObject.Parse(response); if (resp["error"].ToString() != "") { AddLog("Error sending reveal"); AddLog(resp["error"].ToString()); JObject error = JObject.Parse(resp["error"].ToString()); NotifyForm notifyForm = new NotifyForm("Error sending reveal\n" + error["message"].ToString()); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonRevealAll.Enabled = true; return; } if (resp.ContainsKey("result")) { JObject result = JObject.Parse(resp["result"].ToString()); string hash = result["hash"].ToString(); NotifyForm notifyForm = new NotifyForm("Reveal sent\n" + hash, "Explorer", UserSettings["explorer-tx"] + hash); notifyForm.ShowDialog(); notifyForm.Dispose(); } buttonRevealAll.Enabled = true; } private async void buttonRedeemAll_Click(object sender, EventArgs e) { buttonRedeemAll.Enabled = false; string content = "{\"method\": \"sendbatch\", \"params\":[[[\"REDEEM\"]]]}"; string response = await APIPost("", true, content); if (response == "Error") { AddLog("Error sending batch"); NotifyForm notifyForm = new NotifyForm("Error sending batch"); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonRedeemAll.Enabled = true; return; } JObject resp = JObject.Parse(response); if (resp["error"].ToString() != "") { AddLog("Error sending batch"); AddLog(resp["error"].ToString()); JObject error = JObject.Parse(resp["error"].ToString()); NotifyForm notifyForm = new NotifyForm("Error sending batch\n" + error["message"].ToString()); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonRedeemAll.Enabled = true; return; } if (resp.ContainsKey("result")) { JObject result = JObject.Parse(resp["result"].ToString()); string hash = result["hash"].ToString(); NotifyForm notifyForm = new NotifyForm("Batch sent\n" + hash, "Explorer", UserSettings["explorer-tx"] + hash); notifyForm.ShowDialog(); notifyForm.Dispose(); } buttonRedeemAll.Enabled = true; } private async void buttonSendAll_Click(object sender, EventArgs e) { buttonSendAll.Enabled = false; string content = "{\"method\": \"sendbatch\", \"params\":[[[\"REVEAL\"],[\"REDEEM\"],[\"RENEW\"]]]}"; string response = await APIPost("", true, content); if (response == "Error") { AddLog("Error sending batch"); NotifyForm notifyForm = new NotifyForm("Error sending batch"); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonSendAll.Enabled = true; return; } JObject resp = JObject.Parse(response); if (resp["error"].ToString() != "") { AddLog("Error sending batch"); AddLog(resp["error"].ToString()); JObject error = JObject.Parse(resp["error"].ToString()); NotifyForm notifyForm = new NotifyForm("Error sending batch\n" + error["message"].ToString()); notifyForm.ShowDialog(); notifyForm.Dispose(); buttonSendAll.Enabled = true; return; } if (resp.ContainsKey("result")) { JObject result = JObject.Parse(resp["result"].ToString()); string hash = result["hash"].ToString(); NotifyForm notifyForm = new NotifyForm("Batch sent\n" + hash, "Explorer", UserSettings["explorer-tx"] + hash); notifyForm.ShowDialog(); notifyForm.Dispose(); } buttonSendAll.Enabled = true; } #endregion #region API HttpClient httpClient = new HttpClient(); private async void NodeStatus() { if (await APIGet("", false) == "Error") { if (toolStripStatusLabelstatus.Text != "Status: HSD Starting") { toolStripStatusLabelstatus.Text = "Status: Node Not Connected"; } return; } else { if (toolStripStatusLabelstatus.Text != "Status: Node Connected") GetAccounts(); // Get accounts if node was not connected before toolStripStatusLabelstatus.Text = "Status: Node Connected"; } if (Account == "") return; // Don't update balance if not logged in // Try to keep wallet unlocked string path = "wallet/" + Account + "/unlock"; string content = "{\"passphrase\": \"" + Password + "\",\"timeout\": 60}"; await APIPost(path, true, content); path = ""; content = "{\"method\": \"selectwallet\",\"params\":[ \"" + Account + "\"]}"; await APIPost(path, true, content); } private async Task UpdateBalance() { string response = await APIGet("wallet/" + Account + "/balance?account=default", true); if (response == "Error") return; JObject resp = JObject.Parse(response); decimal available = (Convert.ToDecimal(resp["unconfirmed"].ToString()) - Convert.ToDecimal(resp["lockedUnconfirmed"].ToString())) / 1000000; decimal locked = Convert.ToDecimal(resp["lockedUnconfirmed"].ToString()) / 1000000; available = decimal.Round(available, 2); locked = decimal.Round(locked, 2); Balance = available; Balance_Locked = locked; } /// /// Post to HSD API /// /// Path to post to /// Whether to use port 12039 /// Content to post /// public async Task APIPost(string path, bool wallet, string content) { if (content == "{\"passphrase\": \"\",\"timeout\": 60}") { return ""; } string key = NodeSettings["Key"]; string ip = NodeSettings["IP"]; string port = "1203"; if (HSDNetwork == 1) { port = "1303"; } if (wallet) port = port + "9"; else port = port + "7"; HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, "http://" + ip + ":" + port + "/" + path); req.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes("x:" + key))); req.Content = new StringContent(content); // Send request try { HttpResponseMessage resp = await httpClient.SendAsync(req); if (!resp.IsSuccessStatusCode) { AddLog("Post Error: " + resp.StatusCode); AddLog(await resp.Content.ReadAsStringAsync()); return "Error"; } return await resp.Content.ReadAsStringAsync(); } catch (Exception ex) { AddLog("Post Error: " + ex.Message); if (ex.Message.Contains("The request was canceled due to the configured HttpClient.Timeout")) { await RestartNode(); } return "Error"; } } /// /// Get from HSD API /// /// Path to get /// Whether to use port 12039 /// public async Task APIGet(string path, bool wallet) { if (NodeSettings == null) return "Error"; if (!NodeSettings.ContainsKey("Key") || !NodeSettings.ContainsKey("IP")) return "Error"; string key = NodeSettings["Key"]; string ip = NodeSettings["IP"]; string port = "1203"; if (HSDNetwork == 1) { port = "1303"; } if (wallet) port = port + "9"; else port = port + "7"; try { HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://" + ip + ":" + port + "/" + path); // Add API key to header request.Headers.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes("x:" + key))); // Send request and log response HttpResponseMessage response = await httpClient.SendAsync(request); if (!response.IsSuccessStatusCode) { AddLog("Get Error: " + response.StatusCode); AddLog(await response.Content.ReadAsStringAsync()); return "Error"; } return await response.Content.ReadAsStringAsync(); } // Log errors to log textbox catch (Exception ex) { AddLog("Get Error: " + ex.Message); if (ex.Message.Contains("The request was canceled due to the configured HttpClient.Timeout")) { await RestartNode(); } return "Error"; } } private async Task RestartNode() { if (!HSD) { NotifyForm nf = new NotifyForm("NODE Not responding"); nf.ShowDialog(); nf.Dispose(); return false; } this.Enabled = false; this.Visible = false; // Show splash SplashScreen ss = new SplashScreen(false); bool splash = false; if (UserSettings.ContainsKey("hide-splash")) { if (UserSettings["hide-splash"] == "false") { // Show splash screen ss.Show(); splash = true; } } else { // Show splash screen ss.Show(); splash = true; } // Kill node if (HSDProcess != null) { HSDProcess.Kill(); AddLog("Killed HSD"); Thread.Sleep(1000); try { HSDProcess.Dispose(); } catch { AddLog("Dispose failed"); } } else AddLog("HSD was not running"); HSDProcess = new Process(); bool hideScreen = true; if (NodeSettings.ContainsKey("HideScreen")) { if (NodeSettings["HideScreen"].ToLower() == "false") { hideScreen = false; } } try { HSDProcess.StartInfo.CreateNoWindow = hideScreen; if (hideScreen) { HSDProcess.StartInfo.RedirectStandardError = true; HSDProcess.ErrorDataReceived += (sender, e) => AddLog("HSD Error: " + e.Data); } else HSDProcess.StartInfo.RedirectStandardError = false; HSDProcess.StartInfo.RedirectStandardInput = true; HSDProcess.StartInfo.RedirectStandardOutput = false; HSDProcess.StartInfo.UseShellExecute = false; HSDProcess.StartInfo.FileName = "node.exe"; if (NodeSettings.ContainsKey("HSD-command")) { AddLog("Using custom HSD command"); string command = NodeSettings["HSD-command"]; command = command.Replace("{default-dir}", dir + "hsd\\bin\\hsd"); string bobPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Bob\\hsd_data"; if (Directory.Exists(bobPath)) { command = command.Replace("{Bob}", bobPath); } else if (command.Contains("{Bob}")) { AddLog("Bob not found, using default HSD command"); command = dir + "hsd\\bin\\hsd --agent=FireWallet --index-tx --index-address --api-key " + NodeSettings["Key"]; } command = command.Replace("{key}", NodeSettings["Key"]); HSDProcess.StartInfo.Arguments = command; } else { AddLog("Using default HSD command"); HSDProcess.StartInfo.Arguments = dir + "hsd\\bin\\hsd --agent=FireWallet --index-tx --index-address --api-key " + NodeSettings["Key"]; string bobPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Bob\\hsd_data"; if (Directory.Exists(bobPath)) { HSDProcess.StartInfo.Arguments = HSDProcess.StartInfo.Arguments + " --prefix " + bobPath; } } HSDProcess.Start(); // Wait for HSD to start await Task.Delay(2000); // Check if HSD is running if (HSDProcess.HasExited) { AddLog("HSD Failed to start"); AddLog(HSDProcess.StandardError.ReadToEnd()); NotifyForm Notifyinstall = new NotifyForm("HSD Failed to start\nPlease check the logs"); Notifyinstall.ShowDialog(); Notifyinstall.Dispose(); // Wait for the notification to show await Task.Delay(1000); this.Close(); await Task.Delay(1000); return false; } } catch (Exception ex) { AddLog("HSD Failed to start"); AddLog(ex.Message); this.Close(); await Task.Delay(1000); } if (splash) ss.CloseSplash(); this.Enabled = true; this.Visible = true; return true; } private async Task GetAddress() { string content = "{\"account\":\"default\"}"; string path = "wallet/" + Account + "/address"; string APIresponse = await APIPost(path, true, content); if (APIresponse == "Error") { AddLog("GetAddress Error"); return "Error"; } try { JObject resp = JObject.Parse(APIresponse); return resp["address"].ToString(); } catch { return "Error"; } } private async void GetTXHistory() { // Get height and progress String APIresponse = await APIGet("", false); JObject resp = JObject.Parse(APIresponse); JObject chain = JObject.Parse(resp["chain"].ToString()); labelHeight.Text = "Height: " + chain["height"].ToString(); decimal progress = Convert.ToDecimal(chain["progress"].ToString()); labelSyncPercent.Text = "Sync: " + decimal.Round(progress * 100, 2) + "%"; // Exit if set to 0 TXs if (UserSettings.ContainsKey("portfolio-tx")) { if (UserSettings["portfolio-tx"] == "0") return; } // Get Unconfirmed TX string path = "wallet/" + Account + "/tx/unconfirmed"; APIresponse = await APIGet(path, true); if (APIresponse == "Error") { AddLog("GetInfo Error"); return; } JArray pendingTxs = JArray.Parse(APIresponse); labelPendingCount.Text = "Unconfirmed TX: " + pendingTxs.Count.ToString(); // Check how many TX there are APIresponse = await APIGet("wallet/" + Account, true); JObject wallet = JObject.Parse(APIresponse); if (!wallet.ContainsKey("balance")) { AddLog("GetInfo Error"); AddLog(APIresponse); return; } JObject balance = JObject.Parse(wallet["balance"].ToString()); int TotalTX = Convert.ToInt32(balance["tx"].ToString()); int toGet = 10; if (UserSettings.ContainsKey("portfolio-tx")) toGet = Convert.ToInt32(UserSettings["portfolio-tx"]); if (toGet > TotalTX) toGet = TotalTX; int toSkip = TotalTX - toGet; // GET TXs if (WatchOnly) { APIresponse = await APIPost("", true, "{\"method\": \"listtransactions\",\"params\": [\"default\"," + toGet + "," + toSkip + ", true]}"); } else { APIresponse = await APIPost("", true, "{\"method\": \"listtransactions\",\"params\": [\"default\"," + toGet + "," + toSkip + "]}"); } if (APIresponse == "Error") { AddLog("GetInfo Error"); return; } JObject TXGET = JObject.Parse(APIresponse); // Check for error if (TXGET["error"].ToString() != "") { AddLog("GetInfo Error"); AddLog(APIresponse); return; } JArray txs = JArray.Parse(TXGET["result"].ToString()); if (toGet > txs.Count) toGet = txs.Count; // In case there are less TXs than expected (usually happens when the get TX's fails) Control[] tmpControls = new Control[toGet]; for (int i = 0; i < toGet; i++) { // Get last tx JObject tx = JObject.Parse(await APIGet("wallet/" + Account + "/tx/" + txs[toGet - i - 1]["txid"].ToString(), true)); string hash = tx["hash"].ToString(); string date = tx["mdate"].ToString(); date = DateTime.Parse(date).ToShortDateString(); Panel tmpPanel = new Panel(); tmpPanel.Width = groupBoxTransactions.Width - SystemInformation.VerticalScrollBarWidth - 20; tmpPanel.Height = 50; tmpPanel.Location = new Point(5, (i * 55)); tmpPanel.BorderStyle = BorderStyle.FixedSingle; tmpPanel.BackColor = ColorTranslator.FromHtml(Theme["background-alt"]); tmpPanel.ForeColor = ColorTranslator.FromHtml(Theme["foreground-alt"]); tmpPanel.Controls.Add( new Label() { Text = "Date: " + date, Location = new Point(10, 5) } ); int confirmations = Convert.ToInt32(tx["confirmations"].ToString()); if (UserSettings.ContainsKey("confirmations")) { if (confirmations < Convert.ToInt32(UserSettings["confirmations"])) { Label txPending = new Label() { Text = "Pending", Location = new Point(100, 5) }; tmpPanel.Controls.Add(txPending); txPending.BringToFront(); } } Label labelHash = new Label() { Text = "Hash: " + hash.Substring(0, 10) + "..." + hash.Substring(hash.Length - 10), AutoSize = true, Location = new Point(10, 25) }; tmpPanel.Controls.Add(labelHash); JArray inputs = JArray.Parse(tx["inputs"].ToString()); JArray outputs = JArray.Parse(tx["outputs"].ToString()); int inputCount = inputs.Count; int outputCount = outputs.Count; decimal costHNS = decimal.Parse(txs[toGet - i - 1]["amount"].ToString()); string cost = ""; if (costHNS < 0) { cost = "Spent: " + (costHNS * -1).ToString() + " HNS"; } else if (costHNS > 0) { cost = "Received: " + costHNS.ToString() + " HNS"; } Label labelInputOutput = new Label() { Text = "Inputs: " + inputCount + " Outputs: " + outputCount + "\n" + cost, AutoSize = true, Location = new Point(300, 5) }; tmpPanel.Controls.Add(labelInputOutput); tmpPanel.Click += (sender, e) => { TXForm txForm = new TXForm(this, hash); txForm.Show(); }; foreach (Control c in tmpPanel.Controls) { c.Click += (sender, e) => { TXForm txForm = new TXForm(this, hash); txForm.Show(); }; } tmpControls[i] = tmpPanel; } groupBoxTransactions.Controls.Clear(); Panel txPanel = new Panel(); txPanel.Width = groupBoxTransactions.Width - SystemInformation.VerticalScrollBarWidth; txPanel.Controls.AddRange(tmpControls); txPanel.AutoScroll = true; txPanel.Dock = DockStyle.Fill; groupBoxTransactions.Controls.Add(txPanel); } private async Task GetFee() { // This doesn't work try { string response = await APIPost("", false, "{\"method\": \"estimatefee\",\"params\": [ 3 ]}"); JObject resp = JObject.Parse(response); string result = resp["result"].ToString(); decimal fee = decimal.Parse(result); if (fee < 0.001m) fee = 1; return fee.ToString(); } catch { AddLog("GetFee Error"); return "1"; } } public async Task ValidAddress(string address) { string output = await APIPost("", false, "{\"method\": \"validateaddress\",\"params\": [ \"" + address + "\" ]}"); JObject APIresp = JObject.Parse(output); JObject result = JObject.Parse(APIresp["result"].ToString()); if (result["isvalid"].ToString() == "True") return true; else return false; } #endregion #region Timers private void timerNodeStatus_Tick(object sender, EventArgs e) { NodeStatus(); } #endregion #region Nav private void buttonMultiSign_Click(object sender, EventArgs e) { ImportTXForm importTXForm = new ImportTXForm(this); importTXForm.ShowDialog(); importTXForm.Dispose(); } private async void PortfolioPanel_Click(object sender, EventArgs e) { hidePages(); panelPortfolio.Show(); await UpdateBalance(); GetTXHistory(); labelBalance.Text = "Available: " + Balance.ToString() + " HNS"; labelLocked.Text = "Locked: " + Balance_Locked.ToString() + " HNS*"; labelBalanceTotal.Text = "Total: " + (Balance + Balance_Locked).ToString() + " HNS"; if (Theme.ContainsKey("selected-bg") && Theme.ContainsKey("selected-fg")) { buttonNavPortfolio.BackColor = ColorTranslator.FromHtml(Theme["selected-bg"]); buttonNavPortfolio.ForeColor = ColorTranslator.FromHtml(Theme["selected-fg"]); } groupBoxTransactions.Height = panelPortfolio.Height - groupBoxbalance.Height - 10; } private async void SendPanel_Click(object sender, EventArgs e) { hidePages(); panelSend.Show(); if (Theme.ContainsKey("selected-bg") && Theme.ContainsKey("selected-fg")) { buttonNavSend.BackColor = ColorTranslator.FromHtml(Theme["selected-bg"]); buttonNavSend.ForeColor = ColorTranslator.FromHtml(Theme["selected-fg"]); } if (Theme.ContainsKey("error")) { labelSendingError.ForeColor = ColorTranslator.FromHtml(Theme["error"]); } labelSendPrompt.Left = (panelSend.Width - labelSendPrompt.Width) / 2; buttonSendHNS.Left = (panelSend.Width - buttonSendHNS.Width) / 2; labelSendingTo.Left = (panelSend.Width - labelSendingTo.Width - textBoxSendingTo.Width) / 2; labelSendingAmount.Left = labelSendingTo.Left; textBoxSendingTo.Left = labelSendingTo.Left + labelSendingTo.Width + 10; textBoxSendingAmount.Left = textBoxSendingTo.Left; labelSendingMax.Left = labelSendingTo.Left; labelSendingError.Left = textBoxSendingTo.Left + textBoxSendingTo.Width + 10; labelSendingFee.Left = labelSendingTo.Left; buttonSendMax.Left = textBoxSendingAmount.Left + textBoxSendingAmount.Width - buttonSendMax.Width; checkBoxSendSubFee.Left = labelSendingTo.Left; labelSendingMax.Text = "Max: " + Balance.ToString() + " HNS"; textBoxSendingTo.Focus(); string fee = await GetFee(); labelSendingFee.Text = "Est. Fee: " + fee + " HNS"; labelSendingError.Hide(); } private async void ReceivePanel_Click(object sender, EventArgs e) { hidePages(); panelRecieve.Show(); if (Theme.ContainsKey("selected-bg") && Theme.ContainsKey("selected-fg")) { buttonNavReceive.BackColor = ColorTranslator.FromHtml(Theme["selected-bg"]); buttonNavReceive.ForeColor = ColorTranslator.FromHtml(Theme["selected-fg"]); } labelReceive1.Left = (panelRecieve.Width - labelReceive1.Width) / 2; labelReceive2.Left = (panelRecieve.Width - labelReceive2.Width) / 2; textBoxReceiveAddress.Left = (panelRecieve.Width - textBoxReceiveAddress.Width) / 2; string address = await GetAddress(); textBoxReceiveAddress.Text = address; textBoxReceiveAddress.TextAlign = HorizontalAlignment.Center; Size size = TextRenderer.MeasureText(textBoxReceiveAddress.Text, textBoxReceiveAddress.Font); textBoxReceiveAddress.Width = size.Width + 10; textBoxReceiveAddress.Left = (panelRecieve.Width - textBoxReceiveAddress.Width) / 2; QRCodeGenerator qrcode = new QRCodeGenerator(); QRCodeData qrData = qrcode.CreateQrCode(textBoxReceiveAddress.Text, QRCodeGenerator.ECCLevel.Q); QRCode qrCode = new QRCode(qrData); pictureBoxReceiveQR.Image = qrCode.GetGraphic(20, Theme["foreground"], Theme["background"]); pictureBoxReceiveQR.SizeMode = PictureBoxSizeMode.Zoom; pictureBoxReceiveQR.Width = panelRecieve.Width / 3; pictureBoxReceiveQR.Left = (panelRecieve.Width - pictureBoxReceiveQR.Width) / 2; } private void buttonNavDomains_Click(object sender, EventArgs e) { hidePages(); panelDomains.Show(); if (Theme.ContainsKey("selected-bg") && Theme.ContainsKey("selected-fg")) { buttonNavDomains.BackColor = ColorTranslator.FromHtml(Theme["selected-bg"]); buttonNavDomains.ForeColor = ColorTranslator.FromHtml(Theme["selected-fg"]); } textBoxDomainSearch.Focus(); groupBoxDomains.Width = panelDomains.Width - 20; groupBoxDomains.Left = 10; groupBoxDomains.Height = panelDomains.Height - groupBoxDomains.Top - 10; comboBoxDomainSort.SelectedIndex = 0; UpdateDomains(); } private void hidePages() { panelSend.Hide(); panelPortfolio.Hide(); panelRecieve.Hide(); panelDomains.Hide(); panelSettings.Hide(); buttonNavPortfolio.BackColor = ColorTranslator.FromHtml(Theme["background"]); buttonNavPortfolio.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); buttonNavSend.BackColor = ColorTranslator.FromHtml(Theme["background"]); buttonNavSend.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); buttonNavReceive.BackColor = ColorTranslator.FromHtml(Theme["background"]); buttonNavReceive.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); buttonNavDomains.BackColor = ColorTranslator.FromHtml(Theme["background"]); buttonNavDomains.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); buttonNavSettings.BackColor = ColorTranslator.FromHtml(Theme["background"]); buttonNavSettings.ForeColor = ColorTranslator.FromHtml(Theme["foreground"]); } private void buttonNavSettings_Click(object sender, EventArgs e) { hidePages(); if (Theme.ContainsKey("selected-bg") && Theme.ContainsKey("selected-fg")) { buttonNavSettings.BackColor = ColorTranslator.FromHtml(Theme["selected-bg"]); buttonNavSettings.ForeColor = ColorTranslator.FromHtml(Theme["selected-fg"]); } panelSettings.Show(); panelSettings.Dock = DockStyle.Fill; buttonSettingsSave.Top = panelSettings.Height - buttonSettingsSave.Height - 10; labelSettingsSaved.Top = buttonSettingsSave.Top + 10; textBoxExTX.Text = UserSettings["explorer-tx"]; textBoxExAddr.Text = UserSettings["explorer-addr"]; textBoxExBlock.Text = UserSettings["explorer-block"]; textBoxExName.Text = UserSettings["explorer-domain"]; numericUpDownConfirmations.Value = int.Parse(UserSettings["confirmations"]); numericUpDownTXCount.Value = int.Parse(UserSettings["portfolio-tx"]); labelSettingsSaved.Hide(); } #endregion #region Send // Store TLSA hash public string TLSA { get; set; } public async Task HIP02Lookup(string domain) { try { IPAddress iPAddress = null; TLSA = ""; // Create an instance of LookupClient using the custom options string ip = "127.0.0.1"; int port = 5350; if (UserSettings.ContainsKey("hip-02-ip")) { ip = UserSettings["hip-02-ip"]; } if (UserSettings.ContainsKey("hip-02-port")) { port = int.Parse(UserSettings["hip-02-port"]); } else if (!HSD) { string bobPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData) + "\\Bob\\hsd_data"; if (Directory.Exists(bobPath)) { port = 9892; } } NameServer nameServer = new NameServer(IPAddress.Parse(ip), port); var options = new LookupClientOptions(nameServer); options.EnableAuditTrail = true; options.UseTcpOnly = true; options.Recursion = true; options.UseCache = false; options.RequestDnsSecRecords = true; options.Timeout = httpClient.Timeout; var client = new LookupClient(options); // Perform the DNS lookup for the specified domain using DNSSec var result = client.Query(domain, QueryType.A); // Display the DNS lookup results foreach (var record in result.Answers.OfType()) { iPAddress = record.Address; } if (iPAddress == null) { labelSendingError.Show(); labelSendingError.Text = "HIP-02 lookup failed"; AddLog("No IP found"); return "ERROR"; } // Get TLSA record var resultTLSA = client.Query("_443._tcp." + domain, QueryType.TLSA); foreach (var record in resultTLSA.Answers.OfType()) { TLSA = record.CertificateAssociationDataAsString; } string url = "https://" + iPAddress.ToString() + "/.well-known/wallets/HNS"; var handler = new HttpClientHandler(); handler.ServerCertificateCustomValidationCallback = ValidateServerCertificate; // Create an instance of HttpClient with the custom handler using (var httpclient = new HttpClient(handler)) { httpclient.DefaultRequestHeaders.Add("Host", domain); // Send a GET request to the specified URL HttpResponseMessage response = httpclient.GetAsync(url).Result; if (response.StatusCode != HttpStatusCode.OK) { labelSendingError.Show(); labelSendingError.Text = "HIP-02 lookup failed"; AddLog("HTTPS get failed"); AddLog(response.Content.ReadAsStringAsync().Result); return "ERROR"; } // Response string address = response.Content.ReadAsStringAsync().Result; address = address.Trim(); if (await ValidAddress(address)) { return address; } else { AddLog("Invalid Address\n" + address); return "ERROR"; } } } catch (Exception ex) { AddLog("HIP-02 lookup error"); AddLog(ex.Message); return "ERROR"; } } private async void textBoxSendingTo_Leave(object sender, EventArgs e) { labelSendingError.Hide(); labelHIPArrow.Hide(); labelSendingHIPAddress.Hide(); if (textBoxSendingTo.Text == "") return; if (textBoxSendingTo.Text.Substring(0, 1) == "@") { string domain = textBoxSendingTo.Text.Substring(1); string address = await HIP02Lookup(domain); if (address == "ERROR") { labelSendingError.Show(); labelSendingError.Text = "HIP-02 lookup failed"; } else { labelSendingHIPAddress.Text = address; labelSendingHIPAddress.Show(); labelHIPArrow.Show(); } } else { try { bool valid = await ValidAddress(textBoxSendingTo.Text); if (valid) { labelSendingError.Hide(); labelSendingError.Text = ""; } else { labelSendingError.Show(); labelSendingError.Text = "Invalid Address"; } } catch (Exception ex) { labelSendingError.Show(); labelSendingError.Text = ex.Message; } } } /// /// DANE validation. Used verifying Handshake domains /// /// certificate to check /// True if DANE passes, Else False public bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) { X509Certificate2 cert2 = new X509Certificate2(certificate); var rsaPublicKey = (RSA)cert2.PublicKey.Key; using (var sha256 = SHA256.Create()) { byte[] publicKeyBytes = rsaPublicKey.ExportSubjectPublicKeyInfo(); byte[] publicKeyHash = sha256.ComputeHash(publicKeyBytes); string hexFingerprint = ByteArrayToHexString(publicKeyHash); if (hexFingerprint == TLSA) return true; else { AddLog("TLSA mismatch"); return false; } } } /// /// Convert byte array to hex string /// /// Byte array to convert /// String of converted byte array static string ByteArrayToHexString(byte[] bytes) { StringBuilder hex = new StringBuilder(bytes.Length * 2); foreach (byte b in bytes) { hex.AppendFormat("{0:X2}", b); } return hex.ToString(); } private void textBoxSendingAmount_Leave(object sender, EventArgs e) { decimal amount = 0; if (decimal.TryParse(textBoxSendingAmount.Text, out amount)) { labelSendingError.Hide(); labelSendingError.Text = ""; } else { labelSendingError.Show(); labelSendingError.Text = "Invalid Amount"; } } private async void buttonSendMax_Click(object sender, EventArgs e) { string fee = await GetFee(); decimal feeDecimal = decimal.Parse(fee); textBoxSendingAmount.Text = (Balance - feeDecimal).ToString(); } private async void buttonSendHNS_Click(object sender, EventArgs e) { try { string address = textBoxSendingTo.Text; if (labelHIPArrow.Visible) { address = labelSendingHIPAddress.Text; } bool valid = await ValidAddress(address); if (!valid) { labelSendingError.Show(); labelSendingError.Text = "Invalid Address"; return; } decimal amount = 0; if (!decimal.TryParse(textBoxSendingAmount.Text, out amount)) { labelSendingError.Show(); labelSendingError.Text += " Invalid Amount"; return; } string feeString = await GetFee(); decimal fee = decimal.Parse(feeString); string subtractFee = "false"; if (checkBoxSendSubFee.Checked) subtractFee = "true"; else if (amount > (Balance - fee)) { labelSendingError.Show(); labelSendingError.Text += " Insufficient Funds"; return; } if (!WatchOnly && !multiSig) { string content = "{\"method\": \"sendtoaddress\",\"params\": [ \"" + address + "\", " + amount.ToString() + ", \"\", \"\", " + subtractFee + " ]}"; string output = await APIPost("", true, content); JObject APIresp = JObject.Parse(output); if (APIresp["error"].ToString() != "") { AddLog("Failed:"); AddLog(APIresp.ToString()); JObject error = JObject.Parse(APIresp["error"].ToString()); string ErrorMessage = error["message"].ToString(); NotifyForm notify = new NotifyForm("Error Transaction Failed\n" + ErrorMessage); notify.ShowDialog(); return; } string hash = APIresp["result"].ToString(); string link = UserSettings["explorer-tx"] + hash; NotifyForm notifySuccess = new NotifyForm("Transaction Sent\nThis transaction could take up to 20 minutes to mine", "Explorer", link); notifySuccess.ShowDialog(); textBoxSendingTo.Text = ""; textBoxSendingAmount.Text = ""; labelSendingError.Hide(); labelSendingError.Text = ""; buttonNavPortfolio.PerformClick(); } else // Cold or multisig wallet signing { if (multiSig) { if (!WatchOnly) { string content = "{\"method\": \"createsendtoaddress\",\"params\": [ \"" + address + "\", " + amount.ToString() + ", \"\", \"\", " + subtractFee + " ]}"; string output = await APIPost("", true, content); JObject APIresp = JObject.Parse(output); if (APIresp["error"].ToString() != "") { AddLog("Failed:"); AddLog(APIresp.ToString()); JObject error = JObject.Parse(APIresp["error"].ToString()); string ErrorMessage = error["message"].ToString(); NotifyForm notify = new NotifyForm("Error Transaction Failed\n" + ErrorMessage); notify.ShowDialog(); return; } JObject result = JObject.Parse(APIresp["result"].ToString()); string hex = result["hex"].ToString(); content = "{\"tx\": \"" + hex + "\",\"passphrase\":\"" + Password + "\"}"; output = await APIPost("wallet/" + Account + "/sign", true, content); if (!output.Contains("hex")) { AddLog("Failed:"); AddLog(APIresp.ToString()); NotifyForm notify = new NotifyForm("Error Transaction Failed\nCheck the logs"); notify.ShowDialog(); return; } ExportTransaction(output); } else { string content = "{\"method\": \"createsendtoaddress\",\"params\": [ \"" + address + "\", " + amount.ToString() + ", \"\", \"\", " + subtractFee + " ]}"; string output = await APIPost("", true, content); JObject APIresp = JObject.Parse(output); if (APIresp["error"].ToString() != "") { AddLog("Failed:"); AddLog(APIresp.ToString()); JObject error = JObject.Parse(APIresp["error"].ToString()); string ErrorMessage = error["message"].ToString(); NotifyForm notify = new NotifyForm("Error Transaction Failed\n" + ErrorMessage); notify.ShowDialog(); return; } string signed = signRaw(output, new string[0]); ExportTransaction(signed); } } else // Cold wallet non multisig { AddLog("Sending CW " + amount.ToString() + " HNS to " + address); if (!Directory.Exists(dir + "hsd-ledger")) { if (CheckNodeInstalled() == false) { AddLog("Node not installed"); NotifyForm notify1 = new NotifyForm("Node not installed\nPlease install it to use Ledger"); notify1.ShowDialog(); notify1.Dispose(); return; } AddLog("Installing hsd-ledger"); // Try to install hsd-ledger try { NotifyForm Notifyinstall = new NotifyForm("Installing hsd-ledger\nThis may take a few minutes\nDo not close FireWallet", false); Notifyinstall.Show(); // Wait for the notification to show await Task.Delay(1000); string repositoryUrl = "https://github.com/handshake-org/hsd-ledger.git"; string destinationPath = dir + "hsd-ledger"; CloneRepository(repositoryUrl, destinationPath); Notifyinstall.CloseNotification(); Notifyinstall.Dispose(); } catch (Exception ex) { NotifyForm notifyError = new NotifyForm("Error installing hsd-ledger\n" + ex.Message); AddLog(ex.Message); notifyError.ShowDialog(); notifyError.Dispose(); return; } } NotifyForm notify = new NotifyForm("Please confirm the transaction on your Ledger device", false); notify.Show(); var proc = new Process(); proc.StartInfo.CreateNoWindow = true; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardError = true; proc.StartInfo.FileName = "node.exe"; proc.StartInfo.Arguments = dir + "hsd-ledger/bin/hsd-ledger sendtoaddress " + textBoxSendingTo.Text + " " + textBoxSendingAmount.Text + " --api-key " + NodeSettings["Key"] + " -w " + Account; var outputBuilder = new StringBuilder(); // Event handler for capturing output data proc.OutputDataReceived += (sender, args) => { if (!string.IsNullOrEmpty(args.Data)) { outputBuilder.AppendLine(args.Data); } }; proc.Start(); proc.BeginOutputReadLine(); proc.WaitForExit(); notify.CloseNotification(); notify.Dispose(); string output = outputBuilder.ToString(); AddLog(output); if (output.Contains("Submitted TXID")) { string hash = output.Substring(output.IndexOf("Submitted TXID") + 16, 64); string link = UserSettings["explorer-tx"] + hash; NotifyForm notifySuccess = new NotifyForm("Transaction Sent\nThis transaction could take up to 20 minutes to mine", "Explorer", link); notifySuccess.ShowDialog(); textBoxSendingTo.Text = ""; textBoxSendingAmount.Text = ""; buttonNavPortfolio.PerformClick(); } else { NotifyForm notifyError = new NotifyForm("Error Transaction Failed\nCheck logs for more details"); notifyError.ShowDialog(); notifyError.Dispose(); } } } } catch (Exception ex) { AddLog(ex.Message); labelSendingError.Show(); labelSendingError.Text = ex.Message; } } public void CloneRepository(string repositoryUrl, string destinationPath) { try { bool hideScreen = true; if (NodeSettings.ContainsKey("HideScreen")) { if (NodeSettings["HideScreen"].ToLower() == "false") { hideScreen = false; } } // Check if git is installed Process testInstalled = new Process(); testInstalled.StartInfo.FileName = "git"; testInstalled.StartInfo.Arguments = "-v"; testInstalled.StartInfo.RedirectStandardOutput = true; testInstalled.StartInfo.UseShellExecute = false; testInstalled.StartInfo.CreateNoWindow = true; testInstalled.Start(); string outputInstalled = testInstalled.StandardOutput.ReadToEnd(); testInstalled.WaitForExit(); if (!outputInstalled.Contains("git version")) { AddLog("Git is not installed"); NotifyForm notifyForm = new NotifyForm("Git is not installed\nPlease install it to install HSD dependencies", "Install", "https://git-scm.com/download/win"); notifyForm.ShowDialog(); notifyForm.Dispose(); this.Close(); Thread.Sleep(1000); return; } // Check if node installed testInstalled = new Process(); testInstalled.StartInfo.FileName = "node"; testInstalled.StartInfo.Arguments = "-v"; testInstalled.StartInfo.RedirectStandardOutput = true; testInstalled.StartInfo.UseShellExecute = false; testInstalled.StartInfo.CreateNoWindow = true; testInstalled.Start(); outputInstalled = testInstalled.StandardOutput.ReadToEnd(); testInstalled.WaitForExit(); if (!outputInstalled.Contains("v")) { AddLog("Node is not installed"); NotifyForm notifyForm = new NotifyForm("Node is not installed\nPlease install it to install HSD dependencies", "Install", "https://nodejs.org/en/download"); notifyForm.ShowDialog(); notifyForm.Dispose(); this.Close(); Thread.Sleep(1000); return; } // Check if npm installed testInstalled = new Process(); testInstalled.StartInfo.FileName = "cmd.exe"; testInstalled.StartInfo.Arguments = "npm -v"; testInstalled.StartInfo.RedirectStandardOutput = true; testInstalled.StartInfo.UseShellExecute = false; testInstalled.StartInfo.CreateNoWindow = false; testInstalled.Start(); // Wait 3 seconds and then kill Thread.Sleep(3000); testInstalled.Kill(); outputInstalled = testInstalled.StandardOutput.ReadToEnd(); testInstalled.WaitForExit(); if (Regex.IsMatch(outputInstalled, @"^\d+\.\d+\.\d+$")) { AddLog("NPM is not installed"); AddLog(outputInstalled); NotifyForm notifyForm = new NotifyForm("NPM is not installed\nPlease install it to install HSD dependencies", "Install", "https://docs.npmjs.com/downloading-and-installing-node-js-and-npm"); notifyForm.ShowDialog(); notifyForm.Dispose(); this.Close(); Thread.Sleep(1000); return; } AddLog("Prerequisites installed"); ProcessStartInfo startInfo = new ProcessStartInfo(); startInfo.FileName = "git"; startInfo.Arguments = $"clone {repositoryUrl} {destinationPath}"; if (repositoryUrl == "https://github.com/handshake-org/hsd.git") { startInfo.Arguments = $"clone --depth 1 --branch latest {repositoryUrl} {destinationPath}"; } startInfo.RedirectStandardOutput = true; startInfo.UseShellExecute = false; startInfo.CreateNoWindow = hideScreen; Process process = new Process(); process.StartInfo = startInfo; process.Start(); string output = process.StandardOutput.ReadToEnd(); process.WaitForExit(); while (!process.HasExited) { output += process.StandardOutput.ReadToEnd(); } var psiNpmRunDist = new ProcessStartInfo { FileName = "cmd", RedirectStandardInput = true, WorkingDirectory = destinationPath, CreateNoWindow = hideScreen }; var pNpmRunDist = Process.Start(psiNpmRunDist); pNpmRunDist.StandardInput.WriteLine("npm install & exit"); pNpmRunDist.WaitForExit(); if (repositoryUrl == "https://github.com/handshake-org/hsd-ledger.git") { // Replace /bin/hsd-ledger with /bin/hsd-ledger from // https://raw.githubusercontent.com/Nathanwoodburn/FireWallet/master/hsd-ledger // This version of hsd-ledger has the sendraw transaction function added which is needed for batching string sourcePath = destinationPath + "\\bin\\hsd-ledger"; File.Delete(sourcePath); // Download the new hsd-ledger WebClient downloader = new WebClient(); downloader.DownloadFile("https://raw.githubusercontent.com/Nathanwoodburn/FireWallet/master/hsd-ledger", sourcePath); } } catch (Exception ex) { AddLog("Git/NPM Install FAILED"); AddLog(ex.Message); if (ex.Message.Contains("to start process 'git'")) { NotifyForm notifyForm = new NotifyForm("Git needs to be installed\nCheck logs for more details"); notifyForm.ShowDialog(); notifyForm.Dispose(); } else if (ex.Message.Contains("to start process 'node'")) { NotifyForm notifyForm = new NotifyForm("Node needs to be installed\nCheck logs for more details"); notifyForm.ShowDialog(); notifyForm.Dispose(); } else if (ex.Message.Contains("to start process 'npm'")) { NotifyForm notifyForm = new NotifyForm("NPM needs to be installed\nCheck logs for more details"); notifyForm.ShowDialog(); notifyForm.Dispose(); } else { NotifyForm notifyForm = new NotifyForm("Git/NPM Install FAILED\nCheck logs for more details"); notifyForm.ShowDialog(); notifyForm.Dispose(); } this.Close(); } } public bool CheckNodeInstalled() { try { // Create a new process to execute the 'node' command Process process = new Process(); process.StartInfo.FileName = "node"; process.StartInfo.Arguments = "--version"; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.UseShellExecute = false; process.StartInfo.CreateNoWindow = true; // Start the process and read the output process.Start(); string output = process.StandardOutput.ReadToEnd(); // Wait for the process to exit process.WaitForExit(); // Check if the output contains a version number return !string.IsNullOrEmpty(output); } catch (Exception) { // An exception occurred, indicating that 'node' is not installed or accessible return false; } } #endregion #region Receive private void textBoxRecieveAddress_Click(object sender, EventArgs e) { Clipboard.SetText(textBoxReceiveAddress.Text); labelReceive2.Text = "Copied to clipboard"; labelReceive2.Left = (panelRecieve.Width - labelReceive2.Width) / 2; } private async void buttonAddressVerify_Click(object sender, EventArgs e) { var proc = new Process(); proc.StartInfo.CreateNoWindow = true; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardError = true; proc.StartInfo.FileName = "node.exe"; proc.StartInfo.Arguments = dir + "hsd-ledger/bin/hsd-ledger createaddress --api-key " + NodeSettings["Key"] + " -w " + Account; proc.Start(); // Wait 1 sec await Task.Delay(1000); // Get the output into a string string result = proc.StandardOutput.ReadLine(); try { result = result.Replace("Verify address on Ledger device: ", ""); } catch { } AddLog(result); textBoxReceiveAddress.Text = result; Size size = TextRenderer.MeasureText(textBoxReceiveAddress.Text, textBoxReceiveAddress.Font); textBoxReceiveAddress.Width = size.Width + 10; textBoxReceiveAddress.Left = (panelRecieve.Width - textBoxReceiveAddress.Width) / 2; NotifyForm notify = new NotifyForm("Please confirm the address on your Ledger device", false); notify.Show(); // Handle events until process exits while (!proc.HasExited) { // Check for cancellation if (proc.WaitForExit(100)) break; Application.DoEvents(); } notify.CloseNotification(); notify.Dispose(); } #endregion #region Domains public string[] Domains { get; set; } public string[] DomainsRenewable { get; set; } private async void UpdateDomains() { string response = await APIGet("wallet/" + Account + "/name?own=true", true); try { JArray names = JArray.Parse(response); Domains = new string[names.Count]; DomainsRenewable = new string[names.Count]; int i = 0; int renewable = 0; panelDomainList.Controls.Clear(); // Sort the domains switch (comboBoxDomainSort.Text) { case "Default": break; case "Alphabetical": names = new JArray(names.OrderBy(obj => (string)obj["name"])); break; case "Expiring": names = new JArray(names.OrderBy(obj => { JToken daysUntilExpireToken = obj["stats"]?["daysUntilExpire"]; return (int)(daysUntilExpireToken ?? int.MaxValue); })); break; case "Value": // Sort by most valuable first names = new JArray(names.OrderByDescending(obj => { JToken valueToken = obj?["value"]; return (int)(valueToken ?? 0); })); break; } foreach (JObject name in names) { Domains[i] = name["name"].ToString(); Panel domainTMP = new Panel(); domainTMP.Width = panelDomainList.Width - 20 - SystemInformation.VerticalScrollBarWidth; domainTMP.Height = 30; domainTMP.Top = 30 * (i); domainTMP.Left = 10; domainTMP.BorderStyle = BorderStyle.FixedSingle; Label domainName = new Label(); domainName.Text = Domains[i]; domainName.Top = 5; domainName.Left = 5; domainName.AutoSize = true; domainTMP.Controls.Add(domainName); if (!name.ContainsKey("stats")) { AddLog("Domain " + Domains[i] + " does not have stats"); continue; } Label expiry = new Label(); JObject stats = JObject.Parse(name["stats"].ToString()); if (stats.ContainsKey("daysUntilExpire")) { expiry.Text = "Expires: " + stats["daysUntilExpire"].ToString() + " days"; expiry.Top = 5; expiry.AutoSize = true; expiry.Left = domainTMP.Width - expiry.Width - 100; domainTMP.Controls.Add(expiry); // Add to domains renewable DomainsRenewable[renewable] = Domains[i]; renewable++; } else { expiry.Text = "Expires: Not Registered yet"; expiry.Top = 5; expiry.AutoSize = true; expiry.Left = domainTMP.Width - expiry.Width - 100; domainTMP.Controls.Add(expiry); } // On Click open domain domainTMP.Click += new EventHandler((sender, e) => { DomainForm domainForm = new DomainForm(this, name["name"].ToString(), UserSettings["explorer-tx"], UserSettings["explorer-domain"]); domainForm.Show(); }); foreach (Control c in domainTMP.Controls) { c.Click += new EventHandler((sender, e) => { DomainForm domainForm = new DomainForm(this, name["name"].ToString(), UserSettings["explorer-tx"], UserSettings["explorer-domain"]); domainForm.Show(); }); } panelDomainList.Controls.Add(domainTMP); i++; } } catch (Exception ex) { AddLog("Error getting domains"); AddLog(ex.Message); } } private void textBoxDomainSearch_KeyDown(object sender, KeyEventArgs e) { if (e.KeyValue == 13) { textBoxDomainSearch.Text = textBoxDomainSearch.Text.Trim().ToLower(); e.SuppressKeyPress = true; DomainForm domainForm = new DomainForm(this, textBoxDomainSearch.Text, UserSettings["explorer-tx"], UserSettings["explorer-domain"]); domainForm.Show(); } } private void textBoxDomainSearch_TextChanged(object sender, EventArgs e) { string domainSearch = textBoxDomainSearch.Text; domainSearch = Regex.Replace(textBoxDomainSearch.Text, "[^a-zA-Z0-9-_]", ""); textBoxDomainSearch.Text = domainSearch; } private void export_Click(object sender, EventArgs e) { SaveFileDialog saveFileDialog = new SaveFileDialog(); saveFileDialog.Filter = "CSV file (*.csv)|*.csv"; saveFileDialog.Title = "Export"; if (saveFileDialog.ShowDialog() == DialogResult.OK) { StreamWriter sw = new StreamWriter(saveFileDialog.FileName); foreach (string domain in DomainsRenewable) { if (domain == null) break; sw.WriteLine(domain); } sw.Dispose(); } } private void buttonRenewAll_Click(object sender, EventArgs e) { if (DomainsRenewable == null) { NotifyForm notifyForm = new NotifyForm("No renewable domains found"); notifyForm.ShowDialog(); notifyForm.Dispose(); return; } foreach (string domain in DomainsRenewable) { if (domain == null) break; AddBatch(domain, "RENEW"); } } private void comboBoxDomainSort_DropDownClosed(object sender, EventArgs e) { UpdateDomains(); } #endregion #region Batching public void AddBatch(string domain, string operation) { if (operation == "BID") return; if (!BatchMode) { BatchForm = new BatchForm(this); BatchForm.Show(); BatchMode = true; } BatchForm.AddBatch(domain, operation); BatchForm.UpdateTheme(); } public void AddBatch(string domain, string operation, decimal bid, decimal lockup) { if (operation != "BID") return; if (!BatchMode) { BatchForm = new BatchForm(this); BatchForm.Show(); BatchMode = true; } BatchForm.AddBatch(domain, operation, bid, lockup); BatchForm.UpdateTheme(); } public void AddBatch(string domain, string operation, DNS[] updateRecords) { if (!BatchMode) { BatchForm = new BatchForm(this); BatchForm.Show(); BatchMode = true; } BatchForm.AddBatch(domain, operation, updateRecords); BatchForm.UpdateTheme(); } public void AddBatch(string domain, string operation, string address) { if (!BatchMode) { BatchForm = new BatchForm(this); BatchForm.Show(); BatchMode = true; } BatchForm.AddBatch(domain, operation, address); BatchForm.UpdateTheme(); } public void FinishBatch() { BatchMode = false; BatchForm.Dispose(); } private void buttonBatch_Click(object sender, EventArgs e) { if (!BatchMode) { BatchForm = new BatchForm(this); BatchForm.Show(); BatchMode = true; } else BatchForm.Focus(); } #endregion #region SettingsPage private void buttonSettingsSave_Click(object sender, EventArgs e) { StreamWriter sw = new StreamWriter(dir + "settings.txt"); sw.WriteLine("explorer-tx: " + textBoxExTX.Text); sw.WriteLine("explorer-addr: " + textBoxExAddr.Text); sw.WriteLine("explorer-block: " + textBoxExBlock.Text); sw.WriteLine("explorer-domain: " + textBoxExName.Text); sw.WriteLine("confirmations: " + numericUpDownConfirmations.Value); sw.WriteLine("portfolio-tx: " + numericUpDownTXCount.Value); sw.WriteLine("hide-splash: " + UserSettings["hide-splash"]); sw.Dispose(); LoadSettings(); labelSettingsSaved.Show(); } private async void buttonSeed_Click(object sender, EventArgs e) { string path = "wallet/" + Account + "/master"; string response = await APIGet(path, true); JObject resp = JObject.Parse(response); if (resp["encrypted"].ToString() == "False") { JObject mnemonic = JObject.Parse(resp["mnemonic"].ToString()); string phrase = mnemonic["phrase"].ToString(); NotifyForm notifyForm = new NotifyForm("Your seed phrase is:\n" + phrase, "Copy", phrase, true); notifyForm.ShowDialog(); notifyForm.Dispose(); } else { string algorithm = resp["algorithm"].ToString(); if (algorithm != "pbkdf2") { AddLog("Invalid algorithm"); AddLog(response); NotifyForm notifyForm = new NotifyForm("Invalid algorithm"); notifyForm.ShowDialog(); notifyForm.Dispose(); return; } try { string iv = resp["iv"].ToString(); string ciphertext = resp["ciphertext"].ToString(); string tmpn = resp["n"].ToString(); string tmpr = resp["r"].ToString(); string tmpp = resp["p"].ToString(); int n = int.Parse(tmpn); int p = int.Parse(tmpp); int iterations = n; } catch (Exception ex) { AddLog("Error decrypting seed"); AddLog(ex.ToString()); NotifyForm notifyForm = new NotifyForm("Error decrypting seed"); notifyForm.ShowDialog(); notifyForm.Dispose(); } } } private async void Rescan_Click(object sender, EventArgs e) { string content = "{\"height\": 0}"; string response = await APIPost("rescan", true, content); if (!response.Contains("true")) { AddLog("Error starting rescan"); AddLog(response); return; } AddLog("Starting rescan"); } #endregion #region Help Menu private void githubToolStripMenuItem_Click(object sender, EventArgs e) { // Open the GitHub page ProcessStartInfo psi = new ProcessStartInfo { FileName = "https://github.com/Nathanwoodburn/FireWallet/", UseShellExecute = true }; Process.Start(psi); } private void websiteToolStripMenuItem_Click(object sender, EventArgs e) { ProcessStartInfo psi = new ProcessStartInfo { FileName = "https://firewallet", UseShellExecute = true }; Process.Start(psi); } private void supportDiscordServerToolStripMenuItem_Click(object sender, EventArgs e) { ProcessStartInfo psi = new ProcessStartInfo { FileName = "https://l.woodburn.au/discord", UseShellExecute = true }; Process.Start(psi); } private void otherProjectsToolStripMenuItem_Click(object sender, EventArgs e) { ProcessStartInfo psi = new ProcessStartInfo { FileName = "https://nathan.woodburn.au/projects", UseShellExecute = true }; Process.Start(psi); } #endregion #region Multi public async Task ExportTransaction(string rawTX) { return await ExportTransaction(rawTX, new string[0]); } public async Task ExportTransaction(string rawTX, string[] domains) { JObject tx = JObject.Parse(rawTX); JObject toExport = new JObject(); toExport["version"] = 1; toExport["tx"] = tx["hex"]; JArray inputsParsed = new JArray(); JArray outputsParsed = new JArray(); JArray inputs = JArray.Parse(tx["inputs"].ToString()); JArray outputs = JArray.Parse(tx["outputs"].ToString()); Dictionary hashes = new Dictionary(); foreach (string domain in domains) { // sha3 hash of domain hashes.Add(CalculateSHA3Hash(domain), domain); } foreach (JObject input in inputs) { JObject coin = JObject.Parse(input["coin"].ToString()); JObject covenant = JObject.Parse(coin["covenant"].ToString()); string type = covenant["type"].ToString(); JObject data = new JObject(); if (type == "0") { inputsParsed.Add(data); } else { AddLog("Not supported yet"); return "Error"; } } foreach (JObject output in outputs) { JObject covenant = JObject.Parse(output["covenant"].ToString()); string type = covenant["type"].ToString(); JObject data = new JObject(); if (type == "0") { outputsParsed.Add(data); } else { string hash = covenant["items"][0].ToString(); if (hashes.ContainsKey(hash)) { data["name"] = hashes[hash]; } else { AddLog("Cannot find name for hash " + hash); return "Error"; } //data["name"]; outputsParsed.Add(data); } } JObject metadata = new JObject(); metadata["inputs"] = inputsParsed; metadata["outputs"] = outputsParsed; toExport["metadata"] = metadata; SaveFileDialog saveFileDialog = new SaveFileDialog { Filter = "JSON file (*.json)|*.json", Title = "Save transaction as JSON" }; if (saveFileDialog.ShowDialog() == DialogResult.OK) { StreamWriter sw = new StreamWriter(saveFileDialog.FileName); sw.Write(toExport.ToString()); sw.Dispose(); return toExport.ToString(); } else { return "Error"; } } public string signRaw(string tx, string[] domains) { string domainslist = string.Join(",", domains.Select(domain => "\\\"" + domain + "\\\"")); NotifyForm notify = new NotifyForm("Please confirm the transaction on your Ledger device", false); notify.Show(); var proc = new Process(); proc.StartInfo.CreateNoWindow = true; proc.StartInfo.RedirectStandardInput = true; proc.StartInfo.RedirectStandardOutput = true; proc.StartInfo.UseShellExecute = false; proc.StartInfo.RedirectStandardError = true; proc.StartInfo.FileName = "node.exe"; proc.StartInfo.WorkingDirectory = dir + "hsd-ledger/bin/"; string args = "hsd-ledger/bin/hsd-ledger signraw \"\"" + tx.Replace("\"", "\\\"").Trim() + "\"\" [" + domainslist + "] --api-key " + NodeSettings["Key"] + " -w " + Account; AddLog(args); proc.StartInfo.Arguments = dir + args; var outputBuilder = new StringBuilder(); // Event handler for capturing output data proc.OutputDataReceived += (sender, args) => { if (!string.IsNullOrEmpty(args.Data)) { outputBuilder.AppendLine(args.Data); } }; proc.Start(); proc.BeginOutputReadLine(); proc.WaitForExit(); notify.CloseNotification(); notify.Dispose(); string output = outputBuilder.ToString(); AddLog(output); if (output.Length > 2) { // Signed return output; } else { AddLog(args); AddLog(proc.StandardError.ReadToEnd()); NotifyForm notifyError = new NotifyForm("Error sign Failed\nCheck logs for more details"); notifyError.ShowDialog(); notifyError.Dispose(); return "Error"; } } static string CalculateSHA3Hash(string input) { var hashAlgorithm = new Org.BouncyCastle.Crypto.Digests.Sha3Digest(256); // Choose correct encoding based on your usecase byte[] inputBytes = Encoding.UTF8.GetBytes(input); hashAlgorithm.BlockUpdate(inputBytes, 0, inputBytes.Length); byte[] result = new byte[256 / 8]; hashAlgorithm.DoFinal(result, 0); string hashString = BitConverter.ToString(result); hashString = hashString.Replace("-", "").ToLowerInvariant(); return hashString; } private void buttonMultiSettings_Click(object sender, EventArgs e) { MultisigSettingsForm multisigSettingsForm = new MultisigSettingsForm(this); multisigSettingsForm.Show(); } #endregion } }