# Introduction Welcome to the Salmon Wallet documentation. Salmon is the **open-source, community-owned, self-custodial Solana wallet**—built to make crypto easy and safe for storing, buying, sending, receiving, and swapping tokens and NFTs. ## Supported chains and recovery - **Chains:** Solana and Bitcoin. Solana ships with mainnet and dev/test environments; Bitcoin supports mainnet, testnet, and regtest (for development builds). - **Derivation paths (BIP44):** Solana uses `m/44'/501'/{index}'/0'`; Bitcoin uses `m/44'/0'/{index}'/0/0`. The `{index}` increases per wallet you create. - **Recovering elsewhere:** Use the same seed phrase plus the matching path above in any BIP44-compatible wallet to import your Salmon accounts. ## What is Salmon? Salmon keeps private keys on your device, focuses on the speed of Solana, and invites the community to audit and improve the code. These docs are here to help you: - Install and restore Salmon safely. - Understand how self-custody and recovery work. - Connect Salmon to Solana dApps with confidence and see what’s leaving your wallet before you sign. - Follow updates from the team and community. > We want Salmon to be 100% transparent. Open-source software promotes decentralization and allows for a more democratic and inclusive ecosystem. It enables anyone to participate and contribute to the project, fostering long-term sustainability and growth. It also helps to ensure the security and reliability of the code. Why should we use a closed-source self-custodial wallet then? ## Core principles - **Self-custodial safety** — Your tokens, your control, your keys. Private keys stay on your device and are encrypted with your password. - **Open-source transparency** — The codebase is open for review so security and reliability can be independently verified. - **Community ownership** — Contributions shape the roadmap; we grow Salmon together. # Installation / Usage ## Use Salmon today ::steps ### Chrome/Brave extension (recommended) Install the Salmon extension from the Chrome Web Store: {rel=""nofollow""}. Confirm you’re on the official listing before installing. The extension uses the same self-custodial model as the web app. ### Web app Go to [v2.salmonwallet.io](https://v2.salmonwallet.io){rel=""nofollow""} or [salmonwallet.io](https://salmonwallet.io){rel=""nofollow""} and launch the web wallet. Always verify the URL before you sign in. ### Create or restore your wallet Choose **Create** to generate a new self-custodial wallet or **Restore** to import your existing recovery phrase. Your keys never leave your device. ### Secure your recovery phrase Write the recovery phrase on paper and store it offline. Do not share it with anyone. Anyone with the phrase can move your funds. ### Connect to Solana dApps Use Salmon to approve transactions and signatures when connecting to Solana applications. Confirm the network and permissions before approving. :: ## Coming soon ::prose-steps ### Firefox extension The Firefox extension is in progress. Watch for the official release link from [salmonwallet.io](https://salmonwallet.io){rel=""nofollow""} or the [blog](https://medium.com/@salmonwallet){rel=""nofollow""}. ### Android and iOS apps Mobile apps are planned. Until then, use the web app or extension. We’ll announce mobile availability on the blog and official site. :: ## Verify authenticity - Always check you are on `v2.salmonwallet.io` or `salmonwallet.io`. - For extensions, only install from the official Chrome Web Store listing linked on our site. - Follow updates on [medium.com/@salmonwallet](https://medium.com/@salmonwallet){rel=""nofollow""} for release announcements. # Why Self-Custody Matters Self-custody means you control your private keys—no third party can move your funds. Salmon is built around this principle to keep you in charge of your assets. ## Why self-custody? - **Control:** Only you can approve transactions; there’s no centralized switch that can freeze or seize funds. - **Resilience:** Outages or policy changes from a company can’t lock you out of your wallet. - **Privacy:** You decide when and how to share signing data; no custodial service intermediates your activity. - **Transparency:** Open-source code lets the community audit how keys are generated, stored, and used. ## How Salmon enforces it - Keys are generated and stored locally; mnemonics never leave your device. - Signing happens in an isolated context; dApps see approvals, not your secrets. - Recovery is handled via your seed phrase—there’s no “forgot password” that bypasses your control. - Open-source development invites audits and contributions to keep the stack trustworthy. ## Good practices - Back up your recovery phrase offline; never share or type it into websites. - Set a strong password and lock Salmon when you’re away from your device. - Verify dApp domains and signing summaries before approving. - Use devnet for testing and keep mainnet activity intentional. Self-custody is the safest path for a Solana wallet because it keeps power with the holder: you. Salmon’s design, transparency, and community ownership reinforce that promise. # Contribute ## **Why contribute** Salmon is open-source and community-owned. Contributions keep the wallet transparent, secure, and aligned with what Solana users need most. ## **Ways to help** - Review and test code changes to strengthen security. - Improve the documentation to make onboarding clearer for new users. - Share feedback in issues and discussion threads so we keep building what matters. ## **Editing the docs** 1. Clone the repository locally. 2. Install dependencies with `npm install`. 3. Run `npm run dev` to preview the docs and make changes. 4. Open a pull request with a clear description of what you improved. ## **Community resources** - Official Website: [salmonwallet.io](https://salmonwallet.io){rel=""nofollow""} - Updates and announcements: [x.com/@salmonwallet](https://x.com/salmonwallet){rel=""nofollow""} - Official Blog: [medium.com/@salmonwallet](https://medium.com/@salmonwallet){rel=""nofollow""} # Release Notes Release notes are coming soon. Follow the [blog](https://medium.com/@salmonwallet){rel=""nofollow""} for the latest updates in the meantime. # Stack, scripts, and contributing ## Stack at a glance - React 18 + React Native 0.70 for shared UI and logic. - Web client uses Create React App; browser extensions target Chrome/Brave and Firefox. - Solana integration via `@solana/web3.js` 1.95.x; Bitcoin flows use BitcoinJS. - Node 18+ required; serverless helpers ship for release automation. ## Repository layout - `src/` – shared React components, hooks, screens, and blockchain integration logic. - `extension/` – background/content scripts and manifests for Chromium/Firefox bundles. - `android/`, `ios/` – React Native platform projects (run pods in `ios/` after install). - `assets/`, `public/` – static assets consumed across targets. - Env templates live at repo root (`env.local.json`, `env.develop.json`, `env.main.json`, `env.prod.json`). ## Install and run ```bash yarn install cd ios && pod install # iOS only ``` Pick an environment by setting `REACT_APP_SALMON_ENV` or copying the right template into `env.local.json`. - Web dev: `yarn start:web` (CRA dev server on port 3006 by default) - Web builds: `yarn build`, `yarn build:main`, `yarn build:prod` - Extension bundles: `yarn build:extension` (or `:chrome`, `:mozilla`) - Tests: `yarn test` (web) and `yarn test:native` (React Native) - Lint: `yarn lint` / `yarn lint:fix` ## Contributing - Read `CONTRIBUTION-AGREEMENT.md` before opening a PR. - Follow lint/test checks locally before submitting. - Open issues for bugs or feature proposals; avoid public reports for security concerns and contact maintainers directly. # Chapter 1: Onboarding Flow The onboarding flow is the first safety gate in Salmon: it creates or restores a wallet, anchors recovery, and routes users into the main experience with minimal risk. ## Goals - Make “Create” and “Recover” obvious and mutually exclusive. - Keep seed phrases on-device and never expose them outside trusted UI. - Collect only what’s essential (password, seed phrase confirmation). - Block navigation until critical steps are completed. ## Core flow 1. **Entry screen:** CTA for Create vs Recover. Minimal copy, clear benefits for each path. 2. **Create:** Generate seed phrase, force full phrase reveal, require confirmation (word positions), then set a local password. 3. **Recover:** Paste/enter phrase, validate checksum/length, prompt for password. 4. **Post-setup:** Surface backup reminder + “Go to wallet” CTA; offer learn-more links, not extra choices. ## UX safety checklist - Disable screenshots on mobile during phrase display when possible. - Clipboard: avoid auto-copy; if allowed, show a timed warning. - Require explicit acknowledgement of recovery guidance before continuing. - Show network (mainnet/devnet) status early to reduce confusion later. ## Key implementation hooks - Navigation: `useNavigation()` routes between steps. - UI: button/alert components from the component library for consistent emphasis. - Validation: seed utilities for checksum/wordlist validation; password strength hints in-line. ## Test cases - Create flow blocks progression until phrase is confirmed. - Recover rejects malformed/short phrases with actionable errors. - Password prompt appears for both create/recover. - After success, wallet state is present in `AppContext` and storage. ## Sequencing (high level) 1. User selects Create/Recover → route change. 2. Seed phrase generated or validated. 3. Backup confirmation → password set. 4. Account locked + stored → context updated → navigate to wallet. ## Tips for maintainers - Keep copy concise and directive; remove jargon in onboarding screens. - Log declined/failed steps (anonymized) to find friction points. - Reuse the same password/seed components in both create and recover to reduce divergence. --- # Chapter 2: UI Component Library The component library keeps Salmon’s surfaces consistent across web, extension, and mobile while enforcing accessibility and hierarchy. ## Goals - One source of truth for buttons, inputs, alerts, cards, and typography. - Opinionated spacing and color tokens that match the Salmon brand. - Built-in states: hover, focus, error, loading. - Easy theming for light/dark without forking components. ## Foundations - **Tokens:** colors, spacing, typography, radii pulled from theme. - **Layout primitives:** stack, grid, card shells to avoid ad-hoc divs. - **Forms:** inputs with labels, help text, validation slots; password fields with visibility toggles. - **Feedback:** banners, toasts, modals with consistent iconography. ## Usage patterns - Favor composition: wrap primitives into flows (e.g., SeedDisplay + SeedQuiz). - Never inline arbitrary colors; consume tokens. - Keep tap targets 44px+, include keyboard focus rings by default. - Use loading/disabled states instead of conditional rendering to avoid layout shift. ## Testing checklist - Keyboard-only navigation reaches all interactive elements. - High-contrast mode keeps text legible; icons have aria-labels where needed. - Component snapshots cover light/dark variants and error states. - Responsive behavior validated at mobile/desktop breakpoints. ## Tips for maintainers - Add new components only when used in multiple places; otherwise extend existing primitives. - Document props and expected states alongside the component to reduce guesswork. - Keep icons standardized (Lucide set) and size them via tokens, not pixels. Building a wallet app means creating lots of screens: onboarding wizards, wallet lists, transaction histories. Without a library, you'd redesign buttons or layouts from scratch every time, leading to inconsistencies—like a wonky button on mobile that looks fine on desktop. This wastes time and makes the app feel unprofessional. Our central use case: Displaying a list of wallets or transactions in a uniform way. Imagine showing recent transactions as clickable cards—each with an icon, title (like "Received 1 ETH"), description (amount and date), and a subtle arrow to view details. The library provides "Lego blocks" like `GlobalButton`, `GlobalText`, and `CardButton` to snap together quickly, ensuring they adapt to both web (using Material-UI, or MUI) and native apps (using React Native Paper). For beginners, it's like using pre-cut puzzle pieces instead of drawing everything by hand: faster, consistent, and error-proof. By the end of this chapter, you'll know how to use these components to build screens, just like in the onboarding flow where we used `GlobalButton` for "Create Wallet." ## Key Concepts in the UI Component Library We'll break this down like assembling a simple Lego tower: start with basics (text and buttons), then layouts, and finally cards. Each concept is a reusable piece from `src/component-library/`. We'll use the transaction list use case to show how they fit. ### 1. GlobalText: The Basic Building Block for Words This is your go-to for displaying text—like labels, titles, or messages. It handles fonts, colors, and sizes automatically, so "Wallet Balance" looks crisp everywhere. Analogy: Like sticky notes that always match your notebook's style. To use it in a transaction subtitle: ```javascript // Simplified from src/pages/Onboarding/CreateWalletPage.js import { GlobalText } from '../../component-library/Global/GlobalText'; const TransactionItem = ({ title, description }) => ( <> {title} // Bold title, e.g., "Received ETH" {description} // Smaller, gray description ); ``` **What happens here?** Input: Props like `type` (e.g., "body2" for normal text) and `color` (e.g., "secondary" for lighter gray). Output: Styled text that renders consistently. The `type` picks from predefined sizes (e.g., "headline1" for big titles), and `color` pulls from the theme (more on that later). No fuss— just plug in your words! Under the hood, `GlobalText` (in `src/component-library/Global/GlobalText.js`) wraps React Native's `` with styles from our theme file. It calculates font sizes and colors dynamically: ```javascript // Simplified core from GlobalText.js const styles = { body2: { fontSize: 16, // From theme.fontSize.fontSizeNormal color: 'white', // labelPrimary for dark mode fontFamily: 'DM Sans Bold', // Bold variant }, // ... other types }; {children}; // Applies matching style ``` **Explanation:** The theme defines sizes (e.g., 16px for normal) and colors (e.g., white for primary text in dark mode). It skips complex parts like line heights for now—focus is on easy reuse. ### 2. GlobalButton: Clickable Actions Made Simple Buttons are everywhere: "Send," "Receive," or "Next" in onboarding. `GlobalButton` ensures they feel tappable and look uniform, with options for icons or full-width. For a transaction's "View Details" button: ```javascript // Example usage import { GlobalButton } from '../../component-library/Global/GlobalButton'; navigate('/transaction-details')} // Triggers navigation icon={IconChevronRight} // Arrow icon on right />; ``` **What happens here?** Input: `title` for text, `type` (e.g., "secondary" for outlined), `onPress` for action. Output: A button that handles taps smoothly, changing color on hover (web) or press (mobile). In our use case, tapping opens transaction details. Internally, in `src/component-library/Global/GlobalButton.js`, it uses `` (React Native's touch handler) wrapped in styles: ```javascript // Simplified core const buttonStyle = { backgroundColor: type === 'secondary' ? 'transparent' : 'card-color', // From theme borderRadius: 8, // Rounded corners minHeight: 48, // Touch-friendly size }; {title && {title}} {icon && } ; ``` **Explanation:** Styles pull from the theme (e.g., border color for outlined). It supports "card" type for transaction cards, adding borders. For web, it adds hover effects via a `Hoverable` wrapper—keeps it cross-platform without extra code. ### 3. CardButton: Cards for Lists and Interactions Cards bundle text, images, and buttons—like a mini billboard for transactions. `CardButton` is perfect for our use case: clickable cards showing ETH received, with icons and chevrons. Example for a transaction card: ```javascript // Simplified usage like in wallet lists import { CardButton } from '../../component-library/CardButton/CardButton'; viewTransaction()} />; ``` **What happens here?** Input: `title`/`description` for content, `icon` for visuals, `onPress` for clicks. Output: A full card that looks like a button, with automatic spacing and icons. In the app, this renders a uniform transaction row. From `src/component-library/CardButton/CardButton.js`, it's built on `GlobalButton` with extras: ```javascript // Simplified core // Uses GlobalButton base // Flex row for icon + text {icon && } {title} {description} {actionIcon === 'right' && } ; ``` **Explanation:** It extends `GlobalButton` for "card" mode (bordered, flexible). Colors for chips (e.g., ETH blue) come from a helper function. Skips actions like edit/delete for simplicity—those are optional props. ### 4. GlobalLayout: Wrapping It All in a Template Layouts structure pages, like a frame for your photo. `GlobalLayout` provides a scrollable container with headers/footers, ensuring safe areas (e.g., no overlap with phone notches) and refresh pulls. For a transaction list screen: ```javascript // From pages like Wallet import { GlobalLayout } from '../../component-library/Global/GlobalLayout'; Recent Transactions {transactions.map(tx => )} ; ``` **What happens here?** Input: Child components (headers, content). Output: A full-page wrapper that scrolls and adapts. Pull-to-refresh could load new transactions. In `src/component-library/Global/GlobalLayout.js`, it's a `` with theme spacing: ```javascript // Simplified const styles = { mainContainer: { flex: 1, padding: 16, // Responsive from theme.gutters maxWidth: 425, // Mobile-friendly }, }; {children} // Top section {content} // Main body ; ``` **Explanation:** Uses theme for padding (e.g., 16px on mobile). For tabs, there's a variant. It handles fullscreen backgrounds too—keeps pages consistent without boilerplate. ### 5. Theme: The Glue Holding Styles Together All components use a shared "theme" file for colors, fonts, and sizes—like a style guide for your app's personality (dark mode salmon vibes). No direct code to use—just import: `import theme from '../Global/theme';`. Colors like `accentPrimary` (orange for highlights) ensure buttons and text match. ## Step-by-Step Walkthrough: Building a Transaction Screen Using our use case, here's how components assemble internally—like a factory line: 1. **Layout sets the stage** → `GlobalLayout` creates a scrollable frame with padding. 2. **Header adds title** → `` places "Transactions" at top. 3. **Cards fill content** → Loop `CardButton`s, each pulling `GlobalText` for labels and `GlobalButton` for taps. 4. **Theme applies polish** → Every piece uses shared colors (e.g., white text on dark bg). For a visual of rendering a card: ```mermaid sequenceDiagram participant U as User participant L as GlobalLayout participant C as CardButton participant T as GlobalText participant B as GlobalButton U->>L: Render screen L->>C: Add card child C->>T: Display title/text T-->>C: Styled words C->>B: Handle press B-->>U: Navigate on tap ``` This keeps it modular: Change theme colors once, update everything. ## Deeper Dive: Cross-Platform Magic The library shines in `src/component-library/`: Web uses MUI (e.g., `MUICard` in `Card.js` for shadows), native uses React Native Paper (e.g., themed buttons). A shared `theme.js` defines colors like `bgPrimary: '#171B27'` (dark blue-gray) and fonts ('DM Sans'). For example, `ThemeProvider.js` wraps the app: ```javascript // From src/component-library/Theme/ThemeProvider.js - Simplified import { createMuiTheme } from '@mui/material/styles'; const theme = createMuiTheme({ palette: { mode: 'dark', primary: { main: '#ffffff' } }, // Dark mode, white primary typography: { fontFamily: 'DM Sans' }, // Consistent font }); {children}; // Applies to all MUI components ``` **Explanation:** On web, MUI handles responsiveness; on native, styles fall back to React Native. Files like `Header.js` mix globals (e.g., `GlobalImage` for avatars) with platform checks (e.g., QR scanner only on mobile). This ensures our transaction cards look native on iOS/Android and sleek on Chrome. ## Wrapping Up: Your Visual Toolkit Assembled Great job! You've now explored the UI Component Library: reusable blocks like `GlobalText` for words, `GlobalButton` for actions, `CardButton` for rich lists, and `GlobalLayout` for structure—all tied by a theme for consistency. Like Lego, they snap together to build secure, pretty screens without reinventing the wheel. This powers the onboarding buttons we saw before and sets up navigation flows next. Ready for more? Dive into the [Route Navigation System](https://docs.salmonwallet.io/03_route_navigation_system) to see how these components move between screens seamlessly. --- # Chapter 3: Route Navigation System Navigation organizes Salmon’s screens so users move predictably between onboarding, wallet, and settings without losing state. ## Goals - Clear, shallow routes for primary flows (onboarding, portfolio, activity, settings). - Type-safe params and guards for protected areas. - Remember last-known tab and network for smooth returns. - Minimal backstack surprises—especially after signing or funding. ## Route design - **Top-level:** `/onboarding/*`, `/wallet`, `/activity`, `/settings`. - **Nested:** `/onboarding/create|recover|password|success`, `/settings/security|network|preferences`. - **Guards:** redirect unauthenticated users to onboarding; prompt unlock if wallet is locked. - **Deeplinks:** map `salmon://` links to onboarding or wallet routes with analytics. ## Implementation notes - Use navigation hooks for pushes/pops; avoid manual history mutations. - Keep route strings centralized (enum/const) to avoid typos. - Persist navigation context (selected account, network) so reconnecting to a dApp restores the right state. - After actions like “Send” or “Approve signature,” navigate back to the invoking screen instead of the home tab. ## Testing checklist - Direct URL to `/wallet` without an unlocked session triggers unlock flow. - Switching networks does not drop the backstack unexpectedly. - Deeplinks with params (e.g., prefilled address) render safely and sanitize inputs. - Back navigation from modal-like flows (seed quiz, password prompt) works on both mobile and desktop. ## Tips for maintainers - Keep a single navigation provider; avoid nesting routers that drift out of sync. - Centralize transition animations so modal sheets and full pages feel unified. - Log route-level errors to catch broken links after refactors. Imagine your app as a big house with many rooms: the onboarding room for new users, the wallet room for balances, and the settings room for tweaks. Without a navigation system, jumping between rooms would be chaotic—like wandering hallways with no doors or signs. Users might get stuck or frustrated, especially in a wallet app where quick access to transactions or NFTs is key. Our central use case: A new user taps "Create a Wallet" on the welcome screen and smoothly slides to the creation page, where they enter details and navigate back to the wallet overview if needed. This system acts like a GPS: it maps out paths (routes), handles "detours" (like passing account IDs as parameters), and ensures safe travels between screens. For beginners, think of it as labeled doors between rooms—tap one, and you're there, with info like "room 123" passed along if required. By the end, you'll see how this glues your app together, using React Router for web or React Navigation for native apps. ## Key Concepts in the Route Navigation System We'll unpack this like planning a road trip: first the map (routes), then the GPS tool (navigation hook), and finally the engine (builder). Each piece is simple and reusable, defined in files like `routes/app-routes.js`. ### 1. Defining Routes: Your App's Map Routes are like addresses for each screen—unique paths like `/wallet` or `/onboarding/create`. Each route links to a page component and can include "wildcards" for details, like `/token/:tokenId` (the `:tokenId` is a param, like passing a room number). In a file like `src/pages/Onboarding/routes.js`, routes are listed as an array: ```javascript // Simplified from src/pages/Onboarding/routes.js export const ROUTES_MAP = { ONBOARDING_CREATE: 'ONBOARDING_CREATE', }; const routes = [ { key: ROUTES_MAP.ONBOARDING_CREATE, name: 'onboardingCreate', path: 'create', route: '/onboarding/create', Component: CreateWalletPage, // The screen to show }, ]; ``` **What happens here?** Input: A list of objects with path, name, and Component. Output: When the app matches a URL like `/onboarding/create`, it loads `CreateWalletPage`. No params here, but for our use case, tapping "Create" builds this URL. Analogy: Like pinning locations on a map—each pin says "show this room at this address." ### 2. The Navigation Hook: Your GPS for Moving Around To actually travel between routes, use `useNavigation`—a handy tool that creates links or triggers jumps. It's like saying "go to the wallet room" from anywhere. In a button on the welcome screen: ```javascript // Simplified from src/pages/Onboarding/SelectOptionsPage.js import { useNavigation } from '../../routes/hooks'; const SelectOptionsPage = () => { const navigate = useNavigation(); // Get the GPS tool return ( navigate('ONBOARDING_CREATE')} // Jump to create route /> ); }; ``` **What happens here?** Input: A route key like `'ONBOARDING_CREATE'` and optional params (e.g., `{id: '123'}`). Output: The app changes the URL to `/onboarding/create` and loads that screen. In our use case, this takes the user from welcome to creation without reloading the whole app—smooth like a slide door. For params, imagine navigating to a token detail: `navigate('TOKEN_DETAIL', {tokenId: 'ETH'})` builds `/token/ETH`. **Beginner tip:** Import it once per page; it uses React's `useNavigate` under the hood for web. ### 3. Handling Params: Passing Notes Between Rooms Params let you carry info, like an account ID, to the next screen. The hook builds the path automatically, and `useParams` grabs it on arrival. On a token list screen, navigating with param: ```javascript // Example in a token card navigate('TOKEN_DETAIL', { tokenId: selectedToken.id }); // Builds /token/abc123 ``` Then, in the detail page: ```javascript // Simplified in TokenDetailPage.js import { useParams } from 'react-router-dom'; // From React Router const TokenDetailPage = () => { const { tokenId } = useParams(); // Grabs 'abc123' from URL return {`Details for ${tokenId}`}; // Uses it }; ``` **What happens here?** Input: Param in URL. Output: The page reads it to show specific data, like token balance. Analogy: Handing a note saying "show room for guest #456"—the room knows who to prepare for. ### 4. Nested Routes: Sub-Rooms in Bigger Sections For sections like settings (with sub-pages for accounts or NFTs), routes nest under parents. Like a house wing with bedrooms—enter "settings," then branch to "accounts/\:id". From `src/pages/Wallet/routes.js`: ```javascript // Simplified nesting { key: 'WALLET_SETTINGS', path: 'settings/*', // * means "any sub-path" route: '/wallet/settings', Component: SettingsSection, // Wrapper for subs }, // Subs added via getRoutesWithParent utility ``` **What happens here?** Input: Parent route with `/*`. Output: App matches `/wallet/settings/accounts` to a sub-route, loading the right component. In our use case, from wallet overview, tap settings to enter the wing, then accounts for details. ## Step-by-Step Walkthrough: Navigating from Welcome to Wallet Using our central use case, here's how navigation flows—like following GPS directions: 1. **User taps button** → Calls `navigate('ONBOARDING_CREATE')` in the welcome page. 2. **Hook builds URL** → Matches key to `/onboarding/create` in routes. 3. **App loads component** → Renders `CreateWalletPage` with any params. 4. **User finishes** → Next `navigate` to `'WALLET_OVERVIEW'`, passing account ID if needed. 5. **Back navigation** → Built-in browser "back" or explicit `navigate(-1)` returns. This ensures users flow from onboarding to wallet seamlessly, pulling UI components from the library for buttons. For a visual of the use case navigation: ```mermaid sequenceDiagram participant U as User participant B as Button (UI) participant N as Navigation Hook participant R as Routes Map participant P as Page Component U->>B: Tap "Create Wallet" B->>N: navigate('ONBOARDING_CREATE') N->>R: Find route for key R-->>N: /onboarding/create N->>P: Load CreateWalletPage P-->>U: Show creation screen ``` **Explanation:** Four steps, four players—user action triggers the chain, ending at the new page. No data loss; params travel along. ## Deeper Dive: How It All Works Under the Hood Internally, the system starts in `src/AppRoutes.js`, which decides the entry point (welcome if no accounts, wallet if ready—tying into [Onboarding Flow](https://docs.salmonwallet.io/01_onboarding_flow) and later [AppContext](https://docs.salmonwallet.io/04_appcontext)). It uses `RoutesBuilder` to render: ```javascript // Simplified from src/routes/RoutesBuilder.js import { Routes, Route } from 'react-router-dom'; const RoutesBuilder = ({ routes, entry }) => { const EntryComponent = getRouteComponent(routes, entry); // Picks starting page return ( {EntryComponent && } />} {routes.map(({ path, Component }) => ( } /> ))} ); }; ``` **Explanation:** Input: Array of routes and entry key. Output: React Router's `` that matches URL to components. Like a switchboard—URL `/onboarding/create` flips to that route. The `getRouteComponent` utility (in `utils.js`) finds the Component by key. The hook in `src/routes/hooks.js` powers jumps: ```javascript // Simplified from src/routes/hooks.js import { useNavigate } from 'react-router-dom'; import { getRoute } from './utils'; export const useNavigation = () => { const navigate = useNavigate(); return (to, params) => { const toRoute = getRoute(globalRoutes, to); // Find route by key if (toRoute) { const fullPath = buildRouteWithParams(toRoute.route, params); // Add params navigate(fullPath); // Go there } }; }; ``` **Explanation:** `globalRoutes` is the full map (merged from all sections like onboarding or wallet in `app-routes.js`). `buildRouteWithParams` swaps `:id` with values (e.g., `/token/:id` becomes `/token/ETH`). For native, it swaps to React Navigation stacks/tabs—cross-platform without extra work. Nesting uses `getRoutesWithParent` in `utils.js` to prefix subs (e.g., settings routes get `/wallet/settings/` added). This keeps the map tidy, avoiding duplicate code. **Beginner tip:** Errors? Check console for "route not found"—means a key mismatch. Update `ROUTES_MAP` constants to add new paths. ## Wrapping Up: Smooth Travels Between Screens Awesome work! You've now mapped out the Route Navigation System: routes as addresses, the `useNavigation` hook as your GPS, and builders handling the heavy lifting for seamless jumps—like guided doors in a house. This powers flows from [Onboarding Flow](https://docs.salmonwallet.io/01_onboarding_flow) to wallet views, using [UI Component Library](https://docs.salmonwallet.io/02_ui_component_library) buttons to trigger moves. Next, we'll see how shared data (like user accounts) flows across these screens: [AppContext](https://docs.salmonwallet.io/04_appcontext). --- # Chapter 4: AppContext AppContext is the shared state layer that keeps accounts, networks, preferences, and session status in sync across Salmon. ## Responsibilities - Hold active account, balances, tokens, and network. - Expose actions to add/remove/rename accounts and switch networks. - Track lock/unlock state and surface session timeouts. - Provide user prefs (language, theme) to UI components. ## Design principles - Single source of truth; avoid duplicating wallet state elsewhere. - Immutable updates to keep renders predictable. - Derived selectors (e.g., `activeBalance`, `hasPendingSignatures`) to reduce repeated logic. - Memoize context values to avoid re-render storms. ## Core API (examples) - `accounts`, `activeAccountId`, `setActiveAccount(id)`. - `network`, `setNetwork(net)`, `availableNetworks`. - `preferences` (language, theme), `setPreference(key, value)`. - `lock()`, `unlock(password)`, `isLocked`. ## Persistence interactions - On init: load accounts + encrypted mnemonics from storage, then unlock if a password cache exists. - On change: persist account list and prefs; lock-sensitive data remains encrypted. - On lock: clear sensitive in-memory state but keep non-sensitive prefs. ## Testing checklist - Switching accounts updates balances and recent activity views. - Locking clears access to mnemonics and signing; unlocking restores. - Language/theme changes propagate instantly without page reload. - Context remains stable when navigating between tabs. ## Tips for maintainers - Keep context small: expose intent-based actions instead of raw setters where possible. - Add analytics hooks at the action layer, not inside UI components. - When adding fields, document defaults and persistence behavior. ## Key Concepts in AppContext Let's break this down like setting up a family group chat: first the chat room (Context), then adding members (Provider), and finally checking messages (hooks). It's built on React's Context API, but we'll keep it simple—no deep React theory needed. ### 1. React Context Basics: The Shared Bulletin Board React Context is like a magic box where you store app-wide info (state) that any component can access, without passing props through every level. In our app, AppContext is this box, holding things like the active wallet account or hidden balances. To use it, wrap your app in a Provider (the box owner) and "subscribe" in components with a hook. Analogy: The Provider posts the daily news; components read it without asking parents. No code here yet—we'll see it in action next. ### 2. AppProvider: The Central Command Center AppProvider is the boss that creates and manages the Context. It combines data from various hooks (like accounts from useAccounts or translations from useTranslations) into one big package, then shares it via Context.Provider. Think of it as the radio station DJ mixing tracks (data sources) into a broadcast. From `src/AppProvider.js`, here's a simplified setup: ```javascript // Simplified from src/AppProvider.js - Creating the Context import React, { createContext } from 'react'; import useAccounts from './hooks/useAccounts'; // Gets wallet data import useTranslations from './hooks/useTranslations'; // Gets language export const AppContext = createContext(); // Creates the shared box const AppProvider = ({ children }) => { const [accountsState, accountsActions] = useAccounts(); // e.g., { activeAccount, ... } const { selectedLanguage, ...translationsState } = useTranslations(); // e.g., { languages, ... } const value = [ // Bundle state and actions into one package { ...accountsState, ...translationsState }, // All shared data { ...accountsActions, changeLanguage: translationsState.changeLanguage }, // All methods ]; return ( // Share the package app-wide {children} // Your screens go here ); }; ``` **What happens here?** Input: Child components (your whole app). Output: The Provider fetches data from hooks (e.g., active account with balance) and bundles it with actions (e.g., switch account). Any descendant component can now access this without props. In our use case, it loads accounts after onboarding and shares the active wallet instantly. **Beginner tip:** Wrap `` around `` in your main App.js to make it available everywhere, like plugging in the radio for the whole house. ### 3. Using the Hook: Reading and Updating from Anywhere To access the shared state, use `useContext(AppContext)` in any component. It gives you the state (data) and actions (methods), like getting the bulletin board notes and a pen to add your own. In a wallet screen showing balance: ```javascript // Simplified example in a WalletPage.js - Using the Context import { useContext } from 'react'; import { AppContext } from '../AppProvider'; // Import the shared box const WalletPage = () => { const [state, actions] = useContext(AppContext); // Subscribe: get data and methods const { activeAccount } = state; // Read active wallet (e.g., { name: 'My Wallet', balance: 1.5 }) const { changeAccount } = actions; // Method to switch return ( {activeAccount?.name} Balance: ${activeAccount?.balance} changeAccount('new-id')} /> ); }; ``` **What happens here?** Input: Nothing—the hook pulls from Context. Output: State like `activeAccount` (wallet details) updates the UI automatically when it changes (e.g., after a transaction). In our use case, tapping "Switch Account" calls the action, updating the active wallet across the app—no refresh needed. For language, `state.selectedLanguage` shows "English," and `actions.changeLanguage('es')` updates all text. **Explanation:** React re-renders components when Context value changes, like the bulletin board lighting up new notes. This avoids prop drilling: Instead of passing accounts from parent > child > grandchild, everyone reads directly. ### 4. Integrating Hooks: Building the Data Mix AppContext doesn't store data itself—it pulls from specialized hooks like useAccounts (for wallets) or useAddressbook (for contacts). These hooks handle the details (e.g., loading from storage, covered in [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer)), and AppProvider mixes them. From the simplified AppProvider above, see how `useAccounts()` provides `activeAccount` (e.g., Input: Stored wallet data; Output: Object with networks, tokens, balance via blockchain calls). ## Step-by-Step Walkthrough: Sharing Wallet Data in the Use Case Using our central use case, here's how AppContext flows data—like a relay race passing the baton (wallet info) smoothly: 1. **App starts** → AppProvider initializes, calls hooks to load data (e.g., accounts from storage, language from prefs). 2. **User navigates to wallet** → WalletPage uses `useContext` to grab `activeAccount` and display balance. 3. **User switches language** → Settings calls `changeLanguage('fr')`, updating Context value; all pages re-render with French text. 4. **Balance updates** → A transaction happens (via blockchain methods in useAccounts); state changes, UI refreshes everywhere. 5. **Logout** → Actions call `logout()`, clearing accounts; app shows onboarding again. For a visual of the language switch in our use case: ```mermaid sequenceDiagram participant U as User participant S as Settings Page participant C as AppContext (Provider) participant W as Wallet Page participant H as useTranslations Hook U->>S: Tap "French" S->>H: changeLanguage('fr') H-->>C: Update translations state C-->>W: Broadcast new value (selectedLanguage: 'fr') W-->>U: Text changes to French (e.g., "Solde: 1.5 ETH") ``` **Explanation:** User action triggers an action in one place (Settings); Context broadcasts the change, so Wallet (and others) update automatically. Only 4 steps—efficient, like a group chat ping. ## Deeper Dive: Under the Hood in AppProvider Internally, AppProvider orchestrates everything in `src/AppProvider.js`. It uses React's `useReducer` for simple app state (e.g., logged in? hide balance?) and waits for hooks to be "ready" before showing screens. Step-by-step without code: On app load, it checks if accounts/translations are loaded (via `useEffect`). If yes, hides splash screen and sets `isLogged: true` if there's an active account. For logout, it calls `removeAllAccounts()` from useAccounts, then updates state. Simplified reducer for login state: ```javascript // From src/AppProvider.js - Handling login/logout const initialState = { isLogged: false, ready: false }; const reducer = (state, action) => { switch (action.type) { case 'SET_LOGGEDIN': return { ...state, isLogged: action.value }; case 'INITIATE_DONE': return { ...state, ready: true, isLogged: !!action.activeAccount }; default: return state; } }; const [appState, dispatch] = useReducer(reducer, initialState); // Later: dispatch({ type: 'INITIATE_DONE', activeAccount: accountsState.activeAccount }); ``` **Explanation:** Input: Actions like `SET_LOGGEDIN`. Output: Updated state (e.g., `ready: true` shows routes). It merges with hook states (e.g., `...accountsState`) in the Provider value. For security, if locked (password needed), it shows LockedPage instead of wallet. Hooks like useAccounts (in `src/hooks/useAccounts.js`) derive `activeBlockchainAccount` from stored data—e.g., Input: Network ID; Output: Cached account with methods like `getBalance()` (fetches from blockchain, but cached for speed). Translations hook loads i18n resources. This mix ensures AppContext is always up-to-date, like a DJ queuing the latest tracks. **Beginner tip:** Errors? If a hook isn't ready, the app shows a skeleton loader (from [UI Component Library](https://docs.salmonwallet.io/02_ui_component_library)). Test by logging `console.log(state)` in a component. ## Wrapping Up: Your App's Shared Brain Fantastic progress! You've now unlocked AppContext: a React-powered hub that shares wallet data, actions, and settings across your app—like a central radio keeping everyone in sync—eliminating messy prop passing. This powers seamless updates, from balance refreshes to language switches, building on navigation and UI from earlier chapters. Next, discover how this data gets saved securely: [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer). --- # Chapter 5: Storage and Persistence Layer The storage layer keeps wallets, mnemonics, and preferences available across sessions while protecting secrets. ## Responsibilities - Encrypt and persist mnemonics/private data. - Persist non-sensitive metadata (accounts, settings, networks) separately. - Handle migrations/upgrades between schema versions. - Provide simple get/set APIs to AppContext and onboarding. ## Data model - `MNEMONICS`: encrypted map of accountId → seed. - `ACCOUNTS`: array of account metadata (id, name, derivation path, network). - `PREFERENCES`: language, theme, analytics opt-in, etc. - Version key to trigger migrations. ## Security practices - Encryption at rest using password-derived keys. - Wipe decrypted material from memory on lock/unmount. - Avoid writing secrets to logs; gate debug flags behind env checks. - On mobile, use secure storage/backed keychain where available. ## Flow 1. On app start, load persisted data. 2. If encrypted, prompt unlock; else hydrate context directly. 3. On account changes, write metadata + encrypted seeds atomically. 4. On lock, drop decrypted cache; keep prefs. ## Testing checklist - Fresh install: no data loaded, onboarding required. - Create account → restart: accounts appear, unlock prompt shown if password set. - Migration path upgrades data without loss; invalid data fails gracefully. - Lock/unlock cycle removes decrypted seeds from memory. ## Tips for maintainers - Keep persistence boundaries clear: sensitive vs non-sensitive. - Batch writes to reduce churn; debounce preference updates. - Document new keys and include migration steps when changing formats. ## Key Concepts in the Storage and Persistence Layer We'll unpack this like organizing a backpack for a trip: essentials first (basic saving), then locks (encryption), and helpers (temporary stashes or hooks). It's all in utils files under `src/utils/`, keeping things modular and platform-smart (web, mobile, or browser extension). ### 1. Platform-Specific Storage: The Basic Filing Cabinet Storage picks the right "drawer" based on where your app runs—like using a desk drawer for home (web's localStorage) or a phone's secure vault for mobile (AsyncStorage). It handles saving simple data like account lists or user preferences as JSON strings. To save an account after onboarding: ```javascript // Simplified from src/hooks/useAccounts.js - Saving accounts import storage from '../utils/storage'; const saveAccounts = async (accounts) => { await storage.setItem('ACCOUNTS', accounts); // Save as JSON // Loads like: const loaded = await storage.getItem('ACCOUNTS'); }; saveAccounts([{ id: '1', name: 'My Wallet' }]); // Input: Array of account objects ``` **What happens here?** Input: A key like `'ACCOUNTS'` and data (e.g., your wallet list). Output: Data gets stored locally; on next load, `getItem` pulls it back exactly as saved. In our use case, after creating a wallet, this saves the account details. Analogy: Like jotting a shopping list on paper—easy to tuck away and grab later. For web, it's `localStorage.setItem`; for native apps, it's encrypted by default to dodge app crashes wiping data. No worries about formats—`JSON.stringify` and `parse` handle the conversion automatically under the hood. ### 2. Encryption for Sensitive Data: The Secure Lock Not all data is casual; seed phrases (mnemonics) are like house keys—lose them, and poof, your funds are gone. The layer uses password-based encryption (via `lock/unlock` utils) to scramble sensitive info before storing, only unlocking with the right password. From the password step in onboarding: ```javascript // From src/utils/password.js - Locking a mnemonic import { lock } from '../utils/password'; const encryptMnemonic = async (mnemonic, password) => { const locked = await lock({ seed: mnemonic }, password); // Input: Data + password await storage.setItem('MNEMONICS', locked); // Save encrypted version }; encryptMnemonic('abandon ability able...', 'mySecretPass'); // Outputs locked object ``` **What happens here?** Input: Plain data (e.g., `{ seed: 'word1 word2...' }`) and a password. Output: A scrambled "locked" object saved to storage; trying to load without the password throws an error like "Incorrect password." In our use case, during onboarding, the user's password encrypts the seed phrase right after validation. Unlocking happens on app start: `unlock(loaded, password)` decrypts it for the [AppContext](https://docs.salmonwallet.io/04_appcontext). Analogy: Like putting valuables in a safe—only your combo opens it. It uses strong crypto (PBKDF2 for key derivation, NaCl for boxing) to resist hackers, with salts and nonces for extra safety. **Beginner tip:** Always encrypt seeds; never store them plain. The keys are from `STORAGE_KEYS` like `'MNEMONICS'` for organization. ### 3. Stash Utils: Temporary Storage for Quick Notes For short-term stuff like a password during login (not worth encrypting yet), use `stash`—a lightweight, in-memory or extension-channel store that clears on app close. It's like a notepad on your desk, not the filing cabinet. Example for holding a temp password: ```javascript // From src/utils/stash.js - Temp storage import stash from '../utils/stash'; const tempSavePassword = async (password) => { await stash.setItem('tempPass', password); // Input: Key + value // Later: const pass = await stash.getItem('tempPass'); }; tempSavePassword('temp123'); // Outputs: Value stored temporarily ``` **What happens here?** Input: Simple key-value pairs. Output: Data held in RAM (web/native) or a secure channel (extensions), gone when the app restarts. In our use case, stash might hold an entered password during unlock before decrypting accounts. Analogy: Sticky notes for reminders—handy but not permanent. For extensions, it uses Chrome messaging to avoid localStorage limits. ### 4. Integration Hooks: Easy Access for Accounts Hooks like `useAccounts` (from [AppContext](https://docs.salmonwallet.io/04_appcontext)) tie storage together, loading/saving automatically. It's the "librarian" that fetches your filed data on demand. In a component after onboarding: ```javascript // Simplified from src/hooks/useAccounts.js - Loading on app start import { useState, useEffect } from 'react'; import storage from '../utils/storage'; import { unlock } from '../utils/password'; const useAccounts = () => { const [accounts, setAccounts] = useState([]); useEffect(() => { const load = async () => { const saved = await storage.getItem('ACCOUNTS'); // Load accounts const lockedMnemonics = await storage.getItem('MNEMONICS'); if (lockedMnemonics && password) { // Assume password from user const mnemonics = await unlock(lockedMnemonics, password); setAccounts([...saved, ...mnemonics]); // Merge and set } }; load(); }, []); return [accounts, { addAccount: saveAccounts }]; // Output: State + actions }; ``` **What happens here?** Input: Triggers on app load (via `useEffect`). Output: Populated `accounts` array ready for [AppContext](https://docs.salmonwallet.io/04_appcontext), with methods to add/save. In our use case, on restart, it decrypts and loads your wallet, showing "My Wallet" with balance. Analogy: Like a auto-reminder app that pulls your to-do list each morning. ## Step-by-Step Walkthrough: Saving a Wallet in the Use Case Using our central use case, here's how storage persists the new wallet—like archiving a document after writing it: 1. **User finishes onboarding** → Password encrypts the mnemonic via `lock()`. 2. **Save to storage** → `storage.setItem` files accounts and locked seeds. 3. **App closes/reopens** → On start, `useAccounts` loads via `getItem` and decrypts with password. 4. **Data flows to context** → Loaded accounts update [AppContext](https://docs.salmonwallet.io/04_appcontext), showing the wallet. 5. **User sees persistence** → Balance and name appear without re-setup. For a visual of saving after onboarding: ```mermaid sequenceDiagram participant U as User participant H as useAccounts Hook participant S as Storage Utils participant P as Password Utils participant C as AppContext U->>H: Finish create (with password) H->>P: lock(mnemonic, password) P-->>H: Locked data H->>S: setItem('ACCOUNTS'/'MNEMONICS', data) S-->>H: Saved H->>C: Update accounts state C-->>U: Wallet loaded on restart ``` **Explanation:** User action kicks off encryption, then storage files it away. On reload, reverse: load > decrypt > share. Simple chain—5 players, no data lost. ## Deeper Dive: Under the Hood in the Utils Internally, the layer starts in `src/utils/storage.js`, which detects the platform (web, native, extension via `isExtension()`) and picks the right implementation—like choosing a backpack size for your trip. For web (`storage.window.js`): ```javascript // Simplified from src/utils/storage.window.js const storage = { setItem: async (key, value) => window.localStorage.setItem(key, JSON.stringify(value)), // Input: Key + data getItem: async key => JSON.parse(localStorage.getItem(key) || 'null'), // Output: Parsed data or null }; export default storage; ``` **Explanation:** Uses browser's localStorage for persistence (survives page refreshes). JSON handles objects/arrays automatically. Errors? Logs and removes bad keys. For native (`storage.native.js`), it swaps to `react-native-encrypted-storage` for phone security—same API, different engine. Extensions use Chrome's `storage.local` (`storage.extension.js`) for sync across tabs. Encryption in `src/utils/password.js` derives keys securely: ```javascript // Core from password.js - Key derivation import { pbkdf2 } from 'crypto-browserify'; // Or native version const deriveKey = async (password, salt) => new Promise((res, rej) => pbkdf2(password, salt, 100000, 32, 'sha256', (err, key) => err ? rej(err) : res(key) // Input: Password + salt; Output: 32-byte key ) ); ``` **Explanation:** PBKDF2 "stretches" the password (100k iterations) to make brute-force hard, like turning a simple lock into a vault. Then `secretbox` encrypts with the key + random nonce/salt. Unlocking reverses it, checking for decryption success. Platform tweaks: Native uses `react-native-fast-crypto` for speed; web uses browserify shims. Stash (`src/utils/stash.js`) routes similarly: Native uses a Map (in-memory), web a window var, extensions Chrome messages—all async and promise-based. Keys are centralized in `src/utils/storageKeys.js` (e.g., `ACCOUNTS: 'accounts'`) to avoid typos—like labeled folders. Upgrades (old data formats) run via `runUpgrades` in hooks, migrating seamlessly. **Beginner tip:** Test by clearing storage (e.g., `storage.clear()`) and reloading—watch accounts vanish until re-saved. ## Wrapping Up: Your App's Secure Memory Bank Whew, you've now mastered the Storage and Persistence Layer: a smart, encrypted filing system that saves wallets and settings across sessions, blending platform storage, locks for secrets, temp stashes, and hooks for easy use—like a reliable diary for your app's life story. This ensures onboarding gains stick, powering the shared state in [AppContext](https://docs.salmonwallet.io/04_appcontext) without resets. Ready to make your app speak multiple languages? Head to the next chapter: [Translation System](https://docs.salmonwallet.io/06_translation_system). --- # Chapter 6: Translation System The translation system localizes Salmon so copy, errors, and prompts adapt to each user’s language without code changes. ## Goals - Centralize strings with stable keys; avoid hardcoded UI text. - Support runtime language switching without reload. - Provide fallbacks and interpolation for dynamic values. - Keep error messages short, actionable, and translated. ## Structure - Namespaced JSON/YAML dictionaries per language (e.g., `wallet.create_wallet`, `errors.network_unavailable`). - Loader that fetches dictionaries on app start and caches them. - Hook/HOC to expose `t(key, params)` and current locale to components. - Fallback to default locale when keys are missing; log missing keys in dev. ## Patterns - Prefer declarative keys (`actions.continue`) over inline text. - Interpolate with `{amount}` style placeholders; avoid string concatenation. - Keep capitalization in translations, not in code. - Pair translations with accessibility labels where needed. ## Testing checklist - Switching languages updates visible text immediately. - Missing keys surface a clear dev warning and fallback text. - RTL locales render correctly if added; layout remains usable. - Error messages remain concise across locales. ## Tips for maintainers - Add new keys next to the feature, then sync all locale files. - Avoid duplicating similar phrases—reuse keys to stay consistent. - When refactoring features, delete unused keys to prevent drift. ## Why Do We Need a Translation System? Picture this: A user from Spain opens your wallet app and sees everything in English—frustrating, right? They tap settings to switch to Spanish, and suddenly buttons say "Crear Billetera" instead of "Create Wallet," with error messages like "Contraseña incorrecta" for "Wrong password." This personalization boosts trust and usability, especially for non-English speakers handling sensitive crypto. Our central use case: After onboarding, a user in settings selects Spanish. The app loads Spanish translations from JSON files, updates the UI instantly (e.g., wallet balance shows "Saldo" instead of "Balance"), and saves the choice persistently so it sticks on restart—via the [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer). For beginners, it's like giving your app a universal translator earpiece: Load word dictionaries, swap languages with a hook, and sprinkle `t('key')` in components to pull the right words. By the end, you'll know how to make your app bilingual (or more) without rewriting screens. ## Key Concepts in the Translation System We'll break this down like building a simple phrasebook: first the dictionary (i18next setup), then the selector (hook for language state), and finally the lookup (using `t()` in UI). It's powered by the i18next library, keeping code light and React-friendly. ### 1. i18next Setup: Your App's Dictionary i18next loads JSON files as language packs—like pocket dictionaries for English, Spanish, etc. Each file has key-value pairs: e.g., `'wallet.create': 'Create Wallet'` in English, `'Crear Billetera'` in Spanish. From `src/translations/index.js`, the basic init: ```javascript // Simplified from src/translations/index.js import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import enTranslation from './en/translation'; // English JSON: { wallet: { create: 'Create Wallet' } } import esTranslation from './es/translation'; // Spanish JSON: { wallet: { create: 'Crear Billetera' } } const resources = { en: { translation: enTranslation }, es: { translation: esTranslation }, }; i18n.use(initReactI18next).init({ resources, lng: 'en', // Default: English fallbackLng: 'en', // Backup if language missing }); ``` **What happens here?** Input: JSON resources and default language ('en'). Output: i18next is ready; it loads the English pack on start. In our use case, add more languages by importing new JSONs—keys stay the same, values translate. Analogy: Like stacking dictionaries in a backpack; the app picks one based on user choice. No server needed—local files keep it fast and private. ### 2. useTranslations Hook: Managing Language State This custom hook (like a radio tuner) loads/saves the user's language, initializes i18next, and provides a `changeLanguage` method. It ties into [AppContext](https://docs.salmonwallet.io/04_appcontext) for app-wide sharing and [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer) to remember preferences. From `src/hooks/useTranslations.js`: ```javascript // Simplified from src/hooks/useTranslations.js import { useState, useEffect } from 'react'; import i18n from '../translations'; import storage from '../utils/storage'; import STORAGE_KEYS from '../utils/storageKeys'; const useTranslations = () => { const [selected, setSelected] = useState('en'); const [loaded, setLoaded] = useState(false); useEffect(() => { if (!loaded) { storage.getItem(STORAGE_KEYS.LANGUAGE).then(lang => { const lng = lang || 'en'; i18n.init({ lng, resources: /* loaded from index.js */ }); setSelected(lng); setLoaded(true); }); } }, [loaded]); return { selected, loaded, changeLanguage: async lng => { await storage.setItem(STORAGE_KEYS.LANGUAGE, lng); i18n.changeLanguage(lng); setSelected(lng); } }; }; ``` **What happens here?** Input: On app start, pulls saved language (or defaults to 'en'). Output: Sets state (`selected: 'en'`) and initializes i18next with the pack; `loaded: true` signals ready. For our use case, call `changeLanguage('es')` in settings—it saves to storage, swaps the dictionary, and updates the UI. Analogy: Like tuning a radio station; the hook remembers your favorite and switches tunes instantly. ### 3. Using {t('key')} in Components: Pulling Translated Strings Components access translations via a Higher-Order Component (HOC) `withTranslation`, which injects a `t` function. Use it like `t('wallet.create')` to get the string for the current language. In a welcome button, from `src/pages/Welcome/WelcomePage.js`: ```javascript // Simplified from src/pages/Welcome/WelcomePage.js import { withTranslation } from '../../hooks/useTranslations'; const WelcomePage = ({ t }) => ( // t is injected by HOC navigate('/onboarding/create')} /> ); export default withTranslation()(WelcomePage); // Wraps to provide t ``` **What happens here?** Input: Key like `'wallet.create_wallet'`. Output: The translated string from the active dictionary (e.g., "Crear Billetera" in Spanish), rendered in the button. In our use case, after switching languages, all `t()` calls update automatically on re-render. Analogy: Like looking up a word in your current dictionary—swap books, and lookups change. For errors: `t('error.invalid_password')` becomes "Contraseña inválida." ### 4. Integrating with AppContext: Sharing Translations App-Wide The hook feeds into [AppContext](https://docs.salmonwallet.io/04_appcontext) (see `src/AppProvider.js`), bundling `selectedLanguage` and `changeLanguage` for any screen to access—e.g., settings calls it to switch, and wallet UI updates via `t()`. ## Step-by-Step Walkthrough: Switching to Spanish in the Use Case Using our central use case, here's how translations adapt the app—like flipping a language switch in a tour guide app: 1. **App starts** → `useTranslations` loads saved language ('en') from storage, initializes i18next with English JSON. 2. **User goes to settings** → Component uses Context to call `changeLanguage('es')`; saves 'es' and loads Spanish JSON. 3. **UI updates** → Screens re-render; `t('wallet.balance')` now shows "Saldo" instead of "Balance" (e.g., in wallet view). 4. **Persistent** → On restart, storage pulls 'es', so Spanish loads by default—no reset to English. 5. **Error handling** → If a key's missing in Spanish, falls back to English for that string only. For a visual of switching languages: ```mermaid sequenceDiagram participant U as User participant H as useTranslations Hook participant I as i18next participant C as AppContext participant W as Wallet Component U->>C: Tap "Español" in settings C->>H: changeLanguage('es') H->>I: changeLanguage('es') & load JSON I-->>H: Spanish pack active H->>storage: Save 'es' H-->>C: Update selectedLanguage C-->>W: Re-render with new t('balance') = "Saldo" W-->>U: UI shows Spanish text ``` **Explanation:** User action triggers the hook, which updates i18next and storage. Context broadcasts, so components like wallet refresh—smooth, with just 5 steps like a quick channel change. ## Deeper Dive: Under the Hood in the Translation System Internally, the system starts in `src/translations/index.js`, exporting i18n as a singleton (one instance app-wide). JSONs like `en/translation.json` are flat or nested objects—e.g., `{ "wallet": { "create": "Create Wallet" } }`—loaded lazily to save memory. In `src/hooks/useTranslations.js`, the `useEffect` runs once: It fetches from `STORAGE_KEYS.LANGUAGE` (e.g., 'language' key), inits i18next with `compatibilityJSON: 'v3'` for safe key handling (ignores extra dots), and sets interpolation to false (no variable escaping needed here). The `changeLanguage` is async for storage, ensuring the UI waits—no flickering. The HOC in the hook (`withTranslationHOC` from i18next) wraps components, injecting `t` via React context—e.g., Input: Key; Output: Scans active resources for match, falls back if missing. In [AppProvider](https://docs.salmonwallet.io/04_appprovider.js), it spreads `{ selectedLanguage, changeLanguage }` into Context value, so hooks like `useContext(AppContext)` grab them easily. **Beginner tip:** Missing translation? Console warns; add to JSON. Test by forcing `changeLanguage('es')` in dev tools—watch buttons morph! ## Wrapping Up: Your App Goes Global You've now got the Translation System down: i18next dictionaries for words, a hook for state and switching, `t('key')` for easy lookups, and ties to [AppContext](https://docs.salmonwallet.io/04_appcontext) for sharing—all making your app feel at home in any language, like a friendly translator in your pocket. This personalizes the wallet experience post-onboarding, with persistent choices. Next, we'll dive into handling blockchain accounts smartly: [Blockchain Account Abstraction](https://docs.salmonwallet.io/07_blockchain_account_abstraction). --- # Chapter 7: Blockchain Account Abstraction Account abstraction unifies chain-specific logic so Salmon can fetch balances, build transactions, and sign requests through one interface. ## Goals - One API for accounts regardless of chain/network. - Clean separation between UI and RPC/wallet internals. - Predictable fee estimation and simulation before signing. - Safer defaults (e.g., block unsupported programs, validate destinations). ## Interface outline - `getAccounts()`, `getActiveAccount()`, `setActiveAccount(id)`. - `fetchBalance(accountId, network)`, `fetchTokens(accountId)`. - `buildTransaction({ instructions, feePayer, recentBlockhash })`. - `simulateTransaction(tx)` and `signAndSend(tx)`. ## Solana specifics - Default to mainnet; allow devnet/test validators for QA. - Use connection pooling and retry policies to avoid rate limits. - Include program allow/deny lists where possible to flag risky calls. - Normalize token metadata (decimals, symbols) before reaching UI. ## Testing checklist - Balance/token fetch works across networks; errors surface cleanly. - Simulation catches obvious failures before prompting the user. - Signing rejects malformed/unsupported instructions. - Switching networks updates connection targets without stale data. ## Tips for maintainers - Keep transport/config pluggable so RPC endpoints can rotate without code churn. - Log transaction hashes on send for support/debug (avoid logging payloads). - Add metrics around simulation vs send failures to spot integration issues. ## Why Do We Need Blockchain Account Abstraction? Blockchain interactions can be tricky: Solana uses "public keys" differently than Ethereum's "addresses," and fetching NFTs or swaps varies per network. For beginners, it's like trying to plug in devices from around the world without adapters—frustrating and error-prone. Our central use case: A user opens their wallet and sees their token balance (e.g., 1.5 SOL on Solana). The app calls a simple method like `getBalance()` on the active account, and it works seamlessly, whether on Solana, Ethereum, or others. Behind the scenes, it fetches real blockchain data, caches it for speed (no repeated network pings), and handles errors gracefully. By the end of this chapter, you'll know how to use this abstraction in hooks like `useAccounts` to power wallet screens, solving our use case with minimal code. Think of it as a "universal adapter" that turns complex blockchain ops into friendly function calls. ## Key Concepts in Blockchain Account Abstraction We'll break this down like building a simple robot assistant: first the base (raw blockchain access), then the wrapper (abstraction layer), and finally caching (speed boost). Everything lives in classes like `BlockchainAccount` (from `salmon-wallet-adapter`) and our custom `CachedBlockchainAccount`. ### 1. The Base Account: Raw Connection to the Blockchain At the core, a `BlockchainAccount` is like a direct phone line to one chain (e.g., your Solana wallet on a specific network like mainnet). It knows how to derive addresses from your seed phrase, connect to RPC nodes (blockchain servers), and call methods like fetching credits or tokens. Input: Your account details (seed, network). Output: Raw data, like a balance object with amounts in wei (tiny units). But using it directly? Tedious for multi-chain apps—different syntax per blockchain. We abstract it! ### 2. The Abstraction Layer: One Remote for All Chains Blockchain Account Abstraction wraps the base into a unified API. Methods like `getBalance()`, `getTokens()`, or `createTransferTransaction()` work the same way across chains. It's like a universal remote: Press "volume up" for any TV, and it figures out the code underneath. In our use case, when the wallet screen loads, it grabs the active account and calls `activeBlockchainAccount.getBalance()`. Input: Optional token addresses (for specific balances). Output: A balance object (e.g., `{ uiAmount: 1.5, symbol: 'SOL' }`), ready for display. No need to know if it's Solana's SPL tokens or Ethereum's ERC-20—the abstraction handles it. Here's a simplified peek from `src/hooks/useAccounts.js`, where we create the abstracted account: ```javascript // From src/hooks/useAccounts.js - Creating the abstraction const activeBlockchainAccount = useMemo(() => { const base = activeAccount?.networksAccounts?.[networkId]?.[pathIndex]; return base ? new CachedBlockchainAccount(base) : undefined; // Wraps base }, [activeAccount, networkId, pathIndex]); // Later, in a component: activeBlockchainAccount.getBalance() fetches balance ``` **What happens here?** Input: Base account (from seed derivation). Output: A wrapped `CachedBlockchainAccount` object with unified methods. In our use case, this powers the wallet balance card—call once, get Solana or ETH data uniformly. Analogy: Like wrapping gifts in the same paper, regardless of what's inside. ### 3. Caching for Efficiency: Remember, Don't Refetch Fetching from blockchains costs time and gas (fees). Caching stores results temporarily, like a notepad for quick lookups. `CachedBlockchainAccount` adds this: Methods like `getBalance()` check cache first, then hit the network if needed. For our use case, when refreshing the wallet, it caches the balance to avoid slow loads. Input: Method call (e.g., `getBalance()`). Output: Cached data if fresh (e.g., under 5 minutes old); otherwise, fresh fetch. Simplified from `src/accounts/CachedBlockchainAccount.js`: ```javascript // From src/accounts/CachedBlockchainAccount.js - Cached getBalance async getBalance(...args) { const key = `${this.network.id}-${this.base.getReceiveAddress()}`; // Unique key return cache(key, CACHE_TYPES.BALANCE, () => this.base.getBalance(...args)); // Cache or fetch } ``` **What happens here?** Input: Args like token addresses. Output: Balance data, speeding up repeated calls (e.g., UI updates). The `cache` util (from `src/utils/cache.js`) stores in memory or localStorage. Analogy: Like checking your fridge before grocery shopping—grab milk if fresh, buy new if not. ### 4. Common Methods: Building Blocks for Wallet Features Key methods include: - `getTokens()`: Lists your holdings (e.g., SOL, USDC). - `createTransferTransaction(toAddress, amount)`: Preps a send (sign later). - `getAllNfts()`: Fetches owned NFTs. All return chain-agnostic objects, like `{ address: 'abc...', uiAmount: 1.5 }`. In our use case, `getBalance()` feeds the token list in [UI Component Library](https://docs.salmonwallet.io/02_ui_component_library) cards. ## Step-by-Step Walkthrough: Fetching Balance in the Use Case Using our central use case, here's how the abstraction fetches and displays a balance—like a waiter noting your order, checking the kitchen cache, then serving fresh food: 1. **App loads wallet screen** → `useAccounts` hook (from [AppContext](https://docs.salmonwallet.io/04_appcontext)) creates `activeBlockchainAccount`. 2. **Call getBalance()** → Checks cache; if stale, calls base method (e.g., Solana RPC query). 3. **Data ready** → Returns balance object; UI renders it (e.g., "1.5 SOL"). 4. **User refreshes** → Cache hit—fast update, no network lag. 5. **Store for persistence** → Saves via [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer) for offline glimpses. For a visual of fetching balance: ```mermaid sequenceDiagram participant U as User (Wallet Screen) participant H as useAccounts Hook participant C as CachedBlockchainAccount participant B as Base Account participant N as Network (Blockchain) U->>H: Load wallet (getBalance) H->>C: Call getBalance() C->>C: Check cache? alt Cache hit C-->>H: Return cached balance else Cache miss C->>B: base.getBalance() B->>N: Query RPC N-->>B: Raw data (e.g., lamports) B-->>C: Processed balance C->>C: Cache it C-->>H: Balance object end H-->>U: Show "1.5 SOL" in UI ``` **Explanation:** User action triggers the hook; abstraction checks cache first, then network. Five steps total—efficient, with fallback to base for chain specifics. Output: UI updates instantly, even on slow connections. ## Deeper Dive: Under the Hood in CachedBlockchainAccount Internally, the abstraction starts in `src/accounts/CachedBlockchainAccount.js`, a class wrapping the base `BlockchainAccount` from `salmon-wallet-adapter`. It derives your public key from the seed (via `keyPair`), sets the network (e.g., Solana mainnet), and proxies methods. Non-code walkthrough: On creation, it computes a `baseKey()` (unique ID like "solana-mainnet-YourAddress"). For each method (e.g., `getTokens()`), it generates a cache key, checks `cache()` util—if hit, returns stored; else, calls `this.base.method()`, processes (e.g., convert wei to SOL), caches, and returns. Errors? Logs and retries once. Simplified constructor and proxy example: ```javascript // From src/accounts/CachedBlockchainAccount.js - Constructor class CachedBlockchainAccount { constructor(base) { this.base = base; // The raw account this.network = base.network; // e.g., { id: 'solana-mainnet', blockchain: 'SOLANA' } this.publicKey = base.publicKey; // Your address } baseKey() { // Unique ID for caching return `${this.network.id}-${this.base.getReceiveAddress()}`; } // Proxy example: getTokens caches the list async getTokens() { const key = this.baseKey(); return cache(key, CACHE_TYPES.TOKENS, () => this.base.getTokens()); } } ``` **Explanation:** Input to constructor: Base object (with seed-derived keys). Output: Wrapped instance. The `cache(key, type, fetchFn)` (from `src/utils/cache.js`) uses a Map for in-memory storage, expiring after \~5 minutes (tunable). For `getTokens()`, it fetches from RPC (e.g., Solana's `getTokenAccountsByOwner`), normalizes to `{ address, uiAmount, symbol }`, and stores. In our use case, this lists tokens for the [TokenList](https://docs.salmonwallet.io/src/features/TokenList/TokenList.js) component—call once, reuse everywhere. For transactions, like `createTransferTransaction()` in send flows (e.g., [TokenSendPage](https://docs.salmonwallet.io/src/pages/Token/TokenSendPage.js)), it builds a chain-specific TX (Solana instruction vs. Ethereum calldata) but returns a unified `{ txId, executableTx }` for signing. No caching here (one-time ops), but validation uses abstraction like `validateDestinationAccount(address)` to check if "abc123" is valid on the current chain. **Beginner tip:** Test by logging `activeBlockchainAccount.getBalance()` in a console—watch it fetch/caches across networks. Errors? Often network-specific; abstraction logs the base error for debugging. ## Wrapping Up: Your Universal Blockchain Adapter Great job! You've now grasped Blockchain Account Abstraction: a wrapper that unifies multi-chain ops into simple calls like `getBalance()`, with caching for speed—like a smart remote that works for any TV, powering real wallet features without chain-specific code. This ties into [AppContext](https://docs.salmonwallet.io/04_appcontext) for active accounts and [UI Component Library](https://docs.salmonwallet.io/02_ui_component_library) for displays, solving our balance-fetching use case effortlessly. Ready to connect external wallets? Next up: [Wallet Adapter System](https://docs.salmonwallet.io/08_wallet_adapter_system). --- # Chapter 8: Wallet Adapter System The wallet adapter system is Salmon’s bridge to external dApps: it handles connect, approve, sign, and message flows with user-controlled prompts. ## Goals - Use standard Solana adapter interfaces so dApps integrate without custom code. - Make approvals explicit with clear dApp identity (name, icon, domain). - Simulate and summarize transactions before signing. - Handle decline paths cleanly—no silent failures. ## Core capabilities - **Connect/Disconnect:** share public address after approval; revoke on request or inactivity. - **Sign Message:** show domain + reason; block blind signing. - **Sign Transaction(s):** simulate, display amounts/fees/programs; multi-instruction support. - **Eventing:** notify UI when connection state changes. ## Security expectations - Verify origin/domain matches the prompt. - Require fresh approval after lock/unlock or network changes. - Rate-limit signature requests; surface suspicious bursts. - Never expose mnemonics/keys—sign inside isolated context. ## UX patterns - Surface dApp metadata (favicon, verified domain, network). - Provide a concise diff: what’s being spent, destination, programs involved. - Offer “Reject” as a first-class action; don’t hide behind secondary UI. - Persist last connected dApp list for user review and revocation. ## Testing checklist - Connect from a sample dApp succeeds and shows correct address + network. - Declining connect/sign returns explicit errors to the dApp. - Simulation errors block signing and show the error reason. - Switching networks forces re-approval. ## Tips for maintainers - Keep adapter package versions current with Solana ecosystem changes. - Log connection attempts and outcomes (sanitized) to spot flaky integrations. - Add feature flags for experimental adapters without impacting stable flows. ## Key Concepts in the Wallet Adapter System Let's break this down like a security checkpoint at an event: first the entry scan (connection approval), then the pat-down for actions (signing transactions or messages), and finally the oversight (simulation for safety). Everything revolves around listening for requests from dApps and responding securely, using platform-specific bridges like NativeModules for mobile or Chrome APIs for extensions. ### 1. Connection Approval: The Initial Handshake When a dApp wants to connect, it sends a "connect" request with its origin (website URL) and metadata (name, icon). The adapter fetches this info (if needed) and shows a simple approval screen: "Allow NFT Marketplace to view your address?" Users tap approve to add it as a "trusted app," sharing only the public key. No private keys involved—it's read-only access. In our use case, this happens first: The dApp button triggers a popup or native screen in your wallet app. Input: dApp origin (e.g., "{rel=""nofollow""}"). Output: If approved, the dApp gets the user's Solana address (e.g., "abc123..."), enabling features like balance display. Analogy: Like friending someone on social media—they see your profile pic, but not your full history. Simplified flow in `AdapterDetail.js`: ```javascript // From src/pages/Adapter/components/AdapterDetail.js - Handling connect const connect = async () => { setConnected(true); // Mark as approved await addTrustedApp(origin, { name, icon }); // Save to trusted list (via AppContext) postMessage({ // Send back to dApp method: 'connected', params: { publicKey: activeBlockchainAccount.getReceiveAddress() } // Share address only }); }; ``` **What happens here?** Input: User taps "Approve" on the form. Output: Adds the dApp to trusted apps (stored securely via [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer)), sends a response message with the public address. If rejected, it sends an error. This keeps it lightweight—trusted apps remember approvals for future connects. ### 2. Transaction Signing: Securely Approving Actions Once connected, dApps request signs for transactions (e.g., "Sign this buy NFT transfer"). The adapter shows details like "Send 0.5 SOL to seller?" and, if approved, signs using the private key (derived from seed, never exposed) and returns the signature. Supports single or batch ("signAllTransactions") via unified methods. For our use case, after connecting, the dApp asks to sign a purchase. Input: Encoded transaction payload (e.g., base58 string from Solana). Output: Signed transaction (e.g., base58 signature) sent back, ready for the dApp to broadcast. Analogy: Like signing a check—you verify the amount, then authorize without handing over your checkbook. From `SignTransactionForm.js` (used in AdapterDetail): ```javascript // From src/pages/Adapter/components/SignTransactionForm.js - Signing a transaction const createSignature = () => { const secretKey = bs58.decode(activeBlockchainAccount.retrieveSecurePrivateKey()); // Get key safely return bs58.encode(nacl.sign.detached(payload, secretKey)); // Sign the payload }; const getMessage = () => ({ result: { signature: createSignature(), // The signed output publicKey: activeBlockchainAccount.publicKey.toBase58() }, id: request.id // Match the dApp's request }); ``` **What happens here?** Input: Payload from dApp request. Output: A response message with the signature (using NaCl for secure signing) and public key—no full private key shared. The form component shows a preview screen first, calling this only on approve. For batches, it loops over multiple payloads. ### 3. Message Signing and Simulation: Extra Safety Checks For non-transaction requests (e.g., "Sign this message for login"), it decodes and displays the text (e.g., "Verify ownership") for approval, then signs similarly. For transactions, it often simulates first (via an external simulation API) to preview effects (e.g., "Will receive 1 NFT, cost 0.5 SOL") and flag risks (e.g., high fees warning). In our use case, before signing a buy, simulation runs to show "Expected: +1 NFT, -0.5 SOL" on the approval screen. Input: Transaction payload. Output: Preview details (e.g., state changes like balance diffs) for user review. Analogy: Like a trial run of a recipe—see if it "burns" your balance before cooking. Simplified simulation display in `SimulatedTransactions.js`: ```javascript // From src/pages/Adapter/components/SimulatedTransactions.js - Showing simulation const StateChange = ({ humanReadableDiff, rawInfo: { data } }) => { const value = data?.diff?.digits ? /* Format number with sign and symbol */ `${data.diff.sign === 'PLUS' ? '+' : ''}${formatNumber(data.diff.digits, data.decimals)} ${data.symbol}` : ''; return ( {value} {humanReadableDiff} // e.g., "NFT received" ); }; ``` **What happens here?** Input: Simulation results (array of changes from dApp or service). Output: Rendered cards showing gains/losses (green for +, red for -). If warnings (e.g., "Critical: Drains balance"), an alert appears. Ties into [UI Component Library](https://docs.salmonwallet.io/02_ui_component_library) for consistent cards. For messages, it decodes to readable text (UTF-8 or hex). ### 4. Platform Bridges: Web vs. Native Handling The system adapts: On web/browser extensions, it uses `postMessage` for communication (e.g., popup windows). On mobile (React Native), it listens via NativeEventEmitter for requests from dApps. Both route to the same forms for approvals. ## Step-by-Step Walkthrough: Handling a dApp Connection and Transaction Using our central use case, here's how the adapter manages a full flow—like a receptionist handling a guest arrival and requests: 1. **dApp requests connect** → Adapter listens (via message listener or native emitter), fetches dApp metadata (name/icon), shows `ApproveConnectionForm`. 2. **User approves** → Adds to trusted apps in [AppContext](https://docs.salmonwallet.io/04_appcontext), sends public address back via `postMessage`. 3. **dApp sends transaction request** → Adapter queues it, simulates (if Solana), shows `SimulatedTransactions` with previews and fees. 4. **User reviews and approves** → Calls signing (e.g., `nacl.sign`), sends signed response; rejects send error. 5. **dApp broadcasts** → Transaction goes to blockchain (via [Blockchain Account Abstraction](https://docs.salmonwallet.io/07_blockchain_account_abstraction)); adapter closes popup or returns focus. For a visual of the connection approval: ```mermaid sequenceDiagram participant D as dApp participant A as Adapter (Your App) participant U as User participant C as AppContext D->>A: postMessage('connect', origin) A->>A: Fetch metadata (name/icon) A->>U: Show approval screen U->>A: Tap "Approve" A->>C: addTrustedApp(origin) C-->>A: Saved A->>D: postMessage('connected', publicKey) D-->>U: "Wallet connected!" ``` **Explanation:** dApp initiates via message; adapter shows UI for user okay, updates context for persistence, responds. Four steps—quick handshake, securing the session. ## Deeper Dive: Under the Hood in Adapter Components Internally, the system starts in `AdapterPage.js`, which loads on dApp trigger (e.g., via route navigation from [Route Navigation System](https://docs.salmonwallet.io/03_route_navigation_system)). It sets up the active network (e.g., Solana) and steps: Select (onboarding if needed) to Detail (handling requests). In `AdapterDetail.js` (web/extension version), it uses `useEffect` to listen for messages: ```javascript // From src/pages/Adapter/components/AdapterDetail.js - Listening for requests useEffect(() => { function messageHandler(e) { if (e.origin === origin && e.data.method === 'connect') { // Validate source setRequests(reqs => [...reqs, e.data]); // Queue the request } } window.addEventListener('message', messageHandler); // Hook into browser events return () => window.removeEventListener('message', messageHandler); }, [origin]); ``` **Explanation:** Input: Incoming postMessage from dApp. Output: Adds to requests queue if authorized (e.g., 'connect' or 'signTransaction'). Then, `useMemo` picks the first request and renders the right form (e.g., SignTransactionsForm). For simulation, it calls an external API async before showing previews—non-code: Fetch payload > simulate on testnet > parse changes > display. Errors (e.g., invalid method) respond with "Unsupported" immediately. For native (`AdapterDetail.native.js`), it swaps to React Native's NativeEventEmitter: ```javascript // From src/pages/Adapter/components/AdapterDetail.native.js - Native listener useEffect(() => { const emitter = new NativeEventEmitter(AdapterModule); // Bridge to native code const listener = emitter.addListener('onRequest', setRequest); // Set state on event return () => listener.remove(); // Cleanup }, []); ``` **Explanation:** Input: Native module events (e.g., from iOS/Android bridge). Output: Updates `request` state, triggering the same forms. `AdapterModule.js` exposes native functions (e.g., completeWithDecline for rejects). Trusted apps store via [AppContext](https://docs.salmonwallet.io/04_appcontext) hooks, persisting in [Storage and Persistence Layer](https://docs.salmonwallet.io/05_storage_and_persistence_layer). Signing uses `salmon-wallet-adapter` utils for secure key retrieval (encrypted in storage). Simulations decode payloads (bs58 for Solana) and format diffs (e.g., +1.5 SOL as green text with translations from [Translation System](https://docs.salmonwallet.io/06_translation_system)). For failures (e.g., simulation errors), shows `FailedTransactions` with retry option. **Beginner tip:** Test by opening a dApp in dev mode—watch console for messages. If no requests, check origin matching. ## Wrapping Up: Your Secure Bridge to the dApp World Congrats! You've now explored the Wallet Adapter System: a handshake protocol for safe dApp connections, transaction simulations, and signings, using approval forms and platform bridges to keep private keys hidden—like a vigilant doorman ensuring only trusted guests enter. This builds on abstractions from earlier chapters to enable real-world integrations, from simple connects to complex NFT buys, all user-controlled. With this, your `salmonrepo` wallet is ready for the blockchain ecosystem—secure, intuitive, and beginner-proof! --- # Salmon Wallet Docs ::u-page-hero --- ui: container: py-10 sm:py-14 align: center --- #title [![Salmon icon](https://docs.salmonwallet.io/salmon-logo-docs.png){.w-20.h-20.sm:w-24.sm:h-24} [Open Wallet Infrastructure]{.text-balance}]{.flex.flex-col.items-center.gap-5.text-center} #description [In Salmon we trust. Salmon makes crypto easy and safe to store, buy, send, receive, and swap tokens and NFTs—without ever giving up your keys.]{.block.text-center.text-lg.text-gray-600.max-w-3xl.mx-auto} #links :::div{.flex.flex-wrap.justify-center.gap-4.mt-4} ::::u-button --- color: neutral size: xl to: https://docs.salmonwallet.io/getting-started/introduction trailing-icon: i-lucide-arrow-right --- Get started :::: ::::u-button --- color: neutral icon: i-lucide-globe-2 size: xl to: https://salmonwallet.io variant: outline --- Visit salmonwallet.io :::: ::::u-button --- color: neutral icon: simple-icons-medium size: xl to: https://medium.com/@salmonwallet variant: ghost --- Read the blog :::: ::::u-button --- color: neutral icon: simple-icons-x size: xl to: https://twitter.com/salmonwallet variant: ghost --- Follow us on X :::: ::: ::