Unify create/import into single Add Wallet view
All checks were successful
check / check (push) Successful in 13s

Merge "Create new wallet" and "Import recovery phrase" into one
"Add wallet" screen. The recovery phrase textarea starts empty.
A clickable die button generates a random phrase and shows a
backup warning. Users who already have a phrase just paste it.
Welcome screen simplified to two options: "Add wallet" and
"Import private key". README updated to match.
This commit is contained in:
Jeffrey Paul 2026-02-25 15:24:24 +07:00
parent 8431488849
commit 1a49665210
3 changed files with 139 additions and 248 deletions

View File

@ -145,31 +145,31 @@ The popup has the following views, switched via simple show/hide:
1. **Lock**: Password input + Unlock button. Shown when the wallet is locked or
on first open after browser restart.
2. **Welcome**: Shown on first use. Three options: "Create a new wallet", "I
have a recovery phrase", "I have a private key". Password is set during this
first flow.
3. **Create**: Displays a generated 12-word recovery phrase with instructions to
write it down. User sets a password and confirms.
4. **Import recovery phrase**: Paste a 12 or 24 word recovery phrase. Password
fields shown only on first use.
5. **Import private key**: Paste a private key. Password fields shown only on
first use.
6. **Main**: All wallets listed, each showing its addresses with truncated
address and ETH balance. "+" next to HD wallets to add another address. "+
Add wallet" at the bottom. Settings and Lock buttons in the header. Future: a
sub-heading showing total portfolio value in USD (and eventually other
currencies).
7. **Add wallet**: Choose wallet type (new, recovery phrase, private key) — same
three options as Welcome but without password setup.
8. **Address detail**: Full address (click to copy), ETH balance, USD value
2. **Welcome**: Shown on first use. Two options: "Add wallet" (recovery phrase
based) and "Import private key". Password is set during the first wallet
addition.
3. **Add wallet**: A unified view for both creating and importing recovery
phrase wallets. The recovery phrase text area starts empty. A clickable die
button `[die]` generates a random 12-word phrase and fills it in. If the user
already has a phrase, they paste it directly. When the die is clicked, a
warning box appears reminding the user to write the phrase down. Password
fields are shown only on first use.
4. **Import private key**: Paste a private key. This creates a wallet with a
single address. Password fields shown only on first use.
5. **Main**: All wallets listed, each showing its addresses with truncated
address and ETH balance. "+" next to recovery phrase wallets to add another
address. "+ Add wallet" and "+ Import private key" buttons at the bottom.
Settings and Lock buttons in the header. Future: a sub-heading showing total
portfolio value in USD (and eventually other currencies).
6. **Address detail**: Full address (click to copy), ETH balance, USD value
(future), Send/Receive buttons, token list with "+ Add" button.
9. **Send**: Token selector, recipient address, amount. Cancel returns to
7. **Send**: Token selector, recipient address, amount. Cancel returns to
address detail.
10. **Receive**: Full address displayed with "Copy address" button.
11. **Add token**: Enter contract address. The extension looks up the token
name/symbol automatically.
12. **Settings**: Network (RPC endpoint URL) with explanatory text.
13. **Approval**: When a website requests wallet access or a signature, shows
8. **Receive**: Full address displayed with "Copy address" button.
9. **Add token**: Enter contract address. The extension looks up the token
name/symbol automatically.
10. **Settings**: Network (RPC endpoint URL) with explanatory text.
11. **Approval**: When a website requests wallet access or a signature, shows
the site origin, request details, and Allow/Deny buttons.
### External Services

View File

