Redesign UI for non-technical users
All checks were successful
check / check (push) Successful in 13s
All checks were successful
check / check (push) Successful in 13s
Replace jargon-heavy terminal-style UI with plain-language views. New data model: wallets (HD or private key) contain addresses. Main view lists all addresses grouped by wallet with balances. HD wallets get a "+" to add addresses; key wallets have one. Two import paths: recovery phrase and private key. All labels use plain English, full-sentence errors, inline help text. README updated with full UI philosophy, language guide, data model, and navigation docs.
This commit is contained in:
123
README.md
123
README.md
@@ -72,7 +72,11 @@ manifest/
|
|||||||
### UI Design Philosophy
|
### UI Design Philosophy
|
||||||
|
|
||||||
The UI follows a "Universal Paperclips" aesthetic — a deliberately spartan,
|
The UI follows a "Universal Paperclips" aesthetic — a deliberately spartan,
|
||||||
almost brutalist approach:
|
almost brutalist approach. The guiding principle is that an unskilled,
|
||||||
|
non-technical person should be able to figure out how to use it without any
|
||||||
|
crypto knowledge.
|
||||||
|
|
||||||
|
#### Visual Style
|
||||||
|
|
||||||
- **Monochrome**: Black text on white background. No brand colors, no gradients,
|
- **Monochrome**: Black text on white background. No brand colors, no gradients,
|
||||||
no color-coding. Color may be introduced later for specific semantic purposes
|
no color-coding. Color may be introduced later for specific semantic purposes
|
||||||
@@ -84,9 +88,6 @@ almost brutalist approach:
|
|||||||
Ethereum addresses, transaction hashes, and balances are inherently
|
Ethereum addresses, transaction hashes, and balances are inherently
|
||||||
fixed-width data. Rather than mixing proportional and monospace fonts, we use
|
fixed-width data. Rather than mixing proportional and monospace fonts, we use
|
||||||
monospace everywhere for visual consistency and alignment.
|
monospace everywhere for visual consistency and alignment.
|
||||||
- **Icon fonts only**: Where icons are needed (copy, send, settings, etc.) we
|
|
||||||
use an icon font (no SVGs, no PNGs, no image assets). This keeps the extension
|
|
||||||
size tiny and the build simple.
|
|
||||||
- **No images**: Zero image assets in the entire extension. No logos, no
|
- **No images**: Zero image assets in the entire extension. No logos, no
|
||||||
illustrations, no token icons. Token identity is conveyed by symbol text (ETH,
|
illustrations, no token icons. Token identity is conveyed by symbol text (ETH,
|
||||||
USDC, etc.).
|
USDC, etc.).
|
||||||
@@ -100,28 +101,76 @@ almost brutalist approach:
|
|||||||
- **360x600 popup**: Standard browser extension popup dimensions. The UI is
|
- **360x600 popup**: Standard browser extension popup dimensions. The UI is
|
||||||
designed for this fixed viewport — no responsive breakpoints needed.
|
designed for this fixed viewport — no responsive breakpoints needed.
|
||||||
|
|
||||||
|
#### Language & Labeling
|
||||||
|
|
||||||
|
All user-facing text avoids crypto jargon wherever possible:
|
||||||
|
|
||||||
|
- "Recovery phrase" instead of "seed phrase", "mnemonic", or "BIP-39 mnemonic"
|
||||||
|
- "Address" instead of "account", "derived key", or "HD child"
|
||||||
|
- "Password" instead of "encryption key" or "vault passphrase"
|
||||||
|
- "Private key" instead of "secret key" or "signing key"
|
||||||
|
- Buttons use plain verbs: "Send", "Receive", "Copy address", "Add", "Back",
|
||||||
|
"Cancel", "Lock", "Unlock", "Allow", "Deny"
|
||||||
|
- No bracket notation like `[locked]` or `[setup]` — just plain titles
|
||||||
|
- Helpful inline descriptions where needed (e.g. "This password locks the wallet
|
||||||
|
on this device. It is not the same as your recovery phrase.")
|
||||||
|
- Error messages are full sentences ("Please enter your password." not "password
|
||||||
|
required")
|
||||||
|
|
||||||
|
#### Data Model
|
||||||
|
|
||||||
|
The core hierarchy is **Wallets → Addresses**:
|
||||||
|
|
||||||
|
- A **wallet** is either:
|
||||||
|
- An **HD wallet** (recovery phrase): generates multiple addresses from a
|
||||||
|
single 12/24 word recovery phrase using BIP-39/BIP-44 derivation. The user
|
||||||
|
can add more addresses with a "+" button.
|
||||||
|
- A **key wallet** (private key): a single address imported directly from a
|
||||||
|
private key. No "+" button since there is only one address.
|
||||||
|
- An **address** holds ETH and any user-added ERC-20 tokens.
|
||||||
|
- The user can have multiple wallets, each with multiple addresses (HD) or a
|
||||||
|
single address (key).
|
||||||
|
|
||||||
|
#### Navigation
|
||||||
|
|
||||||
|
The main view shows all addresses grouped by wallet, with ETH balances inline.
|
||||||
|
The user taps an address to see its detail view (full address, balance, tokens,
|
||||||
|
send/receive). Navigation is flat — every view has a "Back" or "Cancel" button
|
||||||
|
that returns to the previous context. No deep nesting, no tabs, no hamburger
|
||||||
|
menus.
|
||||||
|
|
||||||
### UI Views
|
### UI Views
|
||||||
|
|
||||||
The popup has the following views, switched via simple show/hide:
|
The popup has the following views, switched via simple show/hide:
|
||||||
|
|
||||||
1. **Lock screen**: Password input + unlock button. Shown when the wallet is
|
1. **Lock**: Password input + Unlock button. Shown when the wallet is locked or
|
||||||
locked or on first open after browser restart.
|
on first open after browser restart.
|
||||||
2. **Setup / Onboarding**: Shown on first use. Create a new wallet (generates
|
2. **Welcome**: Shown on first use. Three options: "Create a new wallet", "I
|
||||||
BIP-39 mnemonic, user confirms backup, sets password) or import an existing
|
have a recovery phrase", "I have a private key". Password is set during this
|
||||||
seed phrase.
|
first flow.
|
||||||
3. **Main / Account view**: The default view after unlock. Shows the current
|
3. **Create**: Displays a generated 12-word recovery phrase with instructions to
|
||||||
account address (truncated, click to copy), ETH balance, list of added ERC-20
|
write it down. User sets a password and confirms.
|
||||||
tokens with balances, and action buttons (Send, Receive, Settings).
|
4. **Import recovery phrase**: Paste a 12 or 24 word recovery phrase. Password
|
||||||
4. **Send**: Form with To address, amount, token selector (ETH or any added
|
fields shown only on first use.
|
||||||
ERC-20), and a Send button. Shows gas estimate before confirming.
|
5. **Import private key**: Paste a private key. Password fields shown only on
|
||||||
5. **Receive**: Displays the current account address in full (copyable). That's
|
first use.
|
||||||
it.
|
6. **Main**: All wallets listed, each showing its addresses with truncated
|
||||||
6. **Settings**: Configure RPC endpoint URL, manage wallets (add/remove seed
|
address and ETH balance. "+" next to HD wallets to add another address. "+
|
||||||
phrases), derive additional accounts, view/export seed phrase (requires
|
Add wallet" at the bottom. Settings and Lock buttons in the header. Future: a
|
||||||
password re-entry), manage added ERC-20 tokens.
|
sub-heading showing total portfolio value in USD (and eventually other
|
||||||
7. **Approval popups**: When a connected site requests a transaction signature
|
currencies).
|
||||||
or message signature, an approval view shows the details and Approve/Reject
|
7. **Add wallet**: Choose wallet type (new, recovery phrase, private key) — same
|
||||||
buttons.
|
three options as Welcome but without password setup.
|
||||||
|
8. **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
|
||||||
|
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
|
||||||
|
the site origin, request details, and Allow/Deny buttons.
|
||||||
|
|
||||||
### External Services
|
### External Services
|
||||||
|
|
||||||
@@ -167,30 +216,31 @@ want maximum privacy can point it at their own Ethereum node.
|
|||||||
|
|
||||||
### Supported Functionality
|
### Supported Functionality
|
||||||
|
|
||||||
- Create new wallet from generated BIP-39 mnemonic
|
- Create new HD wallet (generates 12-word recovery phrase)
|
||||||
- Import wallet from existing BIP-39 mnemonic
|
- Import HD wallet from existing 12 or 24 word recovery phrase
|
||||||
- Derive multiple HD addresses per wallet (`m/44'/60'/0'/0/n`)
|
- Import single-address wallet from private key
|
||||||
- View ETH balance
|
- Add multiple addresses within an HD wallet
|
||||||
|
- Manage multiple wallets simultaneously
|
||||||
|
- View ETH balance per address
|
||||||
- View ERC-20 token balances (user adds token by contract address)
|
- View ERC-20 token balances (user adds token by contract address)
|
||||||
- Send ETH to an address
|
- Send ETH to an address
|
||||||
- Send ERC-20 tokens to an address
|
- Send ERC-20 tokens to an address
|
||||||
- Receive ETH/tokens (display address + copy to clipboard)
|
- Receive ETH/tokens (display address, copy to clipboard)
|
||||||
- Connect to web3 sites (EIP-1193 `eth_requestAccounts`)
|
- Connect to web3 sites (EIP-1193 `eth_requestAccounts`)
|
||||||
- Sign transactions requested by connected sites
|
- Sign transactions requested by connected sites
|
||||||
- Sign messages (`personal_sign`, `eth_sign`)
|
- Sign messages (`personal_sign`, `eth_sign`)
|
||||||
- Switch between wallets/accounts
|
|
||||||
- Lock/unlock with password
|
- Lock/unlock with password
|
||||||
- Configurable RPC endpoint
|
- Configurable RPC endpoint
|
||||||
|
- Future: USD value display (and other fiat currencies)
|
||||||
|
|
||||||
### Non-Goals
|
### Non-Goals
|
||||||
|
|
||||||
- Token swaps (use a DEX in the browser)
|
- Token swaps (use a DEX in the browser)
|
||||||
- Portfolio/price tracking (balances shown in token units only, no fiat)
|
|
||||||
- NFT display or management
|
- NFT display or management
|
||||||
- Multi-chain support (Ethereum mainnet only, for now)
|
- Multi-chain support (Ethereum mainnet only, for now)
|
||||||
- Analytics, telemetry, or tracking of any kind
|
- Analytics, telemetry, or tracking of any kind
|
||||||
- Advertisements or promotions
|
- Advertisements or promotions
|
||||||
- Phishing detection (use your brain)
|
- Phishing detection
|
||||||
- Hardware wallet support (maybe later)
|
- Hardware wallet support (maybe later)
|
||||||
- Token list auto-discovery (user adds tokens manually)
|
- Token list auto-discovery (user adds tokens manually)
|
||||||
- Fiat on/off ramps
|
- Fiat on/off ramps
|
||||||
@@ -200,19 +250,22 @@ want maximum privacy can point it at their own Ethereum node.
|
|||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- [x] Project scaffolding (Makefile, Dockerfile, CI, manifests)
|
- [x] Project scaffolding (Makefile, Dockerfile, CI, manifests)
|
||||||
- [ ] Set up Tailwind CSS build pipeline
|
- [x] Set up Tailwind CSS build pipeline
|
||||||
- [ ] Build popup UI views (lock, setup, main, send, receive, settings)
|
- [x] Build popup UI views (all 13 views with stub state)
|
||||||
- [ ] Implement BIP-39 mnemonic generation and validation
|
- [ ] Implement BIP-39 mnemonic generation and validation
|
||||||
- [ ] Implement BIP-32/BIP-44 HD key derivation for Ethereum
|
- [ ] Implement BIP-32/BIP-44 HD key derivation for Ethereum
|
||||||
- [ ] Implement encrypted storage for seed phrases
|
- [ ] Implement private key import and address derivation
|
||||||
|
- [ ] Implement encrypted storage for wallet data
|
||||||
- [ ] Implement background wallet manager
|
- [ ] Implement background wallet manager
|
||||||
- [ ] Implement EIP-1193 provider and content script injection
|
- [ ] Implement EIP-1193 provider and content script injection
|
||||||
- [ ] Implement ETH balance lookup and send
|
- [ ] Implement ETH balance lookup via RPC
|
||||||
|
- [ ] Implement ETH send (transaction construction + signing)
|
||||||
- [ ] Implement ERC-20 token management (add by contract, view balance, send)
|
- [ ] Implement ERC-20 token management (add by contract, view balance, send)
|
||||||
- [ ] Implement site connection approval flow
|
- [ ] Implement site connection approval flow
|
||||||
- [ ] Implement transaction signing approval flow
|
- [ ] Implement transaction signing approval flow
|
||||||
- [ ] Implement message signing (`personal_sign`, `eth_sign`)
|
- [ ] Implement message signing (`personal_sign`, `eth_sign`)
|
||||||
- [ ] Add configurable RPC endpoint
|
- [ ] Add USD value display (price feed TBD)
|
||||||
|
- [ ] Add multi-currency fiat display support
|
||||||
- [ ] Test on Chrome and Firefox
|
- [ ] Test on Chrome and Firefox
|
||||||
- [ ] Write tests for crypto operations
|
- [ ] Write tests for crypto operations
|
||||||
- [ ] Write tests for transaction construction
|
- [ ] Write tests for transaction construction
|
||||||
|
|||||||
@@ -11,22 +11,24 @@
|
|||||||
<!-- ============ LOCK SCREEN ============ -->
|
<!-- ============ LOCK SCREEN ============ -->
|
||||||
<div id="view-lock" class="view hidden">
|
<div id="view-lock" 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">
|
||||||
AutistMask [locked]
|
AutistMask
|
||||||
</h1>
|
</h1>
|
||||||
|
<p class="mb-3">
|
||||||
|
Your wallet is locked. Enter your password to continue.
|
||||||
|
</p>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Password:</label>
|
<label class="block mb-1">Password</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="unlock-password"
|
id="unlock-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"
|
||||||
placeholder="enter password to unlock"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
id="btn-unlock"
|
id="btn-unlock"
|
||||||
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"
|
||||||
>
|
>
|
||||||
unlock
|
Unlock
|
||||||
</button>
|
</button>
|
||||||
<div
|
<div
|
||||||
id="unlock-error"
|
id="unlock-error"
|
||||||
@@ -34,57 +36,70 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ SETUP: WELCOME ============ -->
|
<!-- ============ WELCOME / FIRST USE ============ -->
|
||||||
<div id="view-setup" class="view hidden">
|
<div id="view-welcome" 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">
|
||||||
AutistMask [setup]
|
Welcome to AutistMask
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mb-3">No wallet found. Create or import one.</p>
|
<p class="mb-3">
|
||||||
<div class="flex gap-2">
|
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">
|
||||||
<button
|
<button
|
||||||
id="btn-setup-create"
|
id="btn-welcome-new"
|
||||||
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 new wallet
|
Create a new wallet
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-setup-import"
|
id="btn-welcome-recovery"
|
||||||
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"
|
||||||
>
|
>
|
||||||
import seed phrase
|
I have a recovery phrase
|
||||||
|
</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
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ SETUP: CREATE ============ -->
|
<!-- ============ CREATE NEW WALLET ============ -->
|
||||||
<div id="view-create" class="view hidden">
|
<div id="view-create" 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">
|
||||||
AutistMask [create wallet]
|
Create New Wallet
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mb-2">
|
<p class="mb-2">
|
||||||
Write down this seed phrase and store it safely. It is the
|
These 12 words are your recovery phrase. Write them down on
|
||||||
only way to recover your wallet.
|
paper and keep them somewhere safe. Anyone with these words
|
||||||
|
can access your funds. If you lose them, your wallet cannot
|
||||||
|
be recovered.
|
||||||
</p>
|
</p>
|
||||||
<div
|
<div
|
||||||
id="create-mnemonic"
|
id="create-mnemonic"
|
||||||
class="border border-border p-2 mb-3 font-mono break-all select-all"
|
class="border border-border p-2 mb-3 break-all select-all leading-relaxed"
|
||||||
></div>
|
></div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Set password:</label>
|
<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
|
||||||
|
the same as your recovery phrase.
|
||||||
|
</p>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="create-password"
|
id="create-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"
|
||||||
placeholder="password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<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="create-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"
|
||||||
placeholder="confirm password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@@ -92,13 +107,13 @@
|
|||||||
id="btn-create-confirm"
|
id="btn-create-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 backed it up, create wallet
|
I wrote it down — continue
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-create-back"
|
id="btn-create-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
|
||||||
@@ -107,61 +122,119 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ SETUP: IMPORT ============ -->
|
<!-- ============ IMPORT RECOVERY PHRASE ============ -->
|
||||||
<div id="view-import" class="view hidden">
|
<div id="view-import-phrase" 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">
|
||||||
AutistMask [import wallet]
|
Import Recovery Phrase
|
||||||
</h1>
|
</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">
|
<div class="mb-2">
|
||||||
<label class="block mb-1"
|
|
||||||
>Seed phrase (12 or 24 words):</label
|
|
||||||
>
|
|
||||||
<textarea
|
<textarea
|
||||||
id="import-mnemonic"
|
id="import-mnemonic"
|
||||||
rows="3"
|
rows="3"
|
||||||
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
|
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg resize-y"
|
||||||
placeholder="word1 word2 word3 ..."
|
placeholder="word word word ..."
|
||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2" id="import-phrase-password-section">
|
||||||
<label class="block mb-1">Set password:</label>
|
<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
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
id="import-password"
|
id="import-phrase-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"
|
||||||
placeholder="password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2" id="import-phrase-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="import-password-confirm"
|
id="import-phrase-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"
|
||||||
placeholder="confirm password"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
id="btn-import-confirm"
|
id="btn-import-phrase-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"
|
||||||
>
|
>
|
||||||
import wallet
|
Import
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-import-back"
|
id="btn-import-phrase-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="import-error"
|
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>
|
||||||
|
|
||||||
<!-- ============ MAIN / ACCOUNT VIEW ============ -->
|
<!-- ============ IMPORT PRIVATE KEY ============ -->
|
||||||
|
<div id="view-import-key" class="view hidden">
|
||||||
|
<h1 class="font-bold border-b border-border pb-1 mb-3">
|
||||||
|
Import Private Key
|
||||||
|
</h1>
|
||||||
|
<p class="mb-2">
|
||||||
|
Paste your private key below. This wallet will have a single
|
||||||
|
address.
|
||||||
|
</p>
|
||||||
|
<div class="mb-2">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="import-private-key"
|
||||||
|
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||||
|
placeholder="0x..."
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2" id="import-key-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-key-password"
|
||||||
|
class="border border-border p-1 w-full font-mono text-sm bg-bg text-fg"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-2" id="import-key-password-confirm-section">
|
||||||
|
<label class="block mb-1">Confirm password</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
id="import-key-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-key-confirm"
|
||||||
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
|
>
|
||||||
|
Import
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
id="btn-import-key-back"
|
||||||
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
id="import-key-error"
|
||||||
|
class="mt-2 border border-border border-dashed p-1 hidden"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ MAIN VIEW: ALL WALLETS & ADDRESSES ============ -->
|
||||||
<div id="view-main" class="view hidden">
|
<div id="view-main" class="view hidden">
|
||||||
<div
|
<div
|
||||||
class="flex justify-between items-center border-b border-border pb-1 mb-2"
|
class="flex justify-between items-center border-b border-border pb-1 mb-2"
|
||||||
@@ -171,61 +244,128 @@
|
|||||||
<button
|
<button
|
||||||
id="btn-settings"
|
id="btn-settings"
|
||||||
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer"
|
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
title="settings"
|
title="Settings"
|
||||||
>
|
>
|
||||||
[=]
|
Settings
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-lock"
|
id="btn-lock"
|
||||||
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer"
|
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
title="lock"
|
title="Lock wallet"
|
||||||
>
|
>
|
||||||
[x]
|
Lock
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- account selector -->
|
<!-- wallet list -->
|
||||||
<div class="mb-2">
|
<div id="wallet-list"></div>
|
||||||
<div class="flex justify-between items-center">
|
|
||||||
<select
|
<!-- add wallet button -->
|
||||||
id="account-selector"
|
<div class="mt-3 border-t border-border pt-2">
|
||||||
class="border border-border p-1 font-mono text-sm bg-bg text-fg flex-1 mr-2"
|
|
||||||
></select>
|
|
||||||
<button
|
<button
|
||||||
id="btn-copy-address"
|
id="btn-add-wallet"
|
||||||
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
title="copy address"
|
|
||||||
>
|
>
|
||||||
[cp]
|
+ Add wallet
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
|
||||||
id="current-address"
|
|
||||||
class="text-xs text-muted mt-1 break-all"
|
|
||||||
></div>
|
|
||||||
</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"
|
||||||
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ============ ADDRESS DETAIL VIEW ============ -->
|
||||||
|
<div id="view-address" class="view hidden">
|
||||||
|
<div
|
||||||
|
class="flex justify-between items-center border-b border-border pb-1 mb-2"
|
||||||
|
>
|
||||||
|
<h1 class="font-bold" id="address-title">Address</h1>
|
||||||
|
<button
|
||||||
|
id="btn-address-back"
|
||||||
|
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="address-full"
|
||||||
|
class="text-xs break-all mb-1 cursor-pointer"
|
||||||
|
title="Click to copy"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="text-xs text-muted mb-3"
|
||||||
|
id="address-copied-msg"
|
||||||
|
></div>
|
||||||
|
|
||||||
<!-- balance -->
|
<!-- balance -->
|
||||||
<div class="border-b border-border-light pb-2 mb-2">
|
<div class="border-b border-border-light pb-2 mb-2">
|
||||||
<div class="text-base font-bold">
|
<div class="text-base font-bold">
|
||||||
<span id="eth-balance">0.0000</span> ETH
|
<span id="address-eth-balance">0.0000</span> ETH
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
class="text-xs text-muted"
|
||||||
|
id="address-usd-value"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- action buttons -->
|
<!-- actions -->
|
||||||
<div class="flex gap-2 mb-3">
|
<div class="flex gap-2 mb-3">
|
||||||
<button
|
<button
|
||||||
id="btn-send"
|
id="btn-send"
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer flex-1"
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer flex-1"
|
||||||
>
|
>
|
||||||
send
|
Send
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-receive"
|
id="btn-receive"
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer flex-1"
|
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer flex-1"
|
||||||
>
|
>
|
||||||
receive
|
Receive
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -239,24 +379,22 @@
|
|||||||
id="btn-add-token"
|
id="btn-add-token"
|
||||||
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
|
class="border border-border px-1 hover:bg-fg hover:text-bg cursor-pointer text-xs"
|
||||||
>
|
>
|
||||||
[+]
|
+ Add
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="token-list">
|
<div id="token-list">
|
||||||
<div class="text-muted text-xs py-1">
|
<div class="text-muted text-xs py-1">
|
||||||
no tokens added
|
No tokens added yet. Use "+ Add" to track a token.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ SEND VIEW ============ -->
|
<!-- ============ SEND ============ -->
|
||||||
<div id="view-send" class="view hidden">
|
<div id="view-send" 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">Send</h1>
|
||||||
AutistMask [send]
|
|
||||||
</h1>
|
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Token:</label>
|
<label class="block mb-1">What to send</label>
|
||||||
<select
|
<select
|
||||||
id="send-token"
|
id="send-token"
|
||||||
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"
|
||||||
@@ -265,16 +403,16 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">To address:</label>
|
<label class="block mb-1">To</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="send-to"
|
id="send-to"
|
||||||
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"
|
||||||
placeholder="0x..."
|
placeholder="Recipient address (0x...)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Amount:</label>
|
<label class="block mb-1">Amount</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="send-amount"
|
id="send-amount"
|
||||||
@@ -283,7 +421,7 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
id="send-gas-estimate"
|
id="send-fee-estimate"
|
||||||
class="text-xs text-muted mb-2 hidden"
|
class="text-xs text-muted mb-2 hidden"
|
||||||
></div>
|
></div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@@ -291,13 +429,13 @@
|
|||||||
id="btn-send-confirm"
|
id="btn-send-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"
|
||||||
>
|
>
|
||||||
send
|
Send
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-send-back"
|
id="btn-send-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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -306,39 +444,46 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ RECEIVE VIEW ============ -->
|
<!-- ============ RECEIVE ============ -->
|
||||||
<div id="view-receive" class="view hidden">
|
<div id="view-receive" 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">
|
||||||
AutistMask [receive]
|
Receive
|
||||||
</h1>
|
</h1>
|
||||||
<p class="mb-2">Your address:</p>
|
<p class="mb-2">
|
||||||
|
Share this address with the sender. Make sure you only use
|
||||||
|
this address to receive Ethereum tokens.
|
||||||
|
</p>
|
||||||
<div
|
<div
|
||||||
id="receive-address"
|
id="receive-address"
|
||||||
class="border border-border p-2 font-mono break-all select-all mb-3"
|
class="border border-border p-2 break-all select-all mb-3"
|
||||||
></div>
|
></div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button
|
<button
|
||||||
id="btn-receive-copy"
|
id="btn-receive-copy"
|
||||||
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"
|
||||||
>
|
>
|
||||||
copy to clipboard
|
Copy address
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-receive-back"
|
id="btn-receive-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>
|
||||||
|
|
||||||
<!-- ============ ADD TOKEN VIEW ============ -->
|
<!-- ============ ADD TOKEN ============ -->
|
||||||
<div id="view-add-token" class="view hidden">
|
<div id="view-add-token" 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">
|
||||||
AutistMask [add token]
|
Add Token
|
||||||
</h1>
|
</h1>
|
||||||
|
<p class="mb-2">
|
||||||
|
Enter the contract address of the token you want to track.
|
||||||
|
You can find this on the token's page on Etherscan.
|
||||||
|
</p>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<label class="block mb-1">Token contract address:</label>
|
<label class="block mb-1">Contract address</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="add-token-address"
|
id="add-token-address"
|
||||||
@@ -355,13 +500,13 @@
|
|||||||
id="btn-add-token-confirm"
|
id="btn-add-token-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"
|
||||||
>
|
>
|
||||||
add token
|
Add
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-add-token-back"
|
id="btn-add-token-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
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -370,81 +515,50 @@
|
|||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ============ SETTINGS VIEW ============ -->
|
<!-- ============ SETTINGS ============ -->
|
||||||
<div id="view-settings" class="view hidden">
|
<div id="view-settings" 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">
|
||||||
AutistMask [settings]
|
Settings
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<h2 class="font-bold mb-1">RPC Endpoint</h2>
|
<h2 class="font-bold mb-1">Network</h2>
|
||||||
|
<p class="text-xs text-muted mb-1">
|
||||||
|
The server used to talk to the Ethereum network. Change this
|
||||||
|
if you run your own node or prefer a different provider.
|
||||||
|
</p>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
id="settings-rpc"
|
id="settings-rpc"
|
||||||
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"
|
||||||
placeholder="https://..."
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
id="btn-save-rpc"
|
id="btn-save-rpc"
|
||||||
class="border border-border px-2 py-1 mt-1 hover:bg-fg hover:text-bg cursor-pointer"
|
class="border border-border px-2 py-1 mt-1 hover:bg-fg hover:text-bg cursor-pointer"
|
||||||
>
|
>
|
||||||
save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="font-bold mb-1">Accounts</h2>
|
|
||||||
<div class="mb-3">
|
|
||||||
<button
|
|
||||||
id="btn-derive-account"
|
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
|
||||||
>
|
|
||||||
derive next account
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h2 class="font-bold mb-1">Wallets</h2>
|
|
||||||
<div class="mb-3">
|
|
||||||
<button
|
|
||||||
id="btn-show-seed"
|
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
|
||||||
>
|
|
||||||
show seed phrase
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
id="btn-import-additional"
|
|
||||||
class="border border-border px-2 py-1 hover:bg-fg hover:text-bg cursor-pointer"
|
|
||||||
>
|
|
||||||
import another wallet
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
id="settings-seed-display"
|
|
||||||
class="border border-border p-2 font-mono break-all mb-3 hidden"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<h2 class="font-bold mb-1">Tokens</h2>
|
|
||||||
<div id="settings-token-list" class="mb-3">
|
|
||||||
<div class="text-muted text-xs">no tokens added</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="border-t border-border pt-2">
|
<div class="border-t border-border pt-2">
|
||||||
<button
|
<button
|
||||||
id="btn-settings-back"
|
id="btn-settings-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>
|
||||||
|
|
||||||
<!-- ============ APPROVAL VIEW ============ -->
|
<!-- ============ APPROVAL ============ -->
|
||||||
<div id="view-approve" class="view hidden">
|
<div id="view-approve" 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">
|
||||||
AutistMask [approve request]
|
A website is requesting access
|
||||||
</h1>
|
</h1>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<div class="text-xs text-muted mb-1">
|
<div class="text-xs text-muted mb-1">
|
||||||
Site: <span id="approve-origin"></span>
|
From:
|
||||||
|
<span id="approve-origin" class="font-bold"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-bold mb-1" id="approve-type"></div>
|
<div class="font-bold mb-1" id="approve-type"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -457,13 +571,13 @@
|
|||||||
id="btn-approve"
|
id="btn-approve"
|
||||||
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"
|
||||||
>
|
>
|
||||||
approve
|
Allow
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="btn-reject"
|
id="btn-reject"
|
||||||
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"
|
||||||
>
|
>
|
||||||
reject
|
Deny
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
// AutistMask popup UI — view management and event wiring
|
// AutistMask popup UI — view management and event wiring
|
||||||
// All wallet logic will live in background/; this file is purely UI.
|
// All wallet logic will live in background/; this file is purely UI.
|
||||||
|
|
||||||
const views = [
|
const VIEWS = [
|
||||||
"lock",
|
"lock",
|
||||||
"setup",
|
"welcome",
|
||||||
"create",
|
"create",
|
||||||
"import",
|
"import-phrase",
|
||||||
|
"import-key",
|
||||||
"main",
|
"main",
|
||||||
|
"add-wallet",
|
||||||
|
"address",
|
||||||
"send",
|
"send",
|
||||||
"receive",
|
"receive",
|
||||||
"add-token",
|
"add-token",
|
||||||
@@ -15,7 +18,7 @@ const views = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function showView(name) {
|
function showView(name) {
|
||||||
for (const v of views) {
|
for (const v of VIEWS) {
|
||||||
const el = document.getElementById(`view-${v}`);
|
const el = document.getElementById(`view-${v}`);
|
||||||
if (el) {
|
if (el) {
|
||||||
el.classList.toggle("hidden", v !== name);
|
el.classList.toggle("hidden", v !== name);
|
||||||
@@ -24,15 +27,18 @@ function showView(name) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// -- mock state (will be replaced by background messaging) --
|
// -- mock state (will be replaced by background messaging) --
|
||||||
|
// A wallet is either { type: "hd", name, mnemonic, addresses: [...] }
|
||||||
|
// or { type: "key", name, privateKey, addresses: [single] }.
|
||||||
|
// Each address is { address, balance, tokens: [...] }.
|
||||||
const state = {
|
const state = {
|
||||||
locked: true,
|
locked: true,
|
||||||
hasWallet: false,
|
hasWallet: false,
|
||||||
password: null,
|
password: null,
|
||||||
accounts: [],
|
wallets: [],
|
||||||
selectedAccount: 0,
|
selectedWallet: null,
|
||||||
tokens: [],
|
selectedAddress: null,
|
||||||
rpcUrl: "https://eth.llamarpc.com",
|
rpcUrl: "https://eth.llamarpc.com",
|
||||||
mnemonic: null,
|
isFirstSetup: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
// -- helpers --
|
// -- helpers --
|
||||||
@@ -52,63 +58,190 @@ function hideError(id) {
|
|||||||
|
|
||||||
function truncateAddress(addr) {
|
function truncateAddress(addr) {
|
||||||
if (!addr) return "";
|
if (!addr) return "";
|
||||||
return addr.slice(0, 6) + "..." + addr.slice(-4);
|
return addr.slice(0, 6) + "\u2026" + addr.slice(-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateAccountSelector() {
|
function makeStubAddress() {
|
||||||
const sel = $("account-selector");
|
const hex = Array.from({ length: 40 }, () =>
|
||||||
sel.innerHTML = "";
|
Math.floor(Math.random() * 16).toString(16),
|
||||||
state.accounts.forEach((acct, i) => {
|
).join("");
|
||||||
const opt = document.createElement("option");
|
return {
|
||||||
opt.value = i;
|
address: "0x" + hex,
|
||||||
opt.textContent = `Account ${i}: ${truncateAddress(acct.address)}`;
|
balance: "0.0000",
|
||||||
sel.appendChild(opt);
|
tokens: [],
|
||||||
});
|
};
|
||||||
sel.value = state.selectedAccount;
|
|
||||||
$("current-address").textContent =
|
|
||||||
state.accounts[state.selectedAccount]?.address || "";
|
|
||||||
$("eth-balance").textContent =
|
|
||||||
state.accounts[state.selectedAccount]?.balance || "0.0000";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateTokenList() {
|
// -- render wallet list on main view --
|
||||||
const list = $("token-list");
|
function renderWalletList() {
|
||||||
if (state.tokens.length === 0) {
|
const container = $("wallet-list");
|
||||||
list.innerHTML =
|
if (state.wallets.length === 0) {
|
||||||
'<div class="text-muted text-xs py-1">no tokens added</div>';
|
container.innerHTML =
|
||||||
|
'<p class="text-muted py-2">No wallets yet. Add one to get started.</p>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
list.innerHTML = state.tokens
|
|
||||||
|
let html = "";
|
||||||
|
state.wallets.forEach((wallet, wi) => {
|
||||||
|
html += `<div class="mb-3">`;
|
||||||
|
html += `<div class="flex justify-between items-center border-b border-border pb-1 mb-1">`;
|
||||||
|
html += `<span class="font-bold">${wallet.name}</span>`;
|
||||||
|
if (wallet.type === "hd") {
|
||||||
|
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>`;
|
||||||
|
|
||||||
|
wallet.addresses.forEach((addr, ai) => {
|
||||||
|
html += `<div class="address-row flex justify-between items-center py-1 border-b border-border-light cursor-pointer hover:bg-hover px-1" data-wallet="${wi}" data-address="${ai}">`;
|
||||||
|
html += `<span class="text-xs">${truncateAddress(addr.address)}</span>`;
|
||||||
|
html += `<span class="text-xs">${addr.balance} ETH</span>`;
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
});
|
||||||
|
container.innerHTML = html;
|
||||||
|
|
||||||
|
// bind clicks on address rows
|
||||||
|
container.querySelectorAll(".address-row").forEach((row) => {
|
||||||
|
row.addEventListener("click", () => {
|
||||||
|
state.selectedWallet = parseInt(row.dataset.wallet, 10);
|
||||||
|
state.selectedAddress = parseInt(row.dataset.address, 10);
|
||||||
|
showAddressDetail();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
state.wallets[wi].addresses.push(makeStubAddress());
|
||||||
|
renderWalletList();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function showAddressDetail() {
|
||||||
|
const wallet = state.wallets[state.selectedWallet];
|
||||||
|
const addr = wallet.addresses[state.selectedAddress];
|
||||||
|
$("address-title").textContent = wallet.name;
|
||||||
|
$("address-full").textContent = addr.address;
|
||||||
|
$("address-copied-msg").textContent = "";
|
||||||
|
$("address-eth-balance").textContent = addr.balance;
|
||||||
|
$("address-usd-value").textContent = "";
|
||||||
|
updateTokenList(addr);
|
||||||
|
updateSendTokenSelect(addr);
|
||||||
|
showView("address");
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTokenList(addr) {
|
||||||
|
const list = $("token-list");
|
||||||
|
if (addr.tokens.length === 0) {
|
||||||
|
list.innerHTML =
|
||||||
|
'<div class="text-muted text-xs py-1">No tokens added yet. Use "+ Add" to track a token.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
list.innerHTML = addr.tokens
|
||||||
.map(
|
.map(
|
||||||
(t) =>
|
(t) =>
|
||||||
`<div class="py-1 border-b border-border-light flex justify-between">` +
|
`<div class="py-1 border-b border-border-light flex justify-between">` +
|
||||||
`<span>${t.symbol}</span>` +
|
`<span>${t.symbol}</span>` +
|
||||||
`<span>${t.balance || "0.0000"}</span>` +
|
`<span>${t.balance || "0"}</span>` +
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
.join("");
|
.join("");
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSendTokenSelect() {
|
function updateSendTokenSelect(addr) {
|
||||||
const sel = $("send-token");
|
const sel = $("send-token");
|
||||||
sel.innerHTML = '<option value="ETH">ETH</option>';
|
sel.innerHTML = '<option value="ETH">ETH</option>';
|
||||||
state.tokens.forEach((t) => {
|
addr.tokens.forEach((t) => {
|
||||||
const opt = document.createElement("option");
|
const opt = document.createElement("option");
|
||||||
opt.value = t.address;
|
opt.value = t.contractAddress;
|
||||||
opt.textContent = t.symbol;
|
opt.textContent = t.symbol;
|
||||||
sel.appendChild(opt);
|
sel.appendChild(opt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function currentAddress() {
|
||||||
|
if (state.selectedWallet === null || state.selectedAddress === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return state.wallets[state.selectedWallet].addresses[state.selectedAddress];
|
||||||
|
}
|
||||||
|
|
||||||
|
function addWalletAndGoToMain(wallet) {
|
||||||
|
state.wallets.push(wallet);
|
||||||
|
state.hasWallet = true;
|
||||||
|
state.isFirstSetup = false;
|
||||||
|
renderWalletList();
|
||||||
|
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 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) {
|
||||||
|
if (!state.isFirstSetup) return true;
|
||||||
|
const pw = $(pwId).value;
|
||||||
|
const pw2 = $(pw2Id).value;
|
||||||
|
if (!pw) {
|
||||||
|
showError(errorId, "Please choose a password.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pw.length < 8) {
|
||||||
|
showError(errorId, "Password must be at least 8 characters.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (pw !== pw2) {
|
||||||
|
showError(errorId, "Passwords do not match.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.password = pw;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// -- init --
|
// -- init --
|
||||||
function init() {
|
function init() {
|
||||||
// For now, always show setup (no wallet exists yet).
|
|
||||||
// Once background messaging is wired, this will check actual state.
|
|
||||||
if (!state.hasWallet) {
|
if (!state.hasWallet) {
|
||||||
showView("setup");
|
showView("welcome");
|
||||||
} else if (state.locked) {
|
} else if (state.locked) {
|
||||||
showView("lock");
|
showView("lock");
|
||||||
} else {
|
} else {
|
||||||
|
renderWalletList();
|
||||||
showView("main");
|
showView("main");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,100 +249,119 @@ function init() {
|
|||||||
$("btn-unlock").addEventListener("click", () => {
|
$("btn-unlock").addEventListener("click", () => {
|
||||||
const pw = $("unlock-password").value;
|
const pw = $("unlock-password").value;
|
||||||
if (!pw) {
|
if (!pw) {
|
||||||
showError("unlock-error", "enter a password");
|
showError("unlock-error", "Please enter your password.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideError("unlock-error");
|
hideError("unlock-error");
|
||||||
// TODO: send unlock message to background
|
// TODO: send unlock message to background
|
||||||
state.locked = false;
|
state.locked = false;
|
||||||
updateAccountSelector();
|
renderWalletList();
|
||||||
updateTokenList();
|
|
||||||
showView("main");
|
showView("main");
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Setup --
|
// -- Welcome --
|
||||||
$("btn-setup-create").addEventListener("click", () => {
|
$("btn-welcome-new").addEventListener("click", showCreateView);
|
||||||
// TODO: request mnemonic generation from background
|
$("btn-welcome-recovery").addEventListener("click", () =>
|
||||||
$("create-mnemonic").textContent =
|
showImportView("phrase"),
|
||||||
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about";
|
);
|
||||||
showView("create");
|
$("btn-welcome-key").addEventListener("click", () => showImportView("key"));
|
||||||
});
|
|
||||||
|
|
||||||
$("btn-setup-import").addEventListener("click", () => {
|
|
||||||
showView("import");
|
|
||||||
});
|
|
||||||
|
|
||||||
// -- Create wallet --
|
// -- Create wallet --
|
||||||
$("btn-create-confirm").addEventListener("click", () => {
|
$("btn-create-confirm").addEventListener("click", () => {
|
||||||
const pw = $("create-password").value;
|
if (
|
||||||
const pw2 = $("create-password-confirm").value;
|
!validatePasswords(
|
||||||
if (!pw) {
|
"create-password",
|
||||||
showError("create-error", "enter a password");
|
"create-password-confirm",
|
||||||
return;
|
"create-error",
|
||||||
}
|
)
|
||||||
if (pw !== pw2) {
|
) {
|
||||||
showError("create-error", "passwords do not match");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideError("create-error");
|
hideError("create-error");
|
||||||
// TODO: send create wallet message to background
|
const walletNum = state.wallets.length + 1;
|
||||||
state.hasWallet = true;
|
addWalletAndGoToMain({
|
||||||
state.locked = false;
|
type: "hd",
|
||||||
state.password = pw;
|
name: "Wallet " + walletNum,
|
||||||
state.mnemonic = $("create-mnemonic").textContent;
|
mnemonic: $("create-mnemonic").textContent,
|
||||||
state.accounts = [
|
addresses: [makeStubAddress()],
|
||||||
{
|
});
|
||||||
address: "0x0000000000000000000000000000000000000001",
|
|
||||||
balance: "0.0000",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
state.selectedAccount = 0;
|
|
||||||
updateAccountSelector();
|
|
||||||
updateTokenList();
|
|
||||||
showView("main");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-create-back").addEventListener("click", () => {
|
$("btn-create-back").addEventListener("click", () => {
|
||||||
showView("setup");
|
showView(state.isFirstSetup ? "welcome" : "add-wallet");
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Import wallet --
|
// -- Import recovery phrase --
|
||||||
$("btn-import-confirm").addEventListener("click", () => {
|
$("btn-import-phrase-confirm").addEventListener("click", () => {
|
||||||
const mnemonic = $("import-mnemonic").value.trim();
|
const mnemonic = $("import-mnemonic").value.trim();
|
||||||
const pw = $("import-password").value;
|
|
||||||
const pw2 = $("import-password-confirm").value;
|
|
||||||
if (!mnemonic) {
|
if (!mnemonic) {
|
||||||
showError("import-error", "enter a seed phrase");
|
showError(
|
||||||
|
"import-phrase-error",
|
||||||
|
"Please enter your recovery phrase.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!pw) {
|
const words = mnemonic.split(/\s+/);
|
||||||
showError("import-error", "enter a password");
|
if (words.length !== 12 && words.length !== 24) {
|
||||||
|
showError(
|
||||||
|
"import-phrase-error",
|
||||||
|
"Recovery phrase must be 12 or 24 words. You entered " +
|
||||||
|
words.length +
|
||||||
|
".",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (pw !== pw2) {
|
if (
|
||||||
showError("import-error", "passwords do not match");
|
!validatePasswords(
|
||||||
|
"import-phrase-password",
|
||||||
|
"import-phrase-password-confirm",
|
||||||
|
"import-phrase-error",
|
||||||
|
)
|
||||||
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideError("import-error");
|
hideError("import-phrase-error");
|
||||||
// TODO: validate mnemonic and send to background
|
const walletNum = state.wallets.length + 1;
|
||||||
state.hasWallet = true;
|
addWalletAndGoToMain({
|
||||||
state.locked = false;
|
type: "hd",
|
||||||
state.password = pw;
|
name: "Wallet " + walletNum,
|
||||||
state.mnemonic = mnemonic;
|
mnemonic: mnemonic,
|
||||||
state.accounts = [
|
addresses: [makeStubAddress()],
|
||||||
{
|
});
|
||||||
address: "0x0000000000000000000000000000000000000001",
|
|
||||||
balance: "0.0000",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
state.selectedAccount = 0;
|
|
||||||
updateAccountSelector();
|
|
||||||
updateTokenList();
|
|
||||||
showView("main");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-import-back").addEventListener("click", () => {
|
$("btn-import-phrase-back").addEventListener("click", () => {
|
||||||
showView("setup");
|
showView(state.isFirstSetup ? "welcome" : "add-wallet");
|
||||||
|
});
|
||||||
|
|
||||||
|
// -- Import private key --
|
||||||
|
$("btn-import-key-confirm").addEventListener("click", () => {
|
||||||
|
const key = $("import-private-key").value.trim();
|
||||||
|
if (!key) {
|
||||||
|
showError("import-key-error", "Please enter your private key.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!validatePasswords(
|
||||||
|
"import-key-password",
|
||||||
|
"import-key-password-confirm",
|
||||||
|
"import-key-error",
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hideError("import-key-error");
|
||||||
|
const walletNum = state.wallets.length + 1;
|
||||||
|
addWalletAndGoToMain({
|
||||||
|
type: "key",
|
||||||
|
name: "Wallet " + walletNum,
|
||||||
|
privateKey: key,
|
||||||
|
addresses: [makeStubAddress()],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
$("btn-import-key-back").addEventListener("click", () => {
|
||||||
|
showView(state.isFirstSetup ? "welcome" : "add-wallet");
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Main view --
|
// -- Main view --
|
||||||
@@ -224,33 +376,50 @@ function init() {
|
|||||||
showView("settings");
|
showView("settings");
|
||||||
});
|
});
|
||||||
|
|
||||||
$("account-selector").addEventListener("change", (e) => {
|
$("btn-add-wallet").addEventListener("click", () => {
|
||||||
state.selectedAccount = parseInt(e.target.value, 10);
|
showView("add-wallet");
|
||||||
$("current-address").textContent =
|
|
||||||
state.accounts[state.selectedAccount]?.address || "";
|
|
||||||
$("eth-balance").textContent =
|
|
||||||
state.accounts[state.selectedAccount]?.balance || "0.0000";
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-copy-address").addEventListener("click", () => {
|
// -- Add wallet menu (from main view) --
|
||||||
const addr = state.accounts[state.selectedAccount]?.address;
|
$("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-full").addEventListener("click", () => {
|
||||||
|
const addr = $("address-full").textContent;
|
||||||
if (addr) {
|
if (addr) {
|
||||||
navigator.clipboard.writeText(addr);
|
navigator.clipboard.writeText(addr);
|
||||||
|
$("address-copied-msg").textContent = "Copied!";
|
||||||
|
setTimeout(() => {
|
||||||
|
$("address-copied-msg").textContent = "";
|
||||||
|
}, 2000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$("btn-address-back").addEventListener("click", () => {
|
||||||
|
renderWalletList();
|
||||||
|
showView("main");
|
||||||
|
});
|
||||||
|
|
||||||
$("btn-send").addEventListener("click", () => {
|
$("btn-send").addEventListener("click", () => {
|
||||||
updateSendTokenSelect();
|
|
||||||
$("send-to").value = "";
|
$("send-to").value = "";
|
||||||
$("send-amount").value = "";
|
$("send-amount").value = "";
|
||||||
$("send-gas-estimate").classList.add("hidden");
|
$("send-fee-estimate").classList.add("hidden");
|
||||||
$("send-status").classList.add("hidden");
|
$("send-status").classList.add("hidden");
|
||||||
showView("send");
|
showView("send");
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-receive").addEventListener("click", () => {
|
$("btn-receive").addEventListener("click", () => {
|
||||||
$("receive-address").textContent =
|
const addr = currentAddress();
|
||||||
state.accounts[state.selectedAccount]?.address || "";
|
$("receive-address").textContent = addr ? addr.address : "";
|
||||||
showView("receive");
|
showView("receive");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,23 +435,23 @@ function init() {
|
|||||||
const to = $("send-to").value.trim();
|
const to = $("send-to").value.trim();
|
||||||
const amount = $("send-amount").value.trim();
|
const amount = $("send-amount").value.trim();
|
||||||
if (!to) {
|
if (!to) {
|
||||||
showError("send-status", "enter a recipient address");
|
showError("send-status", "Please enter a recipient address.");
|
||||||
$("send-status").classList.remove("hidden");
|
$("send-status").classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!amount || isNaN(parseFloat(amount))) {
|
if (!amount || isNaN(parseFloat(amount)) || parseFloat(amount) <= 0) {
|
||||||
showError("send-status", "enter a valid amount");
|
showError("send-status", "Please enter a valid amount.");
|
||||||
$("send-status").classList.remove("hidden");
|
$("send-status").classList.remove("hidden");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: construct and send transaction via background
|
// TODO: construct and send transaction via background
|
||||||
const el = $("send-status");
|
const el = $("send-status");
|
||||||
el.textContent = "transaction sent (stub)";
|
el.textContent = "Sent! (stub)";
|
||||||
el.classList.remove("hidden");
|
el.classList.remove("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-send-back").addEventListener("click", () => {
|
$("btn-send-back").addEventListener("click", () => {
|
||||||
showView("main");
|
showAddressDetail();
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Receive --
|
// -- Receive --
|
||||||
@@ -294,30 +463,35 @@ function init() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$("btn-receive-back").addEventListener("click", () => {
|
$("btn-receive-back").addEventListener("click", () => {
|
||||||
showView("main");
|
showAddressDetail();
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Add Token --
|
// -- Add Token --
|
||||||
$("btn-add-token-confirm").addEventListener("click", () => {
|
$("btn-add-token-confirm").addEventListener("click", () => {
|
||||||
const addr = $("add-token-address").value.trim();
|
const contractAddr = $("add-token-address").value.trim();
|
||||||
if (!addr || !addr.startsWith("0x")) {
|
if (!contractAddr || !contractAddr.startsWith("0x")) {
|
||||||
showError("add-token-error", "enter a valid contract address");
|
showError(
|
||||||
|
"add-token-error",
|
||||||
|
"Please enter a valid contract address starting with 0x.",
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hideError("add-token-error");
|
hideError("add-token-error");
|
||||||
// TODO: look up token name/symbol/decimals from contract via background
|
// TODO: look up token name/symbol/decimals from contract via background
|
||||||
state.tokens.push({
|
const addr = currentAddress();
|
||||||
address: addr,
|
if (addr) {
|
||||||
|
addr.tokens.push({
|
||||||
|
contractAddress: contractAddr,
|
||||||
symbol: "TKN",
|
symbol: "TKN",
|
||||||
decimals: 18,
|
decimals: 18,
|
||||||
balance: "0.0000",
|
balance: "0",
|
||||||
});
|
});
|
||||||
updateTokenList();
|
}
|
||||||
showView("main");
|
showAddressDetail();
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-add-token-back").addEventListener("click", () => {
|
$("btn-add-token-back").addEventListener("click", () => {
|
||||||
showView("main");
|
showAddressDetail();
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Settings --
|
// -- Settings --
|
||||||
@@ -326,45 +500,21 @@ function init() {
|
|||||||
// TODO: persist via background
|
// TODO: persist via background
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-derive-account").addEventListener("click", () => {
|
|
||||||
const idx = state.accounts.length;
|
|
||||||
// TODO: derive from seed via background
|
|
||||||
state.accounts.push({
|
|
||||||
address: `0x${idx.toString(16).padStart(40, "0")}`,
|
|
||||||
balance: "0.0000",
|
|
||||||
});
|
|
||||||
updateAccountSelector();
|
|
||||||
});
|
|
||||||
|
|
||||||
$("btn-show-seed").addEventListener("click", () => {
|
|
||||||
const display = $("settings-seed-display");
|
|
||||||
if (display.classList.contains("hidden")) {
|
|
||||||
// TODO: require password re-entry, get from background
|
|
||||||
display.textContent = state.mnemonic || "(no seed loaded)";
|
|
||||||
display.classList.remove("hidden");
|
|
||||||
} else {
|
|
||||||
display.classList.add("hidden");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
$("btn-import-additional").addEventListener("click", () => {
|
|
||||||
showView("import");
|
|
||||||
});
|
|
||||||
|
|
||||||
$("btn-settings-back").addEventListener("click", () => {
|
$("btn-settings-back").addEventListener("click", () => {
|
||||||
updateAccountSelector();
|
renderWalletList();
|
||||||
updateTokenList();
|
|
||||||
showView("main");
|
showView("main");
|
||||||
});
|
});
|
||||||
|
|
||||||
// -- Approval --
|
// -- Approval --
|
||||||
$("btn-approve").addEventListener("click", () => {
|
$("btn-approve").addEventListener("click", () => {
|
||||||
// TODO: send approval to background
|
// TODO: send approval to background
|
||||||
|
renderWalletList();
|
||||||
showView("main");
|
showView("main");
|
||||||
});
|
});
|
||||||
|
|
||||||
$("btn-reject").addEventListener("click", () => {
|
$("btn-reject").addEventListener("click", () => {
|
||||||
// TODO: send rejection to background
|
// TODO: send rejection to background
|
||||||
|
renderWalletList();
|
||||||
showView("main");
|
showView("main");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user