7 Commits

Author SHA1 Message Date
6218b337fb main: Added Yubikey device check and removed redundant code
- Added a check for the number of connected Yubikeys
- Removed redundant code that prompts user to insert Yubikey before unlocking.
2023-06-15 19:31:24 +10:00
79350570fd main: Added YubiKey
- Added a reference to the Yubico.YubiKey package in FireWallet.csproj

MainForm.cs: Added Yubikey login functionality
- Added code to use a connected Yubikey to encrypt and decrypt account passwords for login.

MainForm.Designer.cs: Added button for Yubikey settings
- Created a button for users to access the settings for using their Yubikey.
2023-06-15 19:08:55 +10:00
88c6b5afe0 README.md: Added instructions for using HIP-02
- Added instructions for using HIP-02 to send HNS to Handshake addresses or domains.
- To use HIP-02, a user needs to have an HSD resolver listening on port 5350.
- A domain must be prefixed with `@` to use HIP-02 (e.g., `@nathan.woodburn`).
2023-06-15 17:38:05 +10:00
f06bc5b711 mainForm.cs: Added domain sorting feature and updated UI
- Added comboBoxDomainSort_DropDownClosed method to update domains
- Added comboBoxDomainSort control to panelDomains
- Added labelDomainSort control to MainForm.Designer.cs
- Updated UpdateDomains method to sort domains based on selected option
2023-06-15 16:47:59 +10:00
42536e47bb README.md: Added a note about BID import syntax
- Added a note about the BID import syntax being BID,LOCKUP where LOCKUP is (BID+BLIND)
2023-06-15 16:43:51 +10:00
404a47ec79 batchForm.cs: Added try-catch block to handle file in use exceptions
- Added try-catch block to handle exceptions when importing batch
- Displayed error message and notification form if an exception occurs
2023-06-15 16:21:13 +10:00
c3abd0b4de README.md: Added ability to send HNS using HIP-02
- Added option to send HNS to Handshake addresses or domains
- Implemented [HIP-02](https://github.com/handshake-org/HIPs/blob/master/HIP-0002.md) for sending HNS
2023-06-15 14:07:26 +10:00
5 changed files with 339 additions and 70 deletions

View File

@@ -638,17 +638,45 @@ namespace FireWallet
openFileDialog.Title = "Open Batch"; openFileDialog.Title = "Open Batch";
if (openFileDialog.ShowDialog() == DialogResult.OK) if (openFileDialog.ShowDialog() == DialogResult.OK)
{ {
StreamReader sr = new StreamReader(openFileDialog.FileName); try
string line;
string[] domains = new string[0];
while ((line = sr.ReadLine()) != null)
{ {
string[] split = line.Split(','); StreamReader sr = new StreamReader(openFileDialog.FileName);
try string line;
string[] domains = new string[0];
while ((line = sr.ReadLine()) != null)
{ {
if (split.Length > 2) string[] split = line.Split(',');
try
{ {
if (split[1] == "UPDATE") if (split.Length > 2)
{
if (split[1] == "UPDATE")
{
// Select operation and import domains
string[] newDomains = new string[domains.Length + 1];
for (int i = 0; i < domains.Length; i++)
{
newDomains[i] = domains[i];
}
newDomains[domains.Length] = split[0];
domains = newDomains;
continue;
}
}
if (split.Length == 2)
{
AddBatch(split[0], split[1]);
}
else if (split.Length == 3)
{
AddBatch(split[0], split[1], split[2]);
}
else if (split.Length == 4)
{
AddBatch(split[0], split[1], Convert.ToDecimal(split[2]), Convert.ToDecimal(split[3]));
}
else
{ {
// Select operation and import domains // Select operation and import domains
string[] newDomains = new string[domains.Length + 1]; string[] newDomains = new string[domains.Length + 1];
@@ -656,68 +684,49 @@ namespace FireWallet
{ {
newDomains[i] = domains[i]; newDomains[i] = domains[i];
} }
newDomains[domains.Length] = split[0]; newDomains[domains.Length] = line.Trim();
domains = newDomains; domains = newDomains;
continue;
} }
} }
catch (Exception ex)
if (split.Length == 2)
{ {
AddBatch(split[0], split[1]); AddLog("Error importing batch: " + ex.Message);
} NotifyForm notifyForm = new NotifyForm("Error importing batch");
else if (split.Length == 3) notifyForm.ShowDialog();
{ notifyForm.Dispose();
AddBatch(split[0], split[1], split[2]);
}
else if (split.Length == 4)
{
AddBatch(split[0], split[1], Convert.ToDecimal(split[2]), Convert.ToDecimal(split[3]));
}
else
{
// Select operation and import domains
string[] newDomains = new string[domains.Length + 1];
for (int i = 0; i < domains.Length; i++)
{
newDomains[i] = domains[i];
}
newDomains[domains.Length] = line.Trim();
domains = newDomains;
} }
} }
catch (Exception ex) if (domains.Length > 0)
{ {
AddLog("Error importing batch: " + ex.Message); BatchImportForm batchImportForm = new BatchImportForm(domains);
NotifyForm notifyForm = new NotifyForm("Error importing batch"); batchImportForm.ShowDialog();
notifyForm.ShowDialog(); if (batchImportForm.batches != null)
notifyForm.Dispose(); {
foreach (Batch b in batchImportForm.batches)
{
if (b.method == "BID")
{
AddBatch(b.domain, b.method, b.bid, b.lockup);
}
else if (b.method == "TRANSFER")
{
AddBatch(b.domain, b.method, b.toAddress);
}
else
{
AddBatch(b.domain, b.method);
}
}
}
} }
} sr.Dispose();
if (domains.Length > 0) } catch (Exception ex)
{ {
BatchImportForm batchImportForm = new BatchImportForm(domains); AddLog("Error importing batch: " + ex.Message);
batchImportForm.ShowDialog(); NotifyForm notifyForm = new NotifyForm("Error importing batch\nMake sure the file is in not in use");
if (batchImportForm.batches != null) notifyForm.ShowDialog();
{ notifyForm.Dispose();
foreach (Batch b in batchImportForm.batches)
{
if (b.method == "BID")
{
AddBatch(b.domain, b.method, b.bid, b.lockup);
}
else if (b.method == "TRANSFER")
{
AddBatch(b.domain, b.method, b.toAddress);
}
else
{
AddBatch(b.domain, b.method);
}
}
}
} }
sr.Dispose();
} }
} }