@ -41,48 +41,59 @@
<h1 class="font-bold border-b border-border pb-1 mb-3">
Welcome to AutistMask
</h1>
<p class="mb-3">
To get started, add a wallet. You can create a brand new one
or bring in one you already have.
</p>
<p class="mb-3">To get started, add a wallet.</p>
<div class="flex flex-col gap-2">
<button
id="btn-welcome-new"
id="btn-welcome-add"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Create a new wallet
</button>
<button
id="btn-welcome-recovery"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
I have a recovery phrase
Add wallet
</button>
<button
id="btn-welcome-key"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
I have a private key
Import private key
</button>
</div>
</div>
<!-- ============ CREATE NEW WALLET ============ -->
<div id="view-create" class="view hidden">
<!-- ============ ADD WALLET (unified create/import) ============ -->
<div id="view-add-wallet" class="view hidden">
<h1 class="font-bold border-b border-border pb-1 mb-3">
Create New Wallet
Add Wallet
</h1>
<p class="mb-2">
These 12 words are your recovery phrase. Write them down on
Enter your 12 or 24 word recovery phrase below, or press the
die to generate a new one.
</p>
<div class="mb-1 flex justify-end">
<button
id="btn-generate-phrase"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
title="Generate a random recovery phrase"
>
[&#9856;]
</button>
</div>
<div class="mb-2">
<textarea
id="wallet-mnemonic"
rows="3"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
placeholder="word word word ..."
></textarea>
</div>
<div
id="add-wallet-phrase-warning"
class="text-xs mb-2 border border-border border-dashed p-2 hidden"
>
These words are your recovery phrase. Write them down on
paper and keep them somewhere safe. Anyone with these words
can access your funds. If you lose them, your wallet cannot
be recovered.
</p>
<div
id="create-mnemonic"
class="border border-border p-2 mb-3 break-all select-all leading-relaxed"
></div>
<div class="mb-2">
</div>
<div class="mb-2" id="add-wallet-password-section">
<label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1">
This password locks the wallet on this device. It is not
@ -90,90 +101,34 @@
</p>
<input
type="password"
id="create-password"
id="add-wallet-password"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<div class="mb-2">
<div class="mb-2" id="add-wallet-password-confirm-section">
<label class="block mb-1">Confirm password</label>
<input
type="password"
id="create-password-confirm"
id="add-wallet-password-confirm"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<div class="flex gap-2">
<button
id="btn-create-confirm"
id="btn-add-wallet-confirm"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
I wrote it down — continue
Add
</button>
<button
id="btn-create-back"
id="btn-add-wallet-back"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Back
</button>
</div>
<div
id="create-error"
class="mt-2 border border-border border-dashed p-1 hidden"
></div>
</div>
<!-- ============ IMPORT RECOVERY PHRASE ============ -->
<div id="view-import-phrase" class="view hidden">
<h1 class="font-bold border-b border-border pb-1 mb-3">
Import Recovery Phrase
</h1>
<p class="mb-2">
Enter the 12 or 24 word recovery phrase from your existing
wallet. Separate each word with a space.
</p>
<div class="mb-2">
<textarea
id="import-mnemonic"
rows="3"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
placeholder="word word word ..."
></textarea>
</div>
<div class="mb-2" id="import-phrase-password-section">
<label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1">
This password locks the wallet on this device.
</p>
<input
type="password"
id="import-phrase-password"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<div class="mb-2" id="import-phrase-password-confirm-section">
<label class="block mb-1">Confirm password</label>
<input
type="password"
id="import-phrase-password-confirm"
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/>
</div>
<div class="flex gap-2">
<button
id="btn-import-phrase-confirm"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Import
</button>
<button
id="btn-import-phrase-back"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Back
</button>
</div>
<div
id="import-phrase-error"
id="add-wallet-error"
class="mt-2 border border-border border-dashed p-1 hidden"
></div>
</div>
@ -262,58 +217,18 @@
<div id="wallet-list"></div>
<!-- add wallet button -->
<div class="mt-3 border-t border-border pt-2">
<div class="mt-3 border-t border-border pt-2 flex gap-2">
<button
id="btn-add-wallet"
id="btn-main-add-wallet"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
+ Add wallet
</button>
</div>
</div>
<!-- ============ ADD WALLET (from main view) ============ -->
<div id="view-add-wallet" class="view hidden">
<h1 class="font-bold border-b border-border pb-1 mb-3">
Add Wallet
</h1>
<p class="mb-3">What kind of wallet do you want to add?</p>
<div class="flex flex-col gap-2">
<button
id="btn-add-wallet-new"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-left"
>
Create a new wallet
<span class="block text-xs text-muted"
>Generates a new recovery phrase with one
address</span
>
</button>
<button
id="btn-add-wallet-phrase"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-left"
>
Import recovery phrase
<span class="block text-xs text-muted"
>Use an existing 12 or 24 word recovery phrase</span
>
</button>
<button
id="btn-add-wallet-key"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer text-left"
>
Import private key
<span class="block text-xs text-muted"
>A single address from a private key</span
>
</button>
</div>
<div class="mt-3">
<button
id="btn-add-wallet-back"
id="btn-main-import-key"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
>
Back
+ Import private key
</button>
</div>
</div>

View File

@ -4,11 +4,9 @@
const VIEWS = [
"lock",
"welcome",
"create",
"import-phrase",
"add-wallet",
"import-key",
"main",
"add-wallet",
"address",
"send",
"receive",
@ -72,6 +70,25 @@ function makeStubAddress() {
};
}
function generateStubMnemonic() {
// TODO: replace with real BIP-39 generation via background
const words = [
"abandon",
"ability",
"able",
"about",
"above",
"absent",
"absorb",
"abstract",
"absurd",
"abuse",
"access",
"accident",
];
return words.join(" ");
}
// -- render wallet list on main view --
function renderWalletList() {
const container = $("wallet-list");
@ -102,7 +119,6 @@ function renderWalletList() {
});
container.innerHTML = html;
// bind clicks on address rows
container.querySelectorAll(".address-row").forEach((row) => {
row.addEventListener("click", () => {
state.selectedWallet = parseInt(row.dataset.wallet, 10);
@ -111,11 +127,11 @@ function renderWalletList() {
});
});
// bind clicks on + buttons within HD wallets
container.querySelectorAll(".btn-add-address").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.stopPropagation();
const wi = parseInt(btn.dataset.wallet, 10);
// TODO: derive next address from seed via background
state.wallets[wi].addresses.push(makeStubAddress());
renderWalletList();
});
@ -179,39 +195,29 @@ function addWalletAndGoToMain(wallet) {
showView("main");
}
function showImportView(type) {
if (type === "phrase") {
$("import-mnemonic").value = "";
hideError("import-phrase-error");
const needsPw = state.isFirstSetup;
$("import-phrase-password-section").classList.toggle(
"hidden",
!needsPw,
);
$("import-phrase-password-confirm-section").classList.toggle(
"hidden",
!needsPw,
);
showView("import-phrase");
} else {
$("import-private-key").value = "";
hideError("import-key-error");
const needsPw = state.isFirstSetup;
$("import-key-password-section").classList.toggle("hidden", !needsPw);
$("import-key-password-confirm-section").classList.toggle(
"hidden",
!needsPw,
);
showView("import-key");
}
function showAddWalletView() {
$("wallet-mnemonic").value = "";
$("add-wallet-phrase-warning").classList.add("hidden");
hideError("add-wallet-error");
const needsPw = state.isFirstSetup;
$("add-wallet-password-section").classList.toggle("hidden", !needsPw);
$("add-wallet-password-confirm-section").classList.toggle(
"hidden",
!needsPw,
);
showView("add-wallet");
}
function showCreateView() {
// TODO: generate real mnemonic via background
$("create-mnemonic").textContent =
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
hideError("create-error");
showView("create");
function showImportKeyView() {
$("import-private-key").value = "";
hideError("import-key-error");
const needsPw = state.isFirstSetup;
$("import-key-password-section").classList.toggle("hidden", !needsPw);
$("import-key-password-confirm-section").classList.toggle(
"hidden",
!needsPw,
);
showView("import-key");
}
function validatePasswords(pwId, pw2Id, errorId) {
@ -234,6 +240,15 @@ function validatePasswords(pwId, pw2Id, errorId) {
return true;
}
function backFromWalletAdd() {
if (state.isFirstSetup) {
showView("welcome");
} else {
renderWalletList();
showView("main");
}
}
// -- init --
function init() {
if (!state.hasWallet) {
@ -260,51 +275,29 @@ function init() {
});
// -- Welcome --
$("btn-welcome-new").addEventListener("click", showCreateView);
$("btn-welcome-recovery").addEventListener("click", () =>
showImportView("phrase"),
);
$("btn-welcome-key").addEventListener("click", () => showImportView("key"));
$("btn-welcome-add").addEventListener("click", showAddWalletView);
$("btn-welcome-key").addEventListener("click", showImportKeyView);
// -- Create wallet --
$("btn-create-confirm").addEventListener("click", () => {
if (
!validatePasswords(
"create-password",
"create-password-confirm",
"create-error",
)
) {
return;
}
hideError("create-error");
const walletNum = state.wallets.length + 1;
addWalletAndGoToMain({
type: "hd",
name: "Wallet " + walletNum,
mnemonic: $("create-mnemonic").textContent,
addresses: [makeStubAddress()],
});
// -- Add wallet (unified create/import) --
$("btn-generate-phrase").addEventListener("click", () => {
const phrase = generateStubMnemonic();
$("wallet-mnemonic").value = phrase;
$("add-wallet-phrase-warning").classList.remove("hidden");
});
$("btn-create-back").addEventListener("click", () => {
showView(state.isFirstSetup ? "welcome" : "add-wallet");
});
// -- Import recovery phrase --
$("btn-import-phrase-confirm").addEventListener("click", () => {
const mnemonic = $("import-mnemonic").value.trim();
$("btn-add-wallet-confirm").addEventListener("click", () => {
const mnemonic = $("wallet-mnemonic").value.trim();
if (!mnemonic) {
showError(
"import-phrase-error",
"Please enter your recovery phrase.",
"add-wallet-error",
"Please enter a recovery phrase or press the die to generate one.",
);
return;
}
const words = mnemonic.split(/\s+/);
if (words.length !== 12 && words.length !== 24) {
showError(
"import-phrase-error",
"add-wallet-error",
"Recovery phrase must be 12 or 24 words. You entered " +
words.length +
".",
@ -313,14 +306,14 @@ function init() {
}
if (
!validatePasswords(
"import-phrase-password",
"import-phrase-password-confirm",
"import-phrase-error",
"add-wallet-password",
"add-wallet-password-confirm",
"add-wallet-error",
)
) {
return;
}
hideError("import-phrase-error");
hideError("add-wallet-error");
const walletNum = state.wallets.length + 1;
addWalletAndGoToMain({
type: "hd",
@ -330,9 +323,7 @@ function init() {
});
});
$("btn-import-phrase-back").addEventListener("click", () => {
showView(state.isFirstSetup ? "welcome" : "add-wallet");
});
$("btn-add-wallet-back").addEventListener("click", backFromWalletAdd);
// -- Import private key --
$("btn-import-key-confirm").addEventListener("click", () => {
@ -360,9 +351,7 @@ function init() {
});
});
$("btn-import-key-back").addEventListener("click", () => {
showView(state.isFirstSetup ? "welcome" : "add-wallet");
});
$("btn-import-key-back").addEventListener("click", backFromWalletAdd);
// -- Main view --
$("btn-lock").addEventListener("click", () => {
@ -376,21 +365,8 @@ function init() {
showView("settings");
});
$("btn-add-wallet").addEventListener("click", () => {
showView("add-wallet");
});
// -- Add wallet menu (from main view) --
$("btn-add-wallet-new").addEventListener("click", showCreateView);
$("btn-add-wallet-phrase").addEventListener("click", () =>
showImportView("phrase"),
);
$("btn-add-wallet-key").addEventListener("click", () =>
showImportView("key"),
);
$("btn-add-wallet-back").addEventListener("click", () => {
showView("main");
});
$("btn-main-add-wallet").addEventListener("click", showAddWalletView);
$("btn-main-import-key").addEventListener("click", showImportKeyView);
// -- Address detail --
$("address-full").addEventListener("click", () => {