feat: add export private key from address detail view
All checks were successful
check / check (push) Successful in 22s
All checks were successful
check / check (push) Successful in 22s
Adds an 'Export Private Key' button to the address detail view. Clicking it opens a password confirmation screen; after verification, the derived private key is displayed in a copyable field with a security warning. The key is cleared when navigating away. Closes #19
This commit is contained in:
@@ -307,6 +307,16 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- actions row 2 -->
|
||||
<div class="flex gap-2 mb-3">
|
||||
<button
|
||||
id="btn-export-privkey"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer flex-1 text-xs"
|
||||
>
|
||||
Export Private Key
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- transactions -->
|
||||
<div class="mt-3">
|
||||
<div class="border-b border-border pb-1 mb-1">
|
||||
@@ -318,6 +328,49 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ EXPORT PRIVATE KEY VIEW ============ -->
|
||||
<div id="view-export-privkey" class="view hidden">
|
||||
<button
|
||||
id="btn-export-privkey-back"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mb-2"
|
||||
>
|
||||
< Back
|
||||
</button>
|
||||
<h2 class="font-bold mb-3">Export Private Key</h2>
|
||||
<p class="text-xs mb-1" id="export-privkey-label"></p>
|
||||
<p class="text-xs mb-3 text-muted">
|
||||
Warning: anyone with this private key can access and
|
||||
transfer all funds from this address. Never share it.
|
||||
</p>
|
||||
<div
|
||||
id="export-privkey-flash"
|
||||
class="text-xs mb-2 hidden"
|
||||
></div>
|
||||
<div id="export-privkey-password-section" class="mb-2">
|
||||
<label class="block mb-1">Password</label>
|
||||
<input
|
||||
type="password"
|
||||
id="export-privkey-password"
|
||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||
placeholder="Enter your password to continue"
|
||||
/>
|
||||
<button
|
||||
id="btn-export-privkey-confirm"
|
||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer mt-2"
|
||||
>
|
||||
Reveal
|
||||
</button>
|
||||
</div>
|
||||
<div id="export-privkey-result" class="hidden">
|
||||
<div
|
||||
id="export-privkey-value"
|
||||
class="border border-dashed border-border p-2 font-mono text-xs break-all cursor-pointer mb-1"
|
||||
title="Click to copy"
|
||||
></div>
|
||||
<p class="text-xs text-muted">Click to copy.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============ ADDRESS-TOKEN DETAIL VIEW ============ -->
|
||||
<div id="view-address-token" class="view hidden">
|
||||
<button
|
||||
|
||||
@@ -18,6 +18,8 @@ const { resolveEnsNames } = require("../../shared/ens");
|
||||
const { updateSendBalance, renderSendTokenSelect } = require("./send");
|
||||
const { log } = require("../../shared/log");
|
||||
const makeBlockie = require("ethereum-blockies-base64");
|
||||
const { decryptWithPassword } = require("../../shared/vault");
|
||||
const { getSignerForAddress } = require("../../shared/wallet");
|
||||
|
||||
let ctx;
|
||||
|
||||
@@ -261,6 +263,64 @@ function init(_ctx) {
|
||||
});
|
||||
|
||||
$("btn-add-token").addEventListener("click", ctx.showAddTokenView);
|
||||
|
||||
$("btn-export-privkey").addEventListener("click", () => {
|
||||
const wallet = state.wallets[state.selectedWallet];
|
||||
const addr = wallet.addresses[state.selectedAddress];
|
||||
const title = addressTitle(addr.address, state.wallets);
|
||||
const label = title || "Address " + (state.selectedAddress + 1);
|
||||
$("export-privkey-label").textContent = "Exporting key for: " + label;
|
||||
$("export-privkey-password").value = "";
|
||||
$("export-privkey-flash").classList.add("hidden");
|
||||
$("export-privkey-flash").textContent = "";
|
||||
$("export-privkey-password-section").classList.remove("hidden");
|
||||
$("export-privkey-result").classList.add("hidden");
|
||||
$("export-privkey-value").textContent = "";
|
||||
showView("export-privkey");
|
||||
});
|
||||
|
||||
$("btn-export-privkey-confirm").addEventListener("click", async () => {
|
||||
const password = $("export-privkey-password").value;
|
||||
if (!password) {
|
||||
$("export-privkey-flash").textContent = "Password is required.";
|
||||
$("export-privkey-flash").classList.remove("hidden");
|
||||
return;
|
||||
}
|
||||
const wallet = state.wallets[state.selectedWallet];
|
||||
try {
|
||||
const secret = await decryptWithPassword(
|
||||
wallet.encryptedSecret,
|
||||
password,
|
||||
);
|
||||
const signer = getSignerForAddress(
|
||||
wallet,
|
||||
state.selectedAddress,
|
||||
secret,
|
||||
);
|
||||
const privateKey = signer.privateKey;
|
||||
$("export-privkey-password-section").classList.add("hidden");
|
||||
$("export-privkey-value").textContent = privateKey;
|
||||
$("export-privkey-result").classList.remove("hidden");
|
||||
$("export-privkey-flash").classList.add("hidden");
|
||||
} catch {
|
||||
$("export-privkey-flash").textContent = "Wrong password.";
|
||||
$("export-privkey-flash").classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
$("export-privkey-value").addEventListener("click", () => {
|
||||
const key = $("export-privkey-value").textContent;
|
||||
if (key) {
|
||||
navigator.clipboard.writeText(key);
|
||||
showFlash("Copied!");
|
||||
}
|
||||
});
|
||||
|
||||
$("btn-export-privkey-back").addEventListener("click", () => {
|
||||
$("export-privkey-value").textContent = "";
|
||||
$("export-privkey-password").value = "";
|
||||
show();
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { init, show };
|
||||
|
||||
Reference in New Issue
Block a user