View File

@@ -30,6 +30,7 @@
<PackageReference Include="DnsClient" Version="1.7.0" /> <PackageReference Include="DnsClient" Version="1.7.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="QRCoder" Version="1.4.3" /> <PackageReference Include="QRCoder" Version="1.4.3" />
<PackageReference Include="Yubico.YubiKey" Version="1.7.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@@ -95,6 +95,8 @@ namespace FireWallet
textBoxReceiveAddress = new TextBox(); textBoxReceiveAddress = new TextBox();
labelReceive1 = new Label(); labelReceive1 = new Label();
panelDomains = new Panel(); panelDomains = new Panel();
labelDomainSort = new Label();
comboBoxDomainSort = new ComboBox();
buttonExportDomains = new Button(); buttonExportDomains = new Button();
groupBoxDomains = new GroupBox(); groupBoxDomains = new GroupBox();
panelDomainList = new Panel(); panelDomainList = new Panel();
@@ -102,6 +104,7 @@ namespace FireWallet
textBoxDomainSearch = new TextBox(); textBoxDomainSearch = new TextBox();
panelSettings = new Panel(); panelSettings = new Panel();
groupBoxSettingsWallet = new GroupBox(); groupBoxSettingsWallet = new GroupBox();
buttonSettingsYubikey = new Button();
buttonSettingsRescan = new Button(); buttonSettingsRescan = new Button();
buttonSeed = new Button(); buttonSeed = new Button();
groupBoxSettingsMisc = new GroupBox(); groupBoxSettingsMisc = new GroupBox();
@@ -239,7 +242,7 @@ namespace FireWallet
// //
panelaccount.BackColor = Color.Transparent; panelaccount.BackColor = Color.Transparent;
panelaccount.Controls.Add(groupBoxaccount); panelaccount.Controls.Add(groupBoxaccount);
panelaccount.Location = new Point(1082, 211); panelaccount.Location = new Point(132, 30);
panelaccount.Name = "panelaccount"; panelaccount.Name = "panelaccount";
panelaccount.Size = new Size(1074, 642); panelaccount.Size = new Size(1074, 642);
panelaccount.TabIndex = 1; panelaccount.TabIndex = 1;
@@ -572,7 +575,7 @@ namespace FireWallet
panelSend.Controls.Add(labelSendingTo); panelSend.Controls.Add(labelSendingTo);
panelSend.Controls.Add(labelSendPrompt); panelSend.Controls.Add(labelSendPrompt);
panelSend.Controls.Add(labelHIPArrow); panelSend.Controls.Add(labelHIPArrow);
panelSend.Location = new Point(138, 33); panelSend.Location = new Point(880, 441);
panelSend.Name = "panelSend"; panelSend.Name = "panelSend";
panelSend.Size = new Size(974, 521); panelSend.Size = new Size(974, 521);
panelSend.TabIndex = 2; panelSend.TabIndex = 2;
@@ -783,17 +786,42 @@ namespace FireWallet
// //
// panelDomains // panelDomains
// //
panelDomains.Controls.Add(labelDomainSort);
panelDomains.Controls.Add(comboBoxDomainSort);
panelDomains.Controls.Add(buttonRenewAll); panelDomains.Controls.Add(buttonRenewAll);
panelDomains.Controls.Add(buttonExportDomains); panelDomains.Controls.Add(buttonExportDomains);
panelDomains.Controls.Add(groupBoxDomains); panelDomains.Controls.Add(groupBoxDomains);
panelDomains.Controls.Add(labelDomainSearch); panelDomains.Controls.Add(labelDomainSearch);
panelDomains.Controls.Add(textBoxDomainSearch); panelDomains.Controls.Add(textBoxDomainSearch);
panelDomains.Location = new Point(120, 48); panelDomains.Location = new Point(861, 364);
panelDomains.Name = "panelDomains"; panelDomains.Name = "panelDomains";
panelDomains.Size = new Size(920, 536); panelDomains.Size = new Size(920, 536);
panelDomains.TabIndex = 18; panelDomains.TabIndex = 18;
panelDomains.Visible = false; panelDomains.Visible = false;
// //
// labelDomainSort
//
labelDomainSort.AutoSize = true;
labelDomainSort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
labelDomainSort.Location = new Point(638, 15);
labelDomainSort.Name = "labelDomainSort";
labelDomainSort.Size = new Size(42, 21);
labelDomainSort.TabIndex = 12;
labelDomainSort.Text = "Sort:";
//
// comboBoxDomainSort
//
comboBoxDomainSort.DropDownStyle = ComboBoxStyle.DropDownList;
comboBoxDomainSort.FlatStyle = FlatStyle.Flat;
comboBoxDomainSort.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
comboBoxDomainSort.FormattingEnabled = true;
comboBoxDomainSort.Items.AddRange(new object[] { "Default", "Alphabetical", "Expiring", "Value" });
comboBoxDomainSort.Location = new Point(686, 12);
comboBoxDomainSort.Name = "comboBoxDomainSort";
comboBoxDomainSort.Size = new Size(121, 29);
comboBoxDomainSort.TabIndex = 11;
comboBoxDomainSort.DropDownClosed += comboBoxDomainSort_DropDownClosed;
//
// buttonExportDomains // buttonExportDomains
// //
buttonExportDomains.FlatStyle = FlatStyle.Flat; buttonExportDomains.FlatStyle = FlatStyle.Flat;
@@ -854,7 +882,7 @@ namespace FireWallet
panelSettings.Controls.Add(buttonSettingsSave); panelSettings.Controls.Add(buttonSettingsSave);
panelSettings.Controls.Add(groupBoxSettingsExplorer); panelSettings.Controls.Add(groupBoxSettingsExplorer);
panelSettings.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point); panelSettings.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point);
panelSettings.Location = new Point(1065, 51); panelSettings.Location = new Point(848, 306);
panelSettings.Name = "panelSettings"; panelSettings.Name = "panelSettings";
panelSettings.Size = new Size(930, 550); panelSettings.Size = new Size(930, 550);
panelSettings.TabIndex = 19; panelSettings.TabIndex = 19;
@@ -862,6 +890,7 @@ namespace FireWallet
// //
// groupBoxSettingsWallet // groupBoxSettingsWallet
// //
groupBoxSettingsWallet.Controls.Add(buttonSettingsYubikey);
groupBoxSettingsWallet.Controls.Add(buttonSettingsRescan); groupBoxSettingsWallet.Controls.Add(buttonSettingsRescan);
groupBoxSettingsWallet.Controls.Add(buttonSeed); groupBoxSettingsWallet.Controls.Add(buttonSeed);
groupBoxSettingsWallet.Location = new Point(507, 16); groupBoxSettingsWallet.Location = new Point(507, 16);
@@ -871,6 +900,17 @@ namespace FireWallet
groupBoxSettingsWallet.TabStop = false; groupBoxSettingsWallet.TabStop = false;
groupBoxSettingsWallet.Text = "Wallet Controls"; groupBoxSettingsWallet.Text = "Wallet Controls";
// //
// buttonSettingsYubikey
//
buttonSettingsYubikey.FlatStyle = FlatStyle.Flat;
buttonSettingsYubikey.Location = new Point(6, 133);
buttonSettingsYubikey.Name = "buttonSettingsYubikey";
buttonSettingsYubikey.Size = new Size(98, 50);
buttonSettingsYubikey.TabIndex = 9;
buttonSettingsYubikey.Text = "YubiKey";
buttonSettingsYubikey.UseVisualStyleBackColor = true;
buttonSettingsYubikey.Click += buttonSettingsYubikey_Click;
//
// buttonSettingsRescan // buttonSettingsRescan
// //
buttonSettingsRescan.FlatStyle = FlatStyle.Flat; buttonSettingsRescan.FlatStyle = FlatStyle.Flat;
@@ -1069,12 +1109,12 @@ namespace FireWallet
AutoScaleDimensions = new SizeF(7F, 15F); AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font; AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(1152, 575); ClientSize = new Size(1152, 575);
Controls.Add(panelSend);
Controls.Add(panelSettings);
Controls.Add(panelaccount); Controls.Add(panelaccount);
Controls.Add(panelSettings);
Controls.Add(panelDomains);
Controls.Add(panelSend);
Controls.Add(panelPortfolio); Controls.Add(panelPortfolio);
Controls.Add(panelRecieve); Controls.Add(panelRecieve);
Controls.Add(panelDomains);
Controls.Add(panelNav); Controls.Add(panelNav);
Controls.Add(statusStripmain); Controls.Add(statusStripmain);
Icon = (Icon)resources.GetObject("$this.Icon"); Icon = (Icon)resources.GetObject("$this.Icon");
@@ -1205,5 +1245,8 @@ namespace FireWallet
private ToolStripMenuItem supportDiscordServerToolStripMenuItem; private ToolStripMenuItem supportDiscordServerToolStripMenuItem;
private Label labelHIPArrow; private Label labelHIPArrow;
private Label labelSendingHIPAddress; private Label labelSendingHIPAddress;
private ComboBox comboBoxDomainSort;
private Label labelDomainSort;
private Button buttonSettingsYubikey;
} }
} }

