UX Surfaces
This document walks every primary route the wallet exposes. All routes live in @xchain-wallet/core (packages/core/src/shared/routes/) and render identically in the web SPA, the extension popup, the extension full-screen view, and the desktop renderer.
Route map
| Route | Component | Purpose |
|---|---|---|
| Onboarding | Onboarding.jsx |
First-run create / import / migrate / restore landing |
| Create wallet | CreateWallet.jsx |
Generate BIP39 mnemonic, set password, optional 25th-word passphrase |
| Import wallet | ImportWallet.jsx |
Restore from BIP39 or single WIF |
| Migrate to BIP39 | MigrateToBip39.jsx |
One-way migration from Counterwallet legacy mnemonic |
| Locked | Locked.jsx |
Password unlock screen |
| Loading | Loading.jsx |
Vault decryption progress |
| Home | Home.jsx |
Multi-chain balance dashboard, primary CTA hub |
| Send | Send.jsx |
Single-asset send with review + sign |
| Receive | Receive.jsx |
Address QR + label + chain picker |
| History | History.jsx |
Per-address action history |
| Address list | AddressList.jsx |
Standalone address browser with multisig badging + filter |
| Contacts | ContactsList.jsx |
Saved address book with edit / remove |
| Actions menu | ActionsMenu.jsx |
Indexed launcher for every supported action |
| Token wizard | TokenWizard.jsx |
Guided issue + mint + distribute flow for new token issuers |
| Issue | IssueTokenForm.jsx |
Raw ISSUE form with all parameters |
| Mint | MintForm.jsx |
MINT form |
| Destroy | DestroyForm.jsx |
DESTROY form |
| Token admin | TokenAdminForm.jsx |
Owner-only token settings (lock supply, transfer ownership, etc.) |
| Dispenser | DispenserForm.jsx |
Create dispenser flow |
| Dispensers list | DispensersList.jsx |
Browse open dispensers |
| Dispenser detail | DispenserDetail.jsx |
Buy / inspect / close a single dispenser |
| Dispenser explorer | DispenserExplorer.jsx |
Cross-token dispenser discovery surface |
| Dividend | DividendForm.jsx |
DIVIDEND form |
| Airdrop | AirdropForm.jsx |
Multi-recipient airdrop with parsed-recipients preview |
| Markets list | MarketsList.jsx |
DEX market discovery |
| Market view | MarketView.jsx |
Per-market view: price chart, place order, orderbook, recent trades, open orders, trade history |
| Compose message | ComposeMessage.jsx |
Encrypted-message compose (ECIES / ECDH / AES) |
| Messaging inbox | MessagingInbox.jsx |
Decrypted message list with reply |
| Multisig create | MultisigCreate.jsx |
n-of-m configuration setup |
| Multisig signing session | MultisigSigningSession.jsx |
Active session view: round labels, paste inbox, partials, finalize |
| Pair signer | PairSignerForm.jsx |
Pair a hardware or remote signer |
| Cross-chain swap | CrossChainSwapForm.jsx |
SWAP coordinator view across chains |
| Cross-chain templates | CrossChainTemplates.jsx |
Parallel composer presets (e.g. issue+seed dispenser on two chains atomically) |
| Parallel composer | ParallelComposer.jsx |
Custom multi-chain action sequence |
| Stake | StakeForm.jsx |
BTC STAKE form |
| Staking action | StakingActionForm.jsx |
UNSTAKE / COLLECT form |
| Staking dashboard | StakingDashboard.jsx |
Current stake + rewards + epoch view |
| Delegation | DelegationActionForm.jsx |
DELEGATE (rotate + revoke) form |
| Operator dashboard | OperatorDashboard.jsx |
Validator / operator view of delegations + uptime |
| Deploy contract | DeployContractForm.jsx |
DEPLOY form with source review + gas estimate |
| Execute contract | ExecuteContractForm.jsx |
EXECUTE method invocation |
| Contract funds | ContractFundsForm.jsx |
DEPOSIT / WITHDRAW form |
| Contracts list | ContractsList.jsx |
Per-chain deployed contracts |
| Contract detail | ContractDetail.jsx |
Bytecode + state + recent calls |
| Coinpay | CoinpayForm.jsx |
Native-coin payment leg of XChain action |
| Sweep | (in flow) | Send entire balance + dust to one destination |
| Link | LinkForm.jsx |
LINK action: cross-chain anchor |
| Broadcast | BroadcastForm.jsx |
BROADCAST action |
| Advanced action | AdvancedActionsForm.jsx |
Catch-all power-user form for any registered action descriptor |
| Swap | SwapForm.jsx |
SWAP action |
| View private key | ViewPrivateKey.jsx |
Per-address WIF export, gated behind password re-entry |
Onboarding
The first-run experience is one of:
- Create new — generate a fresh 24-word BIP39 mnemonic; user is required to acknowledge the phrase has been saved before the vault persists. Closing the tab at this stage leaves no persisted state.
- Import existing — paste a 12 / 15 / 18 / 21 / 24-word BIP39 mnemonic, with word-count + dictionary validation; or import a single WIF private key.
- Restore from backup file — re-wrap an exported vault under a fresh device password.
- Counterwallet migration — accept the legacy 12-word format, then optionally migrate to BIP39 from Settings later.
Optional 25th-word passphrase is offered on both Create and Import. The passphrase materially changes derived addresses; the wallet shows a pre-derivation preview to let the user confirm they’ve entered the correct passphrase.
Lock / unlock / auto-lock
- Manual lock — accessible from the global menu and via keyboard shortcut. Zeroes the in-memory master key + session key.
- Unlock — password entry; on success, master key is derived (Argon2id) and the vault decrypts.
- Foreground auto-lock — configurable timeout in Settings. Default: 5 minutes idle. Lock fires on tab/window blur for the popup; on idle-detection for the desktop and full-screen views.
- Browser-close lock — extension session-key namespace clears automatically. Web shell drops in-memory keys when the tab unloads.
- OS keychain auto-unlock — desktop only, opt-in. Stores the session master key in the OS keychain (Keychain on macOS, Credential Manager on Windows, Secret Service on Linux). Disabled by default; enabled via Settings → Security.
Home
The home dashboard renders:
- A per-chain balance summary (
ChainBalanceCard) with native-coin + token totals - A primary CTA row: Send / Receive / Buy / Stake (chain-conditional)
- A secondary action grid surfacing the most-used actions (Issue / Dispenser / Order / Compose / Stake / Multisig)
- A history strip with the last 5 actions across the wallet
- A messaging-inbox unread-count badge linking to the inbox
The same Home renders in the popup (compact), the full-screen / desktop (expanded with charts), and the web app (responsive between popup-like and full-screen-like layouts).
Send
The Send route is the canonical example of the sign-screen safety rail (see Security & Threat Model — Sign-screen safety rails):
- Form — destination, amount, asset (native or token), optional memo, optional fee tier
- Review — plain-English summary rendered alongside the encoded action string and the encoder’s PSBT
- Sign — signer-specific confirmation (in-vault password re-entry for software, device confirm for Trezor / Ledger)
- Broadcast — wallet broadcasts via SDK; status surface streams encoder accept → mempool → confirmed → indexed
- Done — txid + indexed action, with a link to History
Identical structure for every action form — only the form fields and review template differ.
Receive
- Address QR for the active chain + address-type
- Per-address label
- Chain picker (
ChainBadge+ chain-icon-only buttons witharia-label) - One-click copy for the address string (
CopyButton) - Optional BIP21 URI request (amount + label) for compatible payers
History
Per-address history view. Each row renders the decoded action plus its on-chain status:
- Type — SEND / ISSUE / MINT / ORDER / DISPENSER / DIVIDEND / etc.
- Counterparty — destination or source address (with contact-list lookup)
- Amount + asset
- Fee
- Status — pending / mempool / confirmed / indexed / failed
- Block height + timestamp once indexed
History reads from xchain-explorer via the SDK. The wallet never re-derives state from raw blockchain data.
Sign screens
Every action’s sign screen renders three parallel views:
| Pane | Source | Trust |
|---|---|---|
| Form values | The user’s own form input | Canonical |
| Decoded action summary | Re-decoded by the wallet from the action string | Canonical (deterministic decode) |
| Encoder PSBT | Returned from the encoder | Untrusted (encoder is a remote service) |
A discrepancy between the form pane and the decoded pane never happens unless the action descriptor or the decoder is buggy — both are tested by the same smoke suite. A discrepancy between either of those and the PSBT pane is what the user is being asked to catch by eye, and what the next-iteration byte-level cross-check will catch automatically.
Multisig session view
MultisigSigningSession.jsx renders an active n-of-m or MuSig2 session:
- Round label — explicit “Round 1 of 3 — commit”, “Round 2 of 3 — reveal”, “Round 3 of 3 — sign” so the user knows where they are in the protocol
- Paste inbox — accepts cosigner partial PSBTs by paste or QR scan
- Cosigner list — green check / pending dot per cosigner
- Camera scanner —
QrScanner.jsxreads chunked PSBT-QR viacore/src/uri/psbtQr.js - Animated frames —
AnimatedQrFrames.jsxpaints multi-frame QRs at 3 fps for the next cosigner; honorsprefers-reduced-motion: reducewith manual prev / next stepping
See Multisig for the full session state machine.
Contacts
- Saved address book with edit / remove
- Removed-row buttons announce “Remove address N” rather than the
×codepoint - Contact lookup is integrated into Send (autocomplete) and History (counterparty resolution)
QR scanner
QrScanner.jsx is the device-camera surface used by:
- Send (paste address from another device’s QR)
- Multisig signing session (scan a cosigner’s PSBT-QR frame stream)
- Pair signer (scan a remote-signer pair code)
The scanner runs through getUserMedia. The Chrome extension surfaces the runtime camera prompt only when the user explicitly clicks “Scan” — no implicit camera grant.
Command palette + keyboard shortcuts
Power users get:
- Command palette — Cmd/Ctrl + K opens an indexed launcher over every action route, settings panel, and help topic
- Keyboard shortcuts — Cmd/Ctrl + L (lock), Cmd/Ctrl + N (new send), Cmd/Ctrl + R (refresh balances), Esc (back / close), Tab/Shift+Tab focus order on every form
Settings
Settings are organized under sections:
- Security — auto-lock timeout, OS-keychain auto-unlock (desktop), view private key, change password, export backup
- Chains — per-chain endpoint URLs (encoder / explorer / hub), default fee strategy, address-type preference
- Accounts — manage HD accounts under each wallet
- Signers — registered hardware + remote signers, pair-new, remove, firmware version
- Connected sites — per-origin dApp grants with revoke
- Multisig — registered n-of-m configs
- Display — locale (i18n), theme, reduced-motion override
- Developer — developer-mode toggle, network override, custom RPC, diagnostic dump
- About —
package.json.version, build hash, license, third-party notices
Automatic donation system (ADS)
Optional opt-in. When enabled, a configurable percentage of every action fee is routed to a user-chosen donation address (default: the platform’s open-development fund). Off by default; surfaced in Settings → Display.
Micro-UX polish
AddressText.jsxtruncates long addresses with a hover-revealed full string and a one-click copyCopyButton.jsxflashes a confirmation toast on copyChainBadge.jsxcolor-codes each chain consistently across all surfacesMultisigBadge.jsxmarks multisig addresses with a chip +aria-labelDispenserBadge.jsxmarks dispenser-source addressesMnemonicGrid.jsxrenders the recovery phrase as a confirm-by-position grid on CreateDerivationPathCrossCheck.jsxshows the derivation path next to each derived address so users can verify correct derivation across wallets
Copyright © 2026 Dankest, LLC
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0-or-later). See LICENSE and NOTICE for full terms.