mirror of
https://github.com/Nathanwoodburn/FireWallet.git
synced 2024-11-10 09:18:15 +11:00
2626 lines
103 KiB
C#
2626 lines
103 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
using Newtonsoft.Json.Linq;
|
|
using Point = System.Drawing.Point;
|
|
using Size = System.Drawing.Size;
|
|
using QRCoder;
|
|
using System.Text.RegularExpressions;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using System.Net;
|
|
using DnsClient;
|
|
using DnsClient.Protocol;
|
|
using System.Security.Cryptography.X509Certificates;
|
|
using System.Net.Security;
|
|
|
|
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<string, string> NodeSettings { get; set; }
|
|
public Dictionary<string, string> UserSettings { get; set; }
|
|
public Dictionary<string, string> 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; }
|
|
#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<bool> 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<string, string>();
|
|
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<string, string>();
|
|
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<string, string>();
|
|
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<string, string> 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<bool> 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.Text = "Cold Wallet";
|
|
toolStripStatusLabelLedger.Visible = true;
|
|
buttonRevealAll.Visible = false;
|
|
buttonRedeemAll.Visible = false;
|
|
buttonSendAll.Visible = false;
|
|
}
|
|
else
|
|
{
|
|
WatchOnly = false;
|
|
toolStripStatusLabelLedger.Visible = false;
|
|
buttonRevealAll.Visible = true;
|
|
buttonRedeemAll.Visible = true;
|
|
buttonSendAll.Visible = true;
|
|
}
|
|
if (WatchOnly)
|
|
{
|
|
buttonAddressVerify.Visible = true;
|
|
}
|
|
else
|
|
{
|
|
buttonAddressVerify.Visible = false;
|
|
}
|
|
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);
|
|
AddLog(response);
|
|
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\"]]]}";
|
|
AddLog(content);
|
|
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\"]]]}";
|
|
AddLog(content);
|
|
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;
|
|
}
|
|
/// <summary>
|
|
/// Post to HSD API
|
|
/// </summary>
|
|
/// <param name="path">Path to post to</param>
|
|
/// <param name="wallet">Whether to use port 12039</param>
|
|
/// <param name="content">Content to post</param>
|
|
/// <returns></returns>
|
|
public async Task<string> 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";
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Get from HSD API
|
|
/// </summary>
|
|
/// <param name="path">Path to get</param>
|
|
/// <param name="wallet">Whether to use port 12039</param>
|
|
/// <returns></returns>
|
|
public async Task<string> 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<bool> 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<string> 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<string> 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<bool> 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 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<string> 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<ARecord>())
|
|
{
|
|
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<TlsaRecord>())
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// DANE validation. Used verifying Handshake domains
|
|
/// </summary>
|
|
/// <param name="certificate">certificate to check</param>
|
|
/// <returns>True if DANE passes, Else False</returns>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Convert byte array to hex string
|
|
/// </summary>
|
|
/// <param name="bytes">Byte array to convert</param>
|
|
/// <returns>String of converted byte array</returns>
|
|
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)
|
|
{
|
|
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());
|
|
NotifyForm notify = new NotifyForm("Error Transaction Failed");
|
|
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 wallet signing
|
|
{
|
|
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);
|
|
|
|
}
|
|
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);
|
|
|
|
}
|
|
public void AddBatch(string domain, string operation, DNS[] updateRecords)
|
|
{
|
|
if (!BatchMode)
|
|
{
|
|
BatchForm = new BatchForm(this);
|
|
BatchForm.Show();
|
|
BatchMode = true;
|
|
}
|
|
BatchForm.AddBatch(domain, operation, updateRecords);
|
|
}
|
|
public void AddBatch(string domain, string operation, string address)
|
|
{
|
|
if (!BatchMode)
|
|
{
|
|
BatchForm = new BatchForm(this);
|
|
BatchForm.Show();
|
|
BatchMode = true;
|
|
}
|
|
BatchForm.AddBatch(domain, operation, address);
|
|
}
|
|
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
|
|
|
|
|
|
}
|
|
} |