View File

@@ -7,13 +7,14 @@ using QRCoder;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Security.Policy;
using System.Windows.Forms;
using System.Net; using System.Net;
using DnsClient; using DnsClient;
using DnsClient.Protocol; using DnsClient.Protocol;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using System.Net.Security; using System.Net.Security;
// Used to use Yubikey to login
using Yubico.YubiKey;
using Yubico.YubiKey.Piv;
namespace FireWallet namespace FireWallet
{ {
@@ -700,7 +701,27 @@ namespace FireWallet
} }
account = comboBoxaccount.Text; account = comboBoxaccount.Text;
password = textBoxaccountpassword.Text;
if (textBoxaccountpassword.Text == "")
{
if (File.Exists(dir + account + ".yubikey"))
{
// Check if yubikey is plugged in
var devices = YubiKeyDevice.FindAll();
if (devices.Count() > 0)
{
// Get key from yubikey
password = YubiUnlock();
}
}
} else password = textBoxaccountpassword.Text;
bool loggedin = await Login(); bool loggedin = await Login();
if (loggedin) if (loggedin)
{ {
@@ -1216,6 +1237,8 @@ namespace FireWallet
groupBoxDomains.Width = panelDomains.Width - 20; groupBoxDomains.Width = panelDomains.Width - 20;
groupBoxDomains.Left = 10; groupBoxDomains.Left = 10;
groupBoxDomains.Height = panelDomains.Height - groupBoxDomains.Top - 10; groupBoxDomains.Height = panelDomains.Height - groupBoxDomains.Top - 10;
comboBoxDomainSort.SelectedIndex = 0;
UpdateDomains(); UpdateDomains();
} }
@@ -1881,6 +1904,33 @@ namespace FireWallet
int i = 0; int i = 0;
int renewable = 0; int renewable = 0;
panelDomainList.Controls.Clear(); 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) foreach (JObject name in names)
{ {
Domains[i] = name["name"].ToString(); Domains[i] = name["name"].ToString();
@@ -2212,5 +2262,166 @@ namespace FireWallet
}; };
Process.Start(psi); Process.Start(psi);
} }
private void comboBoxDomainSort_DropDownClosed(object sender, EventArgs e)
{
UpdateDomains();
}
#region yubikey
static bool PinSubmitter(KeyEntryData pin)
{
string s = "123456";
var s_b = Encoding.UTF8.GetBytes(s);
pin.SubmitValue(s_b);
return true;
}
private void buttonSettingsYubikey_Click(object sender, EventArgs e)
{
if (password.Length < 0)
{
return;
}
NotifyForm notifyForm = new NotifyForm("Insert Yubikey\nThis will use your yubikey to encrypt your account password.");
notifyForm.ShowDialog();
notifyForm.Dispose();
var devices = YubiKeyDevice.FindAll();
if (devices.Count() != 1)
{
NotifyForm notifyForm2 = new NotifyForm("Please insert your yubikey and try again.");
notifyForm2.ShowDialog();
notifyForm2.Dispose();
return;
}
NotifyForm yubiLoadingForm = new NotifyForm("Encrypting. . .", false);
yubiLoadingForm.Show();
// Wait for the form to load
Application.DoEvents();
try
{
//Assumes there is exactly one yubikey connected and it has a RSA2048 certificate in slot 9d
//PIV PIN is assumed to be 123456
var ykDevice = devices.First();
PivSession piv = new(ykDevice);
piv.KeyCollector += PinSubmitter;
piv.VerifyPin();
var slot = PivSlot.KeyManagement;
X509Certificate2 cert = piv.GetCertificate(slot);
if (cert.SignatureAlgorithm.FriendlyName != "sha256RSA")
throw new CryptographicException("Certificate must be RSA with SHA256");
var publicKey = cert.GetRSAPublicKey() ?? throw new CryptographicException("Couldn't get public key from certificate.");
Aes aesFirst = Aes.Create();
var encryptedKey = publicKey.Encrypt(aesFirst.Key, RSAEncryptionPadding.Pkcs1);
var decryptedKey = piv.Decrypt(slot, encryptedKey);
//MessageBox.Show($"aesFirst.Key.Length: {aesFirst.Key.Length}");
//MessageBox.Show($"encryptedKey.Length: {encryptedKey.Length}");
//MessageBox.Show($"decryptedKey.Length: {decryptedKey.Length}");
// split the message into blocks of 128 bytes
string message = password;
int blockSize = 128;
int blockCount = (int)Math.Ceiling((double)message.Length / blockSize);
string[] strings = new string[blockCount];
FileStream sw = new FileStream(dir + account + ".yubikey", FileMode.Create, FileAccess.Write);
for (int i = 0; i < blockCount; i++)
{
int size = Math.Min(blockSize, message.Length - i * blockSize);
strings[i] = message.Substring(i * blockSize, size);
byte[] bytes = Encoding.ASCII.GetBytes(strings[i]);
var encryptedBytes = publicKey.Encrypt(bytes, RSAEncryptionPadding.Pkcs1);
sw.Write(encryptedBytes, 0, encryptedBytes.Length);
}
sw.Close();
sw.Dispose();
}
catch (Exception ex)
{
AddLog(ex.Message);
}
yubiLoadingForm.CloseNotification();
}
private string YubiUnlock()
{
NotifyForm yubiLoadingForm = new NotifyForm("Decrypting. . .", false);
yubiLoadingForm.Show();
// Wait for the form to load
Application.DoEvents();
try
{
//Assumes there is exactly one yubikey connected and it has a RSA2048 certificate in slot 9d
//PIV PIN is assumed to be 123456
var devices = YubiKeyDevice.FindAll();
var ykDevice = devices.First();
PivSession piv = new(ykDevice);
piv.KeyCollector += PinSubmitter;
piv.VerifyPin();
var slot = PivSlot.KeyManagement;
X509Certificate2 cert = piv.GetCertificate(slot);
if (cert.SignatureAlgorithm.FriendlyName != "sha256RSA")
throw new CryptographicException("Certificate must be RSA with SHA256");
var publicKey = cert.GetRSAPublicKey() ?? throw new CryptographicException("Couldn't get public key from certificate.");
Aes aesFirst = Aes.Create();
var encryptedKey = publicKey.Encrypt(aesFirst.Key, RSAEncryptionPadding.Pkcs1);
var decryptedKey = piv.Decrypt(slot, encryptedKey);
byte[] input = File.ReadAllBytes(dir + account + ".yubikey");
// decrypt the input
int blockSize = 256;
int blockCount = (int)Math.Ceiling((double)input.Length / blockSize);
byte[][] blocks = new byte[blockCount][];
byte[] decripted = new byte[blockCount * blockSize];
string output = "";
for (int i = 0; i < blockCount; i++)
{
int size = Math.Min(blockSize, input.Length - i * blockSize);
blocks[i] = new byte[size];
Array.Copy(input, i * blockSize, blocks[i], 0, size);
var paddedDecryptedBytes = piv.Decrypt(slot, blocks[i]);
byte[] decryptedBytes;
bool couldParse = Yubico.YubiKey.Cryptography.RsaFormat.TryParsePkcs1Decrypt(paddedDecryptedBytes, out decryptedBytes);
Array.Copy(decryptedBytes, 0, decripted, i * blockSize, decryptedBytes.Length);
output += Encoding.ASCII.GetString(decryptedBytes);
}
yubiLoadingForm.CloseNotification();
return output;
}
catch (Exception ex)
{
AddLog(ex.Message);
yubiLoadingForm.CloseNotification();
return "";
}
}
#endregion
} }
} }

View File

@@ -60,6 +60,9 @@ You can change the number of transactions shown in the `portfolio-tx:` settings.
<br><br> <br><br>
## Sending HNS ## Sending HNS
![Send](assets/send_hns.png) ![Send](assets/send_hns.png)
This page lets you send HNS to Handshake addresses or domains using [HIP-02](https://github.com/handshake-org/HIPs/blob/master/HIP-0002.md).
To use HIP-02 you need to have HSD resolver (or any HNS compatible DNS resolver) listening on port 5350 (default HSD port).
To enter a domain to use HIP-02 you need to prefix the domain with `@` (eg. `@nathan.woodburn`).
## Receiving HNS or Domains ## Receiving HNS or Domains
The receive page shows your current HNS address. The receive page shows your current HNS address.
@@ -104,6 +107,8 @@ The "CANCEL" transaction type is used to cancel an transfer.
At the momemt "UPDATE" or coin only transactions are not supported. At the momemt "UPDATE" or coin only transactions are not supported.
Please not that the import syntax for BIDs is BID,LOCKUP where LOCKUP is (BID+BLIND)
![Batch Import](assets/batch_import.png) ![Batch Import](assets/batch_import.png)
## Exporting ## Exporting