- Write these words down and keep them safe. Anyone with them
- can take your funds; if you lose them, your wallet is gone.
+
+
+
-
-
Import Extended Private Key
-
- Paste your extended private key (xprv) below. This will
- import the HD wallet and scan for used addresses.
-
-
-
-
-
-
-
- This password encrypts your key on this device. You will
- need it to send funds.
-
-
-
-
-
-
-
-
diff --git a/src/popup/index.js b/src/popup/index.js
index bfdb6ee..29e27ab 100644
--- a/src/popup/index.js
+++ b/src/popup/index.js
@@ -10,8 +10,6 @@ const { $, showView } = require("./views/helpers");
const home = require("./views/home");
const welcome = require("./views/welcome");
const addWallet = require("./views/addWallet");
-const importKey = require("./views/importKey");
-const importXprv = require("./views/importXprv");
const addressDetail = require("./views/addressDetail");
const addressToken = require("./views/addressToken");
const send = require("./views/send");
@@ -55,8 +53,6 @@ const ctx = {
renderWalletList,
doRefreshAndRender,
showAddWalletView: () => addWallet.show(),
- showImportKeyView: () => importKey.show(),
- showImportXprvView: () => importXprv.show(),
showAddressDetail: () => addressDetail.show(),
showAddressToken: () => addressToken.show(),
showAddTokenView: () => addToken.show(),
@@ -211,8 +207,6 @@ async function init() {
welcome.init(ctx);
addWallet.init(ctx);
- importKey.init(ctx);
- importXprv.init(ctx);
home.init(ctx);
addressDetail.init(ctx);
addressToken.init(ctx);
diff --git a/src/popup/views/addWallet.js b/src/popup/views/addWallet.js
index ca8c4c6..635fbef 100644
--- a/src/popup/views/addWallet.js
+++ b/src/popup/views/addWallet.js
@@ -3,114 +3,272 @@ const {
generateMnemonic,
hdWalletFromMnemonic,
isValidMnemonic,
+ addressFromPrivateKey,
+ hdWalletFromXprv,
+ isValidXprv,
} = require("../../shared/wallet");
const { encryptWithPassword } = require("../../shared/vault");
const { state, saveState } = require("../../shared/state");
const { scanForAddresses } = require("../../shared/balances");
+let currentMode = "mnemonic";
+
+const MODES = ["mnemonic", "privkey", "xprv"];
+
+const PASSWORD_HINTS = {
+ mnemonic:
+ "This password encrypts your recovery phrase on this device. You will need it to send funds.",
+ privkey:
+ "This password encrypts your private key on this device. You will need it to send funds.",
+ xprv: "This password encrypts your key on this device. You will need it to send funds.",
+};
+
+function switchMode(mode) {
+ currentMode = mode;
+ for (const m of MODES) {
+ $("add-wallet-section-" + m).classList.toggle("hidden", m !== mode);
+ $("tab-" + m).classList.toggle("bg-fg", m === mode);
+ $("tab-" + m).classList.toggle("text-bg", m === mode);
+ }
+ $("add-wallet-password-hint").textContent = PASSWORD_HINTS[mode];
+}
+
function show() {
$("wallet-mnemonic").value = "";
+ $("import-private-key").value = "";
+ $("import-xprv-key").value = "";
$("add-wallet-password").value = "";
$("add-wallet-password-confirm").value = "";
$("add-wallet-phrase-warning").classList.add("hidden");
+ switchMode("mnemonic");
showView("add-wallet");
}
+function validatePassword() {
+ const pw = $("add-wallet-password").value;
+ const pw2 = $("add-wallet-password-confirm").value;
+ if (!pw) {
+ showFlash("Please choose a password.");
+ return null;
+ }
+ if (pw.length < 12) {
+ showFlash("Password must be at least 12 characters.");
+ return null;
+ }
+ if (pw !== pw2) {
+ showFlash("Passwords do not match.");
+ return null;
+ }
+ return pw;
+}
+
+async function importMnemonic(ctx) {
+ const mnemonic = $("wallet-mnemonic").value.trim();
+ if (!mnemonic) {
+ showFlash("Enter a recovery phrase or press the die to generate one.");
+ return;
+ }
+ const words = mnemonic.split(/\s+/);
+ if (words.length !== 12 && words.length !== 24) {
+ showFlash(
+ "Recovery phrase must be 12 or 24 words. You entered " +
+ words.length +
+ ".",
+ );
+ return;
+ }
+ if (!isValidMnemonic(mnemonic)) {
+ showFlash("Invalid recovery phrase. Check for typos.");
+ return;
+ }
+ const pw = validatePassword();
+ if (!pw) return;
+ const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
+ const duplicate = state.wallets.find(
+ (w) =>
+ w.type === "hd" &&
+ w.addresses[0] &&
+ w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
+ );
+ if (duplicate) {
+ showFlash(
+ "This recovery phrase is already added (" + duplicate.name + ").",
+ );
+ return;
+ }
+ const encrypted = await encryptWithPassword(mnemonic, pw);
+ const walletNum = state.wallets.length + 1;
+ const wallet = {
+ type: "hd",
+ name: "Wallet " + walletNum,
+ xpub: xpub,
+ encryptedSecret: encrypted,
+ nextIndex: 1,
+ addresses: [
+ { address: firstAddress, balance: "0.0000", tokenBalances: [] },
+ ],
+ };
+ state.wallets.push(wallet);
+ state.hasWallet = true;
+ await saveState();
+ ctx.renderWalletList();
+ showView("main");
+
+ // Scan for used HD addresses beyond index 0.
+ showFlash("Scanning for addresses...", 30000);
+ const scan = await scanForAddresses(xpub, state.rpcUrl);
+ if (scan.addresses.length > 1) {
+ wallet.addresses = scan.addresses.map((a) => ({
+ address: a.address,
+ balance: "0.0000",
+ tokenBalances: [],
+ }));
+ wallet.nextIndex = scan.nextIndex;
+ await saveState();
+ ctx.renderWalletList();
+ showFlash("Found " + scan.addresses.length + " addresses.");
+ } else {
+ showFlash("Ready.", 1000);
+ }
+
+ ctx.doRefreshAndRender();
+}
+
+async function importPrivateKey(ctx) {
+ const key = $("import-private-key").value.trim();
+ if (!key) {
+ showFlash("Please enter your private key.");
+ return;
+ }
+ let addr;
+ try {
+ addr = addressFromPrivateKey(key);
+ } catch (e) {
+ showFlash("Invalid private key.");
+ return;
+ }
+ const pw = validatePassword();
+ if (!pw) return;
+ const duplicate = state.wallets.find(
+ (w) =>
+ w.type === "key" &&
+ w.addresses[0] &&
+ w.addresses[0].address.toLowerCase() === addr.toLowerCase(),
+ );
+ if (duplicate) {
+ showFlash(
+ "This private key is already added (" + duplicate.name + ").",
+ );
+ return;
+ }
+ const encrypted = await encryptWithPassword(key, pw);
+ const walletNum = state.wallets.length + 1;
+ state.wallets.push({
+ type: "key",
+ name: "Wallet " + walletNum,
+ encryptedSecret: encrypted,
+ addresses: [{ address: addr, balance: "0.0000", tokenBalances: [] }],
+ });
+ state.hasWallet = true;
+ await saveState();
+ ctx.renderWalletList();
+ showView("main");
+
+ ctx.doRefreshAndRender();
+}
+
+async function importXprvKey(ctx) {
+ const xprv = $("import-xprv-key").value.trim();
+ if (!xprv) {
+ showFlash("Please enter your extended private key.");
+ return;
+ }
+ if (!isValidXprv(xprv)) {
+ showFlash("Invalid extended private key.");
+ return;
+ }
+ let result;
+ try {
+ result = hdWalletFromXprv(xprv);
+ } catch (e) {
+ showFlash("Invalid extended private key.");
+ return;
+ }
+ const { xpub, firstAddress } = result;
+ const duplicate = state.wallets.find(
+ (w) =>
+ (w.type === "hd" || w.type === "xprv") &&
+ w.addresses[0] &&
+ w.addresses[0].address.toLowerCase() === firstAddress.toLowerCase(),
+ );
+ if (duplicate) {
+ showFlash("This key is already added (" + duplicate.name + ").");
+ return;
+ }
+ const pw = validatePassword();
+ if (!pw) return;
+ const encrypted = await encryptWithPassword(xprv, pw);
+ const walletNum = state.wallets.length + 1;
+ const wallet = {
+ type: "xprv",
+ name: "Wallet " + walletNum,
+ xpub: xpub,
+ encryptedSecret: encrypted,
+ nextIndex: 1,
+ addresses: [
+ { address: firstAddress, balance: "0.0000", tokenBalances: [] },
+ ],
+ };
+ state.wallets.push(wallet);
+ state.hasWallet = true;
+ await saveState();
+ ctx.renderWalletList();
+ showView("main");
+
+ // Scan for used HD addresses beyond index 0.
+ showFlash("Scanning for addresses...", 30000);
+ const scan = await scanForAddresses(xpub, state.rpcUrl);
+ if (scan.addresses.length > 1) {
+ wallet.addresses = scan.addresses.map((a) => ({
+ address: a.address,
+ balance: "0.0000",
+ tokenBalances: [],
+ }));
+ wallet.nextIndex = scan.nextIndex;
+ await saveState();
+ ctx.renderWalletList();
+ showFlash("Found " + scan.addresses.length + " addresses.");
+ } else {
+ showFlash("Ready.", 1000);
+ }
+
+ ctx.doRefreshAndRender();
+}
+
function init(ctx) {
+ // Tab click handlers
+ $("tab-mnemonic").addEventListener("click", () => switchMode("mnemonic"));
+ $("tab-privkey").addEventListener("click", () => switchMode("privkey"));
+ $("tab-xprv").addEventListener("click", () => switchMode("xprv"));
+
+ // Generate mnemonic
$("btn-generate-phrase").addEventListener("click", () => {
$("wallet-mnemonic").value = generateMnemonic();
$("add-wallet-phrase-warning").classList.remove("hidden");
});
+ // Import / confirm
$("btn-add-wallet-confirm").addEventListener("click", async () => {
- const mnemonic = $("wallet-mnemonic").value.trim();
- if (!mnemonic) {
- showFlash(
- "Enter a recovery phrase or press the die to generate one.",
- );
- return;
+ if (currentMode === "mnemonic") {
+ await importMnemonic(ctx);
+ } else if (currentMode === "privkey") {
+ await importPrivateKey(ctx);
+ } else if (currentMode === "xprv") {
+ await importXprvKey(ctx);
}
- const words = mnemonic.split(/\s+/);
- if (words.length !== 12 && words.length !== 24) {
- showFlash(
- "Recovery phrase must be 12 or 24 words. You entered " +
- words.length +
- ".",
- );
- return;
- }
- if (!isValidMnemonic(mnemonic)) {
- showFlash("Invalid recovery phrase. Check for typos.");
- return;
- }
- const pw = $("add-wallet-password").value;
- const pw2 = $("add-wallet-password-confirm").value;
- if (!pw) {
- showFlash("Please choose a password.");
- return;
- }
- if (pw.length < 12) {
- showFlash("Password must be at least 12 characters.");
- return;
- }
- if (pw !== pw2) {
- showFlash("Passwords do not match.");
- return;
- }
- const { xpub, firstAddress } = hdWalletFromMnemonic(mnemonic);
- const duplicate = state.wallets.find(
- (w) =>
- w.type === "hd" &&
- w.addresses[0] &&
- w.addresses[0].address.toLowerCase() ===
- firstAddress.toLowerCase(),
- );
- if (duplicate) {
- showFlash(
- "This recovery phrase is already added (" +
- duplicate.name +
- ").",
- );
- return;
- }
- const encrypted = await encryptWithPassword(mnemonic, pw);
- const walletNum = state.wallets.length + 1;
- const wallet = {
- type: "hd",
- name: "Wallet " + walletNum,
- xpub: xpub,
- encryptedSecret: encrypted,
- nextIndex: 1,
- addresses: [
- { address: firstAddress, balance: "0.0000", tokenBalances: [] },
- ],
- };
- state.wallets.push(wallet);
- state.hasWallet = true;
- await saveState();
- ctx.renderWalletList();
- showView("main");
-
- // Scan for used HD addresses beyond index 0.
- showFlash("Scanning for addresses...", 30000);
- const scan = await scanForAddresses(xpub, state.rpcUrl);
- if (scan.addresses.length > 1) {
- wallet.addresses = scan.addresses.map((a) => ({
- address: a.address,
- balance: "0.0000",
- tokenBalances: [],
- }));
- wallet.nextIndex = scan.nextIndex;
- await saveState();
- ctx.renderWalletList();
- showFlash("Found " + scan.addresses.length + " addresses.");
- } else {
- showFlash("Ready.", 1000);
- }
-
- ctx.doRefreshAndRender();
});
+ // Back button
$("btn-add-wallet-back").addEventListener("click", () => {
if (!state.hasWallet) {
showView("welcome");
@@ -119,16 +277,6 @@ function init(ctx) {
showView("main");
}
});
-
- $("btn-add-wallet-import-key").addEventListener(
- "click",
- ctx.showImportKeyView,
- );
-
- $("btn-add-wallet-import-xprv").addEventListener(
- "click",
- ctx.showImportXprvView,
- );
}
module.exports = { init, show };
diff --git a/src/popup/views/helpers.js b/src/popup/views/helpers.js
index 8fdfe65..2db8191 100644
--- a/src/popup/views/helpers.js
+++ b/src/popup/views/helpers.js
@@ -13,7 +13,6 @@ const { state, saveState } = require("../../shared/state");
const VIEWS = [
"welcome",
"add-wallet",
- "import-key",
"main",
"address",
"address-token",
diff --git a/src/popup/views/importKey.js b/src/popup/views/importKey.js
deleted file mode 100644
index a3324a3..0000000
--- a/src/popup/views/importKey.js
+++ /dev/null
@@ -1,69 +0,0 @@
-const { $, showView, showFlash } = require("./helpers");
-const { addressFromPrivateKey } = require("../../shared/wallet");
-const { encryptWithPassword } = require("../../shared/vault");
-const { state, saveState } = require("../../shared/state");
-
-function show() {
- $("import-private-key").value = "";
- $("import-key-password").value = "";
- $("import-key-password-confirm").value = "";
- showView("import-key");
-}
-
-function init(ctx) {
- $("btn-import-key-confirm").addEventListener("click", async () => {
- const key = $("import-private-key").value.trim();
- if (!key) {
- showFlash("Please enter your private key.");
- return;
- }
- let addr;
- try {
- addr = addressFromPrivateKey(key);
- } catch (e) {
- showFlash("Invalid private key.");
- return;
- }
- const pw = $("import-key-password").value;
- const pw2 = $("import-key-password-confirm").value;
- if (!pw) {
- showFlash("Please choose a password.");
- return;
- }
- if (pw.length < 12) {
- showFlash("Password must be at least 12 characters.");
- return;
- }
- if (pw !== pw2) {
- showFlash("Passwords do not match.");
- return;
- }
- const encrypted = await encryptWithPassword(key, pw);
- const walletNum = state.wallets.length + 1;
- state.wallets.push({
- type: "key",
- name: "Wallet " + walletNum,
- encryptedSecret: encrypted,
- addresses: [
- { address: addr, balance: "0.0000", tokenBalances: [] },
- ],
- });
- state.hasWallet = true;
- await saveState();
- ctx.renderWalletList();
- showView("main");
-
- ctx.doRefreshAndRender();
- });
-
- $("btn-import-key-back").addEventListener("click", () => {
- if (!state.hasWallet) {
- showView("welcome");
- } else {
- ctx.renderWalletList();
- showView("main");
- }
- });
-}
-
-module.exports = { init, show };
diff --git a/src/popup/views/importXprv.js b/src/popup/views/importXprv.js
deleted file mode 100644
index f1ba60d..0000000
--- a/src/popup/views/importXprv.js
+++ /dev/null
@@ -1,106 +0,0 @@
-const { $, showView, showFlash } = require("./helpers");
-const { hdWalletFromXprv, isValidXprv } = require("../../shared/wallet");
-const { encryptWithPassword } = require("../../shared/vault");
-const { state, saveState } = require("../../shared/state");
-const { scanForAddresses } = require("../../shared/balances");
-
-function show() {
- $("import-xprv-key").value = "";
- $("import-xprv-password").value = "";
- $("import-xprv-password-confirm").value = "";
- showView("import-xprv");
-}
-
-function init(ctx) {
- $("btn-import-xprv-confirm").addEventListener("click", async () => {
- const xprv = $("import-xprv-key").value.trim();
- if (!xprv) {
- showFlash("Please enter your extended private key.");
- return;
- }
- if (!isValidXprv(xprv)) {
- showFlash("Invalid extended private key.");
- return;
- }
- let result;
- try {
- result = hdWalletFromXprv(xprv);
- } catch (e) {
- showFlash("Invalid extended private key.");
- return;
- }
- const { xpub, firstAddress } = result;
- const duplicate = state.wallets.find(
- (w) =>
- (w.type === "hd" || w.type === "xprv") &&
- w.addresses[0] &&
- w.addresses[0].address.toLowerCase() ===
- firstAddress.toLowerCase(),
- );
- if (duplicate) {
- showFlash("This key is already added (" + duplicate.name + ").");
- return;
- }
- const pw = $("import-xprv-password").value;
- const pw2 = $("import-xprv-password-confirm").value;
- if (!pw) {
- showFlash("Please choose a password.");
- return;
- }
- if (pw.length < 12) {
- showFlash("Password must be at least 12 characters.");
- return;
- }
- if (pw !== pw2) {
- showFlash("Passwords do not match.");
- return;
- }
- const encrypted = await encryptWithPassword(xprv, pw);
- const walletNum = state.wallets.length + 1;
- const wallet = {
- type: "xprv",
- name: "Wallet " + walletNum,
- xpub: xpub,
- encryptedSecret: encrypted,
- nextIndex: 1,
- addresses: [
- { address: firstAddress, balance: "0.0000", tokenBalances: [] },
- ],
- };
- state.wallets.push(wallet);
- state.hasWallet = true;
- await saveState();
- ctx.renderWalletList();
- showView("main");
-
- // Scan for used HD addresses beyond index 0.
- showFlash("Scanning for addresses...", 30000);
- const scan = await scanForAddresses(xpub, state.rpcUrl);
- if (scan.addresses.length > 1) {
- wallet.addresses = scan.addresses.map((a) => ({
- address: a.address,
- balance: "0.0000",
- tokenBalances: [],
- }));
- wallet.nextIndex = scan.nextIndex;
- await saveState();
- ctx.renderWalletList();
- showFlash("Found " + scan.addresses.length + " addresses.");
- } else {
- showFlash("Ready.", 1000);
- }
-
- ctx.doRefreshAndRender();
- });
-
- $("btn-import-xprv-back").addEventListener("click", () => {
- if (!state.hasWallet) {
- showView("welcome");
- } else {
- ctx.renderWalletList();
- showView("main");
- }
- });
-}
-
-module.exports = { init, show };