fix: explain a stored non-master xprv wallet instead of throwing at signing time (closes #234)
All checks were successful
check / check (push) Successful in 30s
All checks were successful
check / check (push) Successful in 30s
This commit was merged in pull request #247.
This commit is contained in:
@@ -29,6 +29,16 @@ const { log } = require("../../shared/log");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
const { decryptWithPassword } = require("../../shared/vault");
|
||||
const { getSignerForAddress } = require("../../shared/wallet");
|
||||
const { walletDefect } = require("../../shared/walletDefects");
|
||||
|
||||
// The defect of the wallet the selected address belongs to, or null. Both the
|
||||
// send and the private-key export path check it before asking for a password,
|
||||
// so a wallet that cannot derive its keys says so instead of failing after the
|
||||
// user has typed one in.
|
||||
function selectedWalletDefect() {
|
||||
if (state.selectedWallet === null) return null;
|
||||
return walletDefect(state.wallets[state.selectedWallet]);
|
||||
}
|
||||
|
||||
let ctx;
|
||||
|
||||
@@ -254,6 +264,11 @@ function init(_ctx) {
|
||||
});
|
||||
|
||||
$("btn-send").addEventListener("click", () => {
|
||||
const defect = selectedWalletDefect();
|
||||
if (defect) {
|
||||
showFlash(defect.shortMessage);
|
||||
return;
|
||||
}
|
||||
const addr =
|
||||
state.wallets[state.selectedWallet].addresses[
|
||||
state.selectedAddress
|
||||
@@ -298,6 +313,14 @@ function init(_ctx) {
|
||||
$("btn-export-privkey").addEventListener("click", () => {
|
||||
moreDropdown.classList.add("hidden");
|
||||
moreBtn.classList.remove("bg-fg", "text-bg");
|
||||
// There is no private key to export for an address this wallet
|
||||
// cannot derive. Without this the export screen would take a
|
||||
// password and then report it as wrong.
|
||||
const defect = selectedWalletDefect();
|
||||
if (defect) {
|
||||
showFlash(defect.shortMessage);
|
||||
return;
|
||||
}
|
||||
pushCurrentView();
|
||||
const wallet = state.wallets[state.selectedWallet];
|
||||
const addr = wallet.addresses[state.selectedAddress];
|
||||
|
||||
@@ -35,6 +35,7 @@ const {
|
||||
} = require("./send");
|
||||
const { log } = require("../../shared/log");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
const { walletDefect } = require("../../shared/walletDefects");
|
||||
|
||||
let ctx;
|
||||
|
||||
@@ -338,6 +339,11 @@ function init(_ctx) {
|
||||
});
|
||||
|
||||
$("btn-address-token-send").addEventListener("click", () => {
|
||||
const defect = walletDefect(state.wallets[state.selectedWallet]);
|
||||
if (defect) {
|
||||
showFlash(defect.shortMessage);
|
||||
return;
|
||||
}
|
||||
const addr =
|
||||
state.wallets[state.selectedWallet].addresses[
|
||||
state.selectedAddress
|
||||
|
||||
@@ -21,6 +21,7 @@ const { ERC20_ABI } = require("../../shared/constants");
|
||||
const { TOKEN_BY_ADDRESS } = require("../../shared/tokenList");
|
||||
const { decryptWithPassword } = require("../../shared/vault");
|
||||
const { getSignerForAddress } = require("../../shared/wallet");
|
||||
const { walletDefect } = require("../../shared/walletDefects");
|
||||
const { getProvider } = require("../../shared/balances");
|
||||
const txStatus = require("./txStatus");
|
||||
const uniswap = require("../../shared/uniswap");
|
||||
@@ -280,6 +281,7 @@ function showTxApproval(details) {
|
||||
|
||||
showView("approve-tx");
|
||||
attachCopyHandlers("view-approve-tx");
|
||||
gateOnWalletDefect("approve-tx-error", "btn-approve-tx");
|
||||
}
|
||||
|
||||
function decodeHexMessage(hex) {
|
||||
@@ -379,6 +381,7 @@ function showSignApproval(details) {
|
||||
|
||||
showView("approve-sign");
|
||||
attachCopyHandlers("view-approve-sign");
|
||||
gateOnWalletDefect("approve-sign-error", "btn-approve-sign");
|
||||
}
|
||||
|
||||
function show(id) {
|
||||
@@ -431,6 +434,20 @@ function setSignButtonBusy(busy) {
|
||||
$("btn-approve-sign").classList.toggle("text-muted", busy);
|
||||
}
|
||||
|
||||
// Say so on the approval screen itself, and disable the approve button, when
|
||||
// the active address belongs to a wallet whose keys cannot be derived. Without
|
||||
// this the screen would take a password and fail after deriving it. Reject
|
||||
// stays available; the wallet is not touched. Returns true when it gated.
|
||||
function gateOnWalletDefect(errorId, buttonId) {
|
||||
const active = findActiveWallet();
|
||||
const defect = active ? walletDefect(active.wallet) : null;
|
||||
if (!defect) return false;
|
||||
showError(errorId, defect.shortMessage);
|
||||
$(buttonId).disabled = true;
|
||||
$(buttonId).classList.add("text-muted");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Locate the wallet and the address index owning the currently active
|
||||
// address. Returns null when no wallet holds it.
|
||||
function findActiveWallet() {
|
||||
@@ -492,6 +509,14 @@ function init(ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defect = walletDefect(active.wallet);
|
||||
if (defect) {
|
||||
password = null;
|
||||
showError("approve-tx-error", defect.shortMessage);
|
||||
setTxButtonBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt here, in the popup. The password must never cross the
|
||||
// extension messaging boundary; only the signed transaction does.
|
||||
let decryptedSecret;
|
||||
@@ -583,6 +608,14 @@ function init(ctx) {
|
||||
return;
|
||||
}
|
||||
|
||||
const defect = walletDefect(active.wallet);
|
||||
if (defect) {
|
||||
password = null;
|
||||
showError("approve-sign-error", defect.shortMessage);
|
||||
setSignButtonBusy(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Decrypt here, in the popup. The password must never cross the
|
||||
// extension messaging boundary; only the signature does.
|
||||
let decryptedSecret;
|
||||
|
||||
@@ -21,6 +21,10 @@ const {
|
||||
resetSendValidation,
|
||||
} = require("./send");
|
||||
const { deriveAddressFromXpub } = require("../../shared/wallet");
|
||||
const {
|
||||
walletDefect,
|
||||
walletDefectHtml,
|
||||
} = require("../../shared/walletDefects");
|
||||
const {
|
||||
formatUsd,
|
||||
getPrice,
|
||||
@@ -214,25 +218,23 @@ async function loadHomeTxs(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
function render(ctx) {
|
||||
const container = $("wallet-list");
|
||||
if (state.wallets.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
|
||||
renderTotalValue();
|
||||
renderActiveAddress();
|
||||
return;
|
||||
}
|
||||
|
||||
// The wallet list markup. Pure: it reads state and returns a string, so the
|
||||
// list can be asserted on without a DOM.
|
||||
function walletListHtml() {
|
||||
let html = "";
|
||||
state.wallets.forEach((wallet, wi) => {
|
||||
const defect = walletDefect(wallet);
|
||||
html += `<div>`;
|
||||
html += `<div class="flex justify-between items-center bg-section py-1 px-2" style="margin:0 -0.5rem">`;
|
||||
html += `<span class="font-bold cursor-pointer wallet-name underline decoration-dashed" data-wallet="${wi}">${wallet.name}</span>`;
|
||||
if (wallet.type === "hd" || wallet.type === "xprv") {
|
||||
// No "+" on a defective wallet: deriving another address from that
|
||||
// xpub would only add one more address the key does not produce
|
||||
// under the standard path.
|
||||
if (!defect && (wallet.type === "hd" || wallet.type === "xprv")) {
|
||||
html += `<button class="btn-add-address border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs" data-wallet="${wi}" title="Add another address to this wallet">+</button>`;
|
||||
}
|
||||
html += `</div>`;
|
||||
html += walletDefectHtml(wallet);
|
||||
|
||||
wallet.addresses.forEach((addr, ai) => {
|
||||
html += `<div class="address-row py-1 border-b border-border-light cursor-pointer hover:bg-hover" data-wallet="${wi}" data-address="${ai}">`;
|
||||
@@ -260,7 +262,20 @@ function render(ctx) {
|
||||
|
||||
html += `</div>`;
|
||||
});
|
||||
container.innerHTML = html;
|
||||
return html;
|
||||
}
|
||||
|
||||
function render(ctx) {
|
||||
const container = $("wallet-list");
|
||||
if (state.wallets.length === 0) {
|
||||
container.innerHTML =
|
||||
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
|
||||
renderTotalValue();
|
||||
renderActiveAddress();
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = walletListHtml();
|
||||
|
||||
container.querySelectorAll(".address-row").forEach((row) => {
|
||||
row.addEventListener("click", async () => {
|
||||
@@ -348,6 +363,13 @@ function render(ctx) {
|
||||
loadHomeTxs(ctx);
|
||||
}
|
||||
|
||||
// The defect of the wallet the selected address belongs to, or null. Call
|
||||
// after selectActiveAddress().
|
||||
function selectedWalletDefect() {
|
||||
if (state.selectedWallet === null) return null;
|
||||
return walletDefect(state.wallets[state.selectedWallet]);
|
||||
}
|
||||
|
||||
function selectActiveAddress() {
|
||||
for (let wi = 0; wi < state.wallets.length; wi++) {
|
||||
for (let ai = 0; ai < state.wallets[wi].addresses.length; ai++) {
|
||||
@@ -371,6 +393,13 @@ function init(ctx) {
|
||||
showFlash("No active address selected.");
|
||||
return;
|
||||
}
|
||||
// Before the balance check and before any password is asked for: this
|
||||
// wallet cannot sign at all, so the send screen is a dead end.
|
||||
const defect = selectedWalletDefect();
|
||||
if (defect) {
|
||||
showFlash(defect.shortMessage);
|
||||
return;
|
||||
}
|
||||
const addr = currentAddress();
|
||||
if (!addr.balance || parseFloat(addr.balance) === 0) {
|
||||
showFlash("Cannot send \u2014 zero balance.");
|
||||
@@ -396,4 +425,4 @@ function init(ctx) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, render };
|
||||
module.exports = { init, render, walletListHtml };
|
||||
|
||||
Reference in New Issue
Block a user