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; a green “Active” pill marks each chain’s active address, “Use” sets it, and “Add address” opens AddAddressModal (batch-generate 1-25, Coin + Type)
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 by a warning in an unlocked session (no password re-entry), requires unlock when the wallet is locked
Alerts AlertsRoute.jsx Notification tray: lists info / warning / critical alerts with optional action button per item
Attach content AttachContentForm.jsx Attach a file (with optional title) to a token as on-chain content; polls for confirmation
Contract staked positions ContractStakedPositions.jsx Lists the wallet’s active stakes against deployed contracts
Stake on contract ContractStakeForm.jsx STAKE-to-contract form; BTC-only at launch; scoped to a specific contract by action index
Bind controller ControllerBindForm.jsx CONTROLLERBIND action form; sets per-class policy rules (transfer, trade, burn, mint, stake) on a token
Manage token ManageToken.jsx Owner hub for a token: metadata, holders panel, supply, and links to admin sub-forms
Market activity MarketActivity.jsx Live market feed; opens on the XCHAIN token by default; tap the token header to switch markets
Menu MenuRoute.jsx Full-screen pancake menu opened from the shared app header; links to all top-level sections
My tokens MyTokens.jsx Tokens issued by or owned by the active account; links to issue-new flow
Sign PSBT PsbtSignForm.jsx Paste-in PSBT signing surface; accepts hex or base64; routes hardware signing via signer bridge
Project roster ProjectRosterForm.jsx Manage official tokens linked to a project (LINK action); polls for roster updates
Receive picker ReceivePicker.jsx Token-picker wrapper scoped to receive; pre-filters to assets the wallet can receive
Rename account RenameAccountForm.jsx Inline form to rename an HD account; header carries Back + Save
Rename wallet RenameWalletForm.jsx Inline form to rename a wallet; header carries Back + Save
Scan ScanRoute.jsx Dedicated full-screen QR scanner; classifies each frame via detectQrContent and routes to the matching flow; does not import WIF / mnemonic directly
Sell token name SellOwnershipForm.jsx Form to list a token name for sale via the SELL action
Send picker SendPicker.jsx Token-picker wrapper scoped to send; pre-filters to assets the wallet can send
Settings Settings.jsx Tabbed settings host covering security, chains, accounts, signers, connected sites, multisig, display, developer, and about
Select signer SignerSelectForm.jsx Inline picker for choosing or adding a signer when initiating a signing round
Sign message SignMessageForm.jsx Sign an arbitrary message with a wallet address; returns a copyable signature
Token detail TokenDetail.jsx Full token info view: metadata, balance, price, recent activity, holder snapshot
Verify signature VerifySignatureForm.jsx Verify a message signature against an address without needing a wallet password
Wallet details WalletDetails.jsx View and manage a single wallet: rename, migrate to BIP39, export backup, delete
Wallet picker WalletPicker.jsx List all wallets with an Add Wallet affordance; reached from the gear / account menu
Account picker AccountPicker.jsx Switch between HD accounts under a wallet; add account or rename from this view
Add account AddAccountForm.jsx Derive a new HD account under the active wallet

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, scoped to each chain’s active address (not an across-all-addresses total)
  • 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):

  1. Form: from-address (defaults to the chain’s active address, with an advanced override picker), destination, amount, asset (native or token), optional memo, optional fee tier
  2. Review: plain-English summary rendered alongside the encoded action string and the encoder’s PSBT
  3. Sign: signer-specific confirmation (in-vault password re-entry for software, device confirm for Trezor / Ledger)
  4. Broadcast: wallet broadcasts via SDK; status surface streams encoder accept → mempool → confirmed → indexed
  5. Done: txid + indexed action, with a link to History
stateDiagram-v2
    state "encoder accept" as EncoderAccept
    state "mempool" as Mempool
    state "confirmed" as Confirmed
    state "indexed" as Indexed
    state "failed" as Failed

    [*] --> Form
    Form --> Review
    Review --> Sign
    Sign --> Broadcast
    Broadcast --> EncoderAccept
    EncoderAccept --> Mempool
    Mempool --> Confirmed
    Confirmed --> Indexed
    Confirmed --> Failed
    Indexed --> Done
    Done --> [*]
    Failed --> [*]

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 with aria-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.jsx reads chunked PSBT-QR via core/src/uri/psbtQr.js
  • Animated frames: AnimatedQrFrames.jsx paints multi-frame QRs at 3 fps for the next cosigner; honors prefers-reduced-motion: reduce with 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.jsx truncates long addresses with a hover-revealed full string and a one-click copy
  • CopyButton.jsx flashes a confirmation toast on copy
  • ChainBadge.jsx color-codes each chain consistently across all surfaces
  • MultisigBadge.jsx marks multisig addresses with a chip + aria-label
  • DispenserBadge.jsx marks dispenser-source addresses
  • MnemonicGrid.jsx renders the recovery phrase as a confirm-by-position grid on Create
  • DerivationPathCrossCheck.jsx shows 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.

Edit this page on GitHub ↗