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

View File

@@ -41,48 +41,59 @@
<h1 class="font-bold border-b border-border pb-1 mb-3"> <h1 class="font-bold border-b border-border pb-1 mb-3">
Welcome to AutistMask Welcome to AutistMask
</h1> </h1>
<p class="mb-3"> <p class="mb-3">To get started, add a wallet.</p>
To get started, add a wallet. You can create a brand new one
or bring in one you already have.
</p>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<button <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" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
Create a new wallet Add 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
</button> </button>
<button <button
id="btn-welcome-key" id="btn-welcome-key"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer" 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> </button>
</div> </div>
</div> </div>
<!-- ============ CREATE NEW WALLET ============ --> <!-- ============ ADD WALLET (unified create/import) ============ -->
<div id="view-create" class="view hidden"> <div id="view-add-wallet" class="view hidden">
<h1 class="font-bold border-b border-border pb-1 mb-3"> <h1 class="font-bold border-b border-border pb-1 mb-3">
Create New Wallet Add Wallet
</h1> </h1>
<p class="mb-2"> <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 paper and keep them somewhere safe. Anyone with these words
can access your funds. If you lose them, your wallet cannot can access your funds. If you lose them, your wallet cannot
be recovered. be recovered.
</p> </div>
<div <div class="mb-2" id="add-wallet-password-section">
id="create-mnemonic"
class="border border-border p-2 mb-3 break-all select-all leading-relaxed"
></div>
<div class="mb-2">
<label class="block mb-1">Choose a password</label> <label class="block mb-1">Choose a password</label>
<p class="text-xs text-muted mb-1"> <p class="text-xs text-muted mb-1">
This password locks the wallet on this device. It is not This password locks the wallet on this device. It is not
@@ -90,90 +101,34 @@
</p> </p>
<input <input
type="password" 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" class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/> />
</div> </div>
<div class="mb-2"> <div class="mb-2" id="add-wallet-password-confirm-section">
<label class="block mb-1">Confirm password</label> <label class="block mb-1">Confirm password</label>
<input <input
type="password" 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" class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
/> />
</div> </div>
<div class="flex gap-2"> <div class="flex gap-2">
<button <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" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
I wrote it down — continue Add
</button> </button>
<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" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
Back Back
</button> </button>
</div> </div>
<div <div
id="create-error" id="add-wallet-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"
class="mt-2 border border-border border-dashed p-1 hidden" class="mt-2 border border-border border-dashed p-1 hidden"
></div> ></div>
</div> </div>
@@ -262,58 +217,18 @@
<div id="wallet-list"></div> <div id="wallet-list"></div>
<!-- add wallet button --> <!-- 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 <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" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
+ Add wallet + Add wallet
</button> </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 <button
id="btn-add-wallet-new" id="btn-main-import-key"
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"
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer" class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
> >
Back + Import private key
</button> </button>
</div> </div>
</div> </div>

View File